[libvirt] [PATCH 0/3] Cleanly save session VMs on logout/shutdown

Atm, when you exit the sessin (via logout or shutdown for instance) we just leave the qemu:///session handling libvirtd process around, including any running VMs. This is not particularly nice, as there is no way to know that the VMs are running, and you risk data loss if you shutdown while the VMs are running. This patch series adds a babysitter to the session that will track session lifetime and shutdown events and save running VMs as needed. Outstanding questions: Can this code be inside libvirtd itself? I.E. is it possible to do VM management calls like virDomainManagedSave() from inside libvirtd itself. If not, who will launch the babysitter? Alexander Larsson (3): Fix typo in HAVE_DBUS automake conditional virdbus: Add virDBusGetSessionBus helper Add new libvirt-babysitter tool configure.ac | 2 +- src/util/virdbus.c | 84 ++++++++++---- src/util/virdbus.h | 1 + tools/Makefile.am | 21 ++++ tools/libvirt-babysitter.c | 276 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 363 insertions(+), 21 deletions(-) create mode 100644 tools/libvirt-babysitter.c -- 1.7.12.1

The variable that is set in the script is with_dbus, not have_dbus. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 6d50985..767e06c 100644 --- a/configure.ac +++ b/configure.ac @@ -1251,7 +1251,7 @@ if test "$with_dbus" = "yes" ; then LIBS="$save_LIBS" CFLAGS="$save_CFLAGS" fi -AM_CONDITIONAL([HAVE_DBUS], [test "$have_dbus" = "yes"]) +AM_CONDITIONAL([HAVE_DBUS], [test "$with_dbus" = "yes"]) dnl PolicyKit library -- 1.7.12.1

On Mon, Oct 8, 2012 at 9:57 AM, Alexander Larsson <alexl@redhat.com> wrote:
The variable that is set in the script is with_dbus, not have_dbus. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac index 6d50985..767e06c 100644 --- a/configure.ac +++ b/configure.ac @@ -1251,7 +1251,7 @@ if test "$with_dbus" = "yes" ; then LIBS="$save_LIBS" CFLAGS="$save_CFLAGS" fi -AM_CONDITIONAL([HAVE_DBUS], [test "$have_dbus" = "yes"]) +AM_CONDITIONAL([HAVE_DBUS], [test "$with_dbus" = "yes"])
dnl PolicyKit library --
ACK. Good find. -- Doug Goldstein

On 10/08/2012 09:46 AM, Doug Goldstein wrote:
On Mon, Oct 8, 2012 at 9:57 AM, Alexander Larsson <alexl@redhat.com> wrote:
The variable that is set in the script is with_dbus, not have_dbus. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac index 6d50985..767e06c 100644 --- a/configure.ac +++ b/configure.ac @@ -1251,7 +1251,7 @@ if test "$with_dbus" = "yes" ; then LIBS="$save_LIBS" CFLAGS="$save_CFLAGS" fi -AM_CONDITIONAL([HAVE_DBUS], [test "$have_dbus" = "yes"]) +AM_CONDITIONAL([HAVE_DBUS], [test "$with_dbus" = "yes"])
dnl PolicyKit library --
ACK. Good find.
I've pushed this one, while we wait for more review on the rest of the series. I also added Alexander to AUTHORS; let me know if you prefer any alternate spelling to name or preferred email. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

