Whamcloud - gitweb
ab741af0fdac35a3e53a487b819f1c0cbd26e0a4
[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     echo >&2 "`basename $0`: Missing csv file!"
118     usage
119 fi
120
121 # check_md_item index
122 #
123 # Check the items required for managing MD device ${MD_NAME[index]}
124 check_md_item() {
125     # Check argument
126     if [ $# -eq 0 ]; then
127         echo >&2 "`basename $0`: check_md_item() error:"\
128                  "Missing argument!"
129         return 1
130     fi
131
132     declare -i i=$1
133
134     # Check hostname
135     if [ -z "${HOST_NAME[i]}" ]; then
136         echo >&2 "`basename $0`: check_md_item() error:"\
137                  "hostname item has null value!"
138         return 1
139     fi
140
141     # Check items required by create mode
142     if [ -z "${OP_MODE[i]}" -o "${OP_MODE[i]}" = "create" ]; then
143         # Check MD device name 
144         if [ -z "${MD_NAME[i]}" ]; then
145             echo >&2 "`basename $0`: check_md_item() error:"\
146             "md name item has null value!"
147             return 1
148         fi
149
150         if [ -z "${RAID_LEVEL[i]}" ]; then
151             echo >&2 "`basename $0`: check_md_item() error:"\
152             "raid level item of MD device ${MD_NAME[i]} has null value!"
153             return 1
154         fi
155
156         if [ -z "${MD_DEVS[i]}" ]; then
157             echo >&2 "`basename $0`: check_md_item() error:"\
158             "component devices item of ${MD_NAME[i]} has null value!"
159             return 1
160         fi
161     fi
162
163     return 0
164 }
165
166 # get_md_items csv_file
167 #
168 # Get all the MD device items in the $csv_file and do some checks.
169 get_md_items() {
170     # Check argument
171     if [ $# -eq 0 ]; then
172         echo >&2 "`basename $0`: get_md_items() error: Missing csv file!"
173         return 1
174     fi
175
176     CSV_FILE=$1
177     local LINE
178     local hostname
179     declare -i line_num=0
180     declare -i idx=0
181
182     while read -r LINE; do
183         let "line_num += 1"
184
185         # Skip the comment line
186         [ -z "`echo \"${LINE}\" | egrep -v \"([[:space:]]|^)#\"`" ] && continue
187
188         # Skip the non-MD line
189         [ "$(echo ${LINE} | cut -d, -f 2)" != "${MD_MARKER}" ] && continue
190
191         # Skip the host which is not specified in the host list
192         if ! ${USE_ALLNODES}; then
193             hostname=$(echo ${LINE} | cut -d, -f 1)
194             ! host_in_hostlist ${hostname} ${NODES_TO_USE} && continue
195         fi
196
197         # Parse the config line into CONFIG_ITEM
198         if ! parse_line "$LINE"; then
199             return 1    
200         fi
201
202         HOST_NAME[idx]=${CONFIG_ITEM[0]}
203         MD_NAME[idx]=${CONFIG_ITEM[2]}
204         OP_MODE[idx]=${CONFIG_ITEM[3]}
205         OP_OPTS[idx]=${CONFIG_ITEM[4]}
206         RAID_LEVEL[idx]=${CONFIG_ITEM[5]}
207         MD_DEVS[idx]=${CONFIG_ITEM[6]}
208
209         # Check some required items
210         if ! check_md_item $idx; then
211             echo >&2 "`basename $0`: check_md_item() error:"\
212                      "Occurred on line ${line_num} in ${CSV_FILE}."
213             return 1    
214         fi
215
216         let "idx += 1"
217     done < ${CSV_FILE}
218
219     return 0
220 }
221
222 # md_is_active host_name md_name
223 #
224 # Run remote command to check whether $md_name is active in @host_name
225 md_is_active() {
226     local host_name=$1
227     local md_name=$2
228     local cmd ret_str
229
230     cmd="grep -q ${md_name##*/} /proc/mdstat 2>&1"
231     ret_str=$(${REMOTE} ${host_name} "${cmd}" 2>&1)
232     if [ ${PIPESTATUS[0]} -ne 0 ]; then
233         if [ -n "${ret_str}" ]; then
234             echo >&2 "`basename $0`: md_is_active() error:"\
235             "remote command to ${host_name} error: ${ret_str}!"
236             return 2    # Error occurred
237         else
238             return 1    # inactive
239         fi
240     fi
241
242     return 0            # active
243 }
244
245 # construct_mdadm_create_cmdline index
246 #
247 # Construct the create operation command line of mdadm for ${MD_NAME[index]}
248 construct_mdadm_create_cmdline() {
249     declare -i i=$1
250     local cmd_line
251     local echo_disk disk line
252     declare -i alldisks=0 
253     declare -i raiddisks=0 
254     declare -i sparedisks=0
255
256     cmd_line="${MDADM} -C -R ${MD_NAME[i]} ${OP_OPTS[i]} -l ${RAID_LEVEL[i]}"
257
258     if [ "${OP_OPTS[i]}" != "${OP_OPTS[i]#* -n*}" ]\
259     || [ "${OP_OPTS[i]}" != "${OP_OPTS[i]#*--raid-devices*}" ]; then
260         cmd_line=${cmd_line}" ${MD_DEVS[i]}"
261         echo ${cmd_line}
262         return 0
263     fi
264
265     # FIXME: Get the number of component devices in the array
266     echo_disk="for disk in ${MD_DEVS[i]}; do echo $disk; done"
267     while read line; do
268         let "alldisks += 1"
269     done < <(${REMOTE} ${HOST_NAME[i]} "${echo_disk}")
270
271     if [ ${alldisks} -eq 0 ]; then
272         echo "`basename $0`: construct_mdadm_create_cmdline() error:"\
273         "Failed to execute remote command to get the number of"\
274         "component devices of array ${MD_NAME[i]} from host ${HOST_NAME[i]}!"
275         return 1
276     fi
277
278     # Get the specified number of spare (eXtra) devices
279     if [ "${OP_OPTS[i]}" != "${OP_OPTS[i]#* -x*}" ]; then
280         sparedisks=`echo ${OP_OPTS[i]##* -x}|awk -F" " '{print $1}'`
281     elif [ "${OP_OPTS[i]}" != "${OP_OPTS[i]#*--spare-devices*}" ]; then
282         sparedisks=`echo ${OP_OPTS[i]##*--spare-devices=}|awk -F" " '{print $1}'`
283     fi
284
285     # Get the number of raid devices in the array
286     # The number of raid devices in the array plus the number of spare devices
287     # listed on the command line must equal the number of component devices 
288     # (including "missing" devices). 
289     let "raiddisks = alldisks - sparedisks"
290
291     if [ ${raiddisks} -lt 1 ]; then
292         echo "`basename $0`: construct_mdadm_create_cmdline() error:"\
293         "Invalid number of raid devices in array ${MD_NAME[i]}: ${raiddisks}!"\
294         "Check the number of spare devices and whether all the component devices"\
295         "\"${MD_DEVS[i]}\" (except \"missing\" devices) exist in host ${HOST_NAME[i]}!"
296         return 1
297     fi
298
299     cmd_line=${cmd_line}" -n ${raiddisks} ${MD_DEVS[i]}"
300
301     echo ${cmd_line}
302     return 0
303 }
304
305 # construct_mdadm_rm_cmdline index
306 #
307 # Construct the remove operation command line of mdadm for ${MD_NAME[index]}
308 construct_mdadm_rm_cmdline() {
309     declare -i i=$1
310     local mdadm_cmd
311     local real_devs
312
313     # Deactivate the MD array, releasing all resources
314     mdadm_cmd="${MDADM} -S ${MD_NAME[i]}"
315
316     if [ -n "${MD_DEVS[i]}" ]; then
317         # Remove the "missing" devices from the component devices
318         real_devs=`echo ${MD_DEVS[i]} | sed 's/missing//g'`
319         # Over-written the superblock with zeros
320         mdadm_cmd=${mdadm_cmd}" && ${MDADM} --zero-superblock ${real_devs} || true"
321     fi
322
323     echo ${mdadm_cmd}
324     return 0
325 }
326
327 # construct_mdadm_cmdline host_name
328 #
329 # Construct the command line of mdadm to be run in $host_name
330 construct_mdadm_cmdline() {
331     MDADM_CMDLINE=
332     local host_name=$1
333     local mdadm_stop_cmd mdadm_cmd
334     local rc OK
335     declare -i i
336
337     # Construct command line
338     for ((i = 0; i < ${#HOST_NAME[@]}; i++)); do
339         mdadm_stop_cmd=
340         mdadm_cmd=
341         if [ "${host_name}" = "${HOST_NAME[i]}" ]; then
342             case "${OP_MODE[i]}" in
343             "" | create)
344                     # Check the status of the MD array
345                     md_is_active ${host_name} ${MD_NAME[i]}
346                     rc=${PIPESTATUS[0]}
347                     if [ "$rc" -eq "2" ]; then
348                         return 1
349                     elif [ "$rc" -eq "0" ]; then
350                         OK=
351                         echo -n "`basename $0`: ${MD_NAME[i]} is active on"\
352                         "${host_name}, go ahead to deactivate it and create"\
353                         "the new array? [y/n]:"
354                         read OK
355                         if [ "${OK}" = "n" ]; then
356                                 echo "`basename $0`: ${MD_NAME[i]} on host"\
357                                 "${host_name} remains as it is."
358                                 continue
359                         fi
360
361                         # Construct the remove command line
362                         mdadm_stop_cmd=$(construct_mdadm_rm_cmdline ${i})
363                     fi
364
365                     # Construct the create command line
366                     mdadm_cmd=$(construct_mdadm_create_cmdline ${i})
367                     if [ ${PIPESTATUS[0]} -ne 0 ]; then
368                         echo >&2 "${mdadm_cmd}"
369                         return 1
370                     fi
371
372                     [ -n "${mdadm_stop_cmd}" ] && mdadm_cmd=${mdadm_stop_cmd}" && "${mdadm_cmd}
373                     ;;
374             remove)
375                     if [ -z "${MD_NAME[i]}" ]; then
376                         OK=
377                         echo -n "`basename $0`: Do you really want to remove"\
378                         "all the MD devices in the host ${HOST_NAME[i]}? [y/n]:"
379                         read OK
380                         if [ "${OK}" = "n" ]; then
381                             echo "`basename $0`: MD devices on host"\
382                             "${HOST_NAME[i]} remain as they are."
383                             continue
384                         fi
385
386                         # Construct the teardown command line
387                         mdadm_cmd="(cat /proc/mdstat | egrep \"^md[[:digit:]]\" |"
388                         mdadm_cmd=${mdadm_cmd}" while read md rest; do ${MDADM} -S /dev/\$md; done)"
389                     else
390                         # Construct the remove command line
391                         mdadm_cmd=$(construct_mdadm_rm_cmdline ${i})
392                     fi
393                     ;;
394             *)
395                 # Other operations
396                 mdadm_cmd="${MDADM} ${OP_MODE[i]} ${MD_NAME[i]} ${OP_OPTS[i]} ${MD_DEVS[i]}"
397                 ;;
398             esac
399
400             if [ -z "${MDADM_CMDLINE}" ]; then
401                 MDADM_CMDLINE=${mdadm_cmd}
402             else
403                 MDADM_CMDLINE=${MDADM_CMDLINE}" && "${mdadm_cmd}
404             fi
405         fi
406     done
407
408     return 0
409 }
410
411 # config_md_devs host_name
412 #
413 # Run remote command to configure MD devices in $host_name
414 config_md_devs() {
415     local host_name=$1
416
417     # Construct mdadm command line
418     if ! construct_mdadm_cmdline ${host_name}; then
419         return 1
420     fi
421     
422     if [ -z "${MDADM_CMDLINE}" ]; then
423         verbose_output "There are no MD devices on host ${host_name}"\
424         "needed to be configured."
425         return 0
426     fi
427
428     # Run remote command to configure MD devices in $host_name
429     verbose_output "Configuring MD devices in host ${host_name}..."
430     verbose_output "Configure command line is: \"${MDADM_CMDLINE}\""
431     REMOTE_CMD[pid_num]="${REMOTE} ${host_name} \"${MDADM_CMDLINE}\""
432     ${REMOTE} ${host_name} "${MDADM_CMDLINE}" >&2 &
433     REMOTE_PID[pid_num]=$!
434     let "pid_num += 1"
435     sleep 1
436
437     return 0
438 }
439
440 # Run remote command to configure all the MD devices specified in the csv file
441 config_md() {
442     declare -i i=0
443     declare -i idx=0        # Index of NODE_NAME array
444     local host_name
445     local failed_status
446
447     # Initialize the NODE_NAME array
448     unset NODE_NAME
449
450     for ((i = 0; i < ${#HOST_NAME[@]}; i++)); do
451         host_name=${HOST_NAME[i]}
452         configured_host ${host_name} && continue
453
454         NODE_NAME[idx]=${host_name}
455         let "idx += 1"
456
457         # Run remote command to configure MD devices in $host_name
458         if ! config_md_devs ${host_name}; then
459             return 1
460         fi
461     done
462
463     if [ ${#HOST_NAME[@]} -eq 0 -o ${#REMOTE_PID[@]} -eq 0 ]; then
464         verbose_output "There are no MD devices to be configured."
465         return 0
466     fi
467
468     # Wait for the exit status of the background remote command
469     verbose_output "Waiting for the return of the remote command..."
470     failed_status=false
471     for ((pid_num = 0; pid_num < ${#REMOTE_PID[@]}; pid_num++)); do
472         wait ${REMOTE_PID[${pid_num}]}
473         if [ ${PIPESTATUS[0]} -ne 0 ]; then
474             echo >&2 "`basename $0`: config_md() error: Failed"\
475                  "to execute \"${REMOTE_CMD[${pid_num}]}\"!"
476             failed_status=true
477         fi
478     done
479
480     if ${failed_status}; then
481         return 1
482     fi
483
484     verbose_output "All the MD devices are configured successfully!"
485     return 0
486 }
487
488 # Main flow
489 # Check the csv file
490 if ! check_file $1; then
491     exit 1    
492 fi
493
494 # Get the list of nodes to be operated on
495 NODES_TO_USE=$(get_nodelist)
496 [ ${PIPESTATUS[0]} -ne 0 ] && echo >&2 "${NODES_TO_USE}" && exit 1
497
498 # Check the node list
499 check_nodelist ${NODES_TO_USE} || exit 1
500
501 # Get all the MD device items from the csv file 
502 if ! get_md_items ${CSV_FILE}; then
503     exit 1
504 fi
505
506 # Configure the MD devices 
507 if ! config_md; then
508     exit 1
509 fi
510
511 exit 0