[libvirt] [RFC PATCH v2 0/2] API to invoke S3/S4 on a host and also resume from within libvirt

This patch adds a new API to put a 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 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]. This patch is positioned to go in along with the patch that exports the host power management capabilities as XML, posted in [4]. 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 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-August/msg00324.html Srivatsa S. Bhat (2): Implement the asynchronous suspend and RTC wakeup Make the API public include/libvirt/libvirt.h.in | 9 ++ src/driver.h | 5 + src/libvirt.c | 46 +++++++++ src/libvirt_private.syms | 7 + src/libvirt_public.syms | 1 src/nodeinfo.c | 215 ++++++++++++++++++++++++++++++++++++++++++ src/nodeinfo.h | 14 +++ src/qemu/qemu_driver.c | 6 + src/remote/remote_driver.c | 1 src/remote/remote_protocol.x | 12 ++ src/util/threads-pthread.c | 17 +++ src/util/threads.h | 1 12 files changed, 332 insertions(+), 2 deletions(-)

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@linux.vnet.ibm.com> --- include/libvirt/libvirt.h.in | 5 + src/libvirt_private.syms | 7 + src/nodeinfo.c | 215 ++++++++++++++++++++++++++++++++++++++++++ src/nodeinfo.h | 14 +++ src/qemu/qemu_driver.c | 5 + src/util/threads-pthread.c | 17 +++ src/util/threads.h | 1 7 files changed, 263 insertions(+), 1 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index 5f6a07a..35153a4 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -3238,6 +3238,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 189c597..4060264 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -837,6 +837,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..8c0691c 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,210 @@ 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; +} + +/** + * 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: 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 98f4d7f..fda51c9 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 82ce5c6..67b73b4 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);

Define the required interfaces to make the API publicly accessible. Signed-off-by: Srivatsa S. Bhat <srivatsa.bhat@linux.vnet.ibm.com> --- include/libvirt/libvirt.h.in | 4 ++++ src/driver.h | 5 +++++ src/libvirt.c | 46 ++++++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 1 + src/qemu/qemu_driver.c | 1 + src/remote/remote_driver.c | 1 + src/remote/remote_protocol.x | 12 ++++++++++- 7 files changed, 69 insertions(+), 1 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index 35153a4..3262870 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -989,6 +989,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 */ diff --git a/src/driver.h b/src/driver.h index b899d0e..40b9aca 100644 --- a/src/driver.h +++ b/src/driver.h @@ -735,6 +735,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: @@ -893,6 +897,7 @@ struct _virDriver { virDrvDomainGetBlockJobInfo domainGetBlockJobInfo; virDrvDomainBlockJobSetSpeed domainBlockJobSetSpeed; virDrvDomainBlockPull domainBlockPull; + virDrvNodeSuspendForDuration nodeSuspendForDuration; }; typedef int diff --git a/src/libvirt.c b/src/libvirt.c index f1e6a6b..6f09109 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -6166,6 +6166,52 @@ 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_public.syms b/src/libvirt_public.syms index 9762fc4..44f59ad 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/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index fda51c9..424d020 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -10800,6 +10800,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 4dc6974..75af2c8 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -4433,6 +4433,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 f95253e..380428a 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -2253,6 +2253,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. */ @@ -2546,7 +2555,8 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_SNAPSHOT_GET_PARENT = 244, /* autogen autogen priority:high */ REMOTE_PROC_DOMAIN_RESET = 245, /* autogen autogen */ 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_SNAPSHOT_LIST_CHILDREN_NAMES = 247, /* autogen autogen priority:high */ + REMOTE_PROC_NODE_SUSPEND_FOR_DURATION = 248 /* autogen autogen priority:high */ /* * Notice how the entries are grouped in sets of 10 ?
participants (1)
-
Srivatsa S. Bhat