]> git.ipfire.org Git - thirdparty/libvirt.git/commitdiff
qemu: Add -blockdev support for block pull job
authorPeter Krempa <pkrempa@redhat.com>
Mon, 22 Jul 2019 11:39:24 +0000 (13:39 +0200)
committerPeter Krempa <pkrempa@redhat.com>
Mon, 29 Jul 2019 11:58:25 +0000 (13:58 +0200)
Introduce the handler for finalizing a block pull job which will allow
to use it with blockdev.

This patch also contains some additional machinery which is required to
store all the relevant job data in the status XML which will also be
reused with other block job types.

Signed-off-by: Peter Krempa <pkrempa@redhat.com>
Reviewed-by: Ján Tomko <jtomko@redhat.com>
src/qemu/qemu_blockjob.c
src/qemu/qemu_blockjob.h
src/qemu/qemu_domain.c
src/qemu/qemu_driver.c
tests/qemustatusxml2xmldata/blockjob-blockdev-in.xml

index 1e19347b03a88f2a397b9acd3883f0529cab572c..1d1b46d9bf5e0de30e0f7e53e929d4a060a07a42 100644 (file)
@@ -26,6 +26,7 @@
 #include "qemu_blockjob.h"
 #include "qemu_block.h"
 #include "qemu_domain.h"
+#include "qemu_alias.h"
 
 #include "conf/domain_conf.h"
 #include "conf/domain_event.h"
@@ -207,6 +208,35 @@ qemuBlockJobDiskNew(virDomainObjPtr vm,
 }
 
 
