Blame SOURCES/kvm-iotests-108-Test-new-refcount-rebuild-algorithm.patch

0727d3
From d638552d76db0db9e2b6ae90a35f0b451b0cbaf8 Mon Sep 17 00:00:00 2001
0727d3
From: Hanna Reitz <hreitz@redhat.com>
0727d3
Date: Tue, 5 Apr 2022 15:46:51 +0200
0727d3
Subject: [PATCH 4/6] iotests/108: Test new refcount rebuild algorithm
0727d3
0727d3
RH-Author: Hanna Reitz <hreitz@redhat.com>
0727d3
RH-MergeRequest: 171: qcow2: Improve refcount structure rebuilding
0727d3
RH-Commit: [2/4] 2aa8c383f0c88c414f10ade8bd2e8af07c35f35b
0727d3
RH-Bugzilla: 1519071
0727d3
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
0727d3
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
0727d3
RH-Acked-by: Eric Blake <eblake@redhat.com>
0727d3
0727d3
One clear problem with how qcow2's refcount structure rebuild algorithm
0727d3
used to be before "qcow2: Improve refcount structure rebuilding" was
0727d3
that it is prone to failure for qcow2 images on block devices: There is
0727d3
generally unused space after the actual image, and if that exceeds what
0727d3
one refblock covers, the old algorithm would invariably write the
0727d3
reftable past the block device's end, which cannot work.  The new
0727d3
algorithm does not have this problem.
0727d3
0727d3
Test it with three tests:
0727d3
(1) Create an image with more empty space at the end than what one
0727d3
    refblock covers, see whether rebuilding the refcount structures
0727d3
    results in a change in the image file length.  (It should not.)
0727d3
0727d3
(2) Leave precisely enough space somewhere at the beginning of the image
0727d3
    for the new reftable (and the refblock for that place), see whether
0727d3
    the new algorithm puts the reftable there.  (It should.)
0727d3
0727d3
(3) Test the original problem: Create (something like) a block device
0727d3
    with a fixed size, then create a qcow2 image in there, write some
0727d3
    data, and then have qemu-img check rebuild the refcount structures.
0727d3
    Before HEAD^, the reftable would have been written past the image
0727d3
    file end, i.e. outside of what the block device provides, which
0727d3
    cannot work.  HEAD^ should have fixed that.
0727d3
    ("Something like a block device" means a loop device if we can use
0727d3
    one ("sudo -n losetup" works), or a FUSE block export with
0727d3
    growable=false otherwise.)
0727d3
0727d3
Reviewed-by: Eric Blake <eblake@redhat.com>
0727d3
Signed-off-by: Hanna Reitz <hreitz@redhat.com>
0727d3
Message-Id: <20220405134652.19278-3-hreitz@redhat.com>
0727d3
(cherry picked from commit 9ffd6d646d1d5ee9087a8cbf0b7d2f96c5656162)
0727d3
0727d3
Conflicts:
0727d3
- 108: The downstream qemu-storage-daemon does not support --daemonize,
0727d3
  so this switch has been replaced by a loop waiting for the PID file to
0727d3
  appear
0727d3
0727d3
Signed-off-by: Hanna Reitz <hreitz@redhat.com>
0727d3
---
0727d3
 tests/qemu-iotests/108     | 263 ++++++++++++++++++++++++++++++++++++-
0727d3
 tests/qemu-iotests/108.out |  81 ++++++++++++
0727d3
 2 files changed, 343 insertions(+), 1 deletion(-)
0727d3
0727d3
diff --git a/tests/qemu-iotests/108 b/tests/qemu-iotests/108
0727d3
index 8eaef0b8bf..23abbeaff0 100755
0727d3
--- a/tests/qemu-iotests/108
0727d3
+++ b/tests/qemu-iotests/108
0727d3
@@ -30,13 +30,20 @@ status=1	# failure is the default!
0727d3
 
0727d3
 _cleanup()
0727d3
 {
0727d3
-	_cleanup_test_img
0727d3
+    _cleanup_test_img
0727d3
+    if [ -f "$TEST_DIR/qsd.pid" ]; then
0727d3
+        qsd_pid=$(cat "$TEST_DIR/qsd.pid")
0727d3
+        kill -KILL "$qsd_pid"
0727d3
+        fusermount -u "$TEST_DIR/fuse-export" &>/dev/null
0727d3
+    fi
0727d3
+    rm -f "$TEST_DIR/fuse-export"
0727d3
 }
0727d3
 trap "_cleanup; exit \$status" 0 1 2 3 15
0727d3
 
0727d3
 # get standard environment, filters and checks
