#!/bin/sh
#
# Support:      linux-ha@lists.linux-ha.org
# License:      GNU General Public License (GPL)
#
#   Resource Agent for domains managed by the libvirt API.
#   Requires a running libvirt daemon (libvirtd).
#
#   (c) 2008-2010 Florian Haas, Dejan Muhamedagic,
#                 and Linux-HA contributors
#
#	usage: $0 {start|stop|status|monitor|migrate_to|migrate_from|meta-data|validate-all}
#
#######################################################################
# Initialization:
: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs

# Defaults
OCF_RESKEY_force_stop_default=0
OCF_RESKEY_hypervisor_default="$(virsh --quiet uri)"

: ${OCF_RESKEY_force_stop=${OCF_RESKEY_force_stop_default}}
: ${OCF_RESKEY_hypervisor=${OCF_RESKEY_hypervisor_default}}
#######################################################################

## I'd very much suggest to make this RA use bash,
## and then use magic $SECONDS.
## But for now:
NOW=$(date +%s)

usage() {
  echo "usage: $0 {start|stop|status|monitor|migrate_to|migrate_from|meta-data|validate-all}"
}

meta_data() {
	cat <<EOF
<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="VirtualDomain">
<version>1.1</version>

<longdesc lang="en">
Resource agent for a virtual domain (a.k.a. domU, virtual machine,
virtual environment etc., depending on context) managed by libvirtd.
</longdesc>
<shortdesc lang="en">Manages virtual domains through the libvirt virtualization framework</shortdesc>

<parameters>

<parameter name="config" unique="1" required="1">
<longdesc lang="en">
Absolute path to the libvirt configuration file,
for this virtual domain.
</longdesc>
<shortdesc lang="en">Virtual domain configuration file</shortdesc>
<content type="string" default="" />
</parameter>

<parameter name="hypervisor" unique="0" required="0">
<longdesc lang="en">
Hypervisor URI to connect to. See the libvirt documentation for
details on supported URI formats. The default is system dependent.
</longdesc>
<shortdesc lang="en">Hypervisor URI</shortdesc>
<content type="string" default="${OCF_RESKEY_hypervisor_default}"/>
</parameter>

<parameter name="force_stop" unique="0" required="0">
<longdesc lang="en">
Always forcefully shut down ("destroy") the domain on stop. The default
behavior is to resort to a forceful shutdown only after a graceful
shutdown attempt has failed. You should only set this to true if
your virtual domain (or your virtualization backend) does not support
graceful shutdown.
</longdesc>
<shortdesc lang="en">Always force shutdown on stop</shortdesc>
<content type="boolean" default="${OCF_RESKEY_force_stop_default}" />
</parameter>

<parameter name="migration_transport" unique="0" required="0">
<longdesc lang="en">
Transport used to connect to the remote hypervisor while
migrating. Please refer to the libvirt documentation for details on
transports available. If this parameter is omitted, the resource will
use libvirt's default transport to connect to the remote hypervisor.
</longdesc>
<shortdesc lang="en">Remote hypervisor transport</shortdesc>
<content type="string" default="" />
</parameter>

<parameter name="migration_network_suffix" unique="0" required="0">
<longdesc lang="en">
Use a dedicated migration network. The migration URI is composed by
adding this parameters value to the end of the node name. If the node
name happens to be an FQDN (as opposed to an unqualified host name),
insert the suffix immediately prior to the first period (.) in the FQDN.
At the moment Qemu/KVM and Xen migration via a dedicated network is supported.

Note: Be sure this composed host name is locally resolveable and the
associated IP is reachable through the favored network.
</longdesc>
<shortdesc lang="en">Migration network host name suffix</shortdesc>
<content type="string" default="" />
</parameter>

<parameter name="monitor_scripts" unique="0" required="0">
<longdesc lang="en">
To additionally monitor services within the virtual domain, add this
parameter with a list of scripts to monitor.

Note: when monitor scripts are used, the start and migrate_from operations
will complete only when all monitor scripts have completed successfully.
Be sure to set the timeout of these operations to accommodate this delay.
</longdesc>
<shortdesc lang="en">space-separated list of monitor scripts</shortdesc>
<content type="string" default="" />
</parameter>

</parameters>

<actions>
<action name="start" timeout="90" />
<action name="stop" timeout="90" />
<action name="status" depth="0" timeout="30" interval="10" />
<action name="monitor" depth="0" timeout="30" interval="10" />
<action name="migrate_from" timeout="60" />
<action name="migrate_to" timeout="120" />
<action name="meta-data" timeout="5" />
<action name="validate-all" timeout="5" />
</actions>
</resource-agent>
EOF
}

