#! /bin/bash # Usage: block-dmmd [add args | remove args] # # the dmmd device syntax (in xl commands/configs) is something like: # script=block-dmmd,md;/dev/md0;md;/dev/md1;lvm;/dev/vg1/lv1 # or # script=block-dmmd,lvm;/dev/vg1/lv1;lvm;/dev/vg1/lv2;md;/dev/md0 # device pairs (type;dev) are processed in order, with the last device # assigned to the VM # # Note - When using the libxl stack, the "script=block-dmmd" option # is required. See man xl-disk-configuration(5) for more information. # # md devices can optionally: # specify a config file through: # md;/dev/md100(/var/xen/config/mdadm.conf) # use an array name (mdadm -N option): # md;My-MD-name;lvm;/dev/vg1/lv1 # # Completely expressive syntax should be similar to: # "format=raw, vdev=xvdb, access=rw, script=block-dmmd, \ # target=md;/dev/md0(/etc/mdadm.conf);lvm;/dev/vg1/lv1" # ## # History: # 2017-07-10, mlatimer@suse.com: # Modification to use syslog for progress messages by ldevulder@suse.com # 2017-06-12, mlatimer@suse.com: # Merge LVM improvements by loic.devulder@mpsa.com # Document libxl "script=block-dmmd" syntax in examples # Remove xm/xend references (e.g. parsed_timeout from xend-config.sxp) # 2016-05-27, mlatimer@suse.com: # Merge improvements by loic.devulder@mpsa.com. Highlights include: # - Re-write and simplification to speed up the script! # - Add some (useful) logging messages and comments # Minor tweaks and logging improvements # 2016-05-26, mlatimer@suse.com: # Verify MD activation if mdadm returns 2 # 2016-05-20, mlatimer@suse.com: # Strip leading "dmmd:" if present in xenstore params value # 2013-07-03, loic.devulder@mpsa.com: # Partial rewrite of the script for supporting MD activation by name # 2009-06-09, mh@novell.com: # Emit debugging messages into a temporary file; if no longer needed, # just comment the exec I/O redirection below # Make variables used in functions local to avoid global overridings # Use vgscan and vgchange where required # Use the C locale to avoid dealing with localized messages # Assign output from assembling an MD device to a variable to aid # debugging # We do not want to deal with localized messages # We use LC_ALL because LC_ALL superse LANG # But we also use LANG because some applications may still use LANG... export LC_ALL=C export LANG=${LC_ALL} # Loading common libraries . $(dirname $0)/block-common.sh # Constants typeset -rx MDADM_BIN=/sbin/mdadm typeset -rx LVCHANGE_BIN=/sbin/lvchange typeset -rx PVSCAN_BIN=/sbin/pvscan typeset -rx VGSCAN_BIN=/sbin/vgscan typeset -rx VGCHANGE_BIN=/sbin/vgchange typeset -rx CLVMD_BIN=/usr/sbin/clvmd typeset -rx DATE_SEC="date +%s" # We check for errors ourselves set +e function reload_clvm() { # If we are in cluster mode if ps -e | grep -q [c]lvmd 2>/dev/null; then # Logging message log info "Synchronizing cLVM..." # Synchronize cLVM ${CLVMD_BIN} -R > /dev/null 2>&1 \ || return 1 fi return 0 } function run_mdadm() { local mdadm_cmd=$1 local msg local rc msg="$(${MDADM_BIN} ${mdadm_cmd} 2>&1)" rc=$? case "${msg}" in *"has been started"* | *"already active"*) return 0 ;; *"is already in use"*) # Hmm, might be used by another device in this domU # Leave it to upper layers to detect a real error return 2 ;; *) return ${rc} ;; esac # Normally we should not get here, but if this happens # we have to return an error return 1 } function activate_md() { # Make it explicitly local local par=$1 local cfg dev dev_path rc t mdadm_opts if [[ ${par} == ${par%%(*} ]]; then # No configuration file specified dev=${par} cfg="" else dev=${par%%(*} t=${par#*(} cfg="-c ${t%%)*}" fi # Looking for device name or aliase if [[ ${dev:0:1} == / ]]; then dev_path=${dev%/*} mdadm_opts="" else dev_path=/dev/md mdadm_opts="-s -N" fi # Logging message log info "Activating MD device ${dev}..." # Is MD device already active? # We need to use full path name, aliase is not possible... if [ -e ${dev_path}/${dev##*/} ]; then ${MDADM_BIN} -Q -D ${dev_path}/${dev##*/} 2>/dev/null \ | grep -iq state.*\:.*inactive || return 0 fi # Activate MD device run_mdadm "-A ${mdadm_opts} ${dev} ${cfg}" rc=$? # A return code of 2 can indicate the array configuration was incorrect if [[ ${rc} == 2 ]]; then # Logging message log info "Verifying MD device ${dev} activation..." # If the array is active, return 0, otherwise return an error ${MDADM_BIN} -Q -D ${dev_path}/${dev##*/} &>/dev/null && return 0 \ || return 1 fi return ${rc} } function deactivate_md() { local par=$1 local dev if [[ ${par} == ${par%%(*} ]]; then # No configuration file specified dev=${par} else dev=${par%%(*} fi # Looking for device name or aliase if [[ ${dev:0:1} == / ]]; then dev_path=${dev%/*} else dev_path=/dev/md fi # Logging message log info "Deactivating MD device ${dev}..." # We need the device name only while deactivating ${MDADM_BIN} -S ${dev_path}/${dev##*/} > /dev/null 2>&1 return $? } function lvm_action() { local action=$1 local dev=$2 local run_timeout=90 local end_time # Logging message log info "${action} LVM device ${dev}..." # Set end_time for the loop (( end_time = $(${DATE_SEC}) + run_timeout )) while true; do # Action depends of what the user asks if [[ ${action} == activate ]]; then # First scan for PVs and VGs # We need this for using MD device as PV ${PVSCAN_BIN} > /dev/null 2>&1 ${LVCHANGE_BIN} -aey ${dev} > /dev/null 2>&1 \ && [[ -e ${dev} ]] \ && return 0 elif [[ ${action} == deactivate ]]; then ${LVCHANGE_BIN} -aen ${dev} > /dev/null 2>&1 \ && return 0 # If the LV is already deactivated we may be in an infinite loop # So we need to test if the LV is still present [[ -e ${dev} ]] || return 0 fi # It seems that we had a problem during lvchange # If we are in a cluster the problem may be due to a cLVM locking bug, # so try to reload it reload_clvm # If it takes too long we need to return an error if (( $(${DATE_SEC}) >= end_time )); then log err "Failed to ${action} $1 within ${run_timeout} seconds" return 1 fi # Briefly sleep before restarting the loop sleep 0.1 done # Normally we should not get here, but if this happens # we have to return an error return 1 } # Variables typeset command=$1 typeset BP=100 typeset SP=${BP} typeset VBD typeset -a stack function push() { local value="$1" [[ -n "${value}" ]] \ && stack[$((--SP))]="${value}" return 0 } function pop() { [[ "${SP}" != "${BP}" ]] \ && VBD=${stack[$((SP++))]} \ || VBD="" return 0 } function activate_dmmd() { case "$1" in "md") activate_md $2 return $? ;; "lvm") lvm_action activate $2 return $? ;; esac # Normally we should not get here, but if this happens # we have to return an error return 1 } function deactivate_dmmd() { case "$1" in "md") deactivate_md $2 return $? ;; "lvm") lvm_action deactivate $2 return $? ;; esac # Normally we should not get here, but if this happens # we have to return an error return 1 } function cleanup_stack() { while true; do pop [[ -z "${VBD}" ]] && break deactivate_dmmd ${VBD} done } function parse_par() { # Make these vars explicitly local local ac par rc s t ac=$1 par="$2" par="${par};" while true; do t=${par%%;*} [[ -z "${t}" ]] && return 0 par=${par#*;} s=${par%%;*} [[ -z "${s}" ]] && return 1 par=${par#*;} if [[ "${ac}" == "activate" ]]; then activate_dmmd ${t} ${s} \ || return 1 fi push "${t} ${s}" done } case "${command}" in "add") p=$(xenstore-read ${XENBUS_PATH}/params) || true claim_lock "dmmd" dmmd=${p#dmmd:} if ! parse_par activate "${dmmd}"; then cleanup_stack release_lock "dmmd" exit 1 fi lastparam=${dmmd##*;} usedevice=${lastparam%(*} xenstore-write ${XENBUS_PATH}/node "${usedevice}" write_dev "${usedevice}" release_lock "dmmd" exit 0 ;; "remove") p=$(xenstore-read ${XENBUS_PATH}/params) || true claim_lock "dmmd" dmmd=${p#dmmd:} parse_par noactivate "${dmmd}" cleanup_stack release_lock "dmmd" exit 0 ;; esac # Normally we should not get here, but if this happens # we have to return an error return 1