2008-07-14 03:19:22 +04:00
#!/bin/bash
2008-07-09 08:18:15 +04:00
# Run commands on CTDB nodes.
# See http://ctdb.samba.org/ for more information about CTDB.
# Copyright (C) Martin Schwenke 2008
# Based on an earlier script by Andrew Tridgell and Ronnie Sahlberg.
2008-07-10 08:19:52 +04:00
# Copyright (C) Andrew Tridgell 2007
2008-07-09 08:18:15 +04:00
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
2016-07-06 10:31:51 +03:00
prog=$(basename "$0")
2008-07-09 08:18:15 +04:00
usage ()
{
cat >&2 <<EOF
2008-07-10 08:19:52 +04:00
Usage: onnode [OPTION] ... <NODES> <COMMAND> ...
options:
2009-05-06 07:17:34 +04:00
-c Run in current working directory on specified nodes.
2013-11-13 07:04:17 +04:00
-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.
2009-05-06 07:17:34 +04:00
-o <prefix> Save standard output from each node to file <prefix>.<ip>
-p Run command in parallel on specified nodes.
2013-11-13 07:04:17 +04:00
-P Push given files to nodes instead of running commands.
2009-05-06 07:17:34 +04:00
-q Do not print node addresses (overrides -v).
-v Print node address even for a single node.
2016-04-18 09:59:53 +03:00
<NODES> "all", "any", "ok" (or "healthy"), "con" (or "connected") ; or
2009-10-14 06:44:57 +04:00
a node number (0 base); or
a hostname (if -n is specified); or
2009-05-06 07:17:34 +04:00
list (comma separated) of <NODES>; or
range (hyphen separated) of node numbers.
2008-07-09 08:18:15 +04:00
EOF
exit 1
}
invalid_nodespec ()
{
echo "Invalid <nodespec>" >&2 ; echo >&2
usage
}
# Defaults.
current=false
parallel=false
verbose=false
quiet=false
2009-05-06 07:17:34 +04:00
prefix=""
2009-10-14 06:44:57 +04:00
names_ok=false
2012-07-17 10:45:55 +04:00
push=false
2013-11-13 07:04:17 +04:00
stdin=false
2008-07-09 08:18:15 +04:00
2015-08-17 13:47:58 +03:00
if [ -z "$CTDB_BASE" ] ; then
CTDB_BASE="/usr/local/etc/ctdb"
fi
. "${CTDB_BASE}/functions"
loadconfig "ctdb"
2010-01-21 05:16:18 +03:00
2008-07-09 08:18:15 +04:00
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.
2011-05-17 07:25:08 +04:00
local temp
# Not on the previous line - local returns 0!
2013-11-13 07:04:17 +04:00
temp=$(POSIXLY_CORRECT=1 getopt -n "$prog" -o "cf:hno:pqvPi" -l help -- "$@")
2008-07-09 08:18:15 +04:00
[ $? != 0 ] && usage
eval set -- "$temp"
while true ; do
case "$1" in
-c) current=true ; shift ;;
2009-10-14 06:44:57 +04:00
-f) CTDB_NODES_FILE="$2" ; shift 2 ;;
-n) names_ok=true ; shift ;;
2009-05-06 07:17:34 +04:00
-o) prefix="$2" ; shift 2 ;;
2008-07-10 08:19:52 +04:00
-p) parallel=true ; shift ;;
2008-07-09 08:18:15 +04:00
-q) quiet=true ; shift ;;
-v) verbose=true ; shift ;;
2012-07-17 10:45:55 +04:00
-P) push=true ; shift ;;
2013-11-13 07:04:17 +04:00
-i) stdin=true ; shift ;;
2008-07-09 08:18:15 +04:00
--) shift ; break ;;
2008-07-10 08:19:52 +04:00
-h|--help|*) usage ;; # Shouldn't happen, so this is reasonable.
2008-07-09 08:18:15 +04:00
esac
done
[ $# -lt 2 ] && usage
nodespec="$1" ; shift
2016-07-14 05:28:17 +03:00
command="$*"
2008-07-09 08:18:15 +04:00
}
2009-05-11 07:39:31 +04:00
echo_nth ()
2008-07-09 08:18:15 +04:00
{
2008-09-12 05:22:50 +04:00
local n="$1" ; shift
2008-07-09 08:18:15 +04:00
2016-07-06 10:31:51 +03:00
shift "$n"
2009-05-11 07:39:31 +04:00
local node="$1"
2008-07-09 08:18:15 +04:00
2009-06-02 09:03:44 +04:00
if [ -n "$node" -a "$node" != "#DEAD" ] ; then
2016-07-06 10:31:51 +03:00
echo "$node"
2008-09-12 10:55:18 +04:00
else
echo "${prog}: \"node ${n}\" does not exist" >&2
exit 1
fi
}
2008-07-09 08:18:15 +04:00
parse_nodespec ()
{
# Subshell avoids hacks to restore $IFS.
(
IFS=","
for i in $1 ; do
case "$i" in
*-*) seq "${i%-*}" "${i#*-}" 2>/dev/null || invalid_nodespec ;;
2009-09-15 13:33:35 +04:00
all|any|ok|healthy|con|connected) echo "$i" ;;
2008-07-09 08:18:15 +04:00
*)
2016-07-06 10:31:51 +03:00
[ "$i" -gt -1 ] 2>/dev/null || $names_ok || invalid_nodespec
echo "$i"
2008-07-09 08:18:15 +04:00
esac
done
)
}
2009-05-11 07:39:31 +04:00
ctdb_status_output="" # cache
2008-09-12 05:22:50 +04:00
get_nodes_with_status ()
{
local all_nodes="$1"
local status="$2"
if [ -z "$ctdb_status_output" ] ; then
2014-11-20 06:39:59 +03:00
ctdb_status_output=$(ctdb -X status 2>&1)
2008-09-12 10:55:18 +04:00
if [ $? -ne 0 ] ; then
echo "${prog}: unable to get status of CTDB nodes" >&2
2011-05-23 09:24:52 +04:00
echo "$ctdb_status_output" >&2
2008-09-12 10:55:18 +04:00
exit 1
fi
2011-05-23 09:24:52 +04:00
local nl="
"
ctdb_status_output="${ctdb_status_output#*${nl}}"
2008-09-12 05:22:50 +04:00
fi
2011-05-17 08:24:30 +04:00
(
local i
2014-11-20 06:39:59 +03:00
IFS="${IFS}|"
2011-05-23 09:24:52 +04:00
while IFS="" read i ; do
2011-05-17 08:24:30 +04:00
2016-07-06 10:31:51 +03:00
# Intentional word splitting
# shellcheck disable=SC2086
2011-05-23 09:24:52 +04:00
set -- $i # split line on colons
shift # line starts with : so 1st field is empty
2011-05-17 08:24:30 +04:00
local pnn="$1" ; shift
2016-07-06 10:16:44 +03:00
shift # ignore IP address but need status bits below
2011-05-17 08:24:30 +04:00
case "$status" in
healthy)
2013-10-24 07:15:53 +04:00
# If any bit is 1, don't match this address.
2011-05-17 08:24:30 +04:00
local s
for s ; do
2013-10-24 07:15:53 +04:00
[ "$s" != "1" ] || continue 2
2011-05-17 08:24:30 +04:00
done
;;
connected)
# If disconnected bit is not 0, don't match this address.
[ "$1" = "0" ] || continue
;;
*)
invalid_nodespec
esac
2011-05-23 09:24:52 +04:00
2016-07-06 10:31:51 +03:00
# Intentional multi-word expansion
# shellcheck disable=SC2086
2011-05-17 08:24:30 +04:00
echo_nth "$pnn" $all_nodes
2011-05-23 09:24:52 +04:00
done <<<"$ctdb_status_output"
2011-05-17 08:24:30 +04:00
)
2008-09-12 05:22:50 +04:00
}
2009-09-15 13:33:35 +04:00
get_any_available_node ()
{
local all_nodes="$1"
# We do a recursive onnode to find which nodes are up and running.
2016-07-14 05:27:05 +03:00
local out line
out=$("$0" -pq all ctdb pnn 2>&1)
2016-07-22 09:19:23 +03:00
while read line ; do
if [[ "$line" =~ ^[0-9]+$ ]] ; then
local pnn="$line"
2016-07-06 10:31:51 +03:00
# Intentional multi-word expansion
# shellcheck disable=SC2086
2009-09-15 13:33:35 +04:00
echo_nth "$pnn" $all_nodes
return 0
fi
# Else must be an error message from a down node.
done <<<"$out"
return 1
}
2008-07-09 08:18:15 +04:00
get_nodes ()
{
2008-11-20 12:40:01 +03:00
local all_nodes
if [ -n "$CTDB_NODES_SOCKETS" ] ; then
all_nodes="$CTDB_NODES_SOCKETS"
else
2015-08-17 13:47:58 +03:00
local f="${CTDB_BASE}/nodes"
2010-01-21 05:40:03 +03:00
if [ -n "$CTDB_NODES_FILE" ] ; then
f="$CTDB_NODES_FILE"
if [ ! -e "$f" -a "${f#/}" = "$f" ] ; then
2015-08-17 13:47:58 +03:00
# $f is relative, try in $CTDB_BASE
f="${CTDB_BASE}/${f}"
2010-01-21 05:40:03 +03:00
fi
2015-08-17 13:47:58 +03:00
elif [ -n "$CTDB_NODES" ] ; then
f="$CTDB_NODES"
2010-01-21 05:40:03 +03:00
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")
2008-11-20 12:40:01 +03:00
fi
2008-07-09 08:18:15 +04:00
2008-09-12 05:22:50 +04:00
local nodes=""
local n
2008-07-09 08:18:15 +04:00
for n in $(parse_nodespec "$1") ; do
[ $? != 0 ] && exit 1 # Required to catch exit in above subshell.
case "$n" in
2008-09-12 05:22:50 +04:00
all)
2009-06-02 09:03:44 +04:00
echo "${all_nodes//#DEAD/}"
;;
2009-09-15 13:33:35 +04:00
any)
get_any_available_node "$all_nodes" || exit 1
;;
2009-05-11 07:39:31 +04:00
ok|healthy)
get_nodes_with_status "$all_nodes" "healthy" || exit 1
;;
con|connected)
get_nodes_with_status "$all_nodes" "connected" || exit 1
2008-09-12 05:22:50 +04:00
;;
2009-10-14 06:44:57 +04:00
[0-9]|[0-9][0-9]|[0-9][0-9][0-9])
2016-07-06 10:31:51 +03:00
# Intentional multi-word expansion
# shellcheck disable=SC2086
echo_nth "$n" $all_nodes
2009-10-14 06:44:57 +04:00
;;
*)
$names_ok || invalid_nodespec
2016-07-06 10:31:51 +03:00
echo "$n"
2008-07-09 08:18:15 +04:00
esac
done
}
2012-07-17 10:45:55 +04:00
push()
{
local host="$1"
local files="$2"
local f
for f in $files ; do
$verbose && echo "Pushing $f"
case "$f" in
2014-11-28 13:49:08 +03:00
/*) rsync "$f" "[${host}]:${f}" ;;
*) rsync "${PWD}/${f}" "[${host}]:${PWD}/${f}" ;;
2012-07-17 10:45:55 +04:00
esac
done
}
2008-11-20 12:40:01 +03:00
fakessh ()
{
Fix minor onnode bugs relating to local daemons.
Commit a0f5148ac749758e2dfbd6099e829c5bf1d900e6 caused a subtle
regression. Due to the subtlety, this description is much longer than
the 1 line patch that fixes it! The regression, where a process that
invokes onnode is unexpectedly blocked, is only apparent if the
following conditions are met:
1. $CTDB_NODES_SOCKETS is set;
2. The command passed to onnode attempts to background a process; and
3. onnode is run in certain types of subshell (e.g. foo=$(onnode ...)).
In particular, when testing against local daemons (i.e. condition (1)
is met), tests/simple/07_ctdb_process_exists.sh would fail (because it
does both (2), (3)).
The problem is caused by the use of file descriptor 3 in the code that
allows separate filtering of stdout and stderr. A backgrounded
process will have this descriptor open and the $(...) construct
appears to wait for all file descriptors to be closed. This only
happens with local daemons because SSH is replaced by a shell and file
descriptor 3 leaks into that shell. It does not occur when SSH is
used because the file descriptor does not leak into the remote shell
where the process is backgrounded.
The fix is simply to redirect file descriptor 3 to /dev/null in the
fakessh function, which is used when $CTDB_NODES_SOCKETS is set.
Also fixed is another minor bug when the -o option and
$CTDB_NODES_SOCKETS are used in combination. The code uses the node
name as a suffix for the output filename(s). Usually this is an IP
address. However, when $CTDB_NODES_SOCKETS is in use the node name is
the socket name, which might be a path several directories deep.
Each output file is created via a simple redirection and this would
fail if unexpected directories appear in the filename. 3 possible
fixes were considered:
1. Replace all '/'s in the node name by '_'s. Nice and simple.
2. Use the basename of the node name. However, sockets may be in
different directories but have the same basename.
3. Create all required directories before redirecting. This is a
little more complex and probably doesn't meet the user's
expectations.
Option (1) is implemented here.
Signed-off-by: Martin Schwenke <martin@meltin.net>
(This used to be ctdb commit c97d56d93d9c1007a4e85affb19ed0c2d0e11b6d)
2009-06-19 06:12:39 +04:00
CTDB_SOCKET="$1" sh -c "$2" 3>/dev/null
2008-11-20 12:40:01 +03:00
}
2009-05-06 07:17:34 +04:00
stdout_filter ()
{
if [ -n "$prefix" ] ; then
Fix minor onnode bugs relating to local daemons.
Commit a0f5148ac749758e2dfbd6099e829c5bf1d900e6 caused a subtle
regression. Due to the subtlety, this description is much longer than
the 1 line patch that fixes it! The regression, where a process that
invokes onnode is unexpectedly blocked, is only apparent if the
following conditions are met:
1. $CTDB_NODES_SOCKETS is set;
2. The command passed to onnode attempts to background a process; and
3. onnode is run in certain types of subshell (e.g. foo=$(onnode ...)).
In particular, when testing against local daemons (i.e. condition (1)
is met), tests/simple/07_ctdb_process_exists.sh would fail (because it
does both (2), (3)).
The problem is caused by the use of file descriptor 3 in the code that
allows separate filtering of stdout and stderr. A backgrounded
process will have this descriptor open and the $(...) construct
appears to wait for all file descriptors to be closed. This only
happens with local daemons because SSH is replaced by a shell and file
descriptor 3 leaks into that shell. It does not occur when SSH is
used because the file descriptor does not leak into the remote shell
where the process is backgrounded.
The fix is simply to redirect file descriptor 3 to /dev/null in the
fakessh function, which is used when $CTDB_NODES_SOCKETS is set.
Also fixed is another minor bug when the -o option and
$CTDB_NODES_SOCKETS are used in combination. The code uses the node
name as a suffix for the output filename(s). Usually this is an IP
address. However, when $CTDB_NODES_SOCKETS is in use the node name is
the socket name, which might be a path several directories deep.
Each output file is created via a simple redirection and this would
fail if unexpected directories appear in the filename. 3 possible
fixes were considered:
1. Replace all '/'s in the node name by '_'s. Nice and simple.
2. Use the basename of the node name. However, sockets may be in
different directories but have the same basename.
3. Create all required directories before redirecting. This is a
little more complex and probably doesn't meet the user's
expectations.
Option (1) is implemented here.
Signed-off-by: Martin Schwenke <martin@meltin.net>
(This used to be ctdb commit c97d56d93d9c1007a4e85affb19ed0c2d0e11b6d)
2009-06-19 06:12:39 +04:00
cat >"${prefix}.${n//\//_}"
2009-05-06 07:17:34 +04:00
elif $verbose && $parallel ; then
sed -e "s@^@[$n] @"
else
cat
fi
}
stderr_filter ()
{
if $verbose && $parallel ; then
sed -e "s@^@[$n] @"
else
cat
fi
}
2008-07-09 08:18:15 +04:00
######################################################################
parse_options "$@"
2008-11-20 12:40:01 +03:00
ssh_opts=
2012-07-17 10:45:55 +04:00
if $push ; then
SSH=push
2011-10-07 07:59:46 +04:00
EXTRA_SSH_OPTS=""
2012-07-17 10:45:55 +04:00
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.
2015-08-17 13:47:58 +03:00
[ -r "${CTDB_BASE}/onnode.conf" ] && . "${CTDB_BASE}/onnode.conf"
2012-07-17 10:45:55 +04:00
[ -n "$SSH" ] || SSH=ssh
if [ "$SSH" = "ssh" ] ; then
2013-11-13 07:04:17 +04:00
if $parallel || ! $stdin ; then
ssh_opts="-n"
fi
2012-07-17 10:45:55 +04:00
else
: # rsh? All bets are off!
fi
2008-11-20 12:40:01 +03:00
fi
2008-07-09 08:18:15 +04:00
fi
######################################################################
nodes=$(get_nodes "$nodespec")
[ $? != 0 ] && exit 1 # Required to catch exit in above subshell.
if $quiet ; then
verbose=false
else
# If $nodes contains a space or a newline then assume multiple nodes.
nl="
"
[ "$nodes" != "${nodes%[ ${nl}]*}" ] && verbose=true
fi
pids=""
2016-07-06 10:31:51 +03:00
# Intentional multi-word expansion
# shellcheck disable=SC2086
2008-07-09 08:18:15 +04:00
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,
# the part of the window where it matter is very small.
2008-07-14 03:19:22 +04:00
retcode=0
2008-07-09 08:18:15 +04:00
for n in $nodes ; do
2009-05-06 07:17:34 +04:00
set -o pipefail 2>/dev/null
2008-07-09 08:18:15 +04:00
if $parallel ; then
2016-07-06 10:31:51 +03:00
{ exec 3>&1 ; { $SSH $ssh_opts $EXTRA_SSH_OPTS "$n" "$command" | stdout_filter >&3 ; } 2>&1 | stderr_filter ; } &
2008-07-09 08:18:15 +04:00
pids="${pids} $!"
else
2008-07-10 08:19:52 +04:00
if $verbose ; then
echo >&2 ; echo ">> NODE: $n <<" >&2
fi
2016-07-06 10:31:51 +03:00
{ exec 3>&1 ; { $SSH $ssh_opts $EXTRA_SSH_OPTS "$n" "$command" | stdout_filter >&3 ; } 2>&1 | stderr_filter ; }
2008-07-14 03:19:22 +04:00
[ $? = 0 ] || retcode=$?
2008-07-09 08:18:15 +04:00
fi
done
2008-07-14 03:19:22 +04:00
$parallel && {
for p in $pids; do
2016-07-06 10:31:51 +03:00
wait "$p"
2008-07-14 03:19:22 +04:00
[ $? = 0 ] || retcode=$?
done
}
exit $retcode