Whamcloud - gitweb
Branch HEAD
[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     echo >&2 "`basename $0`: Missing csv file!"
166     usage
167 fi
168
169 # check_lvm_item index
170 #
171 # Check the items required for managing LVM device ${LVM_NAME[index]}
172 check_lvm_item() {
173     # Check argument
174     if [ $# -eq 0 ]; then
175         echo >&2 "`basename $0`: check_lvm_item() error:"\
176                  "Missing argument!"
177         return 1
178     fi
179
180     declare -i i=$1
181
182     # Check hostname
183     if [ -z "${HOST_NAME[i]}" ]; then
184         echo >&2 "`basename $0`: check_lvm_item() error:"\
185                  "hostname item has null value!"
186         return 1
187     fi
188
189     # Check LVM device name 
190     if [ -z "${LVM_NAME[i]}" ] \
191     && [ "${LINE_MARKER[i]}" != "${LV_MARKER}" -a "${OP_MODE[i]}" != "remove" ]
192     then
193         echo >&2 "`basename $0`: check_lvm_item() error:"\
194                  "LVM component name item has null value!"
195         return 1
196     fi
197
198     # Check the operation mode
199     if [ -n "${OP_MODE[i]}" ] \
200     && [ "${OP_MODE[i]}" != "create" -a "${OP_MODE[i]}" != "remove" ]
201     then
202         echo >&2 "`basename $0`: check_lvm_item() error:"\
203                  "Invalid operation mode item - \"${OP_MODE[i]}\"!"
204         return 1
205     fi
206
207     # Check items required by create mode
208     if [ -z "${OP_MODE[i]}" -o "${OP_MODE[i]}" = "create" ]; then
209         if [ "${LINE_MARKER[i]}" = "${VG_MARKER}" -a -z "${SIXTH_ITEM[i]}" ]
210         then
211             echo >&2 "`basename $0`: check_lvm_item() error:"\
212             "pv paths item of vg ${LVM_NAME[i]} has null value!"
213             return 1
214         fi
215
216         if [ "${LINE_MARKER[i]}" = "${LV_MARKER}" ]; then
217             if [ -z "${SIXTH_ITEM[i]}" ]; then
218                 echo >&2 "`basename $0`: check_lvm_item() error:"\
219                          "lv size item has null value!"
220                 return 1
221             fi
222
223             if [ -z "${SEVENTH_ITEM[i]}" ]; then
224                 echo >&2 "`basename $0`: check_lvm_item() error:"\
225                          "vg name item has null value!"
226                 return 1
227             fi
228         fi
229     fi
230
231     return 0
232 }
233
234 # get_lvm_items csv_file
235 #
236 # Get all the LVM device items in the $csv_file and do some checks.
237 get_lvm_items() {
238     # Check argument
239     if [ $# -eq 0 ]; then
240         echo >&2 "`basename $0`: get_lvm_items() error: Missing csv file!"
241         return 1
242     fi
243
244     CSV_FILE=$1
245     local LINE line_marker
246     local hostname
247     declare -i line_num=0
248     declare -i idx=0
249
250     while read -r LINE; do
251         let "line_num += 1"
252
253         # Skip the comment line
254         [ -z "`echo \"${LINE}\" | egrep -v \"([[:space:]]|^)#\"`" ] && continue
255
256         # Skip the non-LVM line
257         line_marker=$(echo ${LINE} | cut -d, -f 2)
258         [ "${line_marker}" != "${PV_MARKER}" ] \
259         && [ "${line_marker}" != "${VG_MARKER}" ] \
260         && [ "${line_marker}" != "${LV_MARKER}" ] && continue
261
262         # Skip the host which is not specified in the host list
263         if ! ${USE_ALLNODES}; then
264             hostname=$(echo ${LINE} | cut -d, -f 1)
265             ! host_in_hostlist ${hostname} ${NODES_TO_USE} && continue
266         fi
267
268         # Parse the config line into CONFIG_ITEM
269         if ! parse_line "$LINE"; then
270             return 1    
271         fi
272
273         HOST_NAME[idx]=${CONFIG_ITEM[0]}
274         LINE_MARKER[idx]=${CONFIG_ITEM[1]}
275         LVM_NAME[idx]=${CONFIG_ITEM[2]}
276         OP_MODE[idx]=${CONFIG_ITEM[3]}
277         OP_OPTS[idx]=${CONFIG_ITEM[4]}
278         SIXTH_ITEM[idx]=${CONFIG_ITEM[5]}
279         SEVENTH_ITEM[idx]=${CONFIG_ITEM[6]}
280
281         # Check some required items
282         if ! check_lvm_item $idx; then
283             echo >&2 "`basename $0`: check_lvm_item() error:"\
284                      "Occurred on line ${line_num} in ${CSV_FILE}."
285             return 1    
286         fi
287
288         let "idx += 1"
289     done < ${CSV_FILE}
290
291     return 0
292 }
293
294 # construct_lvm_create_cmdline index
295 #
296 # Construct the creation command line for ${LVM_NAME[index]}
297 construct_lvm_create_cmdline() {
298     declare -i i=$1
299     local lvm_cmd
300
301     case "${LINE_MARKER[i]}" in
302     "${PV_MARKER}")
303         lvm_cmd="pvcreate -ff -y ${OP_OPTS[i]} ${LVM_NAME[i]}"
304         ;;
305     "${VG_MARKER}")
306         lvm_cmd="vgcreate ${OP_OPTS[i]} ${LVM_NAME[i]} ${SIXTH_ITEM[i]}"
307         ;;
308     "${LV_MARKER}")
309         if [ -z "${LVM_NAME[i]}" ]; then
310             lvm_cmd="lvcreate -L ${SIXTH_ITEM[i]} ${OP_OPTS[i]} ${SEVENTH_ITEM[i]}"
311         else
312             lvm_cmd="lvcreate -L ${SIXTH_ITEM[i]} -n ${LVM_NAME[i]} ${OP_OPTS[i]} ${SEVENTH_ITEM[i]}"
313         fi
314         ;;
315     esac
316
317     echo ${lvm_cmd}
318     return 0
319 }
320
321 # cmdline_rm_LVs vg_name
322 #
323 # Construct command line to remove all the LVs on $vg_name.
324 # If $vg_name is null, then remove all the LVs in the host.
325 cmdline_rm_LVs() {
326     local vg_name=$1
327     local lvm_rm_cmd
328
329     lvm_rm_cmd="vgchange -a n ${vg_name} &&"
330     lvm_rm_cmd=${lvm_rm_cmd}" vgdisplay -v ${vg_name} | grep \"LV Name\" | awk '{print \$3}' |"
331     lvm_rm_cmd=${lvm_rm_cmd}" while read lv; do lvremove -f \$lv; done"
332
333     echo ${lvm_rm_cmd}
334     return 0
335 }
336
337 # cmdline_rm_LV lv_path
338 #
339 # Construct command line to remove LV $lv_path
340 cmdline_rm_LV() {
341     local lv_path=$1
342     local lvm_rm_cmd
343
344     lvm_rm_cmd="lvchange -a n ${lv_path} && lvremove -f ${lv_path}"
345     echo ${lvm_rm_cmd}
346     return 0
347 }
348
349
350 # cmdline_rm_VG vg_name
351 #
352 # Construct command line to remove VG $vg_name
353 cmdline_rm_VG() {
354     local vg_name=$1
355     local lvm_rm_cmd
356
357     # Remove all the LVs on this VG
358     lvm_rm_cmd=$(cmdline_rm_LVs ${vg_name})
359
360     # Remove this VG
361     lvm_rm_cmd=${lvm_rm_cmd}" && vgremove ${vg_name}"
362     echo ${lvm_rm_cmd}
363     return 0
364 }
365
366 # cmdline_rm_VGs
367 #
368 # Construct command line to remove all the VGs in the host
369 cmdline_rm_VGs() {
370     local lvm_rm_cmd
371
372     # Remove all the LVs in the host
373     lvm_rm_cmd=$(cmdline_rm_LVs)
374
375     # Remove all the VGs in the host
376     lvm_rm_cmd=${lvm_rm_cmd}" && vgdisplay | grep \"VG Name\" | awk '{print \$3}' |"
377     lvm_rm_cmd=${lvm_rm_cmd}" while read vg; do vgremove \$vg; done"
378
379     echo ${lvm_rm_cmd}
380     return 0
381 }
382
383 # cmdline_rm_PVs
384 #
385 # Construct command line to remove all the PVs in the host
386 cmdline_rm_PVs() {
387     local lvm_rm_cmd
388
389     # Remove all the LVs and VGs in the host
390     lvm_rm_cmd=$(cmdline_rm_VGs)
391
392     # Remove all the PVs in the host
393     lvm_rm_cmd=${lvm_rm_cmd}" && pvdisplay | grep \"PV Name\" | awk '{print \$3}' |"
394     lvm_rm_cmd=${lvm_rm_cmd}" while read pv; do pvremove -ff -y \$pv; done"
395
396     echo ${lvm_rm_cmd}
397     return 0
398 }
399
400 # construct_lvm_teardown_cmdline index
401 #
402 # Construct the teardown command line for LVM devices in ${HOST_NAME[index]}
403 construct_lvm_teardown_cmdline() {
404     declare -i i=$1
405     local lvm_rm_cmd
406
407     case "${LINE_MARKER[i]}" in
408     "${LV_MARKER}")
409         lvm_rm_cmd=$(cmdline_rm_LVs ${SEVENTH_ITEM[i]})
410         ;;
411     "${VG_MARKER}")
412         # Remove all the VGs in the host
413         lvm_rm_cmd=$(cmdline_rm_VGs)
414         ;;
415     "${PV_MARKER}")
416         # Remove all the PVs in the host
417         lvm_rm_cmd=$(cmdline_rm_PVs)
418         ;;
419     esac
420
421     echo ${lvm_rm_cmd}
422     return 0
423 }
424
425 # construct_lvm_rm_cmdline index
426 #
427 # Construct the remove command line for LVM device ${LVM_NAME[index]}
428 construct_lvm_rm_cmdline() {
429     declare -i i=$1
430     local lvm_rm_cmd
431                         
432     case "${LINE_MARKER[i]}" in
433     "${LV_MARKER}")
434         lvm_rm_cmd=$(cmdline_rm_LV ${LVM_NAME[i]})
435         ;;
436     "${VG_MARKER}")
437         lvm_rm_cmd=$(cmdline_rm_VG ${LVM_NAME[i]})
438         ;;
439     "${PV_MARKER}")
440         lvm_rm_cmd="pvremove -ff -y ${LVM_NAME[i]}"
441         ;;
442     esac
443
444     echo ${lvm_rm_cmd}
445     return 0
446 }
447
448 # construct_lvm_cmdline host_name
449 #
450 # Construct the command line of LVM utilities to be run in the $host_name
451 construct_lvm_cmdline() {
452     LVM_CMDLINE=
453     local host_name=$1
454     local lvm_cmd
455     declare -i i
456
457     # Construct command line
458     for ((i = 0; i < ${#HOST_NAME[@]}; i++)); do
459         lvm_cmd=
460         if [ "${host_name}" = "${HOST_NAME[i]}" ]; then
461             case "${OP_MODE[i]}" in
462             "" | create)
463                     # Construct the create command line
464                     lvm_cmd=$(construct_lvm_create_cmdline ${i})
465                     ;;
466             remove)
467                     if [ -z "${LVM_NAME[i]}" ]; then
468                         # Construct the teardown command line
469                         lvm_cmd=$(construct_lvm_teardown_cmdline ${i})
470                     else    # Remove instead of teardown
471                         # Construct the remove command line
472                         lvm_cmd=$(construct_lvm_rm_cmdline ${i})
473                     fi
474                     ;;
475             *)
476                 echo >&2 "`basename $0`: construct_lvm_cmdline() error:"\
477                          "Invalid operation mode - \"${OP_MODE[i]}\"!"
478                 return 1
479                 ;;
480             esac
481
482             if [ -z "${LVM_CMDLINE}" ]; then
483                 LVM_CMDLINE=${lvm_cmd}
484             else
485                 LVM_CMDLINE=${LVM_CMDLINE}" && "${lvm_cmd}
486             fi
487         fi
488     done
489
490     return 0
491 }
492
493 # config_lvm_devs host_name
494 #
495 # Run remote command to configure LVM devices in $host_name
496 config_lvm_devs() {
497     local host_name=$1
498
499     # Construct the LVM utilities command line
500     if ! construct_lvm_cmdline ${host_name}; then
501         return 1
502     fi
503     
504     if [ -z "${LVM_CMDLINE}" ]; then
505         verbose_output "There are no LVM devices on host ${host_name}"\
506         "needed to be configured."
507         return 0
508     fi
509
510     # Run remote command to configure LVM devices in $host_name
511     verbose_output "Configuring LVM devices in host ${host_name}..."
512     verbose_output "Configure command line is: \"${LVM_CMDLINE}\""
513     REMOTE_CMD[pid_num]="${REMOTE} ${host_name} \"${LVM_CMDLINE}\""
514     ${REMOTE} ${host_name} "(${EXPORT_PATH} ${LVM_CMDLINE})" >&2 &
515     REMOTE_PID[pid_num]=$!
516     let "pid_num += 1"
517
518     return 0
519 }
520
521 # Run remote command to configure all the LVM devices specified
522 # in the csv file
523 config_lvm() {
524     declare -i i=0
525     declare -i idx=0        # Index of NODE_NAME array
526     local host_name
527     local failed_status
528
529     # Initialize the NODE_NAME array
530     unset NODE_NAME
531
532     for ((i = 0; i < ${#HOST_NAME[@]}; i++)); do
533         host_name=${HOST_NAME[i]}
534         configured_host ${host_name} && continue
535
536         NODE_NAME[idx]=${host_name}
537         let "idx += 1"
538
539         # Run remote command to configure LVM devices in $host_name
540         if ! config_lvm_devs ${host_name}; then
541             return 1
542         fi
543     done
544
545     if [ ${#HOST_NAME[@]} -eq 0 -o ${#REMOTE_PID[@]} -eq 0 ]; then
546         verbose_output "There are no LVM devices to be configured."
547         return 0
548     fi
549
550     # Wait for the exit status of the background remote command
551     verbose_output "Waiting for the return of the remote command..."
552     failed_status=false
553     for ((pid_num = 0; pid_num < ${#REMOTE_PID[@]}; pid_num++)); do
554         wait ${REMOTE_PID[${pid_num}]}
555         if [ ${PIPESTATUS[0]} -ne 0 ]; then
556             echo >&2 "`basename $0`: config_lvm() error: Failed"\
557                  "to execute \"${REMOTE_CMD[${pid_num}]}\"!"
558             failed_status=true
559         fi
560     done
561
562     if ${failed_status}; then
563         return 1
564     fi
565
566     verbose_output "All the LVM devices are configured successfully!"
567     return 0
568 }
569
570 # Main flow
571 # Check the csv file
572 if ! check_file $1; then
573     exit 1    
574 fi
575
576 # Get the list of nodes to be operated on
577 NODES_TO_USE=$(get_nodelist)
578 [ ${PIPESTATUS[0]} -ne 0 ] && echo >&2 "${NODES_TO_USE}" && exit 1
579
580 # Check the node list
581 check_nodelist ${NODES_TO_USE} || exit 1
582
583 # Get all the LVM device items from the csv file 
584 if ! get_lvm_items ${CSV_FILE}; then
585     exit 1
586 fi
587
588 # Configure the LVM devices 
589 if ! config_lvm; then
590     exit 1
591 fi
592
593 exit 0