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