The hardest part of this patch is figuring out how to provide proper
security labeling and lock manager setup for the mirror, as well as
rolling it all back on error.
* src/qemu/qemu_driver.c (qemuDomainSnapshotCreateXML): Decide
when mirrors are allowed.
(qemuDomainSnapshotDiskPrepare): Prepare for mirrors.
(qemuDomainSnapshotCreateSingleDiskActive): Turn on mirrors.
(qemuDomainSnapshotUndoSingleDiskActive): Undo mirrors on failure.
(qemuDomainSnapshotCreateDiskActive): Update caller.
---
src/qemu/qemu_driver.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++--
1 files changed, 114 insertions(+), 5 deletions(-)
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 4ce31a3..a74e606 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -9865,6 +9865,25 @@ qemuDomainSnapshotDiskPrepare(virDomainObjPtr vm,
virDomainSnapshotDefPtr def,
disk->name, disk->file);
goto cleanup;
}
+ if (disk->mirror) {
+ if (stat(disk->mirror, &st) < 0) {
+ if (errno != ENOENT) {
+ virReportSystemError(errno,
+ _("unable to stat mirror for "
+ "disk %s: %s"),
+ disk->name, disk->mirror);
+ goto cleanup;
+ }
+ } else if (!(S_ISBLK(st.st_mode) || !st.st_size ||
+ allow_reuse)) {
+ qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+ _("external mirror file for disk %s "
+ "already exists and is not a block "
+ "device: %s"),
+ disk->name, disk->mirror);
+ goto cleanup;
+ }
+ }
found = true;
external++;
break;
@@ -9925,6 +9944,7 @@ qemuDomainSnapshotCreateSingleDiskActive(struct qemud_driver
*driver,
char *origsrc = NULL;
char *origdriver = NULL;
bool need_unlink = false;
+ bool need_unlink_mirror = false;
if (snap->snapshot != VIR_DOMAIN_DISK_SNAPSHOT_EXTERNAL) {
qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s",
@@ -9952,13 +9972,22 @@ qemuDomainSnapshotCreateSingleDiskActive(struct qemud_driver
*driver,
if (fd < 0)
goto cleanup;
VIR_FORCE_CLOSE(fd);
+
+ if (snap->mirror) {
+ fd = qemuOpenFile(driver, snap->mirror,
+ O_WRONLY | O_TRUNC | O_CREAT,
+ &need_unlink_mirror, NULL);
+ if (fd < 0)
+ goto cleanup;
+ VIR_FORCE_CLOSE(fd);
+ }
}
origsrc = disk->src;
- disk->src = source;
origdriver = disk->driverType;
disk->driverType = (char *) "raw"; /* Don't want to probe backing
files */
+ disk->src = source;
if (virDomainLockDiskAttach(driver->lockManager, vm, disk) < 0)
goto cleanup;
if (virSecurityManagerSetImageLabel(driver->securityManager, vm->def,
@@ -9968,9 +9997,21 @@ qemuDomainSnapshotCreateSingleDiskActive(struct qemud_driver
*driver,
goto cleanup;
}
+ if (snap->mirror) {
+ disk->src = snap->mirror;
+ if (virDomainLockDiskAttach(driver->lockManager, vm, disk) < 0)
+ goto cleanup2;
+ if (virSecurityManagerSetImageLabel(driver->securityManager, vm->def,
+ disk) < 0) {
+ if (virDomainLockDiskDetach(driver->lockManager, vm, disk) < 0)
+ VIR_WARN("Unable to release lock on %s", source);
+ goto cleanup2;
+ }
+ }
+
disk->src = origsrc;
- origsrc = NULL;
disk->driverType = origdriver;
+ origsrc = NULL;
origdriver = NULL;
/* create the actual snapshot */
@@ -9979,9 +10020,22 @@ qemuDomainSnapshotCreateSingleDiskActive(struct qemud_driver
*driver,
virDomainAuditDisk(vm, disk->src, source, "snapshot", ret >= 0);
if (ret < 0)
goto cleanup;
+ if (snap->mirror) {
+ ret = qemuMonitorDriveMirror(priv->mon, actions, device, snap->mirror,
+ driverType,
+ (VIR_DOMAIN_BLOCK_COPY_SHALLOW |
+ (reuse ?
+ VIR_DOMAIN_BLOCK_COPY_REUSE_EXT : 0)));
+ virDomainAuditDisk(vm, NULL, snap->mirror, "mirror", ret >= 0);
+ if (ret < 0) {
+ ret = -2;
+ goto cleanup;
+ }
+ }
/* Update vm in place to match changes. */
need_unlink = false;
+ need_unlink_mirror = false;
VIR_FREE(disk->src);
disk->src = source;
source = NULL;
@@ -10008,12 +10062,25 @@ cleanup:
}
if (need_unlink && unlink(source))
VIR_WARN("unable to unlink just-created %s", source);
+ if (need_unlink_mirror && unlink(snap->mirror))
+ VIR_WARN("unable to unlink just-created %s", snap->mirror);
VIR_FREE(device);
VIR_FREE(source);
VIR_FREE(driverType);
VIR_FREE(persistSource);
VIR_FREE(persistDriverType);
return ret;
+
+cleanup2:
+ /* We failed to relabel a mirror, but successfully labeled the
+ * destination; so we need to release the destination. */
+ disk->src = source;
+ if (virDomainLockDiskDetach(driver->lockManager, vm, disk) < 0)
+ VIR_WARN("Unable to release lock on %s", source);
+ if (virSecurityManagerRestoreImageLabel(driver->securityManager,
+ vm->def, disk) < 0)
+ VIR_WARN("Unable to restore security label on %s", source);
+ goto cleanup;
}
/* The domain is expected to hold monitor lock. This is the
@@ -10025,7 +10092,7 @@ qemuDomainSnapshotUndoSingleDiskActive(struct qemud_driver
*driver,
virDomainDiskDefPtr origdisk,
virDomainDiskDefPtr disk,
virDomainDiskDefPtr persistDisk,
- bool need_unlink)
+ bool need_unlink, char *mirror)
{
char *source = NULL;
char *driverType = NULL;
@@ -10043,6 +10110,23 @@ qemuDomainSnapshotUndoSingleDiskActive(struct qemud_driver
*driver,
goto cleanup;
}
+ if (mirror) {
+ char *origsrc = disk->src;
+ char *origdriver = disk->driverType;
+ disk->src = mirror;
+ disk->driverType = (char *) "raw";
+ if (virSecurityManagerRestoreImageLabel(driver->securityManager,
+ vm->def, disk) < 0)
+ VIR_WARN("Unable to restore security label on %s", mirror);
+ if (virDomainLockDiskDetach(driver->lockManager, vm, disk) < 0)
+ VIR_WARN("Unable to release lock on %s", mirror);
+ if (need_unlink && stat(mirror, &st) == 0 &&
+ st.st_size == 0 && S_ISREG(st.st_mode) && unlink(mirror) <
0)
+ VIR_WARN("Unable to remove just-created %s", mirror);
+ disk->src = origsrc;
+ disk->driverType = origdriver;
+ }
+
if (virSecurityManagerRestoreImageLabel(driver->securityManager,
vm->def, disk) < 0)
VIR_WARN("Unable to restore security label on %s", disk->src);
@@ -10182,13 +10266,21 @@ qemuDomainSnapshotCreateDiskActive(virConnectPtr conn,
ret = qemuMonitorTransaction(priv->mon, actions);
virJSONValueFree(actions);
if (ret < 0) {
- /* Transaction failed; undo the changes to vm. */
+ /* Transaction failed; undo the changes to vm. Creating a
+ * mirror takes two parts; a return of -2 tells us that we
+ * failed half-way through. */
+ if (ret == -2)
+ i++;
bool need_unlink = !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT);
while (--i >= 0) {
virDomainDiskDefPtr persistDisk = NULL;
+ char *mirror = NULL;
if (snap->def->disks[i].snapshot == VIR_DOMAIN_DISK_SNAPSHOT_NO)
continue;
+ if (ret != -2)
+ mirror = snap->def->disks[i].mirror;
+ ret = -1;
if (vm->newDef) {
int indx = virDomainDiskIndexByName(vm->newDef,
vm->def->disks[i]->dst,
@@ -10201,7 +10293,7 @@ qemuDomainSnapshotCreateDiskActive(virConnectPtr conn,
snap->def->dom->disks[i],
vm->def->disks[i],
persistDisk,
- need_unlink);
+ need_unlink, mirror);
}
}
}
@@ -10269,6 +10361,7 @@ qemuDomainSnapshotCreateXML(virDomainPtr domain,
unsigned int flags)
{
struct qemud_driver *driver = domain->conn->privateData;
+ qemuDomainObjPrivatePtr priv;
virDomainObjPtr vm = NULL;
char *xml = NULL;
virDomainSnapshotObjPtr snap = NULL;
@@ -10312,6 +10405,7 @@ qemuDomainSnapshotCreateXML(virDomainPtr domain,
_("no domain with matching uuid '%s'"),
uuidstr);
goto cleanup;
}
+ priv = vm->privateData;
if (qemuProcessAutoDestroyActive(driver, vm)) {
qemuReportError(VIR_ERR_OPERATION_INVALID,
@@ -10330,6 +10424,21 @@ qemuDomainSnapshotCreateXML(virDomainPtr domain,
goto cleanup;
}
+ /* Right now, we only permit disk mirroring when qemu is new
+ * enough, and only when creating a new snapshot on a transient
+ * domain; that way, we don't have to deal with complications like
+ * restoring mirroring when stopping and rebooting a domain, or
+ * altering the set of mirrors when redefining a snapshot. We
+ * require the snapshot to exist as long as mirroring is
+ * active. */
+ if (qemuCapsGet(priv->qemuCaps, QEMU_CAPS_DRIVE_MIRROR) &&
+ qemuCapsGet(priv->qemuCaps, QEMU_CAPS_DRIVE_REOPEN) &&
+ !vm->persistent &&
+ (flags & (VIR_DOMAIN_SNAPSHOT_CREATE_HALT |
+ VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA |
+ VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE)) == 0)
+ parse_flags |= VIR_DOMAIN_SNAPSHOT_PARSE_MIRROR;
+
if (!(def = virDomainSnapshotDefParseString(xmlDesc, driver->caps,
QEMU_EXPECTED_VIRT_TYPES,
parse_flags)))
--
1.7.7.6