| 1 | #!/usr/bin/env python3 |
| 2 | # group: rw auto quick |
| 3 | # |
| 4 | # Test case for ejecting BDSs with block jobs still running on them |
| 5 | # |
| 6 | # Originally written in bash by Hanna Czenczek, ported to Python by Stefan |
| 7 | # Hajnoczi. |
| 8 | # |
| 9 | # Copyright Red Hat |
| 10 | # |
| 11 | # This program is free software; you can redistribute it and/or modify |
| 12 | # it under the terms of the GNU General Public License as published by |
| 13 | # the Free Software Foundation; either version 2 of the License, or |
| 14 | # (at your option) any later version. |
| 15 | # |
| 16 | # This program is distributed in the hope that it will be useful, |
| 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 19 | # GNU General Public License for more details. |
| 20 | # |
| 21 | # You should have received a copy of the GNU General Public License |
| 22 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 23 | # |
| 24 | |
| 25 | import iotests |
| 26 | |
| 27 | # Common filters to mask values that vary in the test output |
| 28 | QMP_FILTERS = [iotests.filter_qmp_testfiles, \ |
| 29 | iotests.filter_qmp_imgfmt] |
| 30 | |
| 31 | |
| 32 | class TestCase: |
| 33 | def __init__(self, name, vm, image_path, cancel_event): |
| 34 | self.name = name |
| 35 | self.vm = vm |
| 36 | self.image_path = image_path |
| 37 | self.cancel_event = cancel_event |
| 38 | |
| 39 | def __enter__(self): |
| 40 | iotests.log(f'=== Testing {self.name} ===') |
| 41 | self.vm.qmp_log('blockdev-add', \ |
| 42 | node_name='drv0', \ |
| 43 | driver=iotests.imgfmt, \ |
| 44 | file={'driver': 'file', 'filename': self.image_path}, \ |
| 45 | filters=QMP_FILTERS) |
| 46 | |
| 47 | def __exit__(self, *exc_details): |
| 48 | # This is expected to fail because the job still exists |
| 49 | self.vm.qmp_log('blockdev-del', node_name='drv0', \ |
| 50 | filters=[iotests.filter_qmp_generated_node_ids]) |
| 51 | |
| 52 | self.vm.qmp_log('block-job-cancel', device='job0') |
| 53 | event = self.vm.event_wait(self.cancel_event) |
| 54 | iotests.log(event, filters=[iotests.filter_qmp_event]) |
| 55 | |
| 56 | # This time it succeeds |
| 57 | self.vm.qmp_log('blockdev-del', node_name='drv0') |
| 58 | |
| 59 | # Separate test cases in output |
| 60 | iotests.log('') |
| 61 | |
| 62 | |
| 63 | def main() -> None: |
| 64 | with iotests.FilePath('bottom', 'middle', 'top', 'target') as \ |
| 65 | (bottom_path, middle_path, top_path, target_path), \ |
| 66 | iotests.VM() as vm: |
| 67 | |
| 68 | iotests.log('Creating bottom <- middle <- top backing file chain...') |
| 69 | IMAGE_SIZE='1M' |
| 70 | iotests.qemu_img_create('-f', iotests.imgfmt, bottom_path, IMAGE_SIZE) |
| 71 | iotests.qemu_img_create('-f', iotests.imgfmt, \ |
| 72 | '-F', iotests.imgfmt, \ |
| 73 | '-b', bottom_path, \ |
| 74 | middle_path, \ |
| 75 | IMAGE_SIZE) |
| 76 | iotests.qemu_img_create('-f', iotests.imgfmt, \ |
| 77 | '-F', iotests.imgfmt, \ |
| 78 | '-b', middle_path, \ |
| 79 | top_path, \ |
| 80 | IMAGE_SIZE) |
| 81 | |
| 82 | iotests.log('Starting VM...') |
| 83 | vm.add_args('-nodefaults') |
| 84 | vm.launch() |
| 85 | |
| 86 | # drive-backup will not send BLOCK_JOB_READY by itself, and cancelling |
| 87 | # the job will consequently result in BLOCK_JOB_CANCELLED being |
| 88 | # emitted. |
| 89 | with TestCase('drive-backup', vm, top_path, 'BLOCK_JOB_CANCELLED'): |
| 90 | vm.qmp_log('drive-backup', \ |
| 91 | job_id='job0', \ |
| 92 | device='drv0', \ |
| 93 | target=target_path, \ |
| 94 | format=iotests.imgfmt, \ |
| 95 | sync='none', \ |
| 96 | filters=QMP_FILTERS) |
| 97 | |
| 98 | # drive-mirror will send BLOCK_JOB_READY basically immediately, and |
| 99 | # cancelling the job will consequently result in BLOCK_JOB_COMPLETED |
| 100 | # being emitted. |
| 101 | with TestCase('drive-mirror', vm, top_path, 'BLOCK_JOB_COMPLETED'): |
| 102 | vm.qmp_log('drive-mirror', \ |
| 103 | job_id='job0', \ |
| 104 | device='drv0', \ |
| 105 | target=target_path, \ |
| 106 | format=iotests.imgfmt, \ |
| 107 | sync='none', \ |
| 108 | filters=QMP_FILTERS) |
| 109 | event = vm.event_wait('BLOCK_JOB_READY') |
| 110 | assert event is not None # silence mypy |
| 111 | iotests.log(event, filters=[iotests.filter_qmp_event]) |
| 112 | |
| 113 | # An active block-commit will send BLOCK_JOB_READY basically |
| 114 | # immediately, and cancelling the job will consequently result in |
| 115 | # BLOCK_JOB_COMPLETED being emitted. |
| 116 | with TestCase('active block-commit', vm, top_path, \ |
| 117 | 'BLOCK_JOB_COMPLETED'): |
| 118 | vm.qmp_log('block-commit', \ |
| 119 | job_id='job0', \ |
| 120 | device='drv0') |
| 121 | event = vm.event_wait('BLOCK_JOB_READY') |
| 122 | assert event is not None # silence mypy |
| 123 | iotests.log(event, filters=[iotests.filter_qmp_event]) |
| 124 | |
| 125 | # Give block-commit something to work on, otherwise it would be done |
| 126 | # immediately, send a BLOCK_JOB_COMPLETED and ejecting the BDS would |
| 127 | # work just fine without the block job still running. |
| 128 | iotests.qemu_io(middle_path, '-c', f'write 0 {IMAGE_SIZE}') |
| 129 | with TestCase('non-active block-commit', vm, top_path, \ |
| 130 | 'BLOCK_JOB_CANCELLED'): |
| 131 | vm.qmp_log('block-commit', \ |
| 132 | job_id='job0', \ |
| 133 | device='drv0', \ |
| 134 | top=middle_path, \ |
| 135 | speed=1, \ |
| 136 | filters=[iotests.filter_qmp_testfiles]) |
| 137 | |
| 138 | # Give block-stream something to work on, otherwise it would be done |
| 139 | # immediately, send a BLOCK_JOB_COMPLETED and ejecting the BDS would |
| 140 | # work just fine without the block job still running. |
| 141 | iotests.qemu_io(bottom_path, '-c', f'write 0 {IMAGE_SIZE}') |
| 142 | with TestCase('block-stream', vm, top_path, 'BLOCK_JOB_CANCELLED'): |
| 143 | vm.qmp_log('block-stream', \ |
| 144 | job_id='job0', \ |
| 145 | device='drv0', \ |
| 146 | speed=1) |
| 147 | |
| 148 | if __name__ == '__main__': |
| 149 | iotests.script_main(main, supported_fmts=['qcow2', 'qed'], |
| 150 | supported_protocols=['file']) |