Merge commit 'origin/master' into for-ronnie
[sfrench/samba-autobuild/.git] / ctdb / tools / onnode
1 #!/bin/bash
2
3 # Run commands on CTDB nodes.
4
5 # See http://ctdb.samba.org/ for more information about CTDB.
6
7 # Copyright (C) Martin Schwenke  2008
8
9 # Based on an earlier script by Andrew Tridgell and Ronnie Sahlberg.
10
11 # Copyright (C) Andrew Tridgell  2007
12
13 # This program is free software; you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation; either version 3 of the License, or
16 # (at your option) any later version.
17    
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 # GNU General Public License for more details.
22    
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, see <http://www.gnu.org/licenses/>.
25
26 prog=$(basename $0)
27
28 usage ()
29 {
30     cat >&2 <<EOF
31 Usage: onnode [OPTION] ... <NODES> <COMMAND> ...
32   options:
33     -c         Run in current working directory on specified nodes.
34     -p         Run command in parallel on specified nodes.
35     -q         Do not print node addresses (overrides -v).
36     -v         Print node address even for a single node.
37   <NODES>      "all" or a node number (0 base); or
38                list (comma separated) of <NODES>; or
39                range (hyphen separated) of node numbers.
40 EOF
41     exit 1
42
43 }
44
45 invalid_nodespec ()
46 {
47     echo "Invalid <nodespec>" >&2 ; echo >&2
48     usage
49 }
50
51 # Defaults.
52 current=false
53 parallel=false
54 verbose=false
55 quiet=false
56
57 parse_options ()
58 {
59     # $POSIXLY_CORRECT means that the command passed to onnode can
60     # take options and getopt won't reorder things to make them
61     # options ot onnode.
62     temp=$(POSIXLY_CORRECT=1 getopt -n "$prog" -o "chpqv" -l help -- "$@")
63
64     [ $? != 0 ] && usage
65
66     eval set -- "$temp"
67
68     while true ; do
69         case "$1" in
70             -c) current=true ; shift ;;
71             -p) parallel=true ; shift ;;
72             -q) quiet=true ; shift ;;
73             -v) verbose=true ; shift ;;
74             --) shift ; break ;;
75             -h|--help|*) usage ;; # Shouldn't happen, so this is reasonable.
76         esac
77     done
78
79     [ $# -lt 2 ] && usage
80
81     nodespec="$1" ; shift
82     command="$@"
83 }
84
85 # Can probably be avoided if we use bash?
86 get_nth ()
87 {
88     n="$1" ; shift
89
90     c=0
91     for j ; do
92         if [ $n -eq $c ] ; then
93             echo $j
94             break
95         fi
96         c=$(($c + 1))
97     done
98 }
99
100 parse_nodespec ()
101 {
102     # Subshell avoids hacks to restore $IFS.
103     (
104         IFS=","
105         for i in $1 ; do
106             case "$i" in
107                 *-*) seq "${i%-*}" "${i#*-}" 2>/dev/null || invalid_nodespec ;;
108                 all) echo all ;;
109                 *)
110                     [ $i -gt -1 ] 2>/dev/null || invalid_nodespec
111                     echo $i
112             esac
113         done
114     )
115 }
116
117 get_nodes ()
118 {
119     [ -f "$CTDB_NODES_FILE" ] || CTDB_NODES_FILE=/etc/ctdb/nodes
120     all_nodes=$(egrep '^[[:alnum:]]' $CTDB_NODES_FILE)
121
122     nodes=""
123     for n in $(parse_nodespec "$1") ; do
124         [ $? != 0 ] && exit 1  # Required to catch exit in above subshell.
125         case "$n" in
126             all) echo $all_nodes ;;
127             *)  node=$(get_nth $n $all_nodes)
128                 if [ -n "$node" ] ; then
129                     echo $node
130                 else
131                     echo "${prog}: \"node ${n}\" does not exist" >&2
132                     exit 1
133                 fi
134         esac
135         
136     done
137 }
138
139 ######################################################################
140
141 parse_options "$@"
142
143 $current && command="cd $PWD && $command"
144
145 SSH_OPTS=
146 # Could "2>/dev/null || true" but want to see errors from typos in file.
147 [ -r /etc/ctdb/onnode.conf ] && . /etc/ctdb/onnode.conf
148 [ -n "$SSH" ] || SSH=ssh
149 if [ "$SSH" = "ssh" ] ; then
150     ssh_opts="-n"
151 else
152     : # rsh? All bets are off!
153 fi
154
155 ######################################################################
156
157 nodes=$(get_nodes "$nodespec")
158 [ $? != 0 ] && exit 1   # Required to catch exit in above subshell.
159
160 if $quiet ; then
161     verbose=false
162 else
163     # If $nodes contains a space or a newline then assume multiple nodes.
164     nl="
165 "
166     [ "$nodes" != "${nodes%[ ${nl}]*}" ] && verbose=true
167 fi
168
169 pids=""
170 trap 'kill -TERM $pids 2>/dev/null' INT TERM
171 # There's a small race here where the kill can fail if no processes
172 # have been added to $pids and the script is interrupted.  However,
173 # the part of the window where it matter is very small.
174 retcode=0
175 for n in $nodes ; do
176     if $parallel ; then
177         if $verbose ; then
178             # pipefail is a bashism - is there some way to do this with plain sh?
179             set -o pipefail 2>/dev/null
180             ($SSH $ssh_opts $EXTRA_SSH_OPTS $n "$command" 2>&1 | sed -e "s@^@[$n] @" )&
181         else
182             $SSH $ssh_opts $EXTRA_SSH_OPTS $n "$command" &
183         fi
184         pids="${pids} $!"
185     else
186         if $verbose ; then
187             echo >&2 ; echo ">> NODE: $n <<" >&2
188         fi
189
190         $SSH $ssh_opts $EXTRA_SSH_OPTS $n "$command" 
191         [ $? = 0 ] || retcode=$?
192     fi
193 done
194
195 $parallel && {
196     for p in $pids; do
197         wait $p
198         [ $? = 0 ] || retcode=$?
199     done
200 }
201
202 exit $retcode
203