#!/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 < 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. -u upgrade Lustre targets from 1.4 to 1.6 -v verbose mode csv file a spreadsheet that contains configuration parameters (separated by commas) for each target in a Lustre cluster EOF } # Samples sample() { cat <&2 exit 1 fi ;; n) VERIFY_CONNECT=false ;; d) CONFIG_MD_LVM=true ;; f) REFORMAT_OPTION=$"--reformat " ;; m) MODIFY_FSTAB=false ;; u) UPGRADE_TARGET=true ;; h) usage sample ;; v) VERBOSE_OPT=$" -v" VERBOSE_OUTPUT=true ;; ?) usage 1>&2 exit 1 ;; esac done # Toss out the parameters we've already processed shift `expr $OPTIND - 1` # Here we expect the csv file if [ $# -eq 0 ]; then error_output "Missing csv file!" usage 1>&2 exit 1 fi CSV_FILE=$1 # Construct the command line of mkfs.lustre construct_mkfs_cmdline() { # Check argument if [ $# -eq 0 ]; then error_output "construct_mkfs_cmdline():"\ "Missing argument for function construct_mkfs_cmdline()!" return 1 fi declare -i i=$1 local mgsnids mgsnids_str local failnids failnids_str if $UPGRADE_TARGET; then MKFS_CMD="$TUNEFS --writeconf" else MKFS_CMD="$MKFS $REFORMAT_OPTION" fi 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 --mgs --mdt" ;; *) error_output "construct_mkfs_cmdline():"\ "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 ! $UPGRADE_TARGET && [ -n "${MKFS_OPTIONS[i]}" ]; then MKFS_CMD="$MKFS_CMD --mkfsoptions=\"${MKFS_OPTIONS[i]}\"" fi if [ -n "${MOUNT_OPTIONS[i]}" ] && ! $MODIFY_FSTAB; then MKFS_CMD="$MKFS_CMD --mountfsoptions=\"${MOUNT_OPTIONS[i]}\"" 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 error_output "get_nodenames(): 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 error_output "${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 error_output "gen_ha_config(): 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 $UPGRADE_TARGET || [ -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 } # 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 local checked_hosts="" if [ ${#HOST_NAME[@]} -eq 0 ]; then verbose_output "There are no Lustre targets specified." return 0 fi if ! $UPGRADE_TARGET; then # Start lnet network in the MGS node start_mgs_lnet || 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 error_output "mass_config():"\ "Failed to execute remote command to"\ "create the mountpoint on ${HOST_NAME[i]}!" return 1 fi if ! $UPGRADE_TARGET && ! is_mgs_node ${HOST_NAME[i]} && \ ! host_in_hostlist ${HOST_NAME[i]} $checked_hosts; then # Execute remote command to add lnet options lines to # modprobe.conf/modules.conf add_module_options $i ${HOST_NAME[i]} || return ${PIPESTATUS[0]} # Check lnet networks check_lnet $i || return ${PIPESTATUS[0]} checked_hosts="$checked_hosts,${HOST_NAME[i]}" fi # Execute remote command to format or upgrade Lustre target local OP $UPGRADE_TARGET && OP="Upgrading" || OP="Formatting" verbose_output "$OP Lustre target ${DEVICE_NAME[i]} on ${HOST_NAME[i]}..." COMMAND="export PATH=\$PATH:/sbin:/usr/sbin; $MKFS_CMD" REMOTE_CMD[$pid_num]="$REMOTE ${HOST_NAME[i]} \"$COMMAND\"" verbose_output "$OP command line is: ${REMOTE_CMD[$pid_num]}" $REMOTE ${HOST_NAME[i]} "$COMMAND" & REMOTE_PID[$pid_num]=$! let 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 error_output "mass_config(): Failed"\ "to execute \"${REMOTE_CMD[${pid_num}]}\"!" fail_exit_status=true fi done if ${fail_exit_status}; then return 1 fi verbose_output "Success on all Lustre targets!" 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 error_output "${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 error_output "modify_fstab():"\ "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 check_file $CSV_FILE || exit ${PIPESTATUS[0]} # Get the list of nodes to be operated on NODES_TO_USE=$(get_nodelist) || error_exit ${PIPESTATUS[0]} "$NODES_TO_USE" # Check the node list check_nodelist $NODES_TO_USE || exit ${PIPESTATUS[0]} 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 && ! $UPGRADE_TARGET; 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 BEGIN ********" get_lustre_items $CSV_FILE || exit ${PIPESTATUS[0]} check_mgs || exit ${PIPESTATUS[0]} # Format or upgrade Lustre server targets mass_config || exit ${PIPESTATUS[0]} # Modify /etc/fstab to add the new Lustre server targets modify_fstab || exit ${PIPESTATUS[0]} # 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