[libvirt] [PATCH v2] Init script for handling guests on shutdown/boot

Example output during shutdown: Running guests on default URI: console, rhel6-1, rhel5-64 Running guests on lxc:/// URI: lxc-shell Running guests on xen:/// URI: error: no hypervisor driver available for xen:/// error: failed to connect to the hypervisor Running guests on vbox+tcp://orkuz/system URI: no running guests. Suspending guests on default URI... Suspending console: done Suspending rhel6-1: done Suspending rhel5-64: done Suspending guests on lxc:/// URI... Suspending lxc-shell: error: Failed to save domain 9cba8bfb-56f4-6589-2d12-8a58c886dd3b state error: this function is not supported by the hypervisor: virDomainManagedSave Note, the "Suspending $guest: " shows progress during the suspend phase if domjobinfo gives meaningful output. Example output during boot: Resuming guests on default URI... Resuming guest rhel6-1: done Resuming guest rhel5-64: done Resuming guest console: done Resuming guests on lxc:/// URI... Resuming guest lxc-shell: already active Configuration used for generating the examples above: URIS='default lxc:/// xen:/// vbox+tcp://orkuz/system' The script uses /var/lib/libvirt/libvirt-guests files to note all active guest it should try to resume on next boot. It's content looks like: default 7f8b9d93-30e1-f0b9-47a7-cb408482654b 085b4c95-5da2-e8e1-712f-6ea6a4156af2 fb4d8360-5305-df3a-2da1-07d682891b8c lxc:/// 9cba8bfb-56f4-6589-2d12-8a58c886dd3b --- Version 2 changes: - fixes suggested by Eric - configurable ON_BOOT and ON_SHUTDOWN behavior inspired by Gerd daemon/Makefile.am | 16 ++- daemon/libvirt-guests.init.in | 295 +++++++++++++++++++++++++++++++++++++++++ daemon/libvirt-guests.sysconf | 24 ++++ libvirt.spec.in | 4 + 4 files changed, 335 insertions(+), 4 deletions(-) create mode 100644 daemon/libvirt-guests.init.in create mode 100644 daemon/libvirt-guests.sysconf diff --git a/daemon/Makefile.am b/daemon/Makefile.am index a82e9a9..ed469bf 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -29,6 +29,8 @@ EXTRA_DIST = \ libvirtd.lxc.logrotate.in \ libvirtd.uml.logrotate.in \ test_libvirtd.aug \ + libvirt-guests.init.in \ + libvirt-guests.sysconf \ $(AVAHI_SOURCES) \ $(DAEMON_SOURCES) @@ -216,21 +218,27 @@ install-logrotate: $(LOGROTATE_CONFS) $(INSTALL_DATA) libvirtd.uml.logrotate $(DESTDIR)$(sysconfdir)/logrotate.d/libvirtd.uml if LIBVIRT_INIT_SCRIPT_RED_HAT -install-init: libvirtd.init +install-init: libvirtd.init libvirt-guests.init mkdir -p $(DESTDIR)$(sysconfdir)/rc.d/init.d $(INSTALL_SCRIPT) libvirtd.init \ $(DESTDIR)$(sysconfdir)/rc.d/init.d/libvirtd + $(INSTALL_SCRIPT) libvirt-guests.init \ + $(DESTDIR)$(sysconfdir)/rc.d/init.d/libvirt-guests mkdir -p $(DESTDIR)$(sysconfdir)/sysconfig $(INSTALL_SCRIPT) $(srcdir)/libvirtd.sysconf \ $(DESTDIR)$(sysconfdir)/sysconfig/libvirtd + $(INSTALL_SCRIPT) $(srcdir)/libvirt-guests.sysconf \ + $(DESTDIR)$(sysconfdir)/sysconfig/libvirt-guests uninstall-init: rm -f $(DESTDIR)$(sysconfdir)/rc.d/init.d/libvirtd \ - $(DESTDIR)$(sysconfdir)/sysconfig/libvirtd + $(DESTDIR)$(sysconfdir)/sysconfig/libvirtd \ + $(DESTDIR)$(sysconfdir)/rc.d/init.d/libvirt-guests \ + $(DESTDIR)$(sysconfdir)/sysconfig/libvirt-guests -BUILT_SOURCES += libvirtd.init +BUILT_SOURCES += libvirtd.init libvirt-guests.init -libvirtd.init: libvirtd.init.in +%.init: %.init.in $(AM_V_GEN)sed \ -e s!\@localstatedir\@!@localstatedir@!g \ -e s!\@sbindir\@!@sbindir@!g \ diff --git a/daemon/libvirt-guests.init.in b/daemon/libvirt-guests.init.in new file mode 100644 index 0000000..826f415 --- /dev/null +++ b/daemon/libvirt-guests.init.in @@ -0,0 +1,295 @@ +#!/bin/sh + +# the following is the LSB init header +# +### BEGIN INIT INFO +# Provides: libvirt-guests +# Required-Start: libvirtd +# Required-Stop: libvirtd +# Default-Start: 3 4 5 +# 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 98 02 +# 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@ + +# Source function library. +. "$sysconfdir"/rc.d/init.d/functions + +URIS=default +ON_BOOT=start +ON_SHUTDOWN=suspend +SHUTDOWN_TIMEOUT=0 + +test -f "$sysconfdir"/sysconfig/libvirt-guests && . "$sysconfdir"/sysconfig/libvirt-guests + +LISTFILE="$localstatedir"/lib/libvirt/libvirt-guests + +RETVAL=0 + +retval() { + "$@" + if [ $? -ne 0 ]; then + RETVAL=1 + return 1 + else + return 0 + fi +} + +run_virsh() { + uri=$1 + shift + + if [ "x$uri" = xdefault ]; then + conn= + else + conn="-c $uri" + fi + + virsh $conn "$@" +} + +run_virsh_c() { + ( export LC_ALL=C; run_virsh "$@" ) +} + +list_guests() { + uri=$1 + + list=$(run_virsh_c $uri list) + if [ $? -ne 0 ]; then + RETVAL=1 + return 1 + fi + + uuids= + for id in $(echo "$list" | awk 'NR > 2 {print $1}'); do + uuid=$(run_virsh_c $uri dominfo $id | awk '/^UUID:/{print $2}') + if [ -z "$uuid" ]; then + RETVAL=1 + return 1 + fi + uuids="$uuids $uuid" + done + + echo $uuids +} + +guest_name() { + uri=$1 + uuid=$2 + + name=$(run_virsh_c $uri dominfo $uuid 2>/dev/null | \ + awk '/^Name:/{print $2}') + [ -n "$name" ] || name=$uuid + + echo "$name" +} + +guest_is_on() { + uri=$1 + uuid=$2 + + guest_running=false + info=$(run_virsh_c $uri dominfo $uuid) + if [ $? -ne 0 ]; then + RETVAL=1 + return 1 + fi + + id=$(echo "$info" | awk '/^Id:/{print $2}') + + [ -n "$id" ] && [ "x$id" != x- ] && guest_running=true + return 0 +} + +start() { + [ -f $LISTFILE ] || return 0 + + if [ "x$ON_BOOT" != xstart ]; then + echo $"libvirt-guests is configured not to start any guests on boot" + rm -f $LISTFILE + return 0 + fi + + while read uri list; do + configured=false + for confuri in $URIS; do + if [ $confuri = $uri ]; then + configured=true + break + fi + done + if ! $configured; then + echo $"Ignoring guests on $uri URI" + continue + fi + + echo $"Resuming guests on $uri URI..." + for guest in $list; do + name=$(guest_name $uri $guest) + echo -n $"Resuming guest $name: " + if guest_is_on $uri $guest; then + if $guest_running; then + echo $"already active" + else + retval run_virsh $uri start "$name" >/dev/null && \ + echo $"done" + fi + fi + done + done <$LISTFILE + + rm -f $LISTFILE +} + +suspend_guest() +{ + uri=$1 + guest=$2 + + name=$(guest_name $uri $guest) + label=$"Suspending $name: " + echo -n "$label" + run_virsh $uri managedsave $guest >/dev/null & + virsh_pid=$! + while true; do + sleep 1 + kill -0 $virsh_pid >&/dev/null || break + progress=$(run_virsh_c $uri domjobinfo $guest 2>/dev/null | \ + awk '/^Data processed:/{print $3, $4}') + if [ -n "$progress" ]; then + printf '\r%s%12s ' "$label" "$progress" + else + printf '\r%s%-12s ' "$label" "..." + fi + done + retval wait $virsh_pid && printf '\r%s%-12s\n' "$label" $"done" +} + +shutdown_guest() +{ + uri=$1 + guest=$2 + + name=$(guest_name $uri $guest) + label=$"Shutting down $name: " + echo -n "$label" + retval run_virsh $uri shutdown $guest >/dev/null || return + timeout=$SHUTDOWN_TIMEOUT + while [ $timeout -gt 0 ]; do + sleep 1 + timeout=$[timeout - 1] + guest_is_on $uri $guest || return + $guest_running || break + printf '\r%s%-12d ' "$label" $timeout + done + + if guest_is_on $uri $guest; then + if $guest_running; then + printf '\r%s%-12s\n' "$label" $"failed to shutdown in time" + else + printf '\r%s%-12s\n' "$label" $"done" + fi + fi +} + +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 -le 0 ]; then + echo $"Shutdown action requested but SHUTDOWN_TIMEOUT was not set" + RETVAL=6 + return + fi + fi + + : >$LISTFILE + for uri in $URIS; do + echo -n $"Running guests on $uri URI: " + list=$(list_guests $uri) + if [ $? -eq 0 ]; then + empty=true + for uuid in $list; do + $empty || printf ", " + echo -n $(guest_name $uri $uuid) + empty=false + done + if $empty; then + echo $"no running guests." + else + echo + echo $uri $list >>$LISTFILE + fi + fi + done + + while read uri list; do + if $suspending; then + echo $"Suspending guests on $uri URI..." + else + echo $"Shutting down guests on $uri URI..." + fi + + for guest in $list; do + if $suspending; then + suspend_guest $uri $guest + else + shutdown_guest $uri $guest + fi + done + done <$LISTFILE +} + +gueststatus() { + for uri in $URIS; do + echo "* $uri URI:" + retval run_virsh $uri list || echo + done +} + +# See how we were called. +case "$1" in + start|stop|gueststatus) + $1 + ;; + restart) + stop && start + ;; + force-reload) + ;; + status) + if [ -f $LISTFILE ]; then + RETVAL=3 + else + RETVAL=0 + fi + ;; + shutdown) + ON_SHUTDOWN=shutdown + stop + ;; + *) + echo $"Usage: $0 {start|stop|restart|force-reload|gueststatus|shutdown}" + exit 3 + ;; +esac +exit $RETVAL diff --git a/daemon/libvirt-guests.sysconf b/daemon/libvirt-guests.sysconf new file mode 100644 index 0000000..cd58728 --- /dev/null +++ b/daemon/libvirt-guests.sysconf @@ -0,0 +1,24 @@ +# URIs to check for running guests +# example: URIS='default xen:/// vbox+tcp://host/system lxc:///' +#URIS=default + +# action taken on host boot +# - start all guests which were running on shutdown are started on boot +# regardless on their autostart settings +# - ignore libvirt-guests init script won't start any guest on boot, however, +# guests marked as autostart will still be automatically started by +# libvirtd +#ON_BOOT=start + +# action taken on host shutdown +# - suspend all running guests are suspended using virsh managedsave +# - shutdown all running guests are asked to shutdown. Please be careful with +# this settings since there is no way to distinguish between a +# guest which is stuck or ignores shutdown requests and a guest +# which just needs a long time to shutdown. When setting +# ON_SHUTDOWN=shutdown, you must also set SHUTDOWN_TIMEOUT to a +# value suitable for your guests. +#ON_SHUTDOWN=suspend + +# number of seconds we're willing to wait for a guest to shut down +#SHUTDOWN_TIMEOUT=0 diff --git a/libvirt.spec.in b/libvirt.spec.in index 7d5ea85..3d3b871 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -830,6 +830,10 @@ fi %{_datadir}/libvirt/cpu_map.xml +%{_sysconfdir}/rc.d/init.d/libvirt-guests +%config(noreplace) %{_sysconfdir}/sysconfig/libvirt-guests +%dir %attr(0700, root, root) %{_localstatedir}/lib/libvirt + %if %{with_sasl} %config(noreplace) %{_sysconfdir}/sasl2/libvirt.conf %endif -- 1.7.1

