Rename $CTDB_NUM_NODES to $CTDB_TEST_NUM_DAEMONS and only set it if
[metze/ctdb/wip.git] / 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 #. /root/SOFS/autosofs/scripts/tester.bash
12
13 ctdb_test_begin ()
14 {
15     local name="$1"
16
17     teststarttime=$(date '+%s')
18     testduration=0
19
20     echo "--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--"
21     echo "Running test $name ($(date '+%T'))"
22     echo "--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--"
23 }
24
25 ctdb_test_end ()
26 {
27     local name="$1" ; shift
28     local status="$1" ; shift
29     # "$@" is command-line
30
31     local interp="SKIPPED"
32     local statstr=" (reason $*)"
33     if [ -n "$status" ] ; then
34         if [ $status -eq 0 ] ; then
35             interp="PASSED"
36             statstr=""
37             echo "ALL OK: $*"
38         else
39             interp="FAILED"
40             statstr=" (status $status)"
41             testfailures=$(($testfailures+1))
42         fi
43     fi
44
45     testduration=$(($(date +%s)-$teststarttime))
46
47     echo "=========================================================================="
48     echo "TEST ${interp}: ${name}${statstr} (duration: ${testduration}s)"
49     echo "=========================================================================="
50
51 }
52
53 test_exit ()
54 {
55     exit $(($testfailures+0))
56 }
57
58 ctdb_test_exit ()
59 {
60     local status=$?
61
62     trap - 0
63
64     [ $(($testfailures+0)) -eq 0 -a $status -ne 0 ] && testfailures=$status
65
66     eval "$ctdb_test_exit_hook"
67
68     if ! onnode 0 $CTDB_TEST_WRAPPER cluster_is_healthy ; then
69         echo "Restarting ctdb on all nodes to get back into known state..."
70         restart_ctdb
71     fi
72
73     test_exit
74 }
75
76 ctdb_test_run ()
77 {
78     local name="$1" ; shift
79     
80     [ -n "$1" ] || set -- "$name"
81
82     ctdb_test_begin "$name"
83
84     local status=0
85     "$@" || status=$?
86
87     ctdb_test_end "$name" "$status" "$*"
88     
89     return $status
90 }
91
92 ctdb_test_usage()
93 {
94     local status=${1:-2}
95     
96     cat <<EOF
97 Usage: $0 [option]
98
99 Options:        
100     -h, --help          show this screen.
101     -v, --version       show test case version.
102     --category          show the test category (ACL, CTDB, Samba ...).
103     -d, --description   show test case description.
104     --summary           show short test case summary.
105 EOF
106
107     exit $status
108 }
109
110 ctdb_test_version ()
111 {
112     [ -n "$CTDB_DIR" ] || fail "Can not determine version."
113
114     (cd "$CTDB_DIR" && git describe)
115 }
116
117 ctdb_test_cmd_options()
118 {
119     [ -n "$1" ] || return 0
120
121     case "$1" in
122         -h|--help)        ctdb_test_usage 0   ;;
123         -v|--version)     ctdb_test_version   ;;
124         --category)       echo "CTDB"         ;; 
125         -d|--description) test_info           ;;
126         --summary)        test_info | head -1 ;;
127         *)
128             echo "Error: Unknown parameter = $1"
129             echo
130             ctdb_test_usage 2
131             ;;
132     esac
133
134     exit 0
135 }
136
137 ctdb_test_init () 
138 {
139     scriptname=$(basename "$0")
140     testfailures=0
141
142     ctdb_test_cmd_options $@
143
144     trap "ctdb_test_exit" 0
145 }
146
147 ########################################
148
149 # Sets: $out
150 try_command_on_node ()
151 {
152     local nodespec="$1" ; shift
153
154     local verbose=false
155     if [ "$nodespec" = "-v" ] ; then
156         verbose=true
157         nodespec="$1" ; shift
158     fi
159     local cmd="$*"
160
161     out=$(onnode -q "$nodespec" "$cmd" 2>&1) || {
162
163         echo "Failed to execute \"$cmd\" on node(s) \"$nodespec\""
164         echo "$out"
165         return 1
166     }
167
168     if $verbose ; then
169         echo "Output of \"$cmd\":"
170         echo "$out"
171     fi
172 }
173
174 sanity_check_output ()
175 {
176     local min_lines="$1"
177     local regexp="$2" # Should be anchored as necessary.
178     local output="$3"
179
180     local ret=0
181
182     local num_lines=$(echo "$output" | wc -l)
183     echo "There are $num_lines lines of output"
184     if [ $num_lines -lt $min_lines ] ; then
185         echo "BAD: that's less than the required number (${min_lines})"
186         ret=1
187     fi
188
189     local status=0
190     local unexpected # local doesn't pass through status of command on RHS.
191     unexpected=$(echo "$output" | egrep -v "$regexp") || status=$?
192
193     # Note that this is reversed.
194     if [ $status -eq 0 ] ; then
195         echo "BAD: unexpected lines in output:"
196         echo "$unexpected" | cat -A
197         ret=1
198     else
199         echo "Output lines look OK"
200     fi
201
202     return $ret
203 }
204
205 #######################################
206
207 # Wait until either timeout expires or command succeeds.  The command
208 # will be tried once per second.
209 wait_until ()
210 {
211     local timeout="$1" ; shift # "$@" is the command...
212
213     echo -n "<${timeout}|"
214     while [ $timeout -gt 0 ] ; do
215         if "$@" ; then
216             echo '|'
217             echo "OK"
218             return 0
219         fi
220         echo -n .
221         timeout=$(($timeout - 1))
222         sleep 1
223     done
224     
225     echo "*TIMEOUT*"
226     
227     return 1
228 }
229
230 sleep_for ()
231 {
232     echo -n "=${1}|"
233     for i in $(seq 1 $1) ; do
234         echo -n '.'
235         sleep 1
236     done
237     echo '|'
238 }
239
240 _cluster_is_healthy ()
241 {
242     local out x count line
243
244     out=$(ctdb -Y status 2>&1) || return 1
245
246     {
247         read x
248         count=0
249         while read line ; do
250             count=$(($count + 1))
251             [ "${line#:*:*:}" != "0:0:0:0:" ] && return 1
252         done
253         [ $count -gt 0 ] && return $?
254     } <<<"$out" # Yay bash!
255 }
256
257 cluster_is_healthy ()
258 {
259     if _cluster_is_healthy ; then
260         echo "Cluster is HEALTHY"
261         exit 0
262     else
263         echo "Cluster is UNHEALTHY"
264         exit 1
265     fi
266 }
267
268 wait_until_healthy ()
269 {
270     local timeout="${1:-120}"
271
272     echo "Waiting for cluster to become healthy..."
273
274     wait_until 120 _cluster_is_healthy
275 }
276
277 # This function is becoming nicely overloaded.  Soon it will collapse!  :-)
278 node_has_status ()
279 {
280     local pnn="$1"
281     local status="$2"
282
283     local bits fpat mpat
284     case "$status" in
285         (unhealthy)    bits="?:?:?:1" ;;
286         (healthy)      bits="?:?:?:0" ;;
287         (disconnected) bits="1:?:?:?" ;;
288         (connected)    bits="0:?:?:?" ;;
289         (banned)       bits="?:1:?:?" ;;
290         (unbanned)     bits="?:0:?:?" ;;
291         (disabled)     bits="?:?:1:?" ;;
292         (enabled)      bits="?:?:0:?" ;;
293         (frozen)       fpat='^[[:space:]]+frozen[[:space:]]+1$' ;;
294         (unfrozen)     fpat='^[[:space:]]+frozen[[:space:]]+0$' ;;
295         (monon)        mpat='^Monitoring mode:ACTIVE \(0\)$' ;;
296         (monoff)       mpat='^Monitoring mode:DISABLED \(1\)$' ;;
297         *)
298             echo "node_has_status: unknown status \"$status\""
299             return 1
300     esac
301
302     if [ -n "$bits" ] ; then
303         local out x line
304
305         out=$(ctdb -Y status 2>&1) || return 1
306
307         {
308             read x
309             while read line ; do
310                 [ "${line#:${pnn}:*:${bits}:}" = "" ] && return 0
311             done
312             return 1
313         } <<<"$out" # Yay bash!
314     elif [ -n "$fpat" ] ; then
315         ctdb statistics -n "$pnn" | egrep -q "$fpat"
316     elif [ -n "$mpat" ] ; then
317         ctdb getmonmode -n "$pnn" | egrep -q "$mpat"
318     else
319         echo 'node_has_status: unknown mode, neither $bits nor $fpat is set'
320         return 1
321     fi
322 }
323
324 wait_until_node_has_status ()
325 {
326     local pnn="$1"
327     local status="$2"
328     local timeout="${3:-30}"
329
330     echo "Waiting until node $pnn has status \"$status\"..."
331
332     wait_until $timeout node_has_status "$pnn" "$status"
333 }
334
335 # Useful for superficially testing IP failover.
336 # IPs must be on nodes matching nodeglob.
337 ips_are_on_nodeglob ()
338 {
339     local nodeglob="$1" ; shift
340     local ips="$*"
341
342     local out
343
344     try_command_on_node 1 ctdb ip -n all
345
346     while read ip pnn ; do
347         for check in $ips ; do
348             if [ "$check" = "$ip" ] ; then
349                 case "$pnn" in
350                     ($nodeglob) : ;;
351                     (*) return 1  ;;
352                 esac
353                 ips="${ips/${ip}}" # Remove from list
354             fi
355         done
356     done <<<"$out" # bashism to avoid problem setting variable in pipeline.
357
358     ips="${ips// }" # Remove any spaces.
359     [ -z "$ips" ]
360 }
361
362 wait_until_ips_are_on_nodeglob ()
363 {
364     echo "Waiting for IPs to fail over..."
365
366     wait_until 60 ips_are_on_nodeglob "$@"
367 }
368
369 #######################################
370
371 daemons_stop ()
372 {
373     echo "Attempting to politely shutdown daemons..."
374     onnode 1 ctdb shutdown -n all || true
375
376     echo "Sleeping for a while..."
377     sleep_for 1
378
379     if pidof $CTDB_DIR/bin/ctdbd >/dev/null ; then
380         echo "Killing remaining daemons..."
381         killall $CTDB_DIR/bin/ctdbd
382
383         if pidof $CTDB_DIR/bin/ctdbd >/dev/null ; then
384             echo "Once more with feeling.."
385             killall -9 $CTDB_DIR/bin/ctdbd
386         fi
387     fi
388
389     var_dir=$CTDB_DIR/tests/var
390     rm -rf $var_dir/test.db
391 }
392
393 daemons_setup ()
394 {
395     local num_nodes="${1:-2}" # default is 2 nodes
396
397     local var_dir=$CTDB_DIR/tests/var
398
399     mkdir -p $var_dir/test.db/persistent
400
401     local nodes=$var_dir/nodes.txt
402     local public_addresses=$var_dir/public_addresses.txt
403     local no_public_addresses=$var_dir/no_public_addresses.txt
404     rm -f $nodes $public_addresses $no_public_addresses
405
406     # If there are (strictly) greater than 2 nodes then we'll randomly
407     # choose a node to have no public addresses.
408     local no_public_ips=-1
409     [ $num_nodes -gt 2 ] && no_public_ips=$(($RANDOM % $num_nodes))
410     echo "$no_public_ips" >$no_public_addresses
411
412     local i
413     for i in $(seq 1 $num_nodes) ; do
414         if [ "${CTDB_USE_IPV6}x" != "x" ]; then
415             echo ::$i >> $nodes
416             ip addr add ::$i/128 dev lo
417         else
418             echo 127.0.0.$i >> $nodes
419             # 2 public addresses on most nodes, just to make things interesting.
420             if [ $(($i - 1)) -ne $no_public_ips ] ; then
421                 echo "192.0.2.$i/24 lo" >> $public_addresses
422                 echo "192.0.2.$(($i + $num_nodes))/24 lo" >> $public_addresses
423             fi
424         fi
425     done
426 }
427
428 daemons_start ()
429 {
430     local num_nodes="${1:-2}" # default is 2 nodes
431     shift # "$@" gets passed to ctdbd
432
433     local var_dir=$CTDB_DIR/tests/var
434
435     local nodes=$var_dir/nodes.txt
436     local public_addresses=$var_dir/public_addresses.txt
437     local no_public_addresses=$var_dir/no_public_addresses.txt
438
439     local no_public_ips=-1
440     [ -r $no_public_addresses ] && read no_public_ips <$no_public_addresses
441
442     local ctdb_options="--reclock=$var_dir/rec.lock --nlist $nodes --nopublicipcheck --event-script-dir=tests/events.d --logfile=$var_dir/daemons.log -d 0 --dbdir=$var_dir/test.db --dbdir-persistent=$var_dir/test.db/persistent"
443
444     echo "Starting $num_nodes ctdb daemons..."
445     if  [ "$no_public_ips" != -1 ] ; then
446         echo "Node $no_public_ips will have no public IPs."
447     fi
448
449     for i in $(seq 0 $(($num_nodes - 1))) ; do
450         if [ $(id -u) -eq 0 ]; then
451             ctdb_options="$ctdb_options --public-interface=lo"
452         fi
453
454         if [ $i -eq $no_public_ips ] ; then
455             ctdb_options="$ctdb_options --public-addresses=/dev/null"
456         else
457             ctdb_options="$ctdb_options --public-addresses=$public_addresses"
458         fi
459
460         $VALGRIND bin/ctdbd --socket=$var_dir/sock.$i $ctdb_options "$@" || return 1
461     done
462
463     if [ -L /tmp/ctdb.socket -o ! -S /tmp/ctdb.socket ] ; then 
464         ln -sf $var_dir/sock.0 /tmp/ctdb.socket || return 1
465     fi
466 }
467
468 #######################################
469
470 _restart_ctdb ()
471 {
472     if [ -e /etc/redhat-release ] ; then
473         service ctdb restart
474     else
475         /etc/init.d/ctdb restart
476     fi
477 }
478
479 setup_ctdb ()
480 {
481     if [ -n "$CTDB_NODES_SOCKETS" ] ; then
482         daemons_setup $CTDB_TEST_NUM_DAEMONS
483     fi
484 }
485
486 restart_ctdb ()
487 {
488     if [ -n "$CTDB_NODES_SOCKETS" ] ; then
489         daemons_stop
490         daemons_start $CTDB_TEST_NUM_DAEMONS
491     else
492         onnode -pq all $CTDB_TEST_WRAPPER _restart_ctdb 
493     fi || return 1
494         
495     onnode -q 1  $CTDB_TEST_WRAPPER wait_until_healthy || return 1
496
497     echo "Setting RerecoveryTimeout to 1"
498     onnode -pq all "ctdb setvar RerecoveryTimeout 1"
499
500     # In recent versions of CTDB, forcing a recovery like this blocks
501     # until the recovery is complete.  Hopefully this will help the
502     # cluster to stabilise before a subsequent test.
503     echo "Forcing a recovery..."
504     onnode -q 0 ctdb recover
505
506     #echo "Sleeping to allow ctdb to settle..."
507     #sleep_for 10
508
509     echo "ctdb is ready"
510 }
511
512 #######################################
513
514 install_eventscript ()
515 {
516     local script_name="$1"
517     local script_contents="$2"
518
519     if [ -n "$CTDB_TEST_REAL_CLUSTER" ] ; then
520         # The quoting here is *very* fragile.  However, we do
521         # experience the joy of installing a short script using
522         # onnode, and without needing to know the IP addresses of the
523         # nodes.
524         onnode all "f=\"\${CTDB_BASE:-/etc/ctdb}/events.d/${script_name}\" ; echo \"Installing \$f\" ; echo '${script_contents}' > \"\$f\" ; chmod 755 \"\$f\""
525     else
526         f="${CTDB_DIR}/tests/events.d/${script_name}"
527         echo "$script_contents" >"$f"
528         chmod 755 "$f"
529     fi
530 }
531
532 uninstall_eventscript ()
533 {
534     local script_name="$1"
535
536     if [ -n "$CTDB_TEST_REAL_CLUSTER" ] ; then
537         onnode all "rm -vf \"\${CTDB_BASE:-/etc/ctdb}/events.d/${script_name}\""
538     else
539         rm -vf "${CTDB_DIR}/tests/events.d/${script_name}"
540     fi
541 }