qemu-block
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-block] [PATCH v5 17/17] iotests: Test qcow2's overlap check memory


From: Max Reitz
Subject: [Qemu-block] [PATCH v5 17/17] iotests: Test qcow2's overlap check memory limit
Date: Mon, 27 Jul 2015 21:02:26 +0200

This patch adds some test cases for the memory limit concerning the
in-memory structures used to detect and prevent accidental metadata
overlaps.

Signed-off-by: Max Reitz <address@hidden>
---
 tests/qemu-iotests/060     | 222 +++++++++++++++++++++++++++++++++++++++++++++
 tests/qemu-iotests/060.out |  47 ++++++++++
 tests/qemu-iotests/group   |   2 +-
 3 files changed, 270 insertions(+), 1 deletion(-)

diff --git a/tests/qemu-iotests/060 b/tests/qemu-iotests/060
index c81319c..b0bc86a 100755
--- a/tests/qemu-iotests/060
+++ b/tests/qemu-iotests/060
@@ -37,6 +37,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15
 # get standard environment, filters and checks
 . ./common.rc
 . ./common.filter
+. ./common.qemu
 
 # This tests qocw2-specific low-level functionality
 _supported_fmt qcow2
@@ -243,6 +244,227 @@ poke_file "$TEST_IMG" "$(($l2_offset+8))" 
"\x80\x00\x00\x00\x00\x06\x2a\x00"
 # Should emit two error messages
 $QEMU_IO -c "discard 0 64k" -c "read 64k 64k" "$TEST_IMG" | _filter_qemu_io
 
