This patch adds a new API to put a KVM host to a suspended state
(either Suspend-to-RAM or Suspend-to-Disk) and also resume the host
back from within libvirt. This uses the RTC wakeup mechanism to set
up a timer alarm before suspending the host, so that in-band resume
is facilitated since the firing of the RTC alarm wakes up the host.
The decision to use the RTC Wakeup mechanism to resume the host from
sleep was taken in [1]. An initial API was discussed in [2].
Some design ideas for the asynchronous mechanism implementation was
discussed in [3].
Todo:
1. During libvirtd init time check if S3/S4 is supported by the host.
2. Add synchronization/locking to ensure that only one suspend operation
is active at a time.
References:
[1].
http://www.redhat.com/archives/libvir-list/2011-August/msg00327.html
[2].
http://www.redhat.com/archives/libvir-list/2011-August/msg00248.html
[3].
http://www.redhat.com/archives/libvir-list/2011-September/msg00438.html
Signed-off-by: Srivatsa S. Bhat <srivatsa.bhat(a)linux.vnet.ibm.com>
---
include/libvirt/libvirt.h.in | 9 +++
src/driver.h | 5 ++
src/libvirt.c | 45 ++++++++++++++
src/libvirt_private.syms | 4 +
src/libvirt_public.syms | 1
src/nodeinfo.c | 139 ++++++++++++++++++++++++++++++++++++++++++
src/nodeinfo.h | 10 +++
src/qemu/qemu_driver.c | 1
src/remote/remote_driver.c | 1
src/remote/remote_protocol.x | 12 +++-
10 files changed, 224 insertions(+), 3 deletions(-)
diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in
index 39155a6..aca55e6 100644
--- a/include/libvirt/libvirt.h.in
+++ b/include/libvirt/libvirt.h.in
@@ -988,6 +988,10 @@ unsigned long long virNodeGetFreeMemory (virConnectPtr
conn);
int virNodeGetSecurityModel (virConnectPtr conn,
virSecurityModelPtr secmodel);
+int virNodeSuspendForDuration (virConnectPtr conn,
+ int state,
+ unsigned long long duration);
+
/*
* Gather list of running domains
*/
@@ -3210,6 +3214,11 @@ typedef struct _virTypedParameter virMemoryParameter;
*/
typedef virMemoryParameter *virMemoryParameterPtr;
+typedef enum {
+ VIR_S3, /* Suspend-to-RAM */
+ VIR_S4 /* Suspend-to-disk */
+} virSuspendState;
+
#ifdef __cplusplus
}
#endif
diff --git a/src/driver.h b/src/driver.h
index 3792003..658af5f 100644
--- a/src/driver.h
+++ b/src/driver.h
@@ -718,6 +718,10 @@ typedef int
(*virDrvDomainBlockPull)(virDomainPtr dom, const char *path,
unsigned long bandwidth, unsigned int flags);
+typedef int
+ (*virDrvNodeSuspendForDuration)(virConnectPtr conn, int state,
+ unsigned long long duration);
+
/**
* _virDriver:
@@ -872,6 +876,7 @@ struct _virDriver {
virDrvDomainGetBlockJobInfo domainGetBlockJobInfo;
virDrvDomainBlockJobSetSpeed domainBlockJobSetSpeed;
virDrvDomainBlockPull domainBlockPull;
+ virDrvNodeSuspendForDuration nodeSuspendForDuration;
};
typedef int
diff --git a/src/libvirt.c b/src/libvirt.c
index 05c0a87..688c982 100644
--- a/src/libvirt.c
+++ b/src/libvirt.c
@@ -6103,6 +6103,51 @@ error:
}
/**
+ * virNodeSuspendForDuration:
+ * @conn: pointer to the hypervisor connection
+ * @state: the state to which the host must be suspended to
+ * @duration: the time duration in seconds, for which the host
+ * has to 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.
+ *
+ * Returns 0 on success (i.e., the node will be suspended after a
+ * short delay), -1 on failure (the operation is not supported).
+ */
+int
+virNodeSuspendForDuration(virConnectPtr conn,
+ int state,
+ unsigned long long duration)
+{
+ VIR_DEBUG("conn=%p, state=%d, duration=%lld", conn, state, duration);
+
+ virResetLastError();
+
+ if (!VIR_IS_CONNECT(conn)) {
+ virLibConnError(VIR_ERR_INVALID_CONN, __FUNCTION__);
+ virDispatchError(NULL);
+ return -1;
+ }
+
+ if (conn->driver->nodeSuspendForDuration) {
+ int ret;
+ ret = conn->driver->nodeSuspendForDuration(conn, state, duration);
+ if (ret < 0)
+ goto error;
+ return ret;
+ }
+
+ virLibConnError(VIR_ERR_NO_SUPPORT, __FUNCTION__);
+
+error:
+ virDispatchError(conn);
+ return -1;
+}
+
+
+/**
* virDomainGetSchedulerType:
* @domain: pointer to domain object
* @nparams: pointer to number of scheduler parameters, can be NULL
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 4f33567..3d2870a 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -828,7 +828,9 @@ nodeGetCellsFreeMemory;
nodeGetFreeMemory;
nodeGetInfo;
nodeGetMemoryStats;
-
+nodeSuspendForDuration;
+setNodeWakeup;
+nodeSuspend;
# nwfilter_conf.h
virNWFilterCallbackDriversLock;
diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms
index 8a6d55a..14466f9 100644
--- a/src/libvirt_public.syms
+++ b/src/libvirt_public.syms
@@ -478,6 +478,7 @@ LIBVIRT_0.9.4 {
virDomainGetBlockJobInfo;
virDomainBlockJobSetSpeed;
virDomainBlockPull;
+ virNodeSuspendForDuration;
} LIBVIRT_0.9.3;
LIBVIRT_0.9.5 {
diff --git a/src/nodeinfo.c b/src/nodeinfo.c
index 6448b79..34e9ff8 100644
--- a/src/nodeinfo.c
+++ b/src/nodeinfo.c
@@ -46,7 +46,8 @@
#include "count-one-bits.h"
#include "intprops.h"
#include "virfile.h"
-
+#include "command.h"
+#include "threads.h"
#define VIR_FROM_THIS VIR_FROM_NONE
@@ -897,3 +898,139 @@ unsigned long long nodeGetFreeMemory(virConnectPtr conn
ATTRIBUTE_UNUSED)
return 0;
}
#endif
+
+
+static bool aboutToSuspend = false;
+
+/**
+ * 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.
+ */
+
+int nodeSuspendForDuration(virConnectPtr conn ATTRIBUTE_UNUSED,
+ int state,
+ unsigned long long duration)
+{
+ virThread thread;
+ static char * suspendCmdString;
+ int ret = -1;
+
+ /* Check if we have already started suspending
+ * and this is a duplicate request.
+ */
+ if(aboutToSuspend == true) {
+ /* This is a duplicate request to suspend.
+ * So we need not do anything.
+ */
+ return 0;
+ } else {
+ /* This is not a duplicate request to suspend. */
+ aboutToSuspend = true;
+ }
+
+ /* 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 (state == VIR_S4) {
+ suspendCmdString = "pm-hibernate";
+ } else {
+ suspendCmdString = "pm-suspend";
+ }
+
+ if(virThreadCreate(&thread, false, nodeSuspend, suspendCmdString) < 0) {
+ nodeReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Failed to create thread to suspend the host\n"));
+ goto cleanup;
+ }
+
+ ret = 0;
+
+cleanup:
+ return ret;
+}
+
+/**
+ * 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;
+
+ 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: string containing the command to invoke
+ *
+ * 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);
+
+ /* 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 our sleep state, reset the flag
+ * that formerly indicated that we were about to suspend the node.
+ */
+ aboutToSuspend = false;
+}
+
diff --git a/src/nodeinfo.h b/src/nodeinfo.h
index 4766152..f0c2271 100644
--- a/src/nodeinfo.h
+++ b/src/nodeinfo.h
@@ -46,4 +46,14 @@ int nodeGetCellsFreeMemory(virConnectPtr conn,
int maxCells);
unsigned long long nodeGetFreeMemory(virConnectPtr conn);
+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);
+
#endif /* __VIR_NODEINFO_H__*/
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index e2f428f..c2c72d2 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -10695,6 +10695,7 @@ static virDriver qemuDriver = {
.domainGetBlockJobInfo = qemuDomainGetBlockJobInfo, /* 0.9.4 */
.domainBlockJobSetSpeed = qemuDomainBlockJobSetSpeed, /* 0.9.4 */
.domainBlockPull = qemuDomainBlockPull, /* 0.9.4 */
+ .nodeSuspendForDuration = nodeSuspendForDuration, /* 0.9.4 */
};
diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c
index 9d34b7e..02cd122 100644
--- a/src/remote/remote_driver.c
+++ b/src/remote/remote_driver.c
@@ -4429,6 +4429,7 @@ static virDriver remote_driver = {
.domainGetBlockJobInfo = remoteDomainGetBlockJobInfo, /* 0.9.4 */
.domainBlockJobSetSpeed = remoteDomainBlockJobSetSpeed, /* 0.9.4 */
.domainBlockPull = remoteDomainBlockPull, /* 0.9.4 */
+ .nodeSuspendForDuration = remoteNodeSuspendForDuration, /* 0.9.4 */
};
static virNetworkDriver network_driver = {
diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x
index 4ec1c57..c1b1d94 100644
--- a/src/remote/remote_protocol.x
+++ b/src/remote/remote_protocol.x
@@ -2220,6 +2220,15 @@ struct remote_domain_get_control_info_ret { /* insert@1 */
unsigned hyper stateTime;
};
+struct remote_node_suspend_for_duration_args {
+ int state;
+ unsigned hyper duration;
+};
+
+struct remote_node_suspend_for_duration_ret {
+ int status;
+};
+
/*----- Protocol. -----*/
/* Define the program number, protocol version and procedure numbers here. */
@@ -2509,7 +2518,8 @@ enum remote_procedure {
REMOTE_PROC_DOMAIN_EVENT_BLOCK_JOB = 241, /* skipgen skipgen */
REMOTE_PROC_DOMAIN_MIGRATE_GET_MAX_SPEED = 242, /* autogen autogen */
- REMOTE_PROC_DOMAIN_BLOCK_STATS_FLAGS = 243 /* skipgen skipgen */
+ REMOTE_PROC_DOMAIN_BLOCK_STATS_FLAGS = 243, /* skipgen skipgen */
+ REMOTE_PROC_NODE_SUSPEND_FOR_DURATION = 244 /* autogen autogen priority:high */
/*
* Notice how the entries are grouped in sets of 10 ?