Whamcloud - gitweb
LU-9663 tests: replace “ls -s” with “stat -c '%s'” to get file size
[fs/lustre-release.git] / lustre / tests / ha.sh
1 #!/bin/bash
2 # -*- mode: Bash; tab-width: 4; indent-tabs-mode: t; -*-
3 # vim:shiftwidth=4:softtabstop=4:tabstop=4:
4 #
5 # NAME
6 #
7 #   ha.sh - test Lustre HA (aka failover) configurations
8 #
9 # SYNOPSIS
10 #
11 #   ha.sh [OPTIONS]
12 #
13 # DESCRIPTION
14 #
15 #   ha.sh tests Lustre HA (aka failover) configurations with a CRM.
16 #
17 # OPTIONS
18 #
19 #   -h
20 #       Help.
21 #
22 #   -c HOST[,...]
23 #       Specify client nodes.
24 #
25 #   -s HOST[,...]
26 #       Specify server nodes.
27 #
28 #   -v HOST[,...]
29 #       Specify victim nodes to be rebooted.
30 #
31 #   -d DIRECTORY
32 #       Choose a parent of the test directory.  "/mnt/lustre" if not specified.
33 #
34 #   -u SECONDS
35 #       Define a duration for the test. 86400 seconds if not specified.
36 #
37 #   -w
38 #       Only run the workloads; no failure will be introduced.
39 #
40 # ASSUMPTIONS
41 #
42 #   A Lustre file system is up and mounted on all client nodes.  This script
43 #   does not mount or unmount any Lustre targets or clients, let alone format
44 #   anything.
45 #
46 #   Each target has a failnode, so that workloads can continue after a power
47 #   failure.
48 #
49 #   Targets are automatically failed back when their primary node is back.  This
50 #   assumption avoids calling CRM-specific commands to trigger failbacks, making
51 #   this script more CRM-neural.
52 #
53 #   A crash dump mechanism is configured to catch LBUGs, panics, etc.
54 #
55 # WORKLOADS
56 #
57 #   Each client runs the same set of MPI and non-MPI workloads.  These
58 #   applications are run in short loops so that their exit status can be waited
59 #   for and checked within reasonable time by ha_wait_loads.
60 #
61 # PROCESS STRUCTURE AND IPC
62 #
63 #   On the node where this script is run, the processes look like this:
64 #
65 #       ~ ha.sh (ha_killer)
66 #
67 #           ~ ha.sh (ha_repeat_mpi_load ior)
68 #               ~ mpirun IOR
69 #           ~ ha.sh (ha_repeat_mpi_load simul)
70 #               ~ mpirun simul
71 #           ~ ... (one for each MPI load)
72 #
73 #           ~ ha.sh (ha_repeat_nonmpi_load client2 dbench)
74 #               ~ pdsh client2 dbench
75 #           ~ ha.sh (ha_repeat_nonmpi_load client2 iozone)
76 #               ~ pdsh client2 iozone
77 #           ~ ha.sh (ha_repeat_nonmpi_load client5 iozone)
78 #               ~ pdsh client5 iozone
79 #           ~ ... (one for each non-MPI load on each client)
80 #
81 #   Each tilde represents a process.  Indentations imply parent-children
82 #   relation.
83 #
84 #   IPC is done by files in the temporary directory.
85 #
86
87 ha_info()
88 {
89     echo "$0: $(date +%s):" "$@"
90 }
91
92 ha_error()
93 {
94     ha_info "$@" >&2
95 }
96
97 ha_trap_err()
98 {
99     local i
100
101     ha_error "Trap ERR triggered by:"
102     ha_error "    $BASH_COMMAND"
103     ha_error "Call trace:"
104     for ((i = 0; i < ${#FUNCNAME[@]}; i++)); do
105         ha_error "    ${FUNCNAME[$i]} [${BASH_SOURCE[$i]}:${BASH_LINENO[$i]}]"
106     done
107 }
108
109 trap ha_trap_err ERR
110 set -eE
111
112 declare     ha_tmp_dir=/tmp/$(basename $0)-$$
113 declare     ha_stop_file=$ha_tmp_dir/stop
114 declare     ha_fail_file=$ha_tmp_dir/fail
115 declare     ha_status_file_prefix=$ha_tmp_dir/status
116 declare -a  ha_status_files
117 declare     ha_machine_file=$ha_tmp_dir/machine_file
118 declare     ha_power_down_cmd=${POWER_DOWN:-pm -0}
119 declare     ha_power_up_cmd=${POWER_UP:-pm -1}
120 declare -a  ha_clients
121 declare -a  ha_servers
122 declare -a  ha_victims
123 declare     ha_test_dir=/mnt/lustre/$(basename $0)-$$
124 declare     ha_start_time=$(date +%s)
125 declare     ha_expected_duration=$((60 * 60 * 24))
126 declare     ha_nr_loops=0
127 declare     ha_stop_signals="SIGINT SIGTERM SIGHUP"
128 declare     ha_load_timeout=$((60 * 10))
129 declare     ha_workloads_only=false
130 declare -a  ha_mpi_load_tags=(
131     ior
132     simul
133 )
134 declare -a  ha_mpi_load_cmds=(
135     "/testsuite/tests/x86_64/rhel5/IOR/src/C/IOR -b 256m -o {}/f.ior -t 2m
136                                                  -w -W -T 1"
137     "/testsuite/tests/x86_64/rhel5/simul/simul -d {}"
138 )
139 declare -a  ha_nonmpi_load_tags=(
140     dd
141     tar
142 )
143 declare -a  ha_nonmpi_load_cmds=(
144     "dd if=/dev/zero of={}/f.dd bs=1M count=256"
145     "tar cf - /etc/fonts | tar xf - -C {}"
146 )
147
148 ha_usage()
149 {
150     ha_info "Usage: $0 -c HOST[,...] -s HOST[,...]"                         \
151             "-v HOST[,...] [-d DIRECTORY] [-u SECONDS]"
152 }
153
154 ha_process_arguments()
155 {
156     local opt
157
158     while getopts hc:s:v:d:u:w opt; do
159         case $opt in
160         h)
161             ha_usage
162             exit 0
163             ;;
164         c)
165             ha_clients=(${OPTARG//,/ })
166             ;;
167         s)
168             ha_servers=(${OPTARG//,/ })
169             ;;
170         v)
171             ha_victims=(${OPTARG//,/ })
172             ;;
173         d)
174             ha_test_dir=$OPTARG/$(basename $0)-$$
175             ;;
176         u)
177             ha_expected_duration=$OPTARG
178             ;;
179         w)
180             ha_workloads_only=true
181             ;;
182         \?)
183             ha_usage
184             exit 1
185             ;;
186         esac
187     done
188
189     if [ -z "${ha_clients[*]}" ] ||                                         \
190        [ -z "${ha_servers[*]}" ] ||                                         \
191        [ -z "${ha_victims[*]}" ]; then
192         ha_error "-c, -s, and -v are all mandatory"
193         ha_usage
194         exit 1
195     fi
196 }
197
198 ha_on()
199 {
200     local nodes=$1
201     local rc=0
202
203     shift
204     pdsh -w $nodes PATH=/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin "$@" || rc=$?
205     return $rc
206 }
207
208 ha_trap_exit()
209 {
210     if [ -e "$ha_fail_file" ]; then
211         ha_info "Test directory $ha_test_dir not removed"
212         ha_info "Temporary directory $ha_tmp_dir not removed"
213     else
214         ha_on ${ha_clients[0]} rm -rf "$ha_test_dir"
215         rm -rf "$ha_tmp_dir"
216     fi
217 }
218
219 ha_trap_stop_signals()
220 {
221     ha_info "${ha_stop_signals// /,} received"
222     touch "$ha_stop_file"
223 }
224
225 ha_sleep()
226 {
227     local n=$1
228
229     ha_info "Sleeping for ${n}s"
230     #
231     # sleep(1) could interrupted.
232     #
233     sleep $n || true
234 }
235
236 ha_lock()
237 {
238     local lock=$1
239
240     until mkdir "$lock" >/dev/null 2>&1; do
241         ha_sleep 1 >/dev/null
242     done
243 }
244
245 ha_unlock()
246 {
247     local lock=$1
248
249     rm -r "$lock"
250 }
251
252 ha_dump_logs()
253 {
254     local nodes=${1// /,}
255     local file=/tmp/$(basename $0)-$$-$(date +%s).dk
256     local lock=$ha_tmp_dir/lock-dump-logs
257
258     ha_lock "$lock"
259     ha_info "Dumping lctl log to $file"
260     ha_on $nodes "lctl dk >$file" || true
261     ha_unlock "$lock"
262 }
263
264 ha_repeat_mpi_load()
265 {
266     local load=$1
267     local status=$2
268     local tag=${ha_mpi_load_tags[$load]}
269     local cmd=${ha_mpi_load_cmds[$load]}
270     local dir=$ha_test_dir/$tag
271     local log=$ha_tmp_dir/$tag
272     local rc=0
273     local nr_loops=0
274     local start_time=$(date +%s)
275
276     cmd=${cmd//"{}"/$dir}
277
278     ha_info "Starting $tag"
279
280         while [ ! -e "$ha_stop_file" ] && ((rc == 0)); do
281                 {
282                         ha_on ${ha_clients[0]} mkdir -p "$dir" &&          \
283                         mpirun ${MACHINEFILE_OPTION} "$ha_machine_file"    \
284                                 -np ${#ha_clients[@]} $cmd &&              \
285                         ha_on ${ha_clients[0]} rm -rf "$dir"
286                 } >>"$log" 2>&1 || rc=$?
287
288                 if ((rc != 0)); then
289                         ha_dump_logs "${ha_clients[*]} ${ha_servers[*]}"
290                         touch "$ha_fail_file"
291                         touch "$ha_stop_file"
292                 fi
293                 echo $rc >"$status"
294
295                 nr_loops=$((nr_loops + 1))
296         done
297
298         avg_loop_time=$((($(date +%s) - start_time) / nr_loops))
299
300         ha_info "$tag stopped: rc $rc avg loop time $avg_loop_time"
301 }
302
303 ha_start_mpi_loads()
304 {
305     local client
306     local load
307     local tag
308     local status
309
310     for client in ${ha_clients[@]}; do
311         echo $client >>"$ha_machine_file"
312     done
313
314     for ((load = 0; load < ${#ha_mpi_load_tags[@]}; load++)); do
315         tag=${ha_mpi_load_tags[$load]}
316         status=$ha_status_file_prefix-$tag
317         ha_repeat_mpi_load $load $status &
318         ha_status_files+=("$status")
319     done
320 }
321
322 ha_repeat_nonmpi_load()
323 {
324     local client=$1
325     local load=$2
326     local status=$3
327     local tag=${ha_nonmpi_load_tags[$load]}
328     local cmd=${ha_nonmpi_load_cmds[$load]}
329     local dir=$ha_test_dir/$client-$tag
330     local log=$ha_tmp_dir/$client-$tag
331     local rc=0
332     local nr_loops=0
333     local start_time=$(date +%s)
334
335     cmd=${cmd//"{}"/$dir}
336
337     ha_info "Starting $tag on $client"
338
339     while [ ! -e "$ha_stop_file" ] && ((rc == 0)); do
340         ha_on $client "mkdir -p $dir &&                                     \
341                        $cmd &&                                              \
342                        rm -rf $dir" >>"$log" 2>&1 || rc=$?
343
344         if ((rc != 0)); then
345             ha_dump_logs "$client ${ha_servers[*]}"
346             touch "$ha_fail_file"
347             touch "$ha_stop_file"
348         fi
349         echo $rc >"$status"
350
351         nr_loops=$((nr_loops + 1))
352     done
353
354     avg_loop_time=$((($(date +%s) - start_time) / nr_loops))
355
356     ha_info "$tag on $client stopped: rc $rc avg loop time ${avg_loop_time}s"
357 }
358
359 ha_start_nonmpi_loads()
360 {
361     local client
362     local load
363     local tag
364     local status
365
366     for client in ${ha_clients[@]}; do
367         for ((load = 0; load < ${#ha_nonmpi_load_tags[@]}; load++)); do
368             tag=${ha_nonmpi_load_tags[$load]}
369             status=$ha_status_file_prefix-$tag-$client
370             ha_repeat_nonmpi_load $client $load $status &
371             ha_status_files+=("$status")
372         done
373     done
374 }
375
376 ha_start_loads()
377 {
378     trap ha_trap_stop_signals $ha_stop_signals
379     ha_start_nonmpi_loads
380     ha_start_mpi_loads
381 }
382
383 ha_stop_loads()
384 {
385     touch $ha_stop_file
386     trap - $ha_stop_signals
387     ha_info "Waiting for workloads to stop"
388     wait
389 }
390
391 ha_wait_loads()
392 {
393     local file
394     local end=$(($(date +%s) + ha_load_timeout))
395
396     ha_info "Waiting for workload status"
397     rm -f "${ha_status_files[@]}"
398     for file in "${ha_status_files[@]}"; do
399         until [ -e "$ha_stop_file" ] ||
400               [ -e "$file" ]; do
401             if (($(date +%s) >= end)); then
402                 ha_info "Timed out while waiting for load status file $file"
403                 touch "$ha_fail_file"
404                 return 1
405             fi
406             ha_sleep 1 >/dev/null
407         done
408     done
409 }
410
411 ha_power_down()
412 {
413     local node=$1
414
415     ha_info "Powering down $node"
416     $ha_power_down_cmd $node
417 }
418
419 ha_power_up()
420 {
421     local node=$1
422
423     ha_info "Powering up $node"
424     $ha_power_up_cmd $node
425 }
426
427 #
428 # rand MAX
429 #
430 # Print a random integer within [0, MAX).
431 #
432 ha_rand()
433 {
434     local max=$1
435
436     #
437     # See "5.2 Bash Variables" from "info bash".
438     #
439     echo -n $((RANDOM * max / 32768))
440 }
441
442 ha_aim()
443 {
444     local i=$(ha_rand ${#ha_victims[@]})
445
446     echo -n ${ha_victims[$i]}
447 }
448
449 ha_wait_node()
450 {
451     local node=$1
452     local end=$(($(date +%s) + 5 * 60))
453
454     ha_info "Waiting for $node to boot up"
455     until pdsh -w $node -S hostname >/dev/null 2>&1 ||
456           [ -e "$ha_stop_file" ] ||
457           (($(date +%s) >= end)); do
458         ha_sleep 1 >/dev/null
459     done
460 }
461
462 ha_summarize()
463 {
464     ha_info "---------------8<---------------"
465     ha_info "Summary:"
466     ha_info "    Duration: $(($(date +%s) - $ha_start_time))s"
467     ha_info "    Loops: $ha_nr_loops"
468 }
469
470 ha_killer()
471 {
472     local node
473
474     while (($(date +%s) < ha_start_time + ha_expected_duration)) &&
475           [ ! -e "$ha_stop_file" ]; do
476         ha_info "---------------8<---------------"
477
478         node=$(ha_aim)
479
480         ha_info "Failing $node"
481         ha_sleep $(ha_rand 10)
482         ha_power_down $node
483         ha_sleep 10
484         ha_wait_loads || break
485
486         if [ -e $ha_stop_file ]; then
487             ha_power_up $node
488             break
489         fi
490
491         ha_info "Bringing $node back"
492         ha_sleep $(ha_rand 10)
493         ha_power_up $node
494         ha_wait_node $node
495         #
496         # Wait for the failback to start.
497         #
498         ha_sleep 60
499         ha_wait_loads || break
500
501         ha_sleep $(ha_rand 20)
502
503         ha_nr_loops=$((ha_nr_loops + 1))
504         ha_info "Loop $ha_nr_loops done"
505     done
506     ha_summarize
507 }
508
509 ha_main()
510 {
511     ha_process_arguments "$@"
512
513     trap ha_trap_exit EXIT
514     mkdir "$ha_tmp_dir"
515     ha_on ${ha_clients[0]} mkdir "$ha_test_dir"
516
517     ha_start_loads
518     if ha_wait_loads; then
519         if $ha_workloads_only; then
520             ha_sleep $((60 * 60))
521         else
522             ha_killer
523         fi
524     fi
525     ha_dump_logs "${ha_clients[*]} ${ha_servers[*]}"
526     ha_stop_loads
527
528     if [ -e "$ha_fail_file" ]; then
529         exit 1
530     else
531         exit 0
532     fi
533 }
534
535 ha_main "$@"