[libvirt] [PATCH v4 3/4] Implement the core API to suspend/resume the host

Srivatsa S. Bhat srivatsa.bhat at linux.vnet.ibm.com
Mon Nov 28 12:03:22 UTC 2011


Add the core functions that implement the functionality of the API.
Suspend is done by using an asynchronous mechanism so that we can return
the status to the caller before the host gets suspended. This asynchronous
operation is achieved by suspending the host in a separate thread of
execution. However, returning the status to the caller is only best-effort,
but not guaranteed.

To resume the host, an RTC alarm is set up (based on how long we want to
suspend) before suspending the host. When this alarm fires, the host gets
woken up.

Signed-off-by: Srivatsa S. Bhat <srivatsa.bhat at linux.vnet.ibm.com>
---

 src/libvirt_private.syms |    7 +
 src/nodeinfo.c           |  245 ++++++++++++++++++++++++++++++++++++++++++++++
 src/nodeinfo.h           |    9 ++
 src/qemu/qemu_driver.c   |    4 +
 4 files changed, 265 insertions(+), 0 deletions(-)

diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 2cf50d3..5a83e62 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -803,6 +803,13 @@ nodeGetCellsFreeMemory;
 nodeGetFreeMemory;
 nodeGetInfo;
 nodeGetMemoryStats;
+nodeSuspend;
+nodeSuspendForDuration;
+setNodeWakeup;
+virSuspendCleanup;
+virSuspendInit;
+virSuspendLock;
+virSuspendUnlock;
 
 
 # nwfilter_conf.h
diff --git a/src/nodeinfo.c b/src/nodeinfo.c
index 6448b79..3f09a51 100644
--- a/src/nodeinfo.c
+++ b/src/nodeinfo.c
@@ -46,6 +46,9 @@
 #include "count-one-bits.h"
 #include "intprops.h"
 #include "virfile.h"
+#include "command.h"
+#include "threads.h"
+#include "datatypes.h"
 
 
 #define VIR_FROM_THIS VIR_FROM_NONE
@@ -65,6 +68,11 @@
 # define LINUX_NB_MEMORY_STATS_ALL 4
 # define LINUX_NB_MEMORY_STATS_CELL 2
 
+/* Bitmask to hold the Power Management features supported by the host,
+ * such as Suspend-to-RAM (S3), Suspend-to-Disk (S4), Hybrid-Suspend etc.
+ */
+static unsigned int hostPMFeatures;
+
 /* NB, this is not static as we need to call it from the testsuite */
 int linuxNodeInfoCPUPopulate(FILE *cpuinfo,
                              virNodeInfoPtr nodeinfo,
@@ -897,3 +905,240 @@ unsigned long long nodeGetFreeMemory(virConnectPtr conn ATTRIBUTE_UNUSED)
     return 0;
 }
 #endif
