[libvirt] [PATCH] lxc: suspend/resume support

Ryota Ozaki ozaki.ryota at gmail.com
Wed Sep 16 15:42:08 UTC 2009


Hi

I've updated the patch with Chris' suggestions.

The change includes
- not wait for long period in the case that the freezing
  is complete quickly without increasing the number of polling
  in the case the freezing is slow or never complete
- add event notifications

Thanks,
  ozaki-r

>From 843d989cf1584e7622bd82a023241a9b333deee2 Mon Sep 17 00:00:00 2001
From: Ryota Ozaki <ozaki.ryota at gmail.com>
Date: Thu, 17 Sep 2009 00:26:24 +0900
Subject: [PATCH] lxc: suspend/resume support

---
 src/cgroup.c      |   23 ++++++-
 src/cgroup.h      |    4 +
 src/domain_conf.c |   27 ++++---
 src/lxc_driver.c  |  212 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 251 insertions(+), 15 deletions(-)

diff --git a/src/cgroup.c b/src/cgroup.c
index 111601d..2e646fd 100644
--- a/src/cgroup.c
+++ b/src/cgroup.c
@@ -32,7 +32,8 @@
 #define CGROUP_MAX_VAL 512

 VIR_ENUM_IMPL(virCgroupController, VIR_CGROUP_CONTROLLER_LAST,
-              "cpu", "cpuacct", "cpuset", "memory", "devices");
+              "cpu", "cpuacct", "cpuset", "memory", "devices",
+              "freezer");

 struct virCgroupController {
     int type;
@@ -896,3 +897,23 @@ int virCgroupGetCpuacctUsage(virCgroupPtr group,
unsigned long long *usage)
                                 VIR_CGROUP_CONTROLLER_CPUACCT,
                                 "cpuacct.usage", (uint64_t *)usage);
 }
+
+int virCgroupSetFreezerState(virCgroupPtr group, const char *state)
+{
+    return virCgroupSetValueStr(group,
+                                VIR_CGROUP_CONTROLLER_CPU,
+                                "freezer.state", state);
+}
+
+int virCgroupGetFreezerState(virCgroupPtr group, char **state)
+{
+    int ret;
+    ret = virCgroupGetValueStr(group,
+                                VIR_CGROUP_CONTROLLER_CPU,
+                                "freezer.state", state);
+    if (ret == 0) {
+        char *p = strchr(*state, '¥n');
+        if (p) *p = '¥0';
+    }
+    return ret;
+}
diff --git a/src/cgroup.h b/src/cgroup.h
index 6d43b14..aba56c6 100644
--- a/src/cgroup.h
+++ b/src/cgroup.h
@@ -21,6 +21,7 @@ enum {
     VIR_CGROUP_CONTROLLER_CPUSET,
     VIR_CGROUP_CONTROLLER_MEMORY,
     VIR_CGROUP_CONTROLLER_DEVICES,
+    VIR_CGROUP_CONTROLLER_FREEZER,

     VIR_CGROUP_CONTROLLER_LAST
 };
@@ -68,6 +69,9 @@ int virCgroupGetCpuShares(virCgroupPtr group,
unsigned long long *shares);

 int virCgroupGetCpuacctUsage(virCgroupPtr group, unsigned long long *usage);

+int virCgroupSetFreezerState(virCgroupPtr group, const char *state);
+int virCgroupGetFreezerState(virCgroupPtr group, char **state);
+
 int virCgroupRemove(virCgroupPtr group);

 void virCgroupFree(virCgroupPtr *group);
diff --git a/src/domain_conf.c b/src/domain_conf.c
index 5ae0775..5e37d96 100644
--- a/src/domain_conf.c
+++ b/src/domain_conf.c
@@ -4433,19 +4433,22 @@ char *virDomainObjFormat(virConnectPtr conn,
                       virDomainStateTypeToString(obj->state),
                       obj->pid);

-    switch (obj->monitor_chr->type) {
-    case VIR_DOMAIN_CHR_TYPE_UNIX:
-        monitorpath = obj->monitor_chr->data.nix.path;
-        break;
-    default:
-    case VIR_DOMAIN_CHR_TYPE_PTY:
-        monitorpath = obj->monitor_chr->data.file.path;
-        break;
-    }
+    /* obj->monitor_chr is set only for qemu */
+    if (obj->monitor_chr) {
+        switch (obj->monitor_chr->type) {
+        case VIR_DOMAIN_CHR_TYPE_UNIX:
+            monitorpath = obj->monitor_chr->data.nix.path;
+            break;
+        default:
+        case VIR_DOMAIN_CHR_TYPE_PTY:
+            monitorpath = obj->monitor_chr->data.file.path;
+            break;
+        }

-    virBufferEscapeString(&buf, "  <monitor path='%s'", monitorpath);
-    virBufferVSprintf(&buf, " type='%s'/>¥n",
-                      virDomainChrTypeToString(obj->monitor_chr->type));
+        virBufferEscapeString(&buf, "  <monitor path='%s'", monitorpath);
+        virBufferVSprintf(&buf, " type='%s'/>¥n",
+                          virDomainChrTypeToString(obj->monitor_chr->type));
+    }


     if (obj->nvcpupids) {
diff --git a/src/lxc_driver.c b/src/lxc_driver.c
index 0ec1e92..e4825b4 100644
--- a/src/lxc_driver.c
+++ b/src/lxc_driver.c
@@ -1862,6 +1862,214 @@ static char *lxcGetHostname (virConnectPtr conn)
     return result;
 }