# Set options to be passed to virsh:
VIRSH_OPTIONS="--connect=${OCF_RESKEY_hypervisor} --quiet"

# A state file where we record the domain name:
STATEFILE="${HA_RSCTMP}/VirtualDomain-${OCF_RESOURCE_INSTANCE}.state"

VirtualDomain_Define() {
    local virsh_output
    local domain_name
    # Note: passing in the domain name from outside the script is
    # intended for testing and debugging purposes only. Don't do this
    # in production, instead let the script figure out the domain name
    # from the config file. You have been warned.
    if [ -z "$DOMAIN_NAME" ]; then
	# Spin until we have a domain name
	while true; do
	    virsh_output=`virsh ${VIRSH_OPTIONS} define ${OCF_RESKEY_config}`
	    domain_name=`echo "$virsh_output" | sed -e 's/Domain \(.*\) defined from .*$/\1/'`
            if [ -n "$domain_name" ]; then
		break;
            fi
	    ocf_log debug "Domain not defined yet, probably unable to connect to hypervisor. Retrying."
            sleep 1
	done
	echo "$domain_name" > $STATEFILE
	ocf_log info "Domain name \"$domain_name\" saved to $STATEFILE."
    else
	ocf_log warn "Domain name ${DOMAIN_NAME} already defined, overriding configuration file ${OCF_RESKEY_config}. You should do this for testing only."
    fi
}

VirtualDomain_Cleanup_Statefile() {
    rm -f $STATEFILE || ocf_log warn "Failed to remove $STATEFILE during $__OCF_ACTION."
}

VirtualDomain_Status() {
    local try=0
    rc=$OCF_ERR_GENERIC
    status="no state"
    while [ "$status" = "no state" ]; do
	try=$(($try + 1 ))
        status="`virsh $VIRSH_OPTIONS domstate $DOMAIN_NAME`"
        case "$status" in
	    "shut off")
	        # shut off: domain is defined, but not started
		ocf_log debug "Virtual domain $DOMAIN_NAME is currently $status."
	        rc=$OCF_NOT_RUNNING
	        ;;
	    running|paused|idle|blocked)
		# running: domain is currently actively consuming cycles
		# paused: domain is paused (suspended)
		# idle: domain is running but idle
		# blocked: synonym for idle used by legacy Xen versions
		ocf_log debug "Virtual domain $DOMAIN_NAME is currently $status."
	        rc=$OCF_SUCCESS
	        ;;
            ""|"no state")
		# Empty string may be returned when virsh does not
		# receive a reply from libvirtd.
		# "no state" may occur when the domain is currently
		# being migrated (on the migration target only), or
		# whenever virsh can't reliably obtain the domain
		# state.
		status="no state"
		if [ "$__OCF_ACTION" = "stop" ] && [ $try -ge 3 ]; then
		    # During the stop operation, we want to bail out
		    # quickly, so as to be able to force-stop (destroy)
		    # the domain if necessary.
		    ocf_log error "Virtual domain $DOMAIN_NAME has no state during stop operation, bailing out."
		    return $OCF_ERR_GENERIC;
		else
		    # During all other actions, we just wait and try
		    # again, relying on the CRM/LRM to time us out if
		    # this takes too long.
		    ocf_log info "Virtual domain $DOMAIN_NAME currently has no state, retrying."
		    sleep 1
		fi
		;;
            *)
		# any other output is unexpected.
                ocf_log error "Virtual domain $DOMAIN_NAME has unknown status \"$status\"!"
                ;;
        esac
    done
    return $rc
}

VirtualDomain_Start() {
    if VirtualDomain_Status; then
	ocf_log info "Virtual domain $DOMAIN_NAME already running."
	return $OCF_SUCCESS
    fi

    virsh $VIRSH_OPTIONS start ${DOMAIN_NAME}
    rc=$?
    if [ $rc -ne 0 ]; then
	ocf_log error "Failed to start virtual domain ${DOMAIN_NAME}."
	return $OCF_ERR_GENERIC
    fi

    while ! VirtualDomain_Monitor; do
	sleep 1
    done
    return $OCF_SUCCESS
}

