This patch adds support to take external system checkpoints.
The functionality is layered on top of the previous disk-only snapshot
code. When the checkpoint is requested the domain memory is saved to the
memory image file using live migration to file. (The user may specify to
do take the memory image while the guest is paused with the
VIR_DOMAIN_SNAPSHOT_CREATE_PAUSE flag.) This operation pauses the guest.
After the guest is paused the disk snapshot is taken.
The memory save image shares format with the image created by
virDomainSave() API.
---
src/qemu/qemu_driver.c | 212 +++++++++++++++++++++++++++++++------------------
1 file changed, 135 insertions(+), 77 deletions(-)
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 9a174a4..54cd88c 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -10970,31 +10970,25 @@ cleanup:
/* The domain is expected to be locked and active. */
static int
-qemuDomainSnapshotCreateDiskActive(virConnectPtr conn,
- struct qemud_driver *driver,
- virDomainObjPtr *vmptr,
+qemuDomainSnapshotCreateDiskActive(struct qemud_driver *driver,
+ virDomainObjPtr vm,
virDomainSnapshotObjPtr snap,
- unsigned int flags)
+ unsigned int flags,
+ enum qemuDomainAsyncJob asyncJob)
{
- virDomainObjPtr vm = *vmptr;
qemuDomainObjPrivatePtr priv = vm->privateData;
virJSONValuePtr actions = NULL;
- bool resume = false;
int ret = -1;
int i;
bool persist = false;
int thaw = 0; /* 1 if freeze succeeded, -1 if freeze failed */
- bool atomic = (flags & VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC) != 0;
bool reuse = (flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT) != 0;
virCgroupPtr cgroup = NULL;
- if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_MODIFY) < 0)
- return -1;
-
if (!virDomainObjIsActive(vm)) {
virReportError(VIR_ERR_OPERATION_INVALID,
"%s", _("domain is not running"));
- goto endjob;
+ goto cleanup;
}
if (qemuCgroupControllerActive(driver, VIR_CGROUP_CONTROLLER_DEVICES) &&
@@ -11002,7 +10996,7 @@ qemuDomainSnapshotCreateDiskActive(virConnectPtr conn,
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unable to find cgroup for %s"),
vm->def->name);
- goto endjob;
+ goto cleanup;
}
/* 'cgroup' is still NULL if cgroups are disabled. */
@@ -11014,34 +11008,14 @@ qemuDomainSnapshotCreateDiskActive(virConnectPtr conn,
if (qemuDomainSnapshotFSFreeze(driver, vm) < 0) {
/* helper reported the error */
thaw = -1;
- goto endjob;
+ goto cleanup;
} else {
thaw = 1;
}
}
- /* For multiple disks, libvirt must pause externally to get all
- * snapshots to be at the same point in time, unless qemu supports
- * transactions. For a single disk, snapshot is atomic without
- * requiring a pause. Thanks to qemuDomainSnapshotPrepare, if
- * we got to this point, the atomic flag now says whether we need
- * to pause, and a capability bit says whether to use transaction.
- */
- if (!atomic && virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) {
- if (qemuProcessStopCPUs(driver, vm, VIR_DOMAIN_PAUSED_SAVE,
- QEMU_ASYNC_JOB_NONE) < 0)
- goto cleanup;
-
- resume = true;
- if (!virDomainObjIsActive(vm)) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("guest unexpectedly quit"));
- goto cleanup;
- }
- }
if (qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION)) {
- actions = virJSONValueNewArray();
- if (!actions) {
+ if (!(actions = virJSONValueNewArray())) {
virReportOOMError();
goto cleanup;
}
@@ -11052,7 +11026,9 @@ qemuDomainSnapshotCreateDiskActive(virConnectPtr conn,
* Based on earlier qemuDomainSnapshotPrepare, all
* disks in this list are now either SNAPSHOT_NO, or
* SNAPSHOT_EXTERNAL with a valid file name and qcow2 format. */
- qemuDomainObjEnterMonitorWithDriver(driver, vm);
+ if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0)
+ goto cleanup;
+
for (i = 0; i < snap->def->ndisks; i++) {
virDomainDiskDefPtr persistDisk = NULL;
@@ -11106,9 +11082,96 @@ qemuDomainSnapshotCreateDiskActive(virConnectPtr conn,
}
}
qemuDomainObjExitMonitorWithDriver(driver, vm);
- if (ret < 0)
+
+cleanup:
+ virCgroupFree(&cgroup);
+
+ if (ret == 0 || !qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION)) {
+ if (virDomainSaveStatus(driver->caps, driver->stateDir, vm) < 0 ||
+ (persist && virDomainSaveConfig(driver->configDir, vm->newDef)
< 0))
+ ret = -1;
+ }
+
+ if (thaw != 0 &&
+ qemuDomainSnapshotFSThaw(driver, vm, thaw > 0) < 0) {
+ /* helper reported the error, if it was needed */
+ if (thaw > 0)
+ ret = -1;
+ }
+
+ return ret;
+}
+
+
+static int
+qemuDomainSnapshotCreateActiveExternal(virConnectPtr conn,
+ struct qemud_driver *driver,
+ virDomainObjPtr *vmptr,
+ virDomainSnapshotObjPtr snap,
+ unsigned int flags)
+{
+ bool resume = false;
+ int ret = -1;
+ virDomainObjPtr vm = *vmptr;
+ qemuDomainObjPrivatePtr priv = vm->privateData;
+ char *xml = NULL;
+ bool memory = snap->def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL;
+ bool atomic = !!(flags & VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC);
+ bool transaction = qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION);
+
+ if (qemuDomainObjBeginAsyncJobWithDriver(driver, vm,
+ QEMU_ASYNC_JOB_SNAPSHOT) < 0)
goto cleanup;
+ /* we need to resume the guest only if it was previously running */
+ if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) {
+ resume = true;
+
+ /* For multiple disks, libvirt must pause externally to get all
+ * snapshots to be at the same point in time, unless qemu supports
+ * transactions. For a single disk, snapshot is atomic without
+ * requiring a pause. Thanks to qemuDomainSnapshotPrepare, if
+ * we got to this point, the atomic flag now says whether we need
+ * to pause, and a capability bit says whether to use transaction.
+ */
+ if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_PAUSE ||
+ (atomic && !transaction)) {
+ if (qemuProcessStopCPUs(driver, vm, VIR_DOMAIN_PAUSED_SNAPSHOT,
+ QEMU_ASYNC_JOB_SNAPSHOT) < 0)
+ goto endjob;
+
+ if (!virDomainObjIsActive(vm)) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("guest unexpectedly quit"));
+ goto endjob;
+ }
+ }
+ }
+
+ /* do the memory snapshot if necessary */
+ if (memory) {
+ if (!(xml = qemuDomainDefFormatLive(driver, vm->def, true, false)))
+ goto endjob;
+
+ if ((ret = qemuDomainSaveMemory(driver, vm, snap->def->file,
+ xml, QEMUD_SAVE_FORMAT_RAW,
+ resume, 0,
+ QEMU_ASYNC_JOB_SNAPSHOT)) < 0)
+ goto endjob;
+ }
+
+ /* now the domain is now paused if:
+ * - if a memory snapshot was requested
+ * - an atomic snapshot was requested AND
+ * qemu does not support transactions
+ *
+ * Next we snapshot the disks.
+ */
+ if ((ret = qemuDomainSnapshotCreateDiskActive(driver, vm, snap, flags,
+ QEMU_ASYNC_JOB_SNAPSHOT)) < 0)
+ goto endjob;
+
+ /* the snapshot is complete now */
if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT) {
virDomainEventPtr event;
@@ -11118,53 +11181,41 @@ qemuDomainSnapshotCreateDiskActive(virConnectPtr conn,
virDomainAuditStop(vm, "from-snapshot");
/* We already filtered the _HALT flag for persistent domains
* only, so this end job never drops the last reference. */
- ignore_value(qemuDomainObjEndJob(driver, vm));
+ ignore_value(qemuDomainObjEndAsyncJob(driver, vm));
resume = false;
- thaw = 0;
vm = NULL;
if (event)
qemuDomainEventQueue(driver, event);
}
-cleanup:
- if (resume && virDomainObjIsActive(vm)) {
- if (qemuProcessStartCPUs(driver, vm, conn,
- VIR_DOMAIN_RUNNING_UNPAUSED,
- QEMU_ASYNC_JOB_NONE) < 0 &&
- virGetLastError() == NULL) {
- virReportError(VIR_ERR_OPERATION_FAILED, "%s",
- _("resuming after snapshot failed"));
- goto endjob;
- }
- }
-
- if (vm && (ret == 0 ||
- !qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION))) {
- if (virDomainSaveStatus(driver->caps, driver->stateDir, vm) < 0 ||
- (persist &&
- virDomainSaveConfig(driver->configDir, vm->newDef) < 0))
- ret = -1;
- }
+ ret = 0;
endjob:
- if (cgroup)
- virCgroupFree(&cgroup);
- if (vm && thaw != 0 &&
- qemuDomainSnapshotFSThaw(driver, vm, thaw > 0) < 0) {
- /* helper reported the error, if it was needed */
- if (thaw > 0)
- ret = -1;
- }
- if (vm && (qemuDomainObjEndJob(driver, vm) == 0)) {
+ if (vm && !qemuDomainObjEndAsyncJob(driver, vm)) {
/* Only possible if a transient vm quit while our locks were down,
- * in which case we don't want to save snapshot metadata. */
+ * in which case we don't want to save snapshot metadata.
+ */
*vmptr = NULL;
ret = -1;
}
+cleanup:
+ VIR_FREE(xml);
+ if (resume && vm && virDomainObjIsActive(vm) &&
+ qemuProcessStartCPUs(driver, vm, conn,
+ VIR_DOMAIN_RUNNING_UNPAUSED,
+ QEMU_ASYNC_JOB_NONE) < 0 &&
+ virGetLastError() == NULL) {
+ virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+ _("resuming after snapshot failed"));
+
+ return -1;
+ }
+
return ret;
}
+
static virDomainSnapshotPtr
qemuDomainSnapshotCreateXML(virDomainPtr domain,
const char *xmlDesc,
@@ -11190,7 +11241,8 @@ qemuDomainSnapshotCreateXML(virDomainPtr domain,
VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY |
VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT |
VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE |
- VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC, NULL);
+ VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC |
+ VIR_DOMAIN_SNAPSHOT_CREATE_PAUSE, NULL);
if ((flags & VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE) &&
!(flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY)) {
@@ -11397,16 +11449,22 @@ qemuDomainSnapshotCreateXML(virDomainPtr domain,
/* XXX Should we validate that the redefined snapshot even
* makes sense, such as checking that qemu-img recognizes the
* snapshot name in at least one of the domain's disks? */
- } else if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) {
- if (qemuDomainSnapshotCreateDiskActive(domain->conn, driver,
- &vm, snap, flags) < 0)
- goto cleanup;
- } else if (!virDomainObjIsActive(vm)) {
- if (qemuDomainSnapshotCreateInactive(driver, vm, snap) < 0)
- goto cleanup;
+ } else if (virDomainObjIsActive(vm)) {
+ if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY ||
+ snap->def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) {
+ /* external checkpoint or disk snapshot */
+ if (qemuDomainSnapshotCreateActiveExternal(domain->conn, driver,
+ &vm, snap, flags) < 0)
+ goto cleanup;
+ } else {
+ /* internal checkpoint */
+ if (qemuDomainSnapshotCreateActiveInternal(domain->conn, driver,
+ &vm, snap, flags) < 0)
+ goto cleanup;
+ }
} else {
- if (qemuDomainSnapshotCreateActiveInternal(domain->conn, driver,
- &vm, snap, flags) < 0)
+ /* inactive */
+ if (qemuDomainSnapshotCreateInactive(driver, vm, snap) < 0)
goto cleanup;
}
--
1.7.12.4