aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2021-09-02 13:00:52 +0100
committerPeter Maydell <peter.maydell@linaro.org>2021-09-02 13:00:52 +0100
commit9093028dd48c50bc0392791f78aab44afef57ead (patch)
treeac9ab96c78172fff29ac3da160f39e0bc012637e /tests
parent59a89510b62ec23dbeab8b02fa4e3526e353d8b6 (diff)
parentebd979c74e2b8a7275090475df36dde4ab858320 (diff)
Merge remote-tracking branch 'remotes/hreitz/tags/pull-block-2021-09-01' into staging
Block patches: - Make the backup-top filter driver available for user-created block nodes (i.e. via blockdev-add) - Allow running iotests with gdb or valgrind being attached to qemu instances - Fix the raw format driver's permissions: There is no metadata, so we only need WRITE or RESIZE when the parent needs it - Basic reopen implementation for win32 files (file-win32.c) so that qemu-img commit can work - uclibc/musl build fix for the FUSE export code - Some iotests delinting - block-hmp-cmds.c refactoring # gpg: Signature made Wed 01 Sep 2021 16:01:54 BST # gpg: using RSA key CB62D7A0EE3829E45F004D34A1FA40D098019CDF # gpg: issuer "hreitz@redhat.com" # gpg: Good signature from "Hanna Reitz <hreitz@redhat.com>" [marginal] # gpg: WARNING: This key is not certified with sufficiently trusted signatures! # gpg: It is not certain that the signature belongs to the owner. # Primary key fingerprint: CB62 D7A0 EE38 29E4 5F00 4D34 A1FA 40D0 9801 9CDF * remotes/hreitz/tags/pull-block-2021-09-01: (56 commits) block/file-win32: add reopen handlers block/export/fuse.c: fix fuse-lseek on uclibc or musl block/block-copy: block_copy_state_new(): drop extra arguments iotests/image-fleecing: add test-case for copy-before-write filter iotests/image-fleecing: prepare for adding new test-case iotests/image-fleecing: rename tgt_node iotests/image-fleecing: proper source device iotests.py: hmp_qemu_io: support qdev iotests: move 222 to tests/image-fleecing iotests/222: constantly use single quotes for strings iotests/222: fix pylint and mypy complains python:QEMUMachine: template typing for self returning methods python/qemu/machine: QEMUMachine: improve qmp() method python/qemu/machine.py: refactor _qemu_args() qapi: publish copy-before-write filter block/copy-before-write: make public block driver block/block-copy: make setting progress optional block/copy-before-write: initialize block-copy bitmap block/copy-before-write: cbw_init(): use options block/copy-before-write: bdrv_cbw_append(): drop unused compress arg ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'tests')
-rwxr-xr-xtests/qemu-iotests/222159
-rw-r--r--tests/qemu-iotests/222.out67
-rwxr-xr-xtests/qemu-iotests/28335
-rw-r--r--tests/qemu-iotests/283.out4
-rwxr-xr-xtests/qemu-iotests/2972
-rwxr-xr-xtests/qemu-iotests/check15
-rw-r--r--tests/qemu-iotests/common.qemu7
-rw-r--r--tests/qemu-iotests/common.rc8
-rw-r--r--tests/qemu-iotests/iotests.py75
-rw-r--r--tests/qemu-iotests/testenv.py23
-rwxr-xr-xtests/qemu-iotests/tests/image-fleecing192
-rw-r--r--tests/qemu-iotests/tests/image-fleecing.out139
12 files changed, 455 insertions, 271 deletions
diff --git a/tests/qemu-iotests/222 b/tests/qemu-iotests/222
deleted file mode 100755
index b48afe623e..0000000000
--- a/tests/qemu-iotests/222
+++ /dev/null
@@ -1,159 +0,0 @@
-#!/usr/bin/env python3
-# group: rw quick
-#
-# This test covers the basic fleecing workflow, which provides a
-# point-in-time snapshot of a node that can be queried over NBD.
-#
-# Copyright (C) 2018 Red Hat, Inc.
-# John helped, too.
-#
-# 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/>.
-#
-# Creator/Owner: John Snow <jsnow@redhat.com>
-
-import iotests
-from iotests import log, qemu_img, qemu_io, qemu_io_silent
-
-iotests.script_initialize(
- supported_fmts=['qcow2', 'qcow', 'qed', 'vmdk', 'vhdx', 'raw'],
- supported_platforms=['linux'],
-)
-
-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)
-
-zeroes = [("0", "0x00f8000", "32k"), # Left-end of partial-left (1M-32K)
- ("0", "0x2010000", "32k"), # Right-end of partial-right (32M+64K)
- ("0", "0x3fe0000", "64k")] # overwrite[3]
-
-remainder = [("0xd5", "0x108000", "32k"), # Right-end of partial-left [1]
- ("0xdc", "32M", "32k"), # Left-end of partial-right [2]
- ("0xcd", "0x3ff0000", "64k")] # patterns[3]
-
-with iotests.FilePath('base.img') as base_img_path, \
- iotests.FilePath('fleece.img') as fleece_img_path, \
- iotests.FilePath('nbd.sock', base_dir=iotests.sock_dir) as nbd_sock_path, \
- iotests.VM() as vm:
-
- log('--- Setting up images ---')
- log('')
-
- assert qemu_img('create', '-f', iotests.imgfmt, base_img_path, '64M') == 0
- assert qemu_img('create', '-f', "qcow2", fleece_img_path, '64M') == 0
-
- for p in patterns:
- qemu_io('-f', iotests.imgfmt,
- '-c', 'write -P%s %s %s' % p, base_img_path)
-
- log('Done')
-
- log('')
- log('--- Launching VM ---')
- log('')
-
- vm.add_drive(base_img_path)
- vm.launch()
- log('Done')
-
- log('')
- log('--- Setting up Fleecing Graph ---')
- log('')
-
- src_node = "drive0"
- tgt_node = "fleeceNode"
-
- # create tgt_node backed by src_node
- log(vm.qmp("blockdev-add", **{
- "driver": "qcow2",
- "node-name": tgt_node,
- "file": {
- "driver": "file",
- "filename": fleece_img_path,
- },
- "backing": src_node,
- }))
-
- # Establish COW from source to fleecing node
- log(vm.qmp("blockdev-backup",
- device=src_node,
- target=tgt_node,
- sync="none"))
-
- log('')
- log('--- Setting up NBD Export ---')
- log('')
-
- nbd_uri = 'nbd+unix:///%s?socket=%s' % (tgt_node, nbd_sock_path)
- log(vm.qmp("nbd-server-start",
- **{"addr": { "type": "unix",
- "data": { "path": nbd_sock_path } } }))
-
- log(vm.qmp("nbd-server-add", device=tgt_node))
-
- log('')
- log('--- Sanity Check ---')
- log('')
-
- for p in (patterns + zeroes):
- cmd = "read -P%s %s %s" % p
- log(cmd)
- assert qemu_io_silent('-r', '-f', 'raw', '-c', cmd, nbd_uri) == 0
-
- log('')
- log('--- Testing COW ---')
- log('')
-
- for p in overwrite:
- cmd = "write -P%s %s %s" % p
- log(cmd)
- log(vm.hmp_qemu_io(src_node, cmd))
-
- log('')
- log('--- Verifying Data ---')
- log('')
-
- for p in (patterns + zeroes):
- cmd = "read -P%s %s %s" % p
- log(cmd)
- assert qemu_io_silent('-r', '-f', 'raw', '-c', cmd, nbd_uri) == 0
-
- log('')
- log('--- Cleanup ---')
- log('')
-
- log(vm.qmp('block-job-cancel', device=src_node))
- log(vm.event_wait('BLOCK_JOB_CANCELLED'),
- filters=[iotests.filter_qmp_event])
- log(vm.qmp('nbd-server-stop'))
- log(vm.qmp('blockdev-del', node_name=tgt_node))
- vm.shutdown()
-
- log('')
- log('--- Confirming writes ---')
- log('')
-
- for p in (overwrite + remainder):
- cmd = "read -P%s %s %s" % p
- log(cmd)
- assert qemu_io_silent(base_img_path, '-c', cmd) == 0
-
- log('')
- log('Done')
diff --git a/tests/qemu-iotests/222.out b/tests/qemu-iotests/222.out
deleted file mode 100644
index 16643dde30..0000000000
--- a/tests/qemu-iotests/222.out
+++ /dev/null
@@ -1,67 +0,0 @@
---- Setting up images ---
-
-Done
-
---- Launching VM ---
-
-Done
-
---- Setting up Fleecing Graph ---
-
-{"return": {}}
-{"return": {}}
-
---- Setting up NBD Export ---
-
-{"return": {}}
-{"return": {}}
-
---- Sanity Check ---
-
-read -P0x5d 0 64k
-read -P0xd5 1M 64k
-read -P0xdc 32M 64k
-read -P0xcd 0x3ff0000 64k
-read -P0 0x00f8000 32k
-read -P0 0x2010000 32k
-read -P0 0x3fe0000 64k
-
---- Testing COW ---
-
-write -P0xab 0 64k
-{"return": ""}
-write -P0xad 0x00f8000 64k
-{"return": ""}
-write -P0x1d 0x2008000 64k
-{"return": ""}
-write -P0xea 0x3fe0000 64k
-{"return": ""}
-
---- Verifying Data ---
-
-read -P0x5d 0 64k
-read -P0xd5 1M 64k
-read -P0xdc 32M 64k
-read -P0xcd 0x3ff0000 64k
-read -P0 0x00f8000 32k
-read -P0 0x2010000 32k
-read -P0 0x3fe0000 64k
-
---- Cleanup ---
-
-{"return": {}}
-{"data": {"device": "drive0", "len": 67108864, "offset": 393216, "speed": 0, "type": "backup"}, "event": "BLOCK_JOB_CANCELLED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
-{"return": {}}
-{"return": {}}
-
---- Confirming writes ---
-
-read -P0xab 0 64k
-read -P0xad 0x00f8000 64k
-read -P0x1d 0x2008000 64k
-read -P0xea 0x3fe0000 64k
-read -P0xd5 0x108000 32k
-read -P0xdc 32M 32k
-read -P0xcd 0x3ff0000 64k
-
-Done
diff --git a/tests/qemu-iotests/283 b/tests/qemu-iotests/283
index 010c22f0a2..a09e0183ae 100755
--- a/tests/qemu-iotests/283
+++ b/tests/qemu-iotests/283
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# group: auto quick
#
-# Test for backup-top filter permission activation failure
+# Test for copy-before-write filter permission conflict
#
# Copyright (c) 2019 Virtuozzo International GmbH.
#
@@ -31,13 +31,13 @@ size = 1024 * 1024
""" Test description
When performing a backup, all writes on the source subtree must go through the
-backup-top filter so it can copy all data to the target before it is changed.
-backup-top filter is appended above source node, to achieve this thing, so all
-parents of source node are handled. A configuration with side parents of source
-sub-tree with write permission is unsupported (we'd have append several
-backup-top filter like nodes to handle such parents). The test create an
-example of such configuration and checks that a backup is then not allowed
-(blockdev-backup command should fail).
+copy-before-write filter so it can copy all data to the target before it is
+changed. copy-before-write filter is appended above source node, to achieve
+this thing, so all parents of source node are handled. A configuration with
+side parents of source sub-tree with write permission is unsupported (we'd have
+append several copy-before-write filter like nodes to handle such parents). The
+test create an example of such configuration and checks that a backup is then
+not allowed (blockdev-backup command should fail).
The configuration:
@@ -57,11 +57,10 @@ The configuration:
│ base │ ◀──────────── │ other │
└─────────────┘ └───────┘
-On activation (see .active field of backup-top state in block/backup-top.c),
-backup-top is going to unshare write permission on its source child. Write
-unsharing will be propagated to the "source->base" link and will conflict with
-other node write permission. So permission update will fail and backup job will
-not be started.
+copy-before-write filter wants to unshare write permission on its source child.
+Write unsharing will be propagated to the "source->base" link and will conflict
+with other node write permission. So permission update will fail and backup job
+will not be started.
Note, that the only thing which prevents backup of running on such
configuration is default permission propagation scheme. It may be altered by
@@ -99,13 +98,9 @@ vm.qmp_log('blockdev-backup', sync='full', device='source', target='target')
vm.shutdown()
-print('\n=== backup-top should be gone after job-finalize ===\n')
+print('\n=== copy-before-write filter should be gone after job-finalize ===\n')
-# Check that the backup-top node is gone after job-finalize.
-#
-# During finalization, the node becomes inactive and can no longer
-# function. If it is still present, new parents might be attached, and
-# there would be no meaningful way to handle their I/O requests.
+# Check that the copy-before-write node is gone after job-finalize.
vm = iotests.VM()
vm.launch()
@@ -131,7 +126,7 @@ vm.qmp_log('blockdev-backup',
vm.event_wait('BLOCK_JOB_PENDING', 5.0)
-# The backup-top filter should still be present prior to finalization
+# The copy-before-write filter should still be present prior to finalization
assert vm.node_info('backup-filter') is not None
vm.qmp_log('job-finalize', id='backup')
diff --git a/tests/qemu-iotests/283.out b/tests/qemu-iotests/283.out
index c6e12b15c5..5bb75952ef 100644
--- a/tests/qemu-iotests/283.out
+++ b/tests/qemu-iotests/283.out
@@ -5,9 +5,9 @@
{"execute": "blockdev-add", "arguments": {"driver": "blkdebug", "image": "base", "node-name": "other", "take-child-perms": ["write"]}}
{"return": {}}
{"execute": "blockdev-backup", "arguments": {"device": "source", "sync": "full", "target": "target"}}
-{"error": {"class": "GenericError", "desc": "Cannot append backup-top filter: Permission conflict on node 'base': permissions 'write' are both required by node 'other' (uses node 'base' as 'image' child) and unshared by node 'source' (uses node 'base' as 'image' child)."}}
+{"error": {"class": "GenericError", "desc": "Permission conflict on node 'base': permissions 'write' are both required by node 'other' (uses node 'base' as 'image' child) and unshared by node 'source' (uses node 'base' as 'image' child)."}}
-=== backup-top should be gone after job-finalize ===
+=== copy-before-write filter should be gone after job-finalize ===
{"execute": "blockdev-add", "arguments": {"driver": "null-co", "node-name": "source"}}
{"return": {}}
diff --git a/tests/qemu-iotests/297 b/tests/qemu-iotests/297
index 433b732336..345b617b34 100755
--- a/tests/qemu-iotests/297
+++ b/tests/qemu-iotests/297
@@ -31,7 +31,7 @@ SKIP_FILES = (
'096', '118', '124', '132', '136', '139', '147', '148', '149',
'151', '152', '155', '163', '165', '169', '194', '196', '199', '202',
'203', '205', '206', '207', '208', '210', '211', '212', '213', '216',
- '218', '219', '222', '224', '228', '234', '235', '236', '237', '238',
+ '218', '219', '224', '228', '234', '235', '236', '237', '238',
'240', '242', '245', '246', '248', '255', '256', '257', '258', '260',
'262', '264', '266', '274', '277', '280', '281', '295', '296', '298',
'299', '302', '303', '304', '307',
diff --git a/tests/qemu-iotests/check b/tests/qemu-iotests/check
index 2dd529eb75..da1bfb839e 100755
--- a/tests/qemu-iotests/check
+++ b/tests/qemu-iotests/check
@@ -36,6 +36,15 @@ def make_argparser() -> argparse.ArgumentParser:
help='pretty print output for make check')
p.add_argument('-d', dest='debug', action='store_true', help='debug')
+ p.add_argument('-p', dest='print', action='store_true',
+ help='redirects qemu\'s stdout and stderr to the test output')
+ p.add_argument('-gdb', action='store_true',
+ help="start gdbserver with $GDB_OPTIONS options \
+ ('localhost:12345' if $GDB_OPTIONS is empty)")
+ p.add_argument('-valgrind', action='store_true',
+ help='use valgrind, sets VALGRIND_QEMU environment '
+ 'variable')
+
p.add_argument('-misalign', action='store_true',
help='misalign memory allocations')
p.add_argument('--color', choices=['on', 'off', 'auto'],
@@ -85,9 +94,6 @@ def make_argparser() -> argparse.ArgumentParser:
g_bash.add_argument('-o', dest='imgopts',
help='options to pass to qemu-img create/convert, '
'sets IMGOPTS environment variable')
- g_bash.add_argument('-valgrind', action='store_true',
- help='use valgrind, sets VALGRIND_QEMU environment '
- 'variable')
g_sel = p.add_argument_group('test selecting options',
'The following options specify test set '
@@ -114,7 +120,8 @@ if __name__ == '__main__':
env = TestEnv(imgfmt=args.imgfmt, imgproto=args.imgproto,
aiomode=args.aiomode, cachemode=args.cachemode,
imgopts=args.imgopts, misalign=args.misalign,
- debug=args.debug, valgrind=args.valgrind)
+ debug=args.debug, valgrind=args.valgrind,
+ gdb=args.gdb, qprint=args.print)
if len(sys.argv) > 1 and sys.argv[-len(args.tests)-1] == '--':
if not args.tests:
diff --git a/tests/qemu-iotests/common.qemu b/tests/qemu-iotests/common.qemu
index 0fc52d20d7..0f1fecc68e 100644
--- a/tests/qemu-iotests/common.qemu
+++ b/tests/qemu-iotests/common.qemu
@@ -85,7 +85,12 @@ _timed_wait_for()
timeout=yes
QEMU_STATUS[$h]=0
- while IFS= read -t ${QEMU_COMM_TIMEOUT} resp <&${QEMU_OUT[$h]}
+ read_timeout="-t ${QEMU_COMM_TIMEOUT}"
+ if [ -n "${GDB_OPTIONS}" ]; then
+ read_timeout=
+ fi
+
+ while IFS= read ${read_timeout} resp <&${QEMU_OUT[$h]}
do
if [ -n "$capture_events" ]; then
capture=0
diff --git a/tests/qemu-iotests/common.rc b/tests/qemu-iotests/common.rc
index 609d82de89..d8582454de 100644
--- a/tests/qemu-iotests/common.rc
+++ b/tests/qemu-iotests/common.rc
@@ -166,8 +166,14 @@ _qemu_wrapper()
if [ -n "${QEMU_NEED_PID}" ]; then
echo $BASHPID > "${QEMU_TEST_DIR}/qemu-${_QEMU_HANDLE}.pid"
fi
+
+ GDB=""
+ if [ -n "${GDB_OPTIONS}" ]; then
+ GDB="gdbserver ${GDB_OPTIONS}"
+ fi
+
VALGRIND_QEMU="${VALGRIND_QEMU_VM}" _qemu_proc_exec "${VALGRIND_LOGFILE}" \
- "$QEMU_PROG" $QEMU_OPTIONS "$@"
+ $GDB "$QEMU_PROG" $QEMU_OPTIONS "$@"
)
RETVAL=$?
_qemu_proc_valgrind_log "${VALGRIND_LOGFILE}" $RETVAL
diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
index 89663dac06..11276f380a 100644
--- a/tests/qemu-iotests/iotests.py
+++ b/tests/qemu-iotests/iotests.py
@@ -74,6 +74,13 @@ if os.environ.get('QEMU_NBD_OPTIONS'):
qemu_prog = os.environ.get('QEMU_PROG', 'qemu')
qemu_opts = os.environ.get('QEMU_OPTIONS', '').strip().split(' ')
+gdb_qemu_env = os.environ.get('GDB_OPTIONS')
+qemu_gdb = []
+if gdb_qemu_env:
+ qemu_gdb = ['gdbserver'] + gdb_qemu_env.strip().split(' ')
+
+qemu_print = os.environ.get('PRINT_QEMU', False)
+
imgfmt = os.environ.get('IMGFMT', 'raw')
imgproto = os.environ.get('IMGPROTO', 'file')
output_dir = os.environ.get('OUTPUT_DIR', '.')
@@ -91,6 +98,17 @@ except KeyError:
sys.stderr.write('Please run this test via the "check" script\n')
sys.exit(os.EX_USAGE)
+qemu_valgrind = []
+if os.environ.get('VALGRIND_QEMU') == "y" and \
+ os.environ.get('NO_VALGRIND') != "y":
+ valgrind_logfile = "--log-file=" + test_dir
+ # %p allows to put the valgrind process PID, since
+ # we don't know it a priori (subprocess.Popen is
+ # not yet invoked)
+ valgrind_logfile += "/%p.valgrind"
+
+ qemu_valgrind = ['valgrind', valgrind_logfile, '--error-exitcode=99']
+
socket_scm_helper = os.environ.get('SOCKET_SCM_HELPER', 'socket_scm_helper')
luks_default_secret_object = 'secret,id=keysec0,data=' + \
@@ -219,18 +237,18 @@ def qemu_io_silent(*args):
default_args = qemu_io_args
args = default_args + list(args)
- exitcode = subprocess.call(args, stdout=open('/dev/null', 'w'))
- if exitcode < 0:
+ result = subprocess.run(args, stdout=subprocess.DEVNULL, check=False)
+ if result.returncode < 0:
sys.stderr.write('qemu-io received signal %i: %s\n' %
- (-exitcode, ' '.join(args)))
- return exitcode
+ (-result.returncode, ' '.join(args)))
+ return result.returncode
def qemu_io_silent_check(*args):
'''Run qemu-io and return the true if subprocess returned 0'''
args = qemu_io_args + list(args)
- exitcode = subprocess.call(args, stdout=open('/dev/null', 'w'),
- stderr=subprocess.STDOUT)
- return exitcode == 0
+ result = subprocess.run(args, stdout=subprocess.DEVNULL,
+ stderr=subprocess.STDOUT, check=False)
+ return result.returncode == 0
class QemuIoInteractive:
def __init__(self, *args):
@@ -472,10 +490,14 @@ class Timeout:
self.seconds = seconds
self.errmsg = errmsg
def __enter__(self):
+ if qemu_gdb or qemu_valgrind:
+ return self
signal.signal(signal.SIGALRM, self.timeout)
signal.setitimer(signal.ITIMER_REAL, self.seconds)
return self
def __exit__(self, exc_type, value, traceback):
+ if qemu_gdb or qemu_valgrind:
+ return False
signal.setitimer(signal.ITIMER_REAL, 0)
return False
def timeout(self, signum, frame):
@@ -570,12 +592,35 @@ class VM(qtest.QEMUQtestMachine):
def __init__(self, path_suffix=''):
name = "qemu%s-%d" % (path_suffix, os.getpid())
- super().__init__(qemu_prog, qemu_opts, name=name,
+ timer = 15.0 if not (qemu_gdb or qemu_valgrind) else None
+ if qemu_gdb and qemu_valgrind:
+ sys.stderr.write('gdb and valgrind are mutually exclusive\n')
+ sys.exit(1)
+ wrapper = qemu_gdb if qemu_gdb else qemu_valgrind
+ super().__init__(qemu_prog, qemu_opts, wrapper=wrapper,
+ name=name,
base_temp_dir=test_dir,
socket_scm_helper=socket_scm_helper,
- sock_dir=sock_dir)
+ sock_dir=sock_dir, qmp_timer=timer)
self._num_drives = 0
+ def _post_shutdown(self) -> None:
+ super()._post_shutdown()
+ if not qemu_valgrind or not self._popen:
+ return
+ valgrind_filename = f"{test_dir}/{self._popen.pid}.valgrind"
+ if self.exitcode() == 99:
+ with open(valgrind_filename) as f:
+ print(f.read())
+ else:
+ os.remove(valgrind_filename)
+
+ def _pre_launch(self) -> None:
+ super()._pre_launch()
+ if qemu_print:
+ # set QEMU binary output to stdout
+ self._close_qemu_log_file()
+
def add_object(self, opts):
self._args.append('-object')
self._args.append(opts)
@@ -651,9 +696,10 @@ class VM(qtest.QEMUQtestMachine):
self.hmp(f'qemu-io {drive} "remove_break bp_{drive}"')
def hmp_qemu_io(self, drive: str, cmd: str,
- use_log: bool = False) -> QMPMessage:
+ use_log: bool = False, qdev: bool = False) -> QMPMessage:
"""Write to a given drive using an HMP command"""
- return self.hmp(f'qemu-io {drive} "{cmd}"', use_log=use_log)
+ d = '-d ' if qdev else ''
+ return self.hmp(f'qemu-io {d}{drive} "{cmd}"', use_log=use_log)
def flatten_qmp_object(self, obj, output=None, basestr=''):
if output is None:
@@ -1075,7 +1121,8 @@ def notrun(reason):
# Each test in qemu-iotests has a number ("seq")
seq = os.path.basename(sys.argv[0])
- open('%s/%s.notrun' % (output_dir, seq), 'w').write(reason + '\n')
+ with open('%s/%s.notrun' % (output_dir, seq), 'w') as outfile:
+ outfile.write(reason + '\n')
logger.warning("%s not run: %s", seq, reason)
sys.exit(0)
@@ -1088,8 +1135,8 @@ def case_notrun(reason):
# Each test in qemu-iotests has a number ("seq")
seq = os.path.basename(sys.argv[0])
- open('%s/%s.casenotrun' % (output_dir, seq), 'a').write(
- ' [case not run] ' + reason + '\n')
+ with open('%s/%s.casenotrun' % (output_dir, seq), 'a') as outfile:
+ outfile.write(' [case not run] ' + reason + '\n')
def _verify_image_format(supported_fmts: Sequence[str] = (),
unsupported_fmts: Sequence[str] = ()) -> None:
diff --git a/tests/qemu-iotests/testenv.py b/tests/qemu-iotests/testenv.py
index 0c3fe75636..70da0d60c8 100644
--- a/tests/qemu-iotests/testenv.py
+++ b/tests/qemu-iotests/testenv.py
@@ -27,6 +27,7 @@ import subprocess
import glob
from typing import List, Dict, Any, Optional, ContextManager
+DEF_GDB_OPTIONS = 'localhost:12345'
def isxfile(path: str) -> bool:
return os.path.isfile(path) and os.access(path, os.X_OK)
@@ -72,7 +73,8 @@ class TestEnv(ContextManager['TestEnv']):
'QEMU_NBD_OPTIONS', 'IMGOPTS', 'IMGFMT', 'IMGPROTO',
'AIOMODE', 'CACHEMODE', 'VALGRIND_QEMU',
'CACHEMODE_IS_DEFAULT', 'IMGFMT_GENERIC', 'IMGOPTSSYNTAX',
- 'IMGKEYSECRET', 'QEMU_DEFAULT_MACHINE', 'MALLOC_PERTURB_']
+ 'IMGKEYSECRET', 'QEMU_DEFAULT_MACHINE', 'MALLOC_PERTURB_',
+ 'GDB_OPTIONS', 'PRINT_QEMU']
def prepare_subprocess(self, args: List[str]) -> Dict[str, str]:
if self.debug:
@@ -178,7 +180,9 @@ class TestEnv(ContextManager['TestEnv']):
imgopts: Optional[str] = None,
misalign: bool = False,
debug: bool = False,
- valgrind: bool = False) -> None:
+ valgrind: bool = False,
+ gdb: bool = False,
+ qprint: bool = False) -> None:
self.imgfmt = imgfmt
self.imgproto = imgproto
self.aiomode = aiomode
@@ -186,6 +190,18 @@ class TestEnv(ContextManager['TestEnv']):
self.misalign = misalign
self.debug = debug
+ if qprint:
+ self.print_qemu = 'y'
+
+ if gdb:
+ self.gdb_options = os.getenv('GDB_OPTIONS', DEF_GDB_OPTIONS)
+ if not self.gdb_options:
+ # cover the case 'export GDB_OPTIONS='
+ self.gdb_options = DEF_GDB_OPTIONS
+ elif 'GDB_OPTIONS' in os.environ:
+ # to not propagate it in prepare_subprocess()
+ del os.environ['GDB_OPTIONS']
+
if valgrind:
self.valgrind_qemu = 'y'
@@ -285,6 +301,9 @@ PLATFORM -- {platform}
TEST_DIR -- {TEST_DIR}
SOCK_DIR -- {SOCK_DIR}
SOCKET_SCM_HELPER -- {SOCKET_SCM_HELPER}
+GDB_OPTIONS -- {GDB_OPTIONS}
+VALGRIND_QEMU -- {VALGRIND_QEMU}
+PRINT_QEMU_OUTPUT -- {PRINT_QEMU}
"""
args = collections.defaultdict(str, self.get_env())
diff --git a/tests/qemu-iotests/tests/image-fleecing b/tests/qemu-iotests/tests/image-fleecing
new file mode 100755
index 0000000000..f6318492c6
--- /dev/null
+++ b/tests/qemu-iotests/tests/image-fleecing
@@ -0,0 +1,192 @@
+#!/usr/bin/env python3
+# group: rw quick
+#
+# This test covers the basic fleecing workflow, which provides a
+# point-in-time snapshot of a node that can be queried over NBD.
+#
+# Copyright (C) 2018 Red Hat, Inc.
+# John helped, too.
+#
+# 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/>.
+#
+# Creator/Owner: John Snow <jsnow@redhat.com>
+
+import iotests
+from iotests import log, qemu_img, qemu_io, qemu_io_silent
+
+iotests.script_initialize(
+ supported_fmts=['qcow2', 'qcow', 'qed', 'vmdk', 'vhdx', 'raw'],
+ supported_platforms=['linux'],
+)
+
+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)
+
+zeroes = [('0', '0x00f8000', '32k'), # Left-end of partial-left (1M-32K)
+ ('0', '0x2010000', '32k'), # Right-end of partial-right (32M+64K)
+ ('0', '0x3fe0000', '64k')] # overwrite[3]
+
+remainder = [('0xd5', '0x108000', '32k'), # Right-end of partial-left [1]
+ ('0xdc', '32M', '32k'), # Left-end of partial-right [2]
+ ('0xcd', '0x3ff0000', '64k')] # patterns[3]
+
+def do_test(use_cbw, base_img_path, fleece_img_path, nbd_sock_path, vm):
+ log('--- Setting up images ---')
+ log('')
+
+ assert qemu_img('create', '-f', iotests.imgfmt, base_img_path, '64M') == 0
+ assert qemu_img('create', '-f', 'qcow2', fleece_img_path, '64M') == 0
+
+ for p in patterns:
+ qemu_io('-f', iotests.imgfmt,
+ '-c', 'write -P%s %s %s' % p, base_img_path)
+
+ log('Done')
+
+ log('')
+ log('--- Launching VM ---')
+ log('')
+
+ src_node = 'source'
+ tmp_node = 'temp'
+ qom_path = '/machine/peripheral/sda'
+ vm.add_blockdev(f'driver={iotests.imgfmt},file.driver=file,'
+ f'file.filename={base_img_path},node-name={src_node}')
+ vm.add_device('virtio-scsi')
+ vm.add_device(f'scsi-hd,id=sda,drive={src_node}')
+ vm.launch()
+ log('Done')
+
+ log('')
+ log('--- Setting up Fleecing Graph ---')
+ log('')
+
+
+ # create tmp_node backed by src_node
+ log(vm.qmp('blockdev-add', {
+ 'driver': 'qcow2',
+ 'node-name': tmp_node,
+ 'file': {
+ 'driver': 'file',
+ 'filename': fleece_img_path,
+ },
+ 'backing': src_node,
+ }))
+
+ # Establish CBW from source to fleecing node
+ if use_cbw:
+ log(vm.qmp('blockdev-add', {
+ 'driver': 'copy-before-write',
+ 'node-name': 'fl-cbw',
+ 'file': src_node,
+ 'target': tmp_node
+ }))
+
+ log(vm.qmp('qom-set', path=qom_path, property='drive', value='fl-cbw'))
+ else:
+ log(vm.qmp('blockdev-backup',
+ job_id='fleecing',
+ device=src_node,
+ target=tmp_node,
+ sync='none'))
+
+ log('')
+ log('--- Setting up NBD Export ---')
+ log('')
+
+ nbd_uri = 'nbd+unix:///%s?socket=%s' % (tmp_node, nbd_sock_path)
+ log(vm.qmp('nbd-server-start',
+ {'addr': { 'type': 'unix',
+ 'data': { 'path': nbd_sock_path } } }))
+
+ log(vm.qmp('nbd-server-add', device=tmp_node))
+
+ log('')
+ log('--- Sanity Check ---')
+ log('')
+
+ for p in patterns + zeroes:
+ cmd = 'read -P%s %s %s' % p
+ log(cmd)
+ assert qemu_io_silent('-r', '-f', 'raw', '-c', cmd, nbd_uri) == 0
+
+ log('')
+ log('--- Testing COW ---')
+ log('')
+
+ for p in overwrite:
+ cmd = 'write -P%s %s %s' % p
+ log(cmd)
+ log(vm.hmp_qemu_io(qom_path, cmd, qdev=True))
+
+ log('')
+ log('--- Verifying Data ---')
+ log('')
+
+ for p in patterns + zeroes:
+ cmd = 'read -P%s %s %s' % p
+ log(cmd)
+ assert qemu_io_silent('-r', '-f', 'raw', '-c', cmd, nbd_uri) == 0
+
+ log('')
+ log('--- Cleanup ---')
+ log('')
+
+ if use_cbw:
+ log(vm.qmp('qom-set', path=qom_path, property='drive', value=src_node))
+ log(vm.qmp('blockdev-del', node_name='fl-cbw'))
+ else:
+ log(vm.qmp('block-job-cancel', device='fleecing'))
+ e = vm.event_wait('BLOCK_JOB_CANCELLED')
+ assert e is not None
+ log(e, filters=[iotests.filter_qmp_event])
+
+ log(vm.qmp('nbd-server-stop'))
+ log(vm.qmp('blockdev-del', node_name=tmp_node))
+ vm.shutdown()
+
+ log('')
+ log('--- Confirming writes ---')
+ log('')
+
+ for p in overwrite + remainder:
+ cmd = 'read -P%s %s %s' % p
+ log(cmd)
+ assert qemu_io_silent(base_img_path, '-c', cmd) == 0
+
+ log('')
+ log('Done')
+
+
+def test(use_cbw):
+ with iotests.FilePath('base.img') as base_img_path, \
+ iotests.FilePath('fleece.img') as fleece_img_path, \
+ iotests.FilePath('nbd.sock',
+ base_dir=iotests.sock_dir) as nbd_sock_path, \
+ iotests.VM() as vm:
+ do_test(use_cbw, base_img_path, fleece_img_path, nbd_sock_path, vm)
+
+
+log('=== Test backup(sync=none) based fleecing ===\n')
+test(False)
+
+log('=== Test filter based fleecing ===\n')
+test(True)
diff --git a/tests/qemu-iotests/tests/image-fleecing.out b/tests/qemu-iotests/tests/image-fleecing.out
new file mode 100644
index 0000000000..e96d122a8b
--- /dev/null
+++ b/tests/qemu-iotests/tests/image-fleecing.out
@@ -0,0 +1,139 @@
+=== Test backup(sync=none) based fleecing ===
+
+--- Setting up images ---
+
+Done
+
+--- Launching VM ---
+
+Done
+
+--- Setting up Fleecing Graph ---
+
+{"return": {}}
+{"return": {}}
+
+--- Setting up NBD Export ---
+
+{"return": {}}
+{"return": {}}
+
+--- Sanity Check ---
+
+read -P0x5d 0 64k
+read -P0xd5 1M 64k
+read -P0xdc 32M 64k
+read -P0xcd 0x3ff0000 64k
+read -P0 0x00f8000 32k
+read -P0 0x2010000 32k
+read -P0 0x3fe0000 64k
+
+--- Testing COW ---
+
+write -P0xab 0 64k
+{"return": ""}
+write -P0xad 0x00f8000 64k
+{"return": ""}
+write -P0x1d 0x2008000 64k
+{"return": ""}
+write -P0xea 0x3fe0000 64k
+{"return": ""}
+
+--- Verifying Data ---
+
+read -P0x5d 0 64k
+read -P0xd5 1M 64k
+read -P0xdc 32M 64k
+read -P0xcd 0x3ff0000 64k
+read -P0 0x00f8000 32k
+read -P0 0x2010000 32k
+read -P0 0x3fe0000 64k
+
+--- Cleanup ---
+
+{"return": {}}
+{"data": {"device": "fleecing", "len": 67108864, "offset": 393216, "speed": 0, "type": "backup"}, "event": "BLOCK_JOB_CANCELLED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
+{"return": {}}
+{"return": {}}
+
+--- Confirming writes ---
+
+read -P0xab 0 64k
+read -P0xad 0x00f8000 64k
+read -P0x1d 0x2008000 64k
+read -P0xea 0x3fe0000 64k
+read -P0xd5 0x108000 32k
+read -P0xdc 32M 32k
+read -P0xcd 0x3ff0000 64k
+
+Done
+=== Test filter based fleecing ===
+
+--- Setting up images ---
+
+Done
+
+--- Launching VM ---
+
+Done
+
+--- Setting up Fleecing Graph ---
+
+{"return": {}}
+{"return": {}}
+{"return": {}}
+
+--- Setting up NBD Export ---
+
+{"return": {}}
+{"return": {}}
+
+--- Sanity Check ---
+
+read -P0x5d 0 64k
+read -P0xd5 1M 64k
+read -P0xdc 32M 64k
+read -P0xcd 0x3ff0000 64k
+read -P0 0x00f8000 32k
+read -P0 0x2010000 32k
+read -P0 0x3fe0000 64k
+
+--- Testing COW ---
+
+write -P0xab 0 64k
+{"return": ""}
+write -P0xad 0x00f8000 64k
+{"return": ""}
+write -P0x1d 0x2008000 64k
+{"return": ""}
+write -P0xea 0x3fe0000 64k
+{"return": ""}
+
+--- Verifying Data ---
+
+read -P0x5d 0 64k
+read -P0xd5 1M 64k
+read -P0xdc 32M 64k
+read -P0xcd 0x3ff0000 64k
+read -P0 0x00f8000 32k
+read -P0 0x2010000 32k
+read -P0 0x3fe0000 64k
+
+--- Cleanup ---
+
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+
+--- Confirming writes ---
+
+read -P0xab 0 64k
+read -P0xad 0x00f8000 64k
+read -P0x1d 0x2008000 64k
+read -P0xea 0x3fe0000 64k
+read -P0xd5 0x108000 32k
+read -P0xdc 32M 32k
+read -P0xcd 0x3ff0000 64k
+
+Done