diff --git a/po/POTFILES.in b/po/POTFILES.in index c10526524d..771fe49ee1 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -130,6 +130,7 @@ src/util/virnetdevbridge.c src/util/virnetdevmacvlan.c src/util/virnetdevtap.c src/util/virnetdevvportprofile.c +src/util/virnodesuspend.c src/util/virpidfile.c src/util/virsocketaddr.c src/util/virterror.c diff --git a/src/Makefile.am b/src/Makefile.am index 33a32a85f7..91946149d8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -85,6 +85,7 @@ UTIL_SOURCES = \ util/util.c util/util.h \ util/viraudit.c util/viraudit.h \ util/virfile.c util/virfile.h \ + util/virnodesuspend.c util/virnodesuspend.h \ util/virpidfile.c util/virpidfile.h \ util/xml.c util/xml.h \ util/virterror.c util/virterror_internal.h \ diff --git a/src/libvirt.c b/src/libvirt.c index 02b8343c5f..d5bef92526 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -43,6 +43,7 @@ #include "conf.h" #include "rpc/virnettlscontext.h" #include "command.h" +#include "virnodesuspend.h" #ifndef WITH_DRIVER_MODULES # ifdef WITH_TEST @@ -399,7 +400,8 @@ virInitialize(void) if (virThreadInitialize() < 0 || virErrorInitialize() < 0 || - virRandomInitialize(time(NULL) ^ getpid())) + virRandomInitialize(time(NULL) ^ getpid()) || + virNodeSuspendInit() < 0) return -1; gcry_control(GCRYCTL_SET_THREAD_CBS, &virTLSThreadImpl); diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 0b21cdc2b6..9f2a224a21 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1329,6 +1329,11 @@ virNetTLSContextNewServer; virNetTLSContextNewServerPath; +# virnodesuspend.h +nodeSuspendForDuration; +virNodeSuspendInit; + + # virpidfile.h virPidFileAcquire; virPidFileAcquirePath; diff --git a/src/lxc/lxc_driver.c b/src/lxc/lxc_driver.c index 6f44d099da..c02fe70f53 100644 --- a/src/lxc/lxc_driver.c +++ b/src/lxc/lxc_driver.c @@ -56,6 +56,7 @@ #include "domain_nwfilter.h" #include "network/bridge_driver.h" #include "virnetdev.h" +#include "virnodesuspend.h" #define VIR_FROM_THIS VIR_FROM_LXC @@ -3922,6 +3923,7 @@ static virDriver lxcDriver = { .domainEventDeregisterAny = lxcDomainEventDeregisterAny, /* 0.8.0 */ .domainOpenConsole = lxcDomainOpenConsole, /* 0.8.6 */ .isAlive = lxcIsAlive, /* 0.9.8 */ + .nodeSuspendForDuration = nodeSuspendForDuration, /* 0.9.8 */ }; static virStateDriver lxcStateDriver = { diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 6cfdd1de5a..c4bf68e309 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -88,6 +88,7 @@ #include "locking/lock_manager.h" #include "locking/domain_lock.h" #include "virkeycode.h" +#include "virnodesuspend.h" #define VIR_FROM_THIS VIR_FROM_QEMU @@ -10913,6 +10914,7 @@ static virDriver qemuDriver = { .domainBlockJobSetSpeed = qemuDomainBlockJobSetSpeed, /* 0.9.4 */ .domainBlockPull = qemuDomainBlockPull, /* 0.9.4 */ .isAlive = qemuIsAlive, /* 0.9.8 */ + .nodeSuspendForDuration = nodeSuspendForDuration, /* 0.9.8 */ }; diff --git a/src/uml/uml_driver.c b/src/uml/uml_driver.c index 30c2086331..073f3620de 100644 --- a/src/uml/uml_driver.c +++ b/src/uml/uml_driver.c @@ -62,6 +62,7 @@ #include "fdstream.h" #include "configmake.h" #include "virnetdevtap.h" +#include "virnodesuspend.h" #define VIR_FROM_THIS VIR_FROM_UML @@ -2604,6 +2605,7 @@ static virDriver umlDriver = { .domainEventDeregisterAny = umlDomainEventDeregisterAny, /* 0.9.4 */ .domainOpenConsole = umlDomainOpenConsole, /* 0.8.6 */ .isAlive = umlIsAlive, /* 0.9.8 */ + .nodeSuspendForDuration = nodeSuspendForDuration, /* 0.9.8 */ }; static int diff --git a/src/util/virnodesuspend.c b/src/util/virnodesuspend.c new file mode 100644 index 0000000000..01feaa2b88 --- /dev/null +++ b/src/util/virnodesuspend.c @@ -0,0 +1,271 @@ +/* + * virnodesuspend.c: Support for suspending a node (host machine) + * + * Copyright (C) 2011 Srivatsa S. Bhat + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include "virnodesuspend.h" + +#include "command.h" +#include "threads.h" +#include "datatypes.h" + +#include "memory.h" +#include "logging.h" +#include "virterror_internal.h" + +#define VIR_FROM_THIS VIR_FROM_NONE + +#define virNodeSuspendError(code, ...) \ + virReportErrorHelper(VIR_FROM_NONE, code, __FILE__, \ + __FUNCTION__, __LINE__, __VA_ARGS__) + + +#define SUSPEND_DELAY 10 /* in seconds */ + +/* Give sufficient time for performing the suspend operation on the host */ +#define MIN_TIME_REQ_FOR_SUSPEND 60 /* in seconds */ + +/* + * Bitmask to hold the Power Management features supported by the host, + * such as Suspend-to-RAM, Suspend-to-Disk, Hybrid-Suspend etc. + */ +static unsigned int hostPMFeatures; + +virMutex virNodeSuspendMutex; + +static bool aboutToSuspend; + +static void virNodeSuspendLock(void) +{ + virMutexLock(&virNodeSuspendMutex); +} + +static void virNodeSuspendUnlock(void) +{ + virMutexUnlock(&virNodeSuspendMutex); +} + + +/** + * virNodeSuspendInit: + * + * Get the system-wide sleep states supported by the host, such as + * Suspend-to-RAM, Suspend-to-Disk, 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. + */ +int virNodeSuspendInit(void) +{ + + if (virMutexInit(&virNodeSuspendMutex) < 0) + return -1; + + /* Get the power management capabilities supported by the host */ + hostPMFeatures = 0; + if (virGetPMCapabilities(&hostPMFeatures) < 0) { + if (geteuid() == 0) + VIR_ERROR(_("Failed to get host power management features")); + } + + return 0; +} + + +/** + * virNodeSuspendSetNodeWakeup: + * @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. + */ +static int virNodeSuspendSetNodeWakeup(unsigned long long alarmTime) +{ + virCommandPtr setAlarmCmd; + int ret = -1; + + if (alarmTime <= MIN_TIME_REQ_FOR_SUSPEND) { + virNodeSuspendError(VIR_ERR_INVALID_ARG, "%s", _("Suspend duration is too short")); + return -1; + } + + setAlarmCmd = virCommandNewArgList("rtcwake", "-m", "no", "-s", NULL); + virCommandAddArgFormat(setAlarmCmd, "%lld", alarmTime); + + if (virCommandRun(setAlarmCmd, NULL) < 0) + goto cleanup; + + ret = 0; + +cleanup: + virCommandFree(setAlarmCmd); + return ret; +} + +/** + * virNodeSuspend: + * @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. + */ +static void virNodeSuspend(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) + VIR_WARN("Failed to suspend the host"); + + virCommandFree(suspendCmd); + + /* + * Now that we have resumed from suspend or the suspend failed, + * reset 'aboutToSuspend' flag. + */ + virNodeSuspendLock(); + aboutToSuspend = false; + virNodeSuspendUnlock(); +} + +/** + * nodeSuspendForDuration: + * @conn: pointer to the hypervisor connection + * @target: the state to which the host must be suspended to - + * VIR_NODE_SUSPEND_TARGET_MEM (Suspend-to-RAM), + * VIR_NODE_SUSPEND_TARGET_DISK (Suspend-to-Disk), + * VIR_NODE_SUSPEND_TARGET_HYBRID (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 Mem, Disk 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, + unsigned int target, + unsigned long long duration, + unsigned int flags) +{ + static virThread thread; + char *cmdString = NULL; + + virCheckFlags(0, -1); + + /* + * Ensure that we are the only ones trying to suspend. + * Fail if somebody has already initiated a suspend. + */ + virNodeSuspendLock(); + + if (aboutToSuspend) { + /* A suspend operation is already in progress */ + virNodeSuspendUnlock(); + return -1; + } else { + aboutToSuspend = true; + } + + virNodeSuspendUnlock(); + + /* Check if the host supports the requested suspend target */ + switch (target) { + case VIR_NODE_SUSPEND_TARGET_MEM: + if (hostPMFeatures & VIR_NODE_SUSPEND_TARGET_MEM) { + cmdString = strdup("pm-suspend"); + if (cmdString == NULL) { + virReportOOMError(); + goto cleanup; + } + break; + } + virNodeSuspendError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Suspend-to-RAM")); + goto cleanup; + + case VIR_NODE_SUSPEND_TARGET_DISK: + if (hostPMFeatures & VIR_NODE_SUSPEND_TARGET_DISK) { + cmdString = strdup("pm-hibernate"); + if (cmdString == NULL) { + virReportOOMError(); + goto cleanup; + } + break; + } + virNodeSuspendError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Suspend-to-Disk")); + goto cleanup; + + case VIR_NODE_SUSPEND_TARGET_HYBRID: + if (hostPMFeatures & VIR_NODE_SUSPEND_TARGET_HYBRID) { + cmdString = strdup("pm-suspend-hybrid"); + if (cmdString == NULL) { + virReportOOMError(); + goto cleanup; + } + break; + } + virNodeSuspendError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Hybrid-Suspend")); + goto cleanup; + + default: + virNodeSuspendError(VIR_ERR_INVALID_ARG, "%s", _("Invalid suspend target")); + goto cleanup; + } + + /* Just set the RTC alarm. Don't suspend yet. */ + if (virNodeSuspendSetNodeWakeup(duration) < 0) + goto cleanup; + + if (virThreadCreate(&thread, false, virNodeSuspend, (void *)cmdString) < 0) { + virNodeSuspendError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Failed to create thread to suspend the host\n")); + goto cleanup; + } + + return 0; + +cleanup: + VIR_FREE(cmdString); + return -1; +} diff --git a/src/util/virnodesuspend.h b/src/util/virnodesuspend.h new file mode 100644 index 0000000000..66e3214a19 --- /dev/null +++ b/src/util/virnodesuspend.h @@ -0,0 +1,36 @@ +/* + * virnodesuspend.h: Support for suspending a node (host machine) + * + * Copyright (C) 2011 Srivatsa S. Bhat + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + + +#ifndef __VIR_NODE_SUSPEND_H__ +# define __VIR_NODE_SUSPEND_H__ + +# include "internal.h" + +int nodeSuspendForDuration(virConnectPtr conn, + unsigned int target, + unsigned long long duration, + unsigned int flags); + +int virNodeSuspendInit(void); + + +#endif /* __VIR_NODE_SUSPEND_H__ */ diff --git a/src/xen/xen_driver.c b/src/xen/xen_driver.c index c0d45058dd..d394ff741f 100644 --- a/src/xen/xen_driver.c +++ b/src/xen/xen_driver.c @@ -51,6 +51,7 @@ #include "fdstream.h" #include "virfile.h" #include "command.h" +#include "virnodesuspend.h" #define VIR_FROM_THIS VIR_FROM_XEN @@ -2257,6 +2258,7 @@ static virDriver xenUnifiedDriver = { .domainEventDeregisterAny = xenUnifiedDomainEventDeregisterAny, /* 0.8.0 */ .domainOpenConsole = xenUnifiedDomainOpenConsole, /* 0.8.6 */ .isAlive = xenUnifiedIsAlive, /* 0.9.8 */ + .nodeSuspendForDuration = nodeSuspendForDuration, /* 0.9.8 */ }; /**