[libvirt] [ PATCH v4 0/4] API to invoke S3/S4 on a host and also resume from within libvirt

This patchset adds a new API to put a host to a suspended state (Suspend-to-RAM, Suspend-to-Disk or Hybrid-Suspend) and setup a timed resume to get the host back online, 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 by the firing of the RTC alarm, which 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]. v4: * Applies on top of the Hybrid-Suspend patch posted in [4]. * Incorporated the review comments in [5]. v3: * Rebased to libvirt 0.9.7 * Added a check to see if alarmTime (suspend duration) is within an acceptable range. v2: * Added an init function which finds out if S3/S4 is supported by the host, upon the first request to suspend/hibernate. * Added synchronization/locking to ensure that only one suspend operation is active at a time. v1: http://www.redhat.com/archives/libvir-list/2011-September/msg00830.html v2: http://comments.gmane.org/gmane.comp.emulators.libvirt/46729 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 [4]. http://www.redhat.com/archives/libvir-list/2011-November/msg01407.html [5]. http://thread.gmane.org/gmane.comp.emulators.libvirt/47950 Srivatsa S. Bhat (4): Add a public API to invoke suspend/resume on the host Add the remote protocol implementation for virNodeSuspendForDuration Implement the core API to suspend/resume the host Add virsh command to initiate suspend on the host include/libvirt/libvirt.h.in | 17 +++ src/driver.h | 6 + src/libvirt.c | 61 ++++++++++ src/libvirt_private.syms | 7 + src/libvirt_public.syms | 5 + src/nodeinfo.c | 245 ++++++++++++++++++++++++++++++++++++++++++ src/nodeinfo.h | 9 ++ src/qemu/qemu_driver.c | 4 + src/remote/remote_driver.c | 1 src/remote/remote_protocol.x | 10 ++ tools/virsh.c | 64 +++++++++++ tools/virsh.pod | 7 + 12 files changed, 434 insertions(+), 2 deletions(-)

