#!/bin/sh
#
#
# Support:      linux-ha@lists.linux-ha.org
# License:      GNU General Public License (GPL)
#
#	Resource Agent for the Xen Hypervisor.
#	Manages Xen virtual machine instances by
#	mapping cluster resource start and stop,  
#	to Xen create and shutdown, respectively.
#
# Mem weighting extension by Florian Tischler - flo_web at floti.at
#
#	usage: $0  {start|stop|status|monitor|meta-data}
#
#	  OCF parameters are as below:
#		OCF_RESKEY_xmfile
#			Absolute path to the Xen control file,
#			for this virtual machine.
#		OCF_RESKEY_allow_mem_management
#			Change memory usage on start/stop/migration
#			of virtual machine
#		OCF_RESKEY_use_mem_weighting
#			Use weighting if changing memory usage on
#			start/stop/migration of virtual machine
#		OCF_RESKEY_memweightfile
#			Absolute path to the memory weighting configuration file
#		OCF_RESKEY_reserved_Dom0_memory
#			minimum memory reserved for domain 0
#		OCF_RESKEY_monitor_scripts
#			scripts to monitor services within the
#			virtual domain

#######################################################################
# Initialization:

. ${OCF_ROOT}/resource.d/heartbeat/.ocf-shellfuncs

#######################################################################


usage() {
  cat <<-!
	usage: $0 {start|stop|status|monitor|meta-data|validate-all}
	!
}


: ${OCF_RESKEY_xmfile=/etc/xen/vm/MyDomU}
: ${OCF_RESKEY_allow_migrate=0}
: ${OCF_RESKEY_allow_mem_management=0}
: ${OCF_RESKEY_reserved_Dom0_memory=512}
: ${OCF_RESKEY_use_mem_weighting=0}
: ${OCF_RESKEY_memweightfile=/etc/xen/MEMWEIGHT}
DOMAIN_NAME=`awk '$1~/^name(=|$)/{print}' ${OCF_RESKEY_xmfile} | sed 's/.*=[[:space:]]*//' | tr -d "[\"']"`
: ${DOMAIN_NAME=${OCF_RESOURCE_INSTANCE}}

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

<longdesc lang="en">
Resource Agent for the Xen Hypervisor.
Manages Xen virtual machine instances by
mapping cluster resource start and stop,  
to Xen create and shutdown, respectively.

Para-virtualized guests can also be migrated by enabling the
meta_attribute allow_migrate.

</longdesc>
<shortdesc lang="en">Manages Xen DomUs</shortdesc>

<parameters>

<parameter name="xmfile" unique="0" required="1">
<longdesc lang="en">
Absolute path to the Xen control file,
for this virtual machine.
</longdesc>
<shortdesc lang="en">Xen control file</shortdesc>
<content type="string" default="" />
</parameter>
<parameter name="allow_migrate" unique="0" required="1">
<longdesc lang="en">
This bool parameters allows to use live migration
for paravirtual machines.
</longdesc>
<shortdesc lang="en">Use live migration</shortdesc>
<content type="boolean" default="0" />
</parameter>
<parameter name="allow_mem_management" unique="0" required="1">
<longdesc lang="en">
This parameter enables dynamic adjustment of memory for start 
and stop actions used for Dom0 and the DomUs. The default is
to not adjust memory dynamically.
</longdesc>
<shortdesc lang="en">Use dynamic memory management</shortdesc>
<content type="boolean" default="0" />
</parameter>
<parameter name="use_mem_weighting" unique="0" required="0">
<longdesc lang="en">
This parameter enables memory weighting for dynamic adjustment of
memory used for Dom0 and the DomUs. The default is to not use
memory weighting
</longdesc>
<shortdesc lang="en">Use memory weighting for dynamic memory management</shortdesc>
<content type="boolean" default="0" />
</parameter>
<parameter name="memweightfile" unique="0" required="0">
<longdesc lang="en">
Absolute path to the memory weighting config file for dynamic memory management
for Xen virtual machines.
</longdesc>
<shortdesc lang="en">memory weighting config file</shortdesc>
<content type="string" default="/etc/xen/MEMWEIGHT" />
</parameter>
<parameter name="reserved_Dom0_memory" unique="0" required="1">
<longdesc lang="en">
In case memory management is used, this parameter
defines the minimum amount of memory to be reserved
for the dom0. The default minimum memory is 512MB.
</longdesc>
<shortdesc lang="en">Minimum Dom0 memory</shortdesc>
<content type="string" default="512" />
</parameter>
<parameter name="monitor_scripts" unique="0" required="0">
<longdesc lang="en">
To additionally monitor services within the unprivileged domain,
add this parameter with a list of scripts to monitor.

NB: In this case make sure to set the start-delay of the monitor
operation to at least the time it takes for the DomU to start all
services.
</longdesc>
<shortdesc lang="en">list of space separated monitor scripts</shortdesc>
<content type="string" default="" />
</parameter>

