meson.build: Make keyutils independent from keyring
[qemu/ar7.git] / tests / qemu-iotests / 151
blobb4d1bc2553f846a99c0401b2b1d80fe2229c96c6
1 #!/usr/bin/env python3
2 # group: rw
4 # Tests for active mirroring
6 # Copyright (C) 2018 Red Hat, Inc.
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.
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.
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/>.
22 import math
23 import os
24 import subprocess
25 import time
26 from typing import List, Optional
27 import iotests
28 from iotests import qemu_img
30 source_img = os.path.join(iotests.test_dir, 'source.' + iotests.imgfmt)
31 target_img = os.path.join(iotests.test_dir, 'target.' + iotests.imgfmt)
33 class TestActiveMirror(iotests.QMPTestCase):
34     image_len = 128 * 1024 * 1024 # MB
35     potential_writes_in_flight = True
37     def setUp(self):
38         qemu_img('create', '-f', iotests.imgfmt, source_img, '128M')
39         qemu_img('create', '-f', iotests.imgfmt, target_img, '128M')
41         blk_source = {'id': 'source',
42                       'if': 'none',
43                       'node-name': 'source-node',
44                       'driver': iotests.imgfmt,
45                       'file': {'driver': 'blkdebug',
46                                'image': {'driver': 'file',
47                                          'filename': source_img}}}
49         blk_target = {'node-name': 'target-node',
50                       'driver': iotests.imgfmt,
51                       'file': {'driver': 'file',
52                                'filename': target_img}}
54         self.vm = iotests.VM()
55         self.vm.add_drive_raw(self.vm.qmp_to_opts(blk_source))
56         self.vm.add_blockdev(self.vm.qmp_to_opts(blk_target))
57         self.vm.add_device('virtio-blk,id=vblk,drive=source')
58         self.vm.launch()
60     def tearDown(self):
61         self.vm.shutdown()
63         if not self.potential_writes_in_flight:
64             self.assertTrue(iotests.compare_images(source_img, target_img),
65                             'mirror target does not match source')
67         os.remove(source_img)
68         os.remove(target_img)
70     def doActiveIO(self, sync_source_and_target):
71         # Fill the source image
72         self.vm.hmp_qemu_io('source',
73                             'write -P 1 0 %i' % self.image_len);
75         # Start some background requests
76         for offset in range(1 * self.image_len // 8, 3 * self.image_len // 8, 1024 * 1024):
77             self.vm.hmp_qemu_io('source', 'aio_write -P 2 %i 1M' % offset)
78         for offset in range(2 * self.image_len // 8, 3 * self.image_len // 8, 1024 * 1024):
79             self.vm.hmp_qemu_io('source', 'aio_write -z %i 1M' % offset)
81         # Start the block job
82         result = self.vm.qmp('blockdev-mirror',
83                              job_id='mirror',
84                              filter_node_name='mirror-node',
85                              device='source-node',
86                              target='target-node',
87                              sync='full',
88                              copy_mode='write-blocking')
89         self.assert_qmp(result, 'return', {})
91         # Start some more requests
92         for offset in range(3 * self.image_len // 8, 5 * self.image_len // 8, 1024 * 1024):
93             self.vm.hmp_qemu_io('source', 'aio_write -P 3 %i 1M' % offset)
94         for offset in range(4 * self.image_len // 8, 5 * self.image_len // 8, 1024 * 1024):
95             self.vm.hmp_qemu_io('source', 'aio_write -z %i 1M' % offset)
97         # Wait for the READY event
98         self.wait_ready(drive='mirror')
100         # Now start some final requests; all of these (which land on
101         # the source) should be settled using the active mechanism.
102         # The mirror code itself asserts that the source BDS's dirty
103         # bitmap will stay clean between READY and COMPLETED.
104         for offset in range(5 * self.image_len // 8, 7 * self.image_len // 8, 1024 * 1024):
105             self.vm.hmp_qemu_io('source', 'aio_write -P 3 %i 1M' % offset)
106         for offset in range(6 * self.image_len // 8, 7 * self.image_len // 8, 1024 * 1024):
107             self.vm.hmp_qemu_io('source', 'aio_write -z %i 1M' % offset)
109         if sync_source_and_target:
110             # If source and target should be in sync after the mirror,
111             # we have to flush before completion
112             self.vm.hmp_qemu_io('source', 'aio_flush')
113             self.potential_writes_in_flight = False
115         self.complete_and_wait(drive='mirror', wait_ready=False)
117     def testActiveIO(self):
118         self.doActiveIO(False)
120     def testActiveIOFlushed(self):
121         self.doActiveIO(True)
123     def testUnalignedActiveIO(self):
124         # Fill the source image
125         result = self.vm.hmp_qemu_io('source', 'write -P 1 0 2M')
127         # Start the block job (very slowly)
128         result = self.vm.qmp('blockdev-mirror',
129                              job_id='mirror',
130                              filter_node_name='mirror-node',
131                              device='source-node',
132                              target='target-node',
133                              sync='full',
134                              copy_mode='write-blocking',
135                              buf_size=(1048576 // 4),
136                              speed=1)
137         self.assert_qmp(result, 'return', {})
139         # Start an unaligned request to a dirty area
140         result = self.vm.hmp_qemu_io('source', 'write -P 2 %i 1' % (1048576 + 42))
142         # Let the job finish
143         result = self.vm.qmp('block-job-set-speed', device='mirror', speed=0)
144         self.assert_qmp(result, 'return', {})
145         self.complete_and_wait(drive='mirror')
147         self.potential_writes_in_flight = False
149     def testIntersectingActiveIO(self):
150         # Fill the source image
151         result = self.vm.hmp_qemu_io('source', 'write -P 1 0 2M')
153         # Start the block job (very slowly)
154         result = self.vm.qmp('blockdev-mirror',
155                              job_id='mirror',
156                              filter_node_name='mirror-node',
157                              device='source-node',
158                              target='target-node',
159                              sync='full',
160                              copy_mode='write-blocking',
161                              speed=1)
163         self.vm.hmp_qemu_io('source', 'break write_aio A')
164         self.vm.hmp_qemu_io('source', 'aio_write 0 1M')  # 1
165         self.vm.hmp_qemu_io('source', 'wait_break A')
166         self.vm.hmp_qemu_io('source', 'aio_write 0 2M')  # 2
167         self.vm.hmp_qemu_io('source', 'aio_write 0 2M')  # 3
169         # Now 2 and 3 are in mirror_wait_on_conflicts, waiting for 1
171         self.vm.hmp_qemu_io('source', 'break write_aio B')
172         self.vm.hmp_qemu_io('source', 'aio_write 1M 2M')  # 4
173         self.vm.hmp_qemu_io('source', 'wait_break B')
175         # 4 doesn't wait for 2 and 3, because they didn't yet set
176         # in_flight_bitmap. So, nothing prevents 4 to go except for our
177         # break-point B.
179         self.vm.hmp_qemu_io('source', 'resume A')
181         # Now we resumed 1, so 2 and 3 goes to the next iteration of while loop
182         # in mirror_wait_on_conflicts(). They don't exit, as bitmap is dirty
183         # due to request 4.
184         # In the past at that point 2 and 3 would wait for each other producing
185         # a dead-lock. Now this is fixed and they will wait for request 4.
187         self.vm.hmp_qemu_io('source', 'resume B')
189         # After resuming 4, one of 2 and 3 goes first and set in_flight_bitmap,
190         # so the other will wait for it.
192         result = self.vm.qmp('block-job-set-speed', device='mirror', speed=0)
193         self.assert_qmp(result, 'return', {})
194         self.complete_and_wait(drive='mirror')
196         self.potential_writes_in_flight = False
199 class TestThrottledWithNbdExportBase(iotests.QMPTestCase):
200     image_len = 128 * 1024 * 1024  # MB
201     iops: Optional[int] = None
202     background_processes: List['subprocess.Popen[str]'] = []
204     def setUp(self):
205         # Must be set by subclasses
206         self.assertIsNotNone(self.iops)
208         qemu_img('create', '-f', iotests.imgfmt, source_img, '128M')
209         qemu_img('create', '-f', iotests.imgfmt, target_img, '128M')
211         self.vm = iotests.VM()
212         self.vm.launch()
214         result = self.vm.qmp('object-add', **{
215             'qom-type': 'throttle-group',
216             'id': 'thrgr',
217             'limits': {
218                 'iops-total': self.iops,
219                 'iops-total-max': self.iops
220             }
221         })
222         self.assert_qmp(result, 'return', {})
224         result = self.vm.qmp('blockdev-add', **{
225             'node-name': 'source-node',
226             'driver': 'throttle',
227             'throttle-group': 'thrgr',
228             'file': {
229                 'driver': iotests.imgfmt,
230                 'file': {
231                     'driver': 'file',
232                     'filename': source_img
233                 }
234             }
235         })
236         self.assert_qmp(result, 'return', {})
238         result = self.vm.qmp('blockdev-add', **{
239             'node-name': 'target-node',
240             'driver': iotests.imgfmt,
241             'file': {
242                 'driver': 'file',
243                 'filename': target_img
244             }
245         })
246         self.assert_qmp(result, 'return', {})
248         self.nbd_sock = iotests.file_path('nbd.sock',
249                                           base_dir=iotests.sock_dir)
250         self.nbd_url = f'nbd+unix:///source-node?socket={self.nbd_sock}'
252         result = self.vm.qmp('nbd-server-start', addr={
253             'type': 'unix',
254             'data': {
255                 'path': self.nbd_sock
256             }
257         })
258         self.assert_qmp(result, 'return', {})
260         result = self.vm.qmp('block-export-add', id='exp0', type='nbd',
261                              node_name='source-node', writable=True)
262         self.assert_qmp(result, 'return', {})
264     def tearDown(self):
265         # Wait for background requests to settle
266         try:
267             while True:
268                 p = self.background_processes.pop()
269                 while True:
270                     try:
271                         p.wait(timeout=0.0)
272                         break
273                     except subprocess.TimeoutExpired:
274                         self.vm.qtest(f'clock_step {1 * 1000 * 1000 * 1000}')
275         except IndexError:
276             pass
278         # Cancel ongoing block jobs
279         for job in self.vm.qmp('query-jobs')['return']:
280             self.vm.qmp('block-job-cancel', device=job['id'], force=True)
282         while True:
283             self.vm.qtest(f'clock_step {1 * 1000 * 1000 * 1000}')
284             if len(self.vm.qmp('query-jobs')['return']) == 0:
285                 break
287         self.vm.shutdown()
288         os.remove(source_img)
289         os.remove(target_img)
292 class TestLowThrottledWithNbdExport(TestThrottledWithNbdExportBase):
293     iops = 16
295     def testUnderLoad(self):
296         '''
297         Throttle the source node, then issue a whole bunch of external requests
298         while the mirror job (in write-blocking mode) is running.  We want to
299         see background requests being issued even while the source is under
300         full load by active writes, so that progress can be made towards READY.
301         '''
303         # Fill the first half of the source image; do not fill the second half,
304         # that is where we will have active requests occur.  This ensures that
305         # active mirroring itself will not directly contribute to the job's
306         # progress (because when the job was started, those areas were not
307         # intended to be copied, so active mirroring will only lead to not
308         # losing progress, but also not making any).
309         self.vm.hmp_qemu_io('source-node',
310                             f'aio_write -P 1 0 {self.image_len // 2}')
311         self.vm.qtest(f'clock_step {1 * 1000 * 1000 * 1000}')
313         # Launch the mirror job
314         mirror_buf_size = 65536
315         result = self.vm.qmp('blockdev-mirror',
316                              job_id='mirror',
317                              filter_node_name='mirror-node',
318                              device='source-node',
319                              target='target-node',
320                              sync='full',
321                              copy_mode='write-blocking',
322                              buf_size=mirror_buf_size)
323         self.assert_qmp(result, 'return', {})
325         # We create the external requests via qemu-io processes on the NBD
326         # server.  Have their offset start in the middle of the image so they
327         # do not overlap with the background requests (which start from the
328         # beginning).
329         active_request_offset = self.image_len // 2
330         active_request_len = 4096
332         # Create enough requests to saturate the node for 5 seconds
333         for _ in range(0, 5 * self.iops):
334             req = f'write -P 42 {active_request_offset} {active_request_len}'
335             active_request_offset += active_request_len
336             p = iotests.qemu_io_popen('-f', 'nbd', self.nbd_url, '-c', req)
337             self.background_processes += [p]
339         # Now advance the clock one I/O operation at a time by the 4 seconds
340         # (i.e. one less than 5).  We expect the mirror job to issue background
341         # operations here, even though active requests are still in flight.
342         # The active requests will take precedence, however, because they have
343         # been issued earlier than mirror's background requests.
344         # Once the active requests we have started above are done (i.e. after 5
345         # virtual seconds), we expect those background requests to be worked
346         # on.  We only advance 4 seconds here to avoid race conditions.
347         for _ in range(0, 4 * self.iops):
348             step = math.ceil(1 * 1000 * 1000 * 1000 / self.iops)
349             self.vm.qtest(f'clock_step {step}')
351         # Note how much remains to be done until the mirror job is finished
352         job_status = self.vm.qmp('query-jobs')['return'][0]
353         start_remaining = job_status['total-progress'] - \
354             job_status['current-progress']
356         # Create a whole bunch of more active requests
357         for _ in range(0, 10 * self.iops):
358             req = f'write -P 42 {active_request_offset} {active_request_len}'
359             active_request_offset += active_request_len
360             p = iotests.qemu_io_popen('-f', 'nbd', self.nbd_url, '-c', req)
361             self.background_processes += [p]
363         # Let the clock advance more.  After 1 second, as noted above, we
364         # expect the background requests to be worked on.  Give them a couple
365         # of seconds (specifically 4) to see their impact.
366         for _ in range(0, 5 * self.iops):
367             step = math.ceil(1 * 1000 * 1000 * 1000 / self.iops)
368             self.vm.qtest(f'clock_step {step}')
370         # Note how much remains to be done now.  We expect this number to be
371         # reduced thanks to those background requests.
372         job_status = self.vm.qmp('query-jobs')['return'][0]
373         end_remaining = job_status['total-progress'] - \
374             job_status['current-progress']
376         # See that indeed progress was being made on the job, even while the
377         # node was saturated with active requests
378         self.assertGreater(start_remaining - end_remaining, 0)
381 class TestHighThrottledWithNbdExport(TestThrottledWithNbdExportBase):
382     iops = 1024
384     def testActiveOnCreation(self):
385         '''
386         Issue requests on the mirror source node right as the mirror is
387         instated.  It's possible that requests occur before the actual job is
388         created, but after the node has been put into the graph.  Write
389         requests across the node must in that case be forwarded to the source
390         node without attempting to mirror them (there is no job object yet, so
391         attempting to access it would cause a segfault).
392         We do this with a lightly throttled node (i.e. quite high IOPS limit).
393         Using throttling seems to increase reproductivity, but if the limit is
394         too low, all requests allowed per second will be submitted before
395         mirror_start_job() gets to the problematic point.
396         '''
398         # Let qemu-img bench create write requests (enough for two seconds on
399         # the virtual clock)
400         bench_args = ['bench', '-w', '-d', '1024', '-f', 'nbd',
401                       '-c', str(self.iops * 2), self.nbd_url]
402         p = iotests.qemu_tool_popen(iotests.qemu_img_args + bench_args)
403         self.background_processes += [p]
405         # Give qemu-img bench time to start up and issue requests
406         time.sleep(1.0)
407         # Flush the request queue, so new requests can come in right as we
408         # start blockdev-mirror
409         self.vm.qtest(f'clock_step {1 * 1000 * 1000 * 1000}')
411         result = self.vm.qmp('blockdev-mirror',
412                              job_id='mirror',
413                              device='source-node',
414                              target='target-node',
415                              sync='full',
416                              copy_mode='write-blocking')
417         self.assert_qmp(result, 'return', {})
420 if __name__ == '__main__':
421     iotests.main(supported_fmts=['qcow2', 'raw'],
422                  supported_protocols=['file'])