0727d3
 . ./common.rc
0727d3
 . ./common.filter
0727d3
+. ./common.qemu
0727d3
 
0727d3
 # This tests qcow2-specific low-level functionality
0727d3
 _supported_fmt qcow2
0727d3
@@ -47,6 +54,22 @@ _supported_os Linux
0727d3
 # files
0727d3
 _unsupported_imgopts 'refcount_bits=\([^1]\|.\([^6]\|$\)\)' data_file
0727d3
 
0727d3
+# This test either needs sudo -n losetup or FUSE exports to work
0727d3
+if sudo -n losetup &>/dev/null; then
0727d3
+    loopdev=true
0727d3
+else
0727d3
+    loopdev=false
0727d3
+
0727d3
+    # QSD --export fuse will either yield "Parameter 'id' is missing"
0727d3
+    # or "Invalid parameter 'fuse'", depending on whether there is
0727d3
+    # FUSE support or not.
0727d3
+    error=$($QSD --export fuse 2>&1)
0727d3
+    if [[ $error = *"'fuse'"* ]]; then
0727d3
+        _notrun 'Passwordless sudo for losetup or FUSE support required, but' \
0727d3
+                'neither is available'
0727d3
+    fi
0727d3
+fi
0727d3
+
0727d3
 echo
0727d3
 echo '=== Repairing an image without any refcount table ==='
0727d3
 echo
0727d3
@@ -138,6 +161,244 @@ _make_test_img 64M
0727d3
 poke_file "$TEST_IMG" $((0x10008)) "\xff\xff\xff\xff\xff\xff\x00\x00"
0727d3
 _check_test_img -r all
0727d3
 