This splits out some common code from virDBusGetSystemBus and uses it to implement a new virDBusGetSessionBus helper. --- src/util/virdbus.c | 84 +++++++++++++++++++++++++++++++++++++++++------------- src/util/virdbus.h | 1 + 2 files changed, 65 insertions(+), 20 deletions(-) diff --git a/src/util/virdbus.c b/src/util/virdbus.c index 4acce12..2dc7265 100644 --- a/src/util/virdbus.c +++ b/src/util/virdbus.c @@ -32,40 +32,49 @@ #ifdef HAVE_DBUS static DBusConnection *systembus = NULL; -static virOnceControl once = VIR_ONCE_CONTROL_INITIALIZER; -static DBusError dbuserr; +static DBusConnection *sessionbus = NULL; +static virOnceControl systemonce = VIR_ONCE_CONTROL_INITIALIZER; +static virOnceControl sessiononce = VIR_ONCE_CONTROL_INITIALIZER; +static DBusError systemdbuserr; +static DBusError sessiondbuserr; static dbus_bool_t virDBusAddWatch(DBusWatch *watch, void *data); static void virDBusRemoveWatch(DBusWatch *watch, void *data); static void virDBusToggleWatch(DBusWatch *watch, void *data); -static void virDBusSystemBusInit(void) +static DBusConnection *virDBusBusInit(DBusBusType type, DBusError *dbuserr) { + DBusConnection *bus; + /* Allocate and initialize a new HAL context */ dbus_connection_set_change_sigpipe(FALSE); dbus_threads_init_default(); - dbus_error_init(&dbuserr); - if (!(systembus = dbus_bus_get(DBUS_BUS_SYSTEM, &dbuserr))) - return; + dbus_error_init(dbuserr); + if (!(bus = dbus_bus_get(type, dbuserr))) + return NULL; - dbus_connection_set_exit_on_disconnect(systembus, FALSE); + dbus_connection_set_exit_on_disconnect(bus, FALSE); /* Register dbus watch callbacks */ - if (!dbus_connection_set_watch_functions(systembus, + if (!dbus_connection_set_watch_functions(bus, virDBusAddWatch, virDBusRemoveWatch, virDBusToggleWatch, - NULL, NULL)) { - systembus = NULL; - return; + bus, NULL)) { + return NULL; } + return bus; } +static void virDBusSystemBusInit(void) +{ + systembus = virDBusBusInit (DBUS_BUS_SYSTEM, &systemdbuserr); +} DBusConnection *virDBusGetSystemBus(void) { - if (virOnce(&once, virDBusSystemBusInit) < 0) { + if (virOnce(&systemonce, virDBusSystemBusInit) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Unable to run one time DBus initializer")); return NULL; @@ -74,7 +83,7 @@ DBusConnection *virDBusGetSystemBus(void) if (!systembus) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Unable to get DBus system bus connection: %s"), - dbuserr.message ? dbuserr.message : "watch setup failed"); + systemdbuserr.message ? systemdbuserr.message : "watch setup failed"); return NULL; } @@ -82,13 +91,45 @@ DBusConnection *virDBusGetSystemBus(void) } +static void virDBusSessionBusInit(void) +{ + sessionbus = virDBusBusInit (DBUS_BUS_SESSION, &sessiondbuserr); +} + +DBusConnection *virDBusGetSessionBus(void) +{ + if (virOnce(&sessiononce, virDBusSessionBusInit) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to run one time DBus initializer")); + return NULL; + } + + if (!sessionbus) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unable to get DBus session bus connection: %s"), + sessiondbuserr.message ? sessiondbuserr.message : "watch setup failed"); + return NULL; + } + + return sessionbus; +} + +struct virDBusWatch +{ + int watch; + DBusConnection *bus; +}; + static void virDBusWatchCallback(int fdatch ATTRIBUTE_UNUSED, int fd ATTRIBUTE_UNUSED, int events, void *opaque) { DBusWatch *watch = opaque; + struct virDBusWatch *info; int dbus_flags = 0; + info = dbus_watch_get_data(watch); + if (events & VIR_EVENT_HANDLE_READABLE) dbus_flags |= DBUS_WATCH_READABLE; if (events & VIR_EVENT_HANDLE_WRITABLE) @@ -100,7 +141,7 @@ static void virDBusWatchCallback(int fdatch ATTRIBUTE_UNUSED, (void)dbus_watch_handle(watch, dbus_flags); - while (dbus_connection_dispatch(systembus) == DBUS_DISPATCH_DATA_REMAINS) + while (dbus_connection_dispatch(info->bus) == DBUS_DISPATCH_DATA_REMAINS) /* keep dispatching while data remains */; } @@ -120,18 +161,13 @@ static int virDBusTranslateWatchFlags(int dbus_flags) } -struct virDBusWatch -{ - int watch; -}; - static void virDBusWatchFree(void *data) { struct virDBusWatch *info = data; VIR_FREE(info); } static dbus_bool_t virDBusAddWatch(DBusWatch *watch, - void *data ATTRIBUTE_UNUSED) + void *data) { int flags = 0; int fd; @@ -148,6 +184,7 @@ static dbus_bool_t virDBusAddWatch(DBusWatch *watch, # else fd = dbus_watch_get_fd(watch); # endif + info->bus = (DBusConnection *)data; info->watch = virEventAddHandle(fd, flags, virDBusWatchCallback, watch, NULL); @@ -194,4 +231,11 @@ DBusConnection *virDBusGetSystemBus(void) return NULL; } +DBusConnection *virDBusGetSessionBus(void) +{ + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("DBus support not compiled into this binary")); + return NULL; +} + #endif /* ! HAVE_DBUS */ diff --git a/src/util/virdbus.h b/src/util/virdbus.h index 27dca00..e443fbe 100644 --- a/src/util/virdbus.h +++ b/src/util/virdbus.h @@ -30,5 +30,6 @@ # include "internal.h" DBusConnection *virDBusGetSystemBus(void); +DBusConnection *virDBusGetSessionBus(void); #endif /* __VIR_DBUS_H__ */ -- 1.7.12.1

