#!/usr/bin/env python3 # group: rw quick # # Test what happens when errors occur to a mirror job after it has # been cancelled in the READY phase # # Copyright (C) 2021 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 . # import os import iotests image_size = 1 * 1024 * 1024 source = os.path.join(iotests.test_dir, 'source.img') target = os.path.join(iotests.test_dir, 'target.img') class TestMirrorReadyCancelError(iotests.QMPTestCase): def setUp(self) -> None: iotests.qemu_img_create('-f', iotests.imgfmt, source, str(image_size)) iotests.qemu_img_create('-f', iotests.imgfmt, target, str(image_size)) # Ensure that mirror will copy something before READY so the # target format layer will forward the pre-READY flush to its # file child assert iotests.qemu_io_silent('-c', 'write -P 1 0 64k', source) == 0 self.vm = iotests.VM() self.vm.launch() def tearDown(self) -> None: self.vm.shutdown() os.remove(source) os.remove(target) def add_blockdevs(self, once: bool) -> None: res = self.vm.qmp('blockdev-add', **{'node-name': 'source', 'driver': iotests.imgfmt, 'file': { 'driver': 'file', 'filename': source }}) self.assert_qmp(res, 'return', {}) # blkdebug notes: # Enter state 2 on the first flush, which happens before the # job enters the READY state. The second flush will happen # when the job is about to complete, and we want that one to # fail. res = self.vm.qmp('blockdev-add', **{'node-name': 'target', 'driver': iotests.imgfmt, 'file': { 'driver': 'blkdebug', 'image': { 'driver': 'file', 'filename': target }, 'set-state': [{ 'event': 'flush_to_disk', 'state': 1, 'new_state': 2 }], 'inject-error': [{ 'event': 'flush_to_disk', 'once': once, 'immediately': True, 'state': 2 }]}}) self.assert_qmp(res, 'return', {}) def start_mirror(self) -> None: res = self.vm.qmp('blockdev-mirror', job_id='mirror', device='source', target='target', filter_node_name='mirror-top', sync='full', on_target_error='stop') self.assert_qmp(res, 'return', {}) def cancel_mirror_with_error(self) -> None: self.vm.event_wait('BLOCK_JOB_READY') # Write something so will not leave the job immediately, but # flush first (which will fail, thanks to blkdebug) res = self.vm.qmp('human-monitor-command', command_line='qemu-io mirror-top "write -P 2 0 64k"') self.assert_qmp(res, 'return', '') # Drain status change events while self.vm.event_wait('JOB_STATUS_CHANGE', timeout=0.0) is not None: pass res = self.vm.qmp('block-job-cancel', device='mirror') self.assert_qmp(res, 'return', {}) self.vm.event_wait('BLOCK_JOB_ERROR') def test_transient_error(self) -> None: self.add_blockdevs(True) self.start_mirror() self.cancel_mirror_with_error() while True: e = self.vm.event_wait('JOB_STATUS_CHANGE') if e['data']['status'] == 'standby': # Transient error, try again self.vm.qmp('block-job-resume', device='mirror') elif e['data']['status'] == 'null': break def test_persistent_error(self) -> None: self.add_blockdevs(False) self.start_mirror() self.cancel_mirror_with_error() while True: e = self.vm.event_wait('JOB_STATUS_CHANGE') if e['data']['status'] == 'standby': # Persistent error, no point in continuing self.vm.qmp('block-job-cancel', device='mirror', force=True) elif e['data']['status'] == 'null': break if __name__ == '__main__': # LUKS would require special key-secret handling in add_blockdevs() iotests.main(supported_fmts=['generic'], unsupported_fmts=['luks'], supported_protocols=['file'])