Whamcloud - gitweb
c89dca413396b62bb295111843cea169a211bc8a
[fs/lustre-release.git] / lustre / scripts / lustre_config.in
1 #!/bin/bash
2
3 # vim:expandtab:shiftwidth=4:softtabstop=4:tabstop=4:
4
5 #
6 # lustre_config - format and set up multiple lustre servers from a csv file
7 #
8 # This script is used to parse each line of a spreadsheet (csv file) and 
9 # execute remote commands to format (mkfs.lustre) every Lustre target 
10 # that will be part of the Lustre cluster.
11
12 # In addition, it can also verify the network connectivity and hostnames in 
13 # the cluster, configure Linux MD/LVM devices and produce High-Availability
14 # software configurations for Heartbeat or CluManager.
15 #
16 ################################################################################
17
18 # Usage
19 usage() {
20     cat >&2 <<EOF
21
22 Usage:  `basename $0` [options] <csv file>
23
24     This script is used to format and set up multiple lustre servers from a
25     csv file.
26
27     Options:
28     -h          help and examples
29     -a          select all the nodes from the csv file to operate on
30     -w hostname,hostname,...
31                 select the specified list of nodes (separated by commas) to
32                 operate on rather than all the nodes in the csv file
33     -x hostname,hostname,...
34                 exclude the specified list of nodes (separated by commas)
35     -t HAtype   produce High-Availability software configurations
36                 The argument following -t is used to indicate the High-
37                 Availability software type. The HA software types which 
38                 are currently supported are: hbv1 (Heartbeat version 1)
39                 and hbv2 (Heartbeat version 2).
40     -n          no net - don't verify network connectivity and hostnames
41                 in the cluster
42     -d          configure Linux MD/LVM devices before formatting the
43                 Lustre targets
44     -f          force-format the Lustre targets using --reformat option
45     -m          no fstab change - don't modify /etc/fstab to add the new
46                 Lustre targets
47                 If using this option, then the value of "mount options"
48                 item in the csv file will be passed to mkfs.lustre, else
49                 the value will be added into the /etc/fstab.
50     -v          verbose mode
51     csv file    a spreadsheet that contains configuration parameters
52                 (separated by commas) for each target in a Lustre cluster
53
54 EOF
55     exit 1
56 }
57
58 # Samples 
59 sample() {
60     cat <<EOF
61
62 This script is used to parse each line of a spreadsheet (csv file) and 
63 execute remote commands to format (mkfs.lustre) every Lustre target 
64 that will be part of the Lustre cluster.
65
66 It can also optionally: 
67  * verify the network connectivity and hostnames in the cluster
68  * configure Linux MD/LVM devices
69  * modify /etc/modprobe.conf to add Lustre networking info
70  * add the Lustre server info to /etc/fstab
71  * produce configurations for Heartbeat or CluManager.
72
73 There are 5 kinds of line formats in the csv file. They represent the following 
74 targets:
75 1) Linux MD device
76 The format is:
77 hostname,MD,md name,operation mode,options,raid level,component devices
78
79 hostname            hostname of the node in the cluster
80 MD                  marker of MD device line
81 md name             MD device name, e.g. /dev/md0
82 operation mode      create or remove, default is create
83 options             a "catchall" for other mdadm options, e.g. "-c 128"
84 raid level          raid level: 0,1,4,5,6,10,linear and multipath
85 component devices   block devices to be combined into the MD device
86                     Multiple devices are separated by space or by using
87                     shell expansions, e.g. "/dev/sd{a,b,c}"
88
89 2) Linux LVM PV (Physical Volume)
90 The format is:
91 hostname,PV,pv names,operation mode,options
92
93 hostname            hostname of the node in the cluster
94 PV                  marker of PV line
95 pv names            devices or loopback files to be initialized for later
96                     use by LVM or to be wiped the label, e.g. /dev/sda
97                     Multiple devices or files are separated by space or by
98                     using shell expansions, e.g. "/dev/sd{a,b,c}"
99 operation mode      create or remove, default is create
100 options             a "catchall" for other pvcreate/pvremove options
101                     e.g. "-vv"
102
103 3) Linux LVM VG (Volume Group)
104 The format is:
105 hostname,VG,vg name,operation mode,options,pv paths
106
107 hostname            hostname of the node in the cluster
108 VG                  marker of VG line
109 vg name             name of the volume group, e.g. ost_vg
110 operation mode      create or remove, default is create
111 options             a "catchall" for other vgcreate/vgremove options
112                     e.g. "-s 32M"
113 pv paths            physical volumes to construct this VG, required by
114                     create mode
115                     Multiple PVs are separated by space or by using
116                     shell expansions, e.g. "/dev/sd[k-m]1"
117
118 4) Linux LVM LV (Logical Volume)
119 The format is:
120 hostname,LV,lv name,operation mode,options,lv size,vg name
121
122 hostname            hostname of the node in the cluster
123 LV                  marker of LV line
124 lv name             name of the logical volume to be created (optional)
125                     or path of the logical volume to be removed (required
126                     by remove mode)
127 operation mode      create or remove, default is create
128 options             a "catchall" for other lvcreate/lvremove options
129                     e.g. "-i 2 -I 128"
130 lv size             size [kKmMgGtT] to be allocated for the new LV
131                     Default unit is megabytes.
132 vg name             name of the VG in which the new LV will be created
133
134 5) Lustre target
135 The format is:
136 hostname,module_opts,device name,mount point,device type,fsname,mgs nids,index,
137 format options,mkfs options,mount options,failover nids
138
139 hostname            hostname of the node in the cluster, must match "uname -n"
140 module_opts         Lustre networking module options
141 device name         Lustre target (block device or loopback file)
142 mount point         Lustre target mount point
143 device type         Lustre target type (mgs, mdt, ost, mgs|mdt, mdt|mgs)
144 fsname              Lustre filesystem name, should be limited to 8 characters 
145                     Default is "lustre".
146 mgs nids            NID(s) of remote mgs node, required for mdt and ost targets
147                     If this item is not given for an mdt, it is assumed that
148                     the mdt will also be an mgs, according to mkfs.lustre.
149 index               Lustre target index
150 format options      a "catchall" contains options to be passed to mkfs.lustre
151                     "--device-size", "--param", etc. all goes into this item.
152 mkfs options        format options to be wrapped with --mkfsoptions="" and
153                     passed to mkfs.lustre
154 mount options       If this script is invoked with "-m" option, then the value of
155                     this item will be wrapped with --mountfsoptions="" and passed
156                     to mkfs.lustre, else the value will be added into /etc/fstab.
157 failover nids       NID(s) of failover partner node
158
159 All the NIDs in one node are delimited by commas (','). When multiple nodes are
160 specified, they are delimited by a colon (':').
161
162 Items left blank will be set to defaults.
163
164 Example 1 - Simple, with combo MGS/MDT:
165 -------------------------------------------------------------------------------
166 # combo mdt/mgs
167 lustre-mgs,options lnet networks=tcp,/tmp/mgs,/mnt/mgs,mgs|mdt,,,,--device-size=10240
168
169 # ost0
170 lustre-ost,options lnet networks=tcp,/tmp/ost0,/mnt/ost0,ost,,lustre-mgs@tcp0,,--device-size=10240
171
172 # ost1
173 lustre-ost,options lnet networks=tcp,/tmp/ost1,/mnt/ost1,ost,,lustre-mgs@tcp0,,--device-size=10240
174 -------------------------------------------------------------------------------
175
176 Example 2 - Separate MGS/MDT, two networks interfaces:
177 -------------------------------------------------------------------------------
178 # mgs
179 lustre-mgs1,options lnet 'networks="tcp,elan"',/dev/sda,/mnt/mgs,mgs,,,,--quiet --param="sys.timeout=50",,"defaults,noauto","lustre-mgs2,2@elan"
180
181 # mdt
182 lustre-mdt1,options lnet 'networks="tcp,elan"',/dev/sdb,/mnt/mdt,mdt,lustre2,"lustre-mgs1,1@elan:lustre-mgs2,2@elan",,--quiet --param="lov.stripesize=4194304",-J size=16,"defaults,noauto",lustre-mdt2
183
184 # ost
185 lustre-ost1,options lnet 'networks="tcp,elan"',/dev/sdc,/mnt/ost,ost,lustre2,"lustre-mgs1,1@elan:lustre-mgs2,2@elan",,--quiet,-I 512,"defaults,noauto",lustre-ost2
186 -------------------------------------------------------------------------------
187
188 Example 3 - with combo MGS/MDT failover pair and OST failover pair:
189 -------------------------------------------------------------------------------
190 # combo mgs/mdt
191 lustre-mgs1,options lnet networks=tcp,/tmp/mgs,/mnt/mgs,mgs|mdt,,,,--quiet --device-size=10240,,,lustre-mgs2@tcp0
192
193 # combo mgs/mdt backup (--noformat)
194 lustre-mgs2,options lnet networks=tcp,/tmp/mgs,/mnt/mgs,mgs|mdt,,,,--quiet --device-size=10240 --noformat,,,lustre-mgs1@tcp0
195
196 # ost
197 lustre-ost1,options lnet networks=tcp,/tmp/ost1,/mnt/ost1,ost,,"lustre-mgs1@tcp0:lustre-mgs2@tcp0",,--quiet --device-size=10240,,,lustre-ost2@tcp0
198
199 # ost backup (--noformat) (note different device name)
200 lustre-ost2,options lnet networks=tcp,/tmp/ost2,/mnt/ost2,ost,,"lustre-mgs1@tcp0:lustre-mgs2@tcp0",,--quiet --device-size=10240 --noformat,,,lustre-ost1@tcp0
201 -------------------------------------------------------------------------------
202
203 Example 4 - Configure Linux MD/LVM devices before formatting Lustre targets:
204 -------------------------------------------------------------------------------
205 # MD device on mgsnode
206 mgsnode,MD,/dev/md0,,-q,1,/dev/sda1 /dev/sdb1
207
208 # MD/LVM devices on ostnode
209 ostnode,MD,/dev/md0,,-q -c 128,5,"/dev/sd{a,b,c}"
210 ostnode,MD,/dev/md1,,-q -c 128,5,"/dev/sd{d,e,f}"
211 ostnode,PV,/dev/md0 /dev/md1
212 ostnode,VG,ost_vg,,-s 32M,/dev/md0 /dev/md1
213 ostnode,LV,ost0,,-i 2 -I 128,300G,ost_vg
214 ostnode,LV,ost1,,-i 2 -I 128,300G,ost_vg
215
216 # combo mgs/mdt
217 mgsnode,options lnet networks=tcp,/dev/md0,/mnt/mgs,mgs|mdt,,,,--quiet
218
219 # ost0
220 ostnode,options lnet networks=tcp,/dev/ost_vg/ost0,/mnt/ost0,ost,,mgsnode,,--quiet
221
222 # ost1
223 ostnode,options lnet networks=tcp,/dev/ost_vg/ost1,/mnt/ost1,ost,,mgsnode,,--quiet
224 -------------------------------------------------------------------------------
225
226 EOF
227     exit 0
228 }
229
230 # Get the library of functions
231 . @scriptlibdir@/lc_common
232
233 #***************************** Global variables *****************************#
234 declare -a MGS_NODENAME             # node names of the MGS servers
235 declare -a MGS_IDX                  # indexes of MGSs in the global arrays
236 declare -i MGS_NUM                  # number of MGS servers in the cluster
237 declare -i INIT_IDX
238
239 declare -a NODE_NAMES               # node names in the failover group
240 declare -a TARGET_OPTS              # target services in one failover group
241
242 # All the items in the csv file
243 declare -a HOST_NAME MODULE_OPTS DEVICE_NAME MOUNT_POINT DEVICE_TYPE FS_NAME
244 declare -a MGS_NIDS INDEX FORMAT_OPTIONS MKFS_OPTIONS MOUNT_OPTIONS FAILOVERS
245
246 # Heartbeat software requires that node names in the configuration directive
247 # must (normally) match the "uname -n" of that machine. Since the value of the
248 # "failover nids" field in the csv file is the NID(s) of failover partner node,
249 # we have to figure out the corresponding hostname of that node.
250 declare -a FAILOVERS_NAMES
251
252 VERIFY_CONNECT=true
253 CONFIG_MD_LVM=false
254 MODIFY_FSTAB=true
255 VERBOSE_OUTPUT=false
256 # Get and check the positional parameters
257 while getopts "aw:x:t:ndfmhv" OPTION; do
258     case $OPTION in
259     a)
260         [ -z "${SPECIFIED_NODELIST}" ] && [ -z "${EXCLUDED_NODELIST}" ] \
261         && USE_ALLNODES=true
262         NODELIST_OPT="${NODELIST_OPT} -a"
263         ;;
264     w)
265         USE_ALLNODES=false
266         SPECIFIED_NODELIST=$OPTARG
267         NODELIST_OPT="${NODELIST_OPT} -w ${SPECIFIED_NODELIST}"
268         ;;
269     x)
270         USE_ALLNODES=false
271         EXCLUDED_NODELIST=$OPTARG
272         NODELIST_OPT="${NODELIST_OPT} -x ${EXCLUDED_NODELIST}"
273         ;;
274     t)
275         HATYPE_OPT=$OPTARG
276         if [ "${HATYPE_OPT}" != "${HBVER_HBV1}" ] \
277         && [ "${HATYPE_OPT}" != "${HBVER_HBV2}" ] \
278         && [ "${HATYPE_OPT}" != "${HATYPE_CLUMGR}" ]; then
279             echo >&2 $"`basename $0`: Invalid HA software type" \
280                       "- ${HATYPE_OPT}!"
281             usage
282         fi
283         ;;
284     n)
285         VERIFY_CONNECT=false
286         ;;
287     d)
288         CONFIG_MD_LVM=true
289         ;;
290     f)
291         REFORMAT_OPTION=$"--reformat "
292         ;;
293     m)
294         MODIFY_FSTAB=false
295         ;;
296     h)
297         sample
298         ;;
299     v)
300         VERBOSE_OPT=$" -v"
301         VERBOSE_OUTPUT=true
302         ;;
303     ?)
304         usage 
305     esac
306 done
307
308 # Toss out the parameters we've already processed
309 shift  `expr $OPTIND - 1`
310
311 # Here we expect the csv file
312 if [ $# -eq 0 ]; then
313     echo >&2 $"`basename $0`: Missing csv file!"
314     usage
315 fi
316
317 # Check the items required for OSTs, MDTs and MGS
318 #
319 # When formatting an OST, the following items: hostname, module_opts,
320 # device name, device type and mgs nids, cannot have null value.
321 #
322 # When formatting an MDT or MGS, the following items: hostname,
323 # module_opts, device name and device type, cannot have null value.
324 check_item() {
325     # Check argument
326     if [ $# -eq 0 ]; then
327         echo >&2 $"`basename $0`: check_item() error: Missing argument"\
328                   "for function check_item()!"
329         return 1
330     fi
331
332     declare -i i=$1
333
334     # Check hostname, module_opts, device name and device type
335     if [ -z "${HOST_NAME[i]}" ]||[ -z "${MODULE_OPTS[i]}" ]\
336     ||[ -z "${DEVICE_NAME[i]}" ]||[ -z "${DEVICE_TYPE[i]}" ]; then
337         echo >&2 $"`basename $0`: check_item() error: Some required"\
338                   "item has null value! Check hostname, module_opts,"\
339                   "device name and device type!"
340         return 1
341     fi
342
343     # Check mgs nids
344     if [ "${DEVICE_TYPE[i]}" = "ost" ]&&[ -z "${MGS_NIDS[i]}" ]; then
345         echo >&2 $"`basename $0`: check_item() error: OST's mgs nids"\
346                   "item has null value!"
347         return 1
348     fi
349
350     # Check mount point
351     if [ -z "${MOUNT_POINT[i]}" ]; then
352         echo >&2 $"`basename $0`: check_item() error: mount"\
353                   "point item of target ${DEVICE_NAME[i]} has null value!"
354         return 1
355     fi
356
357     return 0
358 }
359
360 # Get the number of MGS nodes in the cluster
361 get_mgs_num() {
362     INIT_IDX=0
363     MGS_NUM=${#MGS_NODENAME[@]}
364     [ -z "${MGS_NODENAME[0]}" ] && let "INIT_IDX += 1" \
365     && let "MGS_NUM += 1"
366 }
367
368 # is_mgs_node hostname
369 # Verify whether @hostname is a MGS node
370 is_mgs_node() {
371     local host_name=$1
372     declare -i i
373
374     get_mgs_num
375     for ((i = ${INIT_IDX}; i < ${MGS_NUM}; i++)); do
376         [ "${MGS_NODENAME[i]}" = "${host_name}" ] && return 0
377     done
378
379     return 1
380 }
381
382 # Check whether the MGS nodes are in the same failover group
383 check_mgs_group() {
384     declare -i i
385     declare -i j
386     declare -i idx
387     local mgs_node
388
389     get_mgs_num
390     for ((i = ${INIT_IDX}; i < ${MGS_NUM}; i++)); do
391         mgs_node=${MGS_NODENAME[i]}
392         for ((j = ${INIT_IDX}; j < ${MGS_NUM}; j++)); do
393           [ "${MGS_NODENAME[j]}" = "${mgs_node}" ] && continue 1
394
395           idx=${MGS_IDX[j]}
396           if [ "${FAILOVERS_NAMES[idx]#*$mgs_node*}" = "${FAILOVERS_NAMES[idx]}" ]
397           then
398             echo >&2 $"`basename $0`: check_mgs_group() error:"\
399             "MGS node ${mgs_node} is not in the ${HOST_NAME[idx]}"\
400             "failover group!"
401             return 1
402           fi
403         done
404     done
405
406     return 0
407 }
408
409 # Get and check MGS servers.
410 # There should be no more than one MGS specified in the entire csv file.
411 check_mgs() {
412     declare -i i
413     declare -i j
414     declare -i exp_idx    # Index of explicit MGS servers
415     declare -i imp_idx    # Index of implicit MGS servers
416     local is_exp_mgs is_imp_mgs
417     local mgs_node
418
419     # Initialize the MGS_NODENAME and MGS_IDX arrays
420     unset MGS_NODENAME
421     unset MGS_IDX
422
423     exp_idx=1
424     imp_idx=1
425     for ((i = 0; i < ${#HOST_NAME[@]}; i++)); do
426         is_exp_mgs=false
427         is_imp_mgs=false
428
429         # Check whether this node is an explicit MGS node 
430         # or an implicit one
431         if [ "${DEVICE_TYPE[i]#*mgs*}" != "${DEVICE_TYPE[i]}" ]; then
432             verbose_output "Explicit MGS target" \
433             "${DEVICE_NAME[i]} in host ${HOST_NAME[i]}."
434             is_exp_mgs=true
435         fi
436
437         if [ "${DEVICE_TYPE[i]}" = "mdt" -a -z "${MGS_NIDS[i]}" ]; then
438             verbose_output "Implicit MGS target" \
439             "${DEVICE_NAME[i]} in host ${HOST_NAME[i]}."
440             is_imp_mgs=true
441         fi
442
443         # Get and check MGS servers
444         if ${is_exp_mgs} || ${is_imp_mgs}; then
445             # Check whether more than one MGS target in one MGS node
446             if is_mgs_node ${HOST_NAME[i]}; then
447                 echo >&2 $"`basename $0`: check_mgs() error:"\
448                 "More than one MGS target in the same node -"\
449                 "\"${HOST_NAME[i]}\"!"
450                 return 1
451             fi
452
453             # Get and check primary MGS server and backup MGS server        
454             if [ "${FORMAT_OPTIONS[i]}" = "${FORMAT_OPTIONS[i]#*noformat*}" ]
455             then
456                 # Primary MGS server
457                 if [ -z "${MGS_NODENAME[0]}" ]; then
458                     if [ "${is_exp_mgs}" = "true" -a ${imp_idx} -gt 1 ] \
459                     || [ "${is_imp_mgs}" = "true" -a ${exp_idx} -gt 1 ]; then
460                         echo >&2 $"`basename $0`: check_mgs() error:"\
461                         "There exist both explicit and implicit MGS"\
462                         "targets in the csv file!"
463                         return 1
464                     fi
465                     MGS_NODENAME[0]=${HOST_NAME[i]}
466                     MGS_IDX[0]=$i
467                 else
468                     mgs_node=${MGS_NODENAME[0]}
469                     if [ "${FAILOVERS_NAMES[i]#*$mgs_node*}" = "${FAILOVERS_NAMES[i]}" ]
470                     then
471                         echo >&2 $"`basename $0`: check_mgs() error:"\
472                         "More than one primary MGS nodes in the csv" \
473                         "file - ${MGS_NODENAME[0]} and ${HOST_NAME[i]}!"
474                     else
475                         echo >&2 $"`basename $0`: check_mgs() error:"\
476                         "MGS nodes ${MGS_NODENAME[0]} and ${HOST_NAME[i]}"\
477                         "are failover pair, one of them should use"\
478                         "\"--noformat\" in the format options item!"
479                     fi
480                     return 1
481                 fi
482             else    # Backup MGS server
483                 if [ "${is_exp_mgs}" = "true" -a ${imp_idx} -gt 1 ] \
484                 || [ "${is_imp_mgs}" = "true" -a ${exp_idx} -gt 1 ]; then
485                     echo >&2 $"`basename $0`: check_mgs() error:"\
486                     "There exist both explicit and implicit MGS"\
487                     "targets in the csv file!"
488                     return 1
489                 fi
490
491                 if ${is_exp_mgs}; then # Explicit MGS
492                     MGS_NODENAME[exp_idx]=${HOST_NAME[i]}
493                     MGS_IDX[exp_idx]=$i
494                     exp_idx=$(( exp_idx + 1 ))
495                 else    # Implicit MGS
496                     MGS_NODENAME[imp_idx]=${HOST_NAME[i]}
497                     MGS_IDX[imp_idx]=$i
498                     imp_idx=$(( imp_idx + 1 ))
499                 fi
500             fi
501         fi #End of "if ${is_exp_mgs} || ${is_imp_mgs}"
502     done
503
504     # Check whether the MGS nodes are in the same failover group
505     if ! check_mgs_group; then
506         return 1
507     fi
508
509     return 0
510 }
511
512 # Construct the command line of mkfs.lustre
513 construct_mkfs_cmdline() {
514     # Check argument
515     if [ $# -eq 0 ]; then
516         echo >&2 $"`basename $0`: construct_mkfs_cmdline() error:"\
517                   "Missing argument for function construct_mkfs_cmdline()!"
518         return 1
519     fi
520
521     declare -i i=$1
522     local mgsnids mgsnids_str
523     local failnids failnids_str
524
525     MKFS_CMD=${MKFS}$" "
526     MKFS_CMD=${MKFS_CMD}${REFORMAT_OPTION}
527
528     case "${DEVICE_TYPE[i]}" in
529     "ost")
530         MKFS_CMD=${MKFS_CMD}$"--ost "
531         ;;
532     "mdt")
533         MKFS_CMD=${MKFS_CMD}$"--mdt "
534         ;;
535     "mgs")
536         MKFS_CMD=${MKFS_CMD}$"--mgs "
537         ;;
538     "mdt|mgs" | "mgs|mdt")
539         MKFS_CMD=${MKFS_CMD}$"--mdt --mgs "
540         ;;
541     *)
542         echo >&2 $"`basename $0`: construct_mkfs_cmdline() error:"\
543                   "Invalid device type - \"${DEVICE_TYPE[i]}\"!"
544         return 1
545         ;;
546     esac
547
548     if [ -n "${FS_NAME[i]}" ]; then
549         MKFS_CMD=${MKFS_CMD}$"--fsname="${FS_NAME[i]}$" "
550     fi
551
552     if [ -n "${MGS_NIDS[i]}" ]; then
553         mgsnids_str=${MGS_NIDS[i]}
554         for mgsnids in ${mgsnids_str//:/ }; do
555             MKFS_CMD=${MKFS_CMD}$"--mgsnode="${mgsnids}$" "
556         done
557     fi
558
559     if [ -n "${INDEX[i]}" ]; then
560         MKFS_CMD=${MKFS_CMD}$"--index="${INDEX[i]}$" "
561     fi
562
563     if [ -n "${FORMAT_OPTIONS[i]}" ]; then
564         MKFS_CMD=${MKFS_CMD}${FORMAT_OPTIONS[i]}$" "
565     fi
566
567     if [ -n "${MKFS_OPTIONS[i]}" ]; then
568         MKFS_CMD=${MKFS_CMD}$"--mkfsoptions="$"\""${MKFS_OPTIONS[i]}$"\""$" "
569     fi
570
571     if [ -n "${MOUNT_OPTIONS[i]}" ]; then
572         if ! ${MODIFY_FSTAB}; then
573             MKFS_CMD=${MKFS_CMD}$"--mountfsoptions="$"\""${MOUNT_OPTIONS[i]}$"\""$" "
574         fi
575     fi
576
577     if [ -n "${FAILOVERS[i]}" ]; then
578         failnids_str=${FAILOVERS[i]}
579         for failnids in ${failnids_str//:/ }; do
580             MKFS_CMD=${MKFS_CMD}$"--failnode="${failnids}$" "
581         done
582     fi
583
584     MKFS_CMD=${MKFS_CMD}${DEVICE_NAME[i]}
585     return 0
586
587
588 # Get all the node names in this failover group
589 get_nodenames() {
590     # Check argument
591     if [ $# -eq 0 ]; then
592         echo >&2 $"`basename $0`: get_nodenames() error: Missing"\
593                   "argument for function get_nodenames()!"
594         return 1
595     fi
596
597     declare -i i=$1
598     declare -i idx
599     local nids
600
601     # Initialize the NODE_NAMES array
602     unset NODE_NAMES
603
604     NODE_NAMES[0]=${HOST_NAME[i]}
605
606     idx=1
607     for nids in ${FAILOVERS_NAMES[i]//:/ }
608     do
609         NODE_NAMES[idx]=$(nids2hostname ${nids})
610         if [ ${PIPESTATUS[0]} -ne 0 ]; then
611             echo >&2 "${NODE_NAMES[idx]}"
612             return 1
613         fi
614     
615         idx=$idx+1
616     done
617
618     return 0
619 }
620
621 # Verify whether the format line has HA items
622 is_ha_line() {
623     declare -i i=$1
624
625     [ -n "${FAILOVERS[i]}" ] && return 0
626
627     return 1
628 }
629
630 # Produce HA software's configuration files
631 gen_ha_config() {
632     declare -i i=$1
633     declare -i idx
634     local  cmd_line
635
636     # Prepare parameters
637     # Hostnames option
638     HOSTNAME_OPT=${HOST_NAME[i]}
639
640     if ! get_nodenames $i; then
641         echo >&2 $"`basename $0`: gen_ha_config() error: Can not get the"\
642         "failover nodenames from failover nids - \"${FAILOVERS[i]}\" in"\
643         "the \"${HOST_NAME[i]}\" failover group!"
644         return 1
645     fi
646
647     for ((idx = 1; idx < ${#NODE_NAMES[@]}; idx++)); do
648         HOSTNAME_OPT=${HOSTNAME_OPT}$":"${NODE_NAMES[idx]}
649     done
650
651     # Target devices option
652     DEVICE_OPT=" -d "${TARGET_OPTS[0]}
653     for ((idx = 1; idx < ${#TARGET_OPTS[@]}; idx++)); do
654         DEVICE_OPT=${DEVICE_OPT}" -d "${TARGET_OPTS[idx]}
655     done
656
657     # Construct the generation script command line
658     case "${HATYPE_OPT}" in
659     "${HBVER_HBV1}"|"${HBVER_HBV2}")    # Heartbeat 
660         cmd_line=${GEN_HB_CONFIG}$" -r ${HATYPE_OPT} -n ${HOSTNAME_OPT}"
661         cmd_line=${cmd_line}${DEVICE_OPT}${VERBOSE_OPT}
662         ;;
663     "${HATYPE_CLUMGR}")                 # CluManager
664         cmd_line=${GEN_CLUMGR_CONFIG}$" -n ${HOSTNAME_OPT}"
665         cmd_line=${cmd_line}${DEVICE_OPT}${VERBOSE_OPT}
666         ;;
667     esac
668     
669     # Execute script to generate HA software's configuration files
670     verbose_output "Generating HA software's configurations in"\
671                "${HOST_NAME[i]} failover group..."
672     verbose_output "${cmd_line}"
673     eval $(echo "${cmd_line}")
674     if [ ${PIPESTATUS[0]} -ne 0 ]; then
675         return 1
676     fi
677     verbose_output "Generate HA software's configurations in"\
678                "${HOST_NAME[i]} failover group OK"
679     
680     return 0
681 }
682
683 # Configure HA software
684 config_ha() {
685     if [ -z "${HATYPE_OPT}" ]; then
686         return 0
687     fi
688
689     declare -i i j k
690     declare -i prim_idx         # Index for PRIM_HOSTNAMES array
691     declare -i target_idx       # Index for TARGET_OPTS and HOST_INDEX arrays
692
693     declare -a PRIM_HOSTNAMES   # Primary hostnames in all the failover
694                                 # groups in the lustre cluster
695     declare -a HOST_INDEX       # Indices for the same node in all the 
696                                 # format lines in the csv file
697     local prim_host
698
699     # Initialize the PRIM_HOSTNAMES array
700     prim_idx=0
701     unset PRIM_HOSTNAMES
702
703     # Get failover groups and generate HA configuration files
704     for ((i = 0; i < ${#HOST_NAME[@]}; i++)); do
705         prim_host=${HOST_NAME[i]}
706
707         for ((j = 0; j < ${#PRIM_HOSTNAMES[@]}; j++)); do
708             [ "${prim_host}" = "${PRIM_HOSTNAMES[j]}" ] && continue 2
709         done
710
711         target_idx=0
712         unset HOST_INDEX
713         unset TARGET_OPTS
714         for ((k = 0; k < ${#HOST_NAME[@]}; k++)); do
715             if [ "${prim_host}" = "${HOST_NAME[k]}" ] && is_ha_line "${k}"
716             then
717                 HOST_INDEX[target_idx]=$k
718                 TARGET_OPTS[target_idx]=${DEVICE_NAME[k]}:${MOUNT_POINT[k]}
719                 target_idx=$(( target_idx + 1 ))
720             fi
721         done
722
723         if [ ${#TARGET_OPTS[@]} -ne 0 ]; then
724             PRIM_HOSTNAMES[prim_idx]=${prim_host}
725             prim_idx=$(( prim_idx + 1 ))
726
727             if ! gen_ha_config ${HOST_INDEX[0]}; then
728                 return 1
729             fi
730         fi
731     done
732
733     if [ ${#PRIM_HOSTNAMES[@]} -eq 0 ]; then
734         verbose_output "There are no \"failover nids\" items in the"\
735         "csv file. No HA configuration files are generated!"
736     fi
737
738     rm -rf ${TMP_DIRS}
739     return 0
740 }
741
742 # Get all the items in the csv file and do some checks.
743 get_items() {
744     # Check argument
745     if [ $# -eq 0 ]; then
746         echo >&2 $"`basename $0`: get_items() error: Missing argument"\
747                   "for function get_items()!"
748         return 1
749     fi
750
751     CSV_FILE=$1
752     local LINE
753     local marker
754     local hostname
755     declare -i line_num=0
756     declare -i idx=0
757
758     while read -r LINE; do
759         line_num=${line_num}+1
760         # verbose_output "Parsing line ${line_num}: $LINE"
761
762         # Get rid of the empty line
763         if [ -z "`echo ${LINE}|awk '/[[:alnum:]]/ {print $0}'`" ]; then
764             continue
765         fi
766
767         # Get rid of the comment line
768         if [ -z "`echo \"${LINE}\" | egrep -v \"([[:space:]]|^)#\"`" ]
769         then
770             continue
771         fi
772
773         # Skip the Linux MD/LVM line
774         marker=$(echo ${LINE} | cut -d, -f 2)
775         if [ "${marker}" = "${MD_MARKER}" -o "${marker}" = "${PV_MARKER}" ] \
776         || [ "${marker}" = "${VG_MARKER}" -o "${marker}" = "${LV_MARKER}" ]; then
777             continue
778         fi
779
780         # Skip the host which is not specified in the host list
781         if ! ${USE_ALLNODES}; then
782             hostname=$(echo ${LINE} | cut -d, -f 1)
783             ! host_in_hostlist ${hostname} ${NODES_TO_USE} && continue
784         fi
785
786         # Parse the config line into CONFIG_ITEM
787         if ! parse_line "$LINE"; then
788             echo >&2 $"`basename $0`: parse_line() error: Occurred"\
789                   "on line ${line_num} in ${CSV_FILE}: $LINE"
790             return 1    
791         fi
792
793         HOST_NAME[idx]=${CONFIG_ITEM[0]}
794         MODULE_OPTS[idx]=${CONFIG_ITEM[1]}
795         DEVICE_NAME[idx]=${CONFIG_ITEM[2]}
796         MOUNT_POINT[idx]=${CONFIG_ITEM[3]}
797         DEVICE_TYPE[idx]=${CONFIG_ITEM[4]}
798         FS_NAME[idx]=${CONFIG_ITEM[5]}
799         MGS_NIDS[idx]=${CONFIG_ITEM[6]}
800         INDEX[idx]=${CONFIG_ITEM[7]}
801         FORMAT_OPTIONS[idx]=${CONFIG_ITEM[8]}
802         MKFS_OPTIONS[idx]=${CONFIG_ITEM[9]}
803         MOUNT_OPTIONS[idx]=${CONFIG_ITEM[10]}
804         FAILOVERS[idx]=${CONFIG_ITEM[11]}
805
806         MODULE_OPTS[idx]=`echo "${MODULE_OPTS[idx]}" | sed 's/"/\\\"/g'`
807
808         # Convert IP addresses in NIDs to hostnames
809         FAILOVERS_NAMES[idx]=$(ip2hostname_multi_node ${FAILOVERS[idx]})
810         if [ ${PIPESTATUS[0]} -ne 0 ]; then
811             echo >&2 "${FAILOVERS_NAMES[idx]}"
812             return 1
813         fi
814
815         # Check some required items for formatting target
816         if ! check_item $idx; then
817             echo >&2 $"`basename $0`: check_item() error:"\
818                   "Occurred on line ${line_num} in ${CSV_FILE}."
819             return 1    
820         fi
821
822         idx=${idx}+1
823     done < ${CSV_FILE}
824
825     return 0
826 }
827
828 # check_lnet_connect hostname_index mgs_hostname
829 # Check whether the target node can contact the MGS node @mgs_hostname
830 # If @mgs_hostname is null, then it means the primary MGS node
831 check_lnet_connect() {
832     declare -i i=$1
833     local mgs_node=$2
834
835     local COMMAND RET_STR
836     local mgs_prim_nids
837     local nids_str=
838     local mgs_nid 
839     local ping_mgs
840
841     # Execute remote command to check that 
842     # this node can contact the MGS node
843     verbose_output "Checking lnet connectivity between" \
844     "${HOST_NAME[i]} and the MGS node ${mgs_node}"
845     mgs_prim_nids=`echo ${MGS_NIDS[i]} | awk -F: '{print $1}'`
846
847     if [ -z "${mgs_node}" -o $MGS_NUM -eq 1 ]; then
848         nids_str=${mgs_prim_nids}    # nids of primary MGS node
849         if [ -z "${nids_str}" ]; then
850             echo >&2 $"`basename $0`: check_lnet_connect() error:"\
851             "Check the mgs nids item of host ${HOST_NAME[i]}!"\
852             "Missing nids of the primary MGS node!"
853             return 1
854         fi
855     else
856         # Get the corresponding NID(s) of the MGS node ${mgs_node}
857         # from the "mgs nids" field
858         nids_str=$(get_mgs_nids ${mgs_node} ${MGS_NIDS[i]})
859         if [ ${PIPESTATUS[0]} -ne 0 ]; then
860             echo >&2 "${nids_str}"
861             return 1
862         fi
863     fi
864
865     ping_mgs=false
866     for mgs_nid in ${nids_str//,/ }
867     do
868         COMMAND=$"${LCTL} ping ${mgs_nid} 5 || echo failed 2>&1"
869         RET_STR=$(${REMOTE} ${HOST_NAME[i]} "${COMMAND}" 2>&1)
870         if [ ${PIPESTATUS[0]} -eq 0 -a "${RET_STR}" = "${RET_STR#*failed*}" ]
871         then
872             # This node can contact the MGS node
873             verbose_output "${HOST_NAME[i]} can contact the MGS" \
874             "node ${mgs_node} by using nid \"${mgs_nid}\"!"
875             ping_mgs=true
876             break
877         fi
878     done
879
880     if ! ${ping_mgs}; then
881         echo >&2 "`basename $0`: check_lnet_connect() error:" \
882         "${HOST_NAME[i]} cannot contact the MGS node ${mgs_node}"\
883         "with nids - \"${nids_str}\"! Check ${LCTL} command!"
884         return 1
885     fi
886
887     return 0
888 }
889
890 # Start lnet network in the cluster node and check that 
891 # this node can contact the MGS node
892 check_lnet() {
893     if ! ${VERIFY_CONNECT}; then
894         return 0
895     fi
896
897     # Check argument
898     if [ $# -eq 0 ]; then
899         echo >&2 $"`basename $0`: check_lnet() error: Missing"\
900               "argument for function check_lnet()!"
901         return 1
902     fi
903
904     declare -i i=$1
905     declare -i j
906     local COMMAND RET_STR
907
908     # Execute remote command to start lnet network
909     verbose_output "Starting lnet network in ${HOST_NAME[i]}"
910     COMMAND="PATH=\$PATH:/sbin:/usr/sbin modprobe lnet; ${LCTL} network up 2>&1"
911     RET_STR=$(${REMOTE} ${HOST_NAME[i]} "${COMMAND}" 2>&1)
912     if [ ${PIPESTATUS[0]} -ne 0 -o "${RET_STR}" = "${RET_STR#*LNET configured*}" ]
913     then
914         echo >&2 "`basename $0`: check_lnet() error: remote" \
915                  "${HOST_NAME[i]} error: ${RET_STR}"
916         return 1
917     fi
918
919     if is_mgs_node ${HOST_NAME[i]}; then
920         return 0
921     fi
922
923     # Execute remote command to check that 
924     # this node can contact the MGS node
925     for ((j = 0; j < ${MGS_NUM}; j++)); do
926         if ! check_lnet_connect $i ${MGS_NODENAME[j]}; then
927             return 1
928         fi
929     done
930
931     return 0
932 }
933
934 # Start lnet network in the MGS node
935 start_mgs_lnet() {
936     declare -i i
937     declare -i idx
938     local COMMAND
939
940     if [ -z "${MGS_NODENAME[0]}" -a  -z "${MGS_NODENAME[1]}" ]; then
941         if ${USE_ALLNODES}; then
942             verbose_output "There is no MGS target in the ${CSV_FILE} file."
943         else
944             verbose_output "There is no MGS target in the node list \"${NODES_TO_USE}\"."
945         fi
946         return 0
947     fi
948
949     for ((i = ${INIT_IDX}; i < ${MGS_NUM}; i++)); do
950         # Execute remote command to add lnet options lines to 
951         # the MGS node's modprobe.conf/modules.conf
952         idx=${MGS_IDX[i]}
953         COMMAND=$"echo \"${MODULE_OPTS[${idx}]}\"|${MODULE_CONFIG}"
954         verbose_output "Adding lnet module options to ${MGS_NODENAME[i]}"
955         ${REMOTE} ${MGS_NODENAME[i]} "${COMMAND}" >&2 
956         if [ ${PIPESTATUS[0]} -ne 0 ]; then
957             echo >&2 "`basename $0`: start_mgs_lnet() error:"\
958                  "Failed to execute remote command to" \
959                  "add module options to ${MGS_NODENAME[i]}!"\
960                  "Check ${MODULE_CONFIG}!"
961             return 1
962         fi
963
964         # Start lnet network in the MGS node
965         if ! check_lnet ${idx}; then
966             return 1    
967         fi
968     done
969
970     return 0
971 }
972
973 # Execute remote command to add lnet options lines to remote nodes'
974 # modprobe.conf/modules.conf and format(mkfs.lustre) Lustre targets
975 mass_config() {
976     local COMMAND
977     declare -a REMOTE_PID 
978     declare -a REMOTE_CMD 
979     declare -i pid_num=0
980     declare -i i=0
981
982     if [ ${#HOST_NAME[@]} -eq 0 ]; then
983         verbose_output "There are no Lustre targets to be formatted."
984         return 0
985     fi
986
987     # Start lnet network in the MGS node
988     if ! start_mgs_lnet; then
989         return 1    
990     fi
991
992     for ((i = 0; i < ${#HOST_NAME[@]}; i++)); do
993         # Construct the command line of mkfs.lustre
994         if ! construct_mkfs_cmdline $i; then
995             return 1    
996         fi
997
998         # create the mount point on the node
999         COMMAND="mkdir -p ${MOUNT_POINT[i]}"
1000         verbose_output "Creating the mount point ${MOUNT_POINT[i]} on" \
1001                        "${HOST_NAME[i]}"
1002         ${REMOTE} ${HOST_NAME[i]} "${COMMAND}" >&2 
1003         if [ ${PIPESTATUS[0]} -ne 0 ]; then
1004             echo >&2 "`basename $0`: mass_config() error:"\
1005                  "Failed to execute remote command to"\
1006                  "create the mountpoint on ${HOST_NAME[i]}!"
1007             return 1
1008         fi
1009
1010         if ! is_mgs_node ${HOST_NAME[i]}; then
1011             # Execute remote command to add lnet options lines to 
1012             # modprobe.conf/modules.conf
1013             COMMAND=$"echo \"${MODULE_OPTS[i]}\"|${MODULE_CONFIG}"
1014             verbose_output "Adding lnet module options to" \
1015                        "${HOST_NAME[i]}"
1016             ${REMOTE} ${HOST_NAME[i]} "${COMMAND}" >&2 
1017             if [ ${PIPESTATUS[0]} -ne 0 ]; then
1018                 echo >&2 "`basename $0`: mass_config() error:"\
1019                      "Failed to execute remote command to"\
1020                      "add module options to ${HOST_NAME[i]}!"
1021                 return 1
1022             fi
1023
1024             # Check lnet networks
1025             if ! check_lnet $i; then
1026                 return 1    
1027             fi
1028         fi
1029
1030         # Execute remote command to format Lustre target
1031         verbose_output "Formatting Lustre target ${DEVICE_NAME[i]} on ${HOST_NAME[i]}..."
1032         REMOTE_CMD[${pid_num}]="${REMOTE} ${HOST_NAME[i]} \"(${EXPORT_PATH} ${MKFS_CMD})\""
1033         verbose_output "Format command line is: ${REMOTE_CMD[${pid_num}]}"
1034         ${REMOTE} ${HOST_NAME[i]} "(${EXPORT_PATH} ${MKFS_CMD})" >&2 &  
1035         REMOTE_PID[${pid_num}]=$!
1036         pid_num=${pid_num}+1
1037         sleep 1
1038     done
1039
1040     # Wait for the exit status of the background remote command
1041     verbose_output "Waiting for the return of the remote command..."
1042     fail_exit_status=false
1043     for ((pid_num = 0; pid_num < ${#REMOTE_PID[@]}; pid_num++)); do
1044         wait ${REMOTE_PID[${pid_num}]}
1045         if [ ${PIPESTATUS[0]} -ne 0 ]; then
1046             echo >&2 "`basename $0`: mass_config() error: Failed"\
1047             "to execute \"${REMOTE_CMD[${pid_num}]}\"!"
1048             fail_exit_status=true
1049         fi
1050     done
1051
1052     if ${fail_exit_status}; then
1053         return 1
1054     fi    
1055
1056     verbose_output "All the Lustre targets are formatted successfully!"
1057     return 0
1058 }
1059
1060 # get_mntopts hostname device_name failovers
1061 # Construct the mount options of Lustre target @device_name in host @hostname
1062 get_mntopts() {
1063     local host_name=$1
1064     local device_name=$2
1065     local failovers=$3
1066     local mnt_opts=
1067     local ret_str
1068
1069     [ -n "${failovers}" ] && mnt_opts=defaults,noauto || mnt_opts=defaults
1070
1071     # Execute remote command to check whether the device
1072     # is a block device or not
1073     ret_str=$(${REMOTE} ${host_name} \
1074             "[ -b ${device_name} ] && echo block || echo loop" 2>&1)
1075     if [ ${PIPESTATUS[0]} -ne 0 -a -n "${ret_str}" ]; then
1076         echo "`basename $0`: get_mntopts() error:" \
1077         "remote command to ${host_name} error: ${ret_str}"
1078         return 1
1079     fi
1080
1081     if [ -z "${ret_str}" ]; then
1082         echo "`basename $0`: get_mntopts() error: remote error:" \
1083         "No results from remote!" \
1084         "Check network connectivity between the local host and ${host_name}!"
1085         return 1
1086     fi
1087
1088     [ "${ret_str}" != "${ret_str#*loop}" ] && mnt_opts=${mnt_opts},loop
1089
1090     echo ${mnt_opts}
1091     return 0
1092 }
1093
1094 # Execute remote command to modify /etc/fstab to add the new Lustre targets
1095 modify_fstab() {
1096     declare -i i
1097     local mntent mntopts device_name
1098     local COMMAND
1099
1100     if ! ${MODIFY_FSTAB}; then
1101         return 0    
1102     fi
1103
1104     for ((i = 0; i < ${#HOST_NAME[@]}; i++)); do
1105         verbose_output "Modify /etc/fstab of host ${HOST_NAME[i]}"\
1106                    "to add Lustre target ${DEVICE_NAME[i]}"
1107         mntent=${DEVICE_NAME[i]}"\t\t"${MOUNT_POINT[i]}"\t\t"${FS_TYPE}
1108
1109         # Get mount options
1110         if [ -n "${MOUNT_OPTIONS[i]}" ]; then
1111             # The mount options already specified in the csv file.
1112             mntopts=${MOUNT_OPTIONS[i]}
1113         else
1114             mntopts=$(get_mntopts ${HOST_NAME[i]} ${DEVICE_NAME[i]}\
1115                     ${FAILOVERS[i]})
1116             if [ ${PIPESTATUS[0]} -ne 0 ]; then
1117                 echo >&2 "${mntopts}"
1118                 return 1
1119             fi
1120         fi
1121
1122         mntent=${mntent}"\t"${mntopts}"\t"0" "0
1123         verbose_output "`echo -e ${mntent}`"
1124
1125         # Execute remote command to modify /etc/fstab
1126         device_name=${DEVICE_NAME[i]//\//\\/}
1127         COMMAND=". @scriptlibdir@/lc_common; \
1128                 sed -i \"/^${device_name}\t/d\" \$(fcanon /etc/fstab); \
1129                 echo -e \"${mntent}\" >> \$(fcanon /etc/fstab)"
1130         ${REMOTE} ${HOST_NAME[i]} "${COMMAND}" >&2
1131         if [ ${PIPESTATUS[0]} -ne 0 ]; then
1132             echo >&2 "`basename $0`: modify_fstab() error:"\
1133             "Failed to modify /etc/fstab of host ${HOST_NAME[i]}"\
1134             "to add Lustre target ${DEVICE_NAME[i]}!"
1135             return 1
1136         fi
1137     done
1138
1139     return 0
1140 }
1141
1142 # Main flow
1143 # Check the csv file
1144 if ! check_file $1; then
1145     exit 1 
1146 fi
1147
1148 # Get the list of nodes to be operated on
1149 NODES_TO_USE=$(get_nodelist)
1150 [ ${PIPESTATUS[0]} -ne 0 ] && echo >&2 "${NODES_TO_USE}" && exit 1
1151
1152 # Check the node list
1153 check_nodelist ${NODES_TO_USE} || exit 1
1154
1155 if ${VERIFY_CONNECT}; then
1156 # Check the network connectivity and hostnames
1157     echo "`basename $0`: Checking the cluster network connectivity"\
1158          "and hostnames..."
1159     if ! ${VERIFY_CLUSTER_NET} ${NODELIST_OPT} ${VERBOSE_OPT} ${CSV_FILE}; then
1160         exit 1
1161     fi
1162     echo "`basename $0`: Check the cluster network connectivity"\
1163          "and hostnames OK!"
1164     echo
1165 fi
1166
1167 if ${CONFIG_MD_LVM}; then
1168 # Configure Linux MD/LVM devices
1169     echo "`basename $0`: Configuring Linux MD/LVM devices..."
1170     if ! ${SCRIPT_CONFIG_MD} ${NODELIST_OPT} ${VERBOSE_OPT} ${CSV_FILE}; then
1171         exit 1
1172     fi
1173
1174     if ! ${SCRIPT_CONFIG_LVM} ${NODELIST_OPT} ${VERBOSE_OPT} ${CSV_FILE}; then
1175         exit 1
1176     fi
1177     echo "`basename $0`: Configure Linux MD/LVM devices OK!"
1178     echo
1179 fi
1180
1181 # Configure the Lustre cluster
1182 echo "`basename $0`: ******** Lustre cluster configuration START ********"
1183 if ! get_items ${CSV_FILE}; then
1184     exit 1
1185 fi
1186
1187 if ! check_mgs; then
1188     exit 1
1189 fi
1190
1191 if ! mass_config; then
1192     exit 1
1193 fi
1194
1195 if ! modify_fstab; then
1196     exit 1
1197 fi
1198
1199 # Produce HA software's configuration files
1200 if ! config_ha; then
1201     rm -rf ${TMP_DIRS}
1202     exit 1
1203 fi
1204
1205 echo "`basename $0`: ******** Lustre cluster configuration END **********"
1206
1207 exit 0