]> xenbits.xensource.com Git - libvirt.git/commitdiff
qemuDomainBlockResize: Properly resize disks with storage slice
authorPeter Krempa <pkrempa@redhat.com>
Mon, 11 Dec 2023 14:37:51 +0000 (15:37 +0100)
committerPeter Krempa <pkrempa@redhat.com>
Thu, 14 Dec 2023 15:12:04 +0000 (16:12 +0100)
Until now resizing a disk with a storage slice would break in one of the
following ways:

1) for a non-raw format, the virtual size would change, but the slice
would still remain in place
2) for raw disks qemu would refuse to change the size

The only reasonable scenario we want to support is a 'raw' image with 0
offset (inside a block device), where we can just drop the slice.

Anything else comes from a non-standard storage setup that we don't want
to touch.

To facilitate the resize, we first remove the 'size' parameter in qemu
thus dropping the slice and then instructing qemu to resize the disk.

Resolves: https://issues.redhat.com/browse/RHEL-18782
Signed-off-by: Peter Krempa <pkrempa@redhat.com>
Reviewed-by: Ján Tomko <jtomko@redhat.com>
src/qemu/qemu_block.c
src/qemu/qemu_block.h
src/qemu/qemu_driver.c

index f5a6f2493488f22fad015b8daf3120943a41ba58..c9f5cbbf29682b9451d7ba54c5153a064bd4146a 100644 (file)
@@ -3265,6 +3265,71 @@ qemuBlockStorageSourceIsRaw(const virStorageSource *src)
 }
 
 
