Whamcloud - gitweb
Branch HEAD
[fs/lustre-release.git] / lustre / scripts / lustre_config.in
diff --git a/lustre/scripts/lustre_config.in b/lustre/scripts/lustre_config.in
new file mode 100644 (file)
index 0000000..47662b4
--- /dev/null
@@ -0,0 +1,1222 @@
+#!/bin/bash
+
+# vim:expandtab:shiftwidth=4:softtabstop=4:tabstop=4:
+
+#
+# lustre_config - format and set up multiple lustre servers from a csv file
+#
+# This script is used to parse each line of a spreadsheet (csv file) and 
+# execute remote commands to format (mkfs.lustre) every Lustre target 
+# that will be part of the Lustre cluster.
+# 
+# In addition, it can also verify the network connectivity and hostnames in 
+# the cluster, configure Linux MD/LVM devices and produce High-Availability
+# software configurations for Heartbeat or CluManager.
+#
+################################################################################
+
+# Usage
+usage() {
+    cat >&2 <<EOF
+
+Usage:  `basename $0` [options] <csv file>
+
+    This script is used to format and set up multiple lustre servers from a
+    csv file.
+
+    Options:
+    -h          help and examples
+    -a          select all the nodes from the csv file to operate on
+    -w hostname,hostname,...
+                select the specified list of nodes (separated by commas) to
+                operate on rather than all the nodes in the csv file
+    -x hostname,hostname,...
+                exclude the specified list of nodes (separated by commas)
+    -t HAtype   produce High-Availability software configurations
+                The argument following -t is used to indicate the High-
+                Availability software type. The HA software types which 
+                are currently supported are: hbv1 (Heartbeat version 1)
+                and hbv2 (Heartbeat version 2).
+    -n          no net - don't verify network connectivity and hostnames
+                in the cluster
+    -d          configure Linux MD/LVM devices before formatting the
+                Lustre targets
+    -f          force-format the Lustre targets using --reformat option
+    -m          no fstab change - don't modify /etc/fstab to add the new
+                Lustre targets
+                If using this option, then the value of "mount options"
+                item in the csv file will be passed to mkfs.lustre, else
+                the value will be added into the /etc/fstab.
+    -v          verbose mode
+    csv file    a spreadsheet that contains configuration parameters
+                (separated by commas) for each target in a Lustre cluster
+
+EOF
+    exit 1
+}
+
+# Samples 
+sample() {
+    cat <<EOF
+
+This script is used to parse each line of a spreadsheet (csv file) and 
+execute remote commands to format (mkfs.lustre) every Lustre target 
+that will be part of the Lustre cluster.
+
+It can also optionally: 
+ * verify the network connectivity and hostnames in the cluster
+ * configure Linux MD/LVM devices
+ * modify /etc/modprobe.conf to add Lustre networking info
+ * add the Lustre server info to /etc/fstab
+ * produce configurations for Heartbeat or CluManager.
+
+There are 5 kinds of line formats in the csv file. They represent the following 
+targets:
+1) Linux MD device
+The format is:
+hostname,MD,md name,operation mode,options,raid level,component devices
+
+hostname            hostname of the node in the cluster
+MD                  marker of MD device line
+md name             MD device name, e.g. /dev/md0
+operation mode      create or remove, default is create
+options             a "catchall" for other mdadm options, e.g. "-c 128"
+raid level          raid level: 0,1,4,5,6,10,linear and multipath
+component devices   block devices to be combined into the MD device
+                    Multiple devices are separated by space or by using
+                    shell expansions, e.g. "/dev/sd{a,b,c}"
+
+2) Linux LVM PV (Physical Volume)
+The format is:
+hostname,PV,pv names,operation mode,options
+
+hostname            hostname of the node in the cluster
+PV                  marker of PV line
+pv names            devices or loopback files to be initialized for later
+                    use by LVM or to be wiped the label, e.g. /dev/sda
+                    Multiple devices or files are separated by space or by
+                    using shell expansions, e.g. "/dev/sd{a,b,c}"
+operation mode      create or remove, default is create
+options             a "catchall" for other pvcreate/pvremove options
+                    e.g. "-vv"
+
+3) Linux LVM VG (Volume Group)
+The format is:
+hostname,VG,vg name,operation mode,options,pv paths
+
+hostname            hostname of the node in the cluster
+VG                  marker of VG line
+vg name             name of the volume group, e.g. ost_vg
+operation mode      create or remove, default is create
+options             a "catchall" for other vgcreate/vgremove options
+                    e.g. "-s 32M"
+pv paths            physical volumes to construct this VG, required by
+                    create mode
+                    Multiple PVs are separated by space or by using
+                    shell expansions, e.g. "/dev/sd[k-m]1"
+
+4) Linux LVM LV (Logical Volume)
+The format is:
+hostname,LV,lv name,operation mode,options,lv size,vg name
+
+hostname            hostname of the node in the cluster
+LV                  marker of LV line
+lv name             name of the logical volume to be created (optional)
+                    or path of the logical volume to be removed (required
+                    by remove mode)
+operation mode      create or remove, default is create
+options             a "catchall" for other lvcreate/lvremove options
+                    e.g. "-i 2 -I 128"
+lv size             size [kKmMgGtT] to be allocated for the new LV
+                    Default unit is megabytes.
+vg name             name of the VG in which the new LV will be created
+
+5) Lustre target
+The format is:
+hostname,module_opts,device name,mount point,device type,fsname,mgs nids,index,
+format options,mkfs options,mount options,failover nids
+
+hostname            hostname of the node in the cluster, must match "uname -n"
+module_opts         Lustre networking module options
+device name         Lustre target (block device or loopback file)
+mount point         Lustre target mount point
+device type         Lustre target type (mgs, mdt, ost, mgs|mdt, mdt|mgs)
+fsname              Lustre filesystem name, should be limited to 8 characters 
+                    Default is "lustre".
+mgs nids            NID(s) of remote mgs node, required for mdt and ost targets
+                    If this item is not given for an mdt, it is assumed that
+                    the mdt will also be an mgs, according to mkfs.lustre.
+index               Lustre target index
+format options      a "catchall" contains options to be passed to mkfs.lustre
+                    "--device-size", "--param", etc. all goes into this item.
+mkfs options        format options to be wrapped with --mkfsoptions="" and
+                    passed to mkfs.lustre
+mount options       If this script is invoked with "-m" option, then the value of
+                    this item will be wrapped with --mountfsoptions="" and passed
+                    to mkfs.lustre, else the value will be added into /etc/fstab.
+failover nids       NID(s) of failover partner node
+
+All the NIDs in one node are delimited by commas (','). When multiple nodes are
+specified, they are delimited by a colon (':').
+
+Items left blank will be set to defaults.
+
+Example 1 - Simple, with combo MGS/MDT:
+-------------------------------------------------------------------------------
+# combo mdt/mgs
+lustre-mgs,options lnet networks=tcp,/tmp/mgs,/mnt/mgs,mgs|mdt,,,,--device-size=10240
+
+# ost0
+lustre-ost,options lnet networks=tcp,/tmp/ost0,/mnt/ost0,ost,,lustre-mgs@tcp0,,--device-size=10240
+
+# ost1
+lustre-ost,options lnet networks=tcp,/tmp/ost1,/mnt/ost1,ost,,lustre-mgs@tcp0,,--device-size=10240
+-------------------------------------------------------------------------------
+
+Example 2 - Separate MGS/MDT, two networks interfaces:
+-------------------------------------------------------------------------------
+# mgs
+lustre-mgs1,options lnet 'networks="tcp,elan"',/dev/sda,/mnt/mgs,mgs,,,,--quiet --param="sys.timeout=50",,"defaults,noauto","lustre-mgs2,2@elan"
+
+# mdt
+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
+
+# ost
+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
+-------------------------------------------------------------------------------
+
+Example 3 - with combo MGS/MDT failover pair and OST failover pair:
+-------------------------------------------------------------------------------
+# combo mgs/mdt
+lustre-mgs1,options lnet networks=tcp,/tmp/mgs,/mnt/mgs,mgs|mdt,,,,--quiet --device-size=10240,,,lustre-mgs2@tcp0
+
+# combo mgs/mdt backup (--noformat)
+lustre-mgs2,options lnet networks=tcp,/tmp/mgs,/mnt/mgs,mgs|mdt,,,,--quiet --device-size=10240 --noformat,,,lustre-mgs1@tcp0
+
+# ost
+lustre-ost1,options lnet networks=tcp,/tmp/ost1,/mnt/ost1,ost,,"lustre-mgs1@tcp0:lustre-mgs2@tcp0",,--quiet --device-size=10240,,,lustre-ost2@tcp0
+
+# ost backup (--noformat) (note different device name)
+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
+-------------------------------------------------------------------------------
+
+Example 4 - Configure Linux MD/LVM devices before formatting Lustre targets:
+-------------------------------------------------------------------------------
+# MD device on mgsnode
+mgsnode,MD,/dev/md0,,-q,1,/dev/sda1 /dev/sdb1
+
+# MD/LVM devices on ostnode
+ostnode,MD,/dev/md0,,-q -c 128,5,"/dev/sd{a,b,c}"
+ostnode,MD,/dev/md1,,-q -c 128,5,"/dev/sd{d,e,f}"
+ostnode,PV,/dev/md0 /dev/md1
+ostnode,VG,ost_vg,,-s 32M,/dev/md0 /dev/md1
+ostnode,LV,ost0,,-i 2 -I 128,300G,ost_vg
+ostnode,LV,ost1,,-i 2 -I 128,300G,ost_vg
+
+# combo mgs/mdt
+mgsnode,options lnet networks=tcp,/dev/md0,/mnt/mgs,mgs|mdt,,,,--quiet
+
+# ost0
+ostnode,options lnet networks=tcp,/dev/ost_vg/ost0,/mnt/ost0,ost,,mgsnode,,--quiet
+
+# ost1
+ostnode,options lnet networks=tcp,/dev/ost_vg/ost1,/mnt/ost1,ost,,mgsnode,,--quiet
+-------------------------------------------------------------------------------
+
+EOF
+    exit 0
+}
+
+# Get the library of functions
+. @scriptlibdir@/lc_common
+
+#***************************** Global variables *****************************#
+declare -a MGS_NODENAME             # node names of the MGS servers
+declare -a MGS_IDX                  # indexes of MGSs in the global arrays
+declare -i MGS_NUM                  # number of MGS servers in the cluster
+declare -i INIT_IDX
+
+declare -a NODE_NAMES               # node names in the failover group
+declare -a TARGET_OPTS              # target services in one failover group
+
+# All the items in the csv file
+declare -a HOST_NAME MODULE_OPTS DEVICE_NAME MOUNT_POINT DEVICE_TYPE FS_NAME
+declare -a MGS_NIDS INDEX FORMAT_OPTIONS MKFS_OPTIONS MOUNT_OPTIONS FAILOVERS
+
+# Corresponding to MGS_NIDS and FAILOVERS arrays,
+# IP addresses in which were converted to hostnames
+declare -a MGS_NIDS_NAMES FAILOVERS_NAMES
+
+VERIFY_CONNECT=true
+CONFIG_MD_LVM=false
+MODIFY_FSTAB=true
+VERBOSE_OUTPUT=false
+# Get and check the positional parameters
+while getopts "aw:x:t:ndfmhv" OPTION; do
+    case $OPTION in
+    a)
+        [ -z "${SPECIFIED_NODELIST}" ] && [ -z "${EXCLUDED_NODELIST}" ] \
+        && USE_ALLNODES=true
+        NODELIST_OPT="${NODELIST_OPT} -a"
+        ;;
+    w)
+        USE_ALLNODES=false
+        SPECIFIED_NODELIST=$OPTARG
+        NODELIST_OPT="${NODELIST_OPT} -w ${SPECIFIED_NODELIST}"
+        ;;
+    x)
+        USE_ALLNODES=false
+        EXCLUDED_NODELIST=$OPTARG
+        NODELIST_OPT="${NODELIST_OPT} -x ${EXCLUDED_NODELIST}"
+        ;;
+    t)
+        HATYPE_OPT=$OPTARG
+        if [ "${HATYPE_OPT}" != "${HBVER_HBV1}" ] \
+        && [ "${HATYPE_OPT}" != "${HBVER_HBV2}" ] \
+        && [ "${HATYPE_OPT}" != "${HATYPE_CLUMGR}" ]; then
+            echo >&2 $"`basename $0`: Invalid HA software type" \
+                      "- ${HATYPE_OPT}!"
+            usage
+        fi
+        ;;
+    n)
+        VERIFY_CONNECT=false
+        ;;
+    d)
+        CONFIG_MD_LVM=true
+        ;;
+    f)
+        REFORMAT_OPTION=$"--reformat "
+        ;;
+    m)
+        MODIFY_FSTAB=false
+        ;;
+    h)
+        sample
+        ;;
+    v)
+        VERBOSE_OPT=$" -v"
+        VERBOSE_OUTPUT=true
+        ;;
+    ?)
+        usage 
+    esac
+done
+
+# Toss out the parameters we've already processed
+shift  `expr $OPTIND - 1`
+
+# Here we expect the csv file
+if [ $# -eq 0 ]; then
+    echo >&2 $"`basename $0`: Missing csv file!"
+    usage
+fi
+
+# Check the items required for OSTs, MDTs and MGS
+#
+# When formatting an OST, the following items: hostname, module_opts,
+# device name, device type and mgs nids, cannot have null value.
+#
+# When formatting an MDT or MGS, the following items: hostname,
+# module_opts, device name and device type, cannot have null value.
+check_item() {
+    # Check argument
+    if [ $# -eq 0 ]; then
+        echo >&2 $"`basename $0`: check_item() error: Missing argument"\
+                  "for function check_item()!"
+        return 1
+    fi
+
+    declare -i i=$1
+
+    # Check hostname, module_opts, device name and device type
+    if [ -z "${HOST_NAME[i]}" ]||[ -z "${MODULE_OPTS[i]}" ]\
+    ||[ -z "${DEVICE_NAME[i]}" ]||[ -z "${DEVICE_TYPE[i]}" ]; then
+        echo >&2 $"`basename $0`: check_item() error: Some required"\
+                  "item has null value! Check hostname, module_opts,"\
+                  "device name and device type!"
+        return 1
+    fi
+
+    # Check mgs nids
+    if [ "${DEVICE_TYPE[i]}" = "ost" ]&&[ -z "${MGS_NIDS[i]}" ]; then
+        echo >&2 $"`basename $0`: check_item() error: OST's mgs nids"\
+                  "item has null value!"
+        return 1
+    fi
+
+    # Check mount point
+    if [ -z "${MOUNT_POINT[i]}" ]; then
+        echo >&2 $"`basename $0`: check_item() error: mount"\
+                  "point item of target ${DEVICE_NAME[i]} has null value!"
+        return 1
+    fi
+
+    return 0
+}
+
+# Get the number of MGS nodes in the cluster
+get_mgs_num() {
+    INIT_IDX=0
+    MGS_NUM=${#MGS_NODENAME[@]}
+    [ -z "${MGS_NODENAME[0]}" ] && let "INIT_IDX += 1" \
+    && let "MGS_NUM += 1"
+}
+
+# is_mgs_node hostname
+# Verify whether @hostname is a MGS node
+is_mgs_node() {
+    local host_name=$1
+    declare -i i
+
+    get_mgs_num
+    for ((i = ${INIT_IDX}; i < ${MGS_NUM}; i++)); do
+        [ "${MGS_NODENAME[i]}" = "${host_name}" ] && return 0
+    done
+
+    return 1
+}
+
+# Check whether the MGS nodes are in the same failover group
+check_mgs_group() {
+    declare -i i
+    declare -i j
+    declare -i idx
+    local mgs_node
+
+    get_mgs_num
+    for ((i = ${INIT_IDX}; i < ${MGS_NUM}; i++)); do
+        mgs_node=${MGS_NODENAME[i]}
+        for ((j = ${INIT_IDX}; j < ${MGS_NUM}; j++)); do
+          [ "${MGS_NODENAME[j]}" = "${mgs_node}" ] && continue 1
+
+          idx=${MGS_IDX[j]}
+          if [ "${FAILOVERS_NAMES[idx]#*$mgs_node*}" = "${FAILOVERS_NAMES[idx]}" ]
+          then
+            echo >&2 $"`basename $0`: check_mgs_group() error:"\
+            "MGS node ${mgs_node} is not in the ${HOST_NAME[idx]}"\
+            "failover group!"
+            return 1
+          fi
+        done
+    done
+
+    return 0
+}
+
+# Get and check MGS servers.
+# There should be no more than one MGS specified in the entire csv file.
+check_mgs() {
+    declare -i i
+    declare -i j
+    declare -i exp_idx    # Index of explicit MGS servers
+    declare -i imp_idx    # Index of implicit MGS servers
+    local is_exp_mgs is_imp_mgs
+    local mgs_node
+
+    # Initialize the MGS_NODENAME and MGS_IDX arrays
+    unset MGS_NODENAME
+    unset MGS_IDX
+
+    exp_idx=1
+    imp_idx=1
+    for ((i = 0; i < ${#HOST_NAME[@]}; i++)); do
+        is_exp_mgs=false
+        is_imp_mgs=false
+
+        # Check whether this node is an explicit MGS node 
+        # or an implicit one
+        if [ "${DEVICE_TYPE[i]#*mgs*}" != "${DEVICE_TYPE[i]}" ]; then
+            verbose_output "Explicit MGS target" \
+            "${DEVICE_NAME[i]} in host ${HOST_NAME[i]}."
+            is_exp_mgs=true
+        fi
+
+        if [ "${DEVICE_TYPE[i]}" = "mdt" -a -z "${MGS_NIDS[i]}" ]; then
+            verbose_output "Implicit MGS target" \
+            "${DEVICE_NAME[i]} in host ${HOST_NAME[i]}."
+            is_imp_mgs=true
+        fi
+
+        # Get and check MGS servers
+        if ${is_exp_mgs} || ${is_imp_mgs}; then
+            # Check whether more than one MGS target in one MGS node
+            if is_mgs_node ${HOST_NAME[i]}; then
+                echo >&2 $"`basename $0`: check_mgs() error:"\
+                "More than one MGS target in the same node -"\
+                "\"${HOST_NAME[i]}\"!"
+                return 1
+            fi
+
+            # Get and check primary MGS server and backup MGS server        
+            if [ "${FORMAT_OPTIONS[i]}" = "${FORMAT_OPTIONS[i]#*noformat*}" ]
+            then
+                # Primary MGS server
+                if [ -z "${MGS_NODENAME[0]}" ]; then
+                    if [ "${is_exp_mgs}" = "true" -a ${imp_idx} -gt 1 ] \
+                    || [ "${is_imp_mgs}" = "true" -a ${exp_idx} -gt 1 ]; then
+                        echo >&2 $"`basename $0`: check_mgs() error:"\
+                        "There exist both explicit and implicit MGS"\
+                        "targets in the csv file!"
+                        return 1
+                    fi
+                    MGS_NODENAME[0]=${HOST_NAME[i]}
+                    MGS_IDX[0]=$i
+                else
+                    mgs_node=${MGS_NODENAME[0]}
+                    if [ "${FAILOVERS_NAMES[i]#*$mgs_node*}" = "${FAILOVERS_NAMES[i]}" ]
+                    then
+                        echo >&2 $"`basename $0`: check_mgs() error:"\
+                        "More than one primary MGS nodes in the csv" \
+                        "file - ${MGS_NODENAME[0]} and ${HOST_NAME[i]}!"
+                    else
+                        echo >&2 $"`basename $0`: check_mgs() error:"\
+                        "MGS nodes ${MGS_NODENAME[0]} and ${HOST_NAME[i]}"\
+                        "are failover pair, one of them should use"\
+                        "\"--noformat\" in the format options item!"
+                    fi
+                    return 1
+                fi
+            else    # Backup MGS server
+                if [ "${is_exp_mgs}" = "true" -a ${imp_idx} -gt 1 ] \
+                || [ "${is_imp_mgs}" = "true" -a ${exp_idx} -gt 1 ]; then
+                    echo >&2 $"`basename $0`: check_mgs() error:"\
+                    "There exist both explicit and implicit MGS"\
+                    "targets in the csv file!"
+                    return 1
+                fi
+
+                if ${is_exp_mgs}; then # Explicit MGS
+                    MGS_NODENAME[exp_idx]=${HOST_NAME[i]}
+                    MGS_IDX[exp_idx]=$i
+                    exp_idx=$(( exp_idx + 1 ))
+                else    # Implicit MGS
+                    MGS_NODENAME[imp_idx]=${HOST_NAME[i]}
+                    MGS_IDX[imp_idx]=$i
+                    imp_idx=$(( imp_idx + 1 ))
+                fi
+            fi
+        fi #End of "if ${is_exp_mgs} || ${is_imp_mgs}"
+    done
+
+    # Check whether the MGS nodes are in the same failover group
+    if ! check_mgs_group; then
+        return 1
+    fi
+
+    return 0
+}
+
+# Construct the command line of mkfs.lustre
+construct_mkfs_cmdline() {
+    # Check argument
+    if [ $# -eq 0 ]; then
+        echo >&2 $"`basename $0`: construct_mkfs_cmdline() error:"\
+                  "Missing argument for function construct_mkfs_cmdline()!"
+        return 1
+    fi
+
+    declare -i i=$1
+    local mgsnids mgsnids_str
+    local failnids failnids_str
+
+    MKFS_CMD=${MKFS}$" "
+    MKFS_CMD=${MKFS_CMD}${REFORMAT_OPTION}
+
+    case "${DEVICE_TYPE[i]}" in
+    "ost")
+        MKFS_CMD=${MKFS_CMD}$"--ost "
+        ;;
+    "mdt")
+        MKFS_CMD=${MKFS_CMD}$"--mdt "
+        ;;
+    "mgs")
+        MKFS_CMD=${MKFS_CMD}$"--mgs "
+        ;;
+    "mdt|mgs" | "mgs|mdt")
+        MKFS_CMD=${MKFS_CMD}$"--mdt --mgs "
+        ;;
+    *)
+        echo >&2 $"`basename $0`: construct_mkfs_cmdline() error:"\
+                  "Invalid device type - \"${DEVICE_TYPE[i]}\"!"
+        return 1
+        ;;
+    esac
+
+    if [ -n "${FS_NAME[i]}" ]; then
+        MKFS_CMD=${MKFS_CMD}$"--fsname="${FS_NAME[i]}$" "
+    fi
+
+    if [ -n "${MGS_NIDS[i]}" ]; then
+        mgsnids_str=${MGS_NIDS[i]}
+        for mgsnids in ${mgsnids_str//:/ }; do
+            MKFS_CMD=${MKFS_CMD}$"--mgsnode="${mgsnids}$" "
+        done
+    fi
+
+    if [ -n "${INDEX[i]}" ]; then
+        MKFS_CMD=${MKFS_CMD}$"--index="${INDEX[i]}$" "
+    fi
+
+    if [ -n "${FORMAT_OPTIONS[i]}" ]; then
+        MKFS_CMD=${MKFS_CMD}${FORMAT_OPTIONS[i]}$" "
+    fi
+
+    if [ -n "${MKFS_OPTIONS[i]}" ]; then
+        MKFS_CMD=${MKFS_CMD}$"--mkfsoptions="$"\""${MKFS_OPTIONS[i]}$"\""$" "
+    fi
+
+    if [ -n "${MOUNT_OPTIONS[i]}" ]; then
+        if ! ${MODIFY_FSTAB}; then
+            MKFS_CMD=${MKFS_CMD}$"--mountfsoptions="$"\""${MOUNT_OPTIONS[i]}$"\""$" "
+        fi
+    fi
+
+    if [ -n "${FAILOVERS[i]}" ]; then
+        failnids_str=${FAILOVERS[i]}
+        for failnids in ${failnids_str//:/ }; do
+            MKFS_CMD=${MKFS_CMD}$"--failnode="${failnids}$" "
+        done
+    fi
+
+    MKFS_CMD=${MKFS_CMD}${DEVICE_NAME[i]}
+    return 0
+} 
+
+# Get all the node names in this failover group
+get_nodenames() {
+    # Check argument
+    if [ $# -eq 0 ]; then
+        echo >&2 $"`basename $0`: get_nodenames() error: Missing"\
+                  "argument for function get_nodenames()!"
+        return 1
+    fi
+
+    declare -i i=$1
+    declare -i idx
+    local nids
+
+    # Initialize the NODE_NAMES array
+    unset NODE_NAMES
+
+    NODE_NAMES[0]=${HOST_NAME[i]}
+
+    idx=1
+    for nids in ${FAILOVERS_NAMES[i]//:/ }
+    do
+        NODE_NAMES[idx]=$(nids2hostname ${nids})
+        if [ ${PIPESTATUS[0]} -ne 0 ]; then
+            echo >&2 "${NODE_NAMES[idx]}"
+            return 1
+        fi
+    
+        idx=$idx+1
+    done
+
+    return 0
+}
+
+# Verify whether the format line has HA items
+is_ha_line() {
+    declare -i i=$1
+
+    [ -n "${FAILOVERS[i]}" ] && return 0
+
+    return 1
+}
+
+# Produce HA software's configuration files
+gen_ha_config() {
+    declare -i i=$1
+    declare -i idx
+    local  cmd_line
+
+    # Prepare parameters
+    # Hostnames option
+    HOSTNAME_OPT=${HOST_NAME[i]}
+
+    if ! get_nodenames $i; then
+        echo >&2 $"`basename $0`: gen_ha_config() error: Can not get the"\
+        "failover nodenames from failover nids - \"${FAILOVERS[i]}\" in"\
+        "the \"${HOST_NAME[i]}\" failover group!"
+        return 1
+    fi
+
+    for ((idx = 1; idx < ${#NODE_NAMES[@]}; idx++)); do
+        HOSTNAME_OPT=${HOSTNAME_OPT}$":"${NODE_NAMES[idx]}
+    done
+
+    # Target devices option
+    DEVICE_OPT=" -d "${TARGET_OPTS[0]}
+    for ((idx = 1; idx < ${#TARGET_OPTS[@]}; idx++)); do
+        DEVICE_OPT=${DEVICE_OPT}" -d "${TARGET_OPTS[idx]}
+    done
+
+    # Construct the generation script command line
+    case "${HATYPE_OPT}" in
+    "${HBVER_HBV1}"|"${HBVER_HBV2}")    # Heartbeat 
+        cmd_line=${GEN_HB_CONFIG}$" -r ${HATYPE_OPT} -n ${HOSTNAME_OPT}"
+        cmd_line=${cmd_line}${DEVICE_OPT}${VERBOSE_OPT}
+        ;;
+    "${HATYPE_CLUMGR}")                 # CluManager
+        cmd_line=${GEN_CLUMGR_CONFIG}$" -n ${HOSTNAME_OPT}"
+        cmd_line=${cmd_line}${DEVICE_OPT}${VERBOSE_OPT}
+        ;;
+    esac
+    
+    # Execute script to generate HA software's configuration files
+    verbose_output "Generating HA software's configurations in"\
+               "${HOST_NAME[i]} failover group..."
+    verbose_output "${cmd_line}"
+    eval $(echo "${cmd_line}")
+    if [ ${PIPESTATUS[0]} -ne 0 ]; then
+        return 1
+    fi
+    verbose_output "Generate HA software's configurations in"\
+               "${HOST_NAME[i]} failover group OK"
+    
+    return 0
+}
+
+# Configure HA software
+config_ha() {
+    if [ -z "${HATYPE_OPT}" ]; then
+        return 0
+    fi
+
+    declare -i i j k
+    declare -i prim_idx         # Index for PRIM_HOSTNAMES array
+    declare -i target_idx       # Index for TARGET_OPTS and HOST_INDEX arrays
+
+    declare -a PRIM_HOSTNAMES   # Primary hostnames in all the failover
+                                # groups in the lustre cluster
+    declare -a HOST_INDEX       # Indices for the same node in all the 
+                                # format lines in the csv file
+    local prim_host
+
+    # Initialize the PRIM_HOSTNAMES array
+    prim_idx=0
+    unset PRIM_HOSTNAMES
+
+    # Get failover groups and generate HA configuration files
+    for ((i = 0; i < ${#HOST_NAME[@]}; i++)); do
+        prim_host=${HOST_NAME[i]}
+
+        for ((j = 0; j < ${#PRIM_HOSTNAMES[@]}; j++)); do
+            [ "${prim_host}" = "${PRIM_HOSTNAMES[j]}" ] && continue 2
+        done
+
+        target_idx=0
+        unset HOST_INDEX
+        unset TARGET_OPTS
+        for ((k = 0; k < ${#HOST_NAME[@]}; k++)); do
+            if [ "${prim_host}" = "${HOST_NAME[k]}" ] && is_ha_line "${k}"
+            then
+                HOST_INDEX[target_idx]=$k
+                TARGET_OPTS[target_idx]=${DEVICE_NAME[k]}:${MOUNT_POINT[k]}
+                target_idx=$(( target_idx + 1 ))
+            fi
+        done
+
+        if [ ${#TARGET_OPTS[@]} -ne 0 ]; then
+            PRIM_HOSTNAMES[prim_idx]=${prim_host}
+            prim_idx=$(( prim_idx + 1 ))
+
+            if ! gen_ha_config ${HOST_INDEX[0]}; then
+                return 1
+            fi
+        fi
+    done
+
+    if [ ${#PRIM_HOSTNAMES[@]} -eq 0 ]; then
+        verbose_output "There are no \"failover nids\" items in the"\
+        "csv file. No HA configuration files are generated!"
+    fi
+
+    rm -rf ${TMP_DIRS}
+    return 0
+}
+
+# Get all the items in the csv file and do some checks.
+get_items() {
+    # Check argument
+    if [ $# -eq 0 ]; then
+        echo >&2 $"`basename $0`: get_items() error: Missing argument"\
+                  "for function get_items()!"
+        return 1
+    fi
+
+    CSV_FILE=$1
+    local LINE
+    local marker
+    local hostname
+    declare -i line_num=0
+    declare -i idx=0
+
+    while read -r LINE; do
+        line_num=${line_num}+1
+        # verbose_output "Parsing line ${line_num}: $LINE"
+
+        # Get rid of the empty line
+        if [ -z "`echo ${LINE}|awk '/[[:alnum:]]/ {print $0}'`" ]; then
+            continue
+        fi
+
+        # Get rid of the comment line
+        if [ -z "`echo \"${LINE}\" | egrep -v \"([[:space:]]|^)#\"`" ]
+        then
+            continue
+        fi
+
+        # Skip the Linux MD/LVM line
+        marker=$(echo ${LINE} | cut -d, -f 2)
+        if [ "${marker}" = "${MD_MARKER}" -o "${marker}" = "${PV_MARKER}" ] \
+        || [ "${marker}" = "${VG_MARKER}" -o "${marker}" = "${LV_MARKER}" ]; then
+            continue
+        fi
+
+        # Skip the host which is not specified in the host list
+        if ! ${USE_ALLNODES}; then
+            hostname=$(echo ${LINE} | cut -d, -f 1)
+            ! host_in_hostlist ${hostname} ${NODES_TO_USE} && continue
+        fi
+
+        # Parse the config line into CONFIG_ITEM
+        if ! parse_line "$LINE"; then
+            echo >&2 $"`basename $0`: parse_line() error: Occurred"\
+                  "on line ${line_num} in ${CSV_FILE}: $LINE"
+            return 1    
+        fi
+
+        HOST_NAME[idx]=${CONFIG_ITEM[0]}
+        MODULE_OPTS[idx]=${CONFIG_ITEM[1]}
+        DEVICE_NAME[idx]=${CONFIG_ITEM[2]}
+        MOUNT_POINT[idx]=${CONFIG_ITEM[3]}
+        DEVICE_TYPE[idx]=${CONFIG_ITEM[4]}
+        FS_NAME[idx]=${CONFIG_ITEM[5]}
+        MGS_NIDS[idx]=${CONFIG_ITEM[6]}
+        INDEX[idx]=${CONFIG_ITEM[7]}
+        FORMAT_OPTIONS[idx]=${CONFIG_ITEM[8]}
+        MKFS_OPTIONS[idx]=${CONFIG_ITEM[9]}
+        MOUNT_OPTIONS[idx]=${CONFIG_ITEM[10]}
+        FAILOVERS[idx]=${CONFIG_ITEM[11]}
+
+        MODULE_OPTS[idx]=`echo "${MODULE_OPTS[idx]}" | sed 's/"/\\\"/g'`
+
+        # Convert IP addresses in NIDs to hostnames
+        MGS_NIDS_NAMES[idx]=$(ip2hostname_multi_node ${MGS_NIDS[idx]})
+        if [ ${PIPESTATUS[0]} -ne 0 ]; then
+            echo >&2 "${MGS_NIDS_NAMES[idx]}"
+            return 1
+        fi
+
+        FAILOVERS_NAMES[idx]=$(ip2hostname_multi_node ${FAILOVERS[idx]})
+        if [ ${PIPESTATUS[0]} -ne 0 ]; then
+            echo >&2 "${FAILOVERS_NAMES[idx]}"
+            return 1
+        fi
+
+        # Check some required items for formatting target
+        if ! check_item $idx; then
+            echo >&2 $"`basename $0`: check_item() error:"\
+                  "Occurred on line ${line_num} in ${CSV_FILE}."
+            return 1    
+        fi
+
+        idx=${idx}+1
+    done < ${CSV_FILE}
+
+    return 0
+}
+
+# check_lnet_connect hostname_index mgs_hostname
+# Check whether the target node can contact the MGS node @mgs_hostname
+# If @mgs_hostname is null, then it means the primary MGS node
+check_lnet_connect() {
+    declare -i i=$1
+    local mgs_node=$2
+
+    local COMMAND RET_STR
+    local mgs_prim_nids
+    local nids nids_names
+    local nids_str=
+    local mgs_nid 
+    local ping_mgs
+
+    # Execute remote command to check that 
+    # this node can contact the MGS node
+    verbose_output "Checking lnet connectivity between" \
+    "${HOST_NAME[i]} and the MGS node ${mgs_node}"
+    mgs_prim_nids=`echo ${MGS_NIDS[i]} | awk -F: '{print $1}'`
+
+    if [ -z "${mgs_node}" ]; then
+        nids_str=${mgs_prim_nids}    # nids of primary MGS node
+        if [ -z "${nids_str}" ]; then
+            echo >&2 $"`basename $0`: check_lnet_connect() error:"\
+            "Check the mgs nids item of host ${HOST_NAME[i]}!"\
+            "Missing nids of the primary MGS node!"
+            return 1
+        fi
+    else
+        for nids in ${MGS_NIDS[i]//:/ }; do
+            nids_names=$(ip2hostname_single_node ${nids})
+            if [ ${PIPESTATUS[0]} -ne 0 ]; then
+                echo >&2 "${nids_names}"
+                return 1
+            fi
+
+            [ "${nids_names}" != "${nids_names#*$mgs_node*}" ]\
+            && nids_str=${nids} # nids of backup MGS node
+        done
+        if [ -z "${nids_str}" ]; then
+            echo >&2 $"`basename $0`: check_lnet_connect() error:"\
+            "Check the mgs nids item of host ${HOST_NAME[i]}!"\
+            "Can not figure out which nids corresponding to the MGS"\
+            "node ${mgs_node} from \"${MGS_NIDS[i]}\"!"
+            return 1
+        fi
+    fi
+
+    ping_mgs=false
+    for mgs_nid in ${nids_str//,/ }
+    do
+        COMMAND=$"${LCTL} ping ${mgs_nid} 5 || echo failed 2>&1"
+        RET_STR=$(${REMOTE} ${HOST_NAME[i]} "${COMMAND}" 2>&1)
+        if [ ${PIPESTATUS[0]} -eq 0 -a "${RET_STR}" = "${RET_STR#*failed*}" ]
+        then
+            # This node can contact the MGS node
+            verbose_output "${HOST_NAME[i]} can contact the MGS" \
+            "node ${mgs_node} by using nid \"${mgs_nid}\"!"
+            ping_mgs=true
+            break
+        fi
+    done
+
+    if ! ${ping_mgs}; then
+        echo >&2 "`basename $0`: check_lnet_connect() error:" \
+        "${HOST_NAME[i]} cannot contact the MGS node ${mgs_node}"\
+        "with nids - \"${nids_str}\"! Check ${LCTL} command!"
+        return 1
+    fi
+
+    return 0
+}
+
+# Start lnet network in the cluster node and check that 
+# this node can contact the MGS node
+check_lnet() {
+    if ! ${VERIFY_CONNECT}; then
+        return 0
+    fi
+
+    # Check argument
+    if [ $# -eq 0 ]; then
+        echo >&2 $"`basename $0`: check_lnet() error: Missing"\
+              "argument for function check_lnet()!"
+        return 1
+    fi
+
+    declare -i i=$1
+    declare -i j
+    local COMMAND RET_STR
+
+    # Execute remote command to start lnet network
+    verbose_output "Starting lnet network in ${HOST_NAME[i]}"
+    COMMAND="PATH=\$PATH:/sbin:/usr/sbin modprobe lnet; ${LCTL} network up 2>&1"
+    RET_STR=$(${REMOTE} ${HOST_NAME[i]} "${COMMAND}" 2>&1)
+    if [ ${PIPESTATUS[0]} -ne 0 -o "${RET_STR}" = "${RET_STR#*LNET configured*}" ]
+    then
+        echo >&2 "`basename $0`: check_lnet() error: remote" \
+                 "${HOST_NAME[i]} error: ${RET_STR}"
+        return 1
+    fi
+
+    if is_mgs_node ${HOST_NAME[i]}; then
+        return 0
+    fi
+
+    # Execute remote command to check that 
+    # this node can contact the MGS node
+    for ((j = 0; j < ${MGS_NUM}; j++)); do
+        if ! check_lnet_connect $i ${MGS_NODENAME[j]}; then
+            return 1
+        fi
+    done
+
+    return 0
+}
+
+# Start lnet network in the MGS node
+start_mgs_lnet() {
+    declare -i i
+    declare -i idx
+    local COMMAND
+
+    if [ -z "${MGS_NODENAME[0]}" -a  -z "${MGS_NODENAME[1]}" ]; then
+        if ${USE_ALLNODES}; then
+            verbose_output "There is no MGS target in the ${CSV_FILE} file."
+        else
+            verbose_output "There is no MGS target in the node list \"${NODES_TO_USE}\"."
+        fi
+        return 0
+    fi
+
+    for ((i = ${INIT_IDX}; i < ${MGS_NUM}; i++)); do
+        # Execute remote command to add lnet options lines to 
+        # the MGS node's modprobe.conf/modules.conf
+        idx=${MGS_IDX[i]}
+        COMMAND=$"echo \"${MODULE_OPTS[${idx}]}\"|${MODULE_CONFIG}"
+        verbose_output "Adding lnet module options to ${MGS_NODENAME[i]}"
+        ${REMOTE} ${MGS_NODENAME[i]} "${COMMAND}" >&2 
+        if [ ${PIPESTATUS[0]} -ne 0 ]; then
+            echo >&2 "`basename $0`: start_mgs_lnet() error:"\
+                 "Failed to execute remote command to" \
+                 "add module options to ${MGS_NODENAME[i]}!"\
+                 "Check ${MODULE_CONFIG}!"
+            return 1
+        fi
+
+        # Start lnet network in the MGS node
+        if ! check_lnet ${idx}; then
+            return 1    
+        fi
+    done
+
+    return 0
+}
+
+# Execute remote command to add lnet options lines to remote nodes'
+# modprobe.conf/modules.conf and format(mkfs.lustre) Lustre targets
+mass_config() {
+    local COMMAND
+    declare -a REMOTE_PID 
+    declare -a REMOTE_CMD 
+    declare -i pid_num=0
+    declare -i i=0
+
+    if [ ${#HOST_NAME[@]} -eq 0 ]; then
+        verbose_output "There are no Lustre targets to be formatted."
+        return 0
+    fi
+
+    # Start lnet network in the MGS node
+    if ! start_mgs_lnet; then
+        return 1    
+    fi
+
+    for ((i = 0; i < ${#HOST_NAME[@]}; i++)); do
+        # Construct the command line of mkfs.lustre
+        if ! construct_mkfs_cmdline $i; then
+            return 1    
+        fi
+
+        # create the mount point on the node
+        COMMAND="mkdir -p ${MOUNT_POINT[i]}"
+        verbose_output "Creating the mount point ${MOUNT_POINT[i]} on" \
+                       "${HOST_NAME[i]}"
+        ${REMOTE} ${HOST_NAME[i]} "${COMMAND}" >&2 
+        if [ ${PIPESTATUS[0]} -ne 0 ]; then
+            echo >&2 "`basename $0`: mass_config() error:"\
+                 "Failed to execute remote command to"\
+                 "create the mountpoint on ${HOST_NAME[i]}!"
+            return 1
+        fi
+
+        if ! is_mgs_node ${HOST_NAME[i]}; then
+            # Execute remote command to add lnet options lines to 
+            # modprobe.conf/modules.conf
+            COMMAND=$"echo \"${MODULE_OPTS[i]}\"|${MODULE_CONFIG}"
+            verbose_output "Adding lnet module options to" \
+                       "${HOST_NAME[i]}"
+            ${REMOTE} ${HOST_NAME[i]} "${COMMAND}" >&2 
+            if [ ${PIPESTATUS[0]} -ne 0 ]; then
+                echo >&2 "`basename $0`: mass_config() error:"\
+                     "Failed to execute remote command to"\
+                     "add module options to ${HOST_NAME[i]}!"
+                return 1
+            fi
+
+            # Check lnet networks
+            if ! check_lnet $i; then
+                return 1    
+            fi
+        fi
+
+        # Execute remote command to format Lustre target
+        verbose_output "Formatting Lustre target ${DEVICE_NAME[i]} on ${HOST_NAME[i]}..."
+        REMOTE_CMD[${pid_num}]="${REMOTE} ${HOST_NAME[i]} \"(${EXPORT_PATH} ${MKFS_CMD})\""
+        verbose_output "Format command line is: ${REMOTE_CMD[${pid_num}]}"
+        ${REMOTE} ${HOST_NAME[i]} "(${EXPORT_PATH} ${MKFS_CMD})" >&2 &  
+        REMOTE_PID[${pid_num}]=$!
+        pid_num=${pid_num}+1
+        sleep 1
+    done
+
+    # Wait for the exit status of the background remote command
+    verbose_output "Waiting for the return of the remote command..."
+    fail_exit_status=false
+    for ((pid_num = 0; pid_num < ${#REMOTE_PID[@]}; pid_num++)); do
+        wait ${REMOTE_PID[${pid_num}]}
+        if [ ${PIPESTATUS[0]} -ne 0 ]; then
+            echo >&2 "`basename $0`: mass_config() error: Failed"\
+            "to execute \"${REMOTE_CMD[${pid_num}]}\"!"
+            fail_exit_status=true
+        fi
+    done
+
+    if ${fail_exit_status}; then
+        return 1
+    fi    
+
+    verbose_output "All the Lustre targets are formatted successfully!"
+    return 0
+}
+
+# get_mntopts hostname device_name failovers
+# Construct the mount options of Lustre target @device_name in host @hostname
+get_mntopts() {
+    local host_name=$1
+    local device_name=$2
+    local failovers=$3
+    local mnt_opts=
+    local ret_str
+
+    [ -n "${failovers}" ] && mnt_opts=defaults,noauto || mnt_opts=defaults
+
+    # Execute remote command to check whether the device
+    # is a block device or not
+    ret_str=$(${REMOTE} ${host_name} \
+            "[ -b ${device_name} ] && echo block || echo loop" 2>&1)
+    if [ ${PIPESTATUS[0]} -ne 0 -a -n "${ret_str}" ]; then
+        echo "`basename $0`: get_mntopts() error:" \
+        "remote command to ${host_name} error: ${ret_str}"
+        return 1
+    fi
+
+    if [ -z "${ret_str}" ]; then
+        echo "`basename $0`: get_mntopts() error: remote error:" \
+        "No results from remote!" \
+        "Check network connectivity between the local host and ${host_name}!"
+        return 1
+    fi
+
+    [ "${ret_str}" != "${ret_str#*loop}" ] && mnt_opts=${mnt_opts},loop
+
+    echo ${mnt_opts}
+    return 0
+}
+
+# Execute remote command to modify /etc/fstab to add the new Lustre targets
+modify_fstab() {
+    declare -i i
+    local mntent mntopts device_name
+    local COMMAND
+
+    if ! ${MODIFY_FSTAB}; then
+        return 0    
+    fi
+
+    for ((i = 0; i < ${#HOST_NAME[@]}; i++)); do
+        verbose_output "Modify /etc/fstab of host ${HOST_NAME[i]}"\
+                   "to add Lustre target ${DEVICE_NAME[i]}"
+        mntent=${DEVICE_NAME[i]}"\t\t"${MOUNT_POINT[i]}"\t\t"${FS_TYPE}
+
+        # Get mount options
+        if [ -n "${MOUNT_OPTIONS[i]}" ]; then
+            # The mount options already specified in the csv file.
+            mntopts=${MOUNT_OPTIONS[i]}
+        else
+            mntopts=$(get_mntopts ${HOST_NAME[i]} ${DEVICE_NAME[i]}\
+                    ${FAILOVERS[i]})
+            if [ ${PIPESTATUS[0]} -ne 0 ]; then
+                echo >&2 "${mntopts}"
+                return 1
+            fi
+        fi
+
+        mntent=${mntent}"\t"${mntopts}"\t"0" "0
+        verbose_output "`echo -e ${mntent}`"
+
+        # Execute remote command to modify /etc/fstab
+        device_name=${DEVICE_NAME[i]//\//\\/}
+        COMMAND=". @scriptlibdir@/lc_common; \
+                sed -i \"/^${device_name}\t/d\" \$(fcanon /etc/fstab); \
+                echo -e \"${mntent}\" >> \$(fcanon /etc/fstab)"
+        ${REMOTE} ${HOST_NAME[i]} "${COMMAND}" >&2
+        if [ ${PIPESTATUS[0]} -ne 0 ]; then
+            echo >&2 "`basename $0`: modify_fstab() error:"\
+            "Failed to modify /etc/fstab of host ${HOST_NAME[i]}"\
+            "to add Lustre target ${DEVICE_NAME[i]}!"
+            return 1
+        fi
+    done
+
+    return 0
+}
+
+# Main flow
+# Check the csv file
+if ! check_file $1; then
+    exit 1 
+fi
+
+# Get the list of nodes to be operated on
+NODES_TO_USE=$(get_nodelist)
+[ ${PIPESTATUS[0]} -ne 0 ] && echo >&2 "${NODES_TO_USE}" && exit 1
+
+# Check the node list
+check_nodelist ${NODES_TO_USE} || exit 1
+
+if ${VERIFY_CONNECT}; then
+# Check the network connectivity and hostnames
+    echo "`basename $0`: Checking the cluster network connectivity"\
+         "and hostnames..."
+    if ! ${VERIFY_CLUSTER_NET} ${NODELIST_OPT} ${VERBOSE_OPT} ${CSV_FILE}; then
+        exit 1
+    fi
+    echo "`basename $0`: Check the cluster network connectivity"\
+         "and hostnames OK!"
+    echo
+fi
+
+if ${CONFIG_MD_LVM}; then
+# Configure Linux MD/LVM devices
+    echo "`basename $0`: Configuring Linux MD/LVM devices..."
+    if ! ${SCRIPT_CONFIG_MD} ${NODELIST_OPT} ${VERBOSE_OPT} ${CSV_FILE}; then
+        exit 1
+    fi
+
+    if ! ${SCRIPT_CONFIG_LVM} ${NODELIST_OPT} ${VERBOSE_OPT} ${CSV_FILE}; then
+        exit 1
+    fi
+    echo "`basename $0`: Configure Linux MD/LVM devices OK!"
+    echo
+fi
+
+# Configure the Lustre cluster
+echo "`basename $0`: ******** Lustre cluster configuration START ********"
+if ! get_items ${CSV_FILE}; then
+    exit 1
+fi
+
+if ! check_mgs; then
+    exit 1
+fi
+
+if ! mass_config; then
+    exit 1
+fi
+
+if ! modify_fstab; then
+    exit 1
+fi
+
+# Produce HA software's configuration files
+if ! config_ha; then
+    rm -rf ${TMP_DIRS}
+    exit 1
+fi
+
+echo "`basename $0`: ******** Lustre cluster configuration END **********"
+
+exit 0