Skip to content

Commit 900e7d4

Browse files
foxmoxkevmw
authored andcommitted
iotests: add test for changing mirror's copy_mode
One part of the test is using a throttled source to ensure that there are no obvious issues when changing the copy_mode while there are ongoing requests (source and target images are compared at the very end). The other part of the test is using a throttled target to ensure that the change to active mode actually happened. This is done by hitting the throttling limit, issuing a synchronous write and then immediately verifying the target side. QSD is used, because otherwise, a synchronous write would hang there. Signed-off-by: Fiona Ebner <f.ebner@proxmox.com> Message-ID: <20231031135431.393137-11-f.ebner@proxmox.com> Reviewed-by: Kevin Wolf <kwolf@redhat.com> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
1 parent 76cb2f2 commit 900e7d4

File tree

2 files changed

+198
-0
lines changed

2 files changed

+198
-0
lines changed
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
#!/usr/bin/env python3
2+
# group: rw
3+
#
4+
# Test for changing mirror copy mode from background to active
5+
#
6+
# Copyright (C) 2023 Proxmox Server Solutions GmbH
7+
#
8+
# This program is free software; you can redistribute it and/or modify
9+
# it under the terms of the GNU General Public License as published by
10+
# the Free Software Foundation; either version 2 of the License, or
11+
# (at your option) any later version.
12+
#
13+
# This program is distributed in the hope that it will be useful,
14+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
# GNU General Public License for more details.
17+
#
18+
# You should have received a copy of the GNU General Public License
19+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
20+
#
21+
22+
import os
23+
import time
24+
25+
import iotests
26+
from iotests import qemu_img, QemuStorageDaemon
27+
28+
iops_target = 8
29+
iops_source = iops_target * 2
30+
image_size = 1 * 1024 * 1024
31+
source_img = os.path.join(iotests.test_dir, 'source.' + iotests.imgfmt)
32+
target_img = os.path.join(iotests.test_dir, 'target.' + iotests.imgfmt)
33+
nbd_sock = os.path.join(iotests.sock_dir, 'nbd.sock')
34+
35+
class TestMirrorChangeCopyMode(iotests.QMPTestCase):
36+
37+
def setUp(self):
38+
qemu_img('create', '-f', iotests.imgfmt, source_img, str(image_size))
39+
qemu_img('create', '-f', iotests.imgfmt, target_img, str(image_size))
40+
41+
self.qsd = QemuStorageDaemon('--nbd-server',
42+
f'addr.type=unix,addr.path={nbd_sock}',
43+
qmp=True)
44+
45+
self.qsd.cmd('object-add', {
46+
'qom-type': 'throttle-group',
47+
'id': 'thrgr-target',
48+
'limits': {
49+
'iops-write': iops_target,
50+
'iops-write-max': iops_target
51+
}
52+
})
53+
54+
self.qsd.cmd('blockdev-add', {
55+
'node-name': 'target',
56+
'driver': 'throttle',
57+
'throttle-group': 'thrgr-target',
58+
'file': {
59+
'driver': iotests.imgfmt,
60+
'file': {
61+
'driver': 'file',
62+
'filename': target_img
63+
}
64+
}
65+
})
66+
67+
self.qsd.cmd('block-export-add', {
68+
'id': 'exp0',
69+
'type': 'nbd',
70+
'node-name': 'target',
71+
'writable': True
72+
})
73+
74+
self.vm = iotests.VM()
75+
self.vm.add_args('-drive',
76+
f'file={source_img},if=none,format={iotests.imgfmt},'
77+
f'iops_wr={iops_source},'
78+
f'iops_wr_max={iops_source},'
79+
'id=source')
80+
self.vm.launch()
81+
82+
self.vm.cmd('blockdev-add', {
83+
'node-name': 'target',
84+
'driver': 'nbd',
85+
'export': 'target',
86+
'server': {
87+
'type': 'unix',
88+
'path': nbd_sock
89+
}
90+
})
91+
92+
93+
def tearDown(self):
94+
self.vm.shutdown()
95+
self.qsd.stop()
96+
self.check_qemu_io_errors()
97+
self.check_images_identical()
98+
os.remove(source_img)
99+
os.remove(target_img)
100+
101+
# Once the VM is shut down we can parse the log and see if qemu-io ran
102+
# without errors.
103+
def check_qemu_io_errors(self):
104+
self.assertFalse(self.vm.is_running())
105+
log = self.vm.get_log()
106+
for line in log.split("\n"):
107+
assert not line.startswith("Pattern verification failed")
108+
109+
def check_images_identical(self):
110+
qemu_img('compare', '-f', iotests.imgfmt, source_img, target_img)
111+
112+
def start_mirror(self):
113+
self.vm.cmd('blockdev-mirror',
114+
job_id='mirror',
115+
device='source',
116+
target='target',
117+
filter_node_name='mirror-top',
118+
sync='full',
119+
copy_mode='background')
120+
121+
def test_background_to_active(self):
122+
self.vm.hmp_qemu_io('source', f'write 0 {image_size}')
123+
self.vm.hmp_qemu_io('target', f'write 0 {image_size}')
124+
125+
self.start_mirror()
126+
127+
result = self.vm.cmd('query-block-jobs')
128+
assert not result[0]['actively-synced']
129+
130+
self.vm.event_wait('BLOCK_JOB_READY')
131+
132+
result = self.vm.cmd('query-block-jobs')
133+
assert not result[0]['actively-synced']
134+
135+
# Start some background requests.
136+
reqs = 4 * iops_source
137+
req_size = image_size // reqs
138+
for i in range(0, reqs):
139+
req = f'aio_write -P 7 {req_size * i} {req_size}'
140+
self.vm.hmp_qemu_io('source', req)
141+
142+
# Wait for the first few requests.
143+
time.sleep(1)
144+
self.vm.qtest(f'clock_step {1 * 1000 * 1000 * 1000}')
145+
146+
result = self.vm.cmd('query-block-jobs')
147+
# There should've been new requests.
148+
assert result[0]['len'] > image_size
149+
# To verify later that not all requests were completed at this point.
150+
len_before_change = result[0]['len']
151+
152+
# Change the copy mode while requests are happening.
153+
self.vm.cmd('block-job-change',
154+
id='mirror',
155+
type='mirror',
156+
copy_mode='write-blocking')
157+
158+
# Wait until image is actively synced.
159+
while True:
160+
time.sleep(0.1)
161+
self.vm.qtest(f'clock_step {100 * 1000 * 1000}')
162+
result = self.vm.cmd('query-block-jobs')
163+
if result[0]['actively-synced']:
164+
break
165+
166+
# Because of throttling, not all requests should have been completed
167+
# above.
168+
result = self.vm.cmd('query-block-jobs')
169+
assert result[0]['len'] > len_before_change
170+
171+
# Issue enough requests for a few seconds only touching the first half
172+
# of the image.
173+
reqs = 4 * iops_target
174+
req_size = image_size // 2 // reqs
175+
for i in range(0, reqs):
176+
req = f'aio_write -P 19 {req_size * i} {req_size}'
177+
self.vm.hmp_qemu_io('source', req)
178+
179+
# Now issue a synchronous write in the second half of the image and
180+
# immediately verify that it was written to the target too. This would
181+
# fail without switching the copy mode. Note that this only produces a
182+
# log line and the actual checking happens during tearDown().
183+
req_args = f'-P 37 {3 * (image_size // 4)} {req_size}'
184+
self.vm.hmp_qemu_io('source', f'write {req_args}')
185+
self.vm.hmp_qemu_io('target', f'read {req_args}')
186+
187+
self.vm.cmd('block-job-cancel', device='mirror')
188+
while len(self.vm.cmd('query-block-jobs')) > 0:
189+
time.sleep(0.1)
190+
191+
if __name__ == '__main__':
192+
iotests.main(supported_fmts=['qcow2', 'raw'],
193+
supported_protocols=['file'])
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.
2+
----------------------------------------------------------------------
3+
Ran 1 tests
4+
5+
OK

0 commit comments

Comments
 (0)