On Mon, Oct 08, 2012 at 04:57:48PM +0200, Alexander Larsson wrote:
This splits out some common code from virDBusGetSystemBus and uses it to implement a new virDBusGetSessionBus helper. --- src/util/virdbus.c | 84 +++++++++++++++++++++++++++++++++++++++++------------- src/util/virdbus.h | 1 + 2 files changed, 65 insertions(+), 20 deletions(-)
diff --git a/src/util/virdbus.c b/src/util/virdbus.c index 4acce12..2dc7265 100644 --- a/src/util/virdbus.c +++ b/src/util/virdbus.c @@ -32,40 +32,49 @@ #ifdef HAVE_DBUS
static DBusConnection *systembus = NULL; -static virOnceControl once = VIR_ONCE_CONTROL_INITIALIZER; -static DBusError dbuserr; +static DBusConnection *sessionbus = NULL; +static virOnceControl systemonce = VIR_ONCE_CONTROL_INITIALIZER; +static virOnceControl sessiononce = VIR_ONCE_CONTROL_INITIALIZER; +static DBusError systemdbuserr; +static DBusError sessiondbuserr;
static dbus_bool_t virDBusAddWatch(DBusWatch *watch, void *data); static void virDBusRemoveWatch(DBusWatch *watch, void *data); static void virDBusToggleWatch(DBusWatch *watch, void *data);
-static void virDBusSystemBusInit(void) +static DBusConnection *virDBusBusInit(DBusBusType type, DBusError *dbuserr) { + DBusConnection *bus; + /* Allocate and initialize a new HAL context */ dbus_connection_set_change_sigpipe(FALSE); dbus_threads_init_default();
- dbus_error_init(&dbuserr); - if (!(systembus = dbus_bus_get(DBUS_BUS_SYSTEM, &dbuserr))) - return; + dbus_error_init(dbuserr); + if (!(bus = dbus_bus_get(type, dbuserr))) + return NULL;
- dbus_connection_set_exit_on_disconnect(systembus, FALSE); + dbus_connection_set_exit_on_disconnect(bus, FALSE);
/* Register dbus watch callbacks */ - if (!dbus_connection_set_watch_functions(systembus, + if (!dbus_connection_set_watch_functions(bus, virDBusAddWatch, virDBusRemoveWatch, virDBusToggleWatch, - NULL, NULL)) { - systembus = NULL; - return; + bus, NULL)) { + return NULL; } + return bus; }
+static void virDBusSystemBusInit(void) +{ + systembus = virDBusBusInit (DBUS_BUS_SYSTEM, &systemdbuserr); +}
DBusConnection *virDBusGetSystemBus(void) { - if (virOnce(&once, virDBusSystemBusInit) < 0) { + if (virOnce(&systemonce, virDBusSystemBusInit) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Unable to run one time DBus initializer")); return NULL; @@ -74,7 +83,7 @@ DBusConnection *virDBusGetSystemBus(void) if (!systembus) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Unable to get DBus system bus connection: %s"), - dbuserr.message ? dbuserr.message : "watch setup failed"); + systemdbuserr.message ? systemdbuserr.message : "watch setup failed"); return NULL; }
@@ -82,13 +91,45 @@ DBusConnection *virDBusGetSystemBus(void) }
+static void virDBusSessionBusInit(void) +{ + sessionbus = virDBusBusInit (DBUS_BUS_SESSION, &sessiondbuserr); +} + +DBusConnection *virDBusGetSessionBus(void) +{ + if (virOnce(&sessiononce, virDBusSessionBusInit) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to run one time DBus initializer")); + return NULL; + } + + if (!sessionbus) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unable to get DBus session bus connection: %s"), + sessiondbuserr.message ? sessiondbuserr.message : "watch setup failed"); + return NULL; + } + + return sessionbus; +} + +struct virDBusWatch +{ + int watch; + DBusConnection *bus; +}; + static void virDBusWatchCallback(int fdatch ATTRIBUTE_UNUSED, int fd ATTRIBUTE_UNUSED, int events, void *opaque) { DBusWatch *watch = opaque; + struct virDBusWatch *info; int dbus_flags = 0;
+ info = dbus_watch_get_data(watch); + if (events & VIR_EVENT_HANDLE_READABLE) dbus_flags |= DBUS_WATCH_READABLE; if (events & VIR_EVENT_HANDLE_WRITABLE) @@ -100,7 +141,7 @@ static void virDBusWatchCallback(int fdatch ATTRIBUTE_UNUSED,
(void)dbus_watch_handle(watch, dbus_flags);
- while (dbus_connection_dispatch(systembus) == DBUS_DISPATCH_DATA_REMAINS) + while (dbus_connection_dispatch(info->bus) == DBUS_DISPATCH_DATA_REMAINS) /* keep dispatching while data remains */; }
@@ -120,18 +161,13 @@ static int virDBusTranslateWatchFlags(int dbus_flags) }
-struct virDBusWatch -{ - int watch; -}; - static void virDBusWatchFree(void *data) { struct virDBusWatch *info = data; VIR_FREE(info); }
static dbus_bool_t virDBusAddWatch(DBusWatch *watch, - void *data ATTRIBUTE_UNUSED) + void *data) { int flags = 0; int fd; @@ -148,6 +184,7 @@ static dbus_bool_t virDBusAddWatch(DBusWatch *watch, # else fd = dbus_watch_get_fd(watch); # endif + info->bus = (DBusConnection *)data; info->watch = virEventAddHandle(fd, flags, virDBusWatchCallback, watch, NULL); @@ -194,4 +231,11 @@ DBusConnection *virDBusGetSystemBus(void) return NULL; }
+DBusConnection *virDBusGetSessionBus(void) +{ + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("DBus support not compiled into this binary")); + return NULL; +} + #endif /* ! HAVE_DBUS */ diff --git a/src/util/virdbus.h b/src/util/virdbus.h index 27dca00..e443fbe 100644 --- a/src/util/virdbus.h +++ b/src/util/virdbus.h @@ -30,5 +30,6 @@ # include "internal.h"
DBusConnection *virDBusGetSystemBus(void); +DBusConnection *virDBusGetSessionBus(void);
#endif /* __VIR_DBUS_H__ */
ACK, but we need to add a line to libvirt_private.syms when pushing Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

This helper registers with the session bus and libvirt and ensures that we properly save the state when the session exits. It does this in two ways: 1) Whenever the session dbus connection is disconnected (typically due to a logout) we save all domains in the session libvirtd. 2) Whenever there is a active domain in the session libvirtd we take a shutdown inhibition lock[1] which means that systemd will delay shutdown until we saved any active VMs. We then save the VM when we get a PrepareForShutdown event (or when the session dies as in 1). [1] http://www.freedesktop.org/wiki/Software/systemd/inhibit --- tools/Makefile.am | 21 ++++ tools/libvirt-babysitter.c | 276 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 297 insertions(+) create mode 100644 tools/libvirt-babysitter.c diff --git a/tools/Makefile.am b/tools/Makefile.am index 0d7822d..eb3e0be 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -42,6 +42,10 @@ DISTCLEANFILES = bin_SCRIPTS = virt-xml-validate virt-pki-validate bin_PROGRAMS = virsh virt-host-validate +if HAVE_DBUS +bin_PROGRAMS += libvirt-babysitter +endif + if HAVE_SANLOCK sbin_SCRIPTS = virt-sanlock-cleanup DISTCLEANFILES += virt-sanlock-cleanup @@ -133,6 +137,23 @@ virsh_CFLAGS = \ $(COVERAGE_CFLAGS) \ $(LIBXML_CFLAGS) \ $(READLINE_CFLAGS) + +libvirt_babysitter_SOURCES = \ + libvirt-babysitter.c \ + $(NULL) +libvirt_babysitter_LDFLAGS = $(WARN_LDFLAGS) $(COVERAGE_LDFLAGS) +libvirt_babysitter_LDADD = \ + $(STATIC_BINARIES) \ + $(WARN_CFLAGS) \ + ../src/libvirt.la \ + ../src/libvirt_util.la \ + $(NULL) +libvirt_babysitter_CFLAGS = \ + $(WARN_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(NULL) + BUILT_SOURCES = if WITH_WIN_ICON diff --git a/tools/libvirt-babysitter.c b/tools/libvirt-babysitter.c new file mode 100644 index 0000000..8eec2eb --- /dev/null +++ b/tools/libvirt-babysitter.c @@ -0,0 +1,276 @@ +#include <config.h> + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdarg.h> +#include <errno.h> +#include <dbus/dbus.h> + +#define VIR_FROM_THIS VIR_FROM_NONE + +#include "internal.h" +#include "util.h" +#include "virdbus.h" +#include "libvirt/libvirt.h" +#include "virterror_internal.h" +#include "virfile.h" +#include "memory.h" + +static virConnectPtr libvirtConn; +static DBusConnection *sessionBus; +static DBusConnection *systemBus; +static int numActiveDomains; +static bool hasInhibit; +static int inhibitFd = -1; + +static void die(const char *format, ...) ATTRIBUTE_FMT_PRINTF(1, 2); + +static void die(const char *format, ...) +{ + va_list args; + + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + exit(EXIT_FAILURE); +} + +static void libvirtErrorFunc(void *opaque ATTRIBUTE_UNUSED, + virErrorPtr err) +{ + fprintf(stderr, "libvirt Error: %s", err->message); +} + +/* As per: http://www.freedesktop.org/wiki/Software/systemd/inhibit */ +static int callInhibit(const char *what, + const char *who, + const char *why, + const char *mode) +{ + DBusMessage *message, *reply; + int fd; + + if (systemBus == NULL) + return -1; + + message = dbus_message_new_method_call ("org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "Inhibit"); + if (message == NULL) + return -1; + + dbus_message_append_args (message, + DBUS_TYPE_STRING, &what, + DBUS_TYPE_STRING, &who, + DBUS_TYPE_STRING, &why, + DBUS_TYPE_STRING, &mode, + DBUS_TYPE_INVALID); + + reply = dbus_connection_send_with_reply_and_block (systemBus, message, + 25*1000, NULL); + dbus_message_unref (message); + + if (reply == NULL) + return -1; + + if (!dbus_message_get_args (reply, NULL, + DBUS_TYPE_UNIX_FD, &fd, + DBUS_TYPE_INVALID)) { + fd = -1; + } + dbus_message_unref (reply); + + return fd; +} + + +static void numActiveDomainsChanged(void) +{ + if (numActiveDomains > 0 && !hasInhibit) { + inhibitFd = callInhibit("shutdown", _("Libvirt"), _("Virtual machines need to be saved"), "delay"); + hasInhibit = true; + } else if (numActiveDomains == 0 && hasInhibit) { + if (inhibitFd != -1) { + if (VIR_CLOSE (inhibitFd) < 0) { + virReportSystemError(errno, "%s", _("failed to close file")); + } + inhibitFd = -1; + } + hasInhibit = false; + } +} + +static void saveAllDomains(virConnectPtr conn) +{ + virDomainPtr dom; + int numDomains, i; + int *domains; + int state; + unsigned int *flags; + + numDomains = virConnectNumOfDomains(conn); + if (numDomains < 0) + return; + + if (VIR_ALLOC_N(domains, numDomains) < 0) { + virReportOOMError(); + return; + } + + if (VIR_ALLOC_N(flags, numDomains) < 0) { + VIR_FREE (domains); + virReportOOMError(); + return; + } + + numDomains = virConnectListDomains(conn, domains, numDomains); + + /* First we pause all VMs to make them stop dirtying + pages, etc. We remember if any VMs were paused so + we can restore that on resume. */ + for (i = 0 ; i < numDomains ; i++) { + flags[i] = VIR_DOMAIN_SAVE_RUNNING; + dom = virDomainLookupByID(conn, domains[i]); + if (dom != NULL) { + if (virDomainGetState (dom, &state, NULL, 0) == 0) { + if (state == VIR_DOMAIN_PAUSED) { + flags[i] = VIR_DOMAIN_SAVE_PAUSED; + } + } + virDomainSuspend (dom); + virDomainFree (dom); + } + } + + /* Then we save the VMs to disk */ + for (i = 0 ; i < numDomains ; i++) { + dom = virDomainLookupByID(conn, domains[i]); + if (dom != NULL) { + virDomainManagedSave (dom, flags[i]); + virDomainFree (dom); + } + } + + VIR_FREE (domains); + VIR_FREE (flags); + + if (inhibitFd != -1) { + if (VIR_CLOSE (inhibitFd) < 0) { + virReportSystemError(errno, "%s", _("failed to close file")); + } + inhibitFd = -1; + } +} + +static int lifecycleEventCallback(virConnectPtr conn ATTRIBUTE_UNUSED, + virDomainPtr dom ATTRIBUTE_UNUSED, + int event, + int detail ATTRIBUTE_UNUSED, + void *opaque ATTRIBUTE_UNUSED) +{ + if (event == VIR_DOMAIN_EVENT_STOPPED) + numActiveDomains--; + else if (event == VIR_DOMAIN_EVENT_STARTED) + numActiveDomains++; + + numActiveDomainsChanged(); + + return 0; +} + +static void initLibvirt(void) +{ + virSetErrorFunc(NULL, libvirtErrorFunc); + if (virInitialize() < 0) + die ("Can't init libvirt"); + + if (virEventRegisterDefaultImpl() < 0) + die ("Can't register event implementation"); + + libvirtConn = virConnectOpen("qemu+unix:///session"); + if (libvirtConn == NULL) + die ("Can't connect to session libvirtd\n"); +} + +static void initTrackActiveDomains(void) { + numActiveDomains = virConnectNumOfDomains(libvirtConn); + virConnectDomainEventRegisterAny(libvirtConn, + NULL, + VIR_DOMAIN_EVENT_ID_LIFECYCLE, + VIR_DOMAIN_EVENT_CALLBACK (lifecycleEventCallback), + NULL, NULL); + numActiveDomainsChanged(); +} + +static DBusHandlerResult handleSessionMessageFunc(DBusConnection *connection ATTRIBUTE_UNUSED, + DBusMessage *message, + void *userData ATTRIBUTE_UNUSED) +{ + if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) { + saveAllDomains (libvirtConn); + exit (EXIT_SUCCESS); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult handleSystemMessageFunc(DBusConnection *connection ATTRIBUTE_UNUSED, + DBusMessage *message, + void *userData ATTRIBUTE_UNUSED) +{ + if (dbus_message_is_signal(message, "org.freedesktop.login1.Manager", "PrepareForShutdown")) { + saveAllDomains (libvirtConn); + exit (EXIT_SUCCESS); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +int main (int argc ATTRIBUTE_UNUSED, char *argv[] ATTRIBUTE_UNUSED) +{ + DBusError error; + int res; + + initLibvirt (); + + sessionBus = virDBusGetSessionBus (); + if (sessionBus == NULL) + die ("Can't connect to session bus: %s\n", error.message); + + /* Register a unique name so that we can't avoid multiple + babysitters */ + dbus_error_init(&error); + res = dbus_bus_request_name(sessionBus, "org.libvirt.BabySitter", + DBUS_NAME_FLAG_DO_NOT_QUEUE, + &error); + + /* If we didn't get the name, someone else is handling this, + which is fine. */ + if (res != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) + return 0; + + dbus_connection_add_filter(sessionBus, + handleSessionMessageFunc, NULL, NULL); + + systemBus = virDBusGetSystemBus (); + /* Not fatal if we don't get this */ + + if (systemBus != NULL) { + dbus_connection_add_filter(systemBus, + handleSystemMessageFunc, NULL, NULL); + dbus_bus_add_match(systemBus, + "type='signal',sender='org.freedesktop.login1', interface='org.freedesktop.login1.Manager'", + NULL); + } + + initTrackActiveDomains (); + + while (true) { + if (virEventRunDefaultImpl() < 0) + die ("main loop exited"); + } + + return EXIT_SUCCESS; +} -- 1.7.12.1

