Description of problem: When downloading preallocated disk the downloaded image is fully allocated instead of sparse. This is inefficient when the image should be transferred to another machine, or uploaded back to storage. This also affects full and incremental backup, increasing the size of the backup, and slowing down restore. Backup vendors that do zero detection and deduplication should not be affected. When copying images using "qemu-img convert", images are sparsified during the copy, creating smaller images. Example download with 10g preallocated image: $ ./download_disk.py -c engine-dev 5154931f-a988-4b3a-b248-6c9466163ff7 /data/scratch/nsoffer/download.qcow2 [ 0.0 ] Connecting... [ 0.2 ] Creating image transfer... [ 1.9 ] Transfer ID: 596d4685-c15c-451e-a8f9-a96662db7203 [ 1.9 ] Transfer host name: host4 [ 1.9 ] Downloading disk... [ 100.00% ] 10.00 GiB, 27.39 seconds, 373.93 MiB/s [ 29.3 ] Finalizing image transfer... [ 35.5 ] Download completed successfully Querying the image show that is is fully allocated: $ qemu-img info /data/scratch/nsoffer/download.qcow2 image: /data/scratch/nsoffer/download.qcow2 file format: qcow2 virtual size: 10 GiB (10737418240 bytes) disk size: 10 GiB cluster_size: 65536 Format specific information: compat: 1.1 compression type: zlib lazy refcounts: false refcount bits: 16 corrupt: false extended l2: false The original image is empty, so it should downloaded as a very small qcow2 image, with all extents detected as "zero": true, but we get "zero": false. $ qemu-img map --output json /data/scratch/nsoffer/download.qcow2 | head [{ "start": 0, "length": 4194304, "depth": 0, "present": true, "zero": false, "data": true, "offset": 12910592}, { "start": 4194304, "length": 4194304, "depth": 0, "present": true, "zero": false, "data": true, "offset": 29687808}, { "start": 8388608, "length": 4194304, "depth": 0, "present": true, "zero": false, "data": true, "offset": 50659328}, { "start": 12582912, "length": 4194304, "depth": 0, "present": true, "zero": false, "data": true, "offset": 63242240}, { "start": 16777216, "length": 8388608, "depth": 0, "present": true, "zero": false, "data": true, "offset": 92602368}, { "start": 25165824, "length": 4194304, "depth": 0, "present": true, "zero": false, "data": true, "offset": 117768192}, { "start": 29360128, "length": 4194304, "depth": 0, "present": true, "zero": false, "data": true, "offset": 126156800}, { "start": 33554432, "length": 4194304, "depth": 0, "present": true, "zero": false, "data": true, "offset": 147128320}, { "start": 37748736, "length": 4194304, "depth": 0, "present": true, "zero": false, "data": true, "offset": 155516928}, { "start": 41943040, "length": 4194304, "depth": 0, "present": true, "zero": false, "data": true, "offset": 201654272}, $ qemu-img map --output json /data/scratch/nsoffer/download.qcow2 | wc -l 2105 $ qemu-img map --output json /data/scratch/nsoffer/download.qcow2 | grep '"data": true' | wc -l 2105 Uploading the image back to storage is slow, since we have to upload 10 GiB of zeroes: $ ./upload_disk.py -c engine-dev --sd-name alpine-iscsi-01 --disk-format raw /data/scratch/nsoffer/download.qcow2 [ 0.0 ] Checking image... [ 0.0 ] Image format: qcow2 [ 0.0 ] Disk format: raw [ 0.0 ] Disk content type: data [ 0.0 ] Disk provisioned size: 10737418240 [ 0.0 ] Disk initial size: 10737418240 [ 0.0 ] Disk name: download.raw [ 0.0 ] Disk backup: False [ 0.0 ] Connecting... [ 0.0 ] Creating disk... [ 15.8 ] Disk ID: 40f672eb-48b3-4002-87f1-1473369e032e [ 15.8 ] Creating image transfer... [ 17.5 ] Transfer ID: e2886b1a-0edd-4843-9bcb-9b8060f2e098 [ 17.5 ] Transfer host name: host4 [ 17.5 ] Uploading image... [ 100.00% ] 10.00 GiB, 36.87 seconds, 277.75 MiB/s [ 54.4 ] Finalizing image transfer... [ 61.6 ] Upload completed successfully If we use --detect-zeroes=unmap in qemu-nbd: $ ./download_disk.py -c engine-dev 5154931f-a988-4b3a-b248-6c9466163ff7 /data/scratch/nsoffer/download.qcow2 [ 0.0 ] Connecting... [ 0.2 ] Creating image transfer... [ 1.9 ] Transfer ID: 1c1b0f5f-1219-497d-99d3-b006ffcfcb7c [ 1.9 ] Transfer host name: host4 [ 1.9 ] Downloading disk... [ 100.00% ] 10.00 GiB, 22.84 seconds, 448.37 MiB/s [ 24.7 ] Finalizing image transfer... [ 25.8 ] Download completed successfully The download is faster, since writing zeroes is much faster than writing actual data (writing zero clusters in qcow2 image). The image contains only metadata for the zeroed areas: $ qemu-img info /data/scratch/nsoffer/download.qcow2 image: /data/scratch/nsoffer/download.qcow2 file format: qcow2 virtual size: 10 GiB (10737418240 bytes) disk size: 1.5 MiB cluster_size: 65536 Format specific information: compat: 1.1 compression type: zlib lazy refcounts: false refcount bits: 16 corrupt: false extended l2: false And querying the image show that it is completely zero: $ qemu-img map --output json /data/scratch/nsoffer/download.qcow2 [{ "start": 0, "length": 10737418240, "depth": 0, "present": true, "zero": true, "data": false}] Uploading the downloaded image is 4.5 times faster since we don't have to upload any data, only send fast zero requests: $ ./upload_disk.py -c engine-dev --sd-name alpine-iscsi-01 --disk-format raw /data/scratch/nsoffer/download.qcow2 [ 0.0 ] Checking image... [ 0.0 ] Image format: qcow2 [ 0.0 ] Disk format: raw [ 0.0 ] Disk content type: data [ 0.0 ] Disk provisioned size: 10737418240 [ 0.0 ] Disk initial size: 10737418240 [ 0.0 ] Disk name: download.raw [ 0.0 ] Disk backup: False [ 0.0 ] Connecting... [ 0.0 ] Creating disk... [ 8.6 ] Disk ID: 059de1c6-78c0-45eb-90c0-7fc03930f8e9 [ 8.6 ] Creating image transfer... [ 10.3 ] Transfer ID: 1b0c4dba-fa9b-47e1-b467-f8e3e48acf76 [ 10.3 ] Transfer host name: host4 [ 10.3 ] Uploading image... [ 100.00% ] 10.00 GiB, 7.66 seconds, 1.31 GiB/s [ 18.0 ] Finalizing image transfer... [ 24.1 ] Upload completed successfully Version-Release number of selected component (if applicable): 2.3.0-0 How reproducible: Always Steps to Reproduce: 1. Download empty raw preallocated disk Actual results: Zeroed areas are stored as data. Expected results: Zeroed areas are stored as zero cluster (qcow2) or unallocated areas (raw). Testing notes: Should be tested with: - empty image (best case) - image full with non-zero data (worst case) - common images - 50% full image (common case) Can be tested with any setup to evaluate the functionality. To test performance it should be tested with real server and storage. The easiest way to test this is to add a raw preallocated disk to a VM, and download the disk. To ensure that new empty disk contain zeros, zero it: blkdiscard --zeroout /dev/vdb To test partly utilized disk, write data directly to the disk: dd if=/dev/zero bs=1M count=5120 | tr "\0" "\1" > /dev/vdb sync
Example functional test: 1. Add 2g preallocated disk with alias=empty uuid=7be36ade-6b39-4da2-ad1d-1a2a74381d1f 2. Add 2g preallocated disk with alias=half uuid=6f09ea12-d8a6-48a3-8c05-5ecd94b34403 3. Add 2g preallocated disk with alias=full uuid=baf0e5df-ba31-42d5-902a-94735b83e911 4. Start the VM 5. Zero the tests disks for disk_id in 7be36ade-6b39-4da2-ad1d-1a2a74381d1f 6f09ea12-d8a6-48a3-8c05-5ecd94b34403 baf0e5df-ba31-42d5-902a-94735b83e911; do blkdiscard --zeroout /dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_$disk_id done 6. Write 1g data to disk "half" dd if=/dev/zero bs=1M count=1024 | tr "\0" "\1" > /dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_6f09ea12-d8a6-48a3-8c05-5ecd94b34403 7. Write 2g of data to disk "full" dd if=/dev/zero bs=1M count=2048 | tr "\0" "\1" > /dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_baf0e5df-ba31-42d5-902a-94735b83e911 8. Shut down the VM 9. Download disk "empty" $ ./download_disk.py -c engine-dev 7be36ade-6b39-4da2-ad1d-1a2a74381d1f empty.qcow2 10. Download disk "half" $ ./download_disk.py -c engine-dev 6f09ea12-d8a6-48a3-8c05-5ecd94b34403 /data/scratch/nsoffer/half.qcow2 11. Download disk "full" $ ./download_disk.py -c engine-dev baf0e5df-ba31-42d5-902a-94735b83e911 /data/scratch/nsoffer/full.qcow2 12. Check downloaded images size $ qemu-img info empty.qcow2 | grep 'disk size' disk size: 512 KiB $ qemu-img info half.qcow2 | grep 'disk size' disk size: 1 GiB $ qemu-img info full.qcow2 | grep 'disk size' disk size: 2 GiB
$ git tag --contains 70e9fb26d8acdd1dbd72e0de85d063ba56b67e19 v2.3.0 v2.4.1
Verified. Zeroed areas are not stored as data, extends are detected as "zero": true, and image size is as big as it should be while testing all best/common/worst cases. Version 4.5.0-0.237.el8ev ovirt-imageio-client-2.4.1-1.el8ev.x86_64
This bugzilla is included in oVirt 4.5.0 release, published on April 20th 2022. Since the problem described in this bug report should be resolved in oVirt 4.5.0 release, it has been closed with a resolution of CURRENT RELEASE. If the solution does not work for you, please open a new bug report.