VirtualDomain_Stop() {
    local i
    local status
    local shutdown_timeout
    local out ex

    VirtualDomain_Status
    status=$?

    case $status in
	$OCF_SUCCESS)
	    if ! ocf_is_true $OCF_RESKEY_force_stop; then
		# Issue a graceful shutdown request
		ocf_log info "Issuing graceful shutdown request for domain ${DOMAIN_NAME}."
		virsh $VIRSH_OPTIONS shutdown ${DOMAIN_NAME}
	        # The "shutdown_timeout" we use here is the operation
		# timeout specified in the CIB, minus 5 seconds
		shutdown_timeout=$(( $NOW + ($OCF_RESKEY_CRM_meta_timeout/1000) -5 ))
		# Loop on status until we reach $shutdown_timeout
		while [ $NOW -lt $shutdown_timeout ]; do
		    VirtualDomain_Status
		    status=$?
		    case $status in
			$OCF_NOT_RUNNING)
			    # This was a graceful shutdown. Clean
			    # up and return.
			    VirtualDomain_Cleanup_Statefile
			    return $OCF_SUCCESS
			    ;;
			$OCF_SUCCESS)
			    # Domain is still running, keep
			    # waiting (until shutdown_timeout
			    # expires)
			    sleep 1
			    ;;
			*)
			    # Something went wrong. Bail out and
			    # resort to forced stop (destroy).
			    break;
		    esac
		    NOW=$(date +%s)
		done
	    fi
	    ;;
	$OCF_NOT_RUNNING)
	    ocf_log info "Domain $DOMAIN_NAME already stopped."
	    return $OCF_SUCCESS
    esac
    # OK. Now if the above graceful shutdown hasn't worked, kill
    # off the domain with destroy. If that too does not work,
    # have the LRM time us out.
    ocf_log info "Issuing forced shutdown (destroy) request for domain ${DOMAIN_NAME}."
    out=$(virsh $VIRSH_OPTIONS destroy ${DOMAIN_NAME} 2>&1)
    ex=$?
    echo >&2 "$out"
    # unconditionally clean up.
    VirtualDomain_Cleanup_Statefile
    case $ex$out in
	*"error:"*"domain is not running"*)
	    : ;; # unexpected path to the intended outcome, all is well
	[!0]*)
	    return $OCF_ERR_GENERIC ;;
	0*)
	    while [ $status != $OCF_NOT_RUNNING ]; do
		VirtualDomain_Status
		status=$?
	    done ;;
    esac
    return $OCF_SUCCESS
}

VirtualDomain_Migrate_To() {
    local target_node
    local remoteuri
    local transport_suffix
    local migrateuri
    local migrateport
    local migrate_target
    local hypervisor

    target_node="$OCF_RESKEY_CRM_meta_migrate_target"

    if VirtualDomain_Status; then
        # Find out the remote hypervisor to connect to. That is, turn
        # something like "qemu://foo:9999/system" into
        # "qemu+tcp://bar:9999/system"
	if [ -n "${OCF_RESKEY_migration_transport}" ]; then
	    transport_suffix="+${OCF_RESKEY_migration_transport}"
	fi
	# A typical migration URI via a special  migration network looks
	# like "tcp://bar-mig:49152". The port would be randomly chosen
	# by libvirt from the range 49152-49215 if omitted, at least since
	# version 0.7.4 ...
	if [ -n "${OCF_RESKEY_migration_network_suffix}" ]; then
	    hypervisor="${OCF_RESKEY_hypervisor%%[+:]*}"
	    # Hostname might be a FQDN
	    migrate_target=$(echo ${target_node} | sed -e "s,^\([^.]\+\),\1${OCF_RESKEY_migration_network_suffix},")
	    case $hypervisor in
		qemu)
		    # For quiet ancient libvirt versions a migration port is needed
		    # and the URI must not contain the "//". Newer versions can handle
		    # the "bad" URI.
		    migrateport=$(( 49152 + $(ocf_maybe_random) % 64 ))
		    migrateuri="tcp:${migrate_target}:${migrateport}"
		    ;;
		xen)
		    migrateuri="xenmigr://${migrate_target}"
		    ;;
		*)
		    ocf_log warn "$DOMAIN_NAME: Migration via dedicated network currently not supported for ${hypervisor}."
		    ;;
	    esac
	fi
        # Scared of that sed expression? So am I. :-)
	remoteuri=$(echo ${OCF_RESKEY_hypervisor} | sed -e "s,\(.*\)://[^/:]*\(:\?[0-9]*\)/\(.*\),\1${transport_suffix}://${target_node}\2/\3,")

	# OK, we know where to connect to. Now do the actual migration.
	ocf_log info "$DOMAIN_NAME: Starting live migration to ${target_node} (using remote hypervisor URI F_SU elif expr match "$icp_out" \
                '.*Drive is not reserved by any host.*' >/dev/null 2>&1 ; then
                ocf_log "err" "Volume $1 not reserved by any host."
                return $OCF_NOT_RUNNING
        else
                ocf_log "err" "Unknown output from icpclucon. Assuming we do not have a reservation:"
		ocf_log "err" "$icp_out"
                return $OCF_NOT_RUNNING
        fi
}

