Whamcloud - gitweb
LU-1866 lfsck: enhance otable-based iteration
[fs/lustre-release.git] / lustre / scripts / lc_common
1 #!/bin/bash
2
3 # vim:expandtab:shiftwidth=4:softtabstop=4:tabstop=4:
4
5 #
6 # lc_common - This file contains common variables and functions to be used by
7 #             Lustre cluster config scripts.
8 #
9 ################################################################################
10
11 #****************************** Common Variables ******************************#
12 export PATH=$PATH:/sbin:/usr/sbin
13
14 # Remote command
15 export REMOTE=${REMOTE:-"ssh -x -q"}
16 #export REMOTE=${REMOTE:-"pdsh -S -R ssh -w"}
17
18 # Lustre utilities
19 export MKFS=${MKFS:-"mkfs.lustre"}
20 export TUNEFS=${TUNEFS:-"tunefs.lustre"}
21 export LCTL=${LCTL:-"lctl"}
22
23 # Software RAID command
24 export MDADM=${MDADM:-"mdadm"}
25
26 # Some scripts to be called
27 export MODULE_CONFIG=${MODULE_CONFIG:-"lc_modprobe"}
28 export VERIFY_CLUSTER_NET=${VERIFY_CLUSTER_NET:-"lc_net"}
29 export GEN_HB_CONFIG=${GEN_HB_CONFIG:-"lc_hb"}
30 export GEN_CLUMGR_CONFIG=${GEN_CLUMGR_CONFIG:-"lc_cluman"}
31 export SCRIPT_VERIFY_SRVIP=${SCRIPT_VERIFY_SRVIP:-"lc_servip"}
32 export SCRIPT_GEN_MONCF=${SCRIPT_GEN_MONCF:-"lc_mon"}
33 export SCRIPT_CONFIG_MD=${SCRIPT_CONFIG_MD:-"lc_md"}
34 export SCRIPT_CONFIG_LVM=${SCRIPT_CONFIG_LVM:-"lc_lvm"}
35
36 # Variables of HA software
37 HBVER_HBV1="hbv1"                   # Heartbeat version 1
38 HBVER_HBV2="hbv2"                   # Heartbeat version 2
39 HATYPE_CLUMGR="cluman"              # Cluster Manager
40
41 # Configuration directories and files
42 HA_DIR=${HA_DIR:-"/etc/ha.d"}           # Heartbeat configuration directory
43 MON_DIR=${MON_DIR:-"/etc/mon"}          # mon configuration directory
44 CIB_DIR=${CIB_DIR:-"/var/lib/heartbeat/crm"}    # cib.xml directory
45
46 HA_CF=${HA_DIR}/ha.cf               # ha.cf file
47 HA_RES=${HA_DIR}/haresources        # haresources file
48 HA_CIB=${CIB_DIR}/cib.xml
49
50 CLUMAN_DIR="/etc"                               # CluManager configuration directory
51 CLUMAN_CONFIG=${CLUMAN_DIR}/cluster.xml
52
53 CLUMAN_TOOLS_PATH=${CLUMAN_TOOLS_PATH:-"/usr/sbin"}     # CluManager tools
54 CONFIG_CMD=${CONFIG_CMD:-"${CLUMAN_TOOLS_PATH}/redhat-config-cluster-cmd"}
55
56 HB_TMP_DIR="/tmp/heartbeat"         # Temporary directory
57 CLUMGR_TMP_DIR="/tmp/clumanager"
58 TMP_DIRS="${HB_TMP_DIR} ${CLUMGR_TMP_DIR}"
59
60 FS_TYPE=${FS_TYPE:-"lustre"}        # Lustre filesystem type
61 FILE_SUFFIX=${FILE_SUFFIX:-".lustre"}   # Suffix of the generated config files
62
63 # Marker of the MD device line
64 export MD_MARKER=${MD_MARKER:-"MD"}
65
66 # Marker of the LVM device line
67 export PV_MARKER=${PV_MARKER:-"PV"}
68 export VG_MARKER=${VG_MARKER:-"VG"}
69 export LV_MARKER=${LV_MARKER:-"LV"}
70
71 declare -a CONFIG_ITEM              # Items in each line of the CSV file
72 declare -a NODE_NAME                # Hostnames of nodes have been configured
73
74 declare -a MGS_NODENAME             # Node names of the MGS servers
75 declare -a MGS_IDX                  # Indexes of MGSs in the global arrays
76 declare -i MGS_NUM                  # Number of MGS servers in the cluster
77 declare -i INIT_IDX
78
79 # All of the Lustre target items in the CSV file
80 declare -a HOST_NAME MODULE_OPTS DEVICE_NAME MOUNT_POINT DEVICE_TYPE FS_NAME
81 declare -a MGS_NIDS INDEX FORMAT_OPTIONS MKFS_OPTIONS MOUNT_OPTIONS FAILOVERS
82
83 # Heartbeat software requires that node names in the configuration directive
84 # must (normally) match the "uname -n" of that machine. Since the value of the
85 # "failover nids" field in the CSV file is the NID(s) of failover partner node,
86 # we have to figure out the corresponding hostname of that node.
87 declare -a FAILOVERS_NAMES
88
89 export VERIFY_CONNECT=true          # Verify network connectivity by default
90 export USE_ALLNODES=false           # Not operating on all the nodes by default
91 export SPECIFIED_NODELIST=""        # Specified list of nodes to be operated on
92 export EXCLUDED_NODELIST=""         # Specified list of nodes to be excluded
93 export NODES_TO_USE=""              # Defacto list of nodes to be operated on
94 export NODELIST_OPT=""
95 export VERBOSE_OUTPUT=false
96 export VERBOSE_OPT=""
97
98
99 #****************************** Common Functions ******************************#
100
101 # verbose_output string
102 # Output verbose information $string
103 verbose_output() {
104     if ${VERBOSE_OUTPUT}; then
105         echo "`basename $0`: $*"
106     fi
107     return 0
108 }
109
110 # error_output string
111 # Output error string to stderr, prefixing with ERROR
112 # for easy error parsing from the rest of the output.
113 error_output() {
114     echo >&2 "$(basename $0): ERROR: $*"
115     return 0
116 }
117
118 # error_exit rc string
119 # Output error to stderr via error_output and exit with rc.
120 error_exit() {
121     local rc=$1
122     shift
123
124     error_output $*
125     exit $rc
126 }
127
128 # Check whether the reomte command is pdsh
129 is_pdsh() {
130     if [ "${REMOTE}" = "${REMOTE#*pdsh}" ]; then
131         return 1
132     fi
133
134     return 0
135 }
136
137 # check_file csv_file
138 # Check the file $csv_file
139 check_file() {
140     # Check argument
141     if [ $# -eq 0 ]; then
142         error_output "check_file(): Missing CSV file!"
143         return 1
144     fi
145
146     local CSV_FILE=$1
147     if [ ! -s ${CSV_FILE} ]; then
148         error_output "check_file(): ${CSV_FILE}"\
149                  "does not exist or is empty!"
150         return 1
151     fi
152
153     return 0
154 }
155
156 # parse_line line
157 # Parse a line in the CSV file
158 parse_line() {
159     # Check argument
160     if [ $# -eq 0 ]; then
161         error_output "parse_line(): Missing argument!"
162         return 1
163     fi
164
165     declare -i i=0              # Index of the CONFIG_ITEM array
166     declare -i length=0
167     declare -i idx=0
168     declare -i s_quote_flag=0   # Flag of the single quote character
169     declare -i d_quote_flag=0   # Flag of the double quotes character
170     local TMP_LETTER LINE
171
172     LINE="$*"
173
174     # Initialize the CONFIG_ITEM array
175     unset CONFIG_ITEM
176
177     # Get the length of the line
178     length=${#LINE}
179
180     i=0
181     while [ ${idx} -lt ${length} ]; do
182         # Get a letter from the line
183         TMP_LETTER=${LINE:${idx}:1}
184
185         case "${TMP_LETTER}" in
186         ",")
187             if [ ${s_quote_flag} -eq 1 -o ${d_quote_flag} -eq 1 ]
188             then
189                 CONFIG_ITEM[i]=${CONFIG_ITEM[i]}${TMP_LETTER}
190             else
191                 i=$i+1
192             fi
193             idx=${idx}+1
194             continue
195             ;;
196         "'")
197             if [ ${s_quote_flag} -eq 0 ]; then
198                 s_quote_flag=1
199             else
200                 s_quote_flag=0
201             fi
202             ;;
203         "\"")
204             if [ ${d_quote_flag} -eq 0 ]; then
205                 d_quote_flag=1
206             else
207                 d_quote_flag=0
208             fi
209             ;;
210         "\r")
211             idx=${idx}+1
212             continue
213             ;;
214         *)
215             ;;
216         esac
217         CONFIG_ITEM[i]=${CONFIG_ITEM[i]}${TMP_LETTER}
218         idx=${idx}+1
219     done
220
221     # Extract the real value of each field
222     # Remove surrounded double-quotes, etc.
223     for ((idx = 0; idx <= $i; idx++)); do
224         # Strip the leading and trailing space-characters
225         CONFIG_ITEM[idx]=`expr "${CONFIG_ITEM[idx]}" : '[[:space:]]*\(.*\)[[:space:]]*$'`
226
227         [ -z "${CONFIG_ITEM[idx]}" ] && continue
228
229         # Remove the surrounded double-quotes
230         while [ -z "`echo "${CONFIG_ITEM[idx]}"|sed -e 's/^".*"$//'`" ]; do
231             CONFIG_ITEM[idx]=`echo "${CONFIG_ITEM[idx]}" | sed -e 's/^"//' -e 's/"$//'`
232         done
233
234         CONFIG_ITEM[idx]=`echo "${CONFIG_ITEM[idx]}" | sed -e 's/""/"/g'`
235     done
236
237     return 0
238 }
239
240 # fcanon name
241 # If $name is a symbolic link, then display it's value
242 fcanon() {
243     local NAME=$1
244
245     if [ -h "$NAME" ]; then
246         readlink -f "$NAME"
247     else
248         echo "$NAME"
249     fi
250 }
251
252 # configured_host host_name
253 #
254 # Check whether the devices in $host_name has been configured or not
255 configured_host() {
256     local host_name=$1
257     declare -i i
258
259     for ((i = 0; i < ${#NODE_NAME[@]}; i++)); do
260         [ "${host_name}" = "${NODE_NAME[i]}" ] && return 0
261     done
262
263     return 1
264 }
265
266 # remote_error fn_name host_addr ret_str
267 # Verify the return result from remote command
268 remote_error() {
269     local fn_name host_addr ret_str
270
271     fn_name=$1
272     shift
273     host_addr=$1
274     shift
275     ret_str=$*
276
277     if [ "${ret_str}" != "${ret_str#*connect:*}" ]; then
278         error_output "${fn_name}(): ${ret_str}"
279         return 0
280     fi
281
282     if [ -z "${ret_str}" ]; then
283         error_output "${fn_name}():" \
284         "No results from remote!" \
285         "Check network connectivity between the local host and ${host_addr}!"
286         return 0
287     fi
288
289     return 1
290 }
291
292 # nid2hostname nid
293 # Convert $nid to hostname of the lustre cluster node
294 nid2hostname() {
295     local nid=$1
296     local host_name=
297     local addr nettype ip_addr
298     local ret_str
299
300     addr=${nid%@*}
301     [ "${nid}" != "${nid#*@*}" ] && nettype=${nid#*@} || nettype=tcp
302     if [ -z "${addr}" ]; then
303         echo "`basename $0`: nid2hostname() error: Invalid nid - \"${nid}\"!"
304         return 1
305     fi
306
307     case "${nettype}" in
308     lo*)    host_name=`hostname`;;
309     elan*)  # QsNet
310             # FIXME: Parse the /etc/elanhosts configuration file to
311             # convert ElanID to hostname
312             ;;
313     ptl*)   # Portals
314             # FIXME: Convert portal ID to hostname
315             ;;
316     *)  # tcp, o2ib, ra
317         ip_addr=${addr}
318         # Is it IP address or hostname?
319         if [ -n "`echo ${ip_addr} | sed -e 's/\([0-9]\{1,3\}\.\)\{3,3\}[0-9]\{1,3\}//'`" ]
320         then
321             host_name=${ip_addr}
322             echo ${host_name}
323             return 0
324         fi
325
326         # Execute remote command to get the host name
327         ret_str=$(${REMOTE} ${ip_addr} "hostname" 2>&1 </dev/null)
328         if [ ${PIPESTATUS[0]} -ne 0 -a -n "${ret_str}" ]; then
329             echo "`basename $0`: nid2hostname() error:" \
330             "remote command to ${ip_addr} error: ${ret_str}"
331             return 1
332         fi
333         remote_error "nid2hostname" ${ip_addr} "${ret_str}" && return 1
334
335         if is_pdsh; then
336             host_name=`echo ${ret_str} | awk '{print $2}'`
337         else
338             host_name=`echo ${ret_str} | awk '{print $1}'`
339         fi
340         ;;
341     esac
342
343     echo ${host_name}
344     return 0
345 }
346
347 # nids2hostname nids
348 # Get the hostname of the lustre cluster node which has the nids - $nids
349 nids2hostname() {
350     local nids=$1
351     local host_name=
352     local nid
353     local nettype
354
355     for nid in ${nids//,/ }; do
356         [ "${nid}" != "${nid#*@*}" ] && nettype=${nid#*@} || nettype=tcp
357
358         case "${nettype}" in
359         lo* | elan* | ptl*) ;;
360         *)  # tcp, o2ib, ra
361             host_name=$(nid2hostname ${nid})
362             if [ ${PIPESTATUS[0]} -ne 0 ]; then
363                 echo "${host_name}"
364                 return 1
365             fi
366             ;;
367         esac
368     done
369
370     if [ -z "${host_name}" ]; then
371         echo "`basename $0`: nids2hostname() error:" \
372         "Can not get the hostname from nids - \"${nids}\"!"
373         return 1
374     fi
375
376     echo ${host_name}
377     return 0
378 }
379
380 # ip2hostname_single_node nids
381 # Convert IP addresses in $nids into hostnames
382 # NID in $nids are delimited by commas, ie all the $nids belong to one node
383 ip2hostname_single_node() {
384     local orig_nids=$1
385     local nids=
386     local nid host_name
387     local nettype
388
389     for nid in ${orig_nids//,/ }; do
390         [ "${nid}" != "${nid#*@*}" ] && nettype=${nid#*@} || nettype=tcp
391
392         case "${nettype}" in
393         lo* | elan* | ptl*) ;;
394         *)  # tcp, o2ib, ra
395             host_name=$(nid2hostname ${nid})
396             if [ ${PIPESTATUS[0]} -ne 0 ]; then
397                 echo "${host_name}"
398                 return 1
399             fi
400
401             nid=${host_name}@${nettype}
402             ;;
403         esac
404
405         [ -z "${nids}" ] && nids=${nid} || nids=${nids},${nid}
406     done
407
408     echo ${nids}
409     return 0
410 }
411
412 # ip2hostname_multi_node nids
413 # Convert IP addresses in $nids into hostnames
414 # NIDs belong to multiple nodes are delimited by colons in $nids
415 ip2hostname_multi_node() {
416     local orig_nids=$1
417     local nids=
418     local nid
419
420     for nid in ${orig_nids//:/ }; do
421         nid=$(ip2hostname_single_node ${nid})
422         if [ ${PIPESTATUS[0]} -ne 0 ]; then
423             echo "${nid}"
424             return 1
425         fi
426
427         [ -z "${nids}" ] && nids=${nid} || nids=${nids}:${nid}
428     done
429
430     echo ${nids}
431     return 0
432 }
433
434 # comma_list space-delimited-list
435 # Convert a space-delimited list to a sorted list of unique values
436 # separated by commas.
437 comma_list() {
438     # the sed converts spaces to commas, but leaves the last space
439     # alone, so the line doesn't end with a comma.
440     echo "$*" | tr -s " " "\n" | sort -b -u | tr "\n" " " | sed 's/ \([^$]\)/,\1/g'
441 }
442
443 # host_in_hostlist hostname hostlist
444 # Given a hostname, and a list of hostnames, return true if the hostname
445 # appears in the list of hostnames, or false otherwise.
446 host_in_hostlist() {
447     local HOST=$1
448     local HOSTLIST=$2
449
450     [ -z "$HOST" -o -z "$HOSTLIST" ] && false && return
451
452     # Hostnames in the list are separated by commas.
453     [[ ,$HOSTLIST, == *,$HOST,* ]] && true && return
454
455     false && return
456 }
457
458 # exclude_items_from_list list_of_items list_of_items_to_exclude
459 # Given a list of items, and a second list of items to exclude from
460 # the first list, return the contents of the first list minus the contents
461 # of the second.
462 exclude_items_from_list() {
463     local INLIST=$1
464     local EXCLUDELIST=$2
465     local ITEM OUTLIST
466
467     # Handle an empty inlist by throwing back an empty string.
468     if [ -z "$INLIST" ]; then
469         echo ""
470         return 0
471     fi
472
473     # Handle an empty excludelist by throwing back the inlist unmodified.
474     if [ -z "$EXCLUDELIST" ]; then
475         echo $INLIST
476         return 0
477     fi
478
479     for ITEM in ${INLIST//,/ }; do
480         if ! host_in_hostlist $ITEM $EXCLUDELIST; then
481            OUTLIST="$OUTLIST,$ITEM"
482         fi
483     done
484
485     # strip leading comma
486     echo ${OUTLIST#,}
487 }
488
489 # get_csv_nodelist csv_file
490 # Get the comma-separated list of all the nodes from the CSV file
491 get_csv_nodelist() {
492     local csv_file=$1
493     local all_nodelist
494
495     # Check the CSV file
496     ! check_file ${csv_file} 2>&1 && return 1
497
498     all_nodelist=$(egrep -v "([[:space:]]|^)#" ${csv_file} | cut -d, -f 1)
499     all_nodelist=$(comma_list ${all_nodelist})
500
501     echo ${all_nodelist}
502     return 0
503 }
504
505 # get_nodelist
506 # Get the comma-separated list of nodes to be operated on
507 # Note: CSV_FILE, USE_ALLNODES, SPECIFIED_NODELIST and EXCLUDED_NODELIST
508 # are global variables
509 get_nodelist() {
510     local ALL_NODELIST
511
512     # Get the list of all the nodes in the CSV file
513     ALL_NODELIST=$(get_csv_nodelist ${CSV_FILE})
514     [ ${PIPESTATUS[0]} -ne 0 ] && echo "${ALL_NODELIST}" && return 1
515
516     if [ -z "${ALL_NODELIST}" ]; then
517         echo "`basename $0`: get_nodelist() error:"\
518              "There are no hosts in the ${CSV_FILE} file!"
519         return 1
520     fi
521
522     if ${USE_ALLNODES}; then
523         echo ${ALL_NODELIST} && return 0
524     fi
525
526     if [ -n "${SPECIFIED_NODELIST}" ]; then
527         echo $(exclude_items_from_list ${SPECIFIED_NODELIST} ${EXCLUDED_NODELIST})
528         return 0
529     fi
530
531     if [ -n "${EXCLUDED_NODELIST}" ]; then
532         echo $(exclude_items_from_list ${ALL_NODELIST} ${EXCLUDED_NODELIST})
533         return 0
534     fi
535
536     # No hosts to be operated on
537     echo ""
538     return 0
539 }
540
541 # check_nodelist nodelist
542 # Given a list of nodes to be operated on, check whether the nodelist is 
543 # empty or not and output prompt message.
544 check_nodelist() {
545     local nodes_to_use=$1
546
547     if [ -z "${nodes_to_use}" ]; then
548         error_output "There are no nodes to be operated on."\
549              "Check the node selection options (-a, -w or -x)."
550         usage 1>&2
551         return 1
552     else
553         verbose_output "Operating on the following nodes: ${nodes_to_use}"
554     fi
555
556     return 0
557 }
558
559 # nid_in_nidlist nid nidlist
560 # Given a nid, and a list of nids in one node (delimited by comma ','),
561 # return true if the nid appears in the list of nids, or false otherwise.
562 nid_in_nidlist() {
563     local nid="$1"
564     local nidlist="$2"
565     local my_nid
566
567     [ -z "${nid}" -o -z "${nidlist}" ] && false && return
568
569     if [[ "${nid}" != *@* || "${nid#*@}" == tcp* ]]; then
570         # network type is tcp
571         for my_nid in ${nidlist//,/ }; do
572             [ "${nid%@*}" = "${my_nid%@*}" ] && true && return
573         done
574     else
575         # network type is not tcp
576         [[ ,${nidlist}, == *,${nid},* ]] && true && return
577     fi
578
579     false && return
580 }
581
582 # get_mgs_nids mgs_hostname mgs_nids
583 # Get the corresponding NID(s) of the MGS node ${mgs_hostname} from the
584 # "mgs nids" field of one lustre target in the CSV file
585 get_mgs_nids() {
586     local mgs_node="$1"
587     local all_mgs_nids="$2"
588     local mgs_nids
589     local ret_str
590
591     # Check whether the hostname of the mgs node is in 
592     # the mgs nids string
593     for mgs_nids in ${all_mgs_nids//:/ }; do
594         if nid_in_nidlist ${mgs_node} ${mgs_nids}; then
595             echo ${mgs_nids}
596             return 0
597         fi
598     done
599
600     # Let's use lctl to get the real nids from the mgs node
601     ret_str=$($REMOTE $mgs_node "PATH=\$PATH:/sbin:/usr/sbin
602 $LCTL list_nids" 2>&1 </dev/null)
603     if [ ${PIPESTATUS[0]} -ne 0 -a -n "${ret_str}" ]; then
604         echo "$(basename $0): get_mgs_nids() error:" \
605         "remote command to ${mgs_node} error: ${ret_str}"
606         return 1
607     fi
608     remote_error "get_mgs_nids" ${mgs_node} "${ret_str}" && return 1
609
610     local real_mgs_nids=${ret_str//${mgs_node}:/}
611     for real_mgs_nid in ${real_mgs_nids}; do
612         for mgs_nids in ${all_mgs_nids//:/ }; do
613             if nid_in_nidlist ${real_mgs_nid} ${mgs_nids}; then
614                 echo ${mgs_nids}
615                 return 0
616             fi
617         done
618     done
619
620     echo "$(basename $0): get_mgs_nids() error:" \
621     "Can not figure out which nids corresponding to the MGS"\
622     "node ${mgs_node} from \"${all_mgs_nids}\"!"
623
624     return 1
625 }
626
627 # Check the items required for OSTs, MDTs and MGS
628 #
629 # When formatting an OST, the following items: hostname,
630 # device name, device type and mgs nids, cannot have null value.
631 #
632 # When formatting an MDT or MGS, the following items: hostname,
633 # device name and device type, cannot have null value.
634 check_lustre_item() {
635     # Check argument
636     if [ $# -eq 0 ]; then
637         error_output "check_lustre_item(): Missing argument"\
638                   "for function check_lustre_item()!"
639         return 1
640     fi
641
642     declare -i i=$1
643
644     # Check hostname, device name and device type
645     if [ -z "${HOST_NAME[i]}" ] || \
646     [ -z "${DEVICE_NAME[i]}" ] || [ -z "${DEVICE_TYPE[i]}" ]; then
647         error_output "check_lustre_item(): Some required"\
648                   "item has null value! Check hostname,"\
649                   "device name and device type!"
650         return 1
651     fi
652
653     # Check mgs nids
654     if [ "${DEVICE_TYPE[i]}" = "ost" ]&&[ -z "${MGS_NIDS[i]}" ]; then
655         error_output "check_lustre_item(): OST's mgs nids"\
656                   "item has null value!"
657         return 1
658     fi
659
660     # Check mount point
661     if [ -z "${MOUNT_POINT[i]}" ]; then
662         error_output "check_lustre_item(): mount"\
663                   "point item of target ${DEVICE_NAME[i]} has null value!"
664         return 1
665     fi
666
667     return 0
668 }
669
670 # Get the number of MGS nodes in the cluster
671 get_mgs_num() {
672     INIT_IDX=0
673     MGS_NUM=${#MGS_NODENAME[@]}
674     [ -z "${MGS_NODENAME[0]}" ] && let "INIT_IDX += 1" \
675     && let "MGS_NUM += 1"
676 }
677
678 # is_mgs_node hostname
679 # Verify whether @hostname is a MGS node
680 is_mgs_node() {
681     local host_name=$1
682     declare -i i
683
684     get_mgs_num
685     for ((i = ${INIT_IDX}; i < ${MGS_NUM}; i++)); do
686         [ "${MGS_NODENAME[i]}" = "${host_name}" ] && return 0
687     done
688
689     return 1
690 }
691
692 # Check whether the MGS nodes are in the same failover group
693 check_mgs_group() {
694     declare -i i
695     declare -i j
696     declare -i idx
697     local mgs_node
698
699     get_mgs_num
700     for ((i = ${INIT_IDX}; i < ${MGS_NUM}; i++)); do
701         mgs_node=${MGS_NODENAME[i]}
702         for ((j = ${INIT_IDX}; j < ${MGS_NUM}; j++)); do
703           [ "${MGS_NODENAME[j]}" = "${mgs_node}" ] && continue 1
704
705           idx=${MGS_IDX[j]}
706           if [ "${FAILOVERS_NAMES[idx]#*$mgs_node*}" = "${FAILOVERS_NAMES[idx]}" ]
707           then
708             error_output "check_mgs_group():"\
709             "MGS node ${mgs_node} is not in the ${HOST_NAME[idx]}"\
710             "failover group!"
711             return 1
712           fi
713         done
714     done
715
716     return 0
717 }
718
719 # Get and check MGS servers.
720 # There should be no more than one MGS specified in the entire CSV file.
721 check_mgs() {
722     declare -i i
723     declare -i j
724     declare -i exp_idx    # Index of explicit MGS servers
725     declare -i imp_idx    # Index of implicit MGS servers
726     local is_exp_mgs is_imp_mgs
727     local mgs_node
728
729     # Initialize the MGS_NODENAME and MGS_IDX arrays
730     unset MGS_NODENAME
731     unset MGS_IDX
732
733     exp_idx=1
734     imp_idx=1
735     for ((i = 0; i < ${#HOST_NAME[@]}; i++)); do
736         is_exp_mgs=false
737         is_imp_mgs=false
738
739         # Check whether this node is an explicit MGS node 
740         # or an implicit one
741         if [ "${DEVICE_TYPE[i]#*mgs*}" != "${DEVICE_TYPE[i]}" ]; then
742             verbose_output "Explicit MGS target" \
743             "${DEVICE_NAME[i]} in host ${HOST_NAME[i]}."
744             is_exp_mgs=true
745         fi
746
747         if [ "${DEVICE_TYPE[i]}" = "mdt" -a -z "${MGS_NIDS[i]}" ]; then
748             verbose_output "Implicit MGS target" \
749             "${DEVICE_NAME[i]} in host ${HOST_NAME[i]}."
750             is_imp_mgs=true
751         fi
752
753         # Get and check MGS servers
754         if ${is_exp_mgs} || ${is_imp_mgs}; then
755             # Check whether more than one MGS target in one MGS node
756             if is_mgs_node ${HOST_NAME[i]}; then
757                 error_output "check_mgs():"\
758                 "More than one MGS target in the same node -"\
759                 "\"${HOST_NAME[i]}\"!"
760                 return 1
761             fi
762
763             # Get and check primary MGS server and backup MGS server        
764             if [ "${FORMAT_OPTIONS[i]}" = "${FORMAT_OPTIONS[i]#*noformat*}" ]
765             then
766                 # Primary MGS server
767                 if [ -z "${MGS_NODENAME[0]}" ]; then
768                     if [ "${is_exp_mgs}" = "true" -a ${imp_idx} -gt 1 ] \
769                     || [ "${is_imp_mgs}" = "true" -a ${exp_idx} -gt 1 ]; then
770                         error_output "check_mgs():"\
771                         "There exist both explicit and implicit MGS"\
772                         "targets in the CSV file!"
773                         return 1
774                     fi
775                     MGS_NODENAME[0]=${HOST_NAME[i]}
776                     MGS_IDX[0]=$i
777                 else
778                     mgs_node=${MGS_NODENAME[0]}
779                     if [ "${FAILOVERS_NAMES[i]#*$mgs_node*}" = "${FAILOVERS_NAMES[i]}" ]
780                     then
781                         error_output "check_mgs():"\
782                         "More than one primary MGS nodes in the CSV" \
783                         "file - ${MGS_NODENAME[0]} and ${HOST_NAME[i]}!"
784                     else
785                         error_output "check_mgs():"\
786                         "MGS nodes ${MGS_NODENAME[0]} and ${HOST_NAME[i]}"\
787                         "are failover pair, one of them should use"\
788                         "\"--noformat\" in the format options item!"
789                     fi
790                     return 1
791                 fi
792             else    # Backup MGS server
793                 if [ "${is_exp_mgs}" = "true" -a ${imp_idx} -gt 1 ] \
794                 || [ "${is_imp_mgs}" = "true" -a ${exp_idx} -gt 1 ]; then
795                     error_output "check_mgs():"\
796                     "There exist both explicit and implicit MGS"\
797                     "targets in the CSV file!"
798                     return 1
799                 fi
800
801                 if ${is_exp_mgs}; then # Explicit MGS
802                     MGS_NODENAME[exp_idx]=${HOST_NAME[i]}
803                     MGS_IDX[exp_idx]=$i
804                     exp_idx=$(( exp_idx + 1 ))
805                 else    # Implicit MGS
806                     MGS_NODENAME[imp_idx]=${HOST_NAME[i]}
807                     MGS_IDX[imp_idx]=$i
808                     imp_idx=$(( imp_idx + 1 ))
809                 fi
810             fi
811         fi #End of "if ${is_exp_mgs} || ${is_imp_mgs}"
812     done
813
814     # Check whether the MGS nodes are in the same failover group
815     if ! check_mgs_group; then
816         return 1
817     fi
818
819     return 0
820 }
821
822 # Execute remote command to add module options to
823 # the module configuration file
824 add_module_options() {
825     declare -i i=$1
826     local hostname=$2
827
828     if [ -z "$hostname" ]; then
829         error_output "add_module_options(): Missing hostname!"
830         return 1
831     fi
832
833     [ -z "${MODULE_OPTS[i]}" ] && return 0
834
835     # Execute remote command to add module options to
836     # the module configuration file
837     verbose_output "Adding module options to $hostname"
838     $REMOTE $hostname "PATH=\$PATH:/sbin:/usr/sbin
839 echo \"${MODULE_OPTS[i]}\" | $MODULE_CONFIG"
840     local RC=${PIPESTATUS[0]}
841     if [ $RC -ne 0 ]; then
842         error_output "add_module_options():"\
843         "Failed to add module options to $hostname!"
844         return $RC
845     fi
846
847     return 0
848 }
849
850 # check_lnet_connect hostname_index mgs_hostname
851 # Check whether the target node can contact the MGS node @mgs_hostname
852 # If @mgs_hostname is null, then it means the primary MGS node
853 check_lnet_connect() {
854     declare -i i=$1
855     local mgs_node=$2
856
857     local mgs_prim_nids
858     local nids_str=
859     local mgs_nid 
860     local ping_mgs
861     local try
862
863     # Execute remote command to check that 
864     # this node can contact the MGS node
865     verbose_output "Checking lnet connectivity between" \
866     "${HOST_NAME[i]} and the MGS node ${mgs_node}"
867     mgs_prim_nids=`echo ${MGS_NIDS[i]} | awk -F: '{print $1}'`
868
869     if [ -z "${mgs_node}" -o $MGS_NUM -eq 1 ]; then
870         nids_str=${mgs_prim_nids}    # nids of primary MGS node
871         if [ -z "${nids_str}" ]; then
872             error_output "check_lnet_connect():"\
873             "Check the mgs nids item of host ${HOST_NAME[i]}!"\
874             "Missing nids of the primary MGS node!"
875             return 1
876         fi
877     else
878         # Get the corresponding NID(s) of the MGS node ${mgs_node}
879         # from the "mgs nids" field
880         nids_str=$(get_mgs_nids ${mgs_node} ${MGS_NIDS[i]})
881         if [ ${PIPESTATUS[0]} -ne 0 ]; then
882             error_output "${nids_str}"
883             return 1
884         fi
885     fi
886
887     ping_mgs=false
888     for mgs_nid in ${nids_str//,/ }
889     do
890         for try in $(seq 0 5); do
891             $REMOTE ${HOST_NAME[i]} "PATH=\$PATH:/sbin:/usr/sbin
892 $LCTL ping $mgs_nid 5 1>/dev/null"
893             if [ ${PIPESTATUS[0]} -eq 0 ]; then
894                 # This node can contact the MGS node
895                 verbose_output "${HOST_NAME[i]} can contact the MGS" \
896                 "node $mgs_node by using nid \"$mgs_nid\"!"
897                 ping_mgs=true
898                 break
899             fi
900         done
901     done
902
903     if ! ${ping_mgs}; then
904         error_output "check_lnet_connect():" \
905         "${HOST_NAME[i]} cannot contact the MGS node ${mgs_node}"\
906         "with nids - \"${nids_str}\"! Check ${LCTL} command!"
907         return 1
908     fi
909
910     return 0
911 }
912
913 # Start lnet network in the cluster node and check that 
914 # this node can contact the MGS node
915 check_lnet() {
916     if ! $VERIFY_CONNECT; then
917         return 0
918     fi
919
920     # Check argument
921     if [ $# -eq 0 ]; then
922         error_output "check_lnet(): Missing argument!"
923         return 1
924     fi
925
926     declare -i i=$1
927     declare -i j
928     local ret_str
929
930     # Execute remote command to start lnet network
931     verbose_output "Starting lnet network on ${HOST_NAME[i]}"
932     ret_str=$($REMOTE ${HOST_NAME[i]} "PATH=\$PATH:/sbin:/usr/sbin
933 modprobe lnet && $LCTL network up" 2>&1)
934     if [ ${PIPESTATUS[0]} -ne 0 ]; then
935         error_output "check_lnet(): start lnet network on" \
936         "${HOST_NAME[i]} error: $ret_str"
937         return 1
938     fi
939
940     if is_mgs_node ${HOST_NAME[i]}; then
941         return 0
942     fi
943
944     # Execute remote command to check that 
945     # this node can contact the MGS node
946     for ((j = 0; j < ${MGS_NUM}; j++)); do
947         if ! check_lnet_connect $i ${MGS_NODENAME[j]}; then
948             return 1
949         fi
950     done
951
952     return 0
953 }
954
955 # Start lnet network in the MGS node
956 start_mgs_lnet() {
957     declare -i i
958     declare -i idx
959
960     if [ -z "${MGS_NODENAME[0]}" -a  -z "${MGS_NODENAME[1]}" ]; then
961         if ${USE_ALLNODES}; then
962             verbose_output "There is no MGS target in the ${CSV_FILE} file."
963         else
964             verbose_output "There is no MGS target in the node list \"${NODES_TO_USE}\"."
965         fi
966         return 0
967     fi
968
969     for ((i = ${INIT_IDX}; i < ${MGS_NUM}; i++)); do
970         # Execute remote command to add lnet options lines to 
971         # the MGS node's modprobe.conf/modules.conf
972         idx=${MGS_IDX[i]}
973         add_module_options $idx ${MGS_NODENAME[i]} || return ${PIPESTATUS[0]}
974
975         # Start lnet network in the MGS node
976         check_lnet $idx || return ${PIPESTATUS[0]}
977     done
978
979     return 0
980 }
981
982 # Get all the Lustre target items in the CSV file and do some checks.
983 get_lustre_items() {
984     # Check argument
985     if [ $# -eq 0 ]; then
986         error_output "get_lustre_items(): Missing argument"\
987                   "for function get_lustre_items()!"
988         return 1
989     fi
990
991     local CSV_FILE=$1
992     local LINE
993     local marker
994     local hostname
995     declare -i line_num=0
996     declare -i idx=0
997
998     exec 9< ${CSV_FILE}
999     while read -u 9 -r LINE; do
1000         line_num=${line_num}+1
1001         # verbose_output "Parsing line ${line_num}: $LINE"
1002
1003         # Get rid of the empty line
1004         [ -z "`echo ${LINE} | awk '/[[:alnum:]]/ {print $0}'`" ] && continue
1005
1006         # Get rid of the comment line
1007         [ -z "`echo \"${LINE}\" | egrep -v \"([[:space:]]|^)#\"`" ] && continue
1008
1009         # Skip the Linux MD/LVM line
1010         marker=$(echo ${LINE} | cut -d, -f 2)
1011         if [ "${marker}" = "${MD_MARKER}" -o "${marker}" = "${PV_MARKER}" ] \
1012         || [ "${marker}" = "${VG_MARKER}" -o "${marker}" = "${LV_MARKER}" ]; then
1013             continue
1014         fi
1015
1016         # Skip the host which is not specified in the host list
1017         if ! ${USE_ALLNODES}; then
1018             hostname=$(echo ${LINE} | cut -d, -f 1)
1019             ! host_in_hostlist ${hostname} ${NODES_TO_USE} && continue
1020         fi
1021
1022         # Parse the config line into CONFIG_ITEM
1023         if ! parse_line "$LINE"; then
1024             error_output "parse_line(): Occurred"\
1025                   "on line ${line_num} in ${CSV_FILE}: $LINE"
1026             return 1
1027         fi
1028
1029         HOST_NAME[idx]=${CONFIG_ITEM[0]}
1030         MODULE_OPTS[idx]=${CONFIG_ITEM[1]}
1031         DEVICE_NAME[idx]=${CONFIG_ITEM[2]}
1032         MOUNT_POINT[idx]=${CONFIG_ITEM[3]}
1033         DEVICE_TYPE[idx]=${CONFIG_ITEM[4]}
1034         FS_NAME[idx]=${CONFIG_ITEM[5]}
1035         MGS_NIDS[idx]=${CONFIG_ITEM[6]}
1036         INDEX[idx]=${CONFIG_ITEM[7]}
1037         FORMAT_OPTIONS[idx]=${CONFIG_ITEM[8]}
1038         MKFS_OPTIONS[idx]=${CONFIG_ITEM[9]}
1039         MOUNT_OPTIONS[idx]=${CONFIG_ITEM[10]}
1040         FAILOVERS[idx]=${CONFIG_ITEM[11]}
1041
1042         MODULE_OPTS[idx]=`echo "${MODULE_OPTS[idx]}" | sed 's/"/\\\"/g'`
1043
1044         # Convert IP addresses in NIDs to hostnames
1045         FAILOVERS_NAMES[idx]=$(ip2hostname_multi_node ${FAILOVERS[idx]})
1046         if [ ${PIPESTATUS[0]} -ne 0 ]; then
1047             error_output "${FAILOVERS_NAMES[idx]}"
1048             return 1
1049         fi
1050
1051         # Check some required items for formatting target
1052         if ! check_lustre_item $idx; then
1053             error_output "check_lustre_item():"\
1054                   "Occurred on line ${line_num} in ${CSV_FILE}."
1055             return 1    
1056         fi
1057
1058         idx=${idx}+1
1059     done
1060
1061     return 0
1062 }