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