Implement the public definitions for the new API virNodeSuspendForDuration() which will be subsequently used to do a timed suspend on the host. Signed-off-by: Srivatsa S. Bhat <srivatsa.bhat@linux.vnet.ibm.com> --- include/libvirt/libvirt.h.in | 17 +++++++++++- src/driver.h | 6 ++++ src/libvirt.c | 61 ++++++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 5 +++ 4 files changed, 88 insertions(+), 1 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index 2ab89f5..ad9e8f1 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -249,7 +249,17 @@ typedef enum { VIR_DOMAIN_START_FORCE_BOOT = 1 << 3, /* Boot, discarding any managed save */ } virDomainCreateFlags; - +/** + * virNodeSuspendState: + * + * Flags to indicate which system-wide sleep state the host must be + * transitioned to. + */ +typedef enum { + VIR_NODE_S3 = (1 << 0), /* Suspend-to-RAM */ + VIR_NODE_S4 = (1 << 1), /* Suspend-to-Disk */ + VIR_NODE_HYBRID_SUSPEND = (1 << 2), /* Hybrid-Suspend */ +} virNodeSuspendState; /** * virStream: @@ -1085,6 +1095,11 @@ unsigned long long virNodeGetFreeMemory (virConnectPtr conn); int virNodeGetSecurityModel (virConnectPtr conn, virSecurityModelPtr secmodel); +int virNodeSuspendForDuration (virConnectPtr conn, + int state, + unsigned long long duration, + unsigned int flags); + /* * Gather list of running domains */ diff --git a/src/driver.h b/src/driver.h index 4c14aaa..60df410 100644 --- a/src/driver.h +++ b/src/driver.h @@ -740,6 +740,11 @@ 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, + unsigned int flags); + /** * _virDriver: @@ -899,6 +904,7 @@ struct _virDriver { virDrvDomainGetBlockJobInfo domainGetBlockJobInfo; virDrvDomainBlockJobSetSpeed domainBlockJobSetSpeed; virDrvDomainBlockPull domainBlockPull; + virDrvNodeSuspendForDuration nodeSuspendForDuration; }; typedef int diff --git a/src/libvirt.c b/src/libvirt.c index 1518ed2..47c49fa 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -6359,6 +6359,67 @@ error: } /** + * virNodeSuspendForDuration: + * @conn: pointer to the hypervisor connection + * @state: the state to which the host must be suspended to, + * such as: VIR_NODE_S3 (Suspend-to-RAM) + * VIR_NODE_S4 (Suspend-to-Disk) + * VIR_NODE_HYBRID_SUSPEND (Hybrid-Suspend, which is a + * combination of S3 and S4). + * @duration: the time duration in seconds for which the host + * has to be suspended + * @flags: any flag values that might need to be passed; + * currently unused (0). + * + * Attempt to suspend the node (host machine) for the given duration of + * time in the specified state (Suspend-to-RAM, Suspend-to-Disk or + * Hybrid-Suspend). Schedule the node's Real-Time-Clock interrupt to + * resume the node after the 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, or an attempted + * suspend is already underway). + */ +int +virNodeSuspendForDuration(virConnectPtr conn, + int state, + unsigned long long duration, + unsigned int flags) +{ + + 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->flags & VIR_CONNECT_RO) { + virLibConnError(VIR_ERR_OPERATION_DENIED, __FUNCTION__); + goto error; + } + + if (conn->driver->nodeSuspendForDuration) { + int ret; + ret = conn->driver->nodeSuspendForDuration(conn, state, + duration, flags); + 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_public.syms b/src/libvirt_public.syms index bcefb10..a1eede6 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -498,4 +498,9 @@ LIBVIRT_0.9.7 { virDomainSnapshotNumChildren; } LIBVIRT_0.9.5; +LIBVIRT_0.9.8 { + global: + virNodeSuspendForDuration; +} LIBVIRT_0.9.7; + # .... define new API here using predicted next version number ....

On Mon, Nov 28, 2011 at 05:33:06PM +0530, Srivatsa S. Bhat wrote:
Implement the public definitions for the new API virNodeSuspendForDuration() which will be subsequently used to do a timed suspend on the host.
Signed-off-by: Srivatsa S. Bhat <srivatsa.bhat@linux.vnet.ibm.com> ---
include/libvirt/libvirt.h.in | 17 +++++++++++- src/driver.h | 6 ++++ src/libvirt.c | 61 ++++++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 5 +++ 4 files changed, 88 insertions(+), 1 deletions(-)
diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index 2ab89f5..ad9e8f1 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -249,7 +249,17 @@ typedef enum { VIR_DOMAIN_START_FORCE_BOOT = 1 << 3, /* Boot, discarding any managed save */ } virDomainCreateFlags;
- +/** + * virNodeSuspendState: + * + * Flags to indicate which system-wide sleep state the host must be + * transitioned to. + */ +typedef enum { + VIR_NODE_S3 = (1 << 0), /* Suspend-to-RAM */ + VIR_NODE_S4 = (1 << 1), /* Suspend-to-Disk */ + VIR_NODE_HYBRID_SUSPEND = (1 << 2), /* Hybrid-Suspend */ +} virNodeSuspendState;
It is desirable for the constant prefix to be consistent & match the function name. In addition S3/S4 are x86 specific terminology. Lets actually use descriptive names which are architecture indepedant, whereupon you don't need comments to explain what the codename means. eg typedef enum { VIR_NODE_SUSPEND_TARGET_RAM = (1 << 0), VIR_NODE_SUSPEND_TARGET_DISK = (1 << 1), VIR_NODE_SUSPEND_TARGET_HYBRID = (1 << 2) } virNodeSuspendTarget;
/** * virStream: @@ -1085,6 +1095,11 @@ unsigned long long virNodeGetFreeMemory (virConnectPtr conn); int virNodeGetSecurityModel (virConnectPtr conn, virSecurityModelPtr secmodel);
+int virNodeSuspendForDuration (virConnectPtr conn, + int state, + unsigned long long duration, + unsigned int flags);
'state' is better named 'target' IMHO, and can be unsigned, eg. int virNodeSuspendForDuration (virConnectPtr conn, unsigned int target, unsigned long long duration, unsigned int flags); 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 :|

Implement the remote protocol for virNodeSuspendForDuration() API. Signed-off-by: Srivatsa S. Bhat <srivatsa.bhat@linux.vnet.ibm.com> --- src/remote/remote_driver.c | 1 + src/remote/remote_protocol.x | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletions(-) diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index 94fd3e7..e784161 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -4550,6 +4550,7 @@ static virDriver remote_driver = { .domainGetBlockJobInfo = remoteDomainGetBlockJobInfo, /* 0.9.4 */ .domainBlockJobSetSpeed = remoteDomainBlockJobSetSpeed, /* 0.9.4 */ .domainBlockPull = remoteDomainBlockPull, /* 0.9.4 */ + .nodeSuspendForDuration = remoteNodeSuspendForDuration, /* 0.9.8 */ }; static virNetworkDriver network_driver = { diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index 5ea1114..4974f4d 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -2269,6 +2269,13 @@ struct remote_domain_open_graphics_args { unsigned int flags; }; +struct remote_node_suspend_for_duration_args { + int state; + unsigned hyper duration; + unsigned int flags; +}; + + /*----- Protocol. -----*/ /* Define the program number, protocol version and procedure numbers here. */ @@ -2564,7 +2571,8 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_SNAPSHOT_NUM_CHILDREN = 246, /* autogen autogen priority:high */ REMOTE_PROC_DOMAIN_SNAPSHOT_LIST_CHILDREN_NAMES = 247, /* autogen autogen priority:high */ REMOTE_PROC_DOMAIN_EVENT_DISK_CHANGE = 248, /* skipgen skipgen */ - REMOTE_PROC_DOMAIN_OPEN_GRAPHICS = 249 /* skipgen skipgen */ + REMOTE_PROC_DOMAIN_OPEN_GRAPHICS = 249, /* skipgen skipgen */ + REMOTE_PROC_NODE_SUSPEND_FOR_DURATION = 250 /* autogen autogen */ /* * Notice how the entries are grouped in sets of 10 ?

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@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 */ };

