9ee2dd4c22
The common.qemu bash functions allow tests to interact with the QMP monitor of a QEMU process. I spent two days trying to update 141 when the order of the test output changed, but found it would still fail occassionally because printf() and QMP events race with synchronous QMP communication. I gave up and ported 141 to the existing Python API for QMP tests. The Python API is less affected by the order in which QEMU prints output because it does not print all QMP traffic by default. The next commit changes the order in which QMP messages are received. Make 141 reliable first. Cc: Hanna Czenczek <hreitz@redhat.com> Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com> Message-ID: <20240118144823.1497953-3-stefanha@redhat.com> Reviewed-by: Kevin Wolf <kwolf@redhat.com> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
151 lines
6.1 KiB
Python
Executable File
151 lines
6.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# group: rw auto quick
|
|
#
|
|
# Test case for ejecting BDSs with block jobs still running on them
|
|
#
|
|
# Originally written in bash by Hanna Czenczek, ported to Python by Stefan
|
|
# Hajnoczi.
|
|
#
|
|
# Copyright Red Hat
|
|
#
|
|
# 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/>.
|
|
#
|
|
|
|
import iotests
|
|
|
|
# Common filters to mask values that vary in the test output
|
|
QMP_FILTERS = [iotests.filter_qmp_testfiles, \
|
|
iotests.filter_qmp_imgfmt]
|
|
|
|
|
|
class TestCase:
|
|
def __init__(self, name, vm, image_path, cancel_event):
|
|
self.name = name
|
|
self.vm = vm
|
|
self.image_path = image_path
|
|
self.cancel_event = cancel_event
|
|
|
|
def __enter__(self):
|
|
iotests.log(f'=== Testing {self.name} ===')
|
|
self.vm.qmp_log('blockdev-add', \
|
|
node_name='drv0', \
|
|
driver=iotests.imgfmt, \
|
|
file={'driver': 'file', 'filename': self.image_path}, \
|
|
filters=QMP_FILTERS)
|
|
|
|
def __exit__(self, *exc_details):
|
|
# This is expected to fail because the job still exists
|
|
self.vm.qmp_log('blockdev-del', node_name='drv0', \
|
|
filters=[iotests.filter_qmp_generated_node_ids])
|
|
|
|
self.vm.qmp_log('block-job-cancel', device='job0')
|
|
event = self.vm.event_wait(self.cancel_event)
|
|
iotests.log(event, filters=[iotests.filter_qmp_event])
|
|
|
|
# This time it succeeds
|
|
self.vm.qmp_log('blockdev-del', node_name='drv0')
|
|
|
|
# Separate test cases in output
|
|
iotests.log('')
|
|
|
|
|
|
def main() -> None:
|
|
with iotests.FilePath('bottom', 'middle', 'top', 'target') as \
|
|
(bottom_path, middle_path, top_path, target_path), \
|
|
iotests.VM() as vm:
|
|
|
|
iotests.log('Creating bottom <- middle <- top backing file chain...')
|
|
IMAGE_SIZE='1M'
|
|
iotests.qemu_img_create('-f', iotests.imgfmt, bottom_path, IMAGE_SIZE)
|
|
iotests.qemu_img_create('-f', iotests.imgfmt, \
|
|
'-F', iotests.imgfmt, \
|
|
'-b', bottom_path, \
|
|
middle_path, \
|
|
IMAGE_SIZE)
|
|
iotests.qemu_img_create('-f', iotests.imgfmt, \
|
|
'-F', iotests.imgfmt, \
|
|
'-b', middle_path, \
|
|
top_path, \
|
|
IMAGE_SIZE)
|
|
|
|
iotests.log('Starting VM...')
|
|
vm.add_args('-nodefaults')
|
|
vm.launch()
|
|
|
|
# drive-backup will not send BLOCK_JOB_READY by itself, and cancelling
|
|
# the job will consequently result in BLOCK_JOB_CANCELLED being
|
|
# emitted.
|
|
with TestCase('drive-backup', vm, top_path, 'BLOCK_JOB_CANCELLED'):
|
|
vm.qmp_log('drive-backup', \
|
|
job_id='job0', \
|
|
device='drv0', \
|
|
target=target_path, \
|
|
format=iotests.imgfmt, \
|
|
sync='none', \
|
|
filters=QMP_FILTERS)
|
|
|
|
# drive-mirror will send BLOCK_JOB_READY basically immediately, and
|
|
# cancelling the job will consequently result in BLOCK_JOB_COMPLETED
|
|
# being emitted.
|
|
with TestCase('drive-mirror', vm, top_path, 'BLOCK_JOB_COMPLETED'):
|
|
vm.qmp_log('drive-mirror', \
|
|
job_id='job0', \
|
|
device='drv0', \
|
|
target=target_path, \
|
|
format=iotests.imgfmt, \
|
|
sync='none', \
|
|
filters=QMP_FILTERS)
|
|
event = vm.event_wait('BLOCK_JOB_READY')
|
|
assert event is not None # silence mypy
|
|
iotests.log(event, filters=[iotests.filter_qmp_event])
|
|
|
|
# An active block-commit will send BLOCK_JOB_READY basically
|
|
# immediately, and cancelling the job will consequently result in
|
|
# BLOCK_JOB_COMPLETED being emitted.
|
|
with TestCase('active block-commit', vm, top_path, \
|
|
'BLOCK_JOB_COMPLETED'):
|
|
vm.qmp_log('block-commit', \
|
|
job_id='job0', \
|
|
device='drv0')
|
|
event = vm.event_wait('BLOCK_JOB_READY')
|
|
assert event is not None # silence mypy
|
|
iotests.log(event, filters=[iotests.filter_qmp_event])
|
|
|
|
# Give block-commit something to work on, otherwise it would be done
|
|
# immediately, send a BLOCK_JOB_COMPLETED and ejecting the BDS would
|
|
# work just fine without the block job still running.
|
|
iotests.qemu_io(middle_path, '-c', f'write 0 {IMAGE_SIZE}')
|
|
with TestCase('non-active block-commit', vm, top_path, \
|
|
'BLOCK_JOB_CANCELLED'):
|
|
vm.qmp_log('block-commit', \
|
|
job_id='job0', \
|
|
device='drv0', \
|
|
top=middle_path, \
|
|
speed=1, \
|
|
filters=[iotests.filter_qmp_testfiles])
|
|
|
|
# Give block-stream something to work on, otherwise it would be done
|
|
# immediately, send a BLOCK_JOB_COMPLETED and ejecting the BDS would
|
|
# work just fine without the block job still running.
|
|
iotests.qemu_io(bottom_path, '-c', f'write 0 {IMAGE_SIZE}')
|
|
with TestCase('block-stream', vm, top_path, 'BLOCK_JOB_CANCELLED'):
|
|
vm.qmp_log('block-stream', \
|
|
job_id='job0', \
|
|
device='drv0', \
|
|
speed=1)
|
|
|
|
if __name__ == '__main__':
|
|
iotests.script_main(main, supported_fmts=['qcow2', 'qed'],
|
|
supported_protocols=['file'])
|