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