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 res = self.vm.qmp('blockdev-add', 52 **{'node-name': 'source', 53 'driver': iotests.imgfmt, 54 'file': { 55 'driver': 'file', 56 'filename': source 57 }}) 58 self.assert_qmp(res, 'return', {}) 59 60 # blkdebug notes: 61 # Enter state 2 on the first flush, which happens before the 62 # job enters the READY state. The second flush will happen 63 # when the job is about to complete, and we want that one to 64 # fail. 65 res = self.vm.qmp('blockdev-add', 66 **{'node-name': 'target', 67 'driver': iotests.imgfmt, 68 'file': { 69 'driver': 'blkdebug', 70 'image': { 71 'driver': 'file', 72 'filename': target 73 }, 74 'set-state': [{ 75 'event': 'flush_to_disk', 76 'state': 1, 77 'new_state': 2 78 }], 79 'inject-error': [{ 80 'event': 'flush_to_disk', 81 'once': once, 82 'immediately': True, 83 'state': 2 84 }]}}) 85 self.assert_qmp(res, 'return', {}) 86 87 def start_mirror(self) -> None: 88 res = self.vm.qmp('blockdev-mirror', 89 job_id='mirror', 90 device='source', 91 target='target', 92 filter_node_name='mirror-top', 93 sync='full', 94 on_target_error='stop') 95 self.assert_qmp(res, 'return', {}) 96 97 def cancel_mirror_with_error(self) -> None: 98 self.vm.event_wait('BLOCK_JOB_READY') 99 100 # Write something so will not leave the job immediately, but 101 # flush first (which will fail, thanks to blkdebug) 102 res = self.vm.qmp('human-monitor-command', 103 command_line='qemu-io mirror-top "write -P 2 0 64k"') 104 self.assert_qmp(res, 'return', '') 105 106 # Drain status change events 107 while self.vm.event_wait('JOB_STATUS_CHANGE', timeout=0.0) is not None: 108 pass 109 110 res = self.vm.qmp('block-job-cancel', device='mirror') 111 self.assert_qmp(res, 'return', {}) 112 113 self.vm.event_wait('BLOCK_JOB_ERROR') 114 115 def test_transient_error(self) -> None: 116 self.add_blockdevs(True) 117 self.start_mirror() 118 self.cancel_mirror_with_error() 119 120 while True: 121 e = self.vm.event_wait('JOB_STATUS_CHANGE') 122 if e['data']['status'] == 'standby': 123 # Transient error, try again 124 self.vm.qmp('block-job-resume', device='mirror') 125 elif e['data']['status'] == 'null': 126 break 127 128 def test_persistent_error(self) -> None: 129 self.add_blockdevs(False) 130 self.start_mirror() 131 self.cancel_mirror_with_error() 132 133 while True: 134 e = self.vm.event_wait('JOB_STATUS_CHANGE') 135 if e['data']['status'] == 'standby': 136 # Persistent error, no point in continuing 137 self.vm.qmp('block-job-cancel', device='mirror', force=True) 138 elif e['data']['status'] == 'null': 139 break 140 141 142if __name__ == '__main__': 143 # LUKS would require special key-secret handling in add_blockdevs() 144 iotests.main(supported_fmts=['generic'], 145 unsupported_fmts=['luks'], 146 supported_protocols=['file']) 147