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