#!/bin/bash
#
# /etc/init.d/virtdomains
# Start / stop guests automatically when host boots / shuts down.
#
# chkconfig: 345 99 00
# description: Start / stop Xen/KVM guests.
#
# This script offers fairly basic functionality. It should work on Redhat
# but also on LSB-compliant SuSE releases and on Debian with the LSB package
# installed. (LSB is the Linux Standard Base)
#
# Based on the example in the "Designing High Quality Integrated Linux
# Applications HOWTO" by Avi Alkalay
#
#
# Revised by T.Nonogaki, Stray Penguin
#
# Rev.0.1.2
#
### BEGIN INIT INFO
# Provides: virtdomains
# Required-Start: $syslog $remote_fs libvirtd
# Should-Start:
# Required-Stop: $syslog $remote_fs libvirtd
# Should-Stop:
# Default-Start: 3 4 5
# Default-Stop: 0 1 2 6
# Default-Enabled: yes
# Short-Description: Start/stop xen/KVM guests
# Description: Start / stop guests automatically when host
# boots / shuts down.
### END INIT INFO
# Correct exit code would probably be 5, but it's enough
# if xend complains if we're not running as privileged domain
#if ! [ -e /proc/xen/privcmd ]; then
# exit 0
#fi
service libvirtd status > /dev/null || exit 0
LOCKFILE=/var/lock/subsys/virtdomains
XENDOM_CONFIG=/etc/sysconfig/virtdomains
test -r $XENDOM_CONFIG || { echo "$XENDOM_CONFIG not existing";
if [ "$1" = "stop" ]; then exit 0;
else exit 6; fi; }
. $XENDOM_CONFIG
# Use the SUSE rc_ init script functions;
# emulate them on LSB, RH and other systems
if test -e /etc/rc.status; then
# SUSE rc script library
. /etc/rc.status
else
_cmd=$1
declare -a _SMSG
if test "${_cmd}" = "status"; then
_SMSG=(running dead dead unused unknown)
_RC_UNUSED=3
else
_SMSG=(done failed failed missed failed skipped unused failed failed)
_RC_UNUSED=6
fi
if test -e /etc/init.d/functions; then
# REDHAT
. /etc/init.d/functions
echo_rc()
{
#echo -n " [${_SMSG[${_RC_RV}]}] "
if test ${_RC_RV} = 0; then
success " [${_SMSG[${_RC_RV}]}] "
echo
else
failure " [${_SMSG[${_RC_RV}]}] "
echo
fi
}
elif test -e /lib/lsb/init-functions; then
# LSB
. /lib/lsb/init-functions
if alias log_success_msg >/dev/null 2>/dev/null; then
echo_rc()
{
echo " [${_SMSG[${_RC_RV}]}] "
}
else
echo_rc()
{
if test ${_RC_RV} = 0; then
log_success_msg " [${_SMSG[${_RC_RV}]}] "
else
log_failure_msg " [${_SMSG[${_RC_RV}]}] "
fi
}
fi
else
# emulate it
echo_rc()
{
echo " [${_SMSG[${_RC_RV}]}] "
}
fi
rc_reset() { _RC_RV=0; }
rc_failed()
{
if test -z "$1"; then
_RC_RV=1;
elif test "$1" != "0"; then
_RC_RV=$1;
fi
return ${_RC_RV}
}
rc_check()
{
return rc_failed $?
}
rc_status()
{
rc_failed $?
if test "$1" = "-r"; then _RC_RV=0; shift; fi
if test "$1" = "-s"; then rc_failed 5; echo_rc; rc_failed 3; shift; fi
if test "$1" = "-u"; then rc_failed ${_RC_UNUSED}; echo_rc; rc_failed 3; shift; fi
if test "$1" = "-v"; then echo_rc; shift; fi
if test "$1" = "-r"; then _RC_RV=0; shift; fi
return ${_RC_RV}
}
rc_exit() { exit ${_RC_RV}; }
rc_active()
{
if test -z "$RUNLEVEL"; then read RUNLEVEL REST < <(/sbin/runlevel); fi
if test -e /etc/init.d/S[0-9][0-9]${1}; then return 0; fi
return 1
}
fi
if ! which usleep &>/dev/null
then
usleep()
{
if [ -n "$1" ]
then
sleep $(( $1 / 1000000 ))
fi
}
fi
# Reset status of this service
rc_reset
##
# Returns 0 (success) if the given parameter name is a directory, and
# that directory is not empty.
contains_something()
{
if [ -d "$1" ] && [ `/bin/ls $1 | wc -l` -gt 0 ]
then
return 0
else
return 1
fi
}
# Assume the first non empty, non comment line of the given file
# reads a domain name.
# Returns 0 (success) if a line is found, 1 if the given argument is
# not a file, 2 if the file doesn't contain a valid line.
get_dn_from_f()
{
local dn=""
[ -f "$1" ] || return 1
dn=$(cat $1 | /bin/grep -Ev '^[[:space:]]*(#|$)' | /usr/bin/head -n 1)
[ -z "$dn" ] && return 2
echo $dn
return 0
}
# Read a name from virsh output.
rdname()
{
local nm
nm=$(env LANG=C virsh dominfo ${1##*/} 2>/dev/null |
/bin/awk '/^Name:[[:space:]]+/ { print $2; exit; }')
echo $nm
}
# Generate a string suitable for "case" matching expression from the
# content of the files under XENDOMAINS_AUTO directory.
rdnames()
{
local dom fl dom nm
NAMES=
if ! contains_something "$XENDOMAINS_AUTO"
then
return
fi
for fl in $XENDOMAINS_AUTO/*; do
dom=$(get_dn_from_f $fl)
nm=$(rdname $dom)
if test -z "$NAMES"; then
NAMES=$nm;
else
NAMES="$NAMES|$nm"
fi
done
}
# Return 0 (true) if the given domain is up (includes running, paused,
# dying or zombies as well). Return 1 (false) if and only if state is
# "shut off".
is_running()
{
local nm
nm=$(rdname $1)
RC=1
while read LN; do
read id name state < <(echo "$LN")
if [ "$id" = "Id" -o -z "${id##-*-}" ]; then continue; fi
if [ "$id" = "0" -o "$state" = "shut off" ]; then continue; fi
case $name in
$nm)
RC=0
break
;;
esac
done < <(env LANG=C virsh list --all 2>/dev/null)
return $RC
}
# Starts or restores guest domains.
start()
{
local RETVAL fl dom saved_domains autodoms nm
if [ -f $LOCKFILE ]; then
echo -n "virtdomains already running (lockfile exists)"
rc_failed
return;
fi
[ -d $(dirname "$LOCKFILE") ] ||
mkdir -p $(dirname "$LOCKFILE")
if contains_something "$XENDOMAINS_AUTO"
then
# Start all domains with files in XENDOMAINS_AUTO.
# We expect files for auto starting domains to be in XENDOMAINS_AUTO
# - each file must contain a line that match the name of the domain.
# You can manage the starting order through file names, since this
# sorts the files before processing.
# NOTE: This order is respected only when STARTing domains.
# RESTORing doesn't take this into account for now.
#
# TODO: We should record which domain name belongs
# so we have the option to selectively shut down / migrate later
# If a domain statefile from $XENDOMAINS_SAVE matches a domain name
# in $XENDOMAINS_AUTO, do not try to start that domain; if it didn't
# restore correctly it requires administrative attention.
saved_domains=" "
if [ "$XENDOMAINS_RESTORE" = "true" ] &&
contains_something "$XENDOMAINS_SAVE"; then
rdnames
echo -n "Restoring guest domains: "
saved_domains=`ls $XENDOMAINS_SAVE`
for dom in $XENDOMAINS_SAVE/*; do
case ${dom##*/} in
$NAMES)
# do nothing
;;
*)
continue
;;
esac
echo -n "${dom##*/} "
virsh restore $dom &>/dev/null
RETVAL=$?
if [ $RETVAL -ne 0 ]; then
rc_failed $RETVAL
echo -n '!'
else
touch $LOCKFILE
#mv $dom ${dom%/*}/.${dom##*/}
#rm $dom
fi
echo -n ". "
done
HAVE_SAVE=1
fi
[ $HAVE_SAVE -eq 1 ] && echo
echo -n "Starting auto guest domains: "
autodoms=($(ls -1 $XENDOMAINS_AUTO |sort))
for fl in ${autodoms[@]}; do
nm=$(get_dn_from_f ${XENDOMAINS_AUTO}/$fl)
if [ $? -ne 0 ]; then
continue
fi
echo -n "$nm"
echo $saved_domains | grep -qw $nm >/dev/null
if [ $? -eq 0 ]; then
echo -n "(skip)"
elif is_running $nm; then
echo -n "(skip)"
else
echo -n " "
virsh start $nm &>/dev/null
RETVAL=$?
if [ $RETVAL -ne 0 ]; then
rc_failed $RETVAL
echo -n '!'
else
touch $LOCKFILE
usleep $XENDOMAINS_CREATE_USLEEP
fi
HAVE_AUTO=1
fi
echo -n ". "
done
[ $HAVE_AUTO -eq 1 ] || echo
fi
}
# Return TRUE if it seems only zombie domains (not in shutdown process)
# left, FALSE if no domains left or found any shutting-down healthily.
# FIXME: Are these check conditions correct?
all_zombies()
{
local all=0
local zomb=0
while read LN; do
read id name state < <(echo "$LN")
if [ "$id" = "Id" -o -z "${id##-*-}" ]; then continue; fi
if [ "$id" = "0" -o "$state" = "shut off" ]; then continue; fi
: $((all++))
case $state in
dying)
: $((zomb++))
;;
*)
# do nothing
;;
esac
done < <(env LANG=C virsh list --all 2>/dev/null)
[ $all -eq 0 ] && return 1
[ $all -eq $zomb ] && return 0
return 1
}
# Call syntax: watchdog_vm $dom [force]
# Wait for max $XENDOMAINS_STOP_MAXWAIT for a guest to shutdown;
# if it has not exited by that time kill it, so the init script will
# succeed within a finite amount of time; if force is added,
# it will kill the guest immediately.
watchdog_vm()
{
local dom PSAX opt
local force=0
: ${XENDOMAINS_STOP_MAXWAIT:=0}
dom=$1
if [ -n "$2" ]; then
[ $2 = force ] && force=1
fi
usleep 300000
for no in `seq 0 $XENDOMAINS_STOP_MAXWAIT`; do
# go to kill immediately if force flag was set
if [ $force -eq 1 ]; then
break
fi
# exit if save/migrate/shutdown is finished
if ! is_running $dom; then return; fi
echo -n "."; sleep 1
done
sleep 1
# If the domain still left -
# give it another polite try
is_running $dom || return 0
echo -n "(shut)."
virsh shutdown $dom &>/dev/null
# then forcibly destroy
is_running $dom || return 0
echo -n "(destroy)."
virsh destroy $dom &>/dev/null
is_running $dom || return 0
# finally kill the qemu-kvm process
PSAX=$(ps axlww | grep qemu-kvm | grep "-name $dom" | grep -v grep)
read PSF PSUID PSPID PSPPID < <(echo "$PSAX")
echo -n "(TERM)."
kill $PSPID &>/dev/null
PSAX=$(ps axlww | grep qemu-kvm | grep "-name $dom" | grep -v grep)
test -z "$PSAX" && return 0
echo -n "(KILL)."
kill -s KILL $PSPID &>/dev/null
return 0
}
stop()
{
local RETVAL id name state
# Collect list of domains to shut down
if test "$XENDOMAINS_AUTO_ONLY" = "true"; then
rdnames
fi
echo -n "Shutting down guest domains: "
if ! check_domains_left; then
rm -f $LOCKFILE
RETVAL=0
return 0
fi
while read LN; do
read id name state < <(echo "$LN")
if [ "$id" = "Id" -o -z "${id##-*-}" ]; then continue; fi
if [ "$id" = "0" -o -z "$id" ]; then continue; fi
if [ "$state" != "running" ]; then continue; fi
echo -n "$name"
if test "$XENDOMAINS_AUTO_ONLY" = "true"; then
case $name in
$NAMES)
# nothing
;;
*)
echo "(skip). "
continue
;;
esac
fi
if test "$state" = "dying"; then
echo -n "(zomb). "
continue
fi
if test -n "$XENDOMAINS_MIGRATE"; then
echo -n "(migr)"
virsh migrate $name $XENDOMAINS_MIGRATE &>/dev/null
RETVAL=$?
if [ $RETVAL -ne 0 ]; then
rc_failed $RETVAL
echo -n '!. '
else
watchdog_vm $name
echo -n ". "
continue
fi
fi
if test -n "$XENDOMAINS_SAVE"; then
echo -n "(save)"
mkdir -p "$XENDOMAINS_SAVE"
virsh save $name ${XENDOMAINS_SAVE}/$name &>/dev/null
RETVAL=$?
if [ $RETVAL -ne 0 ]; then
rc_failed $RETVAL
echo -n '!. '
else
watchdog_vm $name
echo -n ". "
continue
fi
fi
if test -n "$XENDOMAINS_SHUTDOWN"; then
# XENDOMAINS_SHUTDOWN should be "--halt --wait"
echo -n "(shut)"
virsh shutdown $name &>/dev/null
RETVAL=$?
if [ $RETVAL -ne 0 ]; then
rc_failed $RETVAL
echo -n '!'
else
watchdog_vm $name
fi
echo -n ". "
fi
done < <(env LANG=C virsh list --all 2>/dev/null)
# NB. this stops ALL guest domains, not just the ones in AUTODIR/*
# This is because it's easier to do ;-) but arguably if this script is run
# on system shutdown then it's also the right thing to do.
if test -n "$XENDOMAINS_SHUTDOWN_ALL"; then
if ! all_zombies; then
# XENDOMAINS_SHUTDOWN_ALL should be "--all --halt --wait"
if ! check_domains_left; then
rm -f $LOCKFILE
return
fi
echo
echo -n "SHUTDOWN_ALL "
while read LN; do
read id name state < <(echo "$LN")
if [ "$id" = "Id" -o -z "${id##-*-}" ]; then continue; fi
if [ "$id" = "0" -o -z "$id" ]; then continue; fi
if [ "$state" = "shut off" ]; then continue; fi
echo -n "$name"
if [ "$state" = "running" ]; then
echo -n "(shut)"
virsh shutdown $name &>/dev/null
RETVAL=$?
if [ $RETVAL -ne 0 ]; then
rc_failed $RETVAL
echo -n '! '
else
watchdog_vm $name
echo -n ". "
fi
else
watchdog_vm $name force
echo -n ". "
fi
done < <(env LANG=C virsh list --all 2>/dev/null)
fi
fi
# Unconditionally delete lock file
rm -f $LOCKFILE
}
check_domain_up()
{
while read LN; do
read id name state < <(echo "$LN")
if [ "$id" = "Id" -o -z "${id##-*-}" ]; then continue; fi
if [ "$id" = "0" -o -z "$id" ]; then continue; fi
if [ "$state" != "running" ]; then continue; fi
case $name in
$1)
return 0
;;
esac
done < <(env LANG=C virsh list --all 2>/dev/null)
return 1
}
check_domains_left()
{
while read LN; do
read id name state < <(echo "$LN")
if [ "$id" = "Id" -o -z "${id##-*-}" ]; then continue; fi
if [ "$id" = "0" -o -z "$id" ]; then continue; fi
case $state in
"shut off")
continue;;
*)
return 0
;;
esac
done < <(env LANG=C virsh list --all 2>/dev/null)
return 1
}
check_all_auto_domains_up()
{
local fl nm dom missing
if ! contains_something "$XENDOMAINS_AUTO"
then
return 0
fi
missing=
for fl in $XENDOMAINS_AUTO/*; do
dom=$(get_dn_from_f $fl)
[ $? -ne 0 ] && continue
nm=$(rdname $dom)
if check_domain_up "$nm"; then
echo -n " $nm"
else
missing="$missing $nm"
fi
done
if test -n "$missing"; then
echo -n " MISS AUTO:$missing"
return 1
fi
return 0
}
check_all_saved_domains_up()
{
local missing
if ! contains_something "$XENDOMAINS_SAVE"
then
return 0
fi
missing=`/bin/ls $XENDOMAINS_SAVE`
echo -n " MISS SAVED: " $missing
return 1
}
# This does NOT necessarily restart all running domains: instead it
# stops all running domains and then boots all the domains specified in
# AUTODIR. If other domains have been started manually then they will
# not get restarted.
# Commented out to avoid confusion!
restart()
{
stop
rc_status -v
start
rc_status
}
reload()
{
restart
}
HAVE_SAVE=0
HAVE_AUTO=0
case "$1" in
start)
start
rc_status
if test -f $LOCKFILE; then rc_status -v; fi
;;
stop)
stop
rc_status -v
;;
restart)
restart
;;
reload)
reload
;;
status)
echo -n "Checking for virtdomains:"
if test ! -f $LOCKFILE; then
rc_failed 3
else
check_all_auto_domains_up
rc_status
#check_all_saved_domains_up
#rc_status
fi
rc_status -v
;;
*)
echo "Usage: $0 {start|stop|restart|reload|status}"
rc_failed 3
rc_status -v
;;
esac
rc_exit