}
+qemuBlockJobDataPtr
+qemuBlockJobDiskNewCommit(virDomainObjPtr vm,
+ virDomainDiskDefPtr disk,
+ virStorageSourcePtr topparent,
+ virStorageSourcePtr top,
+ virStorageSourcePtr base)
+{
+ qemuDomainObjPrivatePtr priv = vm->privateData;
+ VIR_AUTOUNREF(qemuBlockJobDataPtr) job = NULL;
+ VIR_AUTOFREE(char *) jobname = NULL;
+ qemuBlockJobType jobtype = QEMU_BLOCKJOB_TYPE_COMMIT;
+
+ if (topparent == NULL)
+ jobtype = QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT;
+
+ if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV)) {
+ if (virAsprintf(&jobname, "commit-%s-%s", disk->dst, top->nodeformat) < 0)
+ return NULL;
+ } else {
+ if (!(jobname = qemuAliasDiskDriveFromDisk(disk)))
+ return NULL;
+ }
+
+ if (!(job = qemuBlockJobDataNew(jobtype, jobname)))
+ return NULL;
+
+ job->data.commit.topparent = topparent;
+ job->data.commit.top = top;
+ job->data.commit.base = base;
+
+ if (qemuBlockJobRegister(job, vm, disk, true) < 0)
+ return NULL;
+
+ VIR_RETURN_PTR(job);
+}
+
+
/**
* qemuBlockJobDiskRegisterMirror:
* @job: block job to register 'mirror' chain on
}
+/**
+ * qemuBlockJobProcessEventCompletedCommit:
+ * @driver: qemu driver object
+ * @vm: domain object
+ * @job: job data
+ * @asyncJob: qemu asynchronous job type (for monitor interaction)
+ *
+ * This function executes the finalizing steps after a successful block commit
+ * job. The commit job moves the blocks from backing chain images starting from
+ * 'top' into the 'base' image. The overlay of the 'top' image ('topparent')
+ * then directly references the 'base' image. All intermediate images can be
+ * removed/deleted.
+ */
+static void
+qemuBlockJobProcessEventCompletedCommit(virQEMUDriverPtr driver,
+ virDomainObjPtr vm,
+ qemuBlockJobDataPtr job,
+ qemuDomainAsyncJob asyncJob)
+{
+ virStorageSourcePtr baseparent = NULL;
+ virDomainDiskDefPtr cfgdisk = NULL;
+ virStorageSourcePtr cfgnext = NULL;
+ virStorageSourcePtr cfgtopparent = NULL;
+ virStorageSourcePtr cfgtop = NULL;
+ virStorageSourcePtr cfgbase = NULL;
+ virStorageSourcePtr cfgbaseparent = NULL;
+ virStorageSourcePtr n;
+
+ VIR_DEBUG("commit job '%s' on VM '%s' completed", job->name, vm->def->name);
+
+ /* if the job isn't associated with a disk there's nothing to do */
+ if (!job->disk)
+ return;
+
+ if ((cfgdisk = qemuBlockJobGetConfigDisk(vm, job->disk, job->data.commit.base)))
+ cfgnext = cfgdisk->src;
+
+ if (!cfgdisk)
+ qemuBlockJobClearConfigChain(vm, job->disk);
+
+ for (n = job->disk->src; n && n != job->data.commit.base; n = n->backingStore) {
+ if (cfgnext) {
+ if (n == job->data.commit.topparent)
+ cfgtopparent = cfgnext;
+
+ if (n == job->data.commit.top)
+ cfgtop = cfgnext;
+
+ cfgbaseparent = cfgnext;
+ cfgnext = cfgnext->backingStore;
+ }
+ baseparent = n;
+ }
+
+ if (!n)
+ return;
+
+ /* revert access to images */
+ qemuDomainStorageSourceAccessAllow(driver, vm, job->data.commit.base, true, false);
+ if (job->data.commit.topparent != job->disk->src)
+ qemuDomainStorageSourceAccessAllow(driver, vm, job->data.commit.topparent, true, false);
+
+ baseparent->backingStore = NULL;
+ job->data.commit.topparent->backingStore = job->data.commit.base;
+
+ qemuBlockJobEventProcessConcludedRemoveChain(driver, vm, asyncJob, job->data.commit.top);
+ virObjectUnref(job->data.commit.top);
+ job->data.commit.top = NULL;
+
+ if (cfgbaseparent) {
+ cfgbase = cfgbaseparent->backingStore;
+ cfgbaseparent->backingStore = NULL;
+
+ if (cfgtopparent)
+ cfgtopparent->backingStore = cfgbase;
+ else
+ cfgdisk->src = cfgbase;
+
+ virObjectUnref(cfgtop);
+ }
+}
+
+
+/**
+ * qemuBlockJobProcessEventCompletedActiveCommit:
+ * @driver: qemu driver object
+ * @vm: domain object
+ * @job: job data
+ * @asyncJob: qemu asynchronous job type (for monitor interaction)
+ *
+ * This function executes the finalizing steps after a successful active layer
+ * block commit job. The commit job moves the blocks from backing chain images
+ * starting from the active disk source image into the 'base' image. The disk
+ * source then changes to the 'base' image. All intermediate images can be
+ * removed/deleted.
+ */
+static void
+qemuBlockJobProcessEventCompletedActiveCommit(virQEMUDriverPtr driver,
+ virDomainObjPtr vm,
+ qemuBlockJobDataPtr job,
+ qemuDomainAsyncJob asyncJob)
+{
+ virStorageSourcePtr baseparent = NULL;
+ virDomainDiskDefPtr cfgdisk = NULL;
+ virStorageSourcePtr cfgnext = NULL;
+ virStorageSourcePtr cfgtop = NULL;
+ virStorageSourcePtr cfgbase = NULL;
+ virStorageSourcePtr cfgbaseparent = NULL;
+ virStorageSourcePtr n;
+
+ VIR_DEBUG("active commit job '%s' on VM '%s' completed", job->name, vm->def->name);
+
+ /* if the job isn't associated with a disk there's nothing to do */
+ if (!job->disk)
+ return;
+
+ if ((cfgdisk = qemuBlockJobGetConfigDisk(vm, job->disk, job->data.commit.base)))
+ cfgnext = cfgdisk->src;
+
+ for (n = job->disk->src; n && n != job->data.commit.base; n = n->backingStore) {
+ if (cfgnext) {
+ if (n == job->data.commit.top)
+ cfgtop = cfgnext;
+
+ cfgbaseparent = cfgnext;
+ cfgnext = cfgnext->backingStore;
+ }
+ baseparent = n;
+ }
+
+ if (!n)
+ return;
+
+ if (!cfgdisk) {
+ /* in case when the config disk chain didn't match but the disk top seems
+ * to be identical we need to modify the disk source since the active
+ * commit makes the top level image invalid.
+ */
+ qemuBlockJobRewriteConfigDiskSource(vm, job->disk, job->data.commit.base);
+ } else {
+ cfgbase = cfgbaseparent->backingStore;
+ cfgbaseparent->backingStore = NULL;
+ cfgdisk->src = cfgbase;
+ virObjectUnref(cfgtop);
+ }
+
+ /* Move security driver metadata */
+ if (qemuSecurityMoveImageMetadata(driver, vm, job->disk->src, job->data.commit.base) < 0)
+ VIR_WARN("Unable to move disk metadata on vm %s", vm->def->name);
+
+ baseparent->backingStore = NULL;
+ job->disk->src = job->data.commit.base;
+
+ qemuBlockJobEventProcessConcludedRemoveChain(driver, vm, asyncJob, job->data.commit.top);
+ virObjectUnref(job->data.commit.top);
+ job->data.commit.top = NULL;
+ /* the mirror element does not serve functional purpose for the commit job */
+ virObjectUnref(job->disk->mirror);
+ job->disk->mirror = NULL;
+}
+
static void
qemuBlockJobEventProcessConcludedTransition(qemuBlockJobDataPtr job,
virQEMUDriverPtr driver,
break;
case QEMU_BLOCKJOB_TYPE_COMMIT:
+ qemuBlockJobProcessEventCompletedCommit(driver, vm, job, asyncJob);
+ break;
+
case QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT:
+ qemuBlockJobProcessEventCompletedActiveCommit(driver, vm, job, asyncJob);
+ break;
+
case QEMU_BLOCKJOB_TYPE_COPY:
case QEMU_BLOCKJOB_TYPE_NONE:
case QEMU_BLOCKJOB_TYPE_INTERNAL:
switch ((qemuBlockJobType) job->type) {
case QEMU_BLOCKJOB_TYPE_PULL:
case QEMU_BLOCKJOB_TYPE_COMMIT:
+ break;
+
case QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT:
+ if (job->disk) {
+ virObjectUnref(job->disk->mirror);
+ job->disk->mirror = NULL;
+ }
+ break;
+
case QEMU_BLOCKJOB_TYPE_COPY:
case QEMU_BLOCKJOB_TYPE_NONE:
case QEMU_BLOCKJOB_TYPE_INTERNAL:
case QEMU_BLOCKJOB_TYPE_COMMIT:
case QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT:
+ if (job->data.commit.base)
+ virBufferAsprintf(&childBuf, "<base node='%s'/>\n", job->data.commit.base->nodeformat);
+ if (job->data.commit.top)
+ virBufferAsprintf(&childBuf, "<top node='%s'/>\n", job->data.commit.top->nodeformat);
+ if (job->data.commit.topparent)
+ virBufferAsprintf(&childBuf, "<topparent node='%s'/>\n", job->data.commit.topparent->nodeformat);
case QEMU_BLOCKJOB_TYPE_COPY:
case QEMU_BLOCKJOB_TYPE_NONE:
case QEMU_BLOCKJOB_TYPE_INTERNAL:
break;
case QEMU_BLOCKJOB_TYPE_COMMIT:
+ qemuDomainObjPrivateXMLParseBlockjobNodename(job,
+ "string(./topparent/@node)",
+ &job->data.commit.topparent,
+ ctxt);
+
+ if (!job->data.commit.topparent)
+ goto broken;
+
+ ATTRIBUTE_FALLTHROUGH;
case QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT:
+ qemuDomainObjPrivateXMLParseBlockjobNodename(job,
+ "string(./top/@node)",
+ &job->data.commit.top,
+ ctxt);
+ qemuDomainObjPrivateXMLParseBlockjobNodename(job,
+ "string(./base/@node)",
+ &job->data.commit.base,
+ ctxt);
+ if (!job->data.commit.top ||
+ !job->data.commit.base)
+ goto broken;
+ break;
+
case QEMU_BLOCKJOB_TYPE_COPY:
case QEMU_BLOCKJOB_TYPE_NONE:
case QEMU_BLOCKJOB_TYPE_INTERNAL:
}
return;
+
+ broken:
+ VIR_DEBUG("marking block job '%s' as invalid: malformed job data", job->name);
+ job->invalidData = true;
}
virQEMUDriverPtr driver = dom->conn->privateData;
qemuDomainObjPrivatePtr priv;
virDomainObjPtr vm = NULL;
- VIR_AUTOFREE(char *) device = NULL;
+ const char *device = NULL;
+ const char *jobname = NULL;
int ret = -1;
virDomainDiskDefPtr disk = NULL;
virStorageSourcePtr topSource;
VIR_AUTOFREE(char *) backingPath = NULL;
unsigned long long speed = bandwidth;
qemuBlockJobDataPtr job = NULL;
- qemuBlockJobType jobtype = QEMU_BLOCKJOB_TYPE_COMMIT;
VIR_AUTOUNREF(virStorageSourcePtr) mirror = NULL;
+ const char *nodetop = NULL;
+ const char *nodebase = NULL;
+ bool persistjob = false;
+ bool blockdev = false;
/* XXX Add support for COMMIT_DELETE */
virCheckFlags(VIR_DOMAIN_BLOCK_COMMIT_SHALLOW |
if (virDomainObjCheckActive(vm) < 0)
goto endjob;
+ blockdev = virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV);
+
/* Convert bandwidth MiB to bytes, if necessary */
if (!(flags & VIR_DOMAIN_BLOCK_COMMIT_BANDWIDTH_BYTES)) {
if (speed > LLONG_MAX >> 20) {
if (!(disk = qemuDomainDiskByName(vm->def, path)))
goto endjob;
- if (!(device = qemuAliasDiskDriveFromDisk(disk)))
- goto endjob;
-
if (virStorageSourceIsEmpty(disk->src)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("disk %s has no source file to be committed"),
disk->dst);
goto endjob;
}
-
- jobtype = QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT;
} else if (flags & VIR_DOMAIN_BLOCK_COMMIT_ACTIVE) {
virReportError(VIR_ERR_INVALID_ARG,
_("active commit requested but '%s' is not active"),
qemuDomainStorageSourceAccessAllow(driver, vm, top_parent, false, false) < 0))
goto endjob;
- if (!(job = qemuBlockJobDiskNew(vm, disk, jobtype, device)))
+ if (!(job = qemuBlockJobDiskNewCommit(vm, disk, top_parent, topSource,
+ baseSource)))
goto endjob;
disk->mirrorState = VIR_DOMAIN_DISK_MIRROR_STATE_NONE;
* depending on whether the input was specified as relative or
* absolute (that is, our absolute top_canon may do the wrong
* thing if the user specified a relative name). */
+
+ if (blockdev) {
+ persistjob = true;
+ jobname = job->name;
+ nodetop = topSource->nodeformat;
+ nodebase = baseSource->nodeformat;
+ device = disk->src->nodeformat;
+ if (!backingPath && top_parent &&
+ !(backingPath = qemuBlockGetBackingStoreString(baseSource)))
+ goto endjob;
+ } else {
+ device = job->name;
+ }
+
qemuDomainObjEnterMonitor(driver, vm);
- basePath = qemuMonitorDiskNameLookup(priv->mon, device, disk->src,
- baseSource);
- topPath = qemuMonitorDiskNameLookup(priv->mon, device, disk->src,
- topSource);
- if (basePath && topPath)
- ret = qemuMonitorBlockCommit(priv->mon, device, NULL, false,
- topPath, NULL, basePath, NULL, backingPath,
- speed);
+
+ if (!blockdev) {
+ basePath = qemuMonitorDiskNameLookup(priv->mon, device, disk->src,
+ baseSource);
+ topPath = qemuMonitorDiskNameLookup(priv->mon, device, disk->src,
+ topSource);
+ }
+
+ if (blockdev || (basePath && topPath))
+ ret = qemuMonitorBlockCommit(priv->mon, device, jobname, persistjob,
+ topPath, nodetop, basePath, nodebase,
+ backingPath, speed);
+
if (qemuDomainObjExitMonitor(driver, vm) < 0 || ret < 0) {
ret = -1;
goto endjob;