+/**
+ * qemuBlockReopenSliceExpand:
+ * @vm: domain object
+ * @src: storage source to reopen
+ *
+ * Reopen @src image to remove its storage slice. Note that this currently
+ * works only for 'raw' disks.
+ *
+ * Note: This changes transforms the definition such that the 'raw' driver
+ *       becomes the 'format' layer rather than the 'slice' layer, to be able
+ *       to free the slice definition.
+ */
+int
+qemuBlockReopenSliceExpand(virDomainObj *vm,
+                           virStorageSource *src)
+{
+    g_autoptr(virJSONValue) reopenoptions = virJSONValueNewArray();
+    g_autoptr(virJSONValue) srcprops = NULL;
+    int rc;
+
+    /* If we are lacking the object here, qemu might have opened an image with
+     * a node name unknown to us */
+    /* Note: This is currently dead code, as only 'raw' images are supported */
+    if (!src->backingStore) {
+        virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
+                       _("can't reopen image with unknown presence of backing store"));
+        return -1;
+    }
+
+    /* If there is an explicit storage slice 'raw' driver layer we need to modify that */
+    if (qemuBlockStorageSourceGetSliceNodename(src)) {
+        /* we need to know whether the slice layer is the "effective" layer */
+        bool isEffective = !qemuBlockStorageSourceGetSliceNodename(src);
+
+        if (!(srcprops = qemuBlockStorageSourceGetBlockdevStorageSliceProps(src, isEffective, true)))
+            return -1;
+    } else {
+        if (!(srcprops = qemuBlockStorageSourceGetFormatProps(src, src->backingStore)))
+            return -1;
+    }
+
+    if (virJSONValueArrayAppend(reopenoptions, &srcprops) < 0)
+        return -1;
+
+    if (qemuDomainObjEnterMonitorAsync(vm, VIR_ASYNC_JOB_NONE) < 0)
+        return -1;
+
+    rc = qemuMonitorBlockdevReopen(qemuDomainGetMonitor(vm), &reopenoptions);
+
+    qemuDomainObjExitMonitor(vm);
+    if (rc < 0)
+        return -1;
+
+    /* transform the 'slice' raw driver into a 'format' driver so that we don't
+     * have to add extra code */
+    if (qemuBlockStorageSourceGetSliceNodename(src))
+        qemuBlockStorageSourceSetFormatNodename(src, g_strdup(qemuBlockStorageSourceGetSliceNodename(src)));
+
+    /* get rid of the slice */
+    g_clear_pointer(&src->sliceStorage, virStorageSourceSliceFree);
+
+    return 0;
+}
+
+
 /**
  * qemuBlockStorageSourceNeedSliceLayer:
  * @src: source to inspect
index 9a9aa9790099f0d55686076f618c5fa035c619c3..0a0f793b45b921d1320add92a8d0dbd3a113e8cd 100644 (file)
@@ -267,6 +267,10 @@ qemuBlockReopenReadOnly(virDomainObj *vm,
                         virStorageSource *src,
                         virDomainAsyncJob asyncJob);
 
+int
+qemuBlockReopenSliceExpand(virDomainObj *vm,
+                           virStorageSource *src);
+
 bool
 qemuBlockStorageSourceIsLUKS(const virStorageSource *src);
 bool
index db99d471d49ae23f3eee0995bae565b49ad03416..9d8be1572179d01bedbae7d79da079ef2da682bb 100644 (file)
@@ -9245,6 +9245,7 @@ qemuDomainBlockResize(virDomainPtr dom,
     g_autofree char *device = NULL;
     const char *nodename = NULL;
     virDomainDiskDef *disk = NULL;
+    virDomainDiskDef *persistDisk = NULL;
 
     virCheckFlags(VIR_DOMAIN_BLOCK_RESIZE_BYTES |
                   VIR_DOMAIN_BLOCK_RESIZE_CAPACITY, -1);
@@ -9293,9 +9294,23 @@ qemuDomainBlockResize(virDomainPtr dom,
         goto endjob;
     }
 
-    if (flags & VIR_DOMAIN_BLOCK_RESIZE_CAPACITY) {
+    /* The physical capacity is needed both when automatic sizing is requested
+     * and when a slice is used on top of a block device.
+     */
+    if (virStorageSourceIsBlockLocal(disk->src) &&
+        ((flags & VIR_DOMAIN_BLOCK_RESIZE_CAPACITY) ||
+         disk->src->sliceStorage)) {
         g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(priv->driver);
 
+        if (qemuDomainStorageUpdatePhysical(cfg, vm, disk->src) < 0) {
+            virReportError(VIR_ERR_OPERATION_FAILED,
+                           _("failed to update capacity of '%1$s'"), disk->src->path);
+            goto endjob;
+        }
+
+    }
+
+    if (flags & VIR_DOMAIN_BLOCK_RESIZE_CAPACITY) {
         if (!qemuBlockStorageSourceIsRaw(disk->src) ||
             !virStorageSourceIsBlockLocal(disk->src)) {
             virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
@@ -9303,12 +9318,6 @@ qemuDomainBlockResize(virDomainPtr dom,
             goto endjob;
         }
 
-        if (qemuDomainStorageUpdatePhysical(cfg, vm, disk->src) < 0) {
-            virReportError(VIR_ERR_OPERATION_FAILED,
-                           _("failed to update capacity of '%1$s'"), disk->src->path);
-            goto endjob;
-        }
-
         size = disk->src->physical;
     }
 
@@ -9319,6 +9328,55 @@ qemuDomainBlockResize(virDomainPtr dom,
         disk->src->format == VIR_STORAGE_FILE_QED)
         size = VIR_ROUND_UP(size, 512);
 
+    if (disk->src->sliceStorage) {
+        if (qemuDiskBusIsSD(disk->bus)) {
+            virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
+                           _("resize of a 'sd' disk with storage slice is not supported"));
+            goto endjob;
+        }
+
+        /* If the storage slice has a non-zero 'offset' it's usually some weird
+         * configuration that we'd rather not touch */
+        if (disk->src->sliceStorage->offset > 0) {
+            virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
+                           _("resize of a disk with storage slice with non-zero 'offset' is not supported"));
+            goto endjob;
+        }
+
+        /* Removing the slice for non-raw will require introducing an attribute that
+         * the slice was fully expanded so that the XML can keep the 'slice' element.
+         * For raw images we simply remove the slice definition as there is no
+         * extra layer. */
+        if (!qemuBlockStorageSourceIsRaw(disk->src)) {
+            virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
+                           _("resize of a disk with storage slice is supported only for 'raw' images"));
+            goto endjob;
+        }
+
+        /* trying to resize a block device to a size not equal to the actual
+         * size of the block device will cause qemu to fail */
+        if (virStorageSourceIsBlockLocal(disk->src) &&
+            disk->src->physical != size) {
+            virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
+                           _("block device backed disk must be resized to its actual size '%1$llu'"),
+                           disk->src->physical);
+            goto endjob;
+        }
+
+        if (vm->newDef &&
+            (persistDisk = virDomainDiskByTarget(vm->newDef, disk->dst))) {
+            if (!virStorageSourceIsSameLocation(disk->src, persistDisk->src) ||
+                !persistDisk->src->sliceStorage)
+                persistDisk = NULL;
+        }
+
+        /* remove the slice completely, we then instruct qemu to resize */
+        if (qemuBlockReopenSliceExpand(vm, disk->src) < 0)
+            goto endjob;
+
+        qemuDomainSaveStatus(vm);
+    }
+
     if (!qemuDiskBusIsSD(disk->bus)) {
         nodename = qemuBlockStorageSourceGetEffectiveNodename(disk->src);
     } else {
@@ -9333,6 +9391,11 @@ qemuDomainBlockResize(virDomainPtr dom,
     }
     qemuDomainObjExitMonitor(vm);
 
+    if (persistDisk) {
+        g_clear_pointer(&persistDisk->src->sliceStorage, virStorageSourceSliceFree);
+        qemuDomainSaveConfig(vm);
+    }
+
     ret = 0;
 
  endjob: