| #!/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']) |