Merge remote branch 'martins/master'
[vlendec/samba-autobuild/.git] / ctdb / tests / scripts / ctdb_test_functions.bash
1 # Hey Emacs, this is a -*- shell-script -*- !!!  :-)
2
3 fail ()
4 {
5     echo "$*"
6     exit 1
7 }
8
9 ######################################################################
10
11 ctdb_check_time_logs ()
12 {
13     local threshold=20
14
15     local jump=false
16     local prev=""
17     local ds_prev=""
18     local node=""
19
20     out=$(onnode all tail -n 20 /var/log/ctdb.test.time.log 2>&1)
21
22     if [ $? -eq 0 ] ; then
23         local line
24         while read line ; do
25             case "$line" in
26                 \>\>\ NODE:\ *\ \<\<)
27                     node="${line#>> NODE: }"
28                     node=${node% <<*}
29                     ds_prev=""
30                     ;;
31                 *\ *)
32                     set -- $line
33                     ds_curr="$1${2:0:1}"
34                     if [ -n "$ds_prev" ] && \
35                         [ $(($ds_curr - $ds_prev)) -ge $threshold ] ; then
36                         echo "Node $node had time jump of $(($ds_curr - $ds_prev))ds between $(date +'%T' -d @${ds_prev%?}) and $(date +'%T' -d @${ds_curr%?})"
37                         jump=true
38                     fi
39                     prev="$line"
40                     ds_prev="$ds_curr"
41                     ;;
42             esac
43         done <<<"$out"
44     else
45         echo Error getting time logs
46     fi
47     if $jump ; then
48         echo "Check time sync (test client first):"
49         date
50         onnode -p all date
51         echo "Information from test client:"
52         hostname
53         top -b -n 1
54         echo "Information from cluster nodes:"
55         onnode all "top -b -n 1 ; echo '/proc/slabinfo' ; cat /proc/slabinfo"
56     fi
57 }
58
59 ctdb_test_exit ()
60 {
61     local status=$?
62
63     trap - 0
64
65     [ $(($testfailures+0)) -eq 0 -a $status -ne 0 ] && testfailures=$status
66     status=$(($testfailures+0))
67
68     # Avoid making a test fail from this point onwards.  The test is
69     # now complete.
70     set +e
71
72     echo "*** TEST COMPLETED (RC=$status) AT $(date '+%F %T'), CLEANING UP..."
73
74     if [ -n "$CTDB_TEST_REAL_CLUSTER"  -a -n "$CTDB_TEST_TIME_LOGGING" -a \
75         $status -ne 0 ] ; then
76         ctdb_check_time_logs
77     fi
78
79     eval "$ctdb_test_exit_hook" || true
80     unset ctdb_test_exit_hook
81
82     if $ctdb_test_restart_scheduled || ! cluster_is_healthy ; then
83
84         restart_ctdb
85     else
86         # This could be made unconditional but then we might get
87         # duplication from the recovery in restart_ctdb.  We want to
88         # leave the recovery in restart_ctdb so that future tests that
89         # might do a manual restart mid-test will benefit.
90         echo "Forcing a recovery..."
91         onnode 0 $CTDB recover
92     fi
93
94     exit $status
95 }
96
97 ctdb_test_exit_hook_add ()
98 {
99     ctdb_test_exit_hook="${ctdb_test_exit_hook}${ctdb_test_exit_hook:+ ; }$*"
100 }
101
102 ctdb_test_usage()
103 {
104     local status=${1:-2}
105     
106     cat <<EOF
107 Usage: $0 [option]
108
109 Options:        
110     -h, --help          show this screen.
111     -v, --version       show test case version.
112     --category          show the test category (ACL, CTDB, Samba ...).
113     -d, --description   show test case description.
114     --summary           show short test case summary.
115     -x                  trace test using set -x
116 EOF
117
118     exit $status
119 }
120
121 ctdb_test_version ()
122 {
123     [ -n "$CTDB_DIR" ] || fail "Can not determine version."
124
125     (cd "$CTDB_DIR" && git describe)
126 }
127
128 ctdb_test_cmd_options()
129 {
130     [ -n "$1" ] || return 0
131
132     case "$1" in
133         -h|--help)        ctdb_test_usage 0   ;;
134         -v|--version)     ctdb_test_version   ;;
135         --category)       echo "CTDB"         ;; 
136         -d|--description) test_info           ;;
137         -x)               set -x ; return 0   ;;
138         *)
139             echo "Error: Unknown parameter = $1"
140             echo
141             ctdb_test_usage 2
142             ;;
143     esac
144
145     exit 0
146 }
147
148 ctdb_test_init () 
149 {
150     scriptname=$(basename "$0")
151     testfailures=0
152     ctdb_test_restart_scheduled=false
153
154     ctdb_test_cmd_options $@
155
156     trap "ctdb_test_exit" 0
157 }
158
159 ctdb_test_check_real_cluster ()
160 {
161     [ -n "$CTDB_TEST_REAL_CLUSTER" ] && return 0
162
163     echo "ERROR: This test must be run on a real/virtual cluster, not local daemons."
164     return 1
165 }
166
167 ########################################
168
169 # Sets: $out
170 try_command_on_node ()
171 {
172     local nodespec="$1" ; shift
173
174     local verbose=false
175     local onnode_opts=""
176
177     while [ "${nodespec#-}" != "$nodespec" ] ; do
178         if [ "$nodespec" = "-v" ] ; then
179             verbose=true
180         else
181             onnode_opts="$nodespec"
182         fi
183         nodespec="$1" ; shift
184     done
185
186     local cmd="$*"
187
188     out=$(onnode -q $onnode_opts "$nodespec" "$cmd" 2>&1) || {
189
190         echo "Failed to execute \"$cmd\" on node(s) \"$nodespec\""
191         echo "$out"
192         return 1
193     }
194
195     if $verbose ; then
196         echo "Output of \"$cmd\":"
197         echo "$out"
198     fi
199 }
200
201 sanity_check_output ()
202 {
203     local min_lines="$1"
204     local regexp="$2" # Should be anchored as necessary.
205     local output="$3"
206
207     local ret=0
208
209     local num_lines=$(echo "$output" | wc -l)
210     echo "There are $num_lines lines of output"
211     if [ $num_lines -lt $min_lines ] ; then
212         echo "BAD: that's less than the required number (${min_lines})"
213         ret=1
214     fi
215
216     local status=0
217     local unexpected # local doesn't pass through status of command on RHS.
218     unexpected=$(echo "$output" | egrep -v "$regexp") || status=$?
219
220     # Note that this is reversed.
221     if [ $status -eq 0 ] ; then
222         echo "BAD: unexpected lines in output:"
223         echo "$unexpected" | cat -A
224         ret=1
225     else
226         echo "Output lines look OK"
227     fi
228
229     return $ret
230 }
231
232 sanity_check_ips ()
233 {
234     local ips="$1" # list of "ip node" lines
235
236     echo "Sanity checking IPs..."
237
238     local x ipp prev
239     prev=""
240     while read x ipp ; do
241         [ "$ipp" = "-1" ] && break
242         if [ -n "$prev" -a "$ipp" != "$prev" ] ; then
243             echo "OK"
244             return 0
245         fi
246         prev="$ipp"
247     done <<<"$ips"
248
249     echo "BAD: a node was -1 or IPs are only assigned to one node"
250     echo "Are you running an old version of CTDB?"
251     return 1
252 }
253
254 # This returns a list of "ip node" lines in $out
255 all_ips_on_node()
256 {
257     local node=$@
258     try_command_on_node $node "$CTDB ip -Y -n all | cut -d ':' -f1-3 | sed -e '1d' -e 's@^:@@' -e 's@:@ @g'"
259 }
260
261 select_test_node_and_ips ()
262 {
263     all_ips_on_node 0
264
265     # When selecting test_node we just want a node that has public
266     # IPs.  This will work and is economically semi-random.  :-)
267     local x
268     read x test_node <<<"$out"
269
270     test_node_ips=""
271     local ip pnn
272     while read ip pnn ; do
273         if [ "$pnn" = "$test_node" ] ; then
274             test_node_ips="${test_node_ips}${test_node_ips:+ }${ip}"
275         fi
276     done <<<"$out" # bashism to avoid problem setting variable in pipeline.
277
278     echo "Selected node ${test_node} with IPs: ${test_node_ips}."
279     test_ip="${test_node_ips%% *}"
280 }
281
282 #######################################
283
284 # Wait until either timeout expires or command succeeds.  The command
285 # will be tried once per second.
286 wait_until ()
287 {
288     local timeout="$1" ; shift # "$@" is the command...
289
290     local negate=false
291     if [ "$1" = "!" ] ; then
292         negate=true
293         shift
294     fi
295
296     echo -n "<${timeout}|"
297     local t=$timeout
298     while [ $t -gt 0 ] ; do
299         local rc=0
300         "$@" || rc=$?
301         if { ! $negate && [ $rc -eq 0 ] ; } || \
302             { $negate && [ $rc -ne 0 ] ; } ; then
303             echo "|$(($timeout - $t))|"
304             echo "OK"
305             return 0
306         fi
307         echo -n .
308         t=$(($t - 1))
309         sleep 1
310     done
311     
312     echo "*TIMEOUT*"
313     
314     return 1
315 }
316
317 sleep_for ()
318 {
319     echo -n "=${1}|"
320     for i in $(seq 1 $1) ; do
321         echo -n '.'
322         sleep 1
323     done
324     echo '|'
325 }
326
327 _cluster_is_healthy ()
328 {
329     local out x count line
330
331     out=$($CTDB -Y status 2>/dev/null) || return 1
332
333     {
334         read x
335         count=0
336         while read line ; do
337             # We need to see valid lines if we're going to be healthy.
338             [ "${line#:[0-9]}" != "$line" ] && count=$(($count + 1))
339             # A line indicating a node is unhealthy causes failure.
340             [ "${line##:*:*:*1:}" != "$line" ] && return 1
341         done
342         [ $count -gt 0 ] && return $?
343     } <<<"$out" # Yay bash!
344 }
345
346 cluster_is_healthy ()
347 {
348     if onnode 0 $CTDB_TEST_WRAPPER _cluster_is_healthy ; then
349         echo "Cluster is HEALTHY"
350         return 0
351     else
352         echo "Cluster is UNHEALTHY"
353         if ! ${ctdb_test_restart_scheduled:-false} ; then
354             echo "DEBUG AT $(date '+%F %T'):"
355             local i
356             for i in "onnode -q 0 $CTDB status" "onnode -q 0 onnode all $CTDB scriptstatus" ; do
357                 echo "$i"
358                 $i || true
359             done
360         fi
361         return 1
362     fi
363 }
364
365 wait_until_healthy ()
366 {
367     local timeout="${1:-120}"
368
369     echo "Waiting for cluster to become healthy..."
370
371     wait_until 120 _cluster_is_healthy
372 }
373
374 # This function is becoming nicely overloaded.  Soon it will collapse!  :-)
375 node_has_status ()
376 {
377     local pnn="$1"
378     local status="$2"
379
380     local bits fpat mpat
381     case "$status" in
382         (unhealthy)    bits="?:?:?:1:*" ;;
383         (healthy)      bits="?:?:?:0:*" ;;
384         (disconnected) bits="1:*" ;;
385         (connected)    bits="0:*" ;;
386         (banned)       bits="?:1:*" ;;
387         (unbanned)     bits="?:0:*" ;;
388         (disabled)     bits="?:?:1:*" ;;
389         (enabled)      bits="?:?:0:*" ;;
390         (stopped)      bits="?:?:?:?:1:*" ;;
391         (notstopped)   bits="?:?:?:?:0:*" ;;
392         (frozen)       fpat='^[[:space:]]+frozen[[:space:]]+1$' ;;
393         (unfrozen)     fpat='^[[:space:]]+frozen[[:space:]]+0$' ;;
394         (monon)        mpat='^Monitoring mode:ACTIVE \(0\)$' ;;
395         (monoff)       mpat='^Monitoring mode:DISABLED \(1\)$' ;;
396         *)
397             echo "node_has_status: unknown status \"$status\""
398             return 1
399     esac
400
401     if [ -n "$bits" ] ; then
402         local out x line
403
404         out=$($CTDB -Y status 2>&1) || return 1
405
406         {
407             read x
408             while read line ; do
409                 # This needs to be done in 2 steps to avoid false matches.
410                 local line_bits="${line#:${pnn}:*:}"
411                 [ "$line_bits" = "$line" ] && continue
412                 [ "${line_bits#${bits}}" != "$line_bits" ] && return 0
413             done
414             return 1
415         } <<<"$out" # Yay bash!
416     elif [ -n "$fpat" ] ; then
417         $CTDB statistics -n "$pnn" | egrep -q "$fpat"
418     elif [ -n "$mpat" ] ; then
419         $CTDB getmonmode -n "$pnn" | egrep -q "$mpat"
420     else
421         echo 'node_has_status: unknown mode, neither $bits nor $fpat is set'
422         return 1
423     fi
424 }
425
426 wait_until_node_has_status ()
427 {
428     local pnn="$1"
429     local status="$2"
430     local timeout="${3:-30}"
431     local proxy_pnn="${4:-any}"
432
433     echo "Waiting until node $pnn has status \"$status\"..."
434
435     if ! wait_until $timeout onnode $proxy_pnn $CTDB_TEST_WRAPPER node_has_status "$pnn" "$status" ; then
436         for i in "onnode -q any $CTDB status" "onnode -q any onnode all $CTDB scriptstatus" ; do
437             echo "$i"
438             $i || true
439         done
440
441         return 1
442     fi
443
444 }
445
446 # Useful for superficially testing IP failover.
447 # IPs must be on nodes matching nodeglob.
448 ips_are_on_nodeglob ()
449 {
450     local nodeglob="$1" ; shift
451     local ips="$*"
452
453     local out
454
455     all_ips_on_node 1
456
457     while read ip pnn ; do
458         for check in $ips ; do
459             if [ "$check" = "$ip" ] ; then
460                 case "$pnn" in
461                     ($nodeglob) : ;;
462                     (*) return 1  ;;
463                 esac
464                 ips="${ips/${ip}}" # Remove from list
465             fi
466         done
467     done <<<"$out" # bashism to avoid problem setting variable in pipeline.
468
469     ips="${ips// }" # Remove any spaces.
470     [ -z "$ips" ]
471 }
472
473 wait_until_ips_are_on_nodeglob ()
474 {
475     echo "Waiting for IPs to fail over..."
476
477     wait_until 60 ips_are_on_nodeglob "$@"
478 }
479
480 node_has_some_ips ()
481 {
482     local node="$1"
483
484     local out
485
486     all_ips_on_node 1
487
488     while read ip pnn ; do
489         if [ "$node" = "$pnn" ] ; then
490             return 0
491         fi
492     done <<<"$out" # bashism to avoid problem setting variable in pipeline.
493
494     return 1
495 }
496
497 wait_until_node_has_some_ips ()
498 {
499     echo "Waiting for node to have some IPs..."
500
501     wait_until 60 node_has_some_ips "$@"
502 }
503
504 get_src_socket ()
505 {
506     local proto="$1"
507     local dst_socket="$2"
508     local pid="$3"
509     local prog="$4"
510
511     local pat="^${proto}[[:space:]]+[[:digit:]]+[[:space:]]+[[:digit:]]+[[:space:]]+[^[:space:]]+[[:space:]]+${dst_socket//./\\.}[[:space:]]+ESTABLISHED[[:space:]]+${pid}/${prog}[[:space:]]*\$"
512     out=$(netstat -tanp |
513         egrep "$pat" |
514         awk '{ print $4 }')
515
516     [ -n "$out" ]
517 }
518
519 wait_until_get_src_socket ()
520 {
521     local proto="$1"
522     local dst_socket="$2"
523     local pid="$3"
524     local prog="$4"
525
526     echo "Waiting for ${prog} to establish connection to ${dst_socket}..."
527
528     wait_until 5 get_src_socket "$@"
529 }
530
531 #######################################
532
533 # filename will be in $tcpdump_filename, pid in $tcpdump_pid
534 tcpdump_start ()
535 {
536     tcpdump_filter="$1" # global
537
538     echo "Running tcpdump..."
539     tcpdump_filename=$(mktemp)
540     ctdb_test_exit_hook_add "rm -f $tcpdump_filename"
541
542     # The only way of being sure that tcpdump is listening is to send
543     # some packets that it will see.  So we use dummy pings - the -U
544     # option to tcpdump ensures that packets are flushed to the file
545     # as they are captured.
546     local dummy_addr="127.3.2.1"
547     local dummy="icmp and dst host ${dummy_addr} and icmp[icmptype] == icmp-echo"
548     tcpdump -n -p -s 0 -e -U -w $tcpdump_filename -i any "($tcpdump_filter) or ($dummy)" &
549     ctdb_test_exit_hook_add "kill $! >/dev/null 2>&1"
550
551     echo "Waiting for tcpdump output file to be ready..."
552     ping -q "$dummy_addr" >/dev/null 2>&1 &
553     ctdb_test_exit_hook_add "kill $! >/dev/null 2>&1"
554
555     tcpdump_listen_for_dummy ()
556     {
557         tcpdump -n -r $tcpdump_filename -c 1 "$dummy" >/dev/null 2>&1
558     }
559
560     wait_until 10 tcpdump_listen_for_dummy
561 }
562
563 # By default, wait for 1 matching packet.
564 tcpdump_wait ()
565 {
566     local count="${1:-1}"
567     local filter="${2:-${tcpdump_filter}}"
568
569     tcpdump_check ()
570     {
571         local found=$(tcpdump -n -r $tcpdump_filename "$filter" 2>/dev/null | wc -l)
572         [ $found -ge $count ]
573     }
574
575     echo "Waiting for tcpdump to capture some packets..."
576     if ! wait_until 30 tcpdump_check ; then
577         echo "DEBUG AT $(date '+%F %T'):"
578         local i
579         for i in "onnode -q 0 $CTDB status" "netstat -tanp" "tcpdump -n -e -r $tcpdump_filename" ; do
580             echo "$i"
581             $i || true
582         done
583         return 1
584     fi
585 }
586
587 tcpdump_show ()
588 {
589     local filter="${1:-${tcpdump_filter}}"
590
591     tcpdump -n -r $tcpdump_filename  "$filter" 2>/dev/null
592 }
593
594 tcptickle_sniff_start ()
595 {
596     local src="$1"
597     local dst="$2"
598
599     local in="src host ${dst%:*} and tcp src port ${dst##*:} and dst host ${src%:*} and tcp dst port ${src##*:}"
600     local out="src host ${src%:*} and tcp src port ${src##*:} and dst host ${dst%:*} and tcp dst port ${dst##*:}"
601     local tickle_ack="${in} and (tcp[tcpflags] & tcp-ack != 0) and (tcp[14] == 4) and (tcp[15] == 210)" # win == 1234
602     local ack_ack="${out} and (tcp[tcpflags] & tcp-ack != 0)"
603     tcptickle_reset="${in} and tcp[tcpflags] & tcp-rst != 0"
604     local filter="(${tickle_ack}) or (${ack_ack}) or (${tcptickle_reset})"
605
606     tcpdump_start "$filter"
607 }
608
609 tcptickle_sniff_wait_show ()
610 {
611     tcpdump_wait 1 "$tcptickle_reset"
612
613     echo "GOOD: here are some TCP tickle packets:"
614     tcpdump_show
615 }
616
617 gratarp_sniff_start ()
618 {
619     tcpdump_start "arp host ${test_ip}"
620 }
621
622 gratarp_sniff_wait_show ()
623 {
624     tcpdump_wait 2
625
626     echo "GOOD: this should be the some gratuitous ARPs:"
627     tcpdump_show
628 }
629
630
631 #######################################
632
633 daemons_stop ()
634 {
635     echo "Attempting to politely shutdown daemons..."
636     onnode 1 $CTDB shutdown -n all || true
637
638     echo "Sleeping for a while..."
639     sleep_for 1
640
641     if pgrep -f $CTDB_DIR/bin/ctdbd >/dev/null ; then
642         echo "Killing remaining daemons..."
643         pkill -f $CTDB_DIR/bin/ctdbd
644
645         if pgrep -f $CTDB_DIR/bin/ctdbd >/dev/null ; then
646             echo "Once more with feeling.."
647             pkill -9 $CTDB_DIR/bin/ctdbd
648         fi
649     fi
650
651     local var_dir=$CTDB_DIR/tests/var
652     rm -rf $var_dir/test.db
653 }
654
655 daemons_setup ()
656 {
657     local num_nodes="${CTDB_TEST_NUM_DAEMONS:-2}" # default is 2 nodes
658
659     local var_dir=$CTDB_DIR/tests/var
660
661     mkdir -p $var_dir/test.db/persistent
662
663     local public_addresses=$var_dir/public_addresses.txt
664     local no_public_addresses=$var_dir/no_public_addresses.txt
665     rm -f $CTDB_NODES $public_addresses $no_public_addresses
666
667     # If there are (strictly) greater than 2 nodes then we'll randomly
668     # choose a node to have no public addresses.
669     local no_public_ips=-1
670     [ $num_nodes -gt 2 ] && no_public_ips=$(($RANDOM % $num_nodes))
671     echo "$no_public_ips" >$no_public_addresses
672
673     local i
674     for i in $(seq 1 $num_nodes) ; do
675         if [ "${CTDB_USE_IPV6}x" != "x" ]; then
676             echo ::$i >> $nodes
677             ip addr add ::$i/128 dev lo
678         else
679             echo 127.0.0.$i >> $CTDB_NODES
680             # 2 public addresses on most nodes, just to make things interesting.
681             if [ $(($i - 1)) -ne $no_public_ips ] ; then
682                 echo "192.0.2.$i/24 lo" >> $public_addresses
683                 echo "192.0.2.$(($i + $num_nodes))/24 lo" >> $public_addresses
684             fi
685         fi
686     done
687 }
688
689 daemons_start_1 ()
690 {
691     local pnn="$1"
692     shift # "$@" gets passed to ctdbd
693
694     local var_dir=$CTDB_DIR/tests/var
695
696     local public_addresses=$var_dir/public_addresses.txt
697     local no_public_addresses=$var_dir/no_public_addresses.txt
698
699     local no_public_ips=-1
700     [ -r $no_public_addresses ] && read no_public_ips <$no_public_addresses
701
702     if  [ "$no_public_ips" = $pnn ] ; then
703         echo "Node $no_public_ips will have no public IPs."
704     fi
705
706     local ctdb_options="--reclock=$var_dir/rec.lock --nlist $CTDB_NODES --nopublicipcheck --event-script-dir=$CTDB_DIR/tests/events.d --logfile=$var_dir/daemons.log -d 3 --dbdir=$var_dir/test.db --dbdir-persistent=$var_dir/test.db/persistent --dbdir-state=$var_dir/test.db/state"
707
708     if [ -z "$CTDB_TEST_REAL_CLUSTER" ]; then
709         ctdb_options="$ctdb_options --public-interface=lo"
710     fi
711
712     if [ $pnn -eq $no_public_ips ] ; then
713         ctdb_options="$ctdb_options --public-addresses=/dev/null"
714     else
715         ctdb_options="$ctdb_options --public-addresses=$public_addresses"
716     fi
717
718     # Need full path so we can use "pkill -f" to kill the daemons.
719     $VALGRIND $CTDB_DIR/bin/ctdbd --socket=$var_dir/sock.$pnn $ctdb_options "$@" ||return 1
720 }
721
722 daemons_start ()
723 {
724     # "$@" gets passed to ctdbd
725
726     local num_nodes="${CTDB_TEST_NUM_DAEMONS:-2}" # default is 2 nodes
727
728     echo "Starting $num_nodes ctdb daemons..."
729
730     for i in $(seq 0 $(($num_nodes - 1))) ; do
731         daemons_start_1 $i "$@"
732     done
733
734     local var_dir=$CTDB_DIR/tests/var
735
736     if [ -L /tmp/ctdb.socket -o ! -S /tmp/ctdb.socket ] ; then 
737         ln -sf $var_dir/sock.0 /tmp/ctdb.socket || return 1
738     fi
739 }
740
741 #######################################
742
743 _ctdb_hack_options ()
744 {
745     local ctdb_options="$*"
746
747     # We really just want to pass CTDB_OPTIONS but on RH
748     # /etc/sysconfig/ctdb can, and frequently does, set that variable.
749     # So instead, we hack badly.  We'll add these as we use them.
750     # Note that these may still be overridden by the above file... but
751     # we tend to use the exotic options here... so that is unlikely.
752
753     case "$ctdb_options" in
754         *--start-as-stopped*)
755             export CTDB_START_AS_STOPPED="yes"
756     esac
757 }
758
759 _restart_ctdb ()
760 {
761     _ctdb_hack_options "$@"
762
763     if [ -e /etc/redhat-release ] ; then
764         service ctdb restart
765     else
766         /etc/init.d/ctdb restart
767     fi
768 }
769
770 _ctdb_start ()
771 {
772     _ctdb_hack_options "$@"
773
774     /etc/init.d/ctdb start
775 }
776
777 setup_ctdb ()
778 {
779     if [ -n "$CTDB_NODES_SOCKETS" ] ; then
780         daemons_setup
781     fi
782 }
783
784 # Common things to do after starting one or more nodes.
785 _ctdb_start_post ()
786 {
787     onnode -q 1  $CTDB_TEST_WRAPPER wait_until_healthy || return 1
788
789     echo "Setting RerecoveryTimeout to 1"
790     onnode -pq all "$CTDB setvar RerecoveryTimeout 1"
791
792     # In recent versions of CTDB, forcing a recovery like this blocks
793     # until the recovery is complete.  Hopefully this will help the
794     # cluster to stabilise before a subsequent test.
795     echo "Forcing a recovery..."
796     onnode -q 0 $CTDB recover
797     sleep_for 1
798     echo "Forcing a recovery..."
799     onnode -q 0 $CTDB recover
800
801     echo "ctdb is ready"
802 }
803
804 # This assumes that ctdbd is not running on the given node.
805 ctdb_start_1 ()
806 {
807     local pnn="$1"
808     shift # "$@" is passed to ctdbd start.
809
810     echo -n "Starting CTDB on node ${pnn}..."
811     
812     if [ -n "$CTDB_NODES_SOCKETS" ] ; then
813         daemons_start_1 $pnn "$@"
814     else
815         onnode $pnn $CTDB_TEST_WRAPPER _ctdb_start "$@"
816     fi
817
818     # If we're starting only 1 node then we're doing something weird.
819     ctdb_restart_when_done
820 }
821
822 restart_ctdb ()
823 {
824     # "$@" is passed to ctdbd start.
825
826     echo -n "Restarting CTDB"
827     if $ctdb_test_restart_scheduled ; then
828         echo -n " (scheduled)"
829     fi
830     echo "..."
831
832     local i
833     for i in $(seq 1 5) ; do
834         if [ -n "$CTDB_NODES_SOCKETS" ] ; then
835             daemons_stop
836             daemons_start "$@"
837         else
838             onnode -p all $CTDB_TEST_WRAPPER _restart_ctdb "$@"
839         fi || {
840             echo "Restart failed.  Trying again in a few seconds..."
841             sleep_for 5
842             continue
843         }
844
845         onnode -q 1  $CTDB_TEST_WRAPPER wait_until_healthy || {
846             echo "Cluster didn't become healthy.  Restarting..."
847             continue
848         }
849
850         local debug_out=$(onnode -p all ctdb status -Y 2>&1; onnode -p all ctdb scriptstatus 2>&1)
851
852         echo "Setting RerecoveryTimeout to 1"
853         onnode -pq all "$CTDB setvar RerecoveryTimeout 1"
854
855         # In recent versions of CTDB, forcing a recovery like this
856         # blocks until the recovery is complete.  Hopefully this will
857         # help the cluster to stabilise before a subsequent test.
858         echo "Forcing a recovery..."
859         onnode -q 0 $CTDB recover
860         sleep_for 1
861         echo "Forcing a recovery..."
862         onnode -q 0 $CTDB recover
863
864         # Cluster is still healthy.  Good, we're done!
865         if ! onnode 0 $CTDB_TEST_WRAPPER _cluster_is_healthy ; then
866             echo "Cluster become UNHEALTHY again.  Restarting..."
867             continue
868         fi
869
870         echo "Doing a sync..."
871         onnode -q 0 $CTDB sync
872
873         echo "ctdb is ready"
874         return 0
875     done
876
877     echo "Cluster UNHEALTHY...  too many attempts..."
878     echo "$debug_out"
879     # Try to make the calling test fail
880     status=1
881     return 1
882 }
883
884 ctdb_restart_when_done ()
885 {
886     ctdb_test_restart_scheduled=true
887 }
888
889 #######################################
890
891 install_eventscript ()
892 {
893     local script_name="$1"
894     local script_contents="$2"
895
896     if [ -n "$CTDB_TEST_REAL_CLUSTER" ] ; then
897         # The quoting here is *very* fragile.  However, we do
898         # experience the joy of installing a short script using
899         # onnode, and without needing to know the IP addresses of the
900         # nodes.
901         onnode all "f=\"\${CTDB_BASE:-/etc/ctdb}/events.d/${script_name}\" ; echo \"Installing \$f\" ; echo '${script_contents}' > \"\$f\" ; chmod 755 \"\$f\""
902     else
903         f="${CTDB_DIR}/tests/events.d/${script_name}"
904         echo "$script_contents" >"$f"
905         chmod 755 "$f"
906     fi
907 }
908
909 uninstall_eventscript ()
910 {
911     local script_name="$1"
912
913     if [ -n "$CTDB_TEST_REAL_CLUSTER" ] ; then
914         onnode all "rm -vf \"\${CTDB_BASE:-/etc/ctdb}/events.d/${script_name}\""
915     else
916         rm -vf "${CTDB_DIR}/tests/events.d/${script_name}"
917     fi
918 }
919
920 #######################################
921
922 # This section deals with the 99.ctdb_test eventscript.
923
924 # Metafunctions: Handle a ctdb-test file on a node.
925 # given event.
926 ctdb_test_eventscript_file_create ()
927 {
928     local pnn="$1"
929     local type="$2"
930
931     try_command_on_node $pnn touch "/tmp/ctdb-test-${type}.${pnn}"
932 }
933
934 ctdb_test_eventscript_file_remove ()
935 {
936     local pnn="$1"
937     local type="$2"
938
939     try_command_on_node $pnn rm -f "/tmp/ctdb-test-${type}.${pnn}"
940 }
941
942 ctdb_test_eventscript_file_exists ()
943 {
944     local pnn="$1"
945     local type="$2"
946
947     try_command_on_node $pnn test -f "/tmp/ctdb-test-${type}.${pnn}" >/dev/null 2>&1
948 }
949
950
951 # Handle a flag file on a node that is removed by 99.ctdb_test on the
952 # given event.
953 ctdb_test_eventscript_flag ()
954 {
955     local cmd="$1"
956     local pnn="$2"
957     local event="$3"
958
959     ctdb_test_eventscript_file_${cmd} "$pnn" "flag-${event}"
960 }
961
962
963 # Handle a trigger that causes 99.ctdb_test to fail it's monitor
964 # event.
965 ctdb_test_eventscript_unhealthy_trigger ()
966 {
967     local cmd="$1"
968     local pnn="$2"
969
970     ctdb_test_eventscript_file_${cmd} "$pnn" "unhealthy-trigger"
971 }
972
973 # Handle the file that 99.ctdb_test created to show that it has marked
974 # a node unhealthy because it detected the above trigger.
975 ctdb_test_eventscript_unhealthy_detected ()
976 {
977     local cmd="$1"
978     local pnn="$2"
979
980     ctdb_test_eventscript_file_${cmd} "$pnn" "unhealthy-detected"
981 }
982
983 # Handle a trigger that causes 99.ctdb_test to timeout it's monitor
984 # event.  This should cause the node to be banned.
985 ctdb_test_eventscript_timeout_trigger ()
986 {
987     local cmd="$1"
988     local pnn="$2"
989     local event="$3"
990
991     ctdb_test_eventscript_file_${cmd} "$pnn" "${event}-timeout"
992 }
993
994 # Note that the eventscript can't use the above functions!
995 ctdb_test_eventscript_install ()
996 {
997
998     local script='#!/bin/sh
999 out=$(ctdb pnn)
1000 pnn="${out#PNN:}"
1001
1002 rm -vf "/tmp/ctdb-test-flag-${1}.${pnn}"
1003
1004 trigger="/tmp/ctdb-test-unhealthy-trigger.${pnn}"
1005 detected="/tmp/ctdb-test-unhealthy-detected.${pnn}"
1006 timeout_trigger="/tmp/ctdb-test-${1}-timeout.${pnn}"
1007 case "$1" in
1008     monitor)
1009         if [ -e "$trigger" ] ; then
1010             echo "${0}: Unhealthy because \"$trigger\" detected"
1011             touch "$detected"
1012             exit 1
1013         elif [ -e "$detected" -a ! -e "$trigger" ] ; then
1014             echo "${0}: Healthy again, \"$trigger\" no longer detected"
1015             rm "$detected"
1016         fi
1017         
1018         ;;
1019     *)
1020         if [ -e "$timeout_trigger" ] ; then
1021             echo "${0}: Sleeping for a long time because \"$timeout_trigger\" detected"
1022             sleep 9999
1023         fi
1024         ;;
1025         *)
1026         
1027 esac
1028
1029 exit 0
1030 '
1031     install_eventscript "99.ctdb_test" "$script"
1032 }
1033
1034 ctdb_test_eventscript_uninstall ()
1035 {
1036     uninstall_eventscript "99.ctdb_test"
1037 }
1038
1039 # Note that this only works if you know all other monitor events will
1040 # succeed.  You also need to install the eventscript before using it.
1041 wait_for_monitor_event ()
1042 {
1043     local pnn="$1"
1044
1045     echo "Waiting for a monitor event on node ${pnn}..."
1046     ctdb_test_eventscript_flag create $pnn "monitor"
1047
1048     wait_until 120 ! ctdb_test_eventscript_flag exists $pnn "monitor"
1049
1050 }
1051
1052 # Make sure that $CTDB is set.
1053 : ${CTDB:=ctdb}