On Mon, Nov 28, 2011 at 05:33:22PM +0530, Srivatsa S. Bhat wrote:
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@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(-)
The nodeinfo.c file were a place to put APIs for collecting info about the host CPU & memory stats. As such the suspend code does not really belong there. Please put it in two new files src/util/virsuspend.[c,h] and ensure all the internal APIs related to this have the name prefix 'virSuspend' to match the filename...
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;
eg, these three would need renaming
+virSuspendCleanup; +virSuspendInit; +virSuspendLock; +virSuspendUnlock;
I'm not really sure why we need to export all these. AFAICT, you are only calling virSuspendCleanup and nodeSuspendForDuration from the QEMU driver. So all the other APIs could just be declared static and thus not listed here
# 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; + }
This is a designed in race condition. You should instead be calling virSuspendInit() from virInitialize() as we do for other initializers.
+ + /* + * 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;
Coding style requires {} around the else clause too.
+ + 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;
Needs to report an error, VIR_ERR_OPERATION_UNSUPPORTED
+ + case VIR_NODE_S4: + if (hostPMFeatures & VIR_NODE_S4) { + cmdString = strdup("pm-hibernate"); + if (cmdString == NULL) { + virReportOOMError(); + goto cleanup; + } + break; + } + goto cleanup;
Needs to report an error, VIR_ERR_OPERATION_UNSUPPORTED
+ + 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;
Needs to report an error, VIR_ERR_OPERATION_UNSUPPORTED
+ + default: + goto cleanup;
Needs to report an error, VIR_ERR_INVALID_ARG
+ } + + /* 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"));
This overwrites errors already raised in setNodeWakeup
+ 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;
Needs to report an error
+ + 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; + }
This overwrites an error already reported by virCommandRun
+ + 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")); + }
Overwrites the error already reported
+ + virCommandFree(suspendCmd); + + /* Now that we have resumed from suspend, reset 'aboutToSuspend' flag. */ + virSuspendLock(); + aboutToSuspend = false; + virSuspendUnlock(); +} + +void virSuspendCleanup(void) +{ + if (initialized) + virMutexDestroy(&virSuspendMutex); +}
This is not good. It is assuming that the QEMU driver is the only driver that will ever use this. It should be implemented by the Xen, LXC, UML drivers too. For static initialized data, we generally do not ever destroy it, so IMHO this method can just be deleted.
+ 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 */ };
Since this is using a shared helper, you can trivially add this to the UML, LXC and Xen drivers too, which all run inside libvirtd. 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 :|

