2 # vim:expandtab:shiftwidth=4:softtabstop=4:tabstop=4:
4 # lc_common - This file contains functions to be used by most or all
5 # Lustre cluster config scripts.
7 ################################################################################
10 REMOTE=${REMOTE:-"ssh -x -q"}
11 #REMOTE=${REMOTE:-"pdsh -S -R ssh -w"}
15 CMD_PATH=${CMD_PATH:-"/usr/sbin"}
16 MKFS=${MKFS:-"$CMD_PATH/mkfs.lustre"}
17 TUNEFS=${TUNEFS:-"$CMD_PATH/tunefs.lustre"}
18 LCTL=${LCTL:-"$CMD_PATH/lctl"}
20 EXPORT_PATH=${EXPORT_PATH:-"PATH=\$PATH:/sbin:/usr/sbin;"}
23 RAID_CMD_PATH=${RAID_CMD_PATH:-"/sbin"}
24 MDADM=${MDADM:-"$RAID_CMD_PATH/mdadm"}
26 # Some scripts to be called
27 SCRIPTS_PATH=${CLUSTER_SCRIPTS_PATH:-"$(cd `dirname $0`; echo $PWD)"}
28 MODULE_CONFIG=${SCRIPTS_PATH}/lc_modprobe
29 VERIFY_CLUSTER_NET=${SCRIPTS_PATH}/lc_net
30 GEN_HB_CONFIG=${SCRIPTS_PATH}/lc_hb
31 GEN_CLUMGR_CONFIG=${SCRIPTS_PATH}/lc_cluman
32 SCRIPT_VERIFY_SRVIP=${SCRIPTS_PATH}/lc_servip
33 SCRIPT_GEN_MONCF=${SCRIPTS_PATH}/lc_mon
34 SCRIPT_CONFIG_MD=${SCRIPTS_PATH}/lc_md
35 SCRIPT_CONFIG_LVM=${SCRIPTS_PATH}/lc_lvm
37 # Variables of HA software
38 HBVER_HBV1="hbv1" # Heartbeat version 1
39 HBVER_HBV2="hbv2" # Heartbeat version 2
40 HATYPE_CLUMGR="cluman" # Cluster Manager
42 # Configuration directories and files
43 HA_DIR=${HA_DIR:-"/etc/ha.d"} # Heartbeat configuration directory
44 MON_DIR=${MON_DIR:-"/etc/mon"} # mon configuration directory
45 CIB_DIR=${CIB_DIR:-"/var/lib/heartbeat/crm"} # cib.xml directory
47 HA_CF=${HA_DIR}/ha.cf # ha.cf file
48 HA_RES=${HA_DIR}/haresources # haresources file
49 HA_CIB=${CIB_DIR}/cib.xml
51 CLUMAN_DIR="/etc" # CluManager configuration directory
52 CLUMAN_CONFIG=${CLUMAN_DIR}/cluster.xml
54 CLUMAN_TOOLS_PATH=${CLUMAN_TOOLS_PATH:-"/usr/sbin"} # CluManager tools
55 CONFIG_CMD=${CONFIG_CMD:-"${CLUMAN_TOOLS_PATH}/redhat-config-cluster-cmd"}
57 HB_TMP_DIR="/tmp/heartbeat" # Temporary directory
58 CLUMGR_TMP_DIR="/tmp/clumanager"
59 TMP_DIRS="${HB_TMP_DIR} ${CLUMGR_TMP_DIR}"
61 FS_TYPE=${FS_TYPE:-"lustre"} # Lustre filesystem type
62 FILE_SUFFIX=${FILE_SUFFIX:-".lustre"} # Suffix of the generated config files
64 # Marker of the MD device line
65 MD_MARKER=${MD_MARKER:-"MD"}
67 # Marker of the LVM device line
68 PV_MARKER=${PV_MARKER:-"PV"}
69 VG_MARKER=${VG_MARKER:-"VG"}
70 LV_MARKER=${LV_MARKER:-"LV"}
72 declare -a CONFIG_ITEM # Items in each line of the csv file
73 declare -a NODE_NAME # Hostnames of nodes have been configured
76 USE_ALLNODES=false # default is not to operate on all the nodes
77 SPECIFIED_NODELIST="" # specified list of nodes to be operated on
78 EXCLUDED_NODELIST="" # list of nodes to be excluded
80 export PATH=$PATH:$CMD_PATH:$SCRIPTS_PATH:$CLUMAN_TOOLS_PATH:$RAID_CMD_PATH:/sbin:/usr/sbin
83 # verbose_output string
84 # Output verbose information $string
86 if ${VERBOSE_OUTPUT}; then
87 echo "`basename $0`: $*"
92 # Check whether the reomte command is pdsh
94 if [ "${REMOTE}" = "${REMOTE#*pdsh}" ]; then
101 # check_file csv_file
102 # Check the file $csv_file
105 if [ $# -eq 0 ]; then
106 echo >&2 "`basename $0`: check_file() error: Missing csv file!"
111 if [ ! -s ${CSV_FILE} ]; then
112 echo >&2 "`basename $0`: check_file() error: ${CSV_FILE}"\
113 "does not exist or is empty!"
121 # Parse a line in the csv file
124 if [ $# -eq 0 ]; then
125 echo >&2 "`basename $0`: parse_line() error: Missing argument!"
129 declare -i i=0 # Index of the CONFIG_ITEM array
132 declare -i s_quote_flag=0 # Flag of the single quote character
133 declare -i d_quote_flag=0 # Flag of the double quotes character
134 local TMP_LETTER LINE
138 # Initialize the CONFIG_ITEM array
141 # Get the length of the line
145 while [ ${idx} -lt ${length} ]; do
146 # Get a letter from the line
147 TMP_LETTER=${LINE:${idx}:1}
149 case "${TMP_LETTER}" in
151 if [ ${s_quote_flag} -eq 1 -o ${d_quote_flag} -eq 1 ]
153 CONFIG_ITEM[i]=${CONFIG_ITEM[i]}${TMP_LETTER}
161 if [ ${s_quote_flag} -eq 0 ]; then
168 if [ ${d_quote_flag} -eq 0 ]; then
181 CONFIG_ITEM[i]=${CONFIG_ITEM[i]}${TMP_LETTER}
185 # Extract the real value of each field
186 # Remove surrounded double-quotes, etc.
187 for ((idx = 0; idx <= $i; idx++)); do
188 # Strip the leading and trailing space-characters
189 CONFIG_ITEM[idx]=`expr "${CONFIG_ITEM[idx]}" : '[[:space:]]*\(.*\)[[:space:]]*$'`
191 [ -z "${CONFIG_ITEM[idx]}" ] && continue
193 # Remove the surrounded double-quotes
194 while [ -z "`echo "${CONFIG_ITEM[idx]}"|sed -e 's/^".*"$//'`" ]; do
195 CONFIG_ITEM[idx]=`echo "${CONFIG_ITEM[idx]}" | sed -e 's/^"//' -e 's/"$//'`
198 CONFIG_ITEM[idx]=`echo "${CONFIG_ITEM[idx]}" | sed -e 's/""/"/g'`
205 # If $name is a symbolic link, then display it's value
209 if [ -h "$NAME" ]; then
216 # configured_host host_name
218 # Check whether the devices in $host_name has been configured or not
223 for ((i = 0; i < ${#NODE_NAME[@]}; i++)); do
224 [ "${host_name}" = "${NODE_NAME[i]}" ] && return 0
230 # remote_error fn_name host_addr ret_str
231 # Verify the return result from remote command
233 local fn_name host_addr ret_str
241 if [ "${ret_str}" != "${ret_str#*connect:*}" ]; then
242 echo >&2 "`basename $0`: ${fn_name}() error: ${ret_str}"
246 if [ -z "${ret_str}" ]; then
247 echo >&2 "`basename $0`: ${fn_name}() error:" \
248 "No results from remote!" \
249 "Check network connectivity between the local host and ${host_addr}!"
257 # Convert $nid to hostname of the lustre cluster node
261 local addr nettype ip_addr
265 [ "${nid}" != "${nid#*@*}" ] && nettype=${nid#*@} || nettype=tcp
266 if [ -z "${addr}" ]; then
267 echo "`basename $0`: nid2hostname() error: Invalid nid - \"${nid}\"!"
272 lo*) host_name=`hostname`;;
274 # FIXME: Parse the /etc/elanhosts configuration file to
275 # convert ElanID to hostname
278 # FIXME: Use /usr/sbin/gmlndnid to find the hostname of
279 # the specified GM Global node ID
282 # FIXME: Convert portal ID to hostname
284 *) # tcp, o2ib, cib, openib, iib, vib, ra
286 # Is it IP address or hostname?
287 if [ -n "`echo ${ip_addr} | sed -e 's/\([0-9]\{1,3\}\.\)\{3,3\}[0-9]\{1,3\}//'`" ]
294 # Execute remote command to get the host name
295 ret_str=$(${REMOTE} ${ip_addr} "hostname" 2>&1 </dev/null)
296 if [ ${PIPESTATUS[0]} -ne 0 -a -n "${ret_str}" ]; then
297 echo "`basename $0`: nid2hostname() error:" \
298 "remote command to ${ip_addr} error: ${ret_str}"
301 remote_error "nid2hostname" ${ip_addr} "${ret_str}" && return 1
304 host_name=`echo ${ret_str} | awk '{print $2}'`
306 host_name=`echo ${ret_str} | awk '{print $1}'`
316 # Get the hostname of the lustre cluster node which has the nids - $nids
323 for nid in ${nids//,/ }; do
324 [ "${nid}" != "${nid#*@*}" ] && nettype=${nid#*@} || nettype=tcp
327 lo* | elan* | gm* | ptl*) ;;
328 *) # tcp, o2ib, cib, openib, iib, vib, ra
329 host_name=$(nid2hostname ${nid})
330 if [ ${PIPESTATUS[0]} -ne 0 ]; then
338 if [ -z "${host_name}" ]; then
339 echo "`basename $0`: nids2hostname() error:" \
340 "Can not get the hostname from nids - \"${nids}\"!"
348 # ip2hostname_single_node nids
349 # Convert IP addresses in $nids into hostnames
350 # NID in $nids are delimited by commas, ie all the $nids belong to one node
351 ip2hostname_single_node() {
357 for nid in ${orig_nids//,/ }; do
358 [ "${nid}" != "${nid#*@*}" ] && nettype=${nid#*@} || nettype=tcp
361 lo* | elan* | gm* | ptl*) ;;
362 *) # tcp, o2ib, cib, openib, iib, vib, ra
363 host_name=$(nid2hostname ${nid})
364 if [ ${PIPESTATUS[0]} -ne 0 ]; then
369 nid=${host_name}@${nettype}
373 [ -z "${nids}" ] && nids=${nid} || nids=${nids},${nid}
380 # ip2hostname_multi_node nids
381 # Convert IP addresses in $nids into hostnames
382 # NIDs belong to multiple nodes are delimited by colons in $nids
383 ip2hostname_multi_node() {
388 for nid in ${orig_nids//:/ }; do
389 nid=$(ip2hostname_single_node ${nid})
390 if [ ${PIPESTATUS[0]} -ne 0 ]; then
395 [ -z "${nids}" ] && nids=${nid} || nids=${nids}:${nid}
402 # comma_list space-delimited-list
403 # Convert a space-delimited list to a sorted list of unique values
404 # separated by commas.
406 # the sed converts spaces to commas, but leaves the last space
407 # alone, so the line doesn't end with a comma.
408 echo "$*" | tr -s " " "\n" | sort -b -u | tr "\n" " " | sed 's/ \([^$]\)/,\1/g'
411 # host_in_hostlist hostname hostlist
412 # Given a hostname, and a list of hostnames, return true if the hostname
413 # appears in the list of hostnames, or false otherwise.
418 [ -z "$HOST" -o -z "$HOSTLIST" ] && false && return
420 # Hostnames in the list are separated by commas.
421 [[ ,$HOSTLIST, == *,$HOST,* ]] && true && return
426 # exclude_items_from_list list_of_items list_of_items_to_exclude
427 # Given a list of items, and a second list of items to exclude from
428 # the first list, return the contents of the first list minus the contents
430 exclude_items_from_list() {
435 # Handle an empty inlist by throwing back an empty string.
436 if [ -z "$INLIST" ]; then
441 # Handle an empty excludelist by throwing back the inlist unmodified.
442 if [ -z "$EXCLUDELIST" ]; then
447 for ITEM in ${INLIST//,/ }; do
448 if ! host_in_hostlist $ITEM $EXCLUDELIST; then
449 OUTLIST="$OUTLIST,$ITEM"
453 # strip leading comma
457 # get_csv_nodelist csv_file
458 # Get the comma-separated list of all the nodes from the csv file
464 ! check_file ${csv_file} 2>&1 && return 1
466 all_nodelist=$(egrep -v "([[:space:]]|^)#" ${csv_file} | cut -d, -f 1)
467 all_nodelist=$(comma_list ${all_nodelist})
474 # Get the comma-separated list of nodes to be operated on
475 # Note: CSV_FILE, USE_ALLNODES, SPECIFIED_NODELIST and EXCLUDED_NODELIST
476 # are global variables
480 # Get the list of all the nodes in the csv file
481 ALL_NODELIST=$(get_csv_nodelist ${CSV_FILE})
482 [ ${PIPESTATUS[0]} -ne 0 ] && echo "${ALL_NODELIST}" && return 1
484 if [ -z "${ALL_NODELIST}" ]; then
485 echo "`basename $0`: get_nodelist() error:"\
486 "There are no hosts in the ${CSV_FILE} file!"
490 if ${USE_ALLNODES}; then
491 echo ${ALL_NODELIST} && return 0
494 if [ -n "${SPECIFIED_NODELIST}" ]; then
495 echo $(exclude_items_from_list ${SPECIFIED_NODELIST} ${EXCLUDED_NODELIST})
499 if [ -n "${EXCLUDED_NODELIST}" ]; then
500 echo $(exclude_items_from_list ${ALL_NODELIST} ${EXCLUDED_NODELIST})
504 # No hosts to be operated on
509 # check_nodelist nodelist
510 # Given a list of nodes to be operated on, check whether the nodelist is
511 # empty or not and output prompt message.
513 local nodes_to_use=$1
515 if [ -z "${nodes_to_use}" ]; then
516 echo "`basename $0`: There are no hosts to be operated on."\
517 "Check the node selection options (-a, -w or -x)."
520 verbose_output "Operating on the following nodes: ${nodes_to_use}"
526 # nid_in_nidlist nid nidlist
527 # Given a nid, and a list of nids in one node (delimited by comma ','),
528 # return true if the nid appears in the list of nids, or false otherwise.
534 [ -z "${nid}" -o -z "${nidlist}" ] && false && return
536 if [[ "${nid}" != *@* || "${nid#*@}" == tcp* ]]; then
537 # network type is tcp
538 for my_nid in ${nidlist//,/ }; do
539 [ "${nid%@*}" = "${my_nid%@*}" ] && true && return
542 # network type is not tcp
543 [[ ,${nidlist}, == *,${nid},* ]] && true && return
549 # get_mgs_nids mgs_hostname mgs_nids
550 # Get the corresponding NID(s) of the MGS node ${mgs_hostname} from the
551 # "mgs nids" field of one lustre target in the csv file
554 local all_mgs_nids="$2"
558 # Check whether the hostname of the mgs node is in
559 # the mgs nids string
560 for mgs_nids in ${all_mgs_nids//:/ }; do
561 if nid_in_nidlist ${mgs_node} ${mgs_nids}; then
567 # Let's use lctl to get the real nids from the mgs node
568 ret_str=$(${REMOTE} ${mgs_node} "${LCTL} list_nids" 2>&1 </dev/null)
569 if [ ${PIPESTATUS[0]} -ne 0 -a -n "${ret_str}" ]; then
570 echo "$(basename $0): get_mgs_nids() error:" \
571 "remote command to ${mgs_node} error: ${ret_str}"
574 remote_error "get_mgs_nids" ${mgs_node} "${ret_str}" && return 1
576 local real_mgs_nids=${ret_str//${mgs_node}:/}
577 for real_mgs_nid in ${real_mgs_nids}; do
578 for mgs_nids in ${all_mgs_nids//:/ }; do
579 if nid_in_nidlist ${real_mgs_nid} ${mgs_nids}; then
586 echo "$(basename $0): get_mgs_nids() error:" \
587 "Can not figure out which nids corresponding to the MGS"\
588 "node ${mgs_node} from \"${all_mgs_nids}\"!"