File block-dmmd of Package xen.4218

#! /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-11-18, loic.devulder@mpsa.com:
#       Execute clvmd in cluster mode to synchronize block device informations
#  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_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 reload_clvm()
{
    # If we are in cluster mode
    if ps -e | grep -q [c]lvmd 2>/dev/null; then
      # Logging message
      echo "[$(${DATE_LOG})] synchronize cLVM..." >&2

      # 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
    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

        # 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 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 || :

        # 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

        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
openSUSE Build Service is sponsored by