Hi Daniel, On 11/28/2011 05:50 PM, Daniel P. Berrange wrote:
On Mon, Nov 28, 2011 at 05:33:22PM +0530, Srivatsa S. Bhat wrote:
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@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(-)
The nodeinfo.c file were a place to put APIs for collecting info about the host CPU & memory stats. As such the suspend code does not really belong there.
Please put it in two new files src/util/virsuspend.[c,h]
and ensure all the internal APIs related to this have the name prefix 'virSuspend' to match the filename...
How about src/util/virnodesuspend.[c,h]? And a corresponding virNodeSuspend prefix? Eric Blake raised a point about separating out domain suspend and node suspend operations, in my previous version of the patchset. So I feel it would be better to explicitly state what we are doing, by using 'virnodesuspend'. Would that be fine? And thanks a lot for the review! I will send an updated version with your comments addressed. -- Regards, Srivatsa S. Bhat IBM Linux Technology Center

On 11/28/2011 05:50 PM, Daniel P. Berrange wrote:
On Mon, Nov 28, 2011 at 05:33:22PM +0530, Srivatsa S. Bhat wrote:
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@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(-)
+ + 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;
Needs to report an error, VIR_ERR_OPERATION_UNSUPPORTED
Ok, then I will define VIR_ERR_OPERATION_UNSUPPORTED in src/util/virterror.c and use it, since it is not there at present. And that definition should be in a separate, self-contained patch right?
+ + case VIR_NODE_S4: + if (hostPMFeatures & VIR_NODE_S4) { + cmdString = strdup("pm-hibernate"); + if (cmdString == NULL) { + virReportOOMError(); + goto cleanup; + } + break; + } + goto cleanup;
Needs to report an error, VIR_ERR_OPERATION_UNSUPPORTED
+ + 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;
Needs to report an error, VIR_ERR_OPERATION_UNSUPPORTED
+ + default: + goto cleanup;
Needs to report an error, VIR_ERR_INVALID_ARG
-- Regards, Srivatsa S. Bhat IBM Linux Technology Center