+qemuBlockJobDataPtr
+qemuBlockJobDiskNewPull(virDomainObjPtr vm,
+                        virDomainDiskDefPtr disk,
+                        virStorageSourcePtr base)
+{
+    qemuDomainObjPrivatePtr priv = vm->privateData;
+    VIR_AUTOUNREF(qemuBlockJobDataPtr) job = NULL;
+    VIR_AUTOFREE(char *) jobname = NULL;
+
+    if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV)) {
+        if (virAsprintf(&jobname, "pull-%s-%s", disk->dst, disk->src->nodeformat) < 0)
+            return NULL;
+    } else {
+        if (!(jobname = qemuAliasDiskDriveFromDisk(disk)))
+            return NULL;
+    }
+
+    if (!(job = qemuBlockJobDataNew(QEMU_BLOCKJOB_TYPE_PULL, jobname)))
+        return NULL;
+
+    job->data.pull.base = base;
+
+    if (qemuBlockJobRegister(job, vm, disk, true) < 0)
+        return NULL;
+
+    VIR_RETURN_PTR(job);
+}
+
+
 /**
  * qemuBlockJobDiskRegisterMirror:
  * @job: block job to register 'mirror' chain on
@@ -630,16 +660,175 @@ qemuBlockJobEventProcessConcludedRemoveChain(virQEMUDriverPtr driver,
 }
 
 
+/**
+ * qemuBlockJobGetConfigDisk:
+ * @vm: domain object
+ * @disk: disk from the running definition
+ * @diskChainBottom: the last element of backing chain of @disk which is relevant
+ *
+ * Finds and returns the disk corresponding to @disk in the inactive definition.
+ * The inactive disk must have the backing chain starting from the source until
+ * @@diskChainBottom identical. If @diskChainBottom is NULL the whole backing
+ * chains of both @disk and the persistent config definition equivalent must
+ * be identical.
+ */
+static virDomainDiskDefPtr
+qemuBlockJobGetConfigDisk(virDomainObjPtr vm,
+                          virDomainDiskDefPtr disk,
+                          virStorageSourcePtr diskChainBottom)
+{
+    virStorageSourcePtr disksrc = NULL;
+    virStorageSourcePtr cfgsrc = NULL;
+    virDomainDiskDefPtr ret = NULL;
+
+    if (!vm->newDef || !disk)
+        return NULL;
+
+    disksrc = disk->src;
+
+    if (!(ret = virDomainDiskByName(vm->newDef, disk->dst, false)))
+        return NULL;
+
+    cfgsrc = ret->src;
+
+    while (disksrc && cfgsrc) {
+        if (!virStorageSourceIsSameLocation(disksrc, cfgsrc))
+            return NULL;
+
+        if (diskChainBottom && diskChainBottom == disksrc)
+            return ret;
+
+        disksrc = disksrc->backingStore;
+        cfgsrc = cfgsrc->backingStore;
+    }
+
+    if (disksrc || cfgsrc)
+        return NULL;
+
+    return ret;
+}
+
+
+/**
+ * qemuBlockJobClearConfigChain:
+ * @vm: domain object
+ * @disk: disk object from running definition of @vm
+ *
+ * In cases when the backing chain definitions of the live disk differ from
+ * the definition for the next start config and the backing chain would touch
+ * it we'd not be able to restore the chain in the next start config properly.
+ *
+ * This function checks that the source of the running disk definition and the
+ * config disk definition are the same and if such it clears the backing chain
+ * data.
+ */
+static void
+qemuBlockJobClearConfigChain(virDomainObjPtr vm,
+                             virDomainDiskDefPtr disk)
+{
+    virDomainDiskDefPtr cfgdisk = NULL;
+
+    if (!vm->newDef || !disk)
+        return;
+
+    if (!(cfgdisk = virDomainDiskByName(vm->newDef, disk->dst, false)))
+        return;
+
+    if (!virStorageSourceIsSameLocation(disk->src, cfgdisk->src))
+        return;
+
+    virObjectUnref(cfgdisk->src->backingStore);
+    cfgdisk->src->backingStore = NULL;
+}
+
+
+/**
+ * qemuBlockJobProcessEventCompletedPull:
+ * @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 pull job
+ * (block-stream in qemu terminology. The pull job copies all the data from the
+ * images in the backing chain up to the 'base' image. The 'base' image becomes
+ * the backing store of the active top level image. If 'base' was not used
+ * everything is pulled into the top level image and the top level image will
+ * cease to have backing store. All intermediate images between the active image
+ * and base image are no longer required and can be unplugged.
+ */
+static void
+qemuBlockJobProcessEventCompletedPull(virQEMUDriverPtr driver,
+                                      virDomainObjPtr vm,
+                                      qemuBlockJobDataPtr job,
+                                      qemuDomainAsyncJob asyncJob)
+{
+    virStorageSourcePtr baseparent = NULL;
+    virDomainDiskDefPtr cfgdisk = NULL;
+    virStorageSourcePtr cfgbase = NULL;
+    virStorageSourcePtr cfgbaseparent = NULL;
+    virStorageSourcePtr n;
+    virStorageSourcePtr tmp;
+
+    VIR_DEBUG("pull 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.pull.base)))
+        cfgbase = cfgdisk->src->backingStore;
+
+    if (!cfgdisk)
+        qemuBlockJobClearConfigChain(vm, job->disk);
+
+    /* when pulling if 'base' is right below the top image we don't have to modify it */
+    if (job->disk->src->backingStore == job->data.pull.base)
+        return;
+
+    if (job->data.pull.base) {
+        for (n = job->disk->src->backingStore; n && n != job->data.pull.base; n = n->backingStore) {
+            /* find the image on top of 'base' */
+
+            if (cfgbase) {
+                cfgbaseparent = cfgbase;
+                cfgbase = cfgbase->backingStore;
+            }
+
+            baseparent = n;
+        }
+    }
+
+    tmp = job->disk->src->backingStore;
+    job->disk->src->backingStore = job->data.pull.base;
+    if (baseparent)
+        baseparent->backingStore = NULL;
+    qemuBlockJobEventProcessConcludedRemoveChain(driver, vm, asyncJob, tmp);
+    virObjectUnref(tmp);
+
+    if (cfgdisk) {
+        tmp = cfgdisk->src->backingStore;
+        cfgdisk->src->backingStore = cfgbase;
+        if (cfgbaseparent)
+            cfgbaseparent->backingStore = NULL;
+        virObjectUnref(tmp);
+    }
+}
+
+
 static void
 qemuBlockJobEventProcessConcludedTransition(qemuBlockJobDataPtr job,
                                             virQEMUDriverPtr driver,
                                             virDomainObjPtr vm,
-                                            qemuDomainAsyncJob asyncJob ATTRIBUTE_UNUSED)
+                                            qemuDomainAsyncJob asyncJob)
 {
     switch ((qemuBlockjobState) job->newstate) {
     case QEMU_BLOCKJOB_STATE_COMPLETED:
         switch ((qemuBlockJobType) job->type) {
         case QEMU_BLOCKJOB_TYPE_PULL:
+            qemuBlockJobProcessEventCompletedPull(driver, vm, job, asyncJob);
+            break;
+
         case QEMU_BLOCKJOB_TYPE_COMMIT:
         case QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT:
         case QEMU_BLOCKJOB_TYPE_COPY:
index 3299207610c419d628e5ad8852dccece1c9cae14..d5848fb72ca81d60a8830290afdeea021acf8c0b 100644 (file)
@@ -68,6 +68,15 @@ verify((int)QEMU_BLOCKJOB_TYPE_INTERNAL == VIR_DOMAIN_BLOCK_JOB_TYPE_LAST);
 
 VIR_ENUM_DECL(qemuBlockjob);
 
+
+typedef struct _qemuBlockJobPullData qemuBlockJobPullData;
+typedef qemuBlockJobPullData *qemuBlockJobDataPullPtr;
+
+struct _qemuBlockJobPullData {
+    virStorageSourcePtr base;
+};
+
+
 typedef struct _qemuBlockJobData qemuBlockJobData;
 typedef qemuBlockJobData *qemuBlockJobDataPtr;
 
@@ -80,6 +89,10 @@ struct _qemuBlockJobData {
     virStorageSourcePtr chain; /* Reference to the chain the job operates on. */
     virStorageSourcePtr mirrorChain; /* reference to 'mirror' part of the job */
 
+    union {
+        qemuBlockJobPullData pull;
+    } data;
+
     int type; /* qemuBlockJobType */
     int state; /* qemuBlockjobState */
     char *errmsg;
@@ -114,6 +127,11 @@ void
 qemuBlockJobDiskRegisterMirror(qemuBlockJobDataPtr job)
     ATTRIBUTE_NONNULL(1);
 
+qemuBlockJobDataPtr
+qemuBlockJobDiskNewPull(virDomainObjPtr vm,
+                        virDomainDiskDefPtr disk,
+                        virStorageSourcePtr base);
+
 qemuBlockJobDataPtr
 qemuBlockJobDiskGetJob(virDomainDiskDefPtr disk)
     ATTRIBUTE_NONNULL(1);
index c508f5528748923324826bc9f6ab3e358be770fd..b638d096e307c4a85e54ee6d52b80f6bce5c38ae 100644 (file)
@@ -2390,6 +2390,21 @@ qemuDomainObjPrivateXMLFormatBlockjobIterator(void *payload,
             return -1;
     }
 
+    switch ((qemuBlockJobType) job->type) {
+        case QEMU_BLOCKJOB_TYPE_PULL:
+            if (job->data.pull.base)
+                virBufferAsprintf(&childBuf, "<base node='%s'/>\n", job->data.pull.base->nodeformat);
+            break;
+
+        case QEMU_BLOCKJOB_TYPE_COMMIT:
+        case QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT:
+        case QEMU_BLOCKJOB_TYPE_COPY:
+        case QEMU_BLOCKJOB_TYPE_NONE:
+        case QEMU_BLOCKJOB_TYPE_INTERNAL:
+        case QEMU_BLOCKJOB_TYPE_LAST:
+            break;
+    }
+
     return virXMLFormatElement(data->buf, "blockjob", &attrBuf, &childBuf);
 }
 
@@ -2793,6 +2808,64 @@ qemuDomainObjPrivateXMLParseBlockjobChain(xmlNodePtr node,
 }
 
 
+static void
+qemuDomainObjPrivateXMLParseBlockjobNodename(qemuBlockJobDataPtr job,
+                                             const char *xpath,
+                                             virStorageSourcePtr *src,
+                                             xmlXPathContextPtr ctxt)
+{
+    VIR_AUTOFREE(char *) nodename = NULL;
+
+    *src = NULL;
+
+    if (!(nodename = virXPathString(xpath, ctxt)))
+        return;
+
+    if (job->disk &&
+        (*src = virStorageSourceFindByNodeName(job->disk->src, nodename, NULL)))
+        return;
+
+    if (job->chain &&
+        (*src = virStorageSourceFindByNodeName(job->chain, nodename, NULL)))
+        return;
+
+    if (job->mirrorChain &&
+        (*src = virStorageSourceFindByNodeName(job->mirrorChain, nodename, NULL)))
+        return;
+
+    /* the node was in the XML but was not found in the job definitions */
+    VIR_DEBUG("marking block job '%s' as invalid: node name '%s' missing",
+              job->name, nodename);
+    job->invalidData = true;
+}
+
+
+static void
+qemuDomainObjPrivateXMLParseBlockjobDataSpecific(qemuBlockJobDataPtr job,
+                                                 xmlXPathContextPtr ctxt)
+{
+    switch ((qemuBlockJobType) job->type) {
+        case QEMU_BLOCKJOB_TYPE_PULL:
+            qemuDomainObjPrivateXMLParseBlockjobNodename(job,
+                                                         "string(./base/@node)",
+                                                         &job->data.pull.base,
+                                                         ctxt);
+            /* base is not present if pulling everything */
+            break;
+
+        case QEMU_BLOCKJOB_TYPE_COMMIT:
+        case QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT:
+        case QEMU_BLOCKJOB_TYPE_COPY:
+        case QEMU_BLOCKJOB_TYPE_NONE:
+        case QEMU_BLOCKJOB_TYPE_INTERNAL:
+        case QEMU_BLOCKJOB_TYPE_LAST:
+            break;
+    }
+
+    return;
+}
+
+
 static int
 qemuDomainObjPrivateXMLParseBlockjobData(virDomainObjPtr vm,
                                          xmlNodePtr node,
@@ -2867,6 +2940,8 @@ qemuDomainObjPrivateXMLParseBlockjobData(virDomainObjPtr vm,
     if (mirror)
         qemuBlockJobDiskRegisterMirror(job);
 
+    qemuDomainObjPrivateXMLParseBlockjobDataSpecific(job, ctxt);
+
     if (qemuBlockJobRegister(job, vm, disk, false) < 0)
         return -1;
 
index 9110d15cca23f11b8e8720042f8c54a3868ddd09..6c332359cd0e5a8bd934c5becdb667c136f67eb2 100644 (file)
@@ -17069,7 +17069,8 @@ qemuDomainBlockPullCommon(virQEMUDriverPtr driver,
                           unsigned int flags)
 {
     qemuDomainObjPrivatePtr priv = vm->privateData;
-    VIR_AUTOFREE(char *) device = NULL;
+    const char *device = NULL;
+    const char *jobname = NULL;
     virDomainDiskDefPtr disk;
     virStorageSourcePtr baseSource = NULL;
     unsigned int baseIndex = 0;
@@ -17077,6 +17078,9 @@ qemuDomainBlockPullCommon(virQEMUDriverPtr driver,
     VIR_AUTOFREE(char *) backingPath = NULL;
     unsigned long long speed = bandwidth;
     qemuBlockJobDataPtr job = NULL;
+    bool persistjob = false;
+    const char *nodebase = NULL;
+    bool blockdev = virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV);
     int ret = -1;
 
     if (flags & VIR_DOMAIN_BLOCK_REBASE_RELATIVE && !base) {
@@ -17095,9 +17099,6 @@ qemuDomainBlockPullCommon(virQEMUDriverPtr driver,
     if (!(disk = qemuDomainDiskByName(vm->def, path)))
         goto endjob;
 
-    if (!(device = qemuAliasDiskDriveFromDisk(disk)))
-        goto endjob;
-
     if (qemuDomainDiskBlockJobIsActive(disk))
         goto endjob;
 
@@ -17140,16 +17141,32 @@ qemuDomainBlockPullCommon(virQEMUDriverPtr driver,
         speed <<= 20;
     }
 
-    if (!(job = qemuBlockJobDiskNew(vm, disk, QEMU_BLOCKJOB_TYPE_PULL, device)))
+    if (!(job = qemuBlockJobDiskNewPull(vm, disk, baseSource)))
         goto endjob;
 
+    if (blockdev) {
+        jobname = job->name;
+        persistjob = true;
+        if (baseSource) {
+            nodebase = baseSource->nodeformat;
+            if (!backingPath &&
+                !(backingPath = qemuBlockGetBackingStoreString(baseSource)))
+                goto endjob;
+        }
+        device = disk->src->nodeformat;
+    } else {
+        device = job->name;
+    }
+
     qemuDomainObjEnterMonitor(driver, vm);
-    if (baseSource)
+    if (!blockdev && baseSource)
         basePath = qemuMonitorDiskNameLookup(priv->mon, device, disk->src,
                                              baseSource);
-    if (!baseSource || basePath)
-        ret = qemuMonitorBlockStream(priv->mon, device, NULL, false, basePath,
-                                     NULL, backingPath, speed);
+
+    if (blockdev ||
+        (!baseSource || basePath))
+        ret = qemuMonitorBlockStream(priv->mon, device, jobname, persistjob, basePath,
+                                     nodebase, backingPath, speed);
     if (qemuDomainObjExitMonitor(driver, vm) < 0)
         ret = -1;
 
index 7b9282d059504186b9ed70f4508bcfb831b7c811..e962b837ac482de2f794db6c48790f4223ebf5f1 100644 (file)
   <allowReboot value='yes'/>
   <nodename index='0'/>
   <blockjobs active='yes'>
+    <blockjob name='pull-vdb-libvirt-3-format' type='pull' state='running'>
+      <disk dst='vdb'/>
+      <base node='libvirt-14-format'/>
+    </blockjob>
     <blockjob name='drive-virtio-disk0' type='copy' state='ready'>
       <disk dst='vda' mirror='yes'/>
     </blockjob>