aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2019-01-15 14:19:18 +0000
committerPeter Maydell <peter.maydell@linaro.org>2019-01-15 14:19:18 +0000
commit44ba6010635641a538c9b9b1f377dfa288751906 (patch)
tree080c6791a68fe518db0b523090c012eea98c321b /tests
parentb2f7c27f56bf1116ebb7848c75914aa7c5d6a040 (diff)
parent636192c4b6052820ea126a5287c58a8f53f3c84f (diff)
Merge remote-tracking branch 'remotes/ericb/tags/pull-nbd-2019-01-14' into staging
nbd patches for 2019-01-14 Promote bitmap/NBD interfaces to stable for use in incremental backups. Add 'qemu-nbd --bitmap'. - John Snow: 0/11 bitmaps: remove x- prefix from QMP api - Philippe Mathieu-Daudé: qemu-nbd: Rename 'exp' variable clashing with math::exp() symbol - Eric Blake: 0/8 Promote x-nbd-server-add-bitmap to stable # gpg: Signature made Mon 14 Jan 2019 16:13:45 GMT # gpg: using RSA key A7A16B4A2527436A # gpg: Good signature from "Eric Blake <eblake@redhat.com>" # gpg: aka "Eric Blake (Free Software Programmer) <ebb9@byu.net>" # gpg: aka "[jpeg image of size 6874]" # Primary key fingerprint: 71C2 CC22 B1C4 6029 27D2 F3AA A7A1 6B4A 2527 436A * remotes/ericb/tags/pull-nbd-2019-01-14: qemu-nbd: Add --bitmap=NAME option nbd: Merge nbd_export_bitmap into nbd_export_new nbd: Remove x-nbd-server-add-bitmap nbd: Allow bitmap export during QMP nbd-server-add nbd: Merge nbd_export_set_name into nbd_export_new nbd: Only require disabled bitmap for read-only exports nbd: Forbid nbd-server-stop when server is not running nbd: Add some error case testing to iotests 223 qemu-nbd: Rename 'exp' variable clashing with math::exp() symbol iotests: add iotest 236 for testing bitmap merge iotests: implement pretty-print for log and qmp_log iotests: change qmp_log filters to expect QMP objects only iotests: remove default filters from qmp_log iotests: add qmp recursive sorting function iotests: add filter_generated_node_ids iotests.py: don't abort if IMGKEYSECRET is undefined block: remove 'x' prefix from experimental bitmap APIs blockdev: n-ary bitmap merge block/dirty-bitmap: remove assertion from restore blockdev: abort transactions in reverse order Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'tests')
-rwxr-xr-xtests/qemu-iotests/2068
-rwxr-xr-xtests/qemu-iotests/22352
-rw-r--r--tests/qemu-iotests/223.out23
-rwxr-xr-xtests/qemu-iotests/236161
-rw-r--r--tests/qemu-iotests/236.out351
-rw-r--r--tests/qemu-iotests/group1
-rw-r--r--tests/qemu-iotests/iotests.py64
7 files changed, 635 insertions, 25 deletions
diff --git a/tests/qemu-iotests/206 b/tests/qemu-iotests/206
index 128c334c7c..5bb738bf23 100755
--- a/tests/qemu-iotests/206
+++ b/tests/qemu-iotests/206
@@ -26,7 +26,9 @@ from iotests import imgfmt
iotests.verify_image_format(supported_fmts=['qcow2'])
def blockdev_create(vm, options):
- result = vm.qmp_log('blockdev-create', job_id='job0', options=options)
+ result = vm.qmp_log('blockdev-create',
+ filters=[iotests.filter_qmp_testfiles],
+ job_id='job0', options=options)
if 'return' in result:
assert result['return'] == {}
@@ -52,7 +54,9 @@ with iotests.FilePath('t.qcow2') as disk_path, \
'filename': disk_path,
'size': 0 })
- vm.qmp_log('blockdev-add', driver='file', filename=disk_path,
+ vm.qmp_log('blockdev-add',
+ filters=[iotests.filter_qmp_testfiles],
+ driver='file', filename=disk_path,
node_name='imgfile')
blockdev_create(vm, { 'driver': imgfmt,
diff --git a/tests/qemu-iotests/223 b/tests/qemu-iotests/223
index 397b865d34..773892dbe6 100755
--- a/tests/qemu-iotests/223
+++ b/tests/qemu-iotests/223
@@ -25,6 +25,7 @@ status=1 # failure is the default!
_cleanup()
{
+ nbd_server_stop
_cleanup_test_img
_cleanup_qemu
rm -f "$TEST_DIR/nbd"
@@ -35,6 +36,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15
. ./common.rc
. ./common.filter
. ./common.qemu
+. ./common.nbd
_supported_fmt qcow2
_supported_proto file # uses NBD as well
@@ -61,6 +63,8 @@ echo "=== Create partially sparse image, then add dirty bitmaps ==="
echo
# Two bitmaps, to contrast granularity issues
+# Also note that b will be disabled, while b2 is left enabled, to
+# check for read-only interactions
_make_test_img -o cluster_size=4k 4M
$QEMU_IO -c 'w -P 0x11 1M 2M' "$TEST_IMG" | _filter_qemu_io
run_qemu <<EOF
@@ -107,26 +111,37 @@ echo
_launch_qemu 2> >(_filter_nbd)
+# Intentionally provoke some errors as well, to check error handling
silent=
_send_qemu_cmd $QEMU_HANDLE '{"execute":"qmp_capabilities"}' "return"
_send_qemu_cmd $QEMU_HANDLE '{"execute":"blockdev-add",
"arguments":{"driver":"qcow2", "node-name":"n",
"file":{"driver":"file", "filename":"'"$TEST_IMG"'"}}}' "return"
-_send_qemu_cmd $QEMU_HANDLE '{"execute":"x-block-dirty-bitmap-disable",
+_send_qemu_cmd $QEMU_HANDLE '{"execute":"block-dirty-bitmap-disable",
"arguments":{"node":"n", "name":"b"}}' "return"
-_send_qemu_cmd $QEMU_HANDLE '{"execute":"x-block-dirty-bitmap-disable",
- "arguments":{"node":"n", "name":"b2"}}' "return"
+_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
+ "arguments":{"device":"n"}}' "error" # Attempt add without server
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-start",
"arguments":{"addr":{"type":"unix",
"data":{"path":"'"$TEST_DIR/nbd"'"}}}}' "return"
+_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-start",
+ "arguments":{"addr":{"type":"unix",
+ "data":{"path":"'"$TEST_DIR/nbd"1'"}}}}' "error" # Attempt second server
+_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
+ "arguments":{"device":"n", "bitmap":"b"}}' "return"
+_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
+ "arguments":{"device":"nosuch"}}' "error" # Attempt to export missing node
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
- "arguments":{"device":"n"}}' "return"
-_send_qemu_cmd $QEMU_HANDLE '{"execute":"x-nbd-server-add-bitmap",
- "arguments":{"name":"n", "bitmap":"b"}}' "return"
+ "arguments":{"device":"n"}}' "error" # Attempt to export same name twice
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
- "arguments":{"device":"n", "name":"n2"}}' "return"
-_send_qemu_cmd $QEMU_HANDLE '{"execute":"x-nbd-server-add-bitmap",
- "arguments":{"name":"n2", "bitmap":"b2"}}' "return"
+ "arguments":{"device":"n", "name":"n2",
+ "bitmap":"b2"}}' "error" # enabled vs. read-only
+_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
+ "arguments":{"device":"n", "name":"n2",
+ "bitmap":"b3"}}' "error" # Missing bitmap
+_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
+ "arguments":{"device":"n", "name":"n2", "writable":true,
+ "bitmap":"b2"}}' "return"
echo
echo "=== Contrast normal status to large granularity dirty-bitmap ==="
@@ -150,16 +165,33 @@ $QEMU_IMG map --output=json --image-opts \
"$IMG,x-dirty-bitmap=qemu:dirty-bitmap:b2" | _filter_qemu_img_map
echo
-echo "=== End NBD server ==="
+echo "=== End qemu NBD server ==="
echo
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-remove",
"arguments":{"name":"n"}}' "return"
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-remove",
"arguments":{"name":"n2"}}' "return"
+_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-remove",
+ "arguments":{"name":"n2"}}' "error" # Attempt duplicate clean
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-stop"}' "return"
+_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-stop"}' "error" # Again
_send_qemu_cmd $QEMU_HANDLE '{"execute":"quit"}' "return"
+echo
+echo "=== Use qemu-nbd as server ==="
+echo
+
+nbd_server_start_unix_socket -r -f $IMGFMT -B b "$TEST_IMG"
+IMG="driver=nbd,server.type=unix,server.path=$nbd_unix_socket"
+$QEMU_IMG map --output=json --image-opts \
+ "$IMG,x-dirty-bitmap=qemu:dirty-bitmap:b" | _filter_qemu_img_map
+
+nbd_server_start_unix_socket -f $IMGFMT -B b2 "$TEST_IMG"
+IMG="driver=nbd,server.type=unix,server.path=$nbd_unix_socket"
+$QEMU_IMG map --output=json --image-opts \
+ "$IMG,x-dirty-bitmap=qemu:dirty-bitmap:b2" | _filter_qemu_img_map
+
# success, all done
echo '*** done'
rm -f $seq.full
diff --git a/tests/qemu-iotests/223.out b/tests/qemu-iotests/223.out
index 99ca172fbb..0de5240a75 100644
--- a/tests/qemu-iotests/223.out
+++ b/tests/qemu-iotests/223.out
@@ -27,11 +27,14 @@ wrote 2097152/2097152 bytes at offset 2097152
{"return": {}}
{"return": {}}
{"return": {}}
+{"error": {"class": "GenericError", "desc": "NBD server not running"}}
{"return": {}}
+{"error": {"class": "GenericError", "desc": "NBD server already running"}}
{"return": {}}
-{"return": {}}
-{"return": {}}
-{"return": {}}
+{"error": {"class": "GenericError", "desc": "Cannot find device=nosuch nor node_name=nosuch"}}
+{"error": {"class": "GenericError", "desc": "NBD server already has export named 'n'"}}
+{"error": {"class": "GenericError", "desc": "Enabled bitmap 'b2' incompatible with readonly export"}}
+{"error": {"class": "GenericError", "desc": "Bitmap 'b3' is not found"}}
{"return": {}}
=== Contrast normal status to large granularity dirty-bitmap ===
@@ -58,10 +61,22 @@ read 2097152/2097152 bytes at offset 2097152
{ "start": 1024, "length": 2096128, "depth": 0, "zero": false, "data": true},
{ "start": 2097152, "length": 2097152, "depth": 0, "zero": false, "data": false}]
-=== End NBD server ===
+=== End qemu NBD server ===
{"return": {}}
{"return": {}}
+{"error": {"class": "GenericError", "desc": "Export 'n2' is not found"}}
{"return": {}}
+{"error": {"class": "GenericError", "desc": "NBD server not running"}}
{"return": {}}
+
+=== Use qemu-nbd as server ===
+
+[{ "start": 0, "length": 65536, "depth": 0, "zero": false, "data": false},
+{ "start": 65536, "length": 2031616, "depth": 0, "zero": false, "data": true},
+{ "start": 2097152, "length": 2097152, "depth": 0, "zero": false, "data": false}]
+[{ "start": 0, "length": 512, "depth": 0, "zero": false, "data": true},
+{ "start": 512, "length": 512, "depth": 0, "zero": false, "data": false},
+{ "start": 1024, "length": 2096128, "depth": 0, "zero": false, "data": true},
+{ "start": 2097152, "length": 2097152, "depth": 0, "zero": false, "data": false}]
*** done
diff --git a/tests/qemu-iotests/236 b/tests/qemu-iotests/236
new file mode 100755
index 0000000000..79a6381f8e
--- /dev/null
+++ b/tests/qemu-iotests/236
@@ -0,0 +1,161 @@
+#!/usr/bin/env python
+#
+# Test bitmap merges.
+#
+# Copyright (c) 2018 John Snow for Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# owner=jsnow@redhat.com
+
+import iotests
+from iotests import log
+
+iotests.verify_image_format(supported_fmts=['generic'])
+size = 64 * 1024 * 1024
+granularity = 64 * 1024
+
+patterns = [("0x5d", "0", "64k"),
+ ("0xd5", "1M", "64k"),
+ ("0xdc", "32M", "64k"),
+ ("0xcd", "0x3ff0000", "64k")] # 64M - 64K
+
+overwrite = [("0xab", "0", "64k"), # Full overwrite
+ ("0xad", "0x00f8000", "64k"), # Partial-left (1M-32K)
+ ("0x1d", "0x2008000", "64k"), # Partial-right (32M+32K)
+ ("0xea", "0x3fe0000", "64k")] # Adjacent-left (64M - 128K)
+
+def query_bitmaps(vm):
+ res = vm.qmp("query-block")
+ return { "bitmaps": { device['device']: device.get('dirty-bitmaps', []) for
+ device in res['return'] } }
+
+with iotests.FilePath('img') as img_path, \
+ iotests.VM() as vm:
+
+ log('--- Preparing image & VM ---\n')
+ iotests.qemu_img_create('-f', iotests.imgfmt, img_path, str(size))
+ vm.add_drive(img_path)
+ vm.launch()
+
+ log('\n--- Adding preliminary bitmaps A & B ---\n')
+ vm.qmp_log("block-dirty-bitmap-add", node="drive0",
+ name="bitmapA", granularity=granularity)
+ vm.qmp_log("block-dirty-bitmap-add", node="drive0",
+ name="bitmapB", granularity=granularity)
+
+ # Dirties 4 clusters. count=262144
+ log('\n--- Emulating writes ---\n')
+ for p in patterns:
+ cmd = "write -P%s %s %s" % p
+ log(cmd)
+ log(vm.hmp_qemu_io("drive0", cmd))
+
+ log(query_bitmaps(vm), indent=2)
+
+ log('\n--- Submitting & Aborting Transaction ---\n')
+ vm.qmp_log("transaction", indent=2, actions=[
+ { "type": "block-dirty-bitmap-disable",
+ "data": { "node": "drive0", "name": "bitmapB" }},
+ { "type": "block-dirty-bitmap-add",
+ "data": { "node": "drive0", "name": "bitmapC",
+ "granularity": granularity }},
+ { "type": "block-dirty-bitmap-clear",
+ "data": { "node": "drive0", "name": "bitmapA" }},
+ { "type": "abort", "data": {}}
+ ])
+ log(query_bitmaps(vm), indent=2)
+
+ log('\n--- Disabling B & Adding C ---\n')
+ vm.qmp_log("transaction", indent=2, actions=[
+ { "type": "block-dirty-bitmap-disable",
+ "data": { "node": "drive0", "name": "bitmapB" }},
+ { "type": "block-dirty-bitmap-add",
+ "data": { "node": "drive0", "name": "bitmapC",
+ "granularity": granularity }},
+ # Purely extraneous, but test that it works:
+ { "type": "block-dirty-bitmap-disable",
+ "data": { "node": "drive0", "name": "bitmapC" }},
+ { "type": "block-dirty-bitmap-enable",
+ "data": { "node": "drive0", "name": "bitmapC" }},
+ ])
+
+ log('\n--- Emulating further writes ---\n')
+ # Dirties 6 clusters, 3 of which are new in contrast to "A".
+ # A = 64 * 1024 * (4 + 3) = 458752
+ # C = 64 * 1024 * 6 = 393216
+ for p in overwrite:
+ cmd = "write -P%s %s %s" % p
+ log(cmd)
+ log(vm.hmp_qemu_io("drive0", cmd))
+
+ log('\n--- Disabling A & C ---\n')
+ vm.qmp_log("transaction", indent=2, actions=[
+ { "type": "block-dirty-bitmap-disable",
+ "data": { "node": "drive0", "name": "bitmapA" }},
+ { "type": "block-dirty-bitmap-disable",
+ "data": { "node": "drive0", "name": "bitmapC" }}
+ ])
+
+ # A: 7 clusters
+ # B: 4 clusters
+ # C: 6 clusters
+ log(query_bitmaps(vm), indent=2)
+
+ log('\n--- Submitting & Aborting Merge Transaction ---\n')
+ vm.qmp_log("transaction", indent=2, actions=[
+ { "type": "block-dirty-bitmap-add",
+ "data": { "node": "drive0", "name": "bitmapD",
+ "disabled": True, "granularity": granularity }},
+ { "type": "block-dirty-bitmap-merge",
+ "data": { "node": "drive0", "target": "bitmapD",
+ "bitmaps": ["bitmapB", "bitmapC"] }},
+ { "type": "abort", "data": {}}
+ ])
+ log(query_bitmaps(vm), indent=2)
+
+ log('\n--- Creating D as a merge of B & C ---\n')
+ # Good hygiene: create a disabled bitmap as a merge target.
+ vm.qmp_log("transaction", indent=2, actions=[
+ { "type": "block-dirty-bitmap-add",
+ "data": { "node": "drive0", "name": "bitmapD",
+ "disabled": True, "granularity": granularity }},
+ { "type": "block-dirty-bitmap-merge",
+ "data": { "node": "drive0", "target": "bitmapD",
+ "bitmaps": ["bitmapB", "bitmapC"] }}
+ ])
+
+ # A and D should now both have 7 clusters apiece.
+ # B and C remain unchanged with 4 and 6 respectively.
+ log(query_bitmaps(vm), indent=2)
+
+ # A and D should be equivalent.
+ # Some formats round the size of the disk, so don't print the checksums.
+ check_a = vm.qmp('x-debug-block-dirty-bitmap-sha256',
+ node="drive0", name="bitmapA")['return']['sha256']
+ check_d = vm.qmp('x-debug-block-dirty-bitmap-sha256',
+ node="drive0", name="bitmapD")['return']['sha256']
+ assert(check_a == check_d)
+
+ log('\n--- Removing bitmaps A, B, C, and D ---\n')
+ vm.qmp_log("block-dirty-bitmap-remove", node="drive0", name="bitmapA")
+ vm.qmp_log("block-dirty-bitmap-remove", node="drive0", name="bitmapB")
+ vm.qmp_log("block-dirty-bitmap-remove", node="drive0", name="bitmapC")
+ vm.qmp_log("block-dirty-bitmap-remove", node="drive0", name="bitmapD")
+
+ log('\n--- Final Query ---\n')
+ log(query_bitmaps(vm), indent=2)
+
+ log('\n--- Done ---\n')
+ vm.shutdown()
diff --git a/tests/qemu-iotests/236.out b/tests/qemu-iotests/236.out
new file mode 100644
index 0000000000..1dad24db0d
--- /dev/null
+++ b/tests/qemu-iotests/236.out
@@ -0,0 +1,351 @@
+--- Preparing image & VM ---
+
+
+--- Adding preliminary bitmaps A & B ---
+
+{"execute": "block-dirty-bitmap-add", "arguments": {"granularity": 65536, "name": "bitmapA", "node": "drive0"}}
+{"return": {}}
+{"execute": "block-dirty-bitmap-add", "arguments": {"granularity": 65536, "name": "bitmapB", "node": "drive0"}}
+{"return": {}}
+
+--- Emulating writes ---
+
+write -P0x5d 0 64k
+{"return": ""}
+write -P0xd5 1M 64k
+{"return": ""}
+write -P0xdc 32M 64k
+{"return": ""}
+write -P0xcd 0x3ff0000 64k
+{"return": ""}
+{
+ "bitmaps": {
+ "drive0": [
+ {
+ "count": 262144,
+ "granularity": 65536,
+ "name": "bitmapB",
+ "status": "active"
+ },
+ {
+ "count": 262144,
+ "granularity": 65536,
+ "name": "bitmapA",
+ "status": "active"
+ }
+ ]
+ }
+}
+
+--- Submitting & Aborting Transaction ---
+
+{
+ "execute": "transaction",
+ "arguments": {
+ "actions": [
+ {
+ "data": {
+ "node": "drive0",
+ "name": "bitmapB"
+ },
+ "type": "block-dirty-bitmap-disable"
+ },
+ {
+ "data": {
+ "node": "drive0",
+ "name": "bitmapC",
+ "granularity": 65536
+ },
+ "type": "block-dirty-bitmap-add"
+ },
+ {
+ "data": {
+ "node": "drive0",
+ "name": "bitmapA"
+ },
+ "type": "block-dirty-bitmap-clear"
+ },
+ {
+ "data": {},
+ "type": "abort"
+ }
+ ]
+ }
+}
+{
+ "error": {
+ "class": "GenericError",
+ "desc": "Transaction aborted using Abort action"
+ }
+}
+{
+ "bitmaps": {
+ "drive0": [
+ {
+ "count": 262144,
+ "granularity": 65536,
+ "name": "bitmapB",
+ "status": "active"
+ },
+ {
+ "count": 262144,
+ "granularity": 65536,
+ "name": "bitmapA",
+ "status": "active"
+ }
+ ]
+ }
+}
+
+--- Disabling B & Adding C ---
+
+{
+ "execute": "transaction",
+ "arguments": {
+ "actions": [
+ {
+ "data": {
+ "node": "drive0",
+ "name": "bitmapB"
+ },
+ "type": "block-dirty-bitmap-disable"
+ },
+ {
+ "data": {
+ "node": "drive0",
+ "name": "bitmapC",
+ "granularity": 65536
+ },
+ "type": "block-dirty-bitmap-add"
+ },
+ {
+ "data": {
+ "node": "drive0",
+ "name": "bitmapC"
+ },
+ "type": "block-dirty-bitmap-disable"
+ },
+ {
+ "data": {
+ "node": "drive0",
+ "name": "bitmapC"
+ },
+ "type": "block-dirty-bitmap-enable"
+ }
+ ]
+ }
+}
+{
+ "return": {}
+}
+
+--- Emulating further writes ---
+
+write -P0xab 0 64k
+{"return": ""}
+write -P0xad 0x00f8000 64k
+{"return": ""}
+write -P0x1d 0x2008000 64k
+{"return": ""}
+write -P0xea 0x3fe0000 64k
+{"return": ""}
+
+--- Disabling A & C ---
+
+{
+ "execute": "transaction",
+ "arguments": {
+ "actions": [
+ {
+ "data": {
+ "node": "drive0",
+ "name": "bitmapA"
+ },
+ "type": "block-dirty-bitmap-disable"
+ },
+ {
+ "data": {
+ "node": "drive0",
+ "name": "bitmapC"
+ },
+ "type": "block-dirty-bitmap-disable"
+ }
+ ]
+ }
+}
+{
+ "return": {}
+}
+{
+ "bitmaps": {
+ "drive0": [
+ {
+ "count": 393216,
+ "granularity": 65536,
+ "name": "bitmapC",
+ "status": "disabled"
+ },
+ {
+ "count": 262144,
+ "granularity": 65536,
+ "name": "bitmapB",
+ "status": "disabled"
+ },
+ {
+ "count": 458752,
+ "granularity": 65536,
+ "name": "bitmapA",
+ "status": "disabled"
+ }
+ ]
+ }
+}
+
+--- Submitting & Aborting Merge Transaction ---
+
+{
+ "execute": "transaction",
+ "arguments": {
+ "actions": [
+ {
+ "data": {
+ "node": "drive0",
+ "disabled": true,
+ "name": "bitmapD",
+ "granularity": 65536
+ },
+ "type": "block-dirty-bitmap-add"
+ },
+ {
+ "data": {
+ "node": "drive0",
+ "target": "bitmapD",
+ "bitmaps": [
+ "bitmapB",
+ "bitmapC"
+ ]
+ },
+ "type": "block-dirty-bitmap-merge"
+ },
+ {
+ "data": {},
+ "type": "abort"
+ }
+ ]
+ }
+}
+{
+ "error": {
+ "class": "GenericError",
+ "desc": "Transaction aborted using Abort action"
+ }
+}
+{
+ "bitmaps": {
+ "drive0": [
+ {
+ "count": 393216,
+ "granularity": 65536,
+ "name": "bitmapC",
+ "status": "disabled"
+ },
+ {
+ "count": 262144,
+ "granularity": 65536,
+ "name": "bitmapB",
+ "status": "disabled"
+ },
+ {
+ "count": 458752,
+ "granularity": 65536,
+ "name": "bitmapA",
+ "status": "disabled"
+ }
+ ]
+ }
+}
+
+--- Creating D as a merge of B & C ---
+
+{
+ "execute": "transaction",
+ "arguments": {
+ "actions": [
+ {
+ "data": {
+ "node": "drive0",
+ "disabled": true,
+ "name": "bitmapD",
+ "granularity": 65536
+ },
+ "type": "block-dirty-bitmap-add"
+ },
+ {
+ "data": {
+ "node": "drive0",
+ "target": "bitmapD",
+ "bitmaps": [
+ "bitmapB",
+ "bitmapC"
+ ]
+ },
+ "type": "block-dirty-bitmap-merge"
+ }
+ ]
+ }
+}
+{
+ "return": {}
+}
+{
+ "bitmaps": {
+ "drive0": [
+ {
+ "count": 458752,
+ "granularity": 65536,
+ "name": "bitmapD",
+ "status": "disabled"
+ },
+ {
+ "count": 393216,
+ "granularity": 65536,
+ "name": "bitmapC",
+ "status": "disabled"
+ },
+ {
+ "count": 262144,
+ "granularity": 65536,
+ "name": "bitmapB",
+ "status": "disabled"
+ },
+ {
+ "count": 458752,
+ "granularity": 65536,
+ "name": "bitmapA",
+ "status": "disabled"
+ }
+ ]
+ }
+}
+
+--- Removing bitmaps A, B, C, and D ---
+
+{"execute": "block-dirty-bitmap-remove", "arguments": {"name": "bitmapA", "node": "drive0"}}
+{"return": {}}
+{"execute": "block-dirty-bitmap-remove", "arguments": {"name": "bitmapB", "node": "drive0"}}
+{"return": {}}
+{"execute": "block-dirty-bitmap-remove", "arguments": {"name": "bitmapC", "node": "drive0"}}
+{"return": {}}
+{"execute": "block-dirty-bitmap-remove", "arguments": {"name": "bitmapD", "node": "drive0"}}
+{"return": {}}
+
+--- Final Query ---
+
+{
+ "bitmaps": {
+ "drive0": []
+ }
+}
+
+--- Done ---
+
diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group
index 61a6d98ebd..f6b245917a 100644
--- a/tests/qemu-iotests/group
+++ b/tests/qemu-iotests/group
@@ -233,3 +233,4 @@
233 auto quick
234 auto quick migration
235 auto quick
+236 auto quick
diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
index d537538ba0..cbedfaf1df 100644
--- a/tests/qemu-iotests/iotests.py
+++ b/tests/qemu-iotests/iotests.py
@@ -30,6 +30,7 @@ import signal
import logging
import atexit
import io
+from collections import OrderedDict
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'scripts'))
import qtest
@@ -63,7 +64,7 @@ socket_scm_helper = os.environ.get('SOCKET_SCM_HELPER', 'socket_scm_helper')
debug = False
luks_default_secret_object = 'secret,id=keysec0,data=' + \
- os.environ['IMGKEYSECRET']
+ os.environ.get('IMGKEYSECRET', '')
luks_default_key_secret_opt = 'key-secret=keysec0'
@@ -75,6 +76,16 @@ def qemu_img(*args):
sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args))))
return exitcode
+def ordered_kwargs(kwargs):
+ # kwargs prior to 3.6 are not ordered, so:
+ od = OrderedDict()
+ for k, v in sorted(kwargs.items()):
+ if isinstance(v, dict):
+ od[k] = ordered_kwargs(v)
+ else:
+ od[k] = v
+ return od
+
def qemu_img_create(*args):
args = list(args)
@@ -235,10 +246,36 @@ def filter_qmp_event(event):
event['timestamp']['microseconds'] = 'USECS'
return event
+def filter_qmp(qmsg, filter_fn):
+ '''Given a string filter, filter a QMP object's values.
+ filter_fn takes a (key, value) pair.'''
+ # Iterate through either lists or dicts;
+ if isinstance(qmsg, list):
+ items = enumerate(qmsg)
+ else:
+ items = qmsg.items()
+
+ for k, v in items:
+ if isinstance(v, list) or isinstance(v, dict):
+ qmsg[k] = filter_qmp(v, filter_fn)
+ else:
+ qmsg[k] = filter_fn(k, v)
+ return qmsg
+
def filter_testfiles(msg):
prefix = os.path.join(test_dir, "%s-" % (os.getpid()))
return msg.replace(prefix, 'TEST_DIR/PID-')
+def filter_qmp_testfiles(qmsg):
+ def _filter(key, value):
+ if key == 'filename' or key == 'backing-file':
+ return filter_testfiles(value)
+ return value
+ return filter_qmp(qmsg, _filter)
+
+def filter_generated_node_ids(msg):
+ return re.sub("#block[0-9]+", "NODE_NAME", msg)
+
def filter_img_info(output, filename):
lines = []
for line in output.split('\n'):
@@ -251,11 +288,18 @@ def filter_img_info(output, filename):
lines.append(line)
return '\n'.join(lines)
-def log(msg, filters=[]):
+def log(msg, filters=[], indent=None):
+ '''Logs either a string message or a JSON serializable message (like QMP).
+ If indent is provided, JSON serializable messages are pretty-printed.'''
for flt in filters:
msg = flt(msg)
- if type(msg) is dict or type(msg) is list:
- print(json.dumps(msg, sort_keys=True))
+ if isinstance(msg, dict) or isinstance(msg, list):
+ # Python < 3.4 needs to know not to add whitespace when pretty-printing:
+ separators = (', ', ': ') if indent is None else (',', ': ')
+ # Don't sort if it's already sorted
+ do_sort = not isinstance(msg, OrderedDict)
+ print(json.dumps(msg, sort_keys=do_sort,
+ indent=indent, separators=separators))
else:
print(msg)
@@ -444,12 +488,14 @@ class VM(qtest.QEMUQtestMachine):
result.append(filter_qmp_event(ev))
return result
- def qmp_log(self, cmd, filters=[filter_testfiles], **kwargs):
- logmsg = '{"execute": "%s", "arguments": %s}' % \
- (cmd, json.dumps(kwargs, sort_keys=True))
- log(logmsg, filters)
+ def qmp_log(self, cmd, filters=[], indent=None, **kwargs):
+ full_cmd = OrderedDict((
+ ("execute", cmd),
+ ("arguments", ordered_kwargs(kwargs))
+ ))
+ log(full_cmd, filters, indent=indent)
result = self.qmp(cmd, **kwargs)
- log(json.dumps(result, sort_keys=True), filters)
+ log(result, filters, indent=indent)
return result
def run_job(self, job, auto_finalize=True, auto_dismiss=False):