[libvirt] [PATCH 3/3] Add new libvirt-babysitter tool

Alexander Larsson alexl at redhat.com
Mon Oct 8 14:57:49 UTC 2012


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




More information about the libvir-list mailing list