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 iotests.qemu_img_create('-f', iotests.imgfmt, source, str(image_size)) 35 iotests.qemu_img_create('-f', iotests.imgfmt, target, str(image_size)) 36 37 # Ensure that mirror will copy something before READY so the 38 # target format layer will forward the pre-READY flush to its 39 # file child 40 iotests.qemu_io('-c', 'write -P 1 0 64k', source) 41 42 self.vm = iotests.VM() 43 self.vm.launch() 44 45 def tearDown(self) -> None: 46 self.vm.shutdown() 47 os.remove(source) 48 os.remove(target) 49 50 def add_blockdevs(self, once: bool) -> None: 51 self.vm.cmd('blockdev-add', 52 {'node-name': 'source', 53 'driver': iotests.imgfmt, 54 'file': { 55 'driver': 'file', 56 'filename': source 57 }}) 58 59 # blkdebug notes: 60 # Enter state 2 on the first flush, which happens before the 61 # job enters the READY state. The second flush will happen 62 # when the job is about to complete, and we want that one to 63 # fail. 64 self.vm.cmd('blockdev-add', 65 {'node-name': 'target', 66 'driver': iotests.imgfmt, 67 'file': { 68 'driver': 'blkdebug', 69 'image': { 70 'driver': 'file', 71 'filename': target 72 }, 73 'set-state': [{ 74 'event': 'flush_to_disk', 75 'state': 1, 76 'new_state': 2 77 }], 78 'inject-error': [{ 79 'event': 'flush_to_disk', 80 'once': once, 81 'immediately': True, 82 'state': 2 83 }]}}) 84 85 def start_mirror(self) -> None: 86 self.vm.cmd('blockdev-mirror', 87 job_id='mirror', 88 device='source', 89 target='target', 90 filter_node_name='mirror-top', 91 sync='full', 92 on_target_error='stop') 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 -P 2 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 self.vm.cmd('block-job-cancel', device='mirror') 108 109 self.vm.event_wait('BLOCK_JOB_ERROR') 110 111 def test_transient_error(self) -> None: 112 self.add_blockdevs(True) 113 self.start_mirror() 114 self.cancel_mirror_with_error() 115 116 while True: 117 e = self.vm.event_wait('JOB_STATUS_CHANGE') 118 if e['data']['status'] == 'standby': 119 # Transient error, try again 120 self.vm.qmp('block-job-resume', device='mirror') 121 elif e['data']['status'] == 'null': 122 break 123 124 def test_persistent_error(self) -> None: 125 self.add_blockdevs(False) 126 self.start_mirror() 127 self.cancel_mirror_with_error() 128 129 while True: 130 e = self.vm.event_wait('JOB_STATUS_CHANGE') 131 if e['data']['status'] == 'standby': 132 # Persistent error, no point in continuing 133 self.vm.qmp('block-job-cancel', device='mirror', force=True) 134 elif e['data']['status'] == 'null': 135 break 136 137 138if __name__ == '__main__': 139 # LUKS would require special key-secret handling in add_blockdevs() 140 iotests.main(supported_fmts=['generic'], 141 unsupported_fmts=['luks'], 142 supported_protocols=['file']) 143