Whamcloud - gitweb
Branch HEAD
[fs/lustre-release.git] / lustre / scripts / lc_common
1 #
2 # vim:expandtab:shiftwidth=4:softtabstop=4:tabstop=4:
3 #
4 # lc_common - This file contains functions to be used by most or all
5 #             Lustre cluster config scripts.
6 #
7 ################################################################################
8
9 # Remote command 
10 REMOTE=${REMOTE:-"ssh -x -q"}
11 #REMOTE=${REMOTE:-"pdsh -S -R ssh -w"}
12 export REMOTE
13
14 # Lustre utilities
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"}
19
20 EXPORT_PATH=${EXPORT_PATH:-"PATH=\$PATH:/sbin:/usr/sbin;"}
21
22 # Raid command path
23 RAID_CMD_PATH=${RAID_CMD_PATH:-"/sbin"}
24 MDADM=${MDADM:-"$RAID_CMD_PATH/mdadm"}
25
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
36
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
41
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
46
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
50
51 CLUMAN_DIR="/etc"                               # CluManager configuration directory
52 CLUMAN_CONFIG=${CLUMAN_DIR}/cluster.xml
53
54 CLUMAN_TOOLS_PATH=${CLUMAN_TOOLS_PATH:-"/usr/sbin"}     # CluManager tools
55 CONFIG_CMD=${CONFIG_CMD:-"${CLUMAN_TOOLS_PATH}/redhat-config-cluster-cmd"}
56
57 HB_TMP_DIR="/tmp/heartbeat"         # Temporary directory
58 CLUMGR_TMP_DIR="/tmp/clumanager"
59 TMP_DIRS="${HB_TMP_DIR} ${CLUMGR_TMP_DIR}"
60
61 FS_TYPE=${FS_TYPE:-"lustre"}        # Lustre filesystem type
62 FILE_SUFFIX=${FILE_SUFFIX:-".lustre"}   # Suffix of the generated config files
63
64 # Marker of the MD device line
65 MD_MARKER=${MD_MARKER:-"MD"}
66
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"}
71
72 declare -a CONFIG_ITEM              # Items in each line of the csv file
73 declare -a NODE_NAME                # Hostnames of nodes have been configured
74
75 # Nodelist variables
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
79
80 export PATH=$PATH:$CMD_PATH:$SCRIPTS_PATH:$CLUMAN_TOOLS_PATH:$RAID_CMD_PATH:/sbin:/usr/sbin
81
82
83 # verbose_output string
84 # Output verbose information $string
85 verbose_output() {
86     if ${VERBOSE_OUTPUT}; then
87         echo "`basename $0`: $*"
88     fi
89     return 0
90 }
91
92 # Check whether the reomte command is pdsh
93 is_pdsh() {
94     if [ "${REMOTE}" = "${REMOTE#*pdsh}" ]; then
95         return 1
96     fi
97
98     return 0
99 }
100
101 # check_file csv_file
102 # Check the file $csv_file
103 check_file() {
104     # Check argument
105     if [ $# -eq 0 ]; then
106         echo >&2 "`basename $0`: check_file() error: Missing csv file!"
107         return 1
108     fi
109
110     CSV_FILE=$1
111     if [ ! -s ${CSV_FILE} ]; then
112         echo >&2 "`basename $0`: check_file() error: ${CSV_FILE}"\
113                  "does not exist or is empty!"
114         return 1
115     fi
116
117     return 0
118 }
119
120 # parse_line line
121 # Parse a line in the csv file
122 parse_line() {
123     # Check argument
124     if [ $# -eq 0 ]; then
125         echo >&2 "`basename $0`: parse_line() error: Missing argument!"
126         return 1
127     fi
128
129     declare -i i=0              # Index of the CONFIG_ITEM array
130     declare -i length=0 
131     declare -i idx=0
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
135  
136     LINE="$*"
137
138     # Initialize the CONFIG_ITEM array
139     unset CONFIG_ITEM
140
141     # Get the length of the line
142     length=${#LINE}
143
144     i=0
145     while [ ${idx} -lt ${length} ]; do
146         # Get a letter from the line
147         TMP_LETTER=${LINE:${idx}:1}
148
149         case "${TMP_LETTER}" in
150         ",")
151             if [ ${s_quote_flag} -eq 1 -o ${d_quote_flag} -eq 1 ]
152             then
153                 CONFIG_ITEM[i]=${CONFIG_ITEM[i]}${TMP_LETTER}
154             else
155                 i=$i+1
156             fi
157             idx=${idx}+1
158             continue
159             ;;
160         "'")
161             if [ ${s_quote_flag} -eq 0 ]; then
162                 s_quote_flag=1
163             else
164                 s_quote_flag=0
165             fi
166             ;;
167         "\"")
168             if [ ${d_quote_flag} -eq 0 ]; then
169                 d_quote_flag=1
170             else
171                 d_quote_flag=0
172             fi
173             ;;
174         "\r")
175             idx=${idx}+1
176             continue
177             ;;
178         *)
179             ;;
180         esac
181         CONFIG_ITEM[i]=${CONFIG_ITEM[i]}${TMP_LETTER}
182         idx=${idx}+1
183     done
184
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:]]*$'`
190
191         [ -z "${CONFIG_ITEM[idx]}" ] && continue
192
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/"$//'`
196         done
197
198         CONFIG_ITEM[idx]=`echo "${CONFIG_ITEM[idx]}" | sed -e 's/""/"/g'`
199     done
200
201     return 0
202 }
203
204 # fcanon name
205 # If $name is a symbolic link, then display it's value
206 fcanon() {
207     local NAME=$1
208
209     if [ -h "$NAME" ]; then
210         readlink -f "$NAME"
211     else
212         echo "$NAME"
213     fi
214 }
215
216 # configured_host host_name
217 #
218 # Check whether the devices in $host_name has been configured or not
219 configured_host() {
220     local host_name=$1
221     declare -i i
222
223     for ((i = 0; i < ${#NODE_NAME[@]}; i++)); do
224         [ "${host_name}" = "${NODE_NAME[i]}" ] && return 0
225     done
226
227     return 1
228 }
229
230 # remote_error fn_name host_addr ret_str
231 # Verify the return result from remote command
232 remote_error() {
233     local fn_name host_addr ret_str
234
235     fn_name=$1
236     shift
237     host_addr=$1
238     shift
239     ret_str=$*
240
241     if [ "${ret_str}" != "${ret_str#*connect:*}" ]; then
242         echo >&2 "`basename $0`: ${fn_name}() error: ${ret_str}"
243         return 0
244     fi
245
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}!"
250         return 0
251     fi
252
253     return 1
254 }
255
256 # nid2hostname nid
257 # Convert $nid to hostname of the lustre cluster node
258 nid2hostname() {
259     local nid=$1
260     local host_name=
261     local addr nettype ip_addr
262     local ret_str
263
264     addr=${nid%@*}
265     [ "${nid}" != "${nid#*@*}" ] && nettype=${nid#*@} || nettype=tcp
266     if [ -z "${addr}" ]; then
267         echo "`basename $0`: nid2hostname() error: Invalid nid - \"${nid}\"!"
268         return 1
269     fi
270                 
271     case "${nettype}" in
272     lo*)    host_name=`hostname`;;
273     elan*)  # QsNet
274             # FIXME: Parse the /etc/elanhosts configuration file to
275             # convert ElanID to hostname
276             ;;
277     gm*)    # Myrinet
278             # FIXME: Use /usr/sbin/gmlndnid to find the hostname of
279             # the specified GM Global node ID 
280             ;;
281     ptl*)   # Portals
282             # FIXME: Convert portal ID to hostname
283             ;;
284     *)  # tcp, o2ib, cib, openib, iib, vib, ra
285         ip_addr=${addr}
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\}//'`" ]
288         then
289             host_name=${ip_addr}
290             echo ${host_name}
291             return 0
292         fi
293
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}"
299             return 1
300         fi
301         remote_error "nid2hostname" ${ip_addr} "${ret_str}" && return 1
302
303         if is_pdsh; then
304             host_name=`echo ${ret_str} | awk '{print $2}'`
305         else
306             host_name=`echo ${ret_str} | awk '{print $1}'`
307         fi
308         ;;
309     esac
310
311     echo ${host_name}
312     return 0
313 }
314
315 # nids2hostname nids
316 # Get the hostname of the lustre cluster node which has the nids - $nids
317 nids2hostname() {
318     local nids=$1
319     local host_name=
320     local nid
321     local nettype
322
323     for nid in ${nids//,/ }; do
324         [ "${nid}" != "${nid#*@*}" ] && nettype=${nid#*@} || nettype=tcp
325
326         case "${nettype}" in
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
331                 echo "${host_name}"
332                 return 1
333             fi
334             ;;
335         esac
336     done
337
338     if [ -z "${host_name}" ]; then
339         echo "`basename $0`: nids2hostname() error:" \
340         "Can not get the hostname from nids - \"${nids}\"!"
341         return 1
342     fi
343
344     echo ${host_name}
345     return 0
346 }
347
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() {
352     local orig_nids=$1
353     local nids=
354     local nid host_name
355     local nettype
356
357     for nid in ${orig_nids//,/ }; do
358         [ "${nid}" != "${nid#*@*}" ] && nettype=${nid#*@} || nettype=tcp
359
360         case "${nettype}" in
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
365                 echo "${host_name}"
366                 return 1
367             fi
368                         
369             nid=${host_name}@${nettype}
370             ;;
371         esac
372
373         [ -z "${nids}" ] && nids=${nid} || nids=${nids},${nid}
374     done
375
376     echo ${nids}
377     return 0
378 }
379
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() {
384     local orig_nids=$1
385     local nids=
386     local nid
387
388     for nid in ${orig_nids//:/ }; do
389         nid=$(ip2hostname_single_node ${nid})
390         if [ ${PIPESTATUS[0]} -ne 0 ]; then
391             echo "${nid}"
392             return 1
393         fi
394
395         [ -z "${nids}" ] && nids=${nid} || nids=${nids}:${nid}
396     done
397
398     echo ${nids}
399     return 0
400 }
401
402 # comma_list space-delimited-list
403 # Convert a space-delimited list to a sorted list of unique values
404 # separated by commas.
405 comma_list() {
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'
409 }
410
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.
414 host_in_hostlist() {
415     local HOST=$1
416     local HOSTLIST=$2
417
418     [ -z "$HOST" -o -z "$HOSTLIST" ] && false && return
419
420     # Hostnames in the list are separated by commas.
421     [[ ,$HOSTLIST, == *,$HOST,* ]] && true && return
422
423     false && return
424 }
425
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
429 # of the second.
430 exclude_items_from_list() {
431     local INLIST=$1
432     local EXCLUDELIST=$2
433     local ITEM OUTLIST
434
435     # Handle an empty inlist by throwing back an empty string.
436     if [ -z "$INLIST" ]; then
437         echo ""
438         return 0
439     fi
440
441     # Handle an empty excludelist by throwing back the inlist unmodified.
442     if [ -z "$EXCLUDELIST" ]; then
443         echo $INLIST
444         return 0
445     fi
446
447     for ITEM in ${INLIST//,/ }; do
448         if ! host_in_hostlist $ITEM $EXCLUDELIST; then
449            OUTLIST="$OUTLIST,$ITEM"
450         fi
451     done
452                                 
453     # strip leading comma
454     echo ${OUTLIST#,}
455 }
456
457 # get_csv_nodelist csv_file
458 # Get the comma-separated list of all the nodes from the csv file
459 get_csv_nodelist() {
460     local csv_file=$1
461     local all_nodelist
462
463     # Check the csv file
464     ! check_file ${csv_file} 2>&1 && return 1
465
466     all_nodelist=$(egrep -v "([[:space:]]|^)#" ${csv_file} | cut -d, -f 1)
467     all_nodelist=$(comma_list ${all_nodelist})
468
469     echo ${all_nodelist}
470     return 0
471 }
472
473 # get_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
477 get_nodelist() {
478     local ALL_NODELIST
479
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
483
484     if [ -z "${ALL_NODELIST}" ]; then
485         echo "`basename $0`: get_nodelist() error:"\
486              "There are no hosts in the ${CSV_FILE} file!"
487         return 1
488     fi
489
490     if ${USE_ALLNODES}; then
491         echo ${ALL_NODELIST} && return 0
492     fi
493
494     if [ -n "${SPECIFIED_NODELIST}" ]; then
495         echo $(exclude_items_from_list ${SPECIFIED_NODELIST} ${EXCLUDED_NODELIST})
496         return 0
497     fi
498
499     if [ -n "${EXCLUDED_NODELIST}" ]; then
500         echo $(exclude_items_from_list ${ALL_NODELIST} ${EXCLUDED_NODELIST})
501         return 0
502     fi
503
504     # No hosts to be operated on
505     echo ""
506     return 0
507 }
508
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.
512 check_nodelist() {
513     local nodes_to_use=$1
514
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)."
518         usage
519     else
520         verbose_output "Operating on the following nodes: ${nodes_to_use}"
521     fi
522
523     return 0
524 }
525
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.
529 nid_in_nidlist() {
530     local nid="$1"
531     local nidlist="$2"
532     local my_nid
533
534     [ -z "${nid}" -o -z "${nidlist}" ] && false && return
535
536     if [[ "${nid}" != *@* || "${nid#*@}" == tcp* ]]; then
537         # network type is tcp
538         for my_nid in ${nidlist//,/ }; do
539             [ "${nid%@*}" = "${my_nid%@*}" ] && true && return
540         done
541     else
542         # network type is not tcp
543         [[ ,${nidlist}, == *,${nid},* ]] && true && return
544     fi
545
546     false && return
547 }
548
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
552 get_mgs_nids() {
553     local mgs_node="$1"
554     local all_mgs_nids="$2"
555     local mgs_nids
556     local ret_str
557
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
562             echo ${mgs_nids}
563             return 0
564         fi
565     done
566
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}"
572         return 1
573     fi
574     remote_error "get_mgs_nids" ${mgs_node} "${ret_str}" && return 1
575
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
580                 echo ${mgs_nids}
581                 return 0
582             fi
583         done
584     done
585
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}\"!"
589
590     return 1
591 }