#!/bin/sh # the following is the LSB init header # ### BEGIN INIT INFO # Provides: libvirt-guests # Required-Start: libvirtd # Required-Stop: libvirtd # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: suspend/resume libvirt guests on shutdown/boot # Description: This is a script for suspending active libvirt guests # on shutdown and resuming them on next boot # See http://libvirt.org ### END INIT INFO # the following is chkconfig init header # # libvirt-guests: suspend/resume libvirt guests on shutdown/boot # # chkconfig: 345 99 01 # description: This is a script for suspending active libvirt guests \ # on shutdown and resuming them on next boot \ # See http://libvirt.org # sysconfdir="@sysconfdir@" localstatedir="@localstatedir@" libvirtd="@sbindir@"/libvirtd # Source function library. test ! -r "$sysconfdir"/rc.d/init.d/functions || . "$sysconfdir"/rc.d/init.d/functions # Source gettext library. # Make sure this file is recognized as having translations: _("dummy") . "@bindir@"/gettext.sh export TEXTDOMAIN="@PACKAGE@" TEXTDOMAINDIR="@localedir@" URIS=default ON_BOOT=start ON_SHUTDOWN=suspend SHUTDOWN_TIMEOUT=300 PARALLEL_SHUTDOWN=0 START_DELAY=0 BYPASS_CACHE=0 test -f "$sysconfdir"/sysconfig/libvirt-guests && . "$sysconfdir"/sysconfig/libvirt-guests LISTFILE="$localstatedir"/lib/libvirt/libvirt-guests VAR_SUBSYS_LIBVIRT_GUESTS="$localstatedir"/lock/subsys/libvirt-guests RETVAL=0 # retval COMMAND ARGUMENTS... # run command with arguments and convert non-zero return value to 1 and set # the global return variable retval() { "$@" if [ $? -ne 0 ]; then RETVAL=1 return 1 else return 0 fi } # run_virsh URI ARGUMENTS... # start virsh and let it execute ARGUMENTS on URI # If URI is "default" virsh is called without the "-c" argument # (using libvirt's default connection) run_virsh() { uri=$1 shift if [ "x$uri" = xdefault ]; then virsh "$@" /dev/null if [ $? -ne 0 ]; then eval_gettext "Can't connect to \$uri. Skipping." echo return 1 fi } # list_guests URI PERSISTENT # List running guests on URI. # PERSISTENT argument options: # --persistent: list only persistent guests # --transient: list only transient guests # [none]: list both persistent and transient guests list_guests() { uri=$1 persistent=$2 list=$(run_virsh_c "$uri" list --uuid $persistent) if [ $? -ne 0 ]; then RETVAL=1 return 1 fi echo $list } # guest_name URI UUID # return name of guest UUID on URI guest_name() { uri=$1 uuid=$2 run_virsh "$uri" domname "$uuid" 2>/dev/null } # guest_is_on URI UUID # check if guest UUID on URI is running # Result is returned by variable "guest_running" guest_is_on() { uri=$1 uuid=$2 guest_running=false id=$(run_virsh "$uri" domid "$uuid") if [ $? -ne 0 ]; then RETVAL=1 return 1 fi [ -n "$id" ] && [ "x$id" != x- ] && guest_running=true return 0 } # started # Create the startup lock file started() { touch "$VAR_SUBSYS_LIBVIRT_GUESTS" } # start # Start or resume the guests start() { [ -f "$LISTFILE" ] || { started; return 0; } if [ "x$ON_BOOT" != xstart ]; then gettext "libvirt-guests is configured not to start any guests on boot" echo rm -f "$LISTFILE" started return 0 fi isfirst=true bypass= test "x$BYPASS_CACHE" = x0 || bypass=--bypass-cache while read uri list; do configured=false set -f for confuri in $URIS; do set +f if [ "x$confuri" = "x$uri" ]; then configured=true break fi done set +f if ! "$configured"; then eval_gettext "Ignoring guests on \$uri URI"; echo continue fi test_connect "$uri" || continue eval_gettext "Resuming guests on \$uri URI..."; echo for guest in $list; do name=$(guest_name "$uri" "$guest") eval_gettext "Resuming guest \$name: " if guest_is_on "$uri" "$guest"; then if "$guest_running"; then gettext "already active"; echo else if "$isfirst"; then isfirst=false else sleep $START_DELAY fi retval run_virsh "$uri" start $bypass "$name" \ >/dev/null && \ gettext "done"; echo fi fi done done <"$LISTFILE" rm -f "$LISTFILE" started } # suspend_guest URI GUEST # Do a managed save on a GUEST on URI. This function returns after the guest # was saved. suspend_guest() { uri=$1 guest=$2 name=$(guest_name "$uri" "$guest") label=$(eval_gettext "Suspending \$name: ") bypass= slept=0 test "x$BYPASS_CACHE" = x0 || bypass=--bypass-cache printf '%s...\n' "$label" run_virsh "$uri" managedsave $bypass "$guest" >/dev/null & virsh_pid=$! while true; do sleep 1 kill -0 "$virsh_pid" >/dev/null 2>&1 || break slept=$(($slept + 1)) if [ $(($slept % 5)) -eq 0 ]; then progress=$(run_virsh_c "$uri" domjobinfo "$guest" 2>/dev/null | \ awk '/^Data processed:/{print $3, $4}') if [ -n "$progress" ]; then printf '%s%s\n' "$label" "$progress" else printf '%s%s\n' "$label" "..." fi fi done retval wait "$virsh_pid" && printf '%s%s\n' "$label" "$(gettext "done")" } # shutdown_guest URI GUEST # Start a ACPI shutdown of GUEST on URI. This function return after the quest # was successfully shutdown or the timeout defined by $SHUTDOWN_TIMEOUT expires. shutdown_guest() { uri=$1 guest=$2 name=$(guest_name "$uri" "$guest") eval_gettext "Starting shutdown on guest: \$name" echo retval run_virsh "$uri" shutdown "$guest" >/dev/null || return timeout=$SHUTDOWN_TIMEOUT check_timeout=false if [ $timeout -gt 0 ]; then check_timeout=true format=$(eval_gettext "Waiting for guest %s to shut down, %d seconds left\n") else slept=0 format=$(eval_gettext "Waiting for guest %s to shut down\n") fi while ! $check_timeout || [ "$timeout" -gt 0 ]; do sleep 1 guest_is_on "$uri" "$guest" || return "$guest_running" || break if $check_timeout; then if [ $(($timeout % 5)) -eq 0 ]; then printf "$format" "$name" "$timeout" fi timeout=$(($timeout - 1)) else slept=$(($slept + 1)) if [ $(($slept % 5)) -eq 0 ]; then printf "$format" "$name" fi fi done if guest_is_on "$uri" "$guest"; then if "$guest_running"; then eval_gettext "Shutdown of guest \$name failed to complete in time." else eval_gettext "Shutdown of guest \$name complete." fi fi } # shutdown_guest_async URI GUEST # Start a ACPI shutdown of GUEST on URI. This function returns after the command # was issued to libvirt to allow parallel shutdown. shutdown_guest_async() { uri=$1 guest=$2 name=$(guest_name "$uri" "$guest") eval_gettext "Starting shutdown on guest: \$name" echo retval run_virsh "$uri" shutdown "$guest" > /dev/null } # guest_count GUEST_LIST # Returns number of guests in GUEST_LIST guest_count() { set -- $1 echo $# } # check_guests_shutdown URI GUESTS # check if shutdown is complete on guests in "GUESTS" and returns only # guests that are still shutting down check_guests_shutdown() { uri=$1 guests=$2 guests_up= for guest in $guests; do if ! guest_is_on "$uri" "$guest" >/dev/null 2>&1; then eval_gettext "Failed to determine state of guest: \$guest. Not tracking it anymore." echo continue fi if "$guest_running"; then guests_up="$guests_up $guest" fi done echo "$guests_up" } # print_guests_shutdown URI BEFORE AFTER # Checks for differences in the lists BEFORE and AFTER and prints # a shutdown complete notice for guests that have finished print_guests_shutdown() { uri=$1 before=$2 after=$3 for guest in $before; do case " $after " in *" $guest "*) continue;; esac name=$(guest_name "$uri" "$guest") eval_gettext "Shutdown of guest \$name complete." echo done } # shutdown_guests_parallel URI GUESTS # Shutdown guests GUESTS on machine URI in parallel shutdown_guests_parallel() { uri=$1 guests=$2 on_shutdown= check_timeout=false timeout=$SHUTDOWN_TIMEOUT if [ $timeout -gt 0 ]; then check_timeout=true format=$(eval_gettext "Waiting for %d guests to shut down, %d seconds left\n") else slept=0 format=$(eval_gettext "Waiting for %d guests to shut down\n") fi while [ -n "$on_shutdown" ] || [ -n "$guests" ]; do while [ -n "$guests" ] && [ $(guest_count "$on_shutdown") -lt "$PARALLEL_SHUTDOWN" ]; do set -- $guests guest=$1 shift guests=$* shutdown_guest_async "$uri" "$guest" on_shutdown="$on_shutdown $guest" done sleep 1 set -- $guests guestcount=$# set -- $on_shutdown shutdowncount=$# if $check_timeout; then if [ $(($timeout % 5)) -eq 0 ]; then printf "$format" $(($guestcount + $shutdowncount)) "$timeout" fi timeout=$(($timeout - 1)) if [ $timeout -le 0 ]; then eval_gettext "Timeout expired while shutting down domains"; echo RETVAL=1 return fi else slept=$(($slept + 1)) if [ $(($slept % 5)) -eq 0 ]; then printf "$format" $(($guestcount + $shutdowncount)) fi fi on_shutdown_prev=$on_shutdown on_shutdown=$(check_guests_shutdown "$uri" "$on_shutdown") print_guests_shutdown "$uri" "$on_shutdown_prev" "$on_shutdown" done } # stop # Shutdown or save guests on the configured uris stop() { # last stop was not followed by start [ -f "$LISTFILE" ] && return 0 suspending=true if [ "x$ON_SHUTDOWN" = xshutdown ]; then suspending=false if [ $SHUTDOWN_TIMEOUT -lt 0 ]; then gettext "SHUTDOWN_TIMEOUT must be equal or greater than 0" echo RETVAL=6 return fi fi : >"$LISTFILE" set -f for uri in $URIS; do set +f test_connect "$uri" || continue eval_gettext "Running guests on \$uri URI: " list=$(list_guests "$uri") if [ $? -eq 0 ]; then empty=true for uuid in $list; do "$empty" || printf ", " printf %s "$(guest_name "$uri" "$uuid")" empty=false done if "$empty"; then gettext "no running guests." fi echo fi if "$suspending"; then transient=$(list_guests "$uri" "--transient") if [ $? -eq 0 ]; then empty=true for uuid in $transient; do if "$empty"; then eval_gettext "Not suspending transient guests on URI: \$uri: " empty=false else printf ", " fi printf %s "$(guest_name "$uri" "$uuid")" done echo # reload domain list to contain only persistent guests list=$(list_guests "$uri" "--persistent") if [ $? -ne 0 ]; then eval_gettext "Failed to list persistent guests on \$uri" echo RETVAL=1 set +f return fi else gettext "Failed to list transient guests" echo RETVAL=1 set +f return fi fi if [ -n "$list" ]; then echo "$uri" "$list" >>"$LISTFILE" fi done set +f if [ -s "$LISTFILE" ]; then while read uri list; do if "$suspending"; then eval_gettext "Suspending guests on \$uri URI..."; echo else eval_gettext "Shutting down guests on \$uri URI..."; echo fi if [ "$PARALLEL_SHUTDOWN" -gt 1 ] && ! "$suspending"; then shutdown_guests_parallel "$uri" "$list" else for guest in $list; do if "$suspending"; then suspend_guest "$uri" "$guest" else shutdown_guest "$uri" "$guest" fi done fi done <"$LISTFILE" else rm -f "$LISTFILE" fi rm -f "$VAR_SUBSYS_LIBVIRT_GUESTS" } # gueststatus # List status of guests gueststatus() { set -f for uri in $URIS; do set +f echo "* $uri URI:" retval run_virsh "$uri" list || echo done set +f } # rh_status # Display current status: whether saved state exists, and whether start # has been executed. We cannot use status() from the functions library, # since there is no external daemon process matching this init script. rh_status() { if [ -f "$LISTFILE" ]; then gettext "stopped, with saved guests"; echo RETVAL=3 else if [ -f "$VAR_SUBSYS_LIBVIRT_GUESTS" ]; then gettext "started"; echo else gettext "stopped, with no saved guests"; echo fi RETVAL=0 fi } # usage [val] # Display usage string, then exit with VAL (defaults to 2). usage() { program_name=$0 eval_gettext "Usage: \$program_name {start|stop|status|restart|"\ "condrestart|try-restart|reload|force-reload|gueststatus|shutdown}"; echo exit ${1-2} } # See how we were called. if test $# != 1; then usage fi case "$1" in --help) usage 0 ;; start|stop|gueststatus) "$1" ;; restart) stop && start ;; condrestart|try-restart) [ -f "$VAR_SUBSYS_LIBVIRT_GUESTS" ] && stop && start ;; reload|force-reload) # Nothing to do; we reread configuration on each invocation ;; status) rh_status ;; shutdown) ON_SHUTDOWN=shutdown stop ;; *) usage ;; esac exit $RETVAL