0727d3
+echo
0727d3
+echo '=== Check rebuilt reftable location ==='
0727d3
+
0727d3
+# In an earlier version of the refcount rebuild algorithm, the
0727d3
+# reftable was generally placed at the image end (unless something was
0727d3
+# allocated in the area covered by the refblock right before the image
0727d3
+# file end, then we would try to place the reftable in that refblock).
0727d3
+# This was later changed so the reftable would be placed in the
0727d3
+# earliest possible location.  Test this.
0727d3
+
0727d3
+echo
0727d3
+echo '--- Does the image size increase? ---'
0727d3
+echo
0727d3
+
0727d3
+# First test: Just create some image, write some data to it, and
0727d3
+# resize it so there is free space at the end of the image (enough
0727d3
+# that it spans at least one full refblock, which for cluster_size=512
0727d3
+# images, spans 128k).  With the old algorithm, the reftable would
0727d3
+# have then been placed at the end of the image file, but with the new
0727d3
+# one, it will be put in that free space.
0727d3
+# We want to check whether the size of the image file increases due to
0727d3
+# rebuilding the refcount structures (it should not).
0727d3
+
0727d3
+_make_test_img -o 'cluster_size=512' 1M
0727d3
+# Write something
0727d3
+$QEMU_IO -c 'write 0 64k' "$TEST_IMG" | _filter_qemu_io
0727d3
+
0727d3
+# Add free space
0727d3
+file_len=$(stat -c '%s' "$TEST_IMG")
0727d3
+truncate -s $((file_len + 256 * 1024)) "$TEST_IMG"
0727d3
+
0727d3
+# Corrupt the image by saying the image header was not allocated
0727d3
+rt_offset=$(peek_file_be "$TEST_IMG" 48 8)
0727d3
+rb_offset=$(peek_file_be "$TEST_IMG" $rt_offset 8)
0727d3
+poke_file "$TEST_IMG" $rb_offset "\x00\x00"
0727d3
+
0727d3
+# Check whether rebuilding the refcount structures increases the image
0727d3
+# file size
0727d3
+file_len=$(stat -c '%s' "$TEST_IMG")
0727d3
+echo
0727d3
+# The only leaks there can be are the old refcount structures that are
0727d3
+# leaked during rebuilding, no need to clutter the output with them
0727d3
+_check_test_img -r all | grep -v '^Repairing cluster.*refcount=1 reference=0'
0727d3
+echo
0727d3
+post_repair_file_len=$(stat -c '%s' "$TEST_IMG")
0727d3
+
0727d3
+if [[ $file_len -eq $post_repair_file_len ]]; then
0727d3
+    echo 'OK: Image size did not change'
0727d3
+else
0727d3
+    echo 'ERROR: Image size differs' \
0727d3
+        "($file_len before, $post_repair_file_len after)"
0727d3
+fi
0727d3
+
0727d3
+echo
0727d3
+echo '--- Will the reftable occupy a hole specifically left for it?  ---'
0727d3
+echo
0727d3
+
0727d3
+# Note: With cluster_size=512, every refblock covers 128k.
0727d3
+# The reftable covers 8M per reftable cluster.
0727d3
+
0727d3
+# Create an image that requires two reftable clusters (just because
0727d3
+# this is more interesting than a single-clustered reftable).
0727d3
+_make_test_img -o 'cluster_size=512' 9M
0727d3
+$QEMU_IO -c 'write 0 8M' "$TEST_IMG" | _filter_qemu_io
0727d3
+
0727d3
+# Writing 8M will have resized the reftable.  Unfortunately, doing so
0727d3
+# will leave holes in the file, so we need to fill them up so we can
0727d3
+# be sure the whole file is allocated.  Do that by writing
0727d3
+# consecutively smaller chunks starting from 8 MB, until the file
0727d3
+# length increases even with a chunk size of 512.  Then we must have
0727d3
+# filled all holes.
0727d3
+ofs=$((8 * 1024 * 1024))
0727d3
+block_len=$((16 * 1024))
0727d3
+while [[ $block_len -ge 512 ]]; do
0727d3
+    file_len=$(stat -c '%s' "$TEST_IMG")
0727d3
+    while [[ $(stat -c '%s' "$TEST_IMG") -eq $file_len ]]; do
0727d3
+        # Do not include this in the reference output, it does not
0727d3
+        # really matter which qemu-io calls we do here exactly
0727d3
+        $QEMU_IO -c "write $ofs $block_len" "$TEST_IMG" >/dev/null
0727d3
+        ofs=$((ofs + block_len))
0727d3
+    done
0727d3
+    block_len=$((block_len / 2))
0727d3
+done
0727d3
+
0727d3
+# Fill up to 9M (do not include this in the reference output either,
0727d3
+# $ofs is random for all we know)
0727d3
+$QEMU_IO -c "write $ofs $((9 * 1024 * 1024 - ofs))" "$TEST_IMG" >/dev/null
0727d3
+
0727d3
+# Make space as follows:
0727d3
+# - For the first refblock: Right at the beginning of the image (this
0727d3
+#   refblock is placed in the first place possible),
0727d3
+# - For the reftable somewhere soon afterwards, still near the
0727d3
+#   beginning of the image (i.e. covered by the first refblock); the
0727d3
+#   reftable too is placed in the first place possible, but only after
0727d3
+#   all refblocks have been placed)
0727d3
+# No space is needed for the other refblocks, because no refblock is
0727d3
+# put before the space it covers.  In this test case, we do not mind
0727d3
+# if they are placed at the image file's end.
0727d3
+
0727d3
+# Before we make that space, we have to find out the host offset of
0727d3
+# the area that belonged to the two data clusters at guest offset 4k,
0727d3
+# because we expect the reftable to be placed there, and we will have
0727d3
+# to verify that it is.
0727d3
+
0727d3
+l1_offset=$(peek_file_be "$TEST_IMG" 40 8)
0727d3
+l2_offset=$(peek_file_be "$TEST_IMG" $l1_offset 8)
0727d3
+l2_offset=$((l2_offset & 0x00fffffffffffe00))
0727d3
+data_4k_offset=$(peek_file_be "$TEST_IMG" \
0727d3
+                 $((l2_offset + 4096 / 512 * 8)) 8)
0727d3
+data_4k_offset=$((data_4k_offset & 0x00fffffffffffe00))
0727d3
+
0727d3
+$QEMU_IO -c "discard 0 512" -c "discard 4k 1k" "$TEST_IMG" | _filter_qemu_io
0727d3
+
0727d3
+# Corrupt the image by saying the image header was not allocated
0727d3
+rt_offset=$(peek_file_be "$TEST_IMG" 48 8)
0727d3
+rb_offset=$(peek_file_be "$TEST_IMG" $rt_offset 8)
0727d3
+poke_file "$TEST_IMG" $rb_offset "\x00\x00"
0727d3
+
0727d3
+echo
0727d3
+# The only leaks there can be are the old refcount structures that are
0727d3
+# leaked during rebuilding, no need to clutter the output with them
0727d3
+_check_test_img -r all | grep -v '^Repairing cluster.*refcount=1 reference=0'
0727d3
+echo
0727d3
+
0727d3
+# Check whether the reftable was put where we expected
0727d3
+rt_offset=$(peek_file_be "$TEST_IMG" 48 8)
0727d3
+if [[ $rt_offset -eq $data_4k_offset ]]; then
0727d3
+    echo 'OK: Reftable is where we expect it'
0727d3
+else
0727d3
+    echo "ERROR: Reftable is at $rt_offset, but was expected at $data_4k_offset"
0727d3
+fi
0727d3
+
0727d3
+echo
0727d3
+echo '--- Rebuilding refcount structures on block devices ---'
0727d3
+echo
0727d3
+
0727d3
+# A block device cannot really grow, at least not during qemu-img
0727d3
+# check.  As mentioned in the above cases, rebuilding the refcount
0727d3
+# structure may lead to new refcount structures being written after
0727d3
+# the end of the image, and in the past that happened even if there
0727d3
+# was more than sufficient space in the image.  Such post-EOF writes
0727d3
+# will not work on block devices, so test that the new algorithm
0727d3
+# avoids it.
0727d3
+
0727d3
+# If we have passwordless sudo and losetup, we can use those to create
0727d3
+# a block device.  Otherwise, we can resort to qemu's FUSE export to
0727d3
+# create a file that isn't growable, which effectively tests the same
0727d3
+# thing.
0727d3
+
0727d3
+_cleanup_test_img
0727d3
+truncate -s $((64 * 1024 * 1024)) "$TEST_IMG"
0727d3
+
0727d3
+if $loopdev; then
0727d3
+    export_mp=$(sudo -n losetup --show -f "$TEST_IMG")
0727d3
+    export_mp_driver=host_device
0727d3
+    sudo -n chmod go+rw "$export_mp"
0727d3
+else
0727d3
+    # Create non-growable FUSE export that is a bit like an empty
0727d3
+    # block device
0727d3
+    export_mp="$TEST_DIR/fuse-export"
0727d3
+    export_mp_driver=file
0727d3
+    touch "$export_mp"
0727d3
+
0727d3
+    $QSD \
0727d3
+        --blockdev file,node-name=export-node,filename="$TEST_IMG" \
0727d3
+        --export fuse,id=fuse-export,node-name=export-node,mountpoint="$export_mp",writable=on,growable=off \
0727d3
+        --pidfile "$TEST_DIR/qsd.pid" \
0727d3
+        &
0727d3
+
0727d3
+    while [ ! -f "$TEST_DIR/qsd.pid" ]; do
0727d3
+        sleep 0.1
0727d3
+    done
0727d3
+fi
0727d3
+
0727d3
+# Now create a qcow2 image on the device -- unfortunately, qemu-img
0727d3
+# create force-creates the file, so we have to resort to the
0727d3
+# blockdev-create job.
0727d3
+_launch_qemu \
0727d3
+    --blockdev $export_mp_driver,node-name=file,filename="$export_mp"
0727d3
+
0727d3
+_send_qemu_cmd \
0727d3
+    $QEMU_HANDLE \
0727d3
+    '{ "execute": "qmp_capabilities" }' \
0727d3
+    'return'
0727d3
+
0727d3
+# Small cluster size again, so the image needs multiple refblocks
0727d3
+_send_qemu_cmd \
0727d3
+    $QEMU_HANDLE \
0727d3
+    '{ "execute": "blockdev-create",
0727d3
+       "arguments": {
0727d3
+           "job-id": "create",
0727d3
+           "options": {
0727d3
+               "driver": "qcow2",
0727d3
+               "file": "file",
0727d3
+               "size": '$((64 * 1024 * 1024))',
0727d3
+               "cluster-size": 512
0727d3
+           } } }' \
0727d3
+    '"concluded"'
0727d3
+
0727d3
+_send_qemu_cmd \
0727d3
+    $QEMU_HANDLE \
0727d3
+    '{ "execute": "job-dismiss", "arguments": { "id": "create" } }' \
0727d3
+    'return'
0727d3
+
0727d3
+_send_qemu_cmd \
0727d3
+    $QEMU_HANDLE \
0727d3
+    '{ "execute": "quit" }' \
0727d3
+    'return'
0727d3
+
0727d3
+wait=y _cleanup_qemu
0727d3
+echo
0727d3
+
0727d3
+# Write some data
0727d3
+$QEMU_IO -c 'write 0 64k' "$export_mp" | _filter_qemu_io
0727d3
+
0727d3
+# Corrupt the image by saying the image header was not allocated
0727d3
+rt_offset=$(peek_file_be "$export_mp" 48 8)
0727d3
+rb_offset=$(peek_file_be "$export_mp" $rt_offset 8)
0727d3
+poke_file "$export_mp" $rb_offset "\x00\x00"
0727d3
+
0727d3
+# Repairing such a simple case should just work
0727d3
+# (We used to put the reftable at the end of the image file, which can
0727d3
+# never work for non-growable devices.)
0727d3
+echo
0727d3
+TEST_IMG="$export_mp" _check_test_img -r all \
0727d3
+    | grep -v '^Repairing cluster.*refcount=1 reference=0'
0727d3
+
0727d3
+if $loopdev; then
0727d3
+    sudo -n losetup -d "$export_mp"
0727d3
+else
0727d3
+    qsd_pid=$(cat "$TEST_DIR/qsd.pid")
0727d3
+    kill -TERM "$qsd_pid"
0727d3
+    # Wait for process to exit (cannot `wait` because the QSD is daemonized)
0727d3
+    while [ -f "$TEST_DIR/qsd.pid" ]; do
0727d3
+        true
0727d3
+    done
0727d3
+fi
0727d3
+
0727d3
 # success, all done