ICP_report_status() {
  if ICP_status $1 ; then
	echo "$1: running"
	return $OCF_SUCCESS
  else
	echo "$1: not running"
	return $OCF_NOT_RUNNING
  fi
}


#
#	Monitor the host drive - does it really seem to be working?
#
#
ICP_monitor() {

  if
    ICP_status $1
  then
    return $?
  else
    ocf_log "err" "ICP host drive $1 is offline"
    return $OCF_NOT_RUNNING
  fi

}

Clear_bufs() {
    $BLOCKDEV --flushbufs $1
}

#
#	Enable ICP host drive
#
ICP_start() {

  ocf_log "info" "Activating host drive $1"
  ocf_run $ICPCLUCON -v -reserve $1
  if [ $? -ne 0 ]; then
  	ocf_log "info" "Forcing reservation of $1"
	ocf_run $ICPCLUCON -v -force $1 || return $OCF_ERR_GENERIC
  fi

  if
    ICP_status $1
  then
    : OK
    # A reservation isn't as prompt as it should be
    sleep 3
    return $OCF_SUCCESS
  else
    ocf_log "err" "ICP: $1 was not reserved correctly"
    return $OCF_ERR_GENERIC
  fi
}

#
#	Release the ICP host drive
#
ICP_stop() {

  ocf_log "info" "Releasing ICP host drive $1"
  ocf_run $ICPCLUCON -v -release $1 || return $OCF_ERR_GENERIC

  ocf_log "info" "Verifying reservation"
  if ICP_status $1 ; then
    ocf_log "err" "ICP: $1 was not released correctly"
    return $OCF_ERR_GENERIC
  fi
  return $OCF_SUCCESS
}

ICP_validate_all() {
  check_binary $BLOCKDEV
  check_binary $ICPCLUCON
  $ICPCLUCON -v -status $driveid >/dev/null 2>&1
  if [ $? -ne 0 ]; then
	ocf_log err "Invalid driveid $driveid"
	exit $OCF_ERR_ARGS
  fi

  if [ ! -b $device ]; then
	ocf_log err "Device $device is not a block device"
	exit $OCF_ERR_ARGS
  fi

# Do not know how to check the association of $device with $driveid.

  return $OCF_SUCCESS
}

#
#	'main' starts here...
#

if
  ( [ $# -ne 1 ] )
then
  usage
  exit $OCF_ERR_ARGS
fi

# These operations do not require OCF instance parameters to be set
case "$1" in

  meta-data)	meta_data
		exit $OCF_SUCCESS;;

  methods)	ICP_methods
		exit $OCF_SUCCESS;;
  
  usage)	usage
		exit $OCF_SUCCESS;;

  *)		;;
esac

if 
  [ -z "$OCF_RESKEY_driveid" ] 
then
  ocf_log err "Please specify OCF_RESKEY_driveid"
  exit $OCF_ERR_ARGS
fi

if [ -z "$OCF_RESKEY_device" ]; then
  ocf_log err "Please specify OCF_RESKEY_device"
  exit $OCF_ERR_ARGS
fi

driveid=$OCF_RESKEY_driveid
device=$OCF_RESKEY_device

# What kind of method was invoked?
case "$1" in

  start)	ICP_validate_all
		ICP_start $driveid
		Clear_bufs $device
		exit $?;;

  stop)		ICP_stop $driveid
		Clear_bufs $device
		exit $?;;

  status)	ICP_report_status $driveid
		exit $?;;

  monitor)	ICP_monitor $driveid
		exit $?;;

  validate-all)	ICP_validate_all
		exit $?;;

  *)		usage
		exit $OCF_ERR_UNIMPLEMENTED;;
esac
                                                                                                                                                                                                                                                                                                                                                                                                                                 ./usr/lib/ocf/resource.d/heartbeat/VirtualDomain                                                    0000755 0000000 0000000 00000040370 12307430051 020552  0                                                                                                    ustar   root                            root                                                                                                                                                                                                                   #!/bin/sh
#
# Support:      linux-ha@lists.linux-ha.org
# License:      GNU General Public License (GPL)
#
#   Resource Agent for domains managed by the libvirt API.
#   Requires a running libvirt daemon (libvirtd).
#
#   (c) 2008-2010 Florian Haa