xref: /openbmc/qemu/tests/qemu-iotests/041 (revision eabfeb0c)
1#!/usr/bin/env python3
2#
3# Tests for image mirroring.
4#
5# Copyright (C) 2012 Red Hat, Inc.
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program.  If not, see <http://www.gnu.org/licenses/>.
19#
20
21import time
22import os
23import re
24import iotests
25from iotests import qemu_img, qemu_io
26
27backing_img = os.path.join(iotests.test_dir, 'backing.img')
28target_backing_img = os.path.join(iotests.test_dir, 'target-backing.img')
29test_img = os.path.join(iotests.test_dir, 'test.img')
30target_img = os.path.join(iotests.test_dir, 'target.img')
31
32quorum_img1 = os.path.join(iotests.test_dir, 'quorum1.img')
33quorum_img2 = os.path.join(iotests.test_dir, 'quorum2.img')
34quorum_img3 = os.path.join(iotests.test_dir, 'quorum3.img')
35quorum_repair_img = os.path.join(iotests.test_dir, 'quorum_repair.img')
36quorum_snapshot_file = os.path.join(iotests.test_dir, 'quorum_snapshot.img')
37
38nbd_sock_path = os.path.join(iotests.sock_dir, 'nbd.sock')
39
40class TestSingleDrive(iotests.QMPTestCase):
41    image_len = 1 * 1024 * 1024 # MB
42    qmp_cmd = 'drive-mirror'
43    qmp_target = target_img
44
45    def setUp(self):
46        iotests.create_image(backing_img, self.image_len)
47        qemu_img('create', '-f', iotests.imgfmt,
48                 '-o', 'backing_file=%s' % backing_img, '-F', 'raw', test_img)
49        self.vm = iotests.VM().add_drive(test_img, "node-name=top,backing.node-name=base")
50        if iotests.qemu_default_machine == 'pc':
51            self.vm.add_drive(None, 'media=cdrom', 'ide')
52        self.vm.launch()
53
54    def tearDown(self):
55        self.vm.shutdown()
56        os.remove(test_img)
57        os.remove(backing_img)
58        try:
59            os.remove(target_img)
60        except OSError:
61            pass
62
63    def test_complete(self):
64        self.assert_no_active_block_jobs()
65
66        result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
67                             target=self.qmp_target)
68        self.assert_qmp(result, 'return', {})
69
70        self.complete_and_wait()
71        result = self.vm.qmp('query-block')
72        self.assert_qmp(result, 'return[0]/inserted/file', target_img)
73        self.vm.shutdown()
74        self.assertTrue(iotests.compare_images(test_img, target_img),
75                        'target image does not match source after mirroring')
76
77    def test_cancel(self):
78        self.assert_no_active_block_jobs()
79
80        result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
81                             target=self.qmp_target)
82        self.assert_qmp(result, 'return', {})
83
84        self.cancel_and_wait(force=True)
85        result = self.vm.qmp('query-block')
86        self.assert_qmp(result, 'return[0]/inserted/file', test_img)
87
88    def test_cancel_after_ready(self):
89        self.assert_no_active_block_jobs()
90
91        result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
92                             target=self.qmp_target)
93        self.assert_qmp(result, 'return', {})
94
95        self.wait_ready_and_cancel()
96        result = self.vm.qmp('query-block')
97        self.assert_qmp(result, 'return[0]/inserted/file', test_img)
98        self.vm.shutdown()
99        self.assertTrue(iotests.compare_images(test_img, target_img),
100                        'target image does not match source after mirroring')
101
102    def test_pause(self):
103        self.assert_no_active_block_jobs()
104
105        result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
106                             target=self.qmp_target)
107        self.assert_qmp(result, 'return', {})
108
109        self.pause_job('drive0')
110
111        result = self.vm.qmp('query-block-jobs')
112        offset = self.dictpath(result, 'return[0]/offset')
113
114        time.sleep(0.5)
115        result = self.vm.qmp('query-block-jobs')
116        self.assert_qmp(result, 'return[0]/offset', offset)
117
118        result = self.vm.qmp('block-job-resume', device='drive0')
119        self.assert_qmp(result, 'return', {})
120
121        self.complete_and_wait()
122        self.vm.shutdown()
123        self.assertTrue(iotests.compare_images(test_img, target_img),
124                        'target image does not match source after mirroring')
125
126    def test_small_buffer(self):
127        self.assert_no_active_block_jobs()
128
129        # A small buffer is rounded up automatically
130        result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
131                             buf_size=4096, target=self.qmp_target)
132        self.assert_qmp(result, 'return', {})
133
134        self.complete_and_wait()
135        result = self.vm.qmp('query-block')
136        self.assert_qmp(result, 'return[0]/inserted/file', target_img)
137        self.vm.shutdown()
138        self.assertTrue(iotests.compare_images(test_img, target_img),
139                        'target image does not match source after mirroring')
140
141    def test_small_buffer2(self):
142        self.assert_no_active_block_jobs()
143
144        qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,size=%d'
145                        % (self.image_len, self.image_len), target_img)
146        result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
147                             buf_size=65536, mode='existing', target=self.qmp_target)
148        self.assert_qmp(result, 'return', {})
149
150        self.complete_and_wait()
151        result = self.vm.qmp('query-block')
152        self.assert_qmp(result, 'return[0]/inserted/file', target_img)
153        self.vm.shutdown()
154        self.assertTrue(iotests.compare_images(test_img, target_img),
155                        'target image does not match source after mirroring')
156
157    def test_large_cluster(self):
158        self.assert_no_active_block_jobs()
159
160        qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,backing_file=%s'
161                        % (self.image_len, backing_img),
162                 '-F', 'raw', target_img)
163        result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
164                             mode='existing', target=self.qmp_target)
165        self.assert_qmp(result, 'return', {})
166
167        self.complete_and_wait()
168        result = self.vm.qmp('query-block')
169        self.assert_qmp(result, 'return[0]/inserted/file', target_img)
170        self.vm.shutdown()
171        self.assertTrue(iotests.compare_images(test_img, target_img),
172                        'target image does not match source after mirroring')
173
174    # Tests that the insertion of the mirror_top filter node doesn't make a
175    # difference to query-block
176    def test_implicit_node(self):
177        self.assert_no_active_block_jobs()
178
179        result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
180                             target=self.qmp_target)
181        self.assert_qmp(result, 'return', {})
182
183        result = self.vm.qmp('query-block')
184        self.assert_qmp(result, 'return[0]/inserted/file', test_img)
185        self.assert_qmp(result, 'return[0]/inserted/drv', iotests.imgfmt)
186        self.assert_qmp(result, 'return[0]/inserted/backing_file', backing_img)
187        self.assert_qmp(result, 'return[0]/inserted/backing_file_depth', 1)
188        self.assert_qmp(result, 'return[0]/inserted/image/filename', test_img)
189        self.assert_qmp(result, 'return[0]/inserted/image/backing-image/filename', backing_img)
190
191        result = self.vm.qmp('query-blockstats')
192        self.assert_qmp(result, 'return[0]/node-name', 'top')
193        self.assert_qmp(result, 'return[0]/backing/node-name', 'base')
194
195        self.cancel_and_wait(force=True)
196        result = self.vm.qmp('query-block')
197        self.assert_qmp(result, 'return[0]/inserted/file', test_img)
198        self.assert_qmp(result, 'return[0]/inserted/drv', iotests.imgfmt)
199        self.assert_qmp(result, 'return[0]/inserted/backing_file', backing_img)
200        self.assert_qmp(result, 'return[0]/inserted/backing_file_depth', 1)
201        self.assert_qmp(result, 'return[0]/inserted/image/filename', test_img)
202        self.assert_qmp(result, 'return[0]/inserted/image/backing-image/filename', backing_img)
203
204        result = self.vm.qmp('query-blockstats')
205        self.assert_qmp(result, 'return[0]/node-name', 'top')
206        self.assert_qmp(result, 'return[0]/backing/node-name', 'base')
207
208    def test_medium_not_found(self):
209        if iotests.qemu_default_machine != 'pc':
210            return
211
212        result = self.vm.qmp(self.qmp_cmd, device='ide1-cd0', sync='full',
213                             target=self.qmp_target)
214        self.assert_qmp(result, 'error/class', 'GenericError')
215
216    def test_image_not_found(self):
217        result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
218                             mode='existing', target=self.qmp_target)
219        self.assert_qmp(result, 'error/class', 'GenericError')
220
221    def test_device_not_found(self):
222        result = self.vm.qmp(self.qmp_cmd, device='nonexistent', sync='full',
223                             target=self.qmp_target)
224        self.assert_qmp(result, 'error/class', 'GenericError')
225
226class TestSingleBlockdev(TestSingleDrive):
227    qmp_cmd = 'blockdev-mirror'
228    qmp_target = 'node1'
229
230    def setUp(self):
231        TestSingleDrive.setUp(self)
232        qemu_img('create', '-f', iotests.imgfmt,
233                 '-o', 'backing_file=%s' % backing_img, '-F', 'raw', target_img)
234        args = {'driver': iotests.imgfmt,
235                'node-name': self.qmp_target,
236                'file': { 'filename': target_img, 'driver': 'file' } }
237        result = self.vm.qmp("blockdev-add", **args)
238        self.assert_qmp(result, 'return', {})
239
240    def test_mirror_to_self(self):
241        result = self.vm.qmp(self.qmp_cmd, job_id='job0',
242                             device=self.qmp_target, sync='full',
243                             target=self.qmp_target)
244        self.assert_qmp(result, 'error/class', 'GenericError')
245
246    def do_test_resize(self, device, node):
247        def pre_finalize():
248            if device:
249                result = self.vm.qmp('block_resize', device=device, size=65536)
250                self.assert_qmp(result, 'error/class', 'GenericError')
251
252            result = self.vm.qmp('block_resize', node_name=node, size=65536)
253            self.assert_qmp(result, 'error/class', 'GenericError')
254
255        result = self.vm.qmp(self.qmp_cmd, job_id='job0', device='drive0',
256                             sync='full', target=self.qmp_target,
257                             auto_finalize=False, auto_dismiss=False)
258        self.assert_qmp(result, 'return', {})
259
260        result = self.vm.run_job('job0', auto_finalize=False,
261                                 pre_finalize=pre_finalize)
262        self.assertEqual(result, None)
263
264    def test_source_resize(self):
265        self.do_test_resize('drive0', 'top')
266
267    def test_target_resize(self):
268        self.do_test_resize(None, self.qmp_target)
269
270    def do_test_target_size(self, size):
271        result = self.vm.qmp('block_resize', node_name=self.qmp_target,
272                             size=size)
273        self.assert_qmp(result, 'return', {})
274
275        result = self.vm.qmp(self.qmp_cmd, job_id='job0',
276                             device='drive0', sync='full', auto_dismiss=False,
277                             target=self.qmp_target)
278        self.assert_qmp(result, 'return', {})
279
280        result = self.vm.run_job('job0')
281        self.assertEqual(result, 'Source and target image have different sizes')
282
283    # qed does not support shrinking
284    @iotests.skip_for_formats(('qed'))
285    def test_small_target(self):
286        self.do_test_target_size(self.image_len // 2)
287
288    def test_large_target(self):
289        self.do_test_target_size(self.image_len * 2)
290
291    test_large_cluster = None
292    test_image_not_found = None
293    test_small_buffer2 = None
294
295class TestSingleDriveZeroLength(TestSingleDrive):
296    image_len = 0
297    test_small_buffer2 = None
298    test_large_cluster = None
299
300class TestSingleBlockdevZeroLength(TestSingleBlockdev):
301    image_len = 0
302    test_small_target = None
303    test_large_target = None
304
305class TestSingleDriveUnalignedLength(TestSingleDrive):
306    image_len = 1025 * 1024
307    test_small_buffer2 = None
308    test_large_cluster = None
309
310class TestSingleBlockdevUnalignedLength(TestSingleBlockdev):
311    image_len = 1025 * 1024
312
313class TestMirrorNoBacking(iotests.QMPTestCase):
314    image_len = 2 * 1024 * 1024 # MB
315
316    def setUp(self):
317        iotests.create_image(backing_img, TestMirrorNoBacking.image_len)
318        qemu_img('create', '-f', iotests.imgfmt,
319                 '-o', 'backing_file=%s' % backing_img, '-F', 'raw', test_img)
320        self.vm = iotests.VM().add_drive(test_img)
321        self.vm.launch()
322
323    def tearDown(self):
324        self.vm.shutdown()
325        os.remove(test_img)
326        os.remove(backing_img)
327        try:
328            os.remove(target_backing_img)
329        except:
330            pass
331        os.remove(target_img)
332
333    def test_complete(self):
334        self.assert_no_active_block_jobs()
335
336        qemu_img('create', '-f', iotests.imgfmt,
337                 '-o', 'backing_file=%s' % backing_img, '-F', 'raw', target_img)
338        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
339                             mode='existing', target=target_img)
340        self.assert_qmp(result, 'return', {})
341
342        self.complete_and_wait()
343        result = self.vm.qmp('query-block')
344        self.assert_qmp(result, 'return[0]/inserted/file', target_img)
345        self.vm.shutdown()
346        self.assertTrue(iotests.compare_images(test_img, target_img),
347                        'target image does not match source after mirroring')
348
349    def test_cancel(self):
350        self.assert_no_active_block_jobs()
351
352        qemu_img('create', '-f', iotests.imgfmt,
353                 '-o', 'backing_file=%s' % backing_img, '-F', 'raw', target_img)
354        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
355                             mode='existing', target=target_img)
356        self.assert_qmp(result, 'return', {})
357
358        self.wait_ready_and_cancel()
359        result = self.vm.qmp('query-block')
360        self.assert_qmp(result, 'return[0]/inserted/file', test_img)
361        self.vm.shutdown()
362        self.assertTrue(iotests.compare_images(test_img, target_img),
363                        'target image does not match source after mirroring')
364
365    def test_large_cluster(self):
366        self.assert_no_active_block_jobs()
367
368        # qemu-img create fails if the image is not there
369        qemu_img('create', '-f', iotests.imgfmt, '-o', 'size=%d'
370                        %(TestMirrorNoBacking.image_len), target_backing_img)
371        qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,backing_file=%s'
372                        % (TestMirrorNoBacking.image_len, target_backing_img),
373                 '-F', iotests.imgfmt, target_img)
374
375        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
376                             mode='existing', target=target_img)
377        self.assert_qmp(result, 'return', {})
378
379        self.complete_and_wait()
380        result = self.vm.qmp('query-block')
381        self.assert_qmp(result, 'return[0]/inserted/file', target_img)
382        self.vm.shutdown()
383        self.assertTrue(iotests.compare_images(test_img, target_img),
384                        'target image does not match source after mirroring')
385
386class TestMirrorResized(iotests.QMPTestCase):
387    backing_len = 1 * 1024 * 1024 # MB
388    image_len = 2 * 1024 * 1024 # MB
389
390    def setUp(self):
391        iotests.create_image(backing_img, TestMirrorResized.backing_len)
392        qemu_img('create', '-f', iotests.imgfmt,
393                 '-o', 'backing_file=%s' % backing_img, '-F', 'raw', test_img)
394        qemu_img('resize', test_img, '2M')
395        self.vm = iotests.VM().add_drive(test_img)
396        self.vm.launch()
397
398    def tearDown(self):
399        self.vm.shutdown()
400        os.remove(test_img)
401        os.remove(backing_img)
402        try:
403            os.remove(target_img)
404        except OSError:
405            pass
406
407    def test_complete_top(self):
408        self.assert_no_active_block_jobs()
409
410        result = self.vm.qmp('drive-mirror', device='drive0', sync='top',
411                             target=target_img)
412        self.assert_qmp(result, 'return', {})
413
414        self.complete_and_wait()
415        result = self.vm.qmp('query-block')
416        self.assert_qmp(result, 'return[0]/inserted/file', target_img)
417        self.vm.shutdown()
418        self.assertTrue(iotests.compare_images(test_img, target_img),
419                        'target image does not match source after mirroring')
420
421    def test_complete_full(self):
422        self.assert_no_active_block_jobs()
423
424        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
425                             target=target_img)
426        self.assert_qmp(result, 'return', {})
427
428        self.complete_and_wait()
429        result = self.vm.qmp('query-block')
430        self.assert_qmp(result, 'return[0]/inserted/file', target_img)
431        self.vm.shutdown()
432        self.assertTrue(iotests.compare_images(test_img, target_img),
433                        'target image does not match source after mirroring')
434
435class TestReadErrors(iotests.QMPTestCase):
436    image_len = 2 * 1024 * 1024 # MB
437
438    # this should be a multiple of twice the default granularity
439    # so that we hit this offset first in state 1
440    MIRROR_GRANULARITY = 1024 * 1024
441
442    def create_blkdebug_file(self, name, event, errno):
443        file = open(name, 'w')
444        file.write('''
445[inject-error]
446state = "1"
447event = "%s"
448errno = "%d"
449immediately = "off"
450once = "on"
451sector = "%d"
452
453[set-state]
454state = "1"
455event = "%s"
456new_state = "2"
457
458[set-state]
459state = "2"
460event = "%s"
461new_state = "1"
462''' % (event, errno, self.MIRROR_GRANULARITY // 512, event, event))
463        file.close()
464
465    def setUp(self):
466        self.blkdebug_file = backing_img + ".blkdebug"
467        iotests.create_image(backing_img, TestReadErrors.image_len)
468        self.create_blkdebug_file(self.blkdebug_file, "read_aio", 5)
469        qemu_img('create', '-f', iotests.imgfmt,
470                 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
471                       % (self.blkdebug_file, backing_img),
472                 test_img)
473        # Write something for tests that use sync='top'
474        qemu_io('-c', 'write %d 512' % (self.MIRROR_GRANULARITY + 65536),
475                        test_img)
476        self.vm = iotests.VM().add_drive(test_img)
477        self.vm.launch()
478
479    def tearDown(self):
480        self.vm.shutdown()
481        os.remove(test_img)
482        os.remove(target_img)
483        os.remove(backing_img)
484        os.remove(self.blkdebug_file)
485
486    def test_report_read(self):
487        self.assert_no_active_block_jobs()
488
489        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
490                             target=target_img)
491        self.assert_qmp(result, 'return', {})
492
493        completed = False
494        error = False
495        while not completed:
496            for event in self.vm.get_qmp_events(wait=True):
497                if event['event'] == 'BLOCK_JOB_ERROR':
498                    self.assert_qmp(event, 'data/device', 'drive0')
499                    self.assert_qmp(event, 'data/operation', 'read')
500                    error = True
501                elif event['event'] == 'BLOCK_JOB_READY':
502                    self.assertTrue(False, 'job completed unexpectedly')
503                elif event['event'] == 'BLOCK_JOB_COMPLETED':
504                    self.assertTrue(error, 'job completed unexpectedly')
505                    self.assert_qmp(event, 'data/type', 'mirror')
506                    self.assert_qmp(event, 'data/device', 'drive0')
507                    self.assert_qmp(event, 'data/error', 'Input/output error')
508                    completed = True
509                elif event['event'] == 'JOB_STATUS_CHANGE':
510                    self.assert_qmp(event, 'data/id', 'drive0')
511
512        self.assert_no_active_block_jobs()
513
514    def test_ignore_read(self):
515        self.assert_no_active_block_jobs()
516
517        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
518                             target=target_img, on_source_error='ignore')
519        self.assert_qmp(result, 'return', {})
520
521        event = self.vm.get_qmp_event(wait=True)
522        while event['event'] == 'JOB_STATUS_CHANGE':
523            self.assert_qmp(event, 'data/id', 'drive0')
524            event = self.vm.get_qmp_event(wait=True)
525
526        self.assertEqual(event['event'], 'BLOCK_JOB_ERROR')
527        self.assert_qmp(event, 'data/device', 'drive0')
528        self.assert_qmp(event, 'data/operation', 'read')
529        result = self.vm.qmp('query-block-jobs')
530        self.assert_qmp(result, 'return[0]/paused', False)
531        self.complete_and_wait()
532
533    def test_large_cluster(self):
534        self.assert_no_active_block_jobs()
535
536        # Test COW into the target image.  The first half of the
537        # cluster at MIRROR_GRANULARITY has to be copied from
538        # backing_img, even though sync='top'.
539        qemu_img('create', '-f', iotests.imgfmt,
540                 '-ocluster_size=131072,backing_file=%s' %(backing_img),
541                 '-F', 'raw', target_img)
542        result = self.vm.qmp('drive-mirror', device='drive0', sync='top',
543                             on_source_error='ignore',
544                             mode='existing', target=target_img)
545        self.assert_qmp(result, 'return', {})
546
547        event = self.vm.get_qmp_event(wait=True)
548        while event['event'] == 'JOB_STATUS_CHANGE':
549            self.assert_qmp(event, 'data/id', 'drive0')
550            event = self.vm.get_qmp_event(wait=True)
551
552        self.assertEqual(event['event'], 'BLOCK_JOB_ERROR')
553        self.assert_qmp(event, 'data/device', 'drive0')
554        self.assert_qmp(event, 'data/operation', 'read')
555        result = self.vm.qmp('query-block-jobs')
556        self.assert_qmp(result, 'return[0]/paused', False)
557        self.complete_and_wait()
558        self.vm.shutdown()
559
560        # Detach blkdebug to compare images successfully
561        qemu_img('rebase', '-f', iotests.imgfmt, '-u', '-b', backing_img,
562                 '-F', 'raw', test_img)
563        self.assertTrue(iotests.compare_images(test_img, target_img),
564                        'target image does not match source after mirroring')
565
566    def test_stop_read(self):
567        self.assert_no_active_block_jobs()
568
569        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
570                             target=target_img, on_source_error='stop')
571        self.assert_qmp(result, 'return', {})
572
573        error = False
574        ready = False
575        while not ready:
576            for event in self.vm.get_qmp_events(wait=True):
577                if event['event'] == 'BLOCK_JOB_ERROR':
578                    self.assert_qmp(event, 'data/device', 'drive0')
579                    self.assert_qmp(event, 'data/operation', 'read')
580
581                    result = self.vm.qmp('query-block-jobs')
582                    self.assert_qmp(result, 'return[0]/paused', True)
583                    self.assert_qmp(result, 'return[0]/io-status', 'failed')
584
585                    result = self.vm.qmp('block-job-resume', device='drive0')
586                    self.assert_qmp(result, 'return', {})
587                    error = True
588                elif event['event'] == 'BLOCK_JOB_READY':
589                    self.assertTrue(error, 'job completed unexpectedly')
590                    self.assert_qmp(event, 'data/device', 'drive0')
591                    ready = True
592
593        result = self.vm.qmp('query-block-jobs')
594        self.assert_qmp(result, 'return[0]/paused', False)
595        self.assert_qmp(result, 'return[0]/io-status', 'ok')
596
597        self.complete_and_wait(wait_ready=False)
598        self.assert_no_active_block_jobs()
599
600class TestWriteErrors(iotests.QMPTestCase):
601    image_len = 2 * 1024 * 1024 # MB
602
603    # this should be a multiple of twice the default granularity
604    # so that we hit this offset first in state 1
605    MIRROR_GRANULARITY = 1024 * 1024
606
607    def create_blkdebug_file(self, name, event, errno):
608        file = open(name, 'w')
609        file.write('''
610[inject-error]
611state = "1"
612event = "%s"
613errno = "%d"
614immediately = "off"
615once = "on"
616sector = "%d"
617
618[set-state]
619state = "1"
620event = "%s"
621new_state = "2"
622
623[set-state]
624state = "2"
625event = "%s"
626new_state = "1"
627''' % (event, errno, self.MIRROR_GRANULARITY // 512, event, event))
628        file.close()
629
630    def setUp(self):
631        self.blkdebug_file = target_img + ".blkdebug"
632        iotests.create_image(backing_img, TestWriteErrors.image_len)
633        self.create_blkdebug_file(self.blkdebug_file, "write_aio", 5)
634        qemu_img('create', '-f', iotests.imgfmt,
635                 '-obacking_file=%s' %(backing_img), '-F', 'raw', test_img)
636        self.vm = iotests.VM().add_drive(test_img)
637        self.target_img = 'blkdebug:%s:%s' % (self.blkdebug_file, target_img)
638        qemu_img('create', '-f', iotests.imgfmt, '-osize=%d' %(TestWriteErrors.image_len), target_img)
639        self.vm.launch()
640
641    def tearDown(self):
642        self.vm.shutdown()
643        os.remove(test_img)
644        os.remove(target_img)
645        os.remove(backing_img)
646        os.remove(self.blkdebug_file)
647
648    def test_report_write(self):
649        self.assert_no_active_block_jobs()
650
651        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
652                             mode='existing', target=self.target_img)
653        self.assert_qmp(result, 'return', {})
654
655        completed = False
656        error = False
657        while not completed:
658            for event in self.vm.get_qmp_events(wait=True):
659                if event['event'] == 'BLOCK_JOB_ERROR':
660                    self.assert_qmp(event, 'data/device', 'drive0')
661                    self.assert_qmp(event, 'data/operation', 'write')
662                    error = True
663                elif event['event'] == 'BLOCK_JOB_READY':
664                    self.assertTrue(False, 'job completed unexpectedly')
665                elif event['event'] == 'BLOCK_JOB_COMPLETED':
666                    self.assertTrue(error, 'job completed unexpectedly')
667                    self.assert_qmp(event, 'data/type', 'mirror')
668                    self.assert_qmp(event, 'data/device', 'drive0')
669                    self.assert_qmp(event, 'data/error', 'Input/output error')
670                    completed = True
671
672        self.assert_no_active_block_jobs()
673
674    def test_ignore_write(self):
675        self.assert_no_active_block_jobs()
676
677        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
678                             mode='existing', target=self.target_img,
679                             on_target_error='ignore')
680        self.assert_qmp(result, 'return', {})
681
682        event = self.vm.event_wait(name='BLOCK_JOB_ERROR')
683        self.assertEqual(event['event'], 'BLOCK_JOB_ERROR')
684        self.assert_qmp(event, 'data/device', 'drive0')
685        self.assert_qmp(event, 'data/operation', 'write')
686        result = self.vm.qmp('query-block-jobs')
687        self.assert_qmp(result, 'return[0]/paused', False)
688        self.complete_and_wait()
689
690    def test_stop_write(self):
691        self.assert_no_active_block_jobs()
692
693        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
694                             mode='existing', target=self.target_img,
695                             on_target_error='stop')
696        self.assert_qmp(result, 'return', {})
697
698        error = False
699        ready = False
700        while not ready:
701            for event in self.vm.get_qmp_events(wait=True):
702                if event['event'] == 'BLOCK_JOB_ERROR':
703                    self.assert_qmp(event, 'data/device', 'drive0')
704                    self.assert_qmp(event, 'data/operation', 'write')
705
706                    result = self.vm.qmp('query-block-jobs')
707                    self.assert_qmp(result, 'return[0]/paused', True)
708                    self.assert_qmp(result, 'return[0]/io-status', 'failed')
709
710                    result = self.vm.qmp('block-job-resume', device='drive0')
711                    self.assert_qmp(result, 'return', {})
712
713                    result = self.vm.qmp('query-block-jobs')
714                    self.assert_qmp(result, 'return[0]/paused', False)
715                    self.assert_qmp(result, 'return[0]/io-status', 'ok')
716                    error = True
717                elif event['event'] == 'BLOCK_JOB_READY':
718                    self.assertTrue(error, 'job completed unexpectedly')
719                    self.assert_qmp(event, 'data/device', 'drive0')
720                    ready = True
721
722        self.complete_and_wait(wait_ready=False)
723        self.assert_no_active_block_jobs()
724
725class TestSetSpeed(iotests.QMPTestCase):
726    image_len = 80 * 1024 * 1024 # MB
727
728    def setUp(self):
729        qemu_img('create', backing_img, str(TestSetSpeed.image_len))
730        qemu_img('create', '-f', iotests.imgfmt,
731                 '-o', 'backing_file=%s' % backing_img, '-F', 'raw', test_img)
732        self.vm = iotests.VM().add_drive(test_img)
733        self.vm.launch()
734
735    def tearDown(self):
736        self.vm.shutdown()
737        os.remove(test_img)
738        os.remove(backing_img)
739        os.remove(target_img)
740
741    def test_set_speed(self):
742        self.assert_no_active_block_jobs()
743
744        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
745                             target=target_img)
746        self.assert_qmp(result, 'return', {})
747
748        # Default speed is 0
749        result = self.vm.qmp('query-block-jobs')
750        self.assert_qmp(result, 'return[0]/device', 'drive0')
751        self.assert_qmp(result, 'return[0]/speed', 0)
752
753        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024)
754        self.assert_qmp(result, 'return', {})
755
756        # Ensure the speed we set was accepted
757        result = self.vm.qmp('query-block-jobs')
758        self.assert_qmp(result, 'return[0]/device', 'drive0')
759        self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024)
760
761        self.wait_ready_and_cancel()
762
763        # Check setting speed in drive-mirror works
764        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
765                             target=target_img, speed=4*1024*1024)
766        self.assert_qmp(result, 'return', {})
767
768        result = self.vm.qmp('query-block-jobs')
769        self.assert_qmp(result, 'return[0]/device', 'drive0')
770        self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024)
771
772        self.wait_ready_and_cancel()
773
774    def test_set_speed_invalid(self):
775        self.assert_no_active_block_jobs()
776
777        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
778                             target=target_img, speed=-1)
779        self.assert_qmp(result, 'error/class', 'GenericError')
780
781        self.assert_no_active_block_jobs()
782
783        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
784                             target=target_img)
785        self.assert_qmp(result, 'return', {})
786
787        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1)
788        self.assert_qmp(result, 'error/class', 'GenericError')
789
790        self.wait_ready_and_cancel()
791
792class TestUnbackedSource(iotests.QMPTestCase):
793    image_len = 2 * 1024 * 1024 # MB
794
795    def setUp(self):
796        qemu_img('create', '-f', iotests.imgfmt, test_img,
797                 str(TestUnbackedSource.image_len))
798        self.vm = iotests.VM()
799        self.vm.launch()
800        result = self.vm.qmp('blockdev-add', node_name='drive0',
801                             driver=iotests.imgfmt,
802                             file={
803                                 'driver': 'file',
804                                 'filename': test_img,
805                             })
806        self.assert_qmp(result, 'return', {})
807
808    def tearDown(self):
809        self.vm.shutdown()
810        os.remove(test_img)
811        os.remove(target_img)
812
813    def test_absolute_paths_full(self):
814        self.assert_no_active_block_jobs()
815        result = self.vm.qmp('drive-mirror', job_id='drive0', device='drive0',
816                             sync='full', target=target_img,
817                             mode='absolute-paths')
818        self.assert_qmp(result, 'return', {})
819        self.complete_and_wait()
820        self.assert_no_active_block_jobs()
821
822    def test_absolute_paths_top(self):
823        self.assert_no_active_block_jobs()
824        result = self.vm.qmp('drive-mirror', job_id='drive0', device='drive0',
825                             sync='top', target=target_img,
826                             mode='absolute-paths')
827        self.assert_qmp(result, 'return', {})
828        self.complete_and_wait()
829        self.assert_no_active_block_jobs()
830
831    def test_absolute_paths_none(self):
832        self.assert_no_active_block_jobs()
833        result = self.vm.qmp('drive-mirror', job_id='drive0', device='drive0',
834                             sync='none', target=target_img,
835                             mode='absolute-paths')
836        self.assert_qmp(result, 'return', {})
837        self.complete_and_wait()
838        self.assert_no_active_block_jobs()
839
840    def test_existing_full(self):
841        qemu_img('create', '-f', iotests.imgfmt, target_img,
842                 str(self.image_len))
843        qemu_io('-c', 'write -P 42 0 64k', target_img)
844
845        self.assert_no_active_block_jobs()
846        result = self.vm.qmp('drive-mirror', job_id='drive0', device='drive0',
847                             sync='full', target=target_img, mode='existing')
848        self.assert_qmp(result, 'return', {})
849        self.complete_and_wait()
850        self.assert_no_active_block_jobs()
851
852        result = self.vm.qmp('blockdev-del', node_name='drive0')
853        self.assert_qmp(result, 'return', {})
854
855        self.assertTrue(iotests.compare_images(test_img, target_img),
856                        'target image does not match source after mirroring')
857
858    def test_blockdev_full(self):
859        qemu_img('create', '-f', iotests.imgfmt, target_img,
860                 str(self.image_len))
861        qemu_io('-c', 'write -P 42 0 64k', target_img)
862
863        result = self.vm.qmp('blockdev-add', node_name='target',
864                             driver=iotests.imgfmt,
865                             file={
866                                 'driver': 'file',
867                                 'filename': target_img,
868                             })
869        self.assert_qmp(result, 'return', {})
870
871        self.assert_no_active_block_jobs()
872        result = self.vm.qmp('blockdev-mirror', job_id='drive0', device='drive0',
873                             sync='full', target='target')
874        self.assert_qmp(result, 'return', {})
875        self.complete_and_wait()
876        self.assert_no_active_block_jobs()
877
878        result = self.vm.qmp('blockdev-del', node_name='drive0')
879        self.assert_qmp(result, 'return', {})
880
881        result = self.vm.qmp('blockdev-del', node_name='target')
882        self.assert_qmp(result, 'return', {})
883
884        self.assertTrue(iotests.compare_images(test_img, target_img),
885                        'target image does not match source after mirroring')
886
887class TestGranularity(iotests.QMPTestCase):
888    image_len = 10 * 1024 * 1024 # MB
889
890    def setUp(self):
891        qemu_img('create', '-f', iotests.imgfmt, test_img,
892                 str(TestGranularity.image_len))
893        qemu_io('-c', 'write 0 %d' % (self.image_len),
894                test_img)
895        self.vm = iotests.VM().add_drive(test_img)
896        self.vm.launch()
897
898    def tearDown(self):
899        self.vm.shutdown()
900        self.assertTrue(iotests.compare_images(test_img, target_img),
901                        'target image does not match source after mirroring')
902        os.remove(test_img)
903        os.remove(target_img)
904
905    def test_granularity(self):
906        self.assert_no_active_block_jobs()
907        result = self.vm.qmp('drive-mirror', device='drive0',
908                             sync='full', target=target_img,
909                             mode='absolute-paths', granularity=8192)
910        self.assert_qmp(result, 'return', {})
911
912        event = self.vm.get_qmp_event(wait=60.0)
913        while event['event'] == 'JOB_STATUS_CHANGE':
914            self.assert_qmp(event, 'data/id', 'drive0')
915            event = self.vm.get_qmp_event(wait=60.0)
916
917        # Failures will manifest as COMPLETED/ERROR.
918        self.assert_qmp(event, 'event', 'BLOCK_JOB_READY')
919        self.complete_and_wait(drive='drive0', wait_ready=False)
920        self.assert_no_active_block_jobs()
921
922class TestRepairQuorum(iotests.QMPTestCase):
923    """ This class test quorum file repair using drive-mirror.
924        It's mostly a fork of TestSingleDrive """
925    image_len = 1 * 1024 * 1024 # MB
926    IMAGES = [ quorum_img1, quorum_img2, quorum_img3 ]
927
928    @iotests.skip_if_unsupported(['quorum'])
929    def setUp(self):
930        self.vm = iotests.VM()
931
932        if iotests.qemu_default_machine == 'pc':
933            self.vm.add_drive(None, 'media=cdrom', 'ide')
934
935        # Add each individual quorum images
936        for i in self.IMAGES:
937            qemu_img('create', '-f', iotests.imgfmt, i,
938                     str(self.image_len))
939            # Assign a node name to each quorum image in order to manipulate
940            # them
941            opts = "node-name=img%i" % self.IMAGES.index(i)
942            opts += ',driver=%s' % iotests.imgfmt
943            opts += ',file.driver=file'
944            opts += ',file.filename=%s' % i
945            self.vm = self.vm.add_blockdev(opts)
946
947        self.vm.launch()
948
949        #assemble the quorum block device from the individual files
950        args = { "driver": "quorum", "node-name": "quorum0",
951                 "vote-threshold": 2, "children": [ "img0", "img1", "img2" ] }
952        result = self.vm.qmp("blockdev-add", **args)
953        self.assert_qmp(result, 'return', {})
954
955
956    def tearDown(self):
957        self.vm.shutdown()
958        for i in self.IMAGES + [ quorum_repair_img, quorum_snapshot_file,
959                                 nbd_sock_path ]:
960            # Do a try/except because the test may have deleted some images
961            try:
962                os.remove(i)
963            except OSError:
964                pass
965
966    def test_complete(self):
967        result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
968                             sync='full', node_name="repair0", replaces="img1",
969                             target=quorum_repair_img, format=iotests.imgfmt)
970        self.assert_qmp(result, 'return', {})
971
972        self.complete_and_wait(drive="job0")
973        self.assert_has_block_node("repair0", quorum_repair_img)
974        self.vm.assert_block_path('quorum0', '/children.1', 'repair0')
975        self.vm.shutdown()
976        self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img),
977                        'target image does not match source after mirroring')
978
979    def test_cancel(self):
980        result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
981                             sync='full', node_name="repair0", replaces="img1",
982                             target=quorum_repair_img, format=iotests.imgfmt)
983        self.assert_qmp(result, 'return', {})
984
985        self.cancel_and_wait(drive="job0", force=True)
986        # here we check that the last registered quorum file has not been
987        # swapped out and unref
988        self.assert_has_block_node(None, quorum_img3)
989
990    def test_cancel_after_ready(self):
991        result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
992                             sync='full', node_name="repair0", replaces="img1",
993                             target=quorum_repair_img, format=iotests.imgfmt)
994        self.assert_qmp(result, 'return', {})
995
996        self.wait_ready_and_cancel(drive="job0")
997        # here we check that the last registered quorum file has not been
998        # swapped out and unref
999        self.assert_has_block_node(None, quorum_img3)
1000        self.vm.shutdown()
1001        self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img),
1002                        'target image does not match source after mirroring')
1003
1004    def test_pause(self):
1005        result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
1006                             sync='full', node_name="repair0", replaces="img1",
1007                             target=quorum_repair_img, format=iotests.imgfmt)
1008        self.assert_qmp(result, 'return', {})
1009
1010        self.pause_job('job0')
1011
1012        result = self.vm.qmp('query-block-jobs')
1013        offset = self.dictpath(result, 'return[0]/offset')
1014
1015        time.sleep(0.5)
1016        result = self.vm.qmp('query-block-jobs')
1017        self.assert_qmp(result, 'return[0]/offset', offset)
1018
1019        result = self.vm.qmp('block-job-resume', device='job0')
1020        self.assert_qmp(result, 'return', {})
1021
1022        self.complete_and_wait(drive="job0")
1023        self.vm.shutdown()
1024        self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img),
1025                        'target image does not match source after mirroring')
1026
1027    def test_medium_not_found(self):
1028        if iotests.qemu_default_machine != 'pc':
1029            return
1030
1031        result = self.vm.qmp('drive-mirror', job_id='job0', device='drive0', # CD-ROM
1032                             sync='full',
1033                             node_name='repair0',
1034                             replaces='img1',
1035                             target=quorum_repair_img, format=iotests.imgfmt)
1036        self.assert_qmp(result, 'error/class', 'GenericError')
1037
1038    def test_image_not_found(self):
1039        result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
1040                             sync='full', node_name='repair0', replaces='img1',
1041                             mode='existing', target=quorum_repair_img,
1042                             format=iotests.imgfmt)
1043        self.assert_qmp(result, 'error/class', 'GenericError')
1044
1045    def test_device_not_found(self):
1046        result = self.vm.qmp('drive-mirror', job_id='job0',
1047                             device='nonexistent', sync='full',
1048                             node_name='repair0',
1049                             replaces='img1',
1050                             target=quorum_repair_img, format=iotests.imgfmt)
1051        self.assert_qmp(result, 'error/class', 'GenericError')
1052
1053    def test_wrong_sync_mode(self):
1054        result = self.vm.qmp('drive-mirror', device='quorum0', job_id='job0',
1055                             node_name='repair0',
1056                             replaces='img1',
1057                             target=quorum_repair_img, format=iotests.imgfmt)
1058        self.assert_qmp(result, 'error/class', 'GenericError')
1059
1060    def test_no_node_name(self):
1061        result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
1062                             sync='full', replaces='img1',
1063                             target=quorum_repair_img, format=iotests.imgfmt)
1064        self.assert_qmp(result, 'error/class', 'GenericError')
1065
1066    def test_nonexistent_replaces(self):
1067        result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
1068                             sync='full', node_name='repair0', replaces='img77',
1069                             target=quorum_repair_img, format=iotests.imgfmt)
1070        self.assert_qmp(result, 'error/class', 'GenericError')
1071
1072    def test_after_a_quorum_snapshot(self):
1073        result = self.vm.qmp('blockdev-snapshot-sync', node_name='img1',
1074                             snapshot_file=quorum_snapshot_file,
1075                             snapshot_node_name="snap1");
1076
1077        result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
1078                             sync='full', node_name='repair0', replaces="img1",
1079                             target=quorum_repair_img, format=iotests.imgfmt)
1080        self.assert_qmp(result, 'error/class', 'GenericError')
1081
1082        result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
1083                             sync='full', node_name='repair0', replaces="snap1",
1084                             target=quorum_repair_img, format=iotests.imgfmt)
1085        self.assert_qmp(result, 'return', {})
1086
1087        self.complete_and_wait('job0')
1088        self.assert_has_block_node("repair0", quorum_repair_img)
1089        self.vm.assert_block_path('quorum0', '/children.1', 'repair0')
1090
1091    def test_with_other_parent(self):
1092        """
1093        Check that we cannot replace a Quorum child when it has other
1094        parents.
1095        """
1096        result = self.vm.qmp('nbd-server-start',
1097                             addr={
1098                                 'type': 'unix',
1099                                 'data': {'path': nbd_sock_path}
1100                             })
1101        self.assert_qmp(result, 'return', {})
1102
1103        result = self.vm.qmp('nbd-server-add', device='img1')
1104        self.assert_qmp(result, 'return', {})
1105
1106        result = self.vm.qmp('drive-mirror', job_id='mirror', device='quorum0',
1107                             sync='full', node_name='repair0', replaces='img1',
1108                             target=quorum_repair_img, format=iotests.imgfmt)
1109        self.assert_qmp(result, 'error/desc',
1110                        "Cannot replace 'img1' by a node mirrored from "
1111                        "'quorum0', because it cannot be guaranteed that doing "
1112                        "so would not lead to an abrupt change of visible data")
1113
1114    def test_with_other_parents_after_mirror_start(self):
1115        """
1116        The same as test_with_other_parent(), but add the NBD server
1117        only when the mirror job is already running.
1118        """
1119        result = self.vm.qmp('nbd-server-start',
1120                             addr={
1121                                 'type': 'unix',
1122                                 'data': {'path': nbd_sock_path}
1123                             })
1124        self.assert_qmp(result, 'return', {})
1125
1126        result = self.vm.qmp('drive-mirror', job_id='mirror', device='quorum0',
1127                             sync='full', node_name='repair0', replaces='img1',
1128                             target=quorum_repair_img, format=iotests.imgfmt)
1129        self.assert_qmp(result, 'return', {})
1130
1131        result = self.vm.qmp('nbd-server-add', device='img1')
1132        self.assert_qmp(result, 'return', {})
1133
1134        # The full error message goes to stderr, we will check it later
1135        self.complete_and_wait('mirror',
1136                               completion_error='Operation not permitted')
1137
1138        # Should not have been replaced
1139        self.vm.assert_block_path('quorum0', '/children.1', 'img1')
1140
1141        # Check the full error message now
1142        self.vm.shutdown()
1143        log = self.vm.get_log()
1144        log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log)
1145        log = re.sub(r'^Formatting.*\n', '', log)
1146        log = re.sub(r'\n\[I \+\d+\.\d+\] CLOSED\n?$', '', log)
1147        log = re.sub(r'^%s: ' % os.path.basename(iotests.qemu_prog), '', log)
1148
1149        self.assertEqual(log,
1150                         "Can no longer replace 'img1' by 'repair0', because " +
1151                         "it can no longer be guaranteed that doing so would " +
1152                         "not lead to an abrupt change of visible data")
1153
1154
1155# Test mirroring with a source that does not have any parents (not even a
1156# BlockBackend)
1157class TestOrphanedSource(iotests.QMPTestCase):
1158    def setUp(self):
1159        blk0 = { 'node-name': 'src',
1160                 'driver': 'null-co' }
1161
1162        blk1 = { 'node-name': 'dest',
1163                 'driver': 'null-co' }
1164
1165        blk2 = { 'node-name': 'dest-ro',
1166                 'driver': 'null-co',
1167                 'read-only': 'on' }
1168
1169        self.vm = iotests.VM()
1170        self.vm.add_blockdev(self.vm.qmp_to_opts(blk0))
1171        self.vm.add_blockdev(self.vm.qmp_to_opts(blk1))
1172        self.vm.add_blockdev(self.vm.qmp_to_opts(blk2))
1173        self.vm.launch()
1174
1175    def tearDown(self):
1176        self.vm.shutdown()
1177
1178    def test_no_job_id(self):
1179        self.assert_no_active_block_jobs()
1180
1181        result = self.vm.qmp('blockdev-mirror', device='src', sync='full',
1182                             target='dest')
1183        self.assert_qmp(result, 'error/class', 'GenericError')
1184
1185    def test_success(self):
1186        self.assert_no_active_block_jobs()
1187
1188        result = self.vm.qmp('blockdev-mirror', job_id='job', device='src',
1189                             sync='full', target='dest')
1190        self.assert_qmp(result, 'return', {})
1191
1192        self.complete_and_wait('job')
1193
1194    def test_failing_permissions(self):
1195        self.assert_no_active_block_jobs()
1196
1197        result = self.vm.qmp('blockdev-mirror', device='src', sync='full',
1198                             target='dest-ro')
1199        self.assert_qmp(result, 'error/class', 'GenericError')
1200
1201    def test_failing_permission_in_complete(self):
1202        self.assert_no_active_block_jobs()
1203
1204        # Unshare consistent-read on the target
1205        # (The mirror job does not care)
1206        result = self.vm.qmp('blockdev-add',
1207                             driver='blkdebug',
1208                             node_name='dest-perm',
1209                             image='dest',
1210                             unshare_child_perms=['consistent-read'])
1211        self.assert_qmp(result, 'return', {})
1212
1213        result = self.vm.qmp('blockdev-mirror', job_id='job', device='src',
1214                             sync='full', target='dest',
1215                             filter_node_name='mirror-filter')
1216        self.assert_qmp(result, 'return', {})
1217
1218        # Require consistent-read on the source
1219        # (We can only add this node once the job has started, or it
1220        # will complain that it does not want to run on non-root nodes)
1221        result = self.vm.qmp('blockdev-add',
1222                             driver='blkdebug',
1223                             node_name='src-perm',
1224                             image='src',
1225                             take_child_perms=['consistent-read'])
1226        self.assert_qmp(result, 'return', {})
1227
1228        # While completing, mirror will attempt to replace src by
1229        # dest, which must fail because src-perm requires
1230        # consistent-read but dest-perm does not share it; thus
1231        # aborting the job when it is supposed to complete
1232        self.complete_and_wait('job',
1233                               completion_error='Operation not permitted')
1234
1235        # Assert that all of our nodes are still there (except for the
1236        # mirror filter, which should be gone despite the failure)
1237        nodes = self.vm.qmp('query-named-block-nodes')['return']
1238        nodes = [node['node-name'] for node in nodes]
1239
1240        for expect in ('src', 'src-perm', 'dest', 'dest-perm'):
1241            self.assertTrue(expect in nodes, '%s disappeared' % expect)
1242        self.assertFalse('mirror-filter' in nodes,
1243                         'Mirror filter node did not disappear')
1244
1245# Test cases for @replaces that do not necessarily involve Quorum
1246class TestReplaces(iotests.QMPTestCase):
1247    # Each of these test cases needs their own block graph, so do not
1248    # create any nodes here
1249    def setUp(self):
1250        self.vm = iotests.VM()
1251        self.vm.launch()
1252
1253    def tearDown(self):
1254        self.vm.shutdown()
1255        for img in (test_img, target_img):
1256            try:
1257                os.remove(img)
1258            except OSError:
1259                pass
1260
1261    @iotests.skip_if_unsupported(['copy-on-read'])
1262    def test_replace_filter(self):
1263        """
1264        Check that we can replace filter nodes.
1265        """
1266        result = self.vm.qmp('blockdev-add', **{
1267                                 'driver': 'copy-on-read',
1268                                 'node-name': 'filter0',
1269                                 'file': {
1270                                     'driver': 'copy-on-read',
1271                                     'node-name': 'filter1',
1272                                     'file': {
1273                                         'driver': 'null-co'
1274                                     }
1275                                 }
1276                             })
1277        self.assert_qmp(result, 'return', {})
1278
1279        result = self.vm.qmp('blockdev-add',
1280                             node_name='target', driver='null-co')
1281        self.assert_qmp(result, 'return', {})
1282
1283        result = self.vm.qmp('blockdev-mirror', job_id='mirror', device='filter0',
1284                             target='target', sync='full', replaces='filter1')
1285        self.assert_qmp(result, 'return', {})
1286
1287        self.complete_and_wait('mirror')
1288
1289        self.vm.assert_block_path('filter0', '/file', 'target')
1290
1291if __name__ == '__main__':
1292    iotests.main(supported_fmts=['qcow2', 'qed'],
1293                 supported_protocols=['file'],
1294                 supported_platforms=['linux', 'freebsd', 'netbsd', 'openbsd'])
1295