1900e7d41SFiona Ebner#!/usr/bin/env python3 2900e7d41SFiona Ebner# group: rw 3900e7d41SFiona Ebner# 4900e7d41SFiona Ebner# Test for changing mirror copy mode from background to active 5900e7d41SFiona Ebner# 6900e7d41SFiona Ebner# Copyright (C) 2023 Proxmox Server Solutions GmbH 7900e7d41SFiona Ebner# 8900e7d41SFiona Ebner# This program is free software; you can redistribute it and/or modify 9900e7d41SFiona Ebner# it under the terms of the GNU General Public License as published by 10900e7d41SFiona Ebner# the Free Software Foundation; either version 2 of the License, or 11900e7d41SFiona Ebner# (at your option) any later version. 12900e7d41SFiona Ebner# 13900e7d41SFiona Ebner# This program is distributed in the hope that it will be useful, 14900e7d41SFiona Ebner# but WITHOUT ANY WARRANTY; without even the implied warranty of 15900e7d41SFiona Ebner# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16900e7d41SFiona Ebner# GNU General Public License for more details. 17900e7d41SFiona Ebner# 18900e7d41SFiona Ebner# You should have received a copy of the GNU General Public License 19900e7d41SFiona Ebner# along with this program. If not, see <http://www.gnu.org/licenses/>. 20900e7d41SFiona Ebner# 21900e7d41SFiona Ebner 22900e7d41SFiona Ebnerimport os 23900e7d41SFiona Ebnerimport time 24900e7d41SFiona Ebner 25900e7d41SFiona Ebnerimport iotests 26900e7d41SFiona Ebnerfrom iotests import qemu_img, QemuStorageDaemon 27900e7d41SFiona Ebner 28900e7d41SFiona Ebneriops_target = 8 29900e7d41SFiona Ebneriops_source = iops_target * 2 30900e7d41SFiona Ebnerimage_size = 1 * 1024 * 1024 31900e7d41SFiona Ebnersource_img = os.path.join(iotests.test_dir, 'source.' + iotests.imgfmt) 32900e7d41SFiona Ebnertarget_img = os.path.join(iotests.test_dir, 'target.' + iotests.imgfmt) 33900e7d41SFiona Ebnernbd_sock = os.path.join(iotests.sock_dir, 'nbd.sock') 34900e7d41SFiona Ebner 35900e7d41SFiona Ebnerclass TestMirrorChangeCopyMode(iotests.QMPTestCase): 36900e7d41SFiona Ebner 37900e7d41SFiona Ebner def setUp(self): 38900e7d41SFiona Ebner qemu_img('create', '-f', iotests.imgfmt, source_img, str(image_size)) 39900e7d41SFiona Ebner qemu_img('create', '-f', iotests.imgfmt, target_img, str(image_size)) 40900e7d41SFiona Ebner 41900e7d41SFiona Ebner self.qsd = QemuStorageDaemon('--nbd-server', 42900e7d41SFiona Ebner f'addr.type=unix,addr.path={nbd_sock}', 43900e7d41SFiona Ebner qmp=True) 44900e7d41SFiona Ebner 45900e7d41SFiona Ebner self.qsd.cmd('object-add', { 46900e7d41SFiona Ebner 'qom-type': 'throttle-group', 47900e7d41SFiona Ebner 'id': 'thrgr-target', 48900e7d41SFiona Ebner 'limits': { 49900e7d41SFiona Ebner 'iops-write': iops_target, 50900e7d41SFiona Ebner 'iops-write-max': iops_target 51900e7d41SFiona Ebner } 52900e7d41SFiona Ebner }) 53900e7d41SFiona Ebner 54900e7d41SFiona Ebner self.qsd.cmd('blockdev-add', { 55900e7d41SFiona Ebner 'node-name': 'target', 56900e7d41SFiona Ebner 'driver': 'throttle', 57900e7d41SFiona Ebner 'throttle-group': 'thrgr-target', 58900e7d41SFiona Ebner 'file': { 59900e7d41SFiona Ebner 'driver': iotests.imgfmt, 60900e7d41SFiona Ebner 'file': { 61900e7d41SFiona Ebner 'driver': 'file', 62900e7d41SFiona Ebner 'filename': target_img 63900e7d41SFiona Ebner } 64900e7d41SFiona Ebner } 65900e7d41SFiona Ebner }) 66900e7d41SFiona Ebner 67900e7d41SFiona Ebner self.qsd.cmd('block-export-add', { 68900e7d41SFiona Ebner 'id': 'exp0', 69900e7d41SFiona Ebner 'type': 'nbd', 70900e7d41SFiona Ebner 'node-name': 'target', 71900e7d41SFiona Ebner 'writable': True 72900e7d41SFiona Ebner }) 73900e7d41SFiona Ebner 74900e7d41SFiona Ebner self.vm = iotests.VM() 75900e7d41SFiona Ebner self.vm.add_args('-drive', 76900e7d41SFiona Ebner f'file={source_img},if=none,format={iotests.imgfmt},' 77900e7d41SFiona Ebner f'iops_wr={iops_source},' 78900e7d41SFiona Ebner f'iops_wr_max={iops_source},' 79900e7d41SFiona Ebner 'id=source') 80900e7d41SFiona Ebner self.vm.launch() 81900e7d41SFiona Ebner 82900e7d41SFiona Ebner self.vm.cmd('blockdev-add', { 83900e7d41SFiona Ebner 'node-name': 'target', 84900e7d41SFiona Ebner 'driver': 'nbd', 85900e7d41SFiona Ebner 'export': 'target', 86900e7d41SFiona Ebner 'server': { 87900e7d41SFiona Ebner 'type': 'unix', 88900e7d41SFiona Ebner 'path': nbd_sock 89900e7d41SFiona Ebner } 90900e7d41SFiona Ebner }) 91900e7d41SFiona Ebner 92900e7d41SFiona Ebner 93900e7d41SFiona Ebner def tearDown(self): 94900e7d41SFiona Ebner self.vm.shutdown() 95900e7d41SFiona Ebner self.qsd.stop() 96900e7d41SFiona Ebner self.check_qemu_io_errors() 97900e7d41SFiona Ebner self.check_images_identical() 98900e7d41SFiona Ebner os.remove(source_img) 99900e7d41SFiona Ebner os.remove(target_img) 100900e7d41SFiona Ebner 101900e7d41SFiona Ebner # Once the VM is shut down we can parse the log and see if qemu-io ran 102900e7d41SFiona Ebner # without errors. 103900e7d41SFiona Ebner def check_qemu_io_errors(self): 104900e7d41SFiona Ebner self.assertFalse(self.vm.is_running()) 105900e7d41SFiona Ebner log = self.vm.get_log() 106900e7d41SFiona Ebner for line in log.split("\n"): 107900e7d41SFiona Ebner assert not line.startswith("Pattern verification failed") 108900e7d41SFiona Ebner 109900e7d41SFiona Ebner def check_images_identical(self): 110900e7d41SFiona Ebner qemu_img('compare', '-f', iotests.imgfmt, source_img, target_img) 111900e7d41SFiona Ebner 112900e7d41SFiona Ebner def start_mirror(self): 113900e7d41SFiona Ebner self.vm.cmd('blockdev-mirror', 114900e7d41SFiona Ebner job_id='mirror', 115900e7d41SFiona Ebner device='source', 116900e7d41SFiona Ebner target='target', 117900e7d41SFiona Ebner filter_node_name='mirror-top', 118900e7d41SFiona Ebner sync='full', 119900e7d41SFiona Ebner copy_mode='background') 120900e7d41SFiona Ebner 121900e7d41SFiona Ebner def test_background_to_active(self): 122900e7d41SFiona Ebner self.vm.hmp_qemu_io('source', f'write 0 {image_size}') 123900e7d41SFiona Ebner self.vm.hmp_qemu_io('target', f'write 0 {image_size}') 124900e7d41SFiona Ebner 125900e7d41SFiona Ebner self.start_mirror() 126900e7d41SFiona Ebner 127900e7d41SFiona Ebner result = self.vm.cmd('query-block-jobs') 128900e7d41SFiona Ebner assert not result[0]['actively-synced'] 129900e7d41SFiona Ebner 130900e7d41SFiona Ebner self.vm.event_wait('BLOCK_JOB_READY') 131900e7d41SFiona Ebner 132900e7d41SFiona Ebner result = self.vm.cmd('query-block-jobs') 133900e7d41SFiona Ebner assert not result[0]['actively-synced'] 134900e7d41SFiona Ebner 135900e7d41SFiona Ebner # Start some background requests. 136900e7d41SFiona Ebner reqs = 4 * iops_source 137900e7d41SFiona Ebner req_size = image_size // reqs 138900e7d41SFiona Ebner for i in range(0, reqs): 139900e7d41SFiona Ebner req = f'aio_write -P 7 {req_size * i} {req_size}' 140900e7d41SFiona Ebner self.vm.hmp_qemu_io('source', req) 141900e7d41SFiona Ebner 142900e7d41SFiona Ebner # Wait for the first few requests. 143900e7d41SFiona Ebner time.sleep(1) 144900e7d41SFiona Ebner self.vm.qtest(f'clock_step {1 * 1000 * 1000 * 1000}') 145900e7d41SFiona Ebner 146900e7d41SFiona Ebner result = self.vm.cmd('query-block-jobs') 147900e7d41SFiona Ebner # There should've been new requests. 148900e7d41SFiona Ebner assert result[0]['len'] > image_size 149900e7d41SFiona Ebner # To verify later that not all requests were completed at this point. 150900e7d41SFiona Ebner len_before_change = result[0]['len'] 151900e7d41SFiona Ebner 152900e7d41SFiona Ebner # Change the copy mode while requests are happening. 153900e7d41SFiona Ebner self.vm.cmd('block-job-change', 154900e7d41SFiona Ebner id='mirror', 155900e7d41SFiona Ebner type='mirror', 156900e7d41SFiona Ebner copy_mode='write-blocking') 157900e7d41SFiona Ebner 158900e7d41SFiona Ebner # Wait until image is actively synced. 159900e7d41SFiona Ebner while True: 160900e7d41SFiona Ebner time.sleep(0.1) 161900e7d41SFiona Ebner self.vm.qtest(f'clock_step {100 * 1000 * 1000}') 162900e7d41SFiona Ebner result = self.vm.cmd('query-block-jobs') 163900e7d41SFiona Ebner if result[0]['actively-synced']: 164900e7d41SFiona Ebner break 165900e7d41SFiona Ebner 166900e7d41SFiona Ebner # Because of throttling, not all requests should have been completed 167900e7d41SFiona Ebner # above. 168900e7d41SFiona Ebner result = self.vm.cmd('query-block-jobs') 169900e7d41SFiona Ebner assert result[0]['len'] > len_before_change 170900e7d41SFiona Ebner 171900e7d41SFiona Ebner # Issue enough requests for a few seconds only touching the first half 172900e7d41SFiona Ebner # of the image. 173900e7d41SFiona Ebner reqs = 4 * iops_target 174900e7d41SFiona Ebner req_size = image_size // 2 // reqs 175900e7d41SFiona Ebner for i in range(0, reqs): 176900e7d41SFiona Ebner req = f'aio_write -P 19 {req_size * i} {req_size}' 177900e7d41SFiona Ebner self.vm.hmp_qemu_io('source', req) 178900e7d41SFiona Ebner 179900e7d41SFiona Ebner # Now issue a synchronous write in the second half of the image and 180900e7d41SFiona Ebner # immediately verify that it was written to the target too. This would 181900e7d41SFiona Ebner # fail without switching the copy mode. Note that this only produces a 182900e7d41SFiona Ebner # log line and the actual checking happens during tearDown(). 183900e7d41SFiona Ebner req_args = f'-P 37 {3 * (image_size // 4)} {req_size}' 184900e7d41SFiona Ebner self.vm.hmp_qemu_io('source', f'write {req_args}') 185900e7d41SFiona Ebner self.vm.hmp_qemu_io('target', f'read {req_args}') 186900e7d41SFiona Ebner 187900e7d41SFiona Ebner self.vm.cmd('block-job-cancel', device='mirror') 188900e7d41SFiona Ebner while len(self.vm.cmd('query-block-jobs')) > 0: 189900e7d41SFiona Ebner time.sleep(0.1) 190900e7d41SFiona Ebner 191900e7d41SFiona Ebnerif __name__ == '__main__': 192900e7d41SFiona Ebner iotests.main(supported_fmts=['qcow2', 'raw'], 193900e7d41SFiona Ebner supported_protocols=['file']) 194