ctdb-tests: Add check for non-lmaster node status in integration tests
[vlendec/samba-autobuild/.git] / ctdb / tests / scripts / integration.bash
1 # Hey Emacs, this is a -*- shell-script -*- !!!  :-)
2
3 . "${TEST_SCRIPTS_DIR}/common.sh"
4
5 ######################################################################
6
7 export CTDB_TIMEOUT=60
8
9 if [ -n "$CTDB_TEST_REMOTE_DIR" ] ; then
10     CTDB_TEST_WRAPPER="${CTDB_TEST_REMOTE_DIR}/test_wrap"
11 else
12     _d=$(cd ${TEST_SCRIPTS_DIR}; echo $PWD)
13     CTDB_TEST_WRAPPER="$_d/test_wrap"
14 fi
15 export CTDB_TEST_WRAPPER
16
17 # If $VALGRIND is set then use it whenever ctdb is called, but only if
18 # $CTDB is not already set.
19 [ -n "$CTDB" ] || export CTDB="${VALGRIND}${VALGRIND:+ }ctdb"
20
21 # why???
22 PATH="${TEST_SCRIPTS_DIR}:${PATH}"
23
24 ######################################################################
25
26 ctdb_test_exit ()
27 {
28     local status=$?
29
30     trap - 0
31
32     [ $(($testfailures+0)) -eq 0 -a $status -ne 0 ] && testfailures=$status
33     status=$(($testfailures+0))
34
35     # Avoid making a test fail from this point onwards.  The test is
36     # now complete.
37     set +e
38
39     echo "*** TEST COMPLETED (RC=$status) AT $(date '+%F %T'), CLEANING UP..."
40
41     eval "$ctdb_test_exit_hook" || true
42     unset ctdb_test_exit_hook
43
44     if $ctdb_test_restart_scheduled || ! cluster_is_healthy ; then
45         echo "Restarting CTDB (scheduled)..."
46         ctdb_stop_all || true  # Might be restarting some daemons were shutdown
47
48         echo "Reconfiguring cluster..."
49         setup_ctdb
50
51         ctdb_start_all
52     else
53         # This could be made unconditional but then we might get
54         # duplication from the recovery in ctdb_start_all().  We want to
55         # leave the recovery in ctdb_start_all() so that future tests that
56         # might do a manual restart mid-test will benefit.
57         echo "Forcing a recovery..."
58         onnode 0 $CTDB recover
59     fi
60
61     exit $status
62 }
63
64 ctdb_test_exit_hook_add ()
65 {
66     ctdb_test_exit_hook="${ctdb_test_exit_hook}${ctdb_test_exit_hook:+ ; }$*"
67 }
68
69 ctdb_test_init ()
70 {
71     scriptname=$(basename "$0")
72     testfailures=0
73     ctdb_test_restart_scheduled=false
74
75     trap "ctdb_test_exit" 0
76 }
77
78 ########################################
79
80 # Sets: $out
81 try_command_on_node ()
82 {
83     local nodespec="$1" ; shift
84
85     local verbose=false
86     local onnode_opts=""
87
88     while [ "${nodespec#-}" != "$nodespec" ] ; do
89         if [ "$nodespec" = "-v" ] ; then
90             verbose=true
91         else
92             onnode_opts="${onnode_opts}${onnode_opts:+ }${nodespec}"
93         fi
94         nodespec="$1" ; shift
95     done
96
97     local cmd="$*"
98
99     out=$(onnode -q $onnode_opts "$nodespec" "$cmd" 2>&1) || {
100
101         echo "Failed to execute \"$cmd\" on node(s) \"$nodespec\""
102         echo "$out"
103         return 1
104     }
105
106     if $verbose ; then
107         echo "Output of \"$cmd\":"
108         echo "$out"
109     fi
110 }
111
112 sanity_check_output ()
113 {
114     local min_lines="$1"
115     local regexp="$2" # Should be anchored as necessary.
116     local output="$3"
117
118     local ret=0
119
120     local num_lines=$(echo "$output" | wc -l)
121     echo "There are $num_lines lines of output"
122     if [ $num_lines -lt $min_lines ] ; then
123         echo "BAD: that's less than the required number (${min_lines})"
124         ret=1
125     fi
126
127     local status=0
128     local unexpected # local doesn't pass through status of command on RHS.
129     unexpected=$(echo "$output" | egrep -v "$regexp") || status=$?
130
131     # Note that this is reversed.
132     if [ $status -eq 0 ] ; then
133         echo "BAD: unexpected lines in output:"
134         echo "$unexpected" | cat -A
135         ret=1
136     else
137         echo "Output lines look OK"
138     fi
139
140     return $ret
141 }
142
143 sanity_check_ips ()
144 {
145     local ips="$1" # list of "ip node" lines
146
147     echo "Sanity checking IPs..."
148
149     local x ipp prev
150     prev=""
151     while read x ipp ; do
152         [ "$ipp" = "-1" ] && break
153         if [ -n "$prev" -a "$ipp" != "$prev" ] ; then
154             echo "OK"
155             return 0
156         fi
157         prev="$ipp"
158     done <<<"$ips"
159
160     echo "BAD: a node was -1 or IPs are only assigned to one node:"
161     echo "$ips"
162     echo "Are you running an old version of CTDB?"
163     return 1
164 }
165
166 # This returns a list of "ip node" lines in $out
167 all_ips_on_node()
168 {
169     local node="$1"
170     try_command_on_node $node \
171         "$CTDB ip -X | awk -F'|' 'NR > 1 { print \$2, \$3 }'"
172 }
173
174 _select_test_node_and_ips ()
175 {
176     try_command_on_node any \
177         "$CTDB ip -X all | awk -F'|' 'NR > 1 { print \$2, \$3 }'"
178
179     test_node=""  # this matches no PNN
180     test_node_ips=""
181     local ip pnn
182     while read ip pnn ; do
183         if [ -z "$test_node" -a "$pnn" != "-1" ] ; then
184             test_node="$pnn"
185         fi
186         if [ "$pnn" = "$test_node" ] ; then
187             test_node_ips="${test_node_ips}${test_node_ips:+ }${ip}"
188         fi
189     done <<<"$out" # bashism to avoid problem setting variable in pipeline.
190
191     echo "Selected node ${test_node} with IPs: ${test_node_ips}."
192     test_ip="${test_node_ips%% *}"
193
194     case "$test_ip" in
195         *:*) test_prefix="${test_ip}/128" ;;
196         *)   test_prefix="${test_ip}/32"  ;;
197     esac
198
199     [ -n "$test_node" ] || return 1
200 }
201
202 select_test_node_and_ips ()
203 {
204     local timeout=10
205     while ! _select_test_node_and_ips ; do
206         echo "Unable to find a test node with IPs assigned"
207         if [ $timeout -le 0 ] ; then
208             echo "BAD: Too many attempts"
209             return 1
210         fi
211         sleep_for 1
212         timeout=$(($timeout - 1))
213     done
214
215     return 0
216 }
217
218 # Sets: mask, iface
219 get_test_ip_mask_and_iface ()
220 {
221     # Find the interface
222     try_command_on_node $test_node "$CTDB ip -v -X | awk -F'|' -v ip=$test_ip '\$2 == ip { print \$4 }'"
223     iface="$out"
224
225     if [ -z "$TEST_LOCAL_DAEMONS" ] ; then
226         # Find the netmask
227         try_command_on_node $test_node ip addr show to $test_ip
228         mask="${out##*/}"
229         mask="${mask%% *}"
230     else
231         mask="24"
232     fi
233
234     echo "$test_ip/$mask is on $iface"
235 }
236
237 ctdb_get_all_pnns ()
238 {
239     try_command_on_node -q all "$CTDB pnn"
240     all_pnns="$out"
241 }
242
243 # The subtlety is that "ctdb delip" will fail if the IP address isn't
244 # configured on a node...
245 delete_ip_from_all_nodes ()
246 {
247     _ip="$1"
248
249     ctdb_get_all_pnns
250
251     _nodes=""
252
253     for _pnn in $all_pnns ; do
254         all_ips_on_node $_pnn
255         while read _i _n ; do
256             if [ "$_ip" = "$_i" ] ; then
257                 _nodes="${_nodes}${_nodes:+,}${_pnn}"
258             fi
259         done <<<"$out" # bashism
260     done
261
262     try_command_on_node -pq "$_nodes" "$CTDB delip $_ip"
263 }
264
265 #######################################
266
267 sleep_for ()
268 {
269     echo -n "=${1}|"
270     for i in $(seq 1 $1) ; do
271         echo -n '.'
272         sleep 1
273     done
274     echo '|'
275 }
276
277 _cluster_is_healthy ()
278 {
279     $CTDB nodestatus all >/dev/null
280 }
281
282 _cluster_is_recovered ()
283 {
284     node_has_status 0 recovered
285 }
286
287 _cluster_is_ready ()
288 {
289     _cluster_is_healthy && _cluster_is_recovered
290 }
291
292 cluster_is_healthy ()
293 {
294     if onnode 0 $CTDB_TEST_WRAPPER _cluster_is_healthy ; then
295         echo "Cluster is HEALTHY"
296         if ! onnode 0 $CTDB_TEST_WRAPPER _cluster_is_recovered ; then
297           echo "WARNING: cluster in recovery mode!"
298         fi
299         return 0
300     else
301         echo "Cluster is UNHEALTHY"
302         if ! ${ctdb_test_restart_scheduled:-false} ; then
303             echo "DEBUG AT $(date '+%F %T'):"
304             local i
305             for i in "onnode -q 0 $CTDB status" "onnode -q 0 onnode all $CTDB scriptstatus" ; do
306                 echo "$i"
307                 $i || true
308             done
309         fi
310         return 1
311     fi
312 }
313
314 wait_until_ready ()
315 {
316     local timeout="${1:-120}"
317
318     echo "Waiting for cluster to become ready..."
319
320     wait_until $timeout onnode -q any $CTDB_TEST_WRAPPER _cluster_is_ready
321 }
322
323 # This function is becoming nicely overloaded.  Soon it will collapse!  :-)
324 node_has_status ()
325 {
326     local pnn="$1"
327     local status="$2"
328
329     local bits fpat mpat rpat
330     case "$status" in
331         (unhealthy)    bits="?|?|?|1|*" ;;
332         (healthy)      bits="?|?|?|0|*" ;;
333         (disconnected) bits="1|*" ;;
334         (connected)    bits="0|*" ;;
335         (banned)       bits="?|1|*" ;;
336         (unbanned)     bits="?|0|*" ;;
337         (disabled)     bits="?|?|1|*" ;;
338         (enabled)      bits="?|?|0|*" ;;
339         (stopped)      bits="?|?|?|?|1|*" ;;
340         (notstopped)   bits="?|?|?|?|0|*" ;;
341         (frozen)       fpat='^[[:space:]]+frozen[[:space:]]+1$' ;;
342         (unfrozen)     fpat='^[[:space:]]+frozen[[:space:]]+0$' ;;
343         (recovered)    rpat='^Recovery mode:RECOVERY \(1\)$' ;;
344         (notlmaster)   rpat="^hash:.* lmaster:${pnn}\$" ;;
345         *)
346             echo "node_has_status: unknown status \"$status\""
347             return 1
348     esac
349
350     if [ -n "$bits" ] ; then
351         local out x line
352
353         out=$($CTDB -X status 2>&1) || return 1
354
355         {
356             read x
357             while read line ; do
358                 # This needs to be done in 2 steps to avoid false matches.
359                 local line_bits="${line#|${pnn}|*|}"
360                 [ "$line_bits" = "$line" ] && continue
361                 [ "${line_bits#${bits}}" != "$line_bits" ] && return 0
362             done
363             return 1
364         } <<<"$out" # Yay bash!
365     elif [ -n "$fpat" ] ; then
366         $CTDB statistics -n "$pnn" | egrep -q "$fpat"
367     elif [ -n "$rpat" ] ; then
368         ! $CTDB status -n "$pnn" | egrep -q "$rpat"
369     else
370         echo 'node_has_status: unknown mode, neither $bits nor $fpat is set'
371         return 1
372     fi
373 }
374
375 wait_until_node_has_status ()
376 {
377     local pnn="$1"
378     local status="$2"
379     local timeout="${3:-30}"
380     local proxy_pnn="${4:-any}"
381
382     echo "Waiting until node $pnn has status \"$status\"..."
383
384     if ! wait_until $timeout onnode $proxy_pnn $CTDB_TEST_WRAPPER node_has_status "$pnn" "$status" ; then
385         for i in "onnode -q any $CTDB status" "onnode -q any onnode all $CTDB scriptstatus" ; do
386             echo "$i"
387             $i || true
388         done
389
390         return 1
391     fi
392
393 }
394
395 # Useful for superficially testing IP failover.
396 # IPs must be on the given node.
397 # If the first argument is '!' then the IPs must not be on the given node.
398 ips_are_on_node ()
399 {
400     local negating=false
401     if [ "$1" = "!" ] ; then
402         negating=true ; shift
403     fi
404     local node="$1" ; shift
405     local ips="$*"
406
407     local out
408
409     all_ips_on_node $node
410
411     local check
412     for check in $ips ; do
413         local ip pnn
414         while read ip pnn ; do
415             if [ "$check" = "$ip" ] ; then
416                 if [ "$pnn" = "$node" ] ; then
417                     if $negating ; then return 1 ; fi
418                 else
419                     if ! $negating ; then return 1 ; fi
420                 fi
421                 ips="${ips/${ip}}" # Remove from list
422                 break
423             fi
424             # If we're negating and we didn't see the address then it
425             # isn't hosted by anyone!
426             if $negating ; then
427                 ips="${ips/${check}}"
428             fi
429         done <<<"$out" # bashism to avoid problem setting variable in pipeline.
430     done
431
432     ips="${ips// }" # Remove any spaces.
433     [ -z "$ips" ]
434 }
435
436 wait_until_ips_are_on_node ()
437 {
438     # Go to some trouble to print a use description of what is happening
439     local not=""
440     if [ "$1" == "!" ] ; then
441         not="no longer "
442     fi
443     local node=""
444     local ips=""
445     local i
446     for i ; do
447         [ "$i" != "!" ] || continue
448         if [ -z "$node" ] ; then
449             node="$i"
450             continue
451         fi
452         ips="${ips}${ips:+, }${i}"
453     done
454     echo "Waiting for ${ips} to ${not}be assigned to node ${node}"
455
456     wait_until 60 ips_are_on_node "$@"
457 }
458
459 node_has_some_ips ()
460 {
461     local node="$1"
462
463     local out
464
465     all_ips_on_node $node
466
467     while read ip pnn ; do
468         if [ "$node" = "$pnn" ] ; then
469             return 0
470         fi
471     done <<<"$out" # bashism to avoid problem setting variable in pipeline.
472
473     return 1
474 }
475
476 wait_until_node_has_some_ips ()
477 {
478     echo "Waiting for some IPs to be assigned to node ${test_node}"
479
480     wait_until 60 node_has_some_ips "$@"
481 }
482
483 #######################################
484
485 _service_ctdb ()
486 {
487     cmd="$1"
488
489     if [ -e /etc/redhat-release ] ; then
490         service ctdb "$cmd"
491     else
492         /etc/init.d/ctdb "$cmd"
493     fi
494 }
495
496 # Stop/start CTDB on all nodes.  Override for local daemons.
497 ctdb_stop_all ()
498 {
499         onnode -p all $CTDB_TEST_WRAPPER _service_ctdb stop
500 }
501 _ctdb_start_all ()
502 {
503         onnode -p all $CTDB_TEST_WRAPPER _service_ctdb start
504 }
505
506 # Nothing needed for a cluster.  Override for local daemons.
507 setup_ctdb ()
508 {
509     :
510 }
511
512 start_ctdb_1 ()
513 {
514     onnode "$1" $CTDB_TEST_WRAPPER _service_ctdb start
515 }
516
517 stop_ctdb_1 ()
518 {
519     onnode "$1" $CTDB_TEST_WRAPPER _service_ctdb stop
520 }
521
522 restart_ctdb_1 ()
523 {
524     onnode "$1" $CTDB_TEST_WRAPPER _service_ctdb restart
525 }
526
527 ctdb_start_all ()
528 {
529     local i
530     for i in $(seq 1 5) ; do
531         _ctdb_start_all || {
532             echo "Start failed.  Trying again in a few seconds..."
533             sleep_for 5
534             continue
535         }
536
537         wait_until_ready || {
538             echo "Cluster didn't become ready.  Restarting..."
539             continue
540         }
541
542         echo "Setting RerecoveryTimeout to 1"
543         onnode -pq all "$CTDB setvar RerecoveryTimeout 1"
544
545         # In recent versions of CTDB, forcing a recovery like this
546         # blocks until the recovery is complete.  Hopefully this will
547         # help the cluster to stabilise before a subsequent test.
548         echo "Forcing a recovery..."
549         onnode -q 0 $CTDB recover
550         sleep_for 2
551
552         if ! onnode -q any $CTDB_TEST_WRAPPER _cluster_is_recovered ; then
553             echo "Cluster has gone into recovery again, waiting..."
554             wait_until 30/2 onnode -q any $CTDB_TEST_WRAPPER _cluster_is_recovered
555         fi
556
557
558         # Cluster is still healthy.  Good, we're done!
559         if ! onnode 0 $CTDB_TEST_WRAPPER _cluster_is_healthy ; then
560             echo "Cluster became UNHEALTHY again [$(date)]"
561             onnode -p all ctdb status -X 2>&1
562             onnode -p all ctdb scriptstatus 2>&1
563             echo "Restarting..."
564             continue
565         fi
566
567         echo "Doing a sync..."
568         onnode -q 0 $CTDB sync
569
570         echo "ctdb is ready"
571         return 0
572     done
573
574     echo "Cluster UNHEALTHY...  too many attempts..."
575     onnode -p all ctdb status -X 2>&1
576     onnode -p all ctdb scriptstatus 2>&1
577
578     # Try to make the calling test fail
579     status=1
580     return 1
581 }
582
583 # Does nothing on cluster and should be overridden for local daemons
584 maybe_stop_ctdb ()
585 {
586     :
587 }
588
589 ctdb_restart_when_done ()
590 {
591     ctdb_test_restart_scheduled=true
592 }
593
594 ctdb_base_show ()
595 {
596         echo "${CTDB_BASE:-${CTDB_SCRIPTS_BASE}}"
597 }
598
599 #######################################
600
601 wait_for_monitor_event ()
602 {
603     local pnn="$1"
604     local timeout=120
605
606     echo "Waiting for a monitor event on node ${pnn}..."
607
608     try_command_on_node "$pnn" $CTDB scriptstatus || {
609         echo "Unable to get scriptstatus from node $pnn"
610         return 1
611     }
612
613     local ctdb_scriptstatus_original="$out"
614     wait_until 120 _ctdb_scriptstatus_changed
615 }
616
617 _ctdb_scriptstatus_changed ()
618 {
619     try_command_on_node "$pnn" $CTDB scriptstatus || {
620         echo "Unable to get scriptstatus from node $pnn"
621         return 1
622     }
623
624     [ "$out" != "$ctdb_scriptstatus_original" ]
625 }
626
627 #######################################
628
629 nfs_test_setup ()
630 {
631     select_test_node_and_ips
632
633     nfs_first_export=$(showmount -e $test_ip | sed -n -e '2s/ .*//p')
634
635     echo "Creating test subdirectory..."
636     try_command_on_node $test_node "mktemp -d --tmpdir=$nfs_first_export"
637     nfs_test_dir="$out"
638     try_command_on_node $test_node "chmod 777 $nfs_test_dir"
639
640     nfs_mnt_d=$(mktemp -d)
641     nfs_local_file="${nfs_mnt_d}/${nfs_test_dir##*/}/TEST_FILE"
642     nfs_remote_file="${nfs_test_dir}/TEST_FILE"
643
644     ctdb_test_exit_hook_add nfs_test_cleanup
645
646     echo "Mounting ${test_ip}:${nfs_first_export} on ${nfs_mnt_d} ..."
647     mount -o timeo=1,hard,intr,vers=3 \
648         "[${test_ip}]:${nfs_first_export}" ${nfs_mnt_d}
649 }
650
651 nfs_test_cleanup ()
652 {
653     rm -f "$nfs_local_file"
654     umount -f "$nfs_mnt_d"
655     rmdir "$nfs_mnt_d"
656     onnode -q $test_node rmdir "$nfs_test_dir"
657 }
658
659 #######################################
660
661 # If the given IP is hosted then print 2 items: maskbits and iface
662 ip_maskbits_iface ()
663 {
664     _addr="$1"
665
666     case "$_addr" in
667         *:*) _family="inet6" ; _bits=128 ;;
668         *)   _family="inet"  ; _bits=32  ;;
669     esac
670
671     ip addr show to "${_addr}/${_bits}" 2>/dev/null | \
672         awk -v family="${_family}" \
673             'NR == 1 { iface = $2; sub(":$", "", iface) } \
674              $1 ~ /inet/ { mask = $2; sub(".*/", "", mask); \
675                            print mask, iface, family }'
676 }
677
678 drop_ip ()
679 {
680     _addr="${1%/*}"  # Remove optional maskbits
681
682     set -- $(ip_maskbits_iface $_addr)
683     if [ -n "$1" ] ; then
684         _maskbits="$1"
685         _iface="$2"
686         echo "Removing public address $_addr/$_maskbits from device $_iface"
687         ip addr del "$_ip/$_maskbits" dev "$_iface" >/dev/null 2>&1 || true
688     fi
689 }
690
691 drop_ips ()
692 {
693     for _ip ; do
694         drop_ip "$_ip"
695     done
696 }
697
698 #######################################
699
700 # $1: pnn, $2: DB name
701 db_get_path ()
702 {
703     try_command_on_node -v $1 $CTDB getdbstatus "$2" |
704     sed -n -e "s@^path: @@p"
705 }
706
707 # $1: pnn, $2: DB name
708 db_ctdb_cattdb_count_records ()
709 {
710     try_command_on_node -v $1 $CTDB cattdb "$2" |
711     grep '^key' | grep -v '__db_sequence_number__' |
712     wc -l
713 }
714
715 # $1: pnn, $2: DB name, $3: key string, $4: value string, $5: RSN (default 7)
716 db_ctdb_tstore ()
717 {
718     _tdb=$(db_get_path $1 "$2")
719     _rsn="${5:-7}"
720     try_command_on_node $1 $CTDB tstore "$_tdb" "$3" "$4" "$_rsn"
721 }
722
723 # $1: pnn, $2: DB name, $3: dbseqnum (must be < 255!!!!!)
724 db_ctdb_tstore_dbseqnum ()
725 {
726     # "__db_sequence_number__" + trailing 0x00
727     _key='0x5f5f64625f73657175656e63655f6e756d6265725f5f00'
728
729     # Construct 8 byte (unit64_t) database sequence number.  This
730     # probably breaks if $3 > 255
731     _value=$(printf "0x%02x%014x" $3 0)
732
733     db_ctdb_tstore $1 "$2" "$_key" "$_value"
734 }
735
736 #######################################
737
738 # Make sure that $CTDB is set.
739 : ${CTDB:=ctdb}
740
741 local="${TEST_SUBDIR}/scripts/local.bash"
742 if [ -r "$local" ] ; then
743     . "$local"
744 fi