xen/block-dmmd
2016-06-21 14:56:06 +00:00

370 lines
8.7 KiB
Bash

#! /bin/bash
# Usage: block-dmmd [add args | remove args]
#
# the dmmd device syntax (in xm/xl commands/configs) is something like:
# dmmd:md;/dev/md0;md;/dev/md1;lvm;/dev/vg1/lv1
# or
# 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
#
# md devices can optionally:
# specify a config file through:
# md;/dev/md100(/var/xen/config/mdadm.conf)
# use an array name (mdadm -N option):
# dmmd:md;My-MD-name;lvm;/dev/vg1/lv1
#
# History:
# 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 DATE_LOG="date +%F_%T.%N"
typeset -rx DATE_SEC="date +%s"
# Uncomment for debugging purposes
# exec >> /tmp/block-dmmd-$(${DATE_LOG}).log 2>&1
# echo shell-flags: $-
# We check for errors ourselves
set +e
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
echo "[$(${DATE_LOG})] activate MD device ${dev}..." >&2
# 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
echo "[$(${DATE_LOG})] verifying MD device ${dev} activation..." >&2
# 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
echo "[$(${DATE_LOG})] deactivate MD device ${dev}..." >&2
# We need the device name only while deactivating
${MDADM_BIN} -S ${dev_path}/${dev##*/} > /dev/null 2>&1
return $?
}
function activate_lvm()
{
local run_timeout=90
local parsed_timeout
local end_time
# If /etc/xen/xend-config.sxp exists (e.g. SLES11), use
# device-create-timeout, instead of the default setting
if [[ -f /etc/xen/xend-config.sxp ]]; then
parsed_timeout=$(grep -v "^[ \t]*#.*" /etc/xen/xend-config.sxp \
|sed -n 's/(device-create-timeout \+\([0-9]\+\))/\1/p')
if [[ ! -z $parsed_timeout ]]; then
run_timeout=$((${parsed_timeout}*9/10))
fi
fi
# First scan for PVs and VGs
# We need this for using MD device as PV
${PVSCAN_BIN} > /dev/null 2>&1
# ${VGSCAN_BIN} --mknodes > /dev/null 2>&1
# Logging message
echo "[$(${DATE_LOG})] activate LVM device ${dev}..." >&2
# Set end_time for the loop
(( end_time = $(${DATE_SEC}) + run_timeout ))
while true; do
${LVCHANGE_BIN} -aey $1 > /dev/null 2>&1
if [ $? -eq 0 -a -e $1 ]; then
return 0
fi
sleep 0.1
# If it takes too long we need to return an error
if (( $(${DATE_SEC}) >= end_time )); then
log err "Failed to activate $1 within ${run_timeout} seconds"
return 1
fi
done
# Normally we should not get here, but if this happens
# we have to return an error
return 1
}
function deactivate_lvm()
{
# Logging message
echo "[$(${DATE_LOG})] deactivate LVM device ${dev}..." >&2
${LVCHANGE_BIN} -aen $1 > /dev/null 2>&1
if [ $? -eq 0 ]; then
# We may have to deactivate the VG now, but can ignore errors:
# ${VGCHANGE_BIN} -an ${1%/*} || :
# Maybe we need to cleanup the LVM cache:
# ${VGSCAN_BIN} --mknodes || :
return 0
fi
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")
activate_lvm $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")
deactivate_lvm $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