</parameters>

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

Xen_Status() {
  STATUS=`xm list --long $1 | grep status 2>/dev/null`
  if [ "X${STATUS}" != "X" ];then
    # we have Xen 3.0.4 or higher
    STATUS_NOSPACES=`echo "$STATUS" | awk '{ print $1,$2}'`
    if [ "$STATUS_NOSPACES" = "(status 2)" -o "$STATUS_NOSPACES" = "(status 1)" ] ; then
      return $OCF_SUCCESS
    else 
      return $OCF_NOT_RUNNING
    fi
  else
    # we have Xen 3.0.3 or lower
    STATUS=`xm list --long $1 | grep state 2>/dev/null`
    echo "${STATUS}" | grep -qs "[-r][-b][-p]---"
    if [ $? -ne 0 ];then
      return $OCF_NOT_RUNNING
    else
      return $OCF_SUCCESS
    fi
    
  fi
}

Get_MemWeight_of() {
    DOMWEIGHT=$1
    MEMWEIGHT=`grep ${DOMWEIGHT} ${OCF_RESKEY_memweightfile} | awk -F= '{ print $2}' | sed 's/[ ]*//'`
    if [ -z ${MEMWEIGHT} ]; then
      echo "1"
      ocf_log info "No MemWeight for $DOMWEIGHT"
    elif [ ! `echo ${MEMWEIGHT} | egrep '^[0-9]{1,2}$'` ]; then
      echo "1"
      ocf_log info "Invalid MemWeight for $DOMWEIGHT"
    elif [ ${MEMWEIGHT} -lt 1 ] || [ ${MEMWEIGHT} -gt 10 ]; then
      echo "1"
      ocf_log info "Out of Range MemWeight for $DOMWEIGHT"
    else
      echo "${MEMWEIGHT}"
    fi
}

Xen_Adjust_Memory() {
    if [ "${OCF_RESKEY_allow_mem_management}" != 0 ];then
      CNTNEW=$1
      RUNNING=`Xen_List_running`
      RUNCNT=`Xen_Count_running`
      MAXMEM=`Xen_Total_Memory`
      if [ ${RUNCNT} -eq 0 -a ${CNTNEW} -eq 0 ];then
	      RUNCNT=1
      fi
      if [ "${OCF_RESKEY_use_mem_weighting}" != 0 ];then
        AVAILABLEMEM=$(( ${MAXMEM} - ${OCF_RESKEY_reserved_Dom0_memory} ))
        if [ ${CNTNEW} -eq 1 ];then
	        MEMWEIGHTSUM=`Get_MemWeight_of ${DOMAIN_NAME}` #memweightsum for a newly started domain
        fi
        for DOMSUM in ${RUNNING};do
          MEMWEIGHTSUM=$(( ${MEMWEIGHTSUM} + `Get_MemWeight_of ${DOMSUM}` )) #add all running domains
        done
        #ocf_log debug "Debug: memweightsum = ${MEMWEIGHTSUM}"
        if [ ! -z ${MEMWEIGHTSUM} ]; then #memweightsum is zero if the last domain has stopped
          MEMPART=$(( ${AVAILABLEMEM} / ${MEMWEIGHTSUM} ))
        fi
        for DOM in ${RUNNING};do
          NEWMEM=$(( ${MEMPART} * `Get_MemWeight_of ${DOM}` )) #adjust all running domains
          xm mem-set ${DOM} ${NEWMEM}
          ocf_log info "Adjusted memory to: $NEWMEM, for running domain: $DOM"
        done
        if [ ${CNTNEW} -eq 1 ];then
          NEWMEM=$(( ${MEMPART} * `Get_MemWeight_of ${DOMAIN_NAME}` )) #newmem for a newly started domain
        fi
      else
        #NEWMEM=`echo "(${MAXMEM}-${OCF_RESKEY_reserved_Dom0_memory})/(${RUNCNT}+${CNTNEW})"|bc`
        NEWMEM=$(( (${MAXMEM} - ${OCF_RESKEY_reserved_Dom0_memory}) / (${RUNCNT} + ${CNTNEW} ) ))
        # do not rely on ballooning add dom0_mem=512 instead to force memory for dom0
        #xm mem-set Domain-0 ${OCF_RESKEY_reserved_Dom0_memory}
        for DOM in ${RUNNING};do
          xm mem-set ${DOM} ${NEWMEM}
        done
        ocf_log info "Adjusted memory to: $NEWMEM, for the following $RUNCNT domains: $RUNNING"
      fi
    fi
}

Xen_List_all() {
	xm list | grep -v -e "Name" -e "Domain-0" | awk '{print $1}'
}
Xen_List_running() {
	ALL_DOMS=`Xen_List_all`
	for DOM in ${ALL_DOMS};do
		if Xen_Status $DOM;then
			echo "${DOM} "
		fi
	done
}
Xen_Count_running() {
	Xen_List_running | wc -w
}