+static int lxcFreezeContainer(lxc_driver_t *driver, virDomainObjPtr vm)
+{
+    int timeout = 1000; /* In milliseconds */
+    int check_interval = 1; /* In milliseconds */
+    int exp = 10;
+    int waited_time = 0;
+    int ret = -1;
+    char *state = NULL;
+    virCgroupPtr cgroup = NULL;
+
+    if (!(driver->cgroup &&
+        virCgroupForDomain(driver->cgroup, vm->def->name, &cgroup, 0) == 0))
+        return -1;
+
+    while (waited_time < timeout) {
+        int r;
+        /*
+         * Writing "FROZEN" to the "freezer.state" freezes the group,
+         * i.e., the container, temporarily transiting "FREEZING" state.
+         * Once the freezing is completed, the state of the group transits
+         * to "FROZEN".
+         * (see linux-2.6/Documentation/cgroups/freezer-subsystem.txt)
+         */
+        r = virCgroupSetFreezerState(cgroup, "FROZEN");
+
+        /*
+         * Returning EBUSY explicitly indicates that the group is
+         * being freezed but incomplete and other errors are true
+         * errors.
+         */
+        if (r < 0 && r != -EBUSY) {
+            VIR_DEBUG("Writing freezer.state failed with errno: %d", r);
+            goto error;
+        }
+        if (r == -EBUSY)
+            VIR_DEBUG0("Writing freezer.state gets EBUSY");
+
+        /*
+         * Unfortunately, returning 0 (success) is likely to happen
+         * even when the freezing has not been completed. Sometimes
+         * the state of the group remains "FREEZING" like when
+         * returning -EBUSY and even worse may never transit to
+         * "FROZEN" even if writing "FROZEN" again.
+         *
+         * So we don't trust the return value anyway and always
+         * decide that the freezing has been complete only with
+         * the state actually transit to "FROZEN".
+         */
+        usleep(check_interval * 1000);
+
+        r = virCgroupGetFreezerState(cgroup, &state);
+
+        if (r < 0) {
+            VIR_DEBUG("Reading freezer.state failed with errno: %d", r);
+            goto error;
+        }
+        VIR_DEBUG("Read freezer.state: %s", state);
+
+        if (STREQ(state, "FROZEN")) {
+            ret = 0;
+            goto cleanup;
+        }
+
+        waited_time += check_interval;
+        /*
+         * Increasing check_interval exponentially starting with
+         * small initial value treats nicely two cases; One is
+         * a container is under no load and waiting for long period
+         * makes no sense. The other is under heavy load. The container
+         * may stay longer time in FREEZING or never transit to FROZEN.
+         * In that case, eager polling will just waste CPU time.
+         */
+        check_interval *= exp;
+        VIR_FREE(state);
+    }
+    VIR_DEBUG0("lxcFreezeContainer timeout");
+error:
+    /*
+     * If timeout or an error on reading the state occurs,
+     * activate the group again and return an error.
+     * This is likely to fall the group back again gracefully.
+     */
+    virCgroupSetFreezerState(cgroup, "THAWED");
+    ret = -1;
+
+cleanup:
+    if (cgroup)
+        virCgroupFree(&cgroup);
+    VIR_FREE(state);
+    return ret;
+}
+
+static int lxcDomainSuspend(virDomainPtr dom)
+{
+    lxc_driver_t *driver = dom->conn->privateData;
+    virDomainObjPtr vm;
+    virDomainEventPtr event = NULL;
+    int ret = -1;
+
+    lxcDriverLock(driver);
+    vm = virDomainFindByUUID(&driver->domains, dom->uuid);
+
+    if (!vm) {
+        char uuidstr[VIR_UUID_STRING_BUFLEN];
+        virUUIDFormat(dom->uuid, uuidstr);
+        lxcError(dom->conn, dom, VIR_ERR_NO_DOMAIN,
+                 _("no domain with matching uuid '%s'"), uuidstr);
+        goto cleanup;
+    }
+
+    if (!virDomainIsActive(vm)) {
+        lxcError(dom->conn, dom, VIR_ERR_OPERATION_INVALID,
+                         "%s", _("domain is not running"));
+        goto cleanup;
+    }
+
+    if (vm->state != VIR_DOMAIN_PAUSED) {
+        if (lxcFreezeContainer(driver, vm) < 0) {
+            lxcError(dom->conn, dom, VIR_ERR_OPERATION_FAILED,
+                             "%s", _("suspend operation failed"));
+            goto cleanup;
+        }
+        vm->state = VIR_DOMAIN_PAUSED;
+    }
+
+    if (virDomainSaveStatus(dom->conn, driver->stateDir, vm) < 0)
+        goto cleanup;
+    ret = 0;
+
+    event = virDomainEventNewFromObj(vm,
+                                     VIR_DOMAIN_EVENT_SUSPENDED,
+                                     VIR_DOMAIN_EVENT_SUSPENDED_PAUSED);
+
+cleanup:
+    if (event)
+        lxcDomainEventQueue(driver, event);
+    if (vm)
+        virDomainObjUnlock(vm);
+    lxcDriverUnlock(driver);
+    return ret;
+}
+
+static int lxcUnfreezeContainer(lxc_driver_t *driver, virDomainObjPtr vm)
+{
+    int ret;
+    virCgroupPtr cgroup = NULL;
+
+    if (!(driver->cgroup &&
+        virCgroupForDomain(driver->cgroup, vm->def->name, &cgroup, 0) == 0))
+        return -1;
+
+    ret = virCgroupSetFreezerState(cgroup, "THAWED");
+
+    virCgroupFree(&cgroup);
+    return ret;
+}
+
+static int lxcDomainResume(virDomainPtr dom)
+{
+    lxc_driver_t *driver = dom->conn->privateData;
+    virDomainObjPtr vm;
+    virDomainEventPtr event = NULL;
+    int ret = -1;
+
+    lxcDriverLock(driver);
+    vm = virDomainFindByUUID(&driver->domains, dom->uuid);
+
+    if (!vm) {
+        char uuidstr[VIR_UUID_STRING_BUFLEN];
+        virUUIDFormat(dom->uuid, uuidstr);
+        lxcError(dom->conn, dom, VIR_ERR_NO_DOMAIN,
+                 _("no domain with matching uuid '%s'"), uuidstr);
+        goto cleanup;
+    }
+
+    if (!virDomainIsActive(vm)) {
+        lxcError(dom->conn, dom, VIR_ERR_OPERATION_INVALID,
+                         "%s", _("domain is not running"));
+        goto cleanup;
+    }
+
+    if (vm->state == VIR_DOMAIN_PAUSED) {
+        if (lxcUnfreezeContainer(driver, vm) < 0) {
+            lxcError(dom->conn, dom, VIR_ERR_OPERATION_FAILED,
+                             "%s", _("resume operation failed"));
+            goto cleanup;
+        }
+        vm->state = VIR_DOMAIN_RUNNING;
+    }
+
+    if (virDomainSaveStatus(dom->conn, driver->stateDir, vm) < 0)
+        goto cleanup;
+    ret = 0;
+
+    event = virDomainEventNewFromObj(vm,
+                                     VIR_DOMAIN_EVENT_RESUMED,
+                                     VIR_DOMAIN_EVENT_RESUMED_UNPAUSED);
+
+cleanup:
+    if (event)
+        lxcDomainEventQueue(driver, event);
+    if (vm)
+        virDomainObjUnlock(vm);
+    lxcDriverUnlock(driver);
+    return ret;
+}
+
+
 /* Function Tables */
 static virDriver lxcDriver = {
     VIR_DRV_LXC, /* the number virDrvNo */
@@ -1881,8 +2089,8 @@ static virDriver lxcDriver = {
     lxcDomainLookupByID, /* domainLookupByID */
     lxcDomainLookupByUUID, /* domainLookupByUUID */
     lxcDomainLookupByName, /* domainLookupByName */
-    NULL, /* domainSuspend */
-    NULL, /* domainResume */
+    lxcDomainSuspend, /* domainSuspend */
+    lxcDomainResume, /* domainResume */
     lxcDomainShutdown, /* domainShutdown */
     NULL, /* domainReboot */
     lxcDomainDestroy, /* domainDestroy */
-- 
1.6.0.6
-------------- next part --------------
A non-text attachment was scrubbed...
Name: 0001-lxc-suspend-resume-support.patch
Type: application/octet-stream
Size: 11138 bytes
Desc: not available
URL: <http://listman.redhat.com/archives/libvir-list/attachments/20090917/9f45709e/attachment-0001.obj>


More information about the libvir-list mailing list