+
+
+static int initialized;
+virMutex virSuspendMutex;
+
+static void virSuspendLock(void)
+{
+    virMutexLock(&virSuspendMutex);
+}
+
+static void virSuspendUnlock(void)
+{
+    virMutexUnlock(&virSuspendMutex);
+}
+
+/**
+ * virSuspendInit:
+ *
+ * Get the system-wide sleep states supported by the host, such as
+ * Suspend-to-RAM (S3), Suspend-to-Disk (S4), or Hybrid-Suspend, so that
+ * a request to suspend/hibernate the host can be handled appropriately
+ * based on this information.
+ *
+ * Returns 0 if successful, and -1 in case of error.
+ */
+static int virSuspendInit(void)
+{
+
+    if (virMutexInit(&virSuspendMutex) < 0)
+        return -1;
+
+    /* Get the power management capabilities supported by the host.
+     * Ensure that this is done only once, by using the 'initialized'
+     * variable.
+     */
+    if (virGetPMCapabilities(&hostPMFeatures) < 0) {
+        VIR_ERROR(_("Failed to get host power management features"));
+        return -1;
+    }
+
+    return 0;
+}
+
+
+static bool aboutToSuspend;
+
+/**
+ * nodeSuspendForDuration:
+ * @conn: pointer to the hypervisor connection
+ * @state: the state to which the host must be suspended to -
+ *         VIR_NODE_S3             (Suspend-to-RAM),
+ *         VIR_NODE_S4             (Suspend-to-Disk),
+ *         VIR_NODE_HYBRID_SUSPEND (Hybrid-Suspend)
+ * @duration: the time duration in seconds, for which the host must be
+ *            suspended
+ * @flags: any flag values that might need to be passed; currently unused.
+ *
+ * Suspend the node (host machine) for the given duration of time in the
+ * specified state (such as S3, S4 or Hybrid-Suspend). Attempt to resume
+ * the node after the time duration is complete.
+ *
+ * First, an RTC alarm is set appropriately to wake up the node from its
+ * sleep state. Then the actual node suspend operation is carried out
+ * asynchronously in another thread, after a short time delay so as to
+ * give enough time for this function to return status to its caller
+ * through the connection. However this returning of status is best-effort
+ * only, and should generally happen due to the short delay but might be
+ * missed if the machine suspends first.
+ *
+ * Returns 0 in case the node is going to be suspended after a short delay,
+ * -1 if suspending the node is not supported, or if a previous suspend
+ * operation is still in progress.
+ */
+int nodeSuspendForDuration(virConnectPtr conn ATTRIBUTE_UNUSED,
+                           int state,
+                           unsigned long long duration,
+                           unsigned int flags ATTRIBUTE_UNUSED)
+{
+    static virThread thread;
+    char *cmdString = NULL;
+
+    if (!initialized) {
+        if (virSuspendInit() < 0)
+            return -1;
+        initialized = 1;
+    }
+
+    /*
+     * Ensure that we are the only ones trying to suspend.
+     * Fail if somebody has already initiated a suspend.
+     */
+    virSuspendLock();
+
+    if (aboutToSuspend) {
+        /* A suspend operation is already in progress */
+        virSuspendUnlock();
+        return -1;
+    } else
+        aboutToSuspend = true;
+
+    virSuspendUnlock();
+
+    /* Check if the host supports the requested suspend state */
+    switch (state) {
+    case VIR_NODE_S3:
+        if (hostPMFeatures & VIR_NODE_S3) {
+            cmdString = strdup("pm-suspend");
+            if (cmdString == NULL) {
+                virReportOOMError();
+                goto cleanup;
+            }
+            break;
+        }
+        goto cleanup;
+
+    case VIR_NODE_S4:
+        if (hostPMFeatures & VIR_NODE_S4) {
+            cmdString = strdup("pm-hibernate");
+            if (cmdString == NULL) {
+                virReportOOMError();
+                goto cleanup;
+            }
+            break;
+        }
+        goto cleanup;
+
+    case VIR_NODE_HYBRID_SUSPEND:
+        if (hostPMFeatures & VIR_NODE_HYBRID_SUSPEND) {
+            cmdString = strdup("pm-suspend-hybrid");
+            if (cmdString == NULL) {
+                virReportOOMError();
+                goto cleanup;
+            }
+            break;
+        }
+        goto cleanup;
+
+    default:
+        goto cleanup;
+    }
+
+    /* Just set the RTC alarm. Don't suspend yet. */
+    if (setNodeWakeup(duration) < 0) {
+        nodeReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                        _("Failed to set up the RTC alarm for node wakeup\n"));
+        goto cleanup;
+    }
+
+    if (virThreadCreate(&thread, false, nodeSuspend, (void *)cmdString) < 0) {
+        nodeReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                        _("Failed to create thread to suspend the host\n"));
+        goto cleanup;
+    }
+
+    return 0;
+
+cleanup:
+    VIR_FREE(cmdString);
+    return -1;
+}
+
+
+# define SUSPEND_DELAY 10 /* in seconds */
+
+/**
+ * setNodeWakeup:
+ * @alarmTime: time in seconds from now, at which the RTC alarm has to be set.
+ *
+ * Set up the RTC alarm to the specified time.
+ * Return 0 on success, -1 on failure.
+ */
+int setNodeWakeup(unsigned long long alarmTime)
+{
+    virCommandPtr setAlarmCmd;
+    int ret = -1;
+
+    if (alarmTime <= SUSPEND_DELAY)
+        return -1;
+
+    setAlarmCmd = virCommandNewArgList("rtcwake", "-m", "no", "-s", NULL);
+    virCommandAddArgFormat(setAlarmCmd, "%lld", alarmTime);
+
+    if (virCommandRun(setAlarmCmd, NULL) < 0) {
+        nodeReportError(VIR_ERR_INTERNAL_ERROR,
+                        "%s", _("Failed to set up the RTC alarm\n"));
+        goto cleanup;
+    }
+
+    ret = 0;
+
+cleanup:
+    virCommandFree(setAlarmCmd);
+    return ret;
+}
+
+
+/**
+ * nodeSuspend:
+ * @cmdString: pointer to the command string this thread has to execute.
+ *
+ * Actually perform the suspend operation by invoking the command.
+ * Give a short delay before executing the command so as to give a chance
+ * to virNodeSuspendForDuration() to return the status to the caller.
+ * If we don't give this delay, that function will not be able to return
+ * the status, since the suspend operation would have begun and hence no
+ * data can be sent through the connection to the caller. However, with
+ * this delay added, the status return is best-effort only.
+ */
+void nodeSuspend(void *cmdString)
+{
+    virCommandPtr suspendCmd = virCommandNew((char *)cmdString);
+
+    VIR_FREE(cmdString);
+
+    /* Delay for sometime so that the function nodeSuspendForDuration()
+     * can return the status to the caller.
+     */
+    sleep(SUSPEND_DELAY);
+    if (virCommandRun(suspendCmd, NULL) < 0) {
+        nodeReportError(VIR_ERR_INTERNAL_ERROR,
+                        "%s", _("Failed to suspend the node\n"));
+    }
+
+    virCommandFree(suspendCmd);
+
+    /* Now that we have resumed from suspend, reset 'aboutToSuspend' flag. */
+    virSuspendLock();
+    aboutToSuspend = false;
+    virSuspendUnlock();
+}
+
+void virSuspendCleanup(void)
+{
+    if (initialized)
+        virMutexDestroy(&virSuspendMutex);
+}
+
diff --git a/src/nodeinfo.h b/src/nodeinfo.h
index 4766152..8ec887d 100644
--- a/src/nodeinfo.h
+++ b/src/nodeinfo.h
@@ -46,4 +46,13 @@ int nodeGetCellsFreeMemory(virConnectPtr conn,
                            int maxCells);
 unsigned long long nodeGetFreeMemory(virConnectPtr conn);
 
+int nodeSuspendForDuration(virConnectPtr conn,
+                           int state,
+                           unsigned long long duration,
+                           unsigned int flags);
+
+int setNodeWakeup(unsigned long long alarmTime);
+void nodeSuspend(void *cmdString);
+void virSuspendCleanup(void);
+
 #endif /* __VIR_NODEINFO_H__*/
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index fe2ab85..9cee95e 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -777,6 +777,9 @@ qemudShutdown(void) {
 
     virSysinfoDefFree(qemu_driver->hostsysinfo);
 
+    /* Cleanup the structures initialized for suspending the host */
+    virSuspendCleanup();
+
     qemuProcessAutoDestroyShutdown(qemu_driver);
 
     VIR_FREE(qemu_driver->configDir);
@@ -10918,6 +10921,7 @@ static virDriver qemuDriver = {
     .domainGetBlockJobInfo = qemuDomainGetBlockJobInfo, /* 0.9.4 */
     .domainBlockJobSetSpeed = qemuDomainBlockJobSetSpeed, /* 0.9.4 */
     .domainBlockPull = qemuDomainBlockPull, /* 0.9.4 */
+    .nodeSuspendForDuration = nodeSuspendForDuration, /* 0.9.8 */
 };
 
 




More information about the libvir-list mailing list