Xen_Monitor() {
  Xen_Status ${DOMAIN_NAME}
  if [ $? -eq ${OCF_NOT_RUNNING} ];then
	return ${OCF_NOT_RUNNING}
  fi
  if [ "X${OCF_RESKEY_monitor_scripts}" == "X" ];then
	return ${OCF_SUCCESS}
  fi
  for SCRIPT in ${OCF_RESKEY_monitor_scripts};do
	$SCRIPT
	if [ $? -ne 0 ];then
		return ${OCF_NOT_RUNNING}
	fi
  done
}

Xen_Total_Memory() {
	xm info | grep "^total_memory" | awk '{print $3}'
}

Xen_Start() {
  if
    Xen_Status ${DOMAIN_NAME}
  then
    ocf_log info "Xen domain $DOMAIN_NAME already running."
    return $OCF_SUCCESS
  else
    Xen_Adjust_Memory 1
    if [ "${OCF_RESKEY_allow_mem_management}" != 0 ];then
      ocf_log info "New memory for new virtual domain ${DOMAIN_NAME}: ${NEWMEM}"
      sed -i -e "/^memory[ ]*=/ s/^memory[ ]*=.*/memory = ${NEWMEM}/" ${OCF_RESKEY_xmfile}
      xm mem-set ${DOMAIN_NAME} ${NEWMEM} 
    fi
    xm create ${OCF_RESKEY_xmfile} name=$DOMAIN_NAME
    rc=$?
    if
      [ $rc -ne 0 ]
    then
      return $OCF_ERR_GENERIC
    else 
      if [ "${OCF_RESKEY_allow_mem_management}" != 0 ];then
        xm mem-set ${DOMAIN_NAME} ${NEWMEM}
      fi
      return $OCF_SUCCESS
    fi
  fi  
}

Xen_Stop() {
  if
    Xen_Status ${DOMAIN_NAME} 
  then
    # xm commands are asynchroneus, therefore wait a bit
    # to just give it time to shutdown correctly
    xm shutdown -w ${DOMAIN_NAME}

    rc=$?
    sleep 3
    if
      [ $rc -ne 0 ]
    then
      xm destroy -w ${DOMAIN_NAME}
      rc2=$?
      sleep 3
      if [ $rc2 -ne 0 ];then 
        return ${OCF_ERR_GENERIC}
      else
        Xen_Adjust_Memory 0
        return ${OCF_SUCCESS}
      fi
    else
      Xen_Adjust_Memory 0
      return ${OCF_SUCCESS}
    fi
  else
    ocf_log info "Xen domain $DOMAIN_NAME already stopped."
    return $OCF_SUCCESS
  fi
}

Xen_Migrate_To() {
  target_node="$OCF_RESKEY_CRM_meta_migrate_target"
  if 
    Xen_Status ${DOMAIN_NAME}
  then
    ocf_log info "$DOMAIN_NAME: Starting xm migrate to $target_node"
    xm migrate --live $DOMAIN_NAME $target_node
    rc=$?
    if
      [ $rc -ne 0 ]
    then
      ocf_log err "$DOMAIN_NAME: xm migrate to $target_node failed: $rc"
      return $OCF_ERR_GENERIC
    else 
      Xen_Adjust_Memory 0
      ocf_log err "$DOMAIN_NAME: xm migrate to $target_node succeeded."
      return $OCF_SUCCESS
    fi
  else
    ocf_log err "$DOMAIN_NAME: migrate_to: Not active locally!"
    return $OCF_ERR_GENERIC
  fi
}

Xen_Migrate_From() {
  if 
    Xen_Status ${DOMAIN_NAME}
  then
    Xen_Adjust_Memory 0
    ocf_log info "$DOMAIN_NAME: Active locally, migration successful"
    return $OCF_SUCCESS
  else
    ocf_log err "$DOMAIN_NAME: Not active locally, migration failed!"
    return $OCF_ERR_GENERIC
  fi
}

Xen_Validate_All() {
  return $OCF_SUCCESS
}

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

case $1 in
  meta-data)		meta_data
			exit $OCF_SUCCESS
			;;
  start)		Xen_Start
			;;
  stop)			Xen_Stop
			;;
  migrate_to)		Xen_Migrate_To
			;;
  migrate_from)		Xen_Migrate_From
			;;
  monitor)		Xen_Monitor
			;;
  status)		Xen_Status ${DOMAIN_NAME}
			;;
  validate-all)		Xen_Validate_All
			;;
  usage)		usage
			exit $OCF_SUCCESS
			;;
  *)			usage
			exit $OCF_ERR_UNIMPLEMENTED
			;;
esac
exit $?

