Whamcloud - gitweb
LU-1187 tests: Fixes in test-framework for DNE
[fs/lustre-release.git] / lustre / scripts / lc_lvm.in
1 #!/bin/bash
2 #
3 # vim:expandtab:shiftwidth=4:softtabstop=4:tabstop=4:
4 #
5 # lc_lvm - configure Linux LVM 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 LVM 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 LVM component
28                 (PV, VG, LV) to be 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 LVM devices in a Lustre cluster
39 from a csv file.
40
41 LVM is a Logical Volume Manager for the Linux operating system. The
42 three-level components of it are PV (Physical Volume), VG (Volume Group)
43 and LV (Logical Volume).
44
45 Each line marked with "PV" in the csv file represents one or more PVs.
46 The format is:
47 hostname,PV,pv names,operation mode,options
48
49 hostname            hostname of the node in the cluster
50 PV                  marker of PV line
51 pv names            devices or loopback files to be initialized for later
52                     use by LVM or to be wiped the label, e.g. /dev/sda
53                     Multiple devices or files are separated by space or by
54                     using shell expansions, e.g. "/dev/sd{a,b,c}"
55 operation mode      create or remove, default is create
56 options             a "catchall" for other pvcreate/pvremove options
57                     e.g. "-vv"
58
59 Each line marked with "VG" in the csv file represents one VG.
60 The format is:
61 hostname,VG,vg name,operation mode,options,pv paths
62
63 hostname            hostname of the node in the cluster
64 VG                  marker of VG line
65 vg name             name of the volume group, e.g. ost_vg
66 operation mode      create or remove, default is create
67 options             a "catchall" for other vgcreate/vgremove options
68                     e.g. "-s 32M"
69 pv paths            physical volumes to construct this VG, required by
70                     create mode
71                     Multiple PVs are separated by space or by using
72                     shell expansions, e.g. "/dev/sd[k-m]1"
73
74 Each line marked with "LV" in the csv file represents one LV.
75 The format is:
76 hostname,LV,lv name,operation mode,options,lv size,vg name
77
78 hostname            hostname of the node in the cluster
79 LV                  marker of LV line
80 lv name             name of the logical volume to be created (optional)
81                     or path of the logical volume to be removed (required
82                     by remove mode)
83 operation mode      create or remove, default is create
84 options             a "catchall" for other lvcreate/lvremove options
85                     e.g. "-i 2 -I 128"
86 lv size             size [kKmMgGtT] to be allocated for the new LV
87                     Default unit is megabytes.
88 vg name             name of the VG in which the new LV will be created
89
90 Items left blank will be set to defaults.
91
92 Example:
93 -------------------------------------------------------
94 # MD/LVM devices on mgsnode
95 # Remove the LVM devices in the mgsnode
96 mgsnode,LV,/dev/mgs_vg/mdt1,remove
97 mgsnode,LV,/dev/mgs_vg/mdt2,remove
98 mgsnode,VG,mgs_vg,remove
99 mgsnode,PV,"/dev/sd{a,b}1",remove
100
101 # Create MD device in the mgsnode
102 mgsnode,MD,/dev/md0,,-q,1,/dev/sda1 /dev/sdb1
103
104
105 # MD/LVM devices on ostnode
106 # Create MD and LVM devices in the ostnode
107 ostnode,MD,/dev/md0,,-q -c 128,5,"/dev/sd{a,b,c}"
108 ostnode,MD,/dev/md1,,-q -c 128,5,"/dev/sd{d,e,f}"
109
110 ostnode,PV,/dev/md0 /dev/md1
111 ostnode,VG,ost_vg,,-s 32M,"/dev/md{0,1}"
112 ostnode,LV,ost0,,-i 2 -I 128,300G,ost_vg
113 ostnode,LV,ost1,,-i 2 -I 128,300G,ost_vg
114 -------------------------------------------------------
115
116 EOF
117     exit 0
118 }
119
120 # Get the library of functions
121 . @scriptlibdir@/lc_common
122
123 #***************************** Global variables *****************************#
124 # All the LVM device items in the csv file
125 declare -a HOST_NAME LINE_MARKER LVM_NAME OP_MODE OP_OPTS SIXTH_ITEM SEVENTH_ITEM
126
127 # Variables related to background executions
128 declare -a REMOTE_CMD
129 declare -a REMOTE_PID
130 declare -i pid_num=0
131
132
133 VERBOSE_OUTPUT=false
134 # Get and check the positional parameters
135 while getopts "aw:x:hv" OPTION; do
136     case $OPTION in
137     a)
138         [ -z "${SPECIFIED_NODELIST}" ] && [ -z "${EXCLUDED_NODELIST}" ] \
139         && USE_ALLNODES=true
140         ;;
141     w)
142         USE_ALLNODES=false
143         SPECIFIED_NODELIST=$OPTARG
144         ;;
145     x)
146         USE_ALLNODES=false
147         EXCLUDED_NODELIST=$OPTARG
148         ;;
149     h)
150         sample
151         ;;
152     v)
153         VERBOSE_OUTPUT=true
154         ;;
155     ?)
156         usage 
157     esac
158 done
159
160 # Toss out the parameters we've already processed
161 shift  `expr $OPTIND - 1`
162
163 # Here we expect the csv file
164 if [ $# -eq 0 ]; then
165     error_output "Missing csv file!"
166     usage
167 fi
168
169 CSV_FILE=$1
170
171 # check_lvm_item index
172 #
173 # Check the items required for managing LVM device ${LVM_NAME[index]}
174 check_lvm_item() {
175     # Check argument
176     if [ $# -eq 0 ]; then
177         error_output "check_lvm_item():"\
178                  "Missing argument!"
179         return 1
180     fi
181
182     declare -i i=$1
183
184     # Check hostname
185     if [ -z "${HOST_NAME[i]}" ]; then
186         error_output "check_lvm_item():"\
187                  "hostname item has null value!"
188         return 1
189     fi
190
191     # Check LVM device name 
192     if [ -z "${LVM_NAME[i]}" ] \
193     && [ "${LINE_MARKER[i]}" != "${LV_MARKER}" -a "${OP_MODE[i]}" != "remove" ]
194     then
195         error_output "check_lvm_item():"\
196                  "LVM component name item has null value!"
197         return 1
198     fi
199
200     # Check the operation mode
201     if [ -n "${OP_MODE[i]}" ] \
202     && [ "${OP_MODE[i]}" != "create" -a "${OP_MODE[i]}" != "remove" ]
203     then
204         error_output "check_lvm_item():"\
205                  "Invalid operation mode item - \"${OP_MODE[i]}\"!"
206         return 1
207     fi
208
209     # Check items required by create mode
210     if [ -z "${OP_MODE[i]}" -o "${OP_MODE[i]}" = "create" ]; then
211         if [ "${LINE_MARKER[i]}" = "${VG_MARKER}" -a -z "${SIXTH_ITEM[i]}" ]
212         then
213             error_output "check_lvm_item():"\
214             "pv paths item of vg ${LVM_NAME[i]} has null value!"
215             return 1
216         fi
217
218         if [ "${LINE_MARKER[i]}" = "${LV_MARKER}" ]; then
219             if [ -z "${SIXTH_ITEM[i]}" ]; then
220                 error_output "check_lvm_item():"\
221                          "lv size item has null value!"
222                 return 1
223             fi
224
225             if [ -z "${SEVENTH_ITEM[i]}" ]; then
226                 error_output "check_lvm_item():"\
227                          "vg name item has null value!"
228                 return 1
229             fi
230         fi
231     fi
232
233     return 0
234 }
235
236 # get_lvm_items csv_file
237 #
238 # Get all the LVM device items in the $csv_file and do some checks.
239 get_lvm_items() {
240     # Check argument
241     if [ $# -eq 0 ]; then
242         error_output "get_lvm_items(): Missing csv file!"
243         return 1
244     fi
245
246     local CSV_FILE=$1
247     local LINE line_marker
248     local hostname
249     declare -i line_num=0
250     declare -i idx=0
251
252     while read -r LINE; do
253         let "line_num += 1"
254
255         # Skip the comment line
256         [ -z "`echo \"${LINE}\" | egrep -v \"([[:space:]]|^)#\"`" ] && continue
257
258         # Skip the non-LVM line
259         line_marker=$(echo ${LINE} | cut -d, -f 2)
260         [ "${line_marker}" != "${PV_MARKER}" ] \
261         && [ "${line_marker}" != "${VG_MARKER}" ] \
262         && [ "${line_marker}" != "${LV_MARKER}" ] && continue
263
264         # Skip the host which is not specified in the host list
265         if ! ${USE_ALLNODES}; then
266             hostname=$(echo ${LINE} | cut -d, -f 1)
267             ! host_in_hostlist ${hostname} ${NODES_TO_USE} && continue
268         fi
269
270         # Parse the config line into CONFIG_ITEM
271         if ! parse_line "$LINE"; then
272             return 1    
273         fi
274
275         HOST_NAME[idx]=${CONFIG_ITEM[0]}
276         LINE_MARKER[idx]=${CONFIG_ITEM[1]}
277         LVM_NAME[idx]=${CONFIG_ITEM[2]}
278         OP_MODE[idx]=${CONFIG_ITEM[3]}
279         OP_OPTS[idx]=${CONFIG_ITEM[4]}
280         SIXTH_ITEM[idx]=${CONFIG_ITEM[5]}
281         SEVENTH_ITEM[idx]=${CONFIG_ITEM[6]}
282
283         # Check some required items
284         if ! check_lvm_item $idx; then
285             error_output "check_lvm_item():"\
286                      "Occurred on line ${line_num} in ${CSV_FILE}."
287             return 1    
288         fi
289
290         let "idx += 1"
291     done < ${CSV_FILE}
292
293     return 0
294 }
295
296 # construct_lvm_create_cmdline index
297 #
298 # Construct the creation command line for ${LVM_NAME[index]}
299 construct_lvm_create_cmdline() {
300     declare -i i=$1
301     local lvm_cmd
302
303     case "${LINE_MARKER[i]}" in
304     "${PV_MARKER}")
305         lvm_cmd="pvcreate -ff -y ${OP_OPTS[i]} ${LVM_NAME[i]}"
306         ;;
307     "${VG_MARKER}")
308         lvm_cmd="vgcreate ${OP_OPTS[i]} ${LVM_NAME[i]} ${SIXTH_ITEM[i]}"
309         ;;
310     "${LV_MARKER}")
311         if [ -z "${LVM_NAME[i]}" ]; then
312             lvm_cmd="lvcreate -L ${SIXTH_ITEM[i]} ${OP_OPTS[i]} ${SEVENTH_ITEM[i]}"
313         else
314             lvm_cmd="lvcreate -L ${SIXTH_ITEM[i]} -n ${LVM_NAME[i]} ${OP_OPTS[i]} ${SEVENTH_ITEM[i]}"
315         fi
316         ;;
317     esac
318
319     echo ${lvm_cmd}
320     return 0
321 }
322
323 # cmdline_rm_LVs vg_name
324 #
325 # Construct command line to remove all the LVs on $vg_name.
326 # If $vg_name is null, then remove all the LVs in the host.
327 cmdline_rm_LVs() {
328     local vg_name=$1
329     local lvm_rm_cmd
330
331     lvm_rm_cmd="vgchange -a n ${vg_name} &&"
332     lvm_rm_cmd=${lvm_rm_cmd}" vgdisplay -v ${vg_name} | grep \"LV Name\" | awk '{print \$3}' |"
333     lvm_rm_cmd=${lvm_rm_cmd}" while read lv; do lvremove -f \$lv; done"
334
335     echo ${lvm_rm_cmd}
336     return 0
337 }
338
339 # cmdline_rm_LV lv_path
340 #
341 # Construct command line to remove LV $lv_path
342 cmdline_rm_LV() {
343     local lv_path=$1
344     local lvm_rm_cmd
345
346     lvm_rm_cmd="lvchange -a n ${lv_path} && lvremove -f ${lv_path}"
347     echo ${lvm_rm_cmd}
348     return 0
349 }
350
351
352 # cmdline_rm_VG vg_name
353 #
354 # Construct command line to remove VG $vg_name
355 cmdline_rm_VG() {
356     local vg_name=$1
357     local lvm_rm_cmd
358
359     # Remove all the LVs on this VG
360     lvm_rm_cmd=$(cmdline_rm_LVs ${vg_name})
361
362     # Remove this VG
363     lvm_rm_cmd=${lvm_rm_cmd}" && vgremove ${vg_name}"
364     echo ${lvm_rm_cmd}
365     return 0
366 }
367
368 # cmdline_rm_VGs
369 #
370 # Construct command line to remove all the VGs in the host
371 cmdline_rm_VGs() {
372     local lvm_rm_cmd
373
374     # Remove all the LVs in the host
375     lvm_rm_cmd=$(cmdline_rm_LVs)
376
377     # Remove all the VGs in the host
378     lvm_rm_cmd=${lvm_rm_cmd}" && vgdisplay | grep \"VG Name\" | awk '{print \$3}' |"
379     lvm_rm_cmd=${lvm_rm_cmd}" while read vg; do vgremove \$vg; done"
380
381     echo ${lvm_rm_cmd}
382     return 0
383 }
384
385 # cmdline_rm_PVs
386 #
387 # Construct command line to remove all the PVs in the host
388 cmdline_rm_PVs() {
389     local lvm_rm_cmd
390
391     # Remove all the LVs and VGs in the host
392     lvm_rm_cmd=$(cmdline_rm_VGs)
393
394     # Remove all the PVs in the host
395     lvm_rm_cmd=${lvm_rm_cmd}" && pvdisplay | grep \"PV Name\" | awk '{print \$3}' |"
396     lvm_rm_cmd=${lvm_rm_cmd}" while read pv; do pvremove -ff -y \$pv; done"
397
398     echo ${lvm_rm_cmd}
399     return 0
400 }
401
402 # construct_lvm_teardown_cmdline index
403 #
404 # Construct the teardown command line for LVM devices in ${HOST_NAME[index]}
405 construct_lvm_teardown_cmdline() {
406     declare -i i=$1
407     local lvm_rm_cmd
408
409     case "${LINE_MARKER[i]}" in
410     "${LV_MARKER}")
411         lvm_rm_cmd=$(cmdline_rm_LVs ${SEVENTH_ITEM[i]})
412         ;;
413     "${VG_MARKER}")
414         # Remove all the VGs in the host
415         lvm_rm_cmd=$(cmdline_rm_VGs)
416         ;;
417     "${PV_MARKER}")
418         # Remove all the PVs in the host
419         lvm_rm_cmd=$(cmdline_rm_PVs)
420         ;;
421     esac
422
423     echo ${lvm_rm_cmd}
424     return 0
425 }
426
427 # construct_lvm_rm_cmdline index
428 #
429 # Construct the remove command line for LVM device ${LVM_NAME[index]}
430 construct_lvm_rm_cmdline() {
431     declare -i i=$1
432     local lvm_rm_cmd
433                         
434     case "${LINE_MARKER[i]}" in
435     "${LV_MARKER}")
436         lvm_rm_cmd=$(cmdline_rm_LV ${LVM_NAME[i]})
437         ;;
438     "${VG_MARKER}")
439         lvm_rm_cmd=$(cmdline_rm_VG ${LVM_NAME[i]})
440         ;;
441     "${PV_MARKER}")
442         lvm_rm_cmd="pvremove -ff -y ${LVM_NAME[i]}"
443         ;;
444     esac
445
446     echo ${lvm_rm_cmd}
447     return 0
448 }
449
450 # construct_lvm_cmdline host_name
451 #
452 # Construct the command line of LVM utilities to be run in the $host_name
453 construct_lvm_cmdline() {
454     LVM_CMDLINE=
455     local host_name=$1
456     local lvm_cmd
457     declare -i i
458
459     # Construct command line
460     for ((i = 0; i < ${#HOST_NAME[@]}; i++)); do
461         lvm_cmd=
462         if [ "${host_name}" = "${HOST_NAME[i]}" ]; then
463             case "${OP_MODE[i]}" in
464             "" | create)
465                     # Construct the create command line
466                     lvm_cmd=$(construct_lvm_create_cmdline ${i})
467                     ;;
468             remove)
469                     if [ -z "${LVM_NAME[i]}" ]; then
470                         # Construct the teardown command line
471                         lvm_cmd=$(construct_lvm_teardown_cmdline ${i})
472                     else    # Remove instead of teardown
473                         # Construct the remove command line
474                         lvm_cmd=$(construct_lvm_rm_cmdline ${i})
475                     fi
476                     ;;
477             *)
478                 error_output "construct_lvm_cmdline():"\
479                          "Invalid operation mode - \"${OP_MODE[i]}\"!"
480                 return 1
481                 ;;
482             esac
483
484             if [ -z "${LVM_CMDLINE}" ]; then
485                 LVM_CMDLINE=${lvm_cmd}
486             else
487                 LVM_CMDLINE=${LVM_CMDLINE}" && "${lvm_cmd}
488             fi
489         fi
490     done
491
492     return 0
493 }
494
495 # config_lvm_devs host_name
496 #
497 # Run remote command to configure LVM devices in $host_name
498 config_lvm_devs() {
499     local host_name=$1
500
501     # Construct the LVM utilities command line
502     if ! construct_lvm_cmdline ${host_name}; then
503         return 1
504     fi
505     
506     if [ -z "${LVM_CMDLINE}" ]; then
507         verbose_output "There are no LVM devices on host ${host_name}"\
508         "needed to be configured."
509         return 0
510     fi
511
512     # Run remote command to configure LVM devices in $host_name
513     verbose_output "Configuring LVM devices in host ${host_name}..."
514     verbose_output "Configure command line is: \"${LVM_CMDLINE}\""
515     REMOTE_CMD[pid_num]="${REMOTE} ${host_name} \"${LVM_CMDLINE}\""
516     $REMOTE $host_name "export PATH=\$PATH:/sbin:/usr/sbin; $LVM_CMDLINE" &
517     REMOTE_PID[pid_num]=$!
518     let "pid_num += 1"
519
520     return 0
521 }
522
523 # Run remote command to configure all the LVM devices specified
524 # in the csv file
525 config_lvm() {
526     declare -i i=0
527     declare -i idx=0        # Index of NODE_NAME array
528     local host_name
529     local failed_status
530
531     # Initialize the NODE_NAME array
532     unset NODE_NAME
533
534     for ((i = 0; i < ${#HOST_NAME[@]}; i++)); do
535         host_name=${HOST_NAME[i]}
536         configured_host ${host_name} && continue
537
538         NODE_NAME[idx]=${host_name}
539         let "idx += 1"
540
541         # Run remote command to configure LVM devices in $host_name
542         if ! config_lvm_devs ${host_name}; then
543             return 1
544         fi
545     done
546
547     if [ ${#HOST_NAME[@]} -eq 0 -o ${#REMOTE_PID[@]} -eq 0 ]; then
548         verbose_output "There are no LVM devices to be configured."
549         return 0
550     fi
551
552     # Wait for the exit status of the background remote command
553     verbose_output "Waiting for the return of the remote command..."
554     failed_status=false
555     for ((pid_num = 0; pid_num < ${#REMOTE_PID[@]}; pid_num++)); do
556         wait ${REMOTE_PID[${pid_num}]}
557         if [ ${PIPESTATUS[0]} -ne 0 ]; then
558             error_output "config_lvm(): Failed"\
559                  "to execute \"${REMOTE_CMD[${pid_num}]}\"!"
560             failed_status=true
561         fi
562     done
563
564     if ${failed_status}; then
565         return 1
566     fi
567
568     verbose_output "All the LVM devices are configured successfully!"
569     return 0
570 }
571
572 # Main flow
573 # Check the csv file
574 check_file $CSV_FILE || exit ${PIPESTATUS[0]}
575
576 # Get the list of nodes to be operated on
577 NODES_TO_USE=$(get_nodelist) || error_exit ${PIPESTATUS[0]} "$NODES_TO_USE"
578
579 # Check the node list
580 check_nodelist ${NODES_TO_USE} || exit 1
581
582 # Get all the LVM device items from the csv file 
583 if ! get_lvm_items ${CSV_FILE}; then
584     exit 1
585 fi
586
587 # Configure the LVM devices 
588 if ! config_lvm; then
589     exit 1
590 fi
591
592 exit 0