0727d3
 echo '*** done'
0727d3
 rm -f $seq.full
0727d3
diff --git a/tests/qemu-iotests/108.out b/tests/qemu-iotests/108.out
0727d3
index 75bab8dc84..b5401d788d 100644
0727d3
--- a/tests/qemu-iotests/108.out
0727d3
+++ b/tests/qemu-iotests/108.out
0727d3
@@ -105,6 +105,87 @@ The following inconsistencies were found and repaired:
0727d3
     0 leaked clusters
0727d3
     1 corruptions
0727d3
 
0727d3
+Double checking the fixed image now...
0727d3
+No errors were found on the image.
0727d3
+
0727d3
+=== Check rebuilt reftable location ===
0727d3
+
0727d3
+--- Does the image size increase? ---
0727d3
+
0727d3
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576
0727d3
+wrote 65536/65536 bytes at offset 0
0727d3
+64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
0727d3
+
0727d3
+ERROR cluster 0 refcount=0 reference=1
0727d3
+Rebuilding refcount structure
0727d3
+The following inconsistencies were found and repaired:
0727d3
+
0727d3
+    0 leaked clusters
0727d3
+    1 corruptions
0727d3
+
0727d3
+Double checking the fixed image now...
0727d3
+No errors were found on the image.
0727d3
+
0727d3
+OK: Image size did not change
0727d3
+
0727d3
+--- Will the reftable occupy a hole specifically left for it?  ---
0727d3
+
0727d3
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=9437184
0727d3
+wrote 8388608/8388608 bytes at offset 0
0727d3
+8 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
0727d3
+discard 512/512 bytes at offset 0
0727d3
+512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
0727d3
+discard 1024/1024 bytes at offset 4096
0727d3
+1 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
0727d3
+
0727d3
+ERROR cluster 0 refcount=0 reference=1
0727d3
+Rebuilding refcount structure
0727d3
+The following inconsistencies were found and repaired:
0727d3
+
0727d3
+    0 leaked clusters
0727d3
+    1 corruptions
0727d3
+
0727d3
+Double checking the fixed image now...
0727d3
+No errors were found on the image.
0727d3
+
0727d3
+OK: Reftable is where we expect it
0727d3
+
0727d3
+--- Rebuilding refcount structures on block devices ---
0727d3
+
0727d3
+{ "execute": "qmp_capabilities" }
0727d3
+{"return": {}}
0727d3
+{ "execute": "blockdev-create",
0727d3
+       "arguments": {
0727d3
+           "job-id": "create",
0727d3
+           "options": {
0727d3
+               "driver": "IMGFMT",
0727d3
+               "file": "file",
0727d3
+               "size": 67108864,
0727d3
+               "cluster-size": 512
0727d3
+           } } }
0727d3
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "create"}}
0727d3
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "create"}}
0727d3
+{"return": {}}
0727d3
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "create"}}
0727d3
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "create"}}
0727d3
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "create"}}
0727d3
+{ "execute": "job-dismiss", "arguments": { "id": "create" } }
0727d3
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "create"}}
0727d3
+{"return": {}}
0727d3
+{ "execute": "quit" }
0727d3
+{"return": {}}
0727d3
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}}
0727d3
+
0727d3
+wrote 65536/65536 bytes at offset 0
0727d3
+64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
0727d3
+
0727d3
+ERROR cluster 0 refcount=0 reference=1
0727d3
+Rebuilding refcount structure
0727d3
+The following inconsistencies were found and repaired:
0727d3
+
0727d3
+    0 leaked clusters
0727d3
+    1 corruptions
0727d3
+
0727d3
 Double checking the fixed image now...
0727d3
 No errors were found on the image.
0727d3
 *** done
0727d3
-- 
0727d3
2.27.0
0727d3