cat >&2 <<EOF
Usage: onnode [OPTION] ... <NODES> <COMMAND> ...
options:
- -c Run in current working directory on specified nodes.
- -p Run command in parallel on specified nodes.
- -q Do not print node addresses (overrides -v).
- -v Print node address even for a single node.
- <NODES> "all", "healthy", "connected";
- or a node number (0 base); or
- list (comma separated) of <NODES>; or
- range (hyphen separated) of node numbers.
+ -c Run in current working directory on specified nodes.
+ -o <prefix> Save standard output from each node to file <prefix>.<ip>
+ -p Run command in parallel on specified nodes.
+ -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
+ a node number (0 base); or
+ a hostname (if -n is specified); or
+ list (comma separated) of <NODES>; or
+ range (hyphen separated) of node numbers.
EOF
exit 1
parallel=false
verbose=false
quiet=false
+prefix=""
+names_ok=false
+
+ctdb_base="${CTDB_BASE:-/etc/ctdb}"
parse_options ()
{
# $POSIXLY_CORRECT means that the command passed to onnode can
# take options and getopt won't reorder things to make them
# options ot onnode.
- local temp=$(POSIXLY_CORRECT=1 getopt -n "$prog" -o "chpqv" -l help -- "$@")
+ local temp=$(POSIXLY_CORRECT=1 getopt -n "$prog" -o "cf:hno:pqv" -l help -- "$@")
[ $? != 0 ] && usage
while true ; do
case "$1" in
-c) current=true ; shift ;;
+ -f) CTDB_NODES_FILE="$2" ; shift 2 ;;
+ -n) names_ok=true ; shift ;;
+ -o) prefix="$2" ; shift 2 ;;
-p) parallel=true ; shift ;;
-q) quiet=true ; shift ;;
-v) verbose=true ; shift ;;
command="$@"
}
-# Can probably be avoided if we use bash?
-get_nth ()
+echo_nth ()
{
local n="$1" ; shift
- local c=0
- local j
- for j ; do
- if [ $n -eq $c ] ; then
- echo $j
- break
- fi
- c=$(($c + 1))
- done
+ shift $n
+ local node="$1"
+
+ if [ -n "$node" -a "$node" != "#DEAD" ] ; then
+ echo $node
+ else
+ echo "${prog}: \"node ${n}\" does not exist" >&2
+ exit 1
+ fi
}
parse_nodespec ()
for i in $1 ; do
case "$i" in
*-*) seq "${i%-*}" "${i#*-}" 2>/dev/null || invalid_nodespec ;;
- all|healthy|connected) echo "$i" ;;
+ # 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 || invalid_nodespec
+ [ $i -gt -1 ] 2>/dev/null || $names_ok || invalid_nodespec
echo $i
esac
done
)
}
-# Cache
-ctdb_status_output=""
+ctdb_status_output="" # cache
get_nodes_with_status ()
{
local all_nodes="$1"
local bits
case "$status" in
healthy)
- bits="0:0:0:0"
+ bits="0:0:0:0:0"
;;
connected)
- bits="0:[0-1]:[0-1]:[0-1]"
+ bits="0:[0-1]:[0-1]:[0-1]:[0-1]"
;;
*)
invalid_nodespec
esac
if [ -z "$ctdb_status_output" ] ; then
- ctdb_status_output=$(ctdb -Y status)
+ # FIXME: need to do something if $CTDB_NODES_SOCKETS is set.
+ ctdb_status_output=$(ctdb -Y status 2>/dev/null)
+ if [ $? -ne 0 ] ; then
+ echo "${prog}: unable to get status of CTDB nodes" >&2
+ exit 1
+ fi
ctdb_status_output="${ctdb_status_output#* }"
fi
local t="${i%:${bits}:}"
if [ "$t" != "$i" ] ; then
# Succeeded. Get address. NOTE: this is an optimisation.
- # It might be better to get the node number and then use
- # get_nth() to get the address. This would make things
- # more consistent if /etc/ctdb/nodes actually contained
+ # It might be better to get the node number and then get
+ # the nth node to get the address. This would make things
+ # more consistent if $ctdb_base/nodes actually contained
# hostnames.
nodes="${nodes} ${t##*:}"
fi
echo $nodes
}
+ctdb_props="" # cache
+get_node_with_property ()
+{
+ local all_nodes="$1"
+ local prop="$2"
+
+ local prop_node=""
+ if [ "${ctdb_props##:${prop}:}" = "$ctdb_props" ] ; then
+ prop_node=$(ctdb "$prop" -Y 2>/dev/null)
+ # We only want the first line.
+ local nl="
+"
+ prop_node="${prop_node%%${nl}*}"
+ if [ $? -eq 0 ] ; then
+ ctdb_props="${ctdb_props}${ctdb_props:+ }:${prop}:${prop_node}"
+ else
+ prop_node=""
+ fi
+ else
+ 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
+ while read line ; do
+ local pnn="${line#PNN:}"
+ if [ "$pnn" != "$line" ] ; then
+ echo_nth "$pnn" $all_nodes
+ return 0
+ fi
+ # Else must be an error message from a down node.
+ done <<<"$out"
+ return 1
+}
+
get_nodes ()
{
- [ -f "$CTDB_NODES_FILE" ] || CTDB_NODES_FILE=/etc/ctdb/nodes
- local all_nodes=$(egrep '^[[:alnum:]]' $CTDB_NODES_FILE)
+ local all_nodes
+
+ if [ -n "$CTDB_NODES_SOCKETS" ] ; then
+ all_nodes="$CTDB_NODES_SOCKETS"
+ else
+ 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}"
+ fi
+ fi
+
+ if [ ! -r "$f" ] ; then
+ echo "${prog}: unable to open nodes file \"${f}\"" >&2
+ exit 1
+ fi
+
+ all_nodes=$(sed -e 's@#.*@@g' -e 's@ *@@g' -e 's@^$@#DEAD@' "$f")
+ fi
local nodes=""
local n
[ $? != 0 ] && exit 1 # Required to catch exit in above subshell.
case "$n" in
all)
- echo $all_nodes ;;
- healthy|connected)
- get_nodes_with_status "$all_nodes" "$n"
+ echo "${all_nodes//#DEAD/}"
+ ;;
+ any)
+ get_any_available_node "$all_nodes" || exit 1
+ ;;
+ ok|healthy)
+ get_nodes_with_status "$all_nodes" "healthy" || exit 1
+ ;;
+ 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
;;
*)
- local node=$(get_nth $n $all_nodes)
- if [ -n "$node" ] ; then
- echo $node
- else
- echo "${prog}: \"node ${n}\" does not exist" >&2
- exit 1
- fi
+ $names_ok || invalid_nodespec
+ echo $n
esac
-
done
}
+fakessh ()
+{
+ CTDB_SOCKET="$1" sh -c "$2" 3>/dev/null
+}
+
+stdout_filter ()
+{
+ if [ -n "$prefix" ] ; then
+ cat >"${prefix}.${n//\//_}"
+ elif $verbose && $parallel ; then
+ sed -e "s@^@[$n] @"
+ else
+ cat
+ fi
+}
+
+stderr_filter ()
+{
+ if $verbose && $parallel ; then
+ sed -e "s@^@[$n] @"
+ else
+ cat
+ fi
+}
+
######################################################################
parse_options "$@"
$current && command="cd $PWD && $command"
-SSH_OPTS=
-# Could "2>/dev/null || true" but want to see errors from typos in file.
-[ -r /etc/ctdb/onnode.conf ] && . /etc/ctdb/onnode.conf
-[ -n "$SSH" ] || SSH=ssh
-if [ "$SSH" = "ssh" ] ; then
- ssh_opts="-n"
-else
- : # rsh? All bets are off!
+ssh_opts=
+if [ -n "$CTDB_NODES_SOCKETS" ] ; then
+ SSH=fakessh
+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!
+ fi
fi
######################################################################
# the part of the window where it matter is very small.
retcode=0
for n in $nodes ; do
+ set -o pipefail 2>/dev/null
if $parallel ; then
- if $verbose ; then
- # pipefail is a bashism - is there some way to do this with plain sh?
- set -o pipefail 2>/dev/null
- ($SSH $ssh_opts $EXTRA_SSH_OPTS $n "$command" 2>&1 | sed -e "s@^@[$n] @" )&
- else
- $SSH $ssh_opts $EXTRA_SSH_OPTS $n "$command" &
- fi
+ { 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
- $SSH $ssh_opts $EXTRA_SSH_OPTS $n "$command"
+ { exec 3>&1 ; { $SSH $ssh_opts $EXTRA_SSH_OPTS $n "$command" | stdout_filter >&3 ; } 2>&1 | stderr_filter ; }
[ $? = 0 ] || retcode=$?
fi
done