1#!/usr/bin/env python3 2# group: auto backup 3# 4# Copyright (c) 2022 Virtuozzo International GmbH 5# 6# This program is free software; you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation; either version 2 of the License, or 9# (at your option) any later version. 10# 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with this program. If not, see <http://www.gnu.org/licenses/>. 18# 19 20import os 21import re 22 23from qemu.machine import QEMUMachine 24 25import iotests 26from iotests import qemu_img_create, qemu_io 27 28 29temp_img = os.path.join(iotests.test_dir, 'temp') 30source_img = os.path.join(iotests.test_dir, 'source') 31size = '1M' 32 33 34class TestCbwError(iotests.QMPTestCase): 35 def tearDown(self): 36 self.vm.shutdown() 37 os.remove(temp_img) 38 os.remove(source_img) 39 40 def setUp(self): 41 qemu_img_create('-f', iotests.imgfmt, source_img, size) 42 qemu_img_create('-f', iotests.imgfmt, temp_img, size) 43 qemu_io('-c', 'write 0 1M', source_img) 44 45 opts = ['-nodefaults', '-display', 'none', '-machine', 'none'] 46 self.vm = QEMUMachine(iotests.qemu_prog, opts, 47 base_temp_dir=iotests.test_dir) 48 self.vm.launch() 49 50 def do_cbw_error(self, on_cbw_error): 51 self.vm.cmd('blockdev-add', { 52 'node-name': 'cbw', 53 'driver': 'copy-before-write', 54 'on-cbw-error': on_cbw_error, 55 'file': { 56 'driver': iotests.imgfmt, 57 'file': { 58 'driver': 'file', 59 'filename': source_img, 60 } 61 }, 62 'target': { 63 'driver': iotests.imgfmt, 64 'file': { 65 'driver': 'blkdebug', 66 'image': { 67 'driver': 'file', 68 'filename': temp_img 69 }, 70 'inject-error': [ 71 { 72 'event': 'write_aio', 73 'errno': 5, 74 'immediately': False, 75 'once': True 76 } 77 ] 78 } 79 } 80 }) 81 82 self.vm.cmd('blockdev-add', { 83 'node-name': 'access', 84 'driver': 'snapshot-access', 85 'file': 'cbw' 86 }) 87 88 result = self.vm.qmp('human-monitor-command', 89 command_line='qemu-io cbw "write 0 1M"') 90 self.assert_qmp(result, 'return', '') 91 92 result = self.vm.qmp('human-monitor-command', 93 command_line='qemu-io access "read 0 1M"') 94 self.assert_qmp(result, 'return', '') 95 96 self.vm.shutdown() 97 log = self.vm.get_log() 98 log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) 99 log = re.sub(r'\[I \+\d+\.\d+\] CLOSED\n?$', '', log) 100 log = iotests.filter_qemu_io(log) 101 return log 102 103 def test_break_snapshot_on_cbw_error(self): 104 """break-snapshot behavior: 105 Guest write succeed, but further snapshot-read fails, as snapshot is 106 broken. 107 """ 108 log = self.do_cbw_error('break-snapshot') 109 110 self.assertEqual(log, """\ 111wrote 1048576/1048576 bytes at offset 0 1121 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) 113read failed: Permission denied 114""") 115 116 def test_break_guest_write_on_cbw_error(self): 117 """break-guest-write behavior: 118 Guest write fails, but snapshot-access continues working and further 119 snapshot-read succeeds. 120 """ 121 log = self.do_cbw_error('break-guest-write') 122 123 self.assertEqual(log, """\ 124write failed: Input/output error 125read 1048576/1048576 bytes at offset 0 1261 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) 127""") 128 129 def do_cbw_timeout(self, on_cbw_error): 130 self.vm.cmd('object-add', { 131 'qom-type': 'throttle-group', 132 'id': 'group0', 133 'limits': {'bps-write': 300 * 1024} 134 }) 135 136 self.vm.cmd('blockdev-add', { 137 'node-name': 'cbw', 138 'driver': 'copy-before-write', 139 'on-cbw-error': on_cbw_error, 140 'cbw-timeout': 1, 141 'file': { 142 'driver': iotests.imgfmt, 143 'file': { 144 'driver': 'file', 145 'filename': source_img, 146 } 147 }, 148 'target': { 149 'driver': 'throttle', 150 'throttle-group': 'group0', 151 'file': { 152 'driver': 'qcow2', 153 'file': { 154 'driver': 'file', 155 'filename': temp_img 156 } 157 } 158 } 159 }) 160 161 self.vm.cmd('blockdev-add', { 162 'node-name': 'access', 163 'driver': 'snapshot-access', 164 'file': 'cbw' 165 }) 166 167 result = self.vm.qmp('human-monitor-command', 168 command_line='qemu-io cbw "write 0 512K"') 169 self.assert_qmp(result, 'return', '') 170 171 # We need second write to trigger throttling 172 result = self.vm.qmp('human-monitor-command', 173 command_line='qemu-io cbw "write 512K 512K"') 174 self.assert_qmp(result, 'return', '') 175 176 result = self.vm.qmp('human-monitor-command', 177 command_line='qemu-io access "read 0 1M"') 178 self.assert_qmp(result, 'return', '') 179 180 self.vm.shutdown() 181 log = self.vm.get_log() 182 log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) 183 log = re.sub(r'\[I \+\d+\.\d+\] CLOSED\n?$', '', log) 184 log = iotests.filter_qemu_io(log) 185 return log 186 187 def test_timeout_break_guest(self): 188 log = self.do_cbw_timeout('break-guest-write') 189 # macOS and FreeBSD tend to represent ETIMEDOUT as 190 # "Operation timed out", when Linux prefer 191 # "Connection timed out" 192 log = log.replace('Operation timed out', 193 'Connection timed out') 194 self.assertEqual(log, """\ 195wrote 524288/524288 bytes at offset 0 196512 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) 197write failed: Connection timed out 198read 1048576/1048576 bytes at offset 0 1991 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) 200""") 201 202 def test_timeout_break_snapshot(self): 203 log = self.do_cbw_timeout('break-snapshot') 204 self.assertEqual(log, """\ 205wrote 524288/524288 bytes at offset 0 206512 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) 207wrote 524288/524288 bytes at offset 524288 208512 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) 209read failed: Permission denied 210""") 211 212 213if __name__ == '__main__': 214 iotests.main(supported_fmts=['qcow2'], 215 supported_protocols=['file'], 216 required_fmts=['copy-before-write']) 217