+echo
+echo "=== Testing memory limit ==="
+echo
+
+_make_test_img 64M
+# Use blockdev-add instead of adding the drive at startup, so that events which
+# are generated when the image is opened will be sent over QMP
+_launch_qemu
+_send_qemu_cmd $QEMU_HANDLE "{ 'execute': 'qmp_capabilities' }" 'return'
+
+echo
+echo '--- Zero limit ---'
+echo
+
+# This should fail (because allocating the metadata structure list itself 
fails)
+_send_qemu_cmd $QEMU_HANDLE \
+    "{ 'execute': 'blockdev-add'," \
+    "  'arguments': {" \
+    "      'options': {" \
+    "          'id': 'drv0'," \
+    "          'driver': 'qcow2'," \
+    "          'overlap-structures': {" \
+    "              'total-size-limit': 0" \
+    "          }," \
+    "          'file': {" \
+    "             'driver': 'file'," \
+    "             'filename': '$TEST_IMG'" \
+    "          } } } }" \
+    'error'
+
+echo
+echo '--- Zero limit with overlap checks disabled ---'
+echo
+
+# This should work (because overlap checks are disabled)
+_send_qemu_cmd $QEMU_HANDLE \
+    "{ 'execute': 'blockdev-add'," \
+    "  'arguments': {" \
+    "      'options': {" \
+    "          'id': 'drv1'," \
+    "          'driver': 'qcow2'," \
+    "          'overlap-check': 'none'," \
+    "          'overlap-structures': {" \
+    "              'total-size-limit': 0" \
+    "          }," \
+    "          'file': {" \
+    "             'driver': 'file'," \
+    "             'filename': '$TEST_IMG'" \
+    "          } } } }" \
+    'return'
+
+echo
+echo '--- Limit too small for entering metadata ---'
+echo
+
+# For the initial metadata structures, the following data is required:
+#  - Qcow2MetadataList (32 B on x86, 56 B on x64)
+#  - nb_windows * Qcow2MetadataWindow (nb_windows is
+#    ceil(x / (64k * WINDOW_SIZE)) = 1 (WINDOW_SIZE = 4096; x is the size of 
the
+#    underlying file, which is probably below 1 MB)); therefore, this is 24 B 
on
+#    x86, and 32 B on x64)
+#  - cache_entries * int (cache_entries is cache_size / WINDOW_SIZE =
+#    65536 / 4096 = 16, therefore this is 64 B on both x86 and x64)
+# In total, we need 120 B on x86 and 152 B on x64 (Linux/gcc).
+#
+# Creating a single bytemap takes WINDOW_SIZE (4096) B. Therefore, setting the
+# limit to 256 B will definitely suffice for the structures required to create
+# the metadata structures, but trying to enter even a single metadata structure
+# will fail.
+# The "start" field of the event(s) generated will be 0; the "length" field 
will
+# be WINDOW_SIZE * cluster_size = 4096 * 64k = 256M.
+_send_qemu_cmd $QEMU_HANDLE \
+    "{ 'execute': 'blockdev-add'," \
+    "  'arguments': {" \
+    "      'options': {" \
+    "          'id': 'drv2'," \
+    "          'driver': 'qcow2'," \
+    "          'overlap-structures': {" \
+    "              'bitmap-size': 65536," \
+    "              'total-size-limit': 256" \
+    "          }," \
+    "          'file': {" \
+    "             'driver': 'file'," \
+    "             'filename': '$TEST_IMG'" \
+    "          } } } }" \
+    'return'
+
+_cleanup_qemu
+
+echo
+echo '--- Limit too small for both bitmap and fragment list ---'
+echo
+
+# Now test the case of trying to convert the bitmap back to the fragment list,
+# but where memory does not suffice to hold both. Because the bitmap is 
released
+# after the fragment list has been created, it should work anyway, because the
+# implementation can consider the bitmap freed before it is actually freed.
+# In order for this to work on both x86 and x64, the fragment list needs to 
take
+# up more than 32 B (152 - 120) of memory. Every entry has 4 B, so we need 9
+# entries. One is the image header, then there is the L1 table, the refcount
+# table, a refcount block, and then there are more than 1000 free clusters; a
+# maximum of 256 clusters can be represented by a single fragment, so these are
+# another 4 entries. In total, there are thus 8 entries so far, so we need
+# another one, which we can do by writing data and thus creating an L2 table.
+$QEMU_IO -c 'write 0 64k' "$TEST_IMG" | _filter_qemu_io
+
+# Also, we need to point to an offset beyond WINDOW_SIZE * cluster_size (256M),
+# so that we can make qcow2 evict the first window.
+poke_file "$TEST_IMG" "$(($l2_offset+8))" "\x80\x00\x00\x00\x10\x00\x00\x00"
+
+_launch_qemu
+_send_qemu_cmd $QEMU_HANDLE "{ 'execute': 'qmp_capabilities' }" 'return'
+
+# For the planned cache eviction to work, bitmap-size must be WINDOW_SIZE;
+# therefore, the initial metadata structure size shrinks by 15 * sizeof(int),
+# that is, 60 B on both x86 and x64.
+# The total-size-limit should thus be 92 + 4096 = 4188
+_send_qemu_cmd $QEMU_HANDLE \
+    "{ 'execute': 'blockdev-add'," \
+    "  'arguments': {" \
+    "      'options': {" \
+    "          'id': 'drv0'," \
+    "          'driver': 'qcow2'," \
+    "          'overlap-structures': {" \
+    "              'bitmap-size': 4096," \
+    "              'total-size-limit': 4188" \
+    "          }," \
+    "          'file': {" \
+    "             'driver': 'file'," \
+    "             'filename': '$TEST_IMG'" \
+    "          } } } }" \
+    'return'
+
+# Writing something to the second data cluster, thus making qemu load the 
second
+# metadata window, evicting the first one
+_send_qemu_cmd $QEMU_HANDLE \
+    "{ 'execute': 'human-monitor-command'," \
+    "  'arguments': { 'command-line': 'qemu-io drv0 \"write 64k 64k\"' } }" \
+    'return'
+
+_cleanup_qemu
+
+echo
+echo '--- Fragment list is larger than bitmap ---'
+echo
+
+# Finally, test that bitmaps are not converted to fragment lists if the 
fragment
+# list actually is longer than the bitmap.
+CLUSTER_SIZE=512 _make_test_img 64M
+
+# Every L2 table describes 512 / 8 = 64 clusters, that is, a range of 32k.
+# Therefore, writing clusters 32k apart results in a alternating pattern of
+# L2 table and data cluster. If we write 1024 clusters in this way, the 
fragment
+# list for the first window would therefore have to contain at least 2048
+# fragments, which is longer (8 kB) than the bitmap is (4 kB fixed). Therefore,
+# the fragment list should not be generated on cache eviction.
+(for i in $(seq 0 1023); do
+    echo "write $(($i*32))k 512"
+done) | $QEMU_IO -t unsafe "$TEST_IMG" &> /dev/null
+
+l2_offset_512=$((0x4600))
+
+# Now point the first data cluster to WINDOW_SIZE * 512 = 2M
+# (the image length is about 2048 * 512 = 1M at this point)
+poke_file "$TEST_IMG" "$(($l2_offset_512))" "\x80\x00\x00\x00\x00\x20\x00\x00"
+# (the first cluster is now leaked, but who cares)
+
+# "Allocate" that cluster
+$QEMU_IO -c 'write 0 512' "$TEST_IMG" | _filter_qemu_io
+
+# And point the second data cluster to the first L2 table so we can test 
whether
+# the overlap check still works
+poke_file "$TEST_IMG" "$(($l2_offset_512+8))" 
"\x80\x00\x00\x00\x00\x00\x46\x00"
+
+cp "$TEST_IMG" /tmp
+
+_launch_qemu
+_send_qemu_cmd $QEMU_HANDLE "{ 'execute': 'qmp_capabilities' }" 'return'
+
+# Memory which we will allow to be allocated:
+#  - Qcow2MetadataList (32 B on x86, 56 B on x64)
+#  - nb_windows * Qcow2MetadataWindow (nb_windows is 2, because we made sure
+#    that we would have an image with two windows; therefore, this is 48 B on
+#    x86, and 64 B on x64)
+#  - cache_entries * int (cache_entries is cache_size / WINDOW_SIZE =
+#    4096 / 4096 = 1, therefore this is 4 B on both x86 and x64)
+#  - One bitmap (WINDOW_SIZE = 4096 B)
+# In total, that is 4220 B at most. If the bytemap would be converted to the
+# according fragment list, it would require 8 kB, which is definitely more.
+_send_qemu_cmd $QEMU_HANDLE \
+    "{ 'execute': 'blockdev-add'," \
+    "  'arguments': {" \
+    "      'options': {" \
+    "          'id': 'drv0'," \
+    "          'driver': 'qcow2'," \
+    "          'overlap-structures': {" \
+    "              'bitmap-size': 4096," \
+    "              'total-size-limit': 4220" \
+    "          }," \
+    "          'file': {" \
+    "             'driver': 'file'," \
+    "             'filename': '$TEST_IMG'" \
+    "          } } } }" \
+    'return'
+
+# Force cache eviction by accessing a cluster in the second window
+# (This will result in a "memory limit reached" event because there is no space
+# for the second bitmap)
+_send_qemu_cmd $QEMU_HANDLE \
+    "{ 'execute': 'human-monitor-command'," \
+    "  'arguments': { 'command-line': 'qemu-io drv0 \"write 0 512\"' } }" \
+    'return'
+
+# Trigger the overlap prevention
+_send_qemu_cmd $QEMU_HANDLE \
+    "{ 'execute': 'human-monitor-command'," \
+    "  'arguments': { 'command-line': 'qemu-io drv0 \"write 512 512\"' } }" \
+    'return'
+
+_cleanup_qemu
+
 # success, all done
 echo "*** done"
 rm -f $seq.full