On Mon, Oct 08, 2012 at 04:57:49PM +0200, Alexander Larsson wrote:
This helper registers with the session bus and libvirt and ensures that we properly save the state when the session exits. It does this in two ways:
1) Whenever the session dbus connection is disconnected (typically due to a logout) we save all domains in the session libvirtd.
2) Whenever there is a active domain in the session libvirtd we take a shutdown inhibition lock[1] which means that systemd will delay shutdown until we saved any active VMs. We then save the VM when we get a PrepareForShutdown event (or when the session dies as in 1).
[1] http://www.freedesktop.org/wiki/Software/systemd/inhibit
In general I think this is a neat idea. We currently have a nasty shell script that we trigger on host shutdown to save/migrate/etc VMs. Although it is not as featureful, I like that this code is using our APIs instead of shell code. My primary feedback would be that this could actually be run inside libvirtd itself. In the libvirtd daemonRunStateInit method you can do the initial dbus signal setup and libvirt lifecycle event registration just after virStateInitialize completes. The only slightly different bit is that you will need to run the saveAllDomains() method in a separate thread so that you don't block the main libvirt event loop which will be required during the VM shutdown process. You could also then directly tell libvirtd to exit.
+static void saveAllDomains(virConnectPtr conn) +{ + virDomainPtr dom; + int numDomains, i; + int *domains; + int state; + unsigned int *flags; + + numDomains = virConnectNumOfDomains(conn); + if (numDomains < 0) + return; + + if (VIR_ALLOC_N(domains, numDomains) < 0) { + virReportOOMError(); + return; + } + + if (VIR_ALLOC_N(flags, numDomains) < 0) { + VIR_FREE (domains); + virReportOOMError(); + return; + } + + numDomains = virConnectListDomains(conn, domains, numDomains);
As a slight optimization you can use new virConnectListAllDomains which returns you the full array of virDomainPtr objects straight away. Oh, and obviously for now we'd only want to have this auto-shutdown code be done for the unprivileged session libvirtd. We'll punt on handling the systemd libvirtd for another day, when someone wants to attack the shell script Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On tis, 2012-10-09 at 12:14 +0100, Daniel P. Berrange wrote:
On Mon, Oct 08, 2012 at 04:57:49PM +0200, Alexander Larsson wrote:
This helper registers with the session bus and libvirt and ensures that we properly save the state when the session exits. It does this in two ways:
1) Whenever the session dbus connection is disconnected (typically due to a logout) we save all domains in the session libvirtd.
2) Whenever there is a active domain in the session libvirtd we take a shutdown inhibition lock[1] which means that systemd will delay shutdown until we saved any active VMs. We then save the VM when we get a PrepareForShutdown event (or when the session dies as in 1).
[1] http://www.freedesktop.org/wiki/Software/systemd/inhibit
In general I think this is a neat idea. We currently have a nasty shell script that we trigger on host shutdown to save/migrate/etc VMs. Although it is not as featureful, I like that this code is using our APIs instead of shell code.
My primary feedback would be that this could actually be run inside libvirtd itself.
Ok, I'll try that out then. Btw, how do i tell libvirtd to exit? Just call exit(0)?

