Whamcloud - gitweb
2d6797182f0696e321c846a290be305154d72ffc
[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     gm*)    # Myrinet
314             # FIXME: Use /usr/sbin/gmlndnid to find the hostname of
315             # the specified GM Global node ID 
316             ;;
317     ptl*)   # Portals
318             # FIXME: Convert portal ID to hostname
319             ;;
320     *)  # tcp, o2ib, cib, openib, iib, vib, ra
321         ip_addr=${addr}
322         # Is it IP address or hostname?
323         if [ -n "`echo ${ip_addr} | sed -e 's/\([0-9]\{1,3\}\.\)\{3,3\}[0-9]\{1,3\}//'`" ]
324         then
325             host_name=${ip_addr}
326             echo ${host_name}
327             return 0
328         fi
329
330         # Execute remote command to get the host name
331         ret_str=$(${REMOTE} ${ip_addr} "hostname" 2>&1 </dev/null)
332         if [ ${PIPESTATUS[0]} -ne 0 -a -n "${ret_str}" ]; then
333             echo "`basename $0`: nid2hostname() error:" \
334             "remote command to ${ip_addr} error: ${ret_str}"
335             return 1
336         fi
337         remote_error "nid2hostname" ${ip_addr} "${ret_str}" && return 1
338
339         if is_pdsh; then
340             host_name=`echo ${ret_str} | awk '{print $2}'`
341         else
342             host_name=`echo ${ret_str} | awk '{print $1}'`
343         fi
344         ;;
345     esac
346
347     echo ${host_name}
348     return 0
349 }
350
351 # nids2hostname nids
352 # Get the hostname of the lustre cluster node which has the nids - $nids
353 nids2hostname() {
354     local nids=$1
355     local host_name=
356     local nid
357     local nettype
358
359     for nid in ${nids//,/ }; do
360         [ "${nid}" != "${nid#*@*}" ] && nettype=${nid#*@} || nettype=tcp
361
362         case "${nettype}" in
363         lo* | elan* | gm* | ptl*) ;;
364         *)  # tcp, o2ib, cib, openib, iib, vib, ra
365             host_name=$(nid2hostname ${nid})
366             if [ ${PIPESTATUS[0]} -ne 0 ]; then
367                 echo "${host_name}"
368                 return 1
369             fi
370             ;;
371         esac
372     done
373
374     if [ -z "${host_name}" ]; then
375         echo "`basename $0`: nids2hostname() error:" \
376         "Can not get the hostname from nids - \"${nids}\"!"
377         return 1
378     fi
379
380     echo ${host_name}
381     return 0
382 }
383
384 # ip2hostname_single_node nids
385 # Convert IP addresses in $nids into hostnames
386 # NID in $nids are delimited by commas, ie all the $nids belong to one node
387 ip2hostname_single_node() {
388     local orig_nids=$1
389     local nids=
390     local nid host_name
391     local nettype
392
393     for nid in ${orig_nids//,/ }; do
394         [ "${nid}" != "${nid#*@*}" ] && nettype=${nid#*@} || nettype=tcp
395
396         case "${nettype}" in
397         lo* | elan* | gm* | ptl*) ;;
398         *)  # tcp, o2ib, cib, openib, iib, vib, ra
399             host_name=$(nid2hostname ${nid})
400             if [ ${PIPESTATUS[0]} -ne 0 ]; then
401                 echo "${host_name}"
402                 return 1
403             fi
404
405             nid=${host_name}@${nettype}
406             ;;
407         esac
408
409         [ -z "${nids}" ] && nids=${nid} || nids=${nids},${nid}
410     done
411
412     echo ${nids}
413     return 0
414 }
415
416 # ip2hostname_multi_node nids
417 # Convert IP addresses in $nids into hostnames
418 # NIDs belong to multiple nodes are delimited by colons in $nids
419 ip2hostname_multi_node() {
420     local orig_nids=$1
421     local nids=
422     local nid
423
424     for nid in ${orig_nids//:/ }; do
425         nid=$(ip2hostname_single_node ${nid})
426         if [ ${PIPESTATUS[0]} -ne 0 ]; then
427             echo "${nid}"
428             return 1
429         fi
430
431         [ -z "${nids}" ] && nids=${nid} || nids=${nids}:${nid}
432     done
433
434     echo ${nids}
435     return 0
436 }
437
438 # comma_list space-delimited-list
439 # Convert a space-delimited list to a sorted list of unique values
440 # separated by commas.
441 comma_list() {
442     # the sed converts spaces to commas, but leaves the last space
443     # alone, so the line doesn't end with a comma.
444     echo "$*" | tr -s " " "\n" | sort -b -u | tr "\n" " " | sed 's/ \([^$]\)/,\1/g'
445 }
446
447 # host_in_hostlist hostname hostlist
448 # Given a hostname, and a list of hostnames, return true if the hostname
449 # appears in the list of hostnames, or false otherwise.
450 host_in_hostlist() {
451     local HOST=$1
452     local HOSTLIST=$2
453
454     [ -z "$HOST" -o -z "$HOSTLIST" ] && false && return
455
456     # Hostnames in the list are separated by commas.
457     [[ ,$HOSTLIST, == *,$HOST,* ]] && true && return
458
459     false && return
460 }
461
462 # exclude_items_from_list list_of_items list_of_items_to_exclude
463 # Given a list of items, and a second list of items to exclude from
464 # the first list, return the contents of the first list minus the contents
465 # of the second.
466 exclude_items_from_list() {
467     local INLIST=$1
468     local EXCLUDELIST=$2
469     local ITEM OUTLIST
470
471     # Handle an empty inlist by throwing back an empty string.
472     if [ -z "$INLIST" ]; then
473         echo ""
474         return 0
475     fi
476
477     # Handle an empty excludelist by throwing back the inlist unmodified.
478     if [ -z "$EXCLUDELIST" ]; then
479         echo $INLIST
480         return 0
481     fi
482
483     for ITEM in ${INLIST//,/ }; do
484         if ! host_in_hostlist $ITEM $EXCLUDELIST; then
485            OUTLIST="$OUTLIST,$ITEM"
486         fi
487     done
488
489     # strip leading comma
490     echo ${OUTLIST#,}
491 }
492
493 # get_csv_nodelist csv_file
494 # Get the comma-separated list of all the nodes from the CSV file
495 get_csv_nodelist() {
496     local csv_file=$1
497     local all_nodelist
498
499     # Check the CSV file
500     ! check_file ${csv_file} 2>&1 && return 1
501
502     all_nodelist=$(egrep -v "([[:space:]]|^)#" ${csv_file} | cut -d, -f 1)
503     all_nodelist=$(comma_list ${all_nodelist})
504
505     echo ${all_nodelist}
506     return 0
507 }
508
509 # get_nodelist
510 # Get the comma-separated list of nodes to be operated on
511 # Note: CSV_FILE, USE_ALLNODES, SPECIFIED_NODELIST and EXCLUDED_NODELIST
512 # are global variables
513 get_nodelist() {
514     local ALL_NODELIST
515
516     # Get the list of all the nodes in the CSV file
517     ALL_NODELIST=$(get_csv_nodelist ${CSV_FILE})
518     [ ${PIPESTATUS[0]} -ne 0 ] && echo "${ALL_NODELIST}" && return 1
519
520     if [ -z "${ALL_NODELIST}" ]; then
521         echo "`basename $0`: get_nodelist() error:"\
522              "There are no hosts in the ${CSV_FILE} file!"
523         return 1
524     fi
525
526     if ${USE_ALLNODES}; then
527         echo ${ALL_NODELIST} && return 0
528     fi
529
530     if [ -n "${SPECIFIED_NODELIST}" ]; then
531         echo $(exclude_items_from_list ${SPECIFIED_NODELIST} ${EXCLUDED_NODELIST})
532         return 0
533     fi
534
535     if [ -n "${EXCLUDED_NODELIST}" ]; then
536         echo $(exclude_items_from_list ${ALL_NODELIST} ${EXCLUDED_NODELIST})
537         return 0
538     fi
539
540     # No hosts to be operated on
541     echo ""
542     return 0
543 }
544
545 # check_nodelist nodelist
546 # Given a list of nodes to be operated on, check whether the nodelist is 
547 # empty or not and output prompt message.
548 check_nodelist() {
549     local nodes_to_use=$1
550
551     if [ -z "${nodes_to_use}" ]; then
552         error_output "There are no nodes to be operated on."\
553              "Check the node selection options (-a, -w or -x)."
554         usage 1>&2
555         return 1
556     else
557         verbose_output "Operating on the following nodes: ${nodes_to_use}"
558     fi
559
560     return 0
561 }
562
563 # nid_in_nidlist nid nidlist
564 # Given a nid, and a list of nids in one node (delimited by comma ','),
565 # return true if the nid appears in the list of nids, or false otherwise.
566 nid_in_nidlist() {
567     local nid="$1"
568     local nidlist="$2"
569     local my_nid
570
571     [ -z "${nid}" -o -z "${nidlist}" ] && false && return
572
573     if [[ "${nid}" != *@* || "${nid#*@}" == tcp* ]]; then
574         # network type is tcp
575         for my_nid in ${nidlist//,/ }; do
576             [ "${nid%@*}" = "${my_nid%@*}" ] && true && return
577         done
578     else
579         # network type is not tcp
580         [[ ,${nidlist}, == *,${nid},* ]] && true && return
581     fi
582
583     false && return
584 }
585
586 # get_mgs_nids mgs_hostname mgs_nids
587 # Get the corresponding NID(s) of the MGS node ${mgs_hostname} from the
588 # "mgs nids" field of one lustre target in the CSV file
589 get_mgs_nids() {
590     local mgs_node="$1"
591     local all_mgs_nids="$2"
592     local mgs_nids
593     local ret_str
594
595     # Check whether the hostname of the mgs node is in 
596     # the mgs nids string
597     for mgs_nids in ${all_mgs_nids//:/ }; do
598         if nid_in_nidlist ${mgs_node} ${mgs_nids}; then
599             echo ${mgs_nids}
600             return 0
601         fi
602     done
603
604     # Let's use lctl to get the real nids from the mgs node
605     ret_str=$($REMOTE $mgs_node "PATH=\$PATH:/sbin:/usr/sbin
606 $LCTL list_nids" 2>&1 </dev/null)
607     if [ ${PIPESTATUS[0]} -ne 0 -a -n "${ret_str}" ]; then
608         echo "$(basename $0): get_mgs_nids() error:" \
609         "remote command to ${mgs_node} error: ${ret_str}"
610         return 1
611     fi
612     remote_error "get_mgs_nids" ${mgs_node} "${ret_str}" && return 1
613
614     local real_mgs_nids=${ret_str//${mgs_node}:/}
615     for real_mgs_nid in ${real_mgs_nids}; do
616         for mgs_nids in ${all_mgs_nids//:/ }; do
617             if nid_in_nidlist ${real_mgs_nid} ${mgs_nids}; then
618                 echo ${mgs_nids}
619                 return 0
620             fi
621         done
622     done
623
624     echo "$(basename $0): get_mgs_nids() error:" \
625     "Can not figure out which nids corresponding to the MGS"\
626     "node ${mgs_node} from \"${all_mgs_nids}\"!"
627
628     return 1
629 }
630
631 # Check the items required for OSTs, MDTs and MGS
632 #
633 # When formatting an OST, the following items: hostname,
634 # device name, device type and mgs nids, cannot have null value.
635 #
636 # When formatting an MDT or MGS, the following items: hostname,
637 # device name and device type, cannot have null value.
638 check_lustre_item() {
639     # Check argument
640     if [ $# -eq 0 ]; then
641         error_output "check_lustre_item(): Missing argument"\
642                   "for function check_lustre_item()!"
643         return 1
644     fi
645
646     declare -i i=$1
647
648     # Check hostname, device name and device type
649     if [ -z "${HOST_NAME[i]}" ] || \
650     [ -z "${DEVICE_NAME[i]}" ] || [ -z "${DEVICE_TYPE[i]}" ]; then
651         error_output "check_lustre_item(): Some required"\
652                   "item has null value! Check hostname,"\
653                   "device name and device type!"
654         return 1
655     fi
656
657     # Check mgs nids
658     if [ "${DEVICE_TYPE[i]}" = "ost" ]&&[ -z "${MGS_NIDS[i]}" ]; then
659         error_output "check_lustre_item(): OST's mgs nids"\
660                   "item has null value!"
661         return 1
662     fi
663
664     # Check mount point
665     if [ -z "${MOUNT_POINT[i]}" ]; then
666         error_output "check_lustre_item(): mount"\
667                   "point item of target ${DEVICE_NAME[i]} has null value!"
668         return 1
669     fi
670
671     return 0
672 }
673
674 # Get the number of MGS nodes in the cluster
675 get_mgs_num() {
676     INIT_IDX=0
677     MGS_NUM=${#MGS_NODENAME[@]}
678     [ -z "${MGS_NODENAME[0]}" ] && let "INIT_IDX += 1" \
679     && let "MGS_NUM += 1"
680 }
681
682 # is_mgs_node hostname
683 # Verify whether @hostname is a MGS node
684 is_mgs_node() {
685     local host_name=$1
686     declare -i i
687
688     get_mgs_num
689     for ((i = ${INIT_IDX}; i < ${MGS_NUM}; i++)); do
690         [ "${MGS_NODENAME[i]}" = "${host_name}" ] && return 0
691     done
692
693     return 1
694 }
695
696 # Check whether the MGS nodes are in the same failover group
697 check_mgs_group() {
698     declare -i i
699     declare -i j
700     declare -i idx
701     local mgs_node
702
703     get_mgs_num
704     for ((i = ${INIT_IDX}; i < ${MGS_NUM}; i++)); do
705         mgs_node=${MGS_NODENAME[i]}
706         for ((j = ${INIT_IDX}; j < ${MGS_NUM}; j++)); do
707           [ "${MGS_NODENAME[j]}" = "${mgs_node}" ] && continue 1
708
709           idx=${MGS_IDX[j]}
710           if [ "${FAILOVERS_NAMES[idx]#*$mgs_node*}" = "${FAILOVERS_NAMES[idx]}" ]
711           then
712             error_output "check_mgs_group():"\
713             "MGS node ${mgs_node} is not in the ${HOST_NAME[idx]}"\
714             "failover group!"
715             return 1
716           fi
717         done
718     done
719
720     return 0
721 }
722
723 # Get and check MGS servers.
724 # There should be no more than one MGS specified in the entire CSV file.
725 check_mgs() {
726     declare -i i
727     declare -i j
728     declare -i exp_idx    # Index of explicit MGS servers
729     declare -i imp_idx    # Index of implicit MGS servers
730     local is_exp_mgs is_imp_mgs
731     local mgs_node
732
733     # Initialize the MGS_NODENAME and MGS_IDX arrays
734     unset MGS_NODENAME
735     unset MGS_IDX
736
737     exp_idx=1
738     imp_idx=1
739     for ((i = 0; i < ${#HOST_NAME[@]}; i++)); do
740         is_exp_mgs=false
741         is_imp_mgs=false
742
743         # Check whether this node is an explicit MGS node 
744         # or an implicit one
745         if [ "${DEVICE_TYPE[i]#*mgs*}" != "${DEVICE_TYPE[i]}" ]; then
746             verbose_output "Explicit MGS target" \
747             "${DEVICE_NAME[i]} in host ${HOST_NAME[i]}."
748             is_exp_mgs=true
749         fi
750
751         if [ "${DEVICE_TYPE[i]}" = "mdt" -a -z "${MGS_NIDS[i]}" ]; then
752             verbose_output "Implicit MGS target" \
753             "${DEVICE_NAME[i]} in host ${HOST_NAME[i]}."
754             is_imp_mgs=true
755         fi
756
757         # Get and check MGS servers
758         if ${is_exp_mgs} || ${is_imp_mgs}; then
759             # Check whether more than one MGS target in one MGS node
760             if is_mgs_node ${HOST_NAME[i]}; then
761                 error_output "check_mgs():"\
762                 "More than one MGS target in the same node -"\
763                 "\"${HOST_NAME[i]}\"!"
764                 return 1
765             fi
766
767             # Get and check primary MGS server and backup MGS server        
768             if [ "${FORMAT_OPTIONS[i]}" = "${FORMAT_OPTIONS[i]#*noformat*}" ]
769             then
770                 # Primary MGS server
771                 if [ -z "${MGS_NODENAME[0]}" ]; then
772                     if [ "${is_exp_mgs}" = "true" -a ${imp_idx} -gt 1 ] \
773                     || [ "${is_imp_mgs}" = "true" -a ${exp_idx} -gt 1 ]; then
774                         error_output "check_mgs():"\
775                         "There exist both explicit and implicit MGS"\
776                         "targets in the CSV file!"
777                         return 1
778                     fi
779                     MGS_NODENAME[0]=${HOST_NAME[i]}
780                     MGS_IDX[0]=$i
781                 else
782                     mgs_node=${MGS_NODENAME[0]}
783                     if [ "${FAILOVERS_NAMES[i]#*$mgs_node*}" = "${FAILOVERS_NAMES[i]}" ]
784                     then
785                         error_output "check_mgs():"\
786                         "More than one primary MGS nodes in the CSV" \
787                         "file - ${MGS_NODENAME[0]} and ${HOST_NAME[i]}!"
788                     else
789                         error_output "check_mgs():"\
790                         "MGS nodes ${MGS_NODENAME[0]} and ${HOST_NAME[i]}"\
791                         "are failover pair, one of them should use"\
792                         "\"--noformat\" in the format options item!"
793                     fi
794                     return 1
795                 fi
796             else    # Backup MGS server
797                 if [ "${is_exp_mgs}" = "true" -a ${imp_idx} -gt 1 ] \
798                 || [ "${is_imp_mgs}" = "true" -a ${exp_idx} -gt 1 ]; then
799                     error_output "check_mgs():"\
800                     "There exist both explicit and implicit MGS"\
801                     "targets in the CSV file!"
802                     return 1
803                 fi
804
805                 if ${is_exp_mgs}; then # Explicit MGS
806                     MGS_NODENAME[exp_idx]=${HOST_NAME[i]}
807                     MGS_IDX[exp_idx]=$i
808                     exp_idx=$(( exp_idx + 1 ))
809                 else    # Implicit MGS
810                     MGS_NODENAME[imp_idx]=${HOST_NAME[i]}
811                     MGS_IDX[imp_idx]=$i
812                     imp_idx=$(( imp_idx + 1 ))
813                 fi
814             fi
815         fi #End of "if ${is_exp_mgs} || ${is_imp_mgs}"
816     done
817
818     # Check whether the MGS nodes are in the same failover group
819     if ! check_mgs_group; then
820         return 1
821     fi
822
823     return 0
824 }
825
826 # Execute remote command to add module options to
827 # the module configuration file
828 add_module_options() {
829     declare -i i=$1
830     local hostname=$2
831
832     if [ -z "$hostname" ]; then
833         error_output "add_module_options(): Missing hostname!"
834         return 1
835     fi
836
837     [ -z "${MODULE_OPTS[i]}" ] && return 0
838
839     # Execute remote command to add module options to
840     # the module configuration file
841     verbose_output "Adding module options to $hostname"
842     $REMOTE $hostname "PATH=\$PATH:/sbin:/usr/sbin
843 echo \"${MODULE_OPTS[i]}\" | $MODULE_CONFIG"
844     local RC=${PIPESTATUS[0]}
845     if [ $RC -ne 0 ]; then
846         error_output "add_module_options():"\
847         "Failed to add module options to $hostname!"
848         return $RC
849     fi
850
851     return 0
852 }
853
854 # check_lnet_connect hostname_index mgs_hostname
855 # Check whether the target node can contact the MGS node @mgs_hostname
856 # If @mgs_hostname is null, then it means the primary MGS node
857 check_lnet_connect() {
858     declare -i i=$1
859     local mgs_node=$2
860
861     local mgs_prim_nids
862     local nids_str=
863     local mgs_nid 
864     local ping_mgs
865     local try
866
867     # Execute remote command to check that 
868     # this node can contact the MGS node
869     verbose_output "Checking lnet connectivity between" \
870     "${HOST_NAME[i]} and the MGS node ${mgs_node}"
871     mgs_prim_nids=`echo ${MGS_NIDS[i]} | awk -F: '{print $1}'`
872
873     if [ -z "${mgs_node}" -o $MGS_NUM -eq 1 ]; then
874         nids_str=${mgs_prim_nids}    # nids of primary MGS node
875         if [ -z "${nids_str}" ]; then
876             error_output "check_lnet_connect():"\
877             "Check the mgs nids item of host ${HOST_NAME[i]}!"\
878             "Missing nids of the primary MGS node!"
879             return 1
880         fi
881     else
882         # Get the corresponding NID(s) of the MGS node ${mgs_node}
883         # from the "mgs nids" field
884         nids_str=$(get_mgs_nids ${mgs_node} ${MGS_NIDS[i]})
885         if [ ${PIPESTATUS[0]} -ne 0 ]; then
886             error_output "${nids_str}"
887             return 1
888         fi
889     fi
890
891     ping_mgs=false
892     for mgs_nid in ${nids_str//,/ }
893     do
894         for try in $(seq 0 5); do
895             $REMOTE ${HOST_NAME[i]} "PATH=\$PATH:/sbin:/usr/sbin
896 $LCTL ping $mgs_nid 5 1>/dev/null"
897             if [ ${PIPESTATUS[0]} -eq 0 ]; then
898                 # This node can contact the MGS node
899                 verbose_output "${HOST_NAME[i]} can contact the MGS" \
900                 "node $mgs_node by using nid \"$mgs_nid\"!"
901                 ping_mgs=true
902                 break
903             fi
904         done
905     done
906
907     if ! ${ping_mgs}; then
908         error_output "check_lnet_connect():" \
909         "${HOST_NAME[i]} cannot contact the MGS node ${mgs_node}"\
910         "with nids - \"${nids_str}\"! Check ${LCTL} command!"
911         return 1
912     fi
913
914     return 0
915 }
916
917 # Start lnet network in the cluster node and check that 
918 # this node can contact the MGS node
919 check_lnet() {
920     if ! $VERIFY_CONNECT; then
921         return 0
922     fi
923
924     # Check argument
925     if [ $# -eq 0 ]; then
926         error_output "check_lnet(): Missing argument!"
927         return 1
928     fi
929
930     declare -i i=$1
931     declare -i j
932     local ret_str
933
934     # Execute remote command to start lnet network
935     verbose_output "Starting lnet network on ${HOST_NAME[i]}"
936     ret_str=$($REMOTE ${HOST_NAME[i]} "PATH=\$PATH:/sbin:/usr/sbin
937 modprobe lnet && $LCTL network up" 2>&1)
938     if [ ${PIPESTATUS[0]} -ne 0 ]; then
939         error_output "check_lnet(): start lnet network on" \
940         "${HOST_NAME[i]} error: $ret_str"
941         return 1
942     fi
943
944     if is_mgs_node ${HOST_NAME[i]}; then
945         return 0
946     fi
947
948     # Execute remote command to check that 
949     # this node can contact the MGS node
950     for ((j = 0; j < ${MGS_NUM}; j++)); do
951         if ! check_lnet_connect $i ${MGS_NODENAME[j]}; then
952             return 1
953         fi
954     done
955
956     return 0
957 }
958
959 # Start lnet network in the MGS node
960 start_mgs_lnet() {
961     declare -i i
962     declare -i idx
963
964     if [ -z "${MGS_NODENAME[0]}" -a  -z "${MGS_NODENAME[1]}" ]; then
965         if ${USE_ALLNODES}; then
966             verbose_output "There is no MGS target in the ${CSV_FILE} file."
967         else
968             verbose_output "There is no MGS target in the node list \"${NODES_TO_USE}\"."
969         fi
970         return 0
971     fi
972
973     for ((i = ${INIT_IDX}; i < ${MGS_NUM}; i++)); do
974         # Execute remote command to add lnet options lines to 
975         # the MGS node's modprobe.conf/modules.conf
976         idx=${MGS_IDX[i]}
977         add_module_options $idx ${MGS_NODENAME[i]} || return ${PIPESTATUS[0]}
978
979         # Start lnet network in the MGS node
980         check_lnet $idx || return ${PIPESTATUS[0]}
981     done
982
983     return 0
984 }
985
986 # Get all the Lustre target items in the CSV file and do some checks.
987 get_lustre_items() {
988     # Check argument
989     if [ $# -eq 0 ]; then
990         error_output "get_lustre_items(): Missing argument"\
991                   "for function get_lustre_items()!"
992         return 1
993     fi
994
995     local CSV_FILE=$1
996     local LINE
997     local marker
998     local hostname
999     declare -i line_num=0
1000     declare -i idx=0
1001
1002     exec 9< ${CSV_FILE}
1003     while read -u 9 -r LINE; do
1004         line_num=${line_num}+1
1005         # verbose_output "Parsing line ${line_num}: $LINE"
1006
1007         # Get rid of the empty line
1008         [ -z "`echo ${LINE} | awk '/[[:alnum:]]/ {print $0}'`" ] && continue
1009
1010         # Get rid of the comment line
1011         [ -z "`echo \"${LINE}\" | egrep -v \"([[:space:]]|^)#\"`" ] && continue
1012
1013         # Skip the Linux MD/LVM line
1014         marker=$(echo ${LINE} | cut -d, -f 2)
1015         if [ "${marker}" = "${MD_MARKER}" -o "${marker}" = "${PV_MARKER}" ] \
1016         || [ "${marker}" = "${VG_MARKER}" -o "${marker}" = "${LV_MARKER}" ]; then
1017             continue
1018         fi
1019
1020         # Skip the host which is not specified in the host list
1021         if ! ${USE_ALLNODES}; then
1022             hostname=$(echo ${LINE} | cut -d, -f 1)
1023             ! host_in_hostlist ${hostname} ${NODES_TO_USE} && continue
1024         fi
1025
1026         # Parse the config line into CONFIG_ITEM
1027         if ! parse_line "$LINE"; then
1028             error_output "parse_line(): Occurred"\
1029                   "on line ${line_num} in ${CSV_FILE}: $LINE"
1030             return 1
1031         fi
1032
1033         HOST_NAME[idx]=${CONFIG_ITEM[0]}
1034         MODULE_OPTS[idx]=${CONFIG_ITEM[1]}
1035         DEVICE_NAME[idx]=${CONFIG_ITEM[2]}
1036         MOUNT_POINT[idx]=${CONFIG_ITEM[3]}
1037         DEVICE_TYPE[idx]=${CONFIG_ITEM[4]}
1038         FS_NAME[idx]=${CONFIG_ITEM[5]}
1039         MGS_NIDS[idx]=${CONFIG_ITEM[6]}
1040         INDEX[idx]=${CONFIG_ITEM[7]}
1041         FORMAT_OPTIONS[idx]=${CONFIG_ITEM[8]}
1042         MKFS_OPTIONS[idx]=${CONFIG_ITEM[9]}
1043         MOUNT_OPTIONS[idx]=${CONFIG_ITEM[10]}
1044         FAILOVERS[idx]=${CONFIG_ITEM[11]}
1045
1046         MODULE_OPTS[idx]=`echo "${MODULE_OPTS[idx]}" | sed 's/"/\\\"/g'`
1047
1048         # Convert IP addresses in NIDs to hostnames
1049         FAILOVERS_NAMES[idx]=$(ip2hostname_multi_node ${FAILOVERS[idx]})
1050         if [ ${PIPESTATUS[0]} -ne 0 ]; then
1051             error_output "${FAILOVERS_NAMES[idx]}"
1052             return 1
1053         fi
1054
1055         # Check some required items for formatting target
1056         if ! check_lustre_item $idx; then
1057             error_output "check_lustre_item():"\
1058                   "Occurred on line ${line_num} in ${CSV_FILE}."
1059             return 1    
1060         fi
1061
1062         idx=${idx}+1
1063     done
1064
1065     return 0
1066 }