| #!/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 <http://www.gnu.org/licenses/>. |
| # |
| |
| 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 |
| iotests.qemu_io('-c', 'write -P 1 0 64k', source) |
| |
| 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']) |