On Tue, Oct 09, 2012 at 01:18:56PM +0200, Alexander Larsson wrote:
On tis, 2012-10-09 at 12:14 +0100, Daniel P. Berrange wrote:
On Mon, Oct 08, 2012 at 04:57:49PM +0200, Alexander Larsson wrote:
This helper registers with the session bus and libvirt and ensures that we properly save the state when the session exits. It does this in two ways:
1) Whenever the session dbus connection is disconnected (typically due to a logout) we save all domains in the session libvirtd.
2) Whenever there is a active domain in the session libvirtd we take a shutdown inhibition lock[1] which means that systemd will delay shutdown until we saved any active VMs. We then save the VM when we get a PrepareForShutdown event (or when the session dies as in 1).
[1] http://www.freedesktop.org/wiki/Software/systemd/inhibit
In general I think this is a neat idea. We currently have a nasty shell script that we trigger on host shutdown to save/migrate/etc VMs. Although it is not as featureful, I like that this code is using our APIs instead of shell code.
My primary feedback would be that this could actually be run inside libvirtd itself.
Ok, I'll try that out then.
Btw, how do i tell libvirtd to exit? Just call exit(0)?
The best way is to call virNetServerQuit() on the virNetServerPtr object since this allows it todo graceful cleanup Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|
participants (4)
-
Alexander Larsson
-
Daniel P. Berrange
-
Doug Goldstein
-
Eric Blake