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

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