ctdb-tests: Rationalise node stop/start/restart
[vlendec/samba-autobuild/.git] / ctdb / tests / local_daemons.sh
1 #!/bin/sh
2
3 set -u
4
5 export CTDB_TEST_MODE="yes"
6
7 # Following 2 lines may be modified by installation script
8 CTDB_TESTS_ARE_INSTALLED=false
9 CTDB_TEST_DIR=$(dirname "$0")
10 export CTDB_TESTS_ARE_INSTALLED CTDB_TEST_DIR
11
12 export TEST_SCRIPTS_DIR="${CTDB_TEST_DIR}/scripts"
13
14 . "${TEST_SCRIPTS_DIR}/common.sh"
15
16 if ! $CTDB_TESTS_ARE_INSTALLED ; then
17         hdir="$CTDB_SCRIPTS_HELPER_BINDIR"
18         export CTDB_EVENTD="${hdir}/ctdb-eventd"
19         export CTDB_EVENT_HELPER="${hdir}/ctdb-event"
20         export CTDB_LOCK_HELPER="${hdir}/ctdb_lock_helper"
21         export CTDB_RECOVERY_HELPER="${hdir}/ctdb_recovery_helper"
22         export CTDB_TAKEOVER_HELPER="${hdir}/ctdb_takeover_helper"
23         export CTDB_CLUSTER_MUTEX_HELPER="${hdir}/ctdb_mutex_fcntl_helper"
24 fi
25
26 ########################################
27
28 # If the given IP is hosted then print 2 items: maskbits and iface
29 have_ip ()
30 {
31         _addr="$1"
32
33         case "$_addr" in
34         *:*) _bits=128 ;;
35         *)   _bits=32  ;;
36         esac
37
38         _t=$(ip addr show to "${_addr}/${_bits}")
39         [ -n "$_t" ]
40 }
41
42 setup_nodes ()
43 {
44         _num_nodes="$1"
45         _use_ipv6="$2"
46
47         _have_all_ips=true
48         for _i in $(seq 0 $((_num_nodes - 1)) ) ; do
49                 if $_use_ipv6 ; then
50                         _j=$(printf "%04x" $((0x5f00 + 1 + _i)) )
51                         _node_ip="fd00::5357:${_j}"
52                         if have_ip "$_node_ip" ; then
53                                 echo "$_node_ip"
54                         else
55                                 cat >&2 <<EOF
56 ERROR: ${_node_ip} not on an interface, please add it
57 EOF
58                                 _have_all_ips=false
59                         fi
60                 else
61                         _c=$(( _i / 100 ))
62                         _d=$(( 1 + (_i % 100) ))
63                         echo "127.0.${_c}.${_d}"
64                 fi
65         done
66
67         # Fail if we don't have all of the IPv6 addresses assigned
68         $_have_all_ips
69 }
70
71 setup_public_addresses ()
72 {
73         _num_nodes="$1"
74         _node_no_ips="$2"
75         _use_ipv6="$3"
76
77         for _i in $(seq 0 $((_num_nodes - 1)) ) ; do
78                 if  [ "$_i" -eq "$_node_no_ips" ] ; then
79                         continue
80                 fi
81
82                 # 2 public addresses on most nodes, just to make
83                 # things interesting
84                 if $_use_ipv6 ; then
85                         printf 'fc00:10::1:%x/64 lo\n' $((1 + _i))
86                         printf 'fc00:10::2:%x/64 lo\n' $((1 + _i))
87                 else
88                         _c1=$(( 100 + (_i / 100) ))
89                         _c2=$(( 200 + (_i / 100) ))
90                         _d=$(( 1 + (_i % 100) ))
91                         printf '192.168.%d.%d/24 lo\n' "$_c1" "$_d"
92                         printf '192.168.%d.%d/24 lo\n' "$_c2" "$_d"
93                 fi
94         done
95 }
96
97 setup_socket_wrapper ()
98 {
99         _socket_wrapper_so="$1"
100
101         _so="${directory}/libsocket-wrapper.so"
102         if [ ! -f "$_socket_wrapper_so" ] ; then
103                 die "$0 setup: Unable to find ${_socket_wrapper_so}"
104         fi
105
106         # Find absolute path if only relative is given
107         case "$_socket_wrapper_so" in
108         /*) : ;;
109         *) _socket_wrapper_so="${PWD}/${_socket_wrapper_so}" ;;
110         esac
111
112         rm -f "$_so"
113         ln -s "$_socket_wrapper_so" "$_so"
114
115         _d="${directory}/sw"
116         rm -rf "$_d"
117         mkdir -p "$_d"
118 }
119
120 local_daemons_setup_usage ()
121 {
122         cat >&2 <<EOF
123 $0 <directory> setup [ <options>... ]
124
125 Options:
126   -F            Disable failover (default: failover enabled)
127   -N <file>     Nodes file (default: automatically generated)
128   -n <num>      Number of nodes (default: 3)
129   -P <file>     Public addresses file (default: automatically generated)
130   -R            Use a command for the recovery lock (default: use a file)
131   -r <time>     Like -R and set recheck interval to <time> (default: use a file)
132   -S <library>  Socket wrapper shared library to preload (default: none)
133   -6            Generate IPv6 IPs for nodes, public addresses (default: IPv4)
134 EOF
135
136         exit 1
137 }
138
139 local_daemons_setup ()
140 {
141         _disable_failover=false
142         _nodes_file=""
143         _num_nodes=3
144         _public_addresses_file=""
145         _recovery_lock_use_command=false
146         _recovery_lock_recheck_interval=""
147         _socket_wrapper=""
148         _use_ipv6=false
149
150         set -e
151
152         while getopts "FN:n:P:Rr:S:6h?" _opt ; do
153                 case "$_opt" in
154                 F) _disable_failover=true ;;
155                 N) _nodes_file="$OPTARG" ;;
156                 n) _num_nodes="$OPTARG" ;;
157                 P) _public_addresses_file="$OPTARG" ;;
158                 R) _recovery_lock_use_command=true ;;
159                 r) _recovery_lock_use_command=true
160                    _recovery_lock_recheck_interval="$OPTARG"
161                    ;;
162                 S) _socket_wrapper="$OPTARG" ;;
163                 6) _use_ipv6=true ;;
164                 \?|h) local_daemons_setup_usage ;;
165                 esac
166         done
167         shift $((OPTIND - 1))
168
169         mkdir -p "$directory"
170
171         _nodes_all="${directory}/nodes"
172         if [ -n "$_nodes_file" ] ; then
173                 cp "$_nodes_file" "$_nodes_all"
174         else
175                 setup_nodes "$_num_nodes" $_use_ipv6 >"$_nodes_all"
176         fi
177
178         # If there are (strictly) greater than 2 nodes then we'll
179         # "randomly" choose a node to have no public addresses
180         _node_no_ips=-1
181         if [ "$_num_nodes" -gt 2 ] ; then
182                 _node_no_ips=$(($$ % _num_nodes))
183         fi
184
185         _public_addresses_all="${directory}/public_addresses"
186         if [ -n "$_public_addresses_file" ] ; then
187                 cp "$_public_addresses_file" "$_public_addresses_all"
188         else
189                 setup_public_addresses "$_num_nodes" \
190                                        $_node_no_ips \
191                                        $_use_ipv6 >"$_public_addresses_all"
192         fi
193
194         _recovery_lock_dir="${directory}/shared/.ctdb"
195         mkdir -p "$_recovery_lock_dir"
196         _recovery_lock="${_recovery_lock_dir}/rec.lock"
197         if $_recovery_lock_use_command ; then
198                 _helper="${CTDB_SCRIPTS_HELPER_BINDIR}/ctdb_mutex_fcntl_helper"
199                 _t="! ${_helper} ${_recovery_lock}"
200                 if [ -n "$_recovery_lock_recheck_interval" ] ; then
201                         _t="${_t} ${_recovery_lock_recheck_interval}"
202                 fi
203                 _recovery_lock="$_t"
204         fi
205
206         if [ -n "$_socket_wrapper" ] ; then
207                 setup_socket_wrapper "$_socket_wrapper"
208         fi
209
210         for _n in $(seq 0 $((_num_nodes - 1))) ; do
211                 # CTDB_TEST_SUITE_DIR needs to be correctly set so
212                 # setup_ctdb_base() finds the etc-ctdb/ subdirectory
213                 # and the test event script is correctly installed
214                 # shellcheck disable=SC2034
215                 CTDB_TEST_SUITE_DIR="$CTDB_TEST_DIR" \
216                            setup_ctdb_base "$directory" "node.${_n}" \
217                                 functions notify.sh debug-hung-script.sh
218
219                 cp "$_nodes_all" "${CTDB_BASE}/nodes"
220
221                 _public_addresses="${CTDB_BASE}/public_addresses"
222
223                 if  [ -z "$_public_addresses_file" ] && \
224                             [ $_node_no_ips -eq "$_n" ] ; then
225                         echo "Node ${_n} will have no public IPs."
226                         : >"$_public_addresses"
227                 else
228                         cp "$_public_addresses_all" "$_public_addresses"
229                 fi
230
231                 _node_ip=$(sed -n -e "$((_n + 1))p" "$_nodes_all")
232
233                 _db_dir="${CTDB_BASE}/db"
234                 for _d in "volatile" "persistent" "state" ; do
235                         mkdir -p "${_db_dir}/${_d}"
236                 done
237
238                 cat >"${CTDB_BASE}/ctdb.conf" <<EOF
239 [logging]
240         location = file:${CTDB_BASE}/log.ctdb
241         log level = INFO
242
243 [cluster]
244         recovery lock = ${_recovery_lock}
245         node address = ${_node_ip}
246
247 [database]
248         volatile database directory = ${_db_dir}/volatile
249         persistent database directory = ${_db_dir}/persistent
250         state database directory = ${_db_dir}/state
251
252 [failover]
253         disabled = ${_disable_failover}
254
255 [event]
256         debug script = debug-hung-script.sh
257 EOF
258         done
259 }
260
261 local_daemons_ssh_usage ()
262 {
263         cat >&2 <<EOF
264 usage: $0 <directory> ssh [ -n ] <ip> <command>
265 EOF
266
267         exit 1
268 }
269
270 local_daemons_ssh ()
271 {
272         if [ $# -lt 2 ] ; then
273                 local_daemons_ssh_usage
274         fi
275
276         # Only try to respect ssh -n option, others can't be used so discard them
277         _close_stdin=false
278         while getopts "nh?" _opt ; do
279                 case "$_opt" in
280                 n) _close_stdin=true ;;
281                 \?|h) local_daemons_ssh_usage ;;
282                 *) : ;;
283                 esac
284         done
285         shift $((OPTIND - 1))
286
287         if [ $# -lt 2 ] ; then
288                 local_daemons_ssh_usage
289         fi
290
291         _nodes="${directory}/nodes"
292
293         # IP adress of node. onnode can pass hostnames but not in these tests
294         _ip="$1" ; shift
295         # "$*" is command
296
297
298         # Determine the correct CTDB base directory
299         _num=$(awk -v ip="$_ip" '$1 == ip { print NR }' "$_nodes")
300         _node=$((_num - 1))
301         export CTDB_BASE="${directory}/node.${_node}"
302
303         if [ ! -d "$CTDB_BASE" ] ; then
304                 die "$0 ssh: Unable to find base for node ${_ip}"
305         fi
306
307         if $_close_stdin ; then
308                 exec sh -c "$*" </dev/null
309         else
310                 exec sh -c "$*"
311         fi
312 }
313
314 onnode_common ()
315 {
316         # onnode will execute this, which fakes ssh against local daemons
317         export ONNODE_SSH="${0} ${directory} ssh"
318
319         # onnode just needs the nodes file, so use the common one
320         export CTDB_BASE="$directory"
321 }
322
323 local_daemons_generic_usage ()
324 {
325         cat >&2 <<EOF
326 usage: $0 <directory> ${1} <nodes>
327
328 <nodes> can be  "all", a node number or any specification supported by onnode
329 EOF
330
331         exit 1
332 }
333
334 local_daemons_start_socket_wrapper ()
335 {
336         _so="${directory}/libsocket-wrapper.so"
337         _d="${directory}/sw"
338
339         if [ -d "$_d" ] && [ -f "$_so" ] ; then
340                 export SOCKET_WRAPPER_DIR="$_d"
341                 export LD_PRELOAD="$_so"
342         fi
343 }
344
345 local_daemons_start ()
346 {
347         if [ $# -ne 1 ] || [ "$1" = "-h" ] ; then
348                 local_daemons_generic_usage "start"
349         fi
350
351         local_daemons_start_socket_wrapper
352
353         _nodes="$1"
354
355         onnode_common
356
357         onnode -i "$_nodes" "${VALGRIND:-} ctdbd"
358 }
359
360 local_daemons_stop ()
361 {
362         if [ $# -ne 1 ] || [ "$1" = "-h" ] ; then
363                 local_daemons_generic_usage "stop"
364         fi
365
366         _nodes="$1"
367
368         onnode_common
369
370         onnode -p "$_nodes" "${CTDB:-${VALGRIND:-} ctdb} shutdown"
371 }
372
373 local_daemons_onnode_usage ()
374 {
375         cat >&2 <<EOF
376 usage: $0 <directory> onnode <nodes> <command>...
377
378 <nodes> can be  "all", a node number or any specification supported by onnode
379 EOF
380
381         exit 1
382 }
383
384 local_daemons_onnode ()
385 {
386         if [ $# -lt 2 ] || [ "$1" = "-h" ] ; then
387                 local_daemons_onnode_usage
388         fi
389
390         _nodes="$1"
391         shift
392
393         onnode_common
394
395         onnode "$_nodes" "$@"
396 }
397
398 local_daemons_print_socket ()
399 {
400         if [ $# -ne 1 ] || [ "$1" = "-h" ] ; then
401                 local_daemons_generic_usage "print-socket"
402         fi
403
404         _nodes="$1"
405         shift
406
407         onnode_common
408
409         _path="${CTDB_SCRIPTS_HELPER_BINDIR}/ctdb-path"
410         onnode -q "$_nodes" "${VALGRIND:-} ${_path} socket ctdbd"
411 }
412
413 local_daemons_print_log ()
414 {
415         if [ $# -ne 1 ] || [ "$1" = "-h" ] ; then
416                 local_daemons_generic_usage "print-log"
417         fi
418
419         _nodes="$1"
420         shift
421
422         onnode_common
423
424         # shellcheck disable=SC2016
425         # $CTDB_BASE must only be expanded under onnode, not in top-level shell
426         onnode -q "$_nodes" 'echo ${CTDB_BASE}/log.ctdb' |
427         while IFS='' read -r _l ; do
428                 _dir=$(dirname "$_l")
429                 _node=$(basename "$_dir")
430                 # Add fake hostname after date and time, which are the
431                 # first 2 words on each line
432                 sed -e "s|^\\([^ ][^ ]* [^ ][^ ]*\\)|\\1 ${_node}|" "$_l"
433         done |
434         sort
435
436 }
437
438 usage ()
439 {
440         cat <<EOF
441 usage: $0 <directory> <command> [ <options>... ]
442
443 Commands:
444   setup          Set up daemon configuration according to given options
445   start          Start specified daemon(s)
446   stop           Stop specified daemon(s)
447   onnode         Run a command in the environment of specified daemon(s)
448   print-socket   Print the Unix domain socket used by specified daemon(s)
449   print-log      Print logs for specified daemon(s) to stdout
450
451 All commands use <directory> for daemon configuration
452
453 Run command with -h option to see per-command usage
454 EOF
455
456         exit 1
457 }
458
459 if [ $# -lt 2 ] ; then
460         usage
461 fi
462
463 directory="$1"
464 command="$2"
465 shift 2
466
467 case "$command" in
468 setup) local_daemons_setup "$@" ;;
469 ssh) local_daemons_ssh "$@" ;; # Internal, not shown by usage()
470 start) local_daemons_start "$@" ;;
471 stop) local_daemons_stop "$@" ;;
472 onnode) local_daemons_onnode "$@" ;;
473 print-socket) local_daemons_print_socket "$@" ;;
474 print-log) local_daemons_print_log "$@" ;;
475 *) usage ;;
476 esac