diff --git a/tests/qemu-iotests/060.out b/tests/qemu-iotests/060.out
index 7511189..f471c2d 100644
--- a/tests/qemu-iotests/060.out
+++ b/tests/qemu-iotests/060.out
@@ -180,4 +180,51 @@ qcow2: Marking image as corrupt: Data cluster offset 
0x62a00 unaligned (L2 offse
 discard 65536/65536 bytes at offset 0
 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 read failed: Input/output error
+
+=== Testing memory limit ===
+
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
+{"return": {}}
+
+--- Zero limit ---
+
+{"error": {"class": "GenericError", "desc": "Cannot allocate metadata list"}}
+
+--- Zero limit with overlap checks disabled ---
+
+{"return": {}}
+
+--- Limit too small for entering metadata ---
+
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": 
"QCOW2_OVERLAP_CHECK_MEMORY_LIMIT_REACHED", "data": {"length": 268435456, 
"start": 0, "reference": "drv2"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": 
"QCOW2_OVERLAP_CHECK_MEMORY_LIMIT_REACHED", "data": {"length": 268435456, 
"start": 0, "reference": "drv2"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": 
"QCOW2_OVERLAP_CHECK_MEMORY_LIMIT_REACHED", "data": {"length": 268435456, 
"start": 0, "reference": "drv2"}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": 
"QCOW2_OVERLAP_CHECK_MEMORY_LIMIT_REACHED", "data": {"length": 268435456, 
"start": 0, "reference": "drv2"}}
+{"return": {}}
+
+--- Limit too small for both bitmap and fragment list ---
+
+wrote 65536/65536 bytes at offset 0
+64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+{"return": {}}
+{"return": {}}
+wrote 65536/65536 bytes at offset 65536
+64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+{"return": ""}
+
+--- Fragment list is larger than bitmap ---
+
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
+wrote 512/512 bytes at offset 0
+512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+{"return": {}}
+{"return": {}}
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": 
"QCOW2_OVERLAP_CHECK_MEMORY_LIMIT_REACHED", "data": {"length": 2097152, 
"start": 2097152, "reference": "drv0"}}
+wrote 512/512 bytes at offset 0
+512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+{"return": ""}
+qcow2: Marking image as corrupt: Preventing invalid write on metadata 
(overlaps with active L2 table); further corruption events will be suppressed
+{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": 
"BLOCK_IMAGE_CORRUPTED", "data": {"device": "drv0", "msg": "Preventing invalid 
write on metadata (overlaps with active L2 table)", "offset": 17920, "fatal": 
true, "size": 512}}
+write failed: Input/output error
+{"return": ""}
 *** done
diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group
index 6206765..a9c2286 100644
--- a/tests/qemu-iotests/group
+++ b/tests/qemu-iotests/group
@@ -66,7 +66,7 @@
 057 rw auto
 058 rw auto quick
 059 rw auto quick
-060 rw auto quick
+060 rw auto
 061 rw auto
 062 rw auto quick
 063 rw auto quick
-- 
2.4.6




reply via email to

[Prev in Thread] Current Thread [Next in Thread]