On 05/18/2010 07:27 AM, Jiri Denemark wrote:
Version 2 changes: - fixes suggested by Eric - configurable ON_BOOT and ON_SHUTDOWN behavior inspired by Gerd
+++ b/daemon/libvirt-guests.init.in @@ -0,0 +1,295 @@ + +sysconfdir=@sysconfdir@ +localstatedir=@localstatedir@
Just in case @sysconfdir@ has spaces (if it has ', the user's hosed themselves anyway): sysconfdir='@sysconfdir@' localstatedir='@localstatedir@' But libvirtd.init.in does the same, so at this point, any cleanups for minor issues like that can be done across both scripts at once, as a separate patch.
+ +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 -le 0 ]; then + echo $"Shutdown action requested but SHUTDOWN_TIMEOUT was not set" + RETVAL=6 + return + fi + fi + + : >$LISTFILE + for uri in $URIS; do + echo -n $"Running guests on $uri URI: " + list=$(list_guests $uri) + if [ $? -eq 0 ]; then + empty=true + for uuid in $list; do + $empty || printf ", " + echo -n $(guest_name $uri $uuid) + empty=false + done + if $empty; then + echo $"no running guests." + else + echo + echo $uri $list >>$LISTFILE + fi + fi + done + + while read uri list; do + if $suspending; then + echo $"Suspending guests on $uri URI..." + else + echo $"Shutting down guests on $uri URI..." + fi + + for guest in $list; do + if $suspending; then + suspend_guest $uri $guest + else + shutdown_guest $uri $guest + fi + done + done <$LISTFILE +}
This works as is, so need to change it. But I would have done something along these lines: how=suspend_guest if [ "x$ON_SHUTDOWN" = xshutdown ]; then how=shutdown_guest fi ... for guest in $list; do $how $uri $guest done to cut down on some of the logic.
+ shutdown) + ON_SHUTDOWN=shutdown + stop + ;;
If someone calls 'service libvirt-guests shutdown', but...
+# action taken on host shutdown +# - suspend all running guests are suspended using virsh managedsave +# - shutdown all running guests are asked to shutdown. Please be careful with +# this settings since there is no way to distinguish between a +# guest which is stuck or ignores shutdown requests and a guest +# which just needs a long time to shutdown. When setting +# ON_SHUTDOWN=shutdown, you must also set SHUTDOWN_TIMEOUT to a +# value suitable for your guests. +#ON_SHUTDOWN=suspend + +# number of seconds we're willing to wait for a guest to shut down +#SHUTDOWN_TIMEOUT=0
...left their config with the defaults, then the shutdown will fail because SHUTDOWN_TIMEOUT is still 0. I guess that makes sense, though. Are we sure that init scripts called during '/sbin/shutdown' are normally called with 'stop' rather than 'shutdown' argument? Looks nicer! ACK. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

