1dd3e97dfSVladimir Sementsov-Ogievskiy#!/usr/bin/env python3
2dd3e97dfSVladimir Sementsov-Ogievskiy# group: auto backup
3dd3e97dfSVladimir Sementsov-Ogievskiy#
4dd3e97dfSVladimir Sementsov-Ogievskiy# Copyright (c) 2022 Virtuozzo International GmbH
5dd3e97dfSVladimir Sementsov-Ogievskiy#
6dd3e97dfSVladimir Sementsov-Ogievskiy# This program is free software; you can redistribute it and/or modify
7dd3e97dfSVladimir Sementsov-Ogievskiy# it under the terms of the GNU General Public License as published by
8dd3e97dfSVladimir Sementsov-Ogievskiy# the Free Software Foundation; either version 2 of the License, or
9dd3e97dfSVladimir Sementsov-Ogievskiy# (at your option) any later version.
10dd3e97dfSVladimir Sementsov-Ogievskiy#
11dd3e97dfSVladimir Sementsov-Ogievskiy# This program is distributed in the hope that it will be useful,
12dd3e97dfSVladimir Sementsov-Ogievskiy# but WITHOUT ANY WARRANTY; without even the implied warranty of
13dd3e97dfSVladimir Sementsov-Ogievskiy# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14dd3e97dfSVladimir Sementsov-Ogievskiy# GNU General Public License for more details.
15dd3e97dfSVladimir Sementsov-Ogievskiy#
16dd3e97dfSVladimir Sementsov-Ogievskiy# You should have received a copy of the GNU General Public License
17dd3e97dfSVladimir Sementsov-Ogievskiy# along with this program.  If not, see <http://www.gnu.org/licenses/>.
18dd3e97dfSVladimir Sementsov-Ogievskiy#
19dd3e97dfSVladimir Sementsov-Ogievskiy
20dd3e97dfSVladimir Sementsov-Ogievskiyimport os
21dd3e97dfSVladimir Sementsov-Ogievskiyimport re
22dd3e97dfSVladimir Sementsov-Ogievskiy
23dd3e97dfSVladimir Sementsov-Ogievskiyfrom qemu.machine import QEMUMachine
24dd3e97dfSVladimir Sementsov-Ogievskiy
25dd3e97dfSVladimir Sementsov-Ogievskiyimport iotests
26dd3e97dfSVladimir Sementsov-Ogievskiyfrom iotests import qemu_img_create, qemu_io
27dd3e97dfSVladimir Sementsov-Ogievskiy
28dd3e97dfSVladimir Sementsov-Ogievskiy
29dd3e97dfSVladimir Sementsov-Ogievskiytemp_img = os.path.join(iotests.test_dir, 'temp')
30dd3e97dfSVladimir Sementsov-Ogievskiysource_img = os.path.join(iotests.test_dir, 'source')
31dd3e97dfSVladimir Sementsov-Ogievskiysize = '1M'
32dd3e97dfSVladimir Sementsov-Ogievskiy
33dd3e97dfSVladimir Sementsov-Ogievskiy
34dd3e97dfSVladimir Sementsov-Ogievskiyclass TestCbwError(iotests.QMPTestCase):
35dd3e97dfSVladimir Sementsov-Ogievskiy    def tearDown(self):
36dd3e97dfSVladimir Sementsov-Ogievskiy        self.vm.shutdown()
37dd3e97dfSVladimir Sementsov-Ogievskiy        os.remove(temp_img)
38dd3e97dfSVladimir Sementsov-Ogievskiy        os.remove(source_img)
39dd3e97dfSVladimir Sementsov-Ogievskiy
40dd3e97dfSVladimir Sementsov-Ogievskiy    def setUp(self):
41dd3e97dfSVladimir Sementsov-Ogievskiy        qemu_img_create('-f', iotests.imgfmt, source_img, size)
42dd3e97dfSVladimir Sementsov-Ogievskiy        qemu_img_create('-f', iotests.imgfmt, temp_img, size)
43dd3e97dfSVladimir Sementsov-Ogievskiy        qemu_io('-c', 'write 0 1M', source_img)
44dd3e97dfSVladimir Sementsov-Ogievskiy
45dd3e97dfSVladimir Sementsov-Ogievskiy        opts = ['-nodefaults', '-display', 'none', '-machine', 'none']
46dd3e97dfSVladimir Sementsov-Ogievskiy        self.vm = QEMUMachine(iotests.qemu_prog, opts,
4746d4747aSJohn Snow                              base_temp_dir=iotests.test_dir)
48dd3e97dfSVladimir Sementsov-Ogievskiy        self.vm.launch()
49dd3e97dfSVladimir Sementsov-Ogievskiy
50dd3e97dfSVladimir Sementsov-Ogievskiy    def do_cbw_error(self, on_cbw_error):
51b6aed193SVladimir Sementsov-Ogievskiy        self.vm.cmd('blockdev-add', {
52dd3e97dfSVladimir Sementsov-Ogievskiy            'node-name': 'cbw',
53dd3e97dfSVladimir Sementsov-Ogievskiy            'driver': 'copy-before-write',
54dd3e97dfSVladimir Sementsov-Ogievskiy            'on-cbw-error': on_cbw_error,
55dd3e97dfSVladimir Sementsov-Ogievskiy            'file': {
56dd3e97dfSVladimir Sementsov-Ogievskiy                'driver': iotests.imgfmt,
57dd3e97dfSVladimir Sementsov-Ogievskiy                'file': {
58dd3e97dfSVladimir Sementsov-Ogievskiy                    'driver': 'file',
59dd3e97dfSVladimir Sementsov-Ogievskiy                    'filename': source_img,
60dd3e97dfSVladimir Sementsov-Ogievskiy                }
61dd3e97dfSVladimir Sementsov-Ogievskiy            },
62dd3e97dfSVladimir Sementsov-Ogievskiy            'target': {
63dd3e97dfSVladimir Sementsov-Ogievskiy                'driver': iotests.imgfmt,
64dd3e97dfSVladimir Sementsov-Ogievskiy                'file': {
65dd3e97dfSVladimir Sementsov-Ogievskiy                    'driver': 'blkdebug',
66dd3e97dfSVladimir Sementsov-Ogievskiy                    'image': {
67dd3e97dfSVladimir Sementsov-Ogievskiy                        'driver': 'file',
68dd3e97dfSVladimir Sementsov-Ogievskiy                        'filename': temp_img
69dd3e97dfSVladimir Sementsov-Ogievskiy                    },
70dd3e97dfSVladimir Sementsov-Ogievskiy                    'inject-error': [
71dd3e97dfSVladimir Sementsov-Ogievskiy                        {
72dd3e97dfSVladimir Sementsov-Ogievskiy                            'event': 'write_aio',
73dd3e97dfSVladimir Sementsov-Ogievskiy                            'errno': 5,
74dd3e97dfSVladimir Sementsov-Ogievskiy                            'immediately': False,
75dd3e97dfSVladimir Sementsov-Ogievskiy                            'once': True
76dd3e97dfSVladimir Sementsov-Ogievskiy                        }
77dd3e97dfSVladimir Sementsov-Ogievskiy                    ]
78dd3e97dfSVladimir Sementsov-Ogievskiy                }
79dd3e97dfSVladimir Sementsov-Ogievskiy            }
80dd3e97dfSVladimir Sementsov-Ogievskiy        })
81dd3e97dfSVladimir Sementsov-Ogievskiy
82b6aed193SVladimir Sementsov-Ogievskiy        self.vm.cmd('blockdev-add', {
83dd3e97dfSVladimir Sementsov-Ogievskiy            'node-name': 'access',
84dd3e97dfSVladimir Sementsov-Ogievskiy            'driver': 'snapshot-access',
85dd3e97dfSVladimir Sementsov-Ogievskiy            'file': 'cbw'
86dd3e97dfSVladimir Sementsov-Ogievskiy        })
87dd3e97dfSVladimir Sementsov-Ogievskiy
88dd3e97dfSVladimir Sementsov-Ogievskiy        result = self.vm.qmp('human-monitor-command',
89dd3e97dfSVladimir Sementsov-Ogievskiy                             command_line='qemu-io cbw "write 0 1M"')
90dd3e97dfSVladimir Sementsov-Ogievskiy        self.assert_qmp(result, 'return', '')
91dd3e97dfSVladimir Sementsov-Ogievskiy
92dd3e97dfSVladimir Sementsov-Ogievskiy        result = self.vm.qmp('human-monitor-command',
93dd3e97dfSVladimir Sementsov-Ogievskiy                             command_line='qemu-io access "read 0 1M"')
94dd3e97dfSVladimir Sementsov-Ogievskiy        self.assert_qmp(result, 'return', '')
95dd3e97dfSVladimir Sementsov-Ogievskiy
96dd3e97dfSVladimir Sementsov-Ogievskiy        self.vm.shutdown()
97dd3e97dfSVladimir Sementsov-Ogievskiy        log = self.vm.get_log()
98dd3e97dfSVladimir Sementsov-Ogievskiy        log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log)
99dd3e97dfSVladimir Sementsov-Ogievskiy        log = re.sub(r'\[I \+\d+\.\d+\] CLOSED\n?$', '', log)
100dd3e97dfSVladimir Sementsov-Ogievskiy        log = iotests.filter_qemu_io(log)
101dd3e97dfSVladimir Sementsov-Ogievskiy        return log
102dd3e97dfSVladimir Sementsov-Ogievskiy
103dd3e97dfSVladimir Sementsov-Ogievskiy    def test_break_snapshot_on_cbw_error(self):
104dd3e97dfSVladimir Sementsov-Ogievskiy        """break-snapshot behavior:
105dd3e97dfSVladimir Sementsov-Ogievskiy        Guest write succeed, but further snapshot-read fails, as snapshot is
106dd3e97dfSVladimir Sementsov-Ogievskiy        broken.
107dd3e97dfSVladimir Sementsov-Ogievskiy        """
108dd3e97dfSVladimir Sementsov-Ogievskiy        log = self.do_cbw_error('break-snapshot')
109dd3e97dfSVladimir Sementsov-Ogievskiy
110dd3e97dfSVladimir Sementsov-Ogievskiy        self.assertEqual(log, """\
111dd3e97dfSVladimir Sementsov-Ogievskiywrote 1048576/1048576 bytes at offset 0
112dd3e97dfSVladimir Sementsov-Ogievskiy1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
113dd3e97dfSVladimir Sementsov-Ogievskiyread failed: Permission denied
114dd3e97dfSVladimir Sementsov-Ogievskiy""")
115dd3e97dfSVladimir Sementsov-Ogievskiy
116dd3e97dfSVladimir Sementsov-Ogievskiy    def test_break_guest_write_on_cbw_error(self):
117dd3e97dfSVladimir Sementsov-Ogievskiy        """break-guest-write behavior:
118dd3e97dfSVladimir Sementsov-Ogievskiy        Guest write fails, but snapshot-access continues working and further
119dd3e97dfSVladimir Sementsov-Ogievskiy        snapshot-read succeeds.
120dd3e97dfSVladimir Sementsov-Ogievskiy        """
121dd3e97dfSVladimir Sementsov-Ogievskiy        log = self.do_cbw_error('break-guest-write')
122dd3e97dfSVladimir Sementsov-Ogievskiy
123dd3e97dfSVladimir Sementsov-Ogievskiy        self.assertEqual(log, """\
124dd3e97dfSVladimir Sementsov-Ogievskiywrite failed: Input/output error
125dd3e97dfSVladimir Sementsov-Ogievskiyread 1048576/1048576 bytes at offset 0
126dd3e97dfSVladimir Sementsov-Ogievskiy1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
127dd3e97dfSVladimir Sementsov-Ogievskiy""")
128dd3e97dfSVladimir Sementsov-Ogievskiy
1299d05a87bSVladimir Sementsov-Ogievskiy    def do_cbw_timeout(self, on_cbw_error):
130b6aed193SVladimir Sementsov-Ogievskiy        self.vm.cmd('object-add', {
1319d05a87bSVladimir Sementsov-Ogievskiy            'qom-type': 'throttle-group',
1329d05a87bSVladimir Sementsov-Ogievskiy            'id': 'group0',
1339d05a87bSVladimir Sementsov-Ogievskiy            'limits': {'bps-write': 300 * 1024}
1349d05a87bSVladimir Sementsov-Ogievskiy        })
1359d05a87bSVladimir Sementsov-Ogievskiy
136b6aed193SVladimir Sementsov-Ogievskiy        self.vm.cmd('blockdev-add', {
1379d05a87bSVladimir Sementsov-Ogievskiy            'node-name': 'cbw',
1389d05a87bSVladimir Sementsov-Ogievskiy            'driver': 'copy-before-write',
1399d05a87bSVladimir Sementsov-Ogievskiy            'on-cbw-error': on_cbw_error,
1409d05a87bSVladimir Sementsov-Ogievskiy            'cbw-timeout': 1,
1419d05a87bSVladimir Sementsov-Ogievskiy            'file': {
1429d05a87bSVladimir Sementsov-Ogievskiy                'driver': iotests.imgfmt,
1439d05a87bSVladimir Sementsov-Ogievskiy                'file': {
1449d05a87bSVladimir Sementsov-Ogievskiy                    'driver': 'file',
1459d05a87bSVladimir Sementsov-Ogievskiy                    'filename': source_img,
1469d05a87bSVladimir Sementsov-Ogievskiy                }
1479d05a87bSVladimir Sementsov-Ogievskiy            },
1489d05a87bSVladimir Sementsov-Ogievskiy            'target': {
1499d05a87bSVladimir Sementsov-Ogievskiy                'driver': 'throttle',
1509d05a87bSVladimir Sementsov-Ogievskiy                'throttle-group': 'group0',
1519d05a87bSVladimir Sementsov-Ogievskiy                'file': {
1529d05a87bSVladimir Sementsov-Ogievskiy                    'driver': 'qcow2',
1539d05a87bSVladimir Sementsov-Ogievskiy                    'file': {
1549d05a87bSVladimir Sementsov-Ogievskiy                        'driver': 'file',
1559d05a87bSVladimir Sementsov-Ogievskiy                        'filename': temp_img
1569d05a87bSVladimir Sementsov-Ogievskiy                    }
1579d05a87bSVladimir Sementsov-Ogievskiy                }
1589d05a87bSVladimir Sementsov-Ogievskiy            }
1599d05a87bSVladimir Sementsov-Ogievskiy        })
1609d05a87bSVladimir Sementsov-Ogievskiy
161b6aed193SVladimir Sementsov-Ogievskiy        self.vm.cmd('blockdev-add', {
1629d05a87bSVladimir Sementsov-Ogievskiy            'node-name': 'access',
1639d05a87bSVladimir Sementsov-Ogievskiy            'driver': 'snapshot-access',
1649d05a87bSVladimir Sementsov-Ogievskiy            'file': 'cbw'
1659d05a87bSVladimir Sementsov-Ogievskiy        })
1669d05a87bSVladimir Sementsov-Ogievskiy
1679d05a87bSVladimir Sementsov-Ogievskiy        result = self.vm.qmp('human-monitor-command',
1689d05a87bSVladimir Sementsov-Ogievskiy                             command_line='qemu-io cbw "write 0 512K"')
1699d05a87bSVladimir Sementsov-Ogievskiy        self.assert_qmp(result, 'return', '')
1709d05a87bSVladimir Sementsov-Ogievskiy
1719d05a87bSVladimir Sementsov-Ogievskiy        # We need second write to trigger throttling
1729d05a87bSVladimir Sementsov-Ogievskiy        result = self.vm.qmp('human-monitor-command',
1739d05a87bSVladimir Sementsov-Ogievskiy                             command_line='qemu-io cbw "write 512K 512K"')
1749d05a87bSVladimir Sementsov-Ogievskiy        self.assert_qmp(result, 'return', '')
1759d05a87bSVladimir Sementsov-Ogievskiy
1769d05a87bSVladimir Sementsov-Ogievskiy        result = self.vm.qmp('human-monitor-command',
1779d05a87bSVladimir Sementsov-Ogievskiy                             command_line='qemu-io access "read 0 1M"')
1789d05a87bSVladimir Sementsov-Ogievskiy        self.assert_qmp(result, 'return', '')
1799d05a87bSVladimir Sementsov-Ogievskiy
1809d05a87bSVladimir Sementsov-Ogievskiy        self.vm.shutdown()
1819d05a87bSVladimir Sementsov-Ogievskiy        log = self.vm.get_log()
1829d05a87bSVladimir Sementsov-Ogievskiy        log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log)
1839d05a87bSVladimir Sementsov-Ogievskiy        log = re.sub(r'\[I \+\d+\.\d+\] CLOSED\n?$', '', log)
1849d05a87bSVladimir Sementsov-Ogievskiy        log = iotests.filter_qemu_io(log)
1859d05a87bSVladimir Sementsov-Ogievskiy        return log
1869d05a87bSVladimir Sementsov-Ogievskiy
1879d05a87bSVladimir Sementsov-Ogievskiy    def test_timeout_break_guest(self):
1889d05a87bSVladimir Sementsov-Ogievskiy        log = self.do_cbw_timeout('break-guest-write')
18905b47eecSVladimir Sementsov-Ogievskiy        # macOS and FreeBSD tend to represent ETIMEDOUT as
19005b47eecSVladimir Sementsov-Ogievskiy        # "Operation timed out", when Linux prefer
19105b47eecSVladimir Sementsov-Ogievskiy        # "Connection timed out"
19205b47eecSVladimir Sementsov-Ogievskiy        log = log.replace('Operation timed out',
19305b47eecSVladimir Sementsov-Ogievskiy                          'Connection timed out')
1949d05a87bSVladimir Sementsov-Ogievskiy        self.assertEqual(log, """\
1959d05a87bSVladimir Sementsov-Ogievskiywrote 524288/524288 bytes at offset 0
1969d05a87bSVladimir Sementsov-Ogievskiy512 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
1979d05a87bSVladimir Sementsov-Ogievskiywrite failed: Connection timed out
1989d05a87bSVladimir Sementsov-Ogievskiyread 1048576/1048576 bytes at offset 0
1999d05a87bSVladimir Sementsov-Ogievskiy1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
2009d05a87bSVladimir Sementsov-Ogievskiy""")
2019d05a87bSVladimir Sementsov-Ogievskiy
2029d05a87bSVladimir Sementsov-Ogievskiy    def test_timeout_break_snapshot(self):
2039d05a87bSVladimir Sementsov-Ogievskiy        log = self.do_cbw_timeout('break-snapshot')
2049d05a87bSVladimir Sementsov-Ogievskiy        self.assertEqual(log, """\
2059d05a87bSVladimir Sementsov-Ogievskiywrote 524288/524288 bytes at offset 0
2069d05a87bSVladimir Sementsov-Ogievskiy512 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
2079d05a87bSVladimir Sementsov-Ogievskiywrote 524288/524288 bytes at offset 524288
2089d05a87bSVladimir Sementsov-Ogievskiy512 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
2099d05a87bSVladimir Sementsov-Ogievskiyread failed: Permission denied
2109d05a87bSVladimir Sementsov-Ogievskiy""")
2119d05a87bSVladimir Sementsov-Ogievskiy
212dd3e97dfSVladimir Sementsov-Ogievskiy
213dd3e97dfSVladimir Sementsov-Ogievskiyif __name__ == '__main__':
214dd3e97dfSVladimir Sementsov-Ogievskiy    iotests.main(supported_fmts=['qcow2'],
2159548cbefSVladimir Sementsov-Ogievskiy                 supported_protocols=['file'],
2169548cbefSVladimir Sementsov-Ogievskiy                 required_fmts=['copy-before-write'])
217