Revert scheduling back to use real-time processes
[sahlberg/ctdb.git] / tools / onnode
index c60127ce090c81c163ae8dd50ae74c24a0ad8b84..fa61b47e5bd6f50c7a0f55bd142ebf3aacd917bc 100755 (executable)
@@ -30,14 +30,20 @@ usage ()
     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
 
@@ -54,13 +60,17 @@ current=false
 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
 
@@ -69,6 +79,9 @@ parse_options ()
     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 ;;
@@ -83,20 +96,19 @@ parse_options ()
     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 ()
@@ -107,17 +119,18 @@ 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"
@@ -126,17 +139,22 @@ get_nodes_with_status ()
     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
 
@@ -147,9 +165,9 @@ get_nodes_with_status ()
        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
@@ -158,10 +176,77 @@ get_nodes_with_status ()
     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
@@ -169,37 +254,79 @@ get_nodes ()
        [ $? != 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
 
 ######################################################################
@@ -223,21 +350,16 @@ trap 'kill -TERM $pids 2>/dev/null' INT TERM
 # 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