Whamcloud - gitweb
LU-1187 tests: Fixes in test-framework for DNE
[fs/lustre-release.git] / lustre / scripts / lc_md.in
1 #!/bin/bash
2 #
3 # vim:expandtab:shiftwidth=4:softtabstop=4:tabstop=4:
4 #
5 # lc_md - configure Linux MD devices from a csv file
6 #
7 ################################################################################
8
9 # Usage
10 usage() {
11     cat >&2 <<EOF
12
13 Usage:  `basename $0` [options] <csv file>
14
15     This script is used to configure Linux MD devices in a Lustre cluster
16     from a csv file.
17
18     Options:
19     -a          select all the nodes from the csv file to operate on
20     -w hostname,hostname,...
21                 select the specified list of nodes (separated by commas)
22     -x hostname,hostname,...
23                 exclude the specified list of nodes (separated by commas)
24     -h          help and examples
25     -v          verbose mode
26     csv file    a spreadsheet that contains configuration parameters
27                 (separated by commas) for each Linux MD device to be
28                 configured in a Lustre cluster
29
30 EOF
31     exit 1
32 }
33
34 # Samples 
35 sample() {
36     cat <<EOF
37
38 This script is used to configure Linux MD devices in a Lustre cluster
39 from a csv file.
40
41 Each line marked with "MD" in the csv file represents one MD device.
42 The format is:
43 hostname,MD,md name,operation mode,options,raid level,component devices
44
45 hostname            hostname of the node in the cluster
46 MD                  marker of MD device line
47 md name             MD device name, e.g. /dev/md0
48 operation mode      create or remove, default is create
49 options             a "catchall" for other mdadm options, e.g. "-c 128"
50 raid level          raid level: 0,1,4,5,6,10,linear and multipath
51 component devices   block devices to be combined into the MD device
52                     Multiple devices are separated by space or by using
53                     shell expansions, e.g. "/dev/sd{a,b,c}"
54
55 Items left blank will be set to defaults.
56
57 Example:
58 -------------------------------------------------------
59 # MD devices on mgsnode
60 mgsnode,MD,/dev/md0,,-q -c 32,1,/dev/sda1 /dev/sdb1
61 mgsnode,MD,/dev/md1,,-q -c 32,1,/dev/sdc1 /dev/sdd1
62 mgsnode,MD,/dev/md2,,-q -c 32,0,/dev/md0 /dev/md1
63
64 # MD device on ostnode
65 ostnode,MD,/dev/md0,,-q -c 128,5,"/dev/sd{a,b,c,d,e}"
66 -------------------------------------------------------
67
68 EOF
69     exit 0
70 }
71
72 # Get the library of functions
73 . @scriptlibdir@/lc_common
74
75 #***************************** Global variables *****************************#
76 # All the MD device items in the csv file
77 declare -a HOST_NAME MD_NAME OP_MODE OP_OPTS RAID_LEVEL MD_DEVS
78
79 # Variables related to background executions
80 declare -a REMOTE_CMD
81 declare -a REMOTE_PID
82 declare -i pid_num=0
83
84
85 VERBOSE_OUTPUT=false
86 # Get and check the positional parameters
87 while getopts "aw:x:hv" OPTION; do
88     case $OPTION in
89     a)
90         [ -z "${SPECIFIED_NODELIST}" ] && [ -z "${EXCLUDED_NODELIST}" ] \
91         && USE_ALLNODES=true
92         ;;
93     w)
94         USE_ALLNODES=false
95         SPECIFIED_NODELIST=$OPTARG
96         ;;
97     x)
98         USE_ALLNODES=false
99         EXCLUDED_NODELIST=$OPTARG
100         ;;
101     h)
102         sample
103         ;;
104     v)
105         VERBOSE_OUTPUT=true
106         ;;
107     ?)
108         usage 
109     esac
110 done
111
112 # Toss out the parameters we've already processed
113 shift  `expr $OPTIND - 1`
114
115 # Here we expect the csv file
116 if [ $# -eq 0 ]; then
117     error_output "Missing csv file!"
118     usage
119 fi
120
121 CSV_FILE=$1
122
123 # check_md_item index
124 #
125 # Check the items required for managing MD device ${MD_NAME[index]}
126 check_md_item() {
127     # Check argument
128     if [ $# -eq 0 ]; then
129         error_output "check_md_item():"\
130                  "Missing argument!"
131         return 1
132     fi
133
134     declare -i i=$1
135
136     # Check hostname
137     if [ -z "${HOST_NAME[i]}" ]; then
138         error_output "check_md_item():"\
139                  "hostname item has null value!"
140         return 1
141     fi
142
143     # Check items required by create mode
144     if [ -z "${OP_MODE[i]}" -o "${OP_MODE[i]}" = "create" ]; then
145         # Check MD device name 
146         if [ -z "${MD_NAME[i]}" ]; then
147             error_output "check_md_item():"\
148             "md name item has null value!"
149             return 1
150         fi
151
152         if [ -z "${RAID_LEVEL[i]}" ]; then
153             error_output "check_md_item():"\
154             "raid level item of MD device ${MD_NAME[i]} has null value!"
155             return 1
156         fi
157
158         if [ -z "${MD_DEVS[i]}" ]; then
159             error_output "check_md_item():"\
160             "component devices item of ${MD_NAME[i]} has null value!"
161             return 1
162         fi
163     fi
164
165     return 0
166 }
167
168 # get_md_items csv_file
169 #
170 # Get all the MD device items in the $csv_file and do some checks.
171 get_md_items() {
172     # Check argument
173     if [ $# -eq 0 ]; then
174         error_output "get_md_items(): Missing csv file!"
175         return 1
176     fi
177
178     local CSV_FILE=$1
179     local LINE
180     local hostname
181     declare -i line_num=0
182     declare -i idx=0
183
184     while read -r LINE; do
185         let "line_num += 1"
186
187         # Skip the comment line
188         [ -z "`echo \"${LINE}\" | egrep -v \"([[:space:]]|^)#\"`" ] && continue
189
190         # Skip the non-MD line
191         [ "$(echo ${LINE} | cut -d, -f 2)" != "${MD_MARKER}" ] && continue
192
193         # Skip the host which is not specified in the host list
194         if ! ${USE_ALLNODES}; then
195             hostname=$(echo ${LINE} | cut -d, -f 1)
196             ! host_in_hostlist ${hostname} ${NODES_TO_USE} && continue
197         fi
198
199         # Parse the config line into CONFIG_ITEM
200         if ! parse_line "$LINE"; then
201             return 1    
202         fi
203
204         HOST_NAME[idx]=${CONFIG_ITEM[0]}
205         MD_NAME[idx]=${CONFIG_ITEM[2]}
206         OP_MODE[idx]=${CONFIG_ITEM[3]}
207         OP_OPTS[idx]=${CONFIG_ITEM[4]}
208         RAID_LEVEL[idx]=${CONFIG_ITEM[5]}
209         MD_DEVS[idx]=${CONFIG_ITEM[6]}
210
211         # Check some required items
212         if ! check_md_item $idx; then
213             error_output "check_md_item():"\
214                      "Occurred on line ${line_num} in ${CSV_FILE}."
215             return 1    
216         fi
217
218         let "idx += 1"
219     done < ${CSV_FILE}
220
221     return 0
222 }
223
224 # md_is_active host_name md_name
225 #
226 # Run remote command to check whether $md_name is active in @host_name
227 md_is_active() {
228     local host_name=$1
229     local md_name=$2
230     local cmd ret_str
231
232     cmd="grep -q ${md_name##*/} /proc/mdstat 2>&1"
233     ret_str=$(${REMOTE} ${host_name} "${cmd}" 2>&1)
234     if [ ${PIPESTATUS[0]} -ne 0 ]; then
235         if [ -n "${ret_str}" ]; then
236             error_output "md_is_active():"\
237             "remote command to ${host_name} error: ${ret_str}!"
238             return 2    # Error occurred
239         else
240             return 1    # inactive
241         fi
242     fi
243
244     return 0            # active
245 }
246
247 # construct_mdadm_create_cmdline index
248 #
249 # Construct the create operation command line of mdadm for ${MD_NAME[index]}
250 construct_mdadm_create_cmdline() {
251     declare -i i=$1
252     local cmd_line
253     local echo_disk disk line
254     declare -i alldisks=0 
255     declare -i raiddisks=0 
256     declare -i sparedisks=0
257
258     cmd_line="${MDADM} -C -R ${MD_NAME[i]} ${OP_OPTS[i]} -l ${RAID_LEVEL[i]}"
259
260     if [ "${OP_OPTS[i]}" != "${OP_OPTS[i]#* -n*}" ]\
261     || [ "${OP_OPTS[i]}" != "${OP_OPTS[i]#*--raid-devices*}" ]; then
262         cmd_line=${cmd_line}" ${MD_DEVS[i]}"
263         echo ${cmd_line}
264         return 0
265     fi
266
267     # FIXME: Get the number of component devices in the array
268     echo_disk="for disk in ${MD_DEVS[i]}; do echo $disk; done"
269     while read line; do
270         let "alldisks += 1"
271     done < <(${REMOTE} ${HOST_NAME[i]} "${echo_disk}")
272
273     if [ ${alldisks} -eq 0 ]; then
274         echo "`basename $0`: construct_mdadm_create_cmdline() error:"\
275         "Failed to execute remote command to get the number of"\
276         "component devices of array ${MD_NAME[i]} from host ${HOST_NAME[i]}!"
277         return 1
278     fi
279
280     # Get the specified number of spare (eXtra) devices
281     if [ "${OP_OPTS[i]}" != "${OP_OPTS[i]#* -x*}" ]; then
282         sparedisks=`echo ${OP_OPTS[i]##* -x}|awk -F" " '{print $1}'`
283     elif [ "${OP_OPTS[i]}" != "${OP_OPTS[i]#*--spare-devices*}" ]; then
284         sparedisks=`echo ${OP_OPTS[i]##*--spare-devices=}|awk -F" " '{print $1}'`
285     fi
286
287     # Get the number of raid devices in the array
288     # The number of raid devices in the array plus the number of spare devices
289     # listed on the command line must equal the number of component devices 
290     # (including "missing" devices). 
291     let "raiddisks = alldisks - sparedisks"
292
293     if [ ${raiddisks} -lt 1 ]; then
294         echo "`basename $0`: construct_mdadm_create_cmdline() error:"\
295         "Invalid number of raid devices in array ${MD_NAME[i]}: ${raiddisks}!"\
296         "Check the number of spare devices and whether all the component devices"\
297         "\"${MD_DEVS[i]}\" (except \"missing\" devices) exist in host ${HOST_NAME[i]}!"
298         return 1
299     fi
300
301     cmd_line=${cmd_line}" -n ${raiddisks} ${MD_DEVS[i]}"
302
303     echo ${cmd_line}
304     return 0
305 }
306
307 # construct_mdadm_rm_cmdline index
308 #
309 # Construct the remove operation command line of mdadm for ${MD_NAME[index]}
310 construct_mdadm_rm_cmdline() {
311     declare -i i=$1
312     local mdadm_cmd
313     local real_devs
314
315     # Deactivate the MD array, releasing all resources
316     mdadm_cmd="${MDADM} -S ${MD_NAME[i]}"
317
318     if [ -n "${MD_DEVS[i]}" ]; then
319         # Remove the "missing" devices from the component devices
320         real_devs=`echo ${MD_DEVS[i]} | sed 's/missing//g'`
321         # Over-written the superblock with zeros
322         mdadm_cmd=${mdadm_cmd}" && ${MDADM} --zero-superblock ${real_devs} || true"
323     fi
324
325     echo ${mdadm_cmd}
326     return 0
327 }
328
329 # construct_mdadm_cmdline host_name
330 #
331 # Construct the command line of mdadm to be run in $host_name
332 construct_mdadm_cmdline() {
333     MDADM_CMDLINE=
334     local host_name=$1
335     local mdadm_stop_cmd mdadm_cmd
336     local rc OK
337     declare -i i
338
339     # Construct command line
340     for ((i = 0; i < ${#HOST_NAME[@]}; i++)); do
341         mdadm_stop_cmd=
342         mdadm_cmd=
343         if [ "${host_name}" = "${HOST_NAME[i]}" ]; then
344             case "${OP_MODE[i]}" in
345             "" | create)
346                     # Check the status of the MD array
347                     md_is_active ${host_name} ${MD_NAME[i]}
348                     rc=${PIPESTATUS[0]}
349                     if [ "$rc" -eq "2" ]; then
350                         return 1
351                     elif [ "$rc" -eq "0" ]; then
352                         OK=
353                         echo -n "`basename $0`: ${MD_NAME[i]} is active on"\
354                         "${host_name}, go ahead to deactivate it and create"\
355                         "the new array? [y/n]:"
356                         read OK
357                         if [ "${OK}" = "n" ]; then
358                                 echo "`basename $0`: ${MD_NAME[i]} on host"\
359                                 "${host_name} remains as it is."
360                                 continue
361                         fi
362
363                         # Construct the remove command line
364                         mdadm_stop_cmd=$(construct_mdadm_rm_cmdline ${i})
365                     fi
366
367                     # Construct the create command line
368                     mdadm_cmd=$(construct_mdadm_create_cmdline ${i})
369                     if [ ${PIPESTATUS[0]} -ne 0 ]; then
370                         error_output "${mdadm_cmd}"
371                         return 1
372                     fi
373
374                     [ -n "${mdadm_stop_cmd}" ] && mdadm_cmd=${mdadm_stop_cmd}" && "${mdadm_cmd}
375                     ;;
376             remove)
377                     if [ -z "${MD_NAME[i]}" ]; then
378                         OK=
379                         echo -n "`basename $0`: Do you really want to remove"\
380                         "all the MD devices in the host ${HOST_NAME[i]}? [y/n]:"
381                         read OK
382                         if [ "${OK}" = "n" ]; then
383                             echo "`basename $0`: MD devices on host"\
384                             "${HOST_NAME[i]} remain as they are."
385                             continue
386                         fi
387
388                         # Construct the teardown command line
389                         mdadm_cmd="(cat /proc/mdstat | egrep \"^md[[:digit:]]\" |"
390                         mdadm_cmd=${mdadm_cmd}" while read md rest; do ${MDADM} -S /dev/\$md; done)"
391                     else
392                         # Construct the remove command line
393                         mdadm_cmd=$(construct_mdadm_rm_cmdline ${i})
394                     fi
395                     ;;
396             *)
397                 # Other operations
398                 mdadm_cmd="${MDADM} ${OP_MODE[i]} ${MD_NAME[i]} ${OP_OPTS[i]} ${MD_DEVS[i]}"
399                 ;;
400             esac
401
402             if [ -z "${MDADM_CMDLINE}" ]; then
403                 MDADM_CMDLINE=${mdadm_cmd}
404             else
405                 MDADM_CMDLINE=${MDADM_CMDLINE}" && "${mdadm_cmd}
406             fi
407         fi
408     done
409
410     return 0
411 }
412
413 # config_md_devs host_name
414 #
415 # Run remote command to configure MD devices in $host_name
416 config_md_devs() {
417     local host_name=$1
418
419     # Construct mdadm command line
420     if ! construct_mdadm_cmdline ${host_name}; then
421         return 1
422     fi
423     
424     if [ -z "${MDADM_CMDLINE}" ]; then
425         verbose_output "There are no MD devices on host ${host_name}"\
426         "needed to be configured."
427         return 0
428     fi
429
430     # Run remote command to configure MD devices in $host_name
431     verbose_output "Configuring MD devices in host ${host_name}..."
432     verbose_output "Configure command line is: \"${MDADM_CMDLINE}\""
433     REMOTE_CMD[pid_num]="${REMOTE} ${host_name} \"${MDADM_CMDLINE}\""
434     $REMOTE $host_name "export PATH=\$PATH:/sbin:/usr/sbin; $MDADM_CMDLINE" &
435     REMOTE_PID[pid_num]=$!
436     let "pid_num += 1"
437     sleep 1
438
439     return 0
440 }
441
442 # Run remote command to configure all the MD devices specified in the csv file
443 config_md() {
444     declare -i i=0
445     declare -i idx=0        # Index of NODE_NAME array
446     local host_name
447     local failed_status
448
449     # Initialize the NODE_NAME array
450     unset NODE_NAME
451
452     for ((i = 0; i < ${#HOST_NAME[@]}; i++)); do
453         host_name=${HOST_NAME[i]}
454         configured_host ${host_name} && continue
455
456         NODE_NAME[idx]=${host_name}
457         let "idx += 1"
458
459         # Run remote command to configure MD devices in $host_name
460         if ! config_md_devs ${host_name}; then
461             return 1
462         fi
463     done
464
465     if [ ${#HOST_NAME[@]} -eq 0 -o ${#REMOTE_PID[@]} -eq 0 ]; then
466         verbose_output "There are no MD devices to be configured."
467         return 0
468     fi
469
470     # Wait for the exit status of the background remote command
471     verbose_output "Waiting for the return of the remote command..."
472     failed_status=false
473     for ((pid_num = 0; pid_num < ${#REMOTE_PID[@]}; pid_num++)); do
474         wait ${REMOTE_PID[${pid_num}]}
475         if [ ${PIPESTATUS[0]} -ne 0 ]; then
476             error_output "config_md(): Failed"\
477                  "to execute \"${REMOTE_CMD[${pid_num}]}\"!"
478             failed_status=true
479         fi
480     done
481
482     if ${failed_status}; then
483         return 1
484     fi
485
486     verbose_output "All the MD devices are configured successfully!"
487     return 0
488 }
489
490 # Main flow
491 # Check the csv file
492 check_file $CSV_FILE || exit ${PIPESTATUS[0]}
493
494 # Get the list of nodes to be operated on
495 NODES_TO_USE=$(get_nodelist) || error_exit ${PIPESTATUS[0]} "$NODES_TO_USE"
496
497 # Check the node list
498 check_nodelist ${NODES_TO_USE} || exit 1
499
500 # Get all the MD device items from the csv file 
501 if ! get_md_items ${CSV_FILE}; then
502     exit 1
503 fi
504
505 # Configure the MD devices 
506 if ! config_md; then
507     exit 1
508 fi
509
510 exit 0