[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

[libvirt] [PATCH 20/20] snapshot: qemu: Implement reverting of external snapshots



This patch adds support for reverting of external snapshots. The support
is somewhat limited yet (you can only revert to a snapshot that has no
children or delete the children that would have their image chains
invalidated).

While reverting an external snapshot, the domain has to be taken offline
as there's no possiblity to start a migration from file on a running
machine. This poses a few dificulties when the user has virt-viewer
attached as apropriate events need to be re-emited even if the machine
doesn't change states.
---
 src/qemu/qemu_driver.c | 333 ++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 328 insertions(+), 5 deletions(-)

diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 196004a..522bcf6 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -11936,6 +11936,327 @@ qemuDomainSnapshotRevertInactive(struct qemud_driver *driver,
     return ret > 0 ? -1 : ret;
 }

+/* helper to create lifecycle for possible outcomes of reverting snapshots */
+static int
+qemuDomainSnapshotCreateEvent(struct qemud_driver *driver,
+                              virDomainObjPtr vm,
+                              virDomainState old_state,
+                              virDomainState new_state)
+{
+    int reason = 0; /* generic unknown reason */
+    int type = -1;
+    virDomainEventPtr event;
+
+    switch (new_state) {
+    case VIR_DOMAIN_RUNNING:
+        switch (old_state) {
+        case VIR_DOMAIN_NOSTATE:
+        case VIR_DOMAIN_SHUTOFF:
+        case VIR_DOMAIN_CRASHED:
+        case VIR_DOMAIN_RUNNING:
+        case VIR_DOMAIN_BLOCKED:
+        case VIR_DOMAIN_SHUTDOWN:
+            type = VIR_DOMAIN_EVENT_STARTED;
+            reason = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT;
+            break;
+
+        case VIR_DOMAIN_PMSUSPENDED:
+        case VIR_DOMAIN_PAUSED:
+            type = VIR_DOMAIN_EVENT_RESUMED;
+            reason = VIR_DOMAIN_EVENT_RESUMED_FROM_SNAPSHOT;
+            break;
+
+       case VIR_DOMAIN_LAST:
+            /* no event */
+            break;
+        }
+        break;
+
+    case VIR_DOMAIN_PAUSED:
+        switch (old_state) {
+        case VIR_DOMAIN_NOSTATE:
+        case VIR_DOMAIN_SHUTOFF:
+        case VIR_DOMAIN_CRASHED:
+            /* the machine was started here, so we need an aditional event */
+            event = virDomainEventNewFromObj(vm,
+                                             VIR_DOMAIN_EVENT_STARTED,
+                                             VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT);
+            if (!event)
+                goto no_memory;
+            qemuDomainEventQueue(driver, event);
+            /* fallthrough! */
+        case VIR_DOMAIN_PMSUSPENDED:
+        case VIR_DOMAIN_RUNNING:
+        case VIR_DOMAIN_SHUTDOWN:
+        case VIR_DOMAIN_BLOCKED:
+            type = VIR_DOMAIN_EVENT_SUSPENDED;
+            reason = VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT;
+            break;
+
+        case VIR_DOMAIN_PAUSED:
+        case VIR_DOMAIN_LAST:
+            /* no event */
+            break;
+        }
+        break;
+
+    case VIR_DOMAIN_SHUTOFF:
+        switch (old_state) {
+        case VIR_DOMAIN_NOSTATE:
+        case VIR_DOMAIN_BLOCKED:
+        case VIR_DOMAIN_SHUTDOWN:
+        case VIR_DOMAIN_PAUSED:
+        case VIR_DOMAIN_RUNNING:
+        case VIR_DOMAIN_PMSUSPENDED:
+            type = VIR_DOMAIN_EVENT_STOPPED;
+            reason = VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT;
+            break;
+
+        case VIR_DOMAIN_SHUTOFF:
+        case VIR_DOMAIN_CRASHED:
+        case VIR_DOMAIN_LAST:
+            /* no event */
+            break;
+        }
+        break;
+
+    case VIR_DOMAIN_PMSUSPENDED:
+        switch (old_state) {
+        case VIR_DOMAIN_NOSTATE:
+        case VIR_DOMAIN_SHUTOFF:
+        case VIR_DOMAIN_CRASHED:
+            /* the machine was started here, so we need an aditional event */
+            event = virDomainEventNewFromObj(vm,
+                                             VIR_DOMAIN_EVENT_STARTED,
+                                             VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT);
+            if (!event)
+                goto no_memory;
+
+            qemuDomainEventQueue(driver, event);
+            /* fallthrough! */
+        case VIR_DOMAIN_BLOCKED:
+        case VIR_DOMAIN_SHUTDOWN:
+        case VIR_DOMAIN_PAUSED:
+        case VIR_DOMAIN_RUNNING:
+            type = VIR_DOMAIN_EVENT_PMSUSPENDED;
+            reason = VIR_DOMAIN_EVENT_PMSUSPENDED_FROM_SNAPSHOT;
+            break;
+
+        case VIR_DOMAIN_PMSUSPENDED:
+        case VIR_DOMAIN_LAST:
+            /* no event */
+            break;
+        }
+        break;
+
+    /* fallthrough */
+    case VIR_DOMAIN_NOSTATE:
+    case VIR_DOMAIN_BLOCKED:
+    case VIR_DOMAIN_SHUTDOWN:
+    case VIR_DOMAIN_CRASHED:
+    case VIR_DOMAIN_LAST:
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("Suspicious domain state '%d' after snapshot"),
+                       new_state);
+        return -1;
+    }
+
+    if (!(event = virDomainEventNewFromObj(vm, type, reason)))
+        goto no_memory;
+
+    qemuDomainEventQueue(driver, event);
+
+    return 0;
+
+no_memory:
+    virReportOOMError();
+    return -1;
+}
+
+
+/* The domain and driver is expected to be locked */
+static int
+qemuDomainSnapshotRevertExternal(virConnectPtr conn,
+                                 struct qemud_driver *driver,
+                                 virDomainObjPtr vm,
+                                 virDomainSnapshotObjPtr snap,
+                                 unsigned int flags)
+{
+    int ret = -1;
+    int loadfd = -1;
+
+    virDomainDefPtr save_def = NULL;
+    struct qemud_save_header header;
+    virFileWrapperFdPtr wrapperFd = NULL;
+
+    virDomainState old_state = virDomainObjGetState(vm, NULL);
+    virDomainDefPtr config = NULL;
+
+    struct qemu_snap_remove rem;
+
+    /* check if snapshot has children */
+    if (snap->nchildren > 0 &&
+        !(flags & VIR_DOMAIN_SNAPSHOT_REVERT_FORCE)) {
+        virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY,
+                       _("Can't revert to snapshot '%s'. "
+                         "Snapshot has children."), snap->def->name);
+        goto cleanup;
+    }
+
+    if (vm->current_snapshot) {
+        vm->current_snapshot->def->current = false;
+        if (qemuDomainSnapshotWriteMetadata(vm, vm->current_snapshot,
+                                            driver->snapshotDir) < 0)
+            goto cleanup;
+        vm->current_snapshot = NULL;
+        /* XXX Should we restore vm->current_snapshot after this point
+         * in the failure cases where we know there was no change?  */
+    }
+
+    /* Prepare to copy the snapshot inactive xml as the config of this
+     * domain.  Easiest way is by a round trip through xml.
+     *
+     * XXX Should domain snapshots track live xml rather
+     * than inactive xml?  */
+    snap->def->current = true;
+    if (snap->def->dom) {
+        char *xml;
+        if (!(xml = qemuDomainDefFormatXML(driver,
+                                           snap->def->dom,
+                                           VIR_DOMAIN_XML_INACTIVE |
+                                           VIR_DOMAIN_XML_SECURE |
+                                           VIR_DOMAIN_XML_MIGRATABLE)))
+            goto cleanup;
+
+        config = virDomainDefParseString(driver->caps, xml,
+                                         QEMU_EXPECTED_VIRT_TYPES,
+                                         VIR_DOMAIN_XML_INACTIVE);
+        VIR_FREE(xml);
+        if (!config)
+            goto cleanup;
+    } else {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("Domain definition not found in snapshot '%s'"),
+                       snap->def->name);
+        goto cleanup;
+    }
+
+    /* try to open the memory save image if one exists */
+    if (snap->def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) {
+        if (!snap->def->file || !virFileExists(snap->def->file)) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("Memory save file '%s' does not exist."),
+                           snap->def->file);
+            goto cleanup;
+        }
+
+        /* open the save image file */
+        if ((loadfd = qemuDomainSaveImageOpen(driver, snap->def->file,
+                                              &save_def, &header,
+                                              false, &wrapperFd, NULL,
+                                              -1, false, false)) < 0)
+            goto cleanup;
+
+        /* opening succeeded, there's a big probability the revert will work.
+         * We can now get rid of the active qemu, if it runs */
+    }
+
+    if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_MODIFY) < 0)
+        goto cleanup;
+
+    /* Now we destroy the (possibly) running qemu process.
+     * Unfortunately, the guest can be restored only using incomming migration.
+     */
+    if (virDomainObjIsActive(vm)) {
+        qemuProcessStop(driver, vm,
+                        VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT, 0);
+        virDomainAuditStop(vm, "from-snapshot");
+    }
+
+    /* remove snapshot descendatns, as we're invalidating the image chain */
+
+    rem.driver = driver;
+    rem.vm = vm;
+    rem.metadata_only = false;
+    rem.err = 0;
+    rem.current = false;
+    virDomainSnapshotForEachDescendant(snap,
+                                       qemuDomainSnapshotDiscardAll,
+                                       &rem);
+    if (rem.err < 0)
+        goto endjob;
+
+    /* assign domain definition */
+    virDomainObjAssignDef(vm, config, false);
+    config = NULL;
+
+    /* wipe and re-create disk images */
+    if (qemuDomainSnapshotCreateInactiveExternal(driver, vm, snap, true) < 0)
+        goto endjob;
+
+    /* save the config files */
+    if (virDomainSaveConfig(driver->configDir, vm->def) < 0) {
+        virReportError(VIR_ERR_OPERATION_INVALID, "%s",
+                       _("Write to config file failed"));
+        goto endjob;
+    }
+
+    /* re-start the guest if necessary */
+    if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_STOPPED) &&
+        (snap->def->state == VIR_DOMAIN_RUNNING ||
+         snap->def->state == VIR_DOMAIN_PAUSED ||
+         flags & VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING ||
+         flags & VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) {
+
+        if (loadfd < 0) {
+            virReportError(VIR_ERR_INVALID_ARG, "%s",
+                           _("Can't revert snapshot to running state. "
+                             "Memory image not found."));
+            goto endjob;
+        }
+
+        if ((ret = qemuDomainSaveImageLoad(conn, driver,
+                                           vm, &loadfd,
+                                           &header, snap->def->file)) < 0) {
+            virDomainAuditStart(vm, "from-snapshot", false);
+            goto endjob;
+        }
+
+        virDomainAuditStart(vm, "from-snapshot", true);
+
+        if ((snap->def->state == VIR_DOMAIN_RUNNING &&
+             !(flags & VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) ||
+            flags & VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING) {
+            if (qemuProcessStartCPUs(driver, vm, conn,
+                                     VIR_DOMAIN_RUNNING_FROM_SNAPSHOT,
+                                     QEMU_ASYNC_JOB_NONE) < 0)
+                goto endjob;
+        }
+    }
+
+    /* create corresponding life cycle events */
+    if (qemuDomainSnapshotCreateEvent(driver, vm, old_state,
+                                      virDomainObjGetState(vm, NULL)) < 0)
+        goto endjob;
+
+    ret = 0;
+endjob:
+    if (qemuDomainObjEndJob(driver, vm) == 0) {
+        vm = NULL;
+    } else if (ret < 0 && !vm->persistent) {
+        qemuDomainRemoveInactive(driver, vm);
+        vm = NULL;
+    }
+
+cleanup:
+    virDomainDefFree(config);
+    virDomainDefFree(save_def);
+    VIR_FORCE_CLOSE(loadfd);
+    virFileWrapperFdFree(wrapperFd);
+
+    return ret;
+}
+
 static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot,
                                       unsigned int flags)
 {
@@ -11991,12 +12312,14 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot,
                          "to revert to inactive snapshot"));
         goto cleanup;
     }
-    if (snap->def->state == VIR_DOMAIN_DISK_SNAPSHOT) {
-        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
-                       _("revert to external disk snapshot not supported "
-                         "yet"));
+
+    /* Check if reverting an external snapshot */
+    if (virDomainSnapshotIsExternal(snap)) {
+        ret = qemuDomainSnapshotRevertExternal(snapshot->domain->conn,
+                                               driver, vm, snap, flags);
         goto cleanup;
     }
+
     if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_FORCE)) {
         if (!snap->def->dom) {
             virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY,
@@ -12015,7 +12338,6 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot,
         }
     }

-
     if (vm->current_snapshot) {
         vm->current_snapshot->def->current = false;
         if (qemuDomainSnapshotWriteMetadata(vm, vm->current_snapshot,
@@ -12048,6 +12370,7 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot,
             goto cleanup;
     }

+    /* Internal snapshot revert code starts below */
     if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_MODIFY) < 0)
         goto cleanup;

-- 
1.7.12.4


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]