+ while read uri list; do + if $suspending; then + echo $"Suspending guests on $uri URI..." + else + echo $"Shutting down guests on $uri URI..." + fi + + for guest in $list; do + if $suspending; then + suspend_guest $uri $guest + else + shutdown_guest $uri $guest + fi + done + done <$LISTFILE +}
This works as is, so need to change it. Nice typo :-P
+ shutdown) + ON_SHUTDOWN=shutdown + stop + ;;
If someone calls 'service libvirt-guests shutdown', but...
+# action taken on host shutdown +# - suspend all running guests are suspended using virsh managedsave +# - shutdown all running guests are asked to shutdown. Please be careful with +# this settings since there is no way to distinguish between a +# guest which is stuck or ignores shutdown requests and a guest +# which just needs a long time to shutdown. When setting +# ON_SHUTDOWN=shutdown, you must also set SHUTDOWN_TIMEOUT to a +# value suitable for your guests. +#ON_SHUTDOWN=suspend + +# number of seconds we're willing to wait for a guest to shut down +#SHUTDOWN_TIMEOUT=0
...left their config with the defaults, then the shutdown will fail because SHUTDOWN_TIMEOUT is still 0. I guess that makes sense, though.
Yeah, that's the intent. We want to force users to set their own shutdown timeout if they want to use this functionality.
Are we sure that init scripts called during '/sbin/shutdown' are normally called with 'stop' rather than 'shutdown' argument?
Hmm, I don't think anything calls init scripts with 'shutdown' argument, but it might probably be better to rename as 'guestshutdown' to match 'gueststatus' and to avoid any confusion.
Looks nicer! ACK.
Thanks for the review. Jirka

Are we sure that init scripts called during '/sbin/shutdown' are normally called with 'stop' rather than 'shutdown' argument?
Hmm, I don't think anything calls init scripts with 'shutdown' argument, but it might probably be better to rename as 'guestshutdown' to match 'gueststatus' and to avoid any confusion.
As discussed off-list, LSB doesn't mention 'shutdown' command for init scripts so it can be used freely.
Looks nicer! ACK.
I added '\' for line continuations in chkconfig description (inspired by Cole's change to libvirtd.init.in) and pushed. Jirka
participants (2)
-
Eric Blake
-
Jiri Denemark