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 sock_dir=iotests.sock_dir) 49 self.vm.launch() 50 51 def do_cbw_error(self, on_cbw_error): 52 result = self.vm.qmp('blockdev-add', { 53 'node-name': 'cbw', 54 'driver': 'copy-before-write', 55 'on-cbw-error': on_cbw_error, 56 'file': { 57 'driver': iotests.imgfmt, 58 'file': { 59 'driver': 'file', 60 'filename': source_img, 61 } 62 }, 63 'target': { 64 'driver': iotests.imgfmt, 65 'file': { 66 'driver': 'blkdebug', 67 'image': { 68 'driver': 'file', 69 'filename': temp_img 70 }, 71 'inject-error': [ 72 { 73 'event': 'write_aio', 74 'errno': 5, 75 'immediately': False, 76 'once': True 77 } 78 ] 79 } 80 } 81 }) 82 self.assert_qmp(result, 'return', {}) 83 84 result = self.vm.qmp('blockdev-add', { 85 'node-name': 'access', 86 'driver': 'snapshot-access', 87 'file': 'cbw' 88 }) 89 self.assert_qmp(result, 'return', {}) 90 91 result = self.vm.qmp('human-monitor-command', 92 command_line='qemu-io cbw "write 0 1M"') 93 self.assert_qmp(result, 'return', '') 94 95 result = self.vm.qmp('human-monitor-command', 96 command_line='qemu-io access "read 0 1M"') 97 self.assert_qmp(result, 'return', '') 98 99 self.vm.shutdown() 100 log = self.vm.get_log() 101 log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) 102 log = re.sub(r'\[I \+\d+\.\d+\] CLOSED\n?$', '', log) 103 log = iotests.filter_qemu_io(log) 104 return log 105 106 def test_break_snapshot_on_cbw_error(self): 107 """break-snapshot behavior: 108 Guest write succeed, but further snapshot-read fails, as snapshot is 109 broken. 110 """ 111 log = self.do_cbw_error('break-snapshot') 112 113 self.assertEqual(log, """\ 114wrote 1048576/1048576 bytes at offset 0 1151 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) 116read failed: Permission denied 117""") 118 119 def test_break_guest_write_on_cbw_error(self): 120 """break-guest-write behavior: 121 Guest write fails, but snapshot-access continues working and further 122 snapshot-read succeeds. 123 """ 124 log = self.do_cbw_error('break-guest-write') 125 126 self.assertEqual(log, """\ 127write failed: Input/output error 128read 1048576/1048576 bytes at offset 0 1291 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) 130""") 131 132 def do_cbw_timeout(self, on_cbw_error): 133 result = self.vm.qmp('object-add', { 134 'qom-type': 'throttle-group', 135 'id': 'group0', 136 'limits': {'bps-write': 300 * 1024} 137 }) 138 self.assert_qmp(result, 'return', {}) 139 140 result = self.vm.qmp('blockdev-add', { 141 'node-name': 'cbw', 142 'driver': 'copy-before-write', 143 'on-cbw-error': on_cbw_error, 144 'cbw-timeout': 1, 145 'file': { 146 'driver': iotests.imgfmt, 147 'file': { 148 'driver': 'file', 149 'filename': source_img, 150 } 151 }, 152 'target': { 153 'driver': 'throttle', 154 'throttle-group': 'group0', 155 'file': { 156 'driver': 'qcow2', 157 'file': { 158 'driver': 'file', 159 'filename': temp_img 160 } 161 } 162 } 163 }) 164 self.assert_qmp(result, 'return', {}) 165 166 result = self.vm.qmp('blockdev-add', { 167 'node-name': 'access', 168 'driver': 'snapshot-access', 169 'file': 'cbw' 170 }) 171 self.assert_qmp(result, 'return', {}) 172 173 result = self.vm.qmp('human-monitor-command', 174 command_line='qemu-io cbw "write 0 512K"') 175 self.assert_qmp(result, 'return', '') 176 177 # We need second write to trigger throttling 178 result = self.vm.qmp('human-monitor-command', 179 command_line='qemu-io cbw "write 512K 512K"') 180 self.assert_qmp(result, 'return', '') 181 182 result = self.vm.qmp('human-monitor-command', 183 command_line='qemu-io access "read 0 1M"') 184 self.assert_qmp(result, 'return', '') 185 186 self.vm.shutdown() 187 log = self.vm.get_log() 188 log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) 189 log = re.sub(r'\[I \+\d+\.\d+\] CLOSED\n?$', '', log) 190 log = iotests.filter_qemu_io(log) 191 return log 192 193 def test_timeout_break_guest(self): 194 log = self.do_cbw_timeout('break-guest-write') 195 # macOS and FreeBSD tend to represent ETIMEDOUT as 196 # "Operation timed out", when Linux prefer 197 # "Connection timed out" 198 log = log.replace('Operation timed out', 199 'Connection timed out') 200 self.assertEqual(log, """\ 201wrote 524288/524288 bytes at offset 0 202512 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) 203write failed: Connection timed out 204read 1048576/1048576 bytes at offset 0 2051 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) 206""") 207 208 def test_timeout_break_snapshot(self): 209 log = self.do_cbw_timeout('break-snapshot') 210 self.assertEqual(log, """\ 211wrote 524288/524288 bytes at offset 0 212512 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) 213wrote 524288/524288 bytes at offset 524288 214512 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) 215read failed: Permission denied 216""") 217 218 219if __name__ == '__main__': 220 iotests.main(supported_fmts=['qcow2'], 221 supported_protocols=['file'], 222 required_fmts=['copy-before-write']) 223