On 11/28/2011 10:46 AM, Srivatsa S. Bhat wrote:
+ /* 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;
Needs to report an error, VIR_ERR_OPERATION_UNSUPPORTED
Ok, then I will define VIR_ERR_OPERATION_UNSUPPORTED in src/util/virterror.c and use it, since it is not there at present. And that definition should be in a separate, self-contained patch right?
Hmm, I think that was probably just a case of Daniel typing by memory instead of checking the source, and evidence of how confusing our existing error naming is. Just use the existing VIR_ERR_OPERATION_INVALID instead of inventing a new error. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On Mon, Nov 28, 2011 at 10:56:33AM -0700, Eric Blake wrote:
On 11/28/2011 10:46 AM, Srivatsa S. Bhat wrote:
+ /* 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;
Needs to report an error, VIR_ERR_OPERATION_UNSUPPORTED
Ok, then I will define VIR_ERR_OPERATION_UNSUPPORTED in src/util/virterror.c and use it, since it is not there at present. And that definition should be in a separate, self-contained patch right?
Hmm, I think that was probably just a case of Daniel typing by memory instead of checking the source, and evidence of how confusing our existing error naming is. Just use the existing VIR_ERR_OPERATION_INVALID instead of inventing a new error.
Sorry, I meant VIR_ERR_ARGUMENT_UNSUPPORTED = 74, /* valid API use but unsupported by the given driver */ 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 11/28/2011 11:26 PM, Eric Blake wrote:
On 11/28/2011 10:46 AM, Srivatsa S. Bhat wrote:
+ /* 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;
Needs to report an error, VIR_ERR_OPERATION_UNSUPPORTED
Ok, then I will define VIR_ERR_OPERATION_UNSUPPORTED in src/util/virterror.c and use it, since it is not there at present. And that definition should be in a separate, self-contained patch right?
Hmm, I think that was probably just a case of Daniel typing by memory instead of checking the source, and evidence of how confusing our existing error naming is. Just use the existing VIR_ERR_OPERATION_INVALID instead of inventing a new error.
Ok thanks, will do that. -- Regards, Srivatsa S. Bhat IBM Linux Technology Center

Add a new command 'nodesuspend' to perform a timed suspend on the host. Signed-off-by: Srivatsa S. Bhat <srivatsa.bhat@linux.vnet.ibm.com> --- tools/virsh.c | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 7 ++++++ 2 files changed, 71 insertions(+), 0 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index 89fb4e7..17bc44c 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -5198,6 +5198,69 @@ cmdNodeMemStats(vshControl *ctl, const vshCmd *cmd) } /* + * "nodesuspend" command + */ +static const vshCmdInfo info_nodesuspend[] = { + {"help", N_("suspend the host node for a given time duration")}, + {"desc", N_("Suspend the host node for a given time duration " + "and attempt to resume thereafter.")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_node_suspend[] = { + {"state", VSH_OT_DATA, VSH_OFLAG_REQ, N_("mem(Suspend-to-RAM), " + "disk(Suspend-to-Disk), hybrid(Hybrid-Suspend)")}, + {"duration", VSH_OT_INT, VSH_OFLAG_REQ, N_("Suspend duration in seconds")}, + {"flags", VSH_OT_INT, VSH_OFLAG_NONE, N_("Suspend flags, 0 for default")}, + {NULL, 0, 0, NULL} +}; + +static bool +cmdNodeSuspend(vshControl *ctl, const vshCmd *cmd) +{ + const char *state = NULL; + int suspendState; + long long duration; + unsigned int flags = 0; + + if (!vshConnectionUsability(ctl, ctl->conn)) + return false; + + if (vshCommandOptString(cmd, "state", &state) < 0) + return false; + + if (vshCommandOptLongLong(cmd, "duration", &duration) < 0) + return false; + + if (vshCommandOptUInt(cmd, "flags", &flags) < 0) + return false; + + if (!strcmp(state, "mem")) + suspendState = VIR_NODE_S3; + else if (!strcmp(state, "disk")) + suspendState = VIR_NODE_S4; + else if (!strcmp(state, "hybrid")) + suspendState = VIR_NODE_HYBRID_SUSPEND; + else { + vshError(ctl, "%s", _("Invalid state")); + return false; + } + + if (duration <= 0) { + vshError(ctl, "%s", _("Invalid duration")); + return false; + } + + if (virNodeSuspendForDuration(ctl->conn, suspendState, duration, + flags) < 0) { + vshError(ctl, "%s", _("The host was not suspended")); + return false; + } + return true; +} + + +/* * "capabilities" command */ static const vshCmdInfo info_capabilities[] = { @@ -14712,6 +14775,7 @@ static const vshCmdDef hostAndHypervisorCmds[] = { {"nodecpustats", cmdNodeCpuStats, opts_node_cpustats, info_nodecpustats, 0}, {"nodeinfo", cmdNodeinfo, NULL, info_nodeinfo, 0}, {"nodememstats", cmdNodeMemStats, opts_node_memstats, info_nodememstats, 0}, + {"nodesuspend", cmdNodeSuspend, opts_node_suspend, info_nodesuspend, 0}, {"qemu-attach", cmdQemuAttach, opts_qemu_attach, info_qemu_attach}, {"qemu-monitor-command", cmdQemuMonitorCommand, opts_qemu_monitor_command, info_qemu_monitor_command, 0}, diff --git a/tools/virsh.pod b/tools/virsh.pod index db872dd..25cdfa9 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -253,6 +253,13 @@ statistics during 1 second. Returns memory stats of the node. If I<cell> is specified, this will prints specified cell statistics only. +=item B<nodesuspend> [I<state>] [I<duration>] [I<flags>] + +Puts the node (host machine) into a system-wide sleep state such as +Suspend-to-RAM (S3), Suspend-to-Disk (S4) or Hybrid-Suspend and sets up +a Real-Time-Clock interrupt to fire (to wake up the node) after a time delay +specified by the 'duration' parameter. + =item B<capabilities> Print an XML document describing the capabilities of the hypervisor

On Mon, Nov 28, 2011 at 05:33:29PM +0530, Srivatsa S. Bhat wrote:
Add a new command 'nodesuspend' to perform a timed suspend on the host.
Signed-off-by: Srivatsa S. Bhat <srivatsa.bhat@linux.vnet.ibm.com> ---
tools/virsh.c | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 7 ++++++ 2 files changed, 71 insertions(+), 0 deletions(-)
diff --git a/tools/virsh.c b/tools/virsh.c index 89fb4e7..17bc44c 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -5198,6 +5198,69 @@ cmdNodeMemStats(vshControl *ctl, const vshCmd *cmd) }
/* + * "nodesuspend" command + */ +static const vshCmdInfo info_nodesuspend[] = { + {"help", N_("suspend the host node for a given time duration")}, + {"desc", N_("Suspend the host node for a given time duration " + "and attempt to resume thereafter.")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_node_suspend[] = { + {"state", VSH_OT_DATA, VSH_OFLAG_REQ, N_("mem(Suspend-to-RAM), " + "disk(Suspend-to-Disk), hybrid(Hybrid-Suspend)")}, + {"duration", VSH_OT_INT, VSH_OFLAG_REQ, N_("Suspend duration in seconds")}, + {"flags", VSH_OT_INT, VSH_OFLAG_NONE, N_("Suspend flags, 0 for default")}, + {NULL, 0, 0, NULL} +}; + +static bool +cmdNodeSuspend(vshControl *ctl, const vshCmd *cmd) +{ + const char *state = NULL; + int suspendState; + long long duration; + unsigned int flags = 0; + + if (!vshConnectionUsability(ctl, ctl->conn)) + return false; + + if (vshCommandOptString(cmd, "state", &state) < 0) + return false; + + if (vshCommandOptLongLong(cmd, "duration", &duration) < 0) + return false; + + if (vshCommandOptUInt(cmd, "flags", &flags) < 0) + return false; + + if (!strcmp(state, "mem")) + suspendState = VIR_NODE_S3;
If we call it 'RAM' in the API, we should be consistent here and use 'ram' too. Otherwise we should make the API VIR_SUSPEND_TARGET_MEM instead.
+ else if (!strcmp(state, "disk")) + suspendState = VIR_NODE_S4; + else if (!strcmp(state, "hybrid")) + suspendState = VIR_NODE_HYBRID_SUSPEND; + else { + vshError(ctl, "%s", _("Invalid state")); + return false; + } + + if (duration <= 0) { + vshError(ctl, "%s", _("Invalid duration")); + return false; + } + + if (virNodeSuspendForDuration(ctl->conn, suspendState, duration, + flags) < 0) { + vshError(ctl, "%s", _("The host was not suspended")); + return false; + } + return true;
diff --git a/tools/virsh.pod b/tools/virsh.pod index db872dd..25cdfa9 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -253,6 +253,13 @@ statistics during 1 second. Returns memory stats of the node. If I<cell> is specified, this will prints specified cell statistics only.
+=item B<nodesuspend> [I<state>] [I<duration>] [I<flags>] + +Puts the node (host machine) into a system-wide sleep state such as +Suspend-to-RAM (S3), Suspend-to-Disk (S4) or Hybrid-Suspend and sets up +a Real-Time-Clock interrupt to fire (to wake up the node) after a time delay +specified by the 'duration' parameter.
Again, S3/S4 are x86 specific terminology we can do without here IMHO 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 (3)
-
Daniel P. Berrange
-
Eric Blake
-
Srivatsa S. Bhat