diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst
index c3526439bf..2ea440e1a1 100644
--- a/docs/formatdomain.rst
+++ b/docs/formatdomain.rst
@@ -3284,6 +3284,13 @@ paravirtualized driver is specified via the ``disk`` element.
format driver of the ``qemu`` hypervisor can be controlled via the
``max_size`` subelement (see example below).
+ The optional ``discard_no_unref`` attribute can be set to control the way
+ the ``qemu`` hypervisor handles guest discard commands inside the qcow2
+ image. When enabled, a discard request from within the guest will mark the
+ qcow2 cluster as zero, but will keep the reference/offset of that cluster.
+ But it will still pass the discard further to the lower layer.
+ This will resolve fragmentation within the qcow2 image. :since:`Since 9.5.0`
+
In the majority of cases the default configuration used by the hypervisor
is sufficient so modifying this setting should not be necessary. For
specifics on how the metadata cache of ``qcow2`` in ``qemu`` behaves refer
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index 1fde53ff4c..4121b6a054 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -7826,6 +7826,10 @@ virDomainDiskDefDriverParseXML(virDomainDiskDef *def,
if (virXMLPropUInt(cur, "queue_size", 10, VIR_XML_PROP_NONE, &def->queue_size) < 0)
return -1;
+ if (virXMLPropTristateSwitch(cur, "discard_no_unref", VIR_XML_PROP_NONE,
+ &def->discard_no_unref) < 0)
+ return -1;
+
return 0;
}
@@ -22501,6 +22505,10 @@ virDomainDiskDefFormatDriver(virBuffer *buf,
virBufferAsprintf(&attrBuf, " detect_zeroes='%s'",
virDomainDiskDetectZeroesTypeToString(disk->detect_zeroes));
+ if (disk->discard_no_unref)
+ virBufferAsprintf(&attrBuf, " discard_no_unref='%s'",
+ virTristateSwitchTypeToString(disk->discard_no_unref));
+
if (disk->queues)
virBufferAsprintf(&attrBuf, " queues='%u'", disk->queues);
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h
index 629e32c39f..cddaa3824d 100644
--- a/src/conf/domain_conf.h
+++ b/src/conf/domain_conf.h
@@ -606,6 +606,7 @@ struct _virDomainDiskDef {
virDomainDiskDiscard discard;
unsigned int iothread; /* unused = 0, > 0 specific thread # */
virDomainDiskDetectZeroes detect_zeroes;
+ virTristateSwitch discard_no_unref;
char *domain_name; /* backend domain name */
unsigned int queues;
unsigned int queue_size;
diff --git a/src/conf/domain_validate.c b/src/conf/domain_validate.c
index 7e3c920a38..6c4cfa16d6 100644
--- a/src/conf/domain_validate.c
+++ b/src/conf/domain_validate.c
@@ -921,6 +921,20 @@ virDomainDiskDefValidate(const virDomainDef *def,
return -1;
}
+ if (disk->discard_no_unref == VIR_TRISTATE_SWITCH_ON) {
+ if (disk->src->format != VIR_STORAGE_FILE_QCOW2) {
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+ _("'discard_no_unref' only works with qcow2 disk format"));
+ return -1;
+ }
+
+ if (disk->src->readonly) {
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+ _("'discard_no_unref' is not compatible with read-only disk"));
+ return -1;
+ }
+ }
+
return 0;
}
diff --git a/src/conf/schemas/domaincommon.rng b/src/conf/schemas/domaincommon.rng
index 4b595fd066..fcf9e00600 100644
--- a/src/conf/schemas/domaincommon.rng
+++ b/src/conf/schemas/domaincommon.rng
@@ -2516,6 +2516,11 @@
+
+
+
+
+
diff --git a/src/conf/storage_source_conf.c b/src/conf/storage_source_conf.c
index 99061e4df7..dcac3a8ff6 100644
--- a/src/conf/storage_source_conf.c
+++ b/src/conf/storage_source_conf.c
@@ -809,6 +809,7 @@ virStorageSourceCopy(const virStorageSource *src,
def->cachemode = src->cachemode;
def->discard = src->discard;
def->detect_zeroes = src->detect_zeroes;
+ def->discard_no_unref = src->discard_no_unref;
def->sslverify = src->sslverify;
def->readahead = src->readahead;
def->timeout = src->timeout;
diff --git a/src/conf/storage_source_conf.h b/src/conf/storage_source_conf.h
index c6187dda59..f13e7c756a 100644
--- a/src/conf/storage_source_conf.h
+++ b/src/conf/storage_source_conf.h
@@ -399,6 +399,7 @@ struct _virStorageSource {
int cachemode; /* enum virDomainDiskCache */
int discard; /* enum virDomainDiskDiscard */
int detect_zeroes; /* enum virDomainDiskDetectZeroes */
+ virTristateSwitch discard_no_unref;
bool floppyimg; /* set to true if the storage source is going to be used
as a source for floppy drive */
diff --git a/src/qemu/qemu_block.c b/src/qemu/qemu_block.c
index 8b2159f845..dcdf883926 100644
--- a/src/qemu/qemu_block.c
+++ b/src/qemu/qemu_block.c
@@ -1117,12 +1117,11 @@ qemuBlockStorageSourceGetFormatQcow2Props(virStorageSource *src,
* see: qemu.git/docs/qcow2-cache.txt
* https://git.qemu.org/?p=qemu.git;a=blob;f=docs/qcow2-cache.txt
*/
- if (src->metadataCacheMaxSize > 0) {
- if (virJSONValueObjectAdd(&props,
- "U:cache-size", src->metadataCacheMaxSize,
- NULL) < 0)
- return -1;
- }
+ if (virJSONValueObjectAdd(&props,
+ "P:cache-size", src->metadataCacheMaxSize,
+ "T:discard-no-unref", src->discard_no_unref,
+ NULL) < 0)
+ return -1;
return 0;
}
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
index c65c571398..94587638c3 100644
--- a/src/qemu/qemu_domain.c
+++ b/src/qemu/qemu_domain.c
@@ -11039,6 +11039,7 @@ qemuDomainPrepareDiskSourceData(virDomainDiskDef *disk,
src->iomode = disk->iomode;
src->cachemode = disk->cachemode;
src->discard = disk->discard;
+ src->discard_no_unref = disk->discard_no_unref;
if (disk->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY)
src->floppyimg = true;
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index c4bd766531..f20544590d 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -14242,8 +14242,10 @@ qemuDomainBlockCopyCommon(virDomainObj *vm,
* into the topmost virStorage source of the disk chain.
* Since 'mirror' has the ambition to replace it we need to propagate
* it into the mirror too. We do it directly as otherwise we'd need
- * to modify all callers of 'qemuDomainPrepareStorageSourceBlockdev' */
+ * to modify all callers of 'qemuDomainPrepareStorageSourceBlockdev'
+ * Same for discard_no_unref */
mirror->detect_zeroes = disk->detect_zeroes;
+ mirror->discard_no_unref = disk->discard_no_unref;
/* If reusing an external image that includes a backing file but the user
* did not enumerate the chain in the XML we need to detect the chain */
diff --git a/src/qemu/qemu_validate.c b/src/qemu/qemu_validate.c
index 04d0c9df73..a53729d349 100644
--- a/src/qemu/qemu_validate.c
+++ b/src/qemu/qemu_validate.c
@@ -3266,6 +3266,13 @@ qemuValidateDomainDeviceDefDisk(const virDomainDiskDef *disk,
return -1;
}
+ if (disk->discard_no_unref != VIR_TRISTATE_SWITCH_ABSENT &&
+ !virQEMUCapsGet(qemuCaps, QEMU_CAPS_QCOW2_DISCARD_NO_UNREF)) {
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+ _("'discard_no_unref' is not supported by this QEMU binary"));
+ return -1;
+ }
+
for (n = disk->src; virStorageSourceIsBacking(n); n = n->backingStore) {
if (qemuDomainValidateStorageSource(n, qemuCaps) < 0)
return -1;
diff --git a/src/vz/vz_utils.c b/src/vz/vz_utils.c
index 2db1146149..7db7dbd419 100644
--- a/src/vz/vz_utils.c
+++ b/src/vz/vz_utils.c
@@ -347,6 +347,12 @@ vzCheckDiskUnsupportedParams(virDomainDiskDef *disk)
return -1;
}
+ if (disk->discard_no_unref) {
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+ _("Disk discard_no_unref is not supported by vz driver."));
+ return -1;
+ }
+
if (disk->startupPolicy != VIR_DOMAIN_STARTUP_POLICY_DEFAULT) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Setting up disk startup policy is not "
diff --git a/tests/qemuxml2argvdata/disk-discard_no_unref.x86_64-latest.args b/tests/qemuxml2argvdata/disk-discard_no_unref.x86_64-latest.args
new file mode 100644
index 0000000000..25b8211cf3
--- /dev/null
+++ b/tests/qemuxml2argvdata/disk-discard_no_unref.x86_64-latest.args
@@ -0,0 +1,36 @@
+LC_ALL=C \
+PATH=/bin \
+HOME=/var/lib/libvirt/qemu/domain--1-test \
+USER=test \
+LOGNAME=test \
+XDG_DATA_HOME=/var/lib/libvirt/qemu/domain--1-test/.local/share \
+XDG_CACHE_HOME=/var/lib/libvirt/qemu/domain--1-test/.cache \
+XDG_CONFIG_HOME=/var/lib/libvirt/qemu/domain--1-test/.config \
+/usr/bin/qemu-system-x86_64 \
+-name guest=test,debug-threads=on \
+-S \
+-object '{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/var/lib/libvirt/qemu/domain--1-test/master-key.aes"}' \
+-machine pc,usb=off,dump-guest-core=off,memory-backend=pc.ram,acpi=off \
+-accel tcg \
+-cpu qemu64 \
+-m size=1048576k \
+-object '{"qom-type":"memory-backend-ram","id":"pc.ram","size":1073741824}' \
+-overcommit mem-lock=off \
+-smp 1,sockets=1,cores=1,threads=1 \
+-uuid 92d7a226-cfae-425b-a6d3-00bbf9ec5c9e \
+-display none \
+-no-user-config \
+-nodefaults \
+-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \
+-mon chardev=charmonitor,id=monitor,mode=control \
+-rtc base=utc \
+-no-shutdown \
+-boot menu=on,strict=on \
+-device '{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}' \
+-blockdev '{"driver":"file","filename":"/var/lib/libvirt/images/f14.img","node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}' \
+-blockdev '{"node-name":"libvirt-1-format","read-only":false,"discard":"unmap","driver":"qcow2","discard-no-unref":true,"file":"libvirt-1-storage"}' \
+-device '{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x4","drive":"libvirt-1-format","id":"virtio-disk0","bootindex":2}' \
+-audiodev '{"id":"audio1","driver":"none"}' \
+-device '{"driver":"virtio-balloon-pci","id":"balloon0","bus":"pci.0","addr":"0x2"}' \
+-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \
+-msg timestamp=on
diff --git a/tests/qemuxml2argvdata/disk-discard_no_unref.xml b/tests/qemuxml2argvdata/disk-discard_no_unref.xml
new file mode 100644
index 0000000000..d5dd054a85
--- /dev/null
+++ b/tests/qemuxml2argvdata/disk-discard_no_unref.xml
@@ -0,0 +1,32 @@
+
+ test
+ 92d7a226-cfae-425b-a6d3-00bbf9ec5c9e
+ 1048576
+ 1048576
+ 1
+
+ hvm
+
+
+
+
+
+ destroy
+ restart
+ restart
+
+ /usr/bin/qemu-system-x86_64
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c
index 0b5a2475c9..c1bba779b3 100644
--- a/tests/qemuxml2argvtest.c
+++ b/tests/qemuxml2argvtest.c
@@ -1245,6 +1245,7 @@ mymain(void)
DO_TEST_CAPS_LATEST("disk-copy_on_read");
DO_TEST_CAPS_LATEST("disk-discard");
DO_TEST_CAPS_LATEST("disk-detect-zeroes");
+ DO_TEST_CAPS_LATEST("disk-discard_no_unref");
DO_TEST_CAPS_LATEST("disk-snapshot");
DO_TEST_CAPS_LATEST_PARSE_ERROR("disk-same-targets");
DO_TEST_CAPS_LATEST_PARSE_ERROR("disk-missing-target-invalid");
diff --git a/tests/qemuxml2xmloutdata/disk-discard_no_unref.x86_64-latest.xml b/tests/qemuxml2xmloutdata/disk-discard_no_unref.x86_64-latest.xml
new file mode 100644
index 0000000000..c57acf99dc
--- /dev/null
+++ b/tests/qemuxml2xmloutdata/disk-discard_no_unref.x86_64-latest.xml
@@ -0,0 +1,42 @@
+
+ test
+ 92d7a226-cfae-425b-a6d3-00bbf9ec5c9e
+ 1048576
+ 1048576
+ 1
+
+ hvm
+
+
+
+
+
+ qemu64
+
+
+ destroy
+ restart
+ restart
+
+ /usr/bin/qemu-system-x86_64
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/qemuxml2xmltest.c b/tests/qemuxml2xmltest.c
index 5821e07cf1..565cb3e1e1 100644
--- a/tests/qemuxml2xmltest.c
+++ b/tests/qemuxml2xmltest.c
@@ -599,6 +599,7 @@ mymain(void)
DO_TEST_CAPS_LATEST("disk-discard");
DO_TEST_CAPS_LATEST("disk-detect-zeroes");
+ DO_TEST_CAPS_LATEST("disk-discard_no_unref");
DO_TEST_NOCAPS("disk-serial");