1#!/usr/bin/env python3 2# group: rw quick 3# 4# Test what happens when errors occur to a mirror job after it has 5# been cancelled in the READY phase 6# 7# Copyright (C) 2021 Red Hat, Inc. 8# 9# This program is free software; you can redistribute it and/or modify 10# it under the terms of the GNU General Public License as published by 11# the Free Software Foundation; either version 2 of the License, or 12# (at your option) any later version. 13# 14# This program is distributed in the hope that it will be useful, 15# but WITHOUT ANY WARRANTY; without even the implied warranty of 16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17# GNU General Public License for more details. 18# 19# You should have received a copy of the GNU General Public License 20# along with this program. If not, see <http://www.gnu.org/licenses/>. 21# 22 23import os 24import iotests 25 26 27image_size = 1 * 1024 * 1024 28source = os.path.join(iotests.test_dir, 'source.img') 29target = os.path.join(iotests.test_dir, 'target.img') 30 31 32class TestMirrorReadyCancelError(iotests.QMPTestCase): 33 def setUp(self) -> None: 34 assert iotests.qemu_img_create('-f', iotests.imgfmt, source, 35 str(image_size)) == 0 36 assert iotests.qemu_img_create('-f', iotests.imgfmt, target, 37 str(image_size)) == 0 38 39 self.vm = iotests.VM() 40 self.vm.launch() 41 42 def tearDown(self) -> None: 43 self.vm.shutdown() 44 os.remove(source) 45 os.remove(target) 46 47 def add_blockdevs(self, once: bool) -> None: 48 res = self.vm.qmp('blockdev-add', 49 **{'node-name': 'source', 50 'driver': iotests.imgfmt, 51 'file': { 52 'driver': 'file', 53 'filename': source 54 }}) 55 self.assert_qmp(res, 'return', {}) 56 57 # blkdebug notes: 58 # Enter state 2 on the first flush, which happens before the 59 # job enters the READY state. The second flush will happen 60 # when the job is about to complete, and we want that one to 61 # fail. 62 res = self.vm.qmp('blockdev-add', 63 **{'node-name': 'target', 64 'driver': iotests.imgfmt, 65 'file': { 66 'driver': 'blkdebug', 67 'image': { 68 'driver': 'file', 69 'filename': target 70 }, 71 'set-state': [{ 72 'event': 'flush_to_disk', 73 'state': 1, 74 'new_state': 2 75 }], 76 'inject-error': [{ 77 'event': 'flush_to_disk', 78 'once': once, 79 'immediately': True, 80 'state': 2 81 }]}}) 82 self.assert_qmp(res, 'return', {}) 83 84 def start_mirror(self) -> None: 85 res = self.vm.qmp('blockdev-mirror', 86 job_id='mirror', 87 device='source', 88 target='target', 89 filter_node_name='mirror-top', 90 sync='full', 91 on_target_error='stop') 92 self.assert_qmp(res, 'return', {}) 93 94 def cancel_mirror_with_error(self) -> None: 95 self.vm.event_wait('BLOCK_JOB_READY') 96 97 # Write something so will not leave the job immediately, but 98 # flush first (which will fail, thanks to blkdebug) 99 res = self.vm.qmp('human-monitor-command', 100 command_line='qemu-io mirror-top "write 0 64k"') 101 self.assert_qmp(res, 'return', '') 102 103 # Drain status change events 104 while self.vm.event_wait('JOB_STATUS_CHANGE', timeout=0.0) is not None: 105 pass 106 107 res = self.vm.qmp('block-job-cancel', device='mirror') 108 self.assert_qmp(res, 'return', {}) 109 110 self.vm.event_wait('BLOCK_JOB_ERROR') 111 112 def test_transient_error(self) -> None: 113 self.add_blockdevs(True) 114 self.start_mirror() 115 self.cancel_mirror_with_error() 116 117 while True: 118 e = self.vm.event_wait('JOB_STATUS_CHANGE') 119 if e['data']['status'] == 'standby': 120 # Transient error, try again 121 self.vm.qmp('block-job-resume', device='mirror') 122 elif e['data']['status'] == 'null': 123 break 124 125 def test_persistent_error(self) -> None: 126 self.add_blockdevs(False) 127 self.start_mirror() 128 self.cancel_mirror_with_error() 129 130 while True: 131 e = self.vm.event_wait('JOB_STATUS_CHANGE') 132 if e['data']['status'] == 'standby': 133 # Persistent error, no point in continuing 134 self.vm.qmp('block-job-cancel', device='mirror', force=True) 135 elif e['data']['status'] == 'null': 136 break 137 138 139if __name__ == '__main__': 140 # LUKS would require special key-secret handling in add_blockdevs() 141 iotests.main(supported_fmts=['generic'], 142 unsupported_fmts=['luks'], 143 supported_protocols=['file']) 144