# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
-prog=$(basename $0)
+prog=$(basename "$0")
usage ()
{
Usage: onnode [OPTION] ... <NODES> <COMMAND> ...
options:
-c Run in current working directory on specified nodes.
+ -f Specify nodes file, overrides CTDB_NODES_FILE.
+ -i Keep standard input open - the default is to close it.
+ -n Allow nodes to be specified by name.
-o <prefix> Save standard output from each node to file <prefix>.<ip>
-p Run command in parallel on specified nodes.
+ -P Push given files to nodes instead of running commands.
-q Do not print node addresses (overrides -v).
- -n Allow nodes to be specified by name.
- -f Specify nodes file, overrides CTDB_NODES_FILE.
-v Print node address even for a single node.
- <NODES> "all", "any", "ok" (or "healthy"), "con" (or "connected"),
- "rm" (or "recmaster"), "lvs" (or "lvsmaster"),
- "natgw" (or "natgwlist"); or
+ <NODES> "all", "any", "ok" (or "healthy"), "con" (or "connected") ; or
a node number (0 base); or
a hostname (if -n is specified); or
list (comma separated) of <NODES>; or
quiet=false
prefix=""
names_ok=false
+push=false
+stdin=false
-ctdb_base="${CTDB_BASE:-/etc/ctdb}"
+if [ -z "$CTDB_BASE" ] ; then
+ CTDB_BASE="/usr/local/etc/ctdb"
+fi
+
+. "${CTDB_BASE}/functions"
+loadconfig "ctdb"
parse_options ()
{
# options ot onnode.
local temp
# Not on the previous line - local returns 0!
- temp=$(POSIXLY_CORRECT=1 getopt -n "$prog" -o "cf:hno:pqv" -l help -- "$@")
+ temp=$(POSIXLY_CORRECT=1 getopt -n "$prog" -o "cf:hno:pqvPi" -l help -- "$@")
[ $? != 0 ] && usage
-p) parallel=true ; shift ;;
-q) quiet=true ; shift ;;
-v) verbose=true ; shift ;;
+ -P) push=true ; shift ;;
+ -i) stdin=true ; shift ;;
--) shift ; break ;;
-h|--help|*) usage ;; # Shouldn't happen, so this is reasonable.
esac
[ $# -lt 2 ] && usage
nodespec="$1" ; shift
- command="$@"
+ command="$*"
}
echo_nth ()
{
local n="$1" ; shift
- shift $n
+ shift "$n"
local node="$1"
if [ -n "$node" -a "$node" != "#DEAD" ] ; then
- echo $node
+ echo "$node"
else
echo "${prog}: \"node ${n}\" does not exist" >&2
exit 1
for i in $1 ; do
case "$i" in
*-*) seq "${i%-*}" "${i#*-}" 2>/dev/null || invalid_nodespec ;;
- # Separate lines for readability.
all|any|ok|healthy|con|connected) echo "$i" ;;
- rm|recmaster|lvs|lvsmaster|natgw|natgwlist) echo "$i" ;;
*)
- [ $i -gt -1 ] 2>/dev/null || $names_ok || invalid_nodespec
- echo $i
+ [ "$i" -gt -1 ] 2>/dev/null || $names_ok || invalid_nodespec
+ echo "$i"
esac
done
)
local status="$2"
if [ -z "$ctdb_status_output" ] ; then
- ctdb_status_output=$(ctdb -Y status 2>&1)
+ ctdb_status_output=$(ctdb -X status 2>&1)
if [ $? -ne 0 ] ; then
echo "${prog}: unable to get status of CTDB nodes" >&2
echo "$ctdb_status_output" >&2
(
local i
- IFS="${IFS}:"
+ IFS="${IFS}|"
while IFS="" read i ; do
+ # Intentional word splitting
+ # shellcheck disable=SC2086
set -- $i # split line on colons
shift # line starts with : so 1st field is empty
local pnn="$1" ; shift
- local ip="$1" ; shift
+ shift # ignore IP address but need status bits below
case "$status" in
healthy)
- # If any bit is not 0, don't match this address.
+ # If any bit is 1, don't match this address.
local s
for s ; do
- [ "$s" = "0" ] || continue 2
+ [ "$s" != "1" ] || continue 2
done
;;
connected)
invalid_nodespec
esac
+ # Intentional multi-word expansion
+ # shellcheck disable=SC2086
echo_nth "$pnn" $all_nodes
done <<<"$ctdb_status_output"
)
}
-ctdb_props="" # cache
-get_node_with_property ()
-{
- local all_nodes="$1"
- local prop="$2"
-
- local prop_node=""
- if [ "${ctdb_props##:${prop}:}" = "$ctdb_props" ] ; then
- # Not in cache.
- prop_node=$(ctdb "$prop" -Y 2>/dev/null)
- if [ $? -eq 0 ] ; then
- if [ "$prop" = "natgwlist" ] ; then
- prop_node="${prop_node%% *}" # 1st word
- if [ "$prop_node" = "-1" ] ; then
- # This works around natgwlist returning 0 even
- # when there's no natgw.
- prop_node=""
- fi
- else
- # We only want the first line.
- local nl="
-"
- prop_node="${prop_node%%${nl}*}"
- fi
- else
- prop_node=""
- fi
-
- if [ -n "$prop_node" ] ; then
- # Add to cache.
- ctdb_props="${ctdb_props}${ctdb_props:+ }:${prop}:${prop_node}"
- fi
- else
- # Get from cache.
- prop_node="${ctdb_props##:${prop}:}"
- prop_node="${prop_node%% *}"
- fi
-
- if [ -n "$prop_node" ] ; then
- echo_nth "$prop_node" $all_nodes
- else
- echo "${prog}: No ${prop} available" >&2
- exit 1
- fi
-}
-
get_any_available_node ()
{
local all_nodes="$1"
# We do a recursive onnode to find which nodes are up and running.
- local out=$($0 -pq all ctdb pnn 2>&1)
- local line
+ local out line
+ out=$("$0" -pq all ctdb pnn 2>&1)
while read line ; do
local pnn="${line#PNN:}"
if [ "$pnn" != "$line" ] ; then
+ # Intentional multi-word expansion
+ # shellcheck disable=SC2086
echo_nth "$pnn" $all_nodes
return 0
fi
if [ -n "$CTDB_NODES_SOCKETS" ] ; then
all_nodes="$CTDB_NODES_SOCKETS"
else
- local f="${ctdb_base}/nodes"
+ local f="${CTDB_BASE}/nodes"
if [ -n "$CTDB_NODES_FILE" ] ; then
f="$CTDB_NODES_FILE"
if [ ! -e "$f" -a "${f#/}" = "$f" ] ; then
- # $f is relative, try in $ctdb_base
- f="${ctdb_base}/${f}"
+ # $f is relative, try in $CTDB_BASE
+ f="${CTDB_BASE}/${f}"
fi
+ elif [ -n "$CTDB_NODES" ] ; then
+ f="$CTDB_NODES"
fi
if [ ! -r "$f" ] ; then
con|connected)
get_nodes_with_status "$all_nodes" "connected" || exit 1
;;
- rm|recmaster)
- get_node_with_property "$all_nodes" "recmaster" || exit 1
- ;;
- lvs|lvsmaster)
- get_node_with_property "$all_nodes" "lvsmaster" || exit 1
- ;;
- natgw|natgwlist)
- get_node_with_property "$all_nodes" "natgwlist" || exit 1
- ;;
[0-9]|[0-9][0-9]|[0-9][0-9][0-9])
- echo_nth $n $all_nodes
+ # Intentional multi-word expansion
+ # shellcheck disable=SC2086
+ echo_nth "$n" $all_nodes
;;
*)
$names_ok || invalid_nodespec
- echo $n
+ echo "$n"
+ esac
+ done
+}
+
+push()
+{
+ local host="$1"
+ local files="$2"
+
+ local f
+ for f in $files ; do
+ $verbose && echo "Pushing $f"
+ case "$f" in
+ /*) rsync "$f" "[${host}]:${f}" ;;
+ *) rsync "${PWD}/${f}" "[${host}]:${PWD}/${f}" ;;
esac
done
}
parse_options "$@"
-$current && command="cd $PWD && $command"
-
ssh_opts=
-if [ -n "$CTDB_NODES_SOCKETS" ] ; then
- SSH=fakessh
+if $push ; then
+ SSH=push
EXTRA_SSH_OPTS=""
-else
- # Could "2>/dev/null || true" but want to see errors from typos in file.
- [ -r "${ctdb_base}/onnode.conf" ] && . "${ctdb_base}/onnode.conf"
- [ -n "$SSH" ] || SSH=ssh
- if [ "$SSH" = "ssh" ] ; then
- ssh_opts="-n"
- else
- : # rsh? All bets are off!
+else
+ $current && command="cd $PWD && $command"
+
+ if [ -n "$CTDB_NODES_SOCKETS" ] ; then
+ SSH=fakessh
+ EXTRA_SSH_OPTS=""
+ else
+ # Could "2>/dev/null || true" but want to see errors from typos in file.
+ [ -r "${CTDB_BASE}/onnode.conf" ] && . "${CTDB_BASE}/onnode.conf"
+ [ -n "$SSH" ] || SSH=ssh
+ if [ "$SSH" = "ssh" ] ; then
+ if $parallel || ! $stdin ; then
+ ssh_opts="-n"
+ fi
+ else
+ : # rsh? All bets are off!
+ fi
fi
fi
fi
pids=""
+# Intentional multi-word expansion
+# shellcheck disable=SC2086
trap 'kill -TERM $pids 2>/dev/null' INT TERM
# There's a small race here where the kill can fail if no processes
# have been added to $pids and the script is interrupted. However,
for n in $nodes ; do
set -o pipefail 2>/dev/null
if $parallel ; then
- { exec 3>&1 ; { $SSH $ssh_opts $EXTRA_SSH_OPTS $n "$command" | stdout_filter >&3 ; } 2>&1 | stderr_filter ; } &
+ { exec 3>&1 ; { $SSH $ssh_opts $EXTRA_SSH_OPTS "$n" "$command" | stdout_filter >&3 ; } 2>&1 | stderr_filter ; } &
pids="${pids} $!"
else
if $verbose ; then
echo >&2 ; echo ">> NODE: $n <<" >&2
fi
- { exec 3>&1 ; { $SSH $ssh_opts $EXTRA_SSH_OPTS $n "$command" | stdout_filter >&3 ; } 2>&1 | stderr_filter ; }
+ { exec 3>&1 ; { $SSH $ssh_opts $EXTRA_SSH_OPTS "$n" "$command" | stdout_filter >&3 ; } 2>&1 | stderr_filter ; }
[ $? = 0 ] || retcode=$?
fi
done
$parallel && {
for p in $pids; do
- wait $p
+ wait "$p"
[ $? = 0 ] || retcode=$?
done
}