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 successfully before the host gets suspended. This
asynchronous operation is achieved by suspending the host in a separate
thread of execution.
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(a)linux.vnet.ibm.com>
---
include/libvirt/libvirt.h.in | 5 +
src/libvirt_private.syms | 7 +
src/nodeinfo.c | 220 ++++++++++++++++++++++++++++++++++++++++++
src/nodeinfo.h | 14 +++
src/qemu/qemu_driver.c | 5 +
src/util/threads-pthread.c | 17 +++
src/util/threads.h | 1
7 files changed, 268 insertions(+), 1 deletions(-)
diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in
index aa320b6..25f1c9b 100644
--- a/include/libvirt/libvirt.h.in
+++ b/include/libvirt/libvirt.h.in
@@ -3357,6 +3357,11 @@ typedef struct _virTypedParameter virMemoryParameter;
*/
typedef virMemoryParameter *virMemoryParameterPtr;
+typedef enum {
+ VIR_S3 = 1, /* Suspend-to-RAM */
+ VIR_S4 = 2 /* Suspend-to-disk */
+} virSuspendState;
+
#ifdef __cplusplus
}
#endif
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 6a1562e..2fa84e0 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -844,6 +844,13 @@ nodeGetCellsFreeMemory;
nodeGetFreeMemory;
nodeGetInfo;
nodeGetMemoryStats;
+virSuspendLock;
+virSuspendUnlock;
+virSuspendInit;
+nodeSuspendForDuration;
+setNodeWakeup;
+nodeSuspend;
+virSuspendCleanup;
# nwfilter_conf.h
diff --git a/src/nodeinfo.c b/src/nodeinfo.c
index 6448b79..3c67fe6 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) 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,215 @@ unsigned long long nodeGetFreeMemory(virConnectPtr conn
ATTRIBUTE_UNUSED)
return 0;
}
#endif
+
+
+static int initialized;
+virMutex virSuspendMutex;
+
+int virSuspendLock(void)
+{
+ return virMutexTryLock(&virSuspendMutex);
+}
+
+void virSuspendUnlock(void)
+{
+ virMutexUnlock(&virSuspendMutex);
+}
+
+/**
+ * virSuspendInit:
+ *
+ * Get the low power states supported by the host, such as Suspend-to-RAM (S3)
+ * or Suspend-to-Disk (S4), 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.
+ */
+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;
+}
+
+
+/**
+ * nodeSuspendForDuration:
+ * @conn: pointer to the hypervisor connection
+ * @state: the state to which the host must be suspended to -
+ * VIR_HOST_PM_S3 (Suspend-to-RAM)
+ * or VIR_HOST_PM_S4 (Suspend-to-disk)
+ * @duration: the time duration in seconds, for which the host
+ * must be suspended
+ *
+ * Suspend the node (host machine) for the given duration of time
+ * in the specified state (such as S3 or S4). Resume the node
+ * after the time duration is complete.
+ *
+ * An RTC alarm is set appropriately to wake up the node from
+ * its sleep state. Then the actual node suspend 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.
+ *
+ * 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)
+{
+ static virThread thread;
+ char *cmdString;
+
+ 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 suspend.
+ */
+ if (!virSuspendLock())
+ return -1;
+
+ /* Check if the host supports the requested suspend state */
+ switch (state) {
+ case VIR_S3:
+ if (hostPMFeatures & VIR_S3) {
+ cmdString = strdup("pm-suspend");
+ if (cmdString == NULL) {
+ virReportOOMError();
+ goto cleanup;
+ }
+ break;
+ }
+ goto cleanup;
+ case VIR_S4:
+ if (hostPMFeatures & VIR_S4) {
+ cmdString = strdup("pm-hibernate");
+ 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;
+ }
+
+ /* virSuspendUnlock() must be called only after resume is complete,
+ * in the thread that did the suspend and resume.
+ */
+ return 0;
+
+cleanup:
+ virSuspendUnlock();
+ return -1;
+}
+
+#define MAX_SUSPEND_DURATION 365*24*60*60 /* 1 year, a reasonable max? */
+
+/**
+ * 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 || alarmTime > MAX_SUSPEND_DURATION)
+ 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.
+ */
+
+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, release the lock we
+ * had acquired while suspending.
+ */
+ virSuspendUnlock();
+}
+
+
+void virSuspendCleanup(void)
+{
+ if(initialized)
+ virMutexDestroy(&virSuspendMutex);
+}
+
diff --git a/src/nodeinfo.h b/src/nodeinfo.h
index 4766152..186547a 100644
--- a/src/nodeinfo.h
+++ b/src/nodeinfo.h
@@ -46,4 +46,18 @@ int nodeGetCellsFreeMemory(virConnectPtr conn,
int maxCells);
unsigned long long nodeGetFreeMemory(virConnectPtr conn);
+int virSuspendLock(void);
+void virSuspendUnlock(void);
+int virSuspendInit(void);
+int nodeSuspendForDuration(virConnectPtr conn,
+ int state,
+ unsigned long long duration);
+
+int setNodeWakeup(unsigned long long alarmTime);
+
+# define SUSPEND_DELAY 10 /* in seconds */
+
+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 118197a..b4dc582 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -778,6 +778,11 @@ qemudShutdown(void) {
virSysinfoDefFree(qemu_driver->hostsysinfo);
+ /* Cleanup up the structures initialized for
+ * suspending the host.
+ */
+ virSuspendCleanup();
+
qemuProcessAutoDestroyShutdown(qemu_driver);
VIR_FREE(qemu_driver->configDir);
diff --git a/src/util/threads-pthread.c b/src/util/threads-pthread.c
index 5b8fd5b..e0a4f71 100644
--- a/src/util/threads-pthread.c
+++ b/src/util/threads-pthread.c
@@ -81,10 +81,25 @@ void virMutexDestroy(virMutexPtr m)
pthread_mutex_destroy(&m->lock);
}
-void virMutexLock(virMutexPtr m){
+void virMutexLock(virMutexPtr m)
+{
pthread_mutex_lock(&m->lock);
}
+/**
+ * virMutexTryLock:
+ * This is same as virMutexLock() except that
+ * if the mutex is unavailable (already locked),
+ * this fails and returns an error.
+ *
+ * Returns 1 if the lock was acquired, 0 if there was
+ * contention or error.
+ */
+int virMutexTryLock(virMutexPtr m)
+{
+ return !pthread_mutex_trylock(&m->lock);
+}
+
void virMutexUnlock(virMutexPtr m)
{
pthread_mutex_unlock(&m->lock);
diff --git a/src/util/threads.h b/src/util/threads.h
index b72610c..5ef8714 100644
--- a/src/util/threads.h
+++ b/src/util/threads.h
@@ -81,6 +81,7 @@ int virMutexInitRecursive(virMutexPtr m) ATTRIBUTE_RETURN_CHECK;
void virMutexDestroy(virMutexPtr m);
void virMutexLock(virMutexPtr m);
+int virMutexTryLock(virMutexPtr m);
void virMutexUnlock(virMutexPtr m);