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 possibility to start a migration from file on a running
machine. This poses a few difficulties when the user has virt-viewer
attached as appropriate events need to be re-emitted even if the machine
doesn't change states.
---
Adapt for the revert flag and a few minor fixes.
---
src/qemu/qemu_driver.c | 330 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 325 insertions(+), 5 deletions(-)
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 2178798..d66551b 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -12112,6 +12112,315 @@ qemuDomainSnapshotRevertInactive(struct qemud_driver *driver,
return ret > 0 ? -1 : ret;
}
+/* helper to create lifecycle for possible outcomes of reverting snapshots
+ * the reemit parameter is used when specific events need to be reemitted
+ * so that virt-viewer for example is able to re-connect */
+static int
+qemuDomainSnapshotCreateEvent(struct qemud_driver *driver,
+ virDomainObjPtr vm,
+ virDomainState old_state,
+ virDomainState new_state,
+ bool reemit)
+{
+ int reason = 0; /* generic unknown reason */
+ int type = -1;
+ virDomainEventPtr event;
+
+ switch (new_state) {
+ case VIR_DOMAIN_RUNNING:
+ switch (old_state) {
+ case VIR_DOMAIN_RUNNING:
+ if (!reemit)
+ break;
+ /* fallthrough */
+ case VIR_DOMAIN_NOSTATE:
+ case VIR_DOMAIN_SHUTOFF:
+ case VIR_DOMAIN_CRASHED:
+ 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 additional 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;
+
+ /* fallthrough */
+ case VIR_DOMAIN_NOSTATE:
+ case VIR_DOMAIN_BLOCKED:
+ case VIR_DOMAIN_SHUTDOWN:
+ case VIR_DOMAIN_CRASHED:
+ case VIR_DOMAIN_LAST:
+ case VIR_DOMAIN_PMSUSPENDED:
+ 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;
+
+ /* check if domain is running and should be running afterwards:
+ * qemu has to be re-started to allow the migration from file
+ */
+ if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_RESPAWN) &&
+ virDomainObjIsActive(vm) &&
+ !(snap->def->state == VIR_DOMAIN_DISK_SNAPSHOT ||
+ snap->def->state == VIR_DOMAIN_SHUTOFF)) {
+ virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY,
+ _("Reverting of snapshot '%s' needs to restart the
"
+ "hypervisor."), snap->def->name);
+ goto cleanup;
+ }
+
+ /* check if snapshot has children */
+ if (snap->nchildren > 0) {
+ if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_FORCE)) {
+ virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY, "%s",
+ _("Reverting of snapshots with children "
+ "is not implemented yet."));
+ goto cleanup;
+ } else {
+ VIR_WARN("Invalidating image chain of snapshot '%s'.",
+ snap->def->name);
+ }
+ }
+
+ 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 and it's needed */
+ 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 (snap->def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) {
+ if (!snap->def->file) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Memory image path does not exist for "
+ "snapshot '%s'"),
snap->def->name);
+ goto cleanup;
+ }
+
+ if (!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 */
+ } else {
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+ _("Can't revert snapshot to running state: "
+ "Memory image not found"));
+ goto cleanup;
+ }
+ }
+
+ 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 incoming migration.
+ */
+ if (virDomainObjIsActive(vm)) {
+ qemuProcessStop(driver, vm,
+ VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT, 0);
+ virDomainAuditStop(vm, "from-snapshot");
+ }
+
+ /* assign domain definition */
+ virDomainObjAssignDef(vm, config, false);
+ config = NULL;
+
+ /* wipe and re-create disk images - qemu-img doesn't care if it exists*/
+ if (qemuDomainSnapshotCreateInactiveExternal(driver, vm, snap, false) < 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 (loadfd > 0) {
+ 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),
+ !!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_RESPAWN))
< 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)
{
@@ -12128,8 +12437,9 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr
snapshot,
virDomainDefPtr config = NULL;
virCheckFlags(VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING |
- VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED |
- VIR_DOMAIN_SNAPSHOT_REVERT_FORCE |
+ VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED |
+ VIR_DOMAIN_SNAPSHOT_REVERT_STOPPED |
+ VIR_DOMAIN_SNAPSHOT_REVERT_FORCE |
VIR_DOMAIN_SNAPSHOT_REVERT_RESPAWN, -1);
if (flags & VIR_DOMAIN_SNAPSHOT_REVERT_FORCE)
@@ -12176,12 +12486,21 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr
snapshot,
"to revert to inactive snapshot"));
goto cleanup;
}
- if (snap->def->state == VIR_DOMAIN_DISK_SNAPSHOT) {
+
+ /* 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_STOPPED)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
- _("revert to external disk snapshot not supported "
- "yet"));
+ _("VIR_DOMAIN_SNAPSHOT_REVERT_STOPPED is not supported "
+ "with internal snapshots"));
goto cleanup;
}
+
if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_FORCE)) {
if (!snap->def->dom) {
virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY,
@@ -12233,6 +12552,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.8.0