]>
git.proxmox.com Git - mirror_qemu.git/blob - tests/qemu-iotests/041
3 # Tests for image mirroring.
5 # Copyright (C) 2012 Red Hat, Inc.
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.
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.
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/>.
25 from iotests
import qemu_img
, qemu_io
27 backing_img
= os
.path
.join(iotests
.test_dir
, 'backing.img')
28 target_backing_img
= os
.path
.join(iotests
.test_dir
, 'target-backing.img')
29 test_img
= os
.path
.join(iotests
.test_dir
, 'test.img')
30 target_img
= os
.path
.join(iotests
.test_dir
, 'target.img')
32 quorum_img1
= os
.path
.join(iotests
.test_dir
, 'quorum1.img')
33 quorum_img2
= os
.path
.join(iotests
.test_dir
, 'quorum2.img')
34 quorum_img3
= os
.path
.join(iotests
.test_dir
, 'quorum3.img')
35 quorum_repair_img
= os
.path
.join(iotests
.test_dir
, 'quorum_repair.img')
36 quorum_snapshot_file
= os
.path
.join(iotests
.test_dir
, 'quorum_snapshot.img')
38 nbd_sock_path
= os
.path
.join(iotests
.sock_dir
, 'nbd.sock')
40 class TestSingleDrive(iotests
.QMPTestCase
):
41 image_len
= 1 * 1024 * 1024 # MB
42 qmp_cmd
= 'drive-mirror'
43 qmp_target
= target_img
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')
57 os
.remove(backing_img
)
63 def test_complete(self
):
64 self
.assert_no_active_block_jobs()
66 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
67 target
=self
.qmp_target
)
68 self
.assert_qmp(result
, 'return', {})
70 self
.complete_and_wait()
71 result
= self
.vm
.qmp('query-block')
72 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
74 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
75 'target image does not match source after mirroring')
77 def test_cancel(self
):
78 self
.assert_no_active_block_jobs()
80 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
81 target
=self
.qmp_target
)
82 self
.assert_qmp(result
, 'return', {})
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
)
88 def test_cancel_after_ready(self
):
89 self
.assert_no_active_block_jobs()
91 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
92 target
=self
.qmp_target
)
93 self
.assert_qmp(result
, 'return', {})
95 self
.wait_ready_and_cancel()
96 result
= self
.vm
.qmp('query-block')
97 self
.assert_qmp(result
, 'return[0]/inserted/file', test_img
)
99 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
100 'target image does not match source after mirroring')
102 def test_pause(self
):
103 self
.assert_no_active_block_jobs()
105 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
106 target
=self
.qmp_target
)
107 self
.assert_qmp(result
, 'return', {})
109 self
.pause_job('drive0')
111 result
= self
.vm
.qmp('query-block-jobs')
112 offset
= self
.dictpath(result
, 'return[0]/offset')
115 result
= self
.vm
.qmp('query-block-jobs')
116 self
.assert_qmp(result
, 'return[0]/offset', offset
)
118 result
= self
.vm
.qmp('block-job-resume', device
='drive0')
119 self
.assert_qmp(result
, 'return', {})
121 self
.complete_and_wait()
123 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
124 'target image does not match source after mirroring')
126 def test_small_buffer(self
):
127 self
.assert_no_active_block_jobs()
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', {})
134 self
.complete_and_wait()
135 result
= self
.vm
.qmp('query-block')
136 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
138 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
139 'target image does not match source after mirroring')
141 def test_small_buffer2(self
):
142 self
.assert_no_active_block_jobs()
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', {})
150 self
.complete_and_wait()
151 result
= self
.vm
.qmp('query-block')
152 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
154 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
155 'target image does not match source after mirroring')
157 def test_large_cluster(self
):
158 self
.assert_no_active_block_jobs()
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', {})
167 self
.complete_and_wait()
168 result
= self
.vm
.qmp('query-block')
169 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
171 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
172 'target image does not match source after mirroring')
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()
179 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
180 target
=self
.qmp_target
)
181 self
.assert_qmp(result
, 'return', {})
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
)
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')
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
)
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')
208 def test_medium_not_found(self
):
209 if iotests
.qemu_default_machine
!= 'pc':
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')
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')
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')
226 class TestSingleBlockdev(TestSingleDrive
):
227 qmp_cmd
= 'blockdev-mirror'
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', {})
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')
246 def do_test_resize(self
, device
, node
):
249 result
= self
.vm
.qmp('block_resize', device
=device
, size
=65536)
250 self
.assert_qmp(result
, 'error/class', 'GenericError')
252 result
= self
.vm
.qmp('block_resize', node_name
=node
, size
=65536)
253 self
.assert_qmp(result
, 'error/class', 'GenericError')
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', {})
260 result
= self
.vm
.run_job('job0', auto_finalize
=False,
261 pre_finalize
=pre_finalize
)
262 self
.assertEqual(result
, None)
264 def test_source_resize(self
):
265 self
.do_test_resize('drive0', 'top')
267 def test_target_resize(self
):
268 self
.do_test_resize(None, self
.qmp_target
)
270 def do_test_target_size(self
, size
):
271 result
= self
.vm
.qmp('block_resize', node_name
=self
.qmp_target
,
273 self
.assert_qmp(result
, 'return', {})
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', {})
280 result
= self
.vm
.run_job('job0')
281 self
.assertEqual(result
, 'Source and target image have different sizes')
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)
288 def test_large_target(self
):
289 self
.do_test_target_size(self
.image_len
* 2)
291 test_large_cluster
= None
292 test_image_not_found
= None
293 test_small_buffer2
= None
295 class TestSingleDriveZeroLength(TestSingleDrive
):
297 test_small_buffer2
= None
298 test_large_cluster
= None
300 class TestSingleBlockdevZeroLength(TestSingleBlockdev
):
302 test_small_target
= None
303 test_large_target
= None
305 class TestSingleDriveUnalignedLength(TestSingleDrive
):
306 image_len
= 1025 * 1024
307 test_small_buffer2
= None
308 test_large_cluster
= None
310 class TestSingleBlockdevUnalignedLength(TestSingleBlockdev
):
311 image_len
= 1025 * 1024
313 class TestMirrorNoBacking(iotests
.QMPTestCase
):
314 image_len
= 2 * 1024 * 1024 # MB
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
)
326 os
.remove(backing_img
)
328 os
.remove(target_backing_img
)
331 os
.remove(target_img
)
333 def test_complete(self
):
334 self
.assert_no_active_block_jobs()
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', {})
342 self
.complete_and_wait()
343 result
= self
.vm
.qmp('query-block')
344 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
346 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
347 'target image does not match source after mirroring')
349 def test_cancel(self
):
350 self
.assert_no_active_block_jobs()
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', {})
358 self
.wait_ready_and_cancel()
359 result
= self
.vm
.qmp('query-block')
360 self
.assert_qmp(result
, 'return[0]/inserted/file', test_img
)
362 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
363 'target image does not match source after mirroring')
365 def test_large_cluster(self
):
366 self
.assert_no_active_block_jobs()
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
)
375 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
376 mode
='existing', target
=target_img
)
377 self
.assert_qmp(result
, 'return', {})
379 self
.complete_and_wait()
380 result
= self
.vm
.qmp('query-block')
381 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
383 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
384 'target image does not match source after mirroring')
386 class TestMirrorResized(iotests
.QMPTestCase
):
387 backing_len
= 1 * 1024 * 1024 # MB
388 image_len
= 2 * 1024 * 1024 # MB
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
)
401 os
.remove(backing_img
)
403 os
.remove(target_img
)
407 def test_complete_top(self
):
408 self
.assert_no_active_block_jobs()
410 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='top',
412 self
.assert_qmp(result
, 'return', {})
414 self
.complete_and_wait()
415 result
= self
.vm
.qmp('query-block')
416 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
418 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
419 'target image does not match source after mirroring')
421 def test_complete_full(self
):
422 self
.assert_no_active_block_jobs()
424 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
426 self
.assert_qmp(result
, 'return', {})
428 self
.complete_and_wait()
429 result
= self
.vm
.qmp('query-block')
430 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
432 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
433 'target image does not match source after mirroring')
435 class TestReadErrors(iotests
.QMPTestCase
):
436 image_len
= 2 * 1024 * 1024 # MB
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
442 def create_blkdebug_file(self
, name
, event
, errno
):
443 file = open(name
, 'w')
462 ''' % (event
, errno
, self
.MIRROR_GRANULARITY
// 512, event
, event
))
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
),
473 # Write something for tests that use sync='top'
474 qemu_io('-c', 'write %d 512' % (self
.MIRROR_GRANULARITY
+ 65536),
476 self
.vm
= iotests
.VM().add_drive(test_img
)
482 os
.remove(target_img
)
483 os
.remove(backing_img
)
484 os
.remove(self
.blkdebug_file
)
486 def test_report_read(self
):
487 self
.assert_no_active_block_jobs()
489 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
491 self
.assert_qmp(result
, 'return', {})
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')
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')
509 elif event
['event'] == 'JOB_STATUS_CHANGE':
510 self
.assert_qmp(event
, 'data/id', 'drive0')
512 self
.assert_no_active_block_jobs()
514 def test_ignore_read(self
):
515 self
.assert_no_active_block_jobs()
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', {})
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)
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()
533 def test_large_cluster(self
):
534 self
.assert_no_active_block_jobs()
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', {})
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)
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()
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')
566 def test_stop_read(self
):
567 self
.assert_no_active_block_jobs()
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', {})
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')
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')
585 result
= self
.vm
.qmp('block-job-resume', device
='drive0')
586 self
.assert_qmp(result
, 'return', {})
588 elif event
['event'] == 'BLOCK_JOB_READY':
589 self
.assertTrue(error
, 'job completed unexpectedly')
590 self
.assert_qmp(event
, 'data/device', 'drive0')
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')
597 self
.complete_and_wait(wait_ready
=False)
598 self
.assert_no_active_block_jobs()
600 class TestWriteErrors(iotests
.QMPTestCase
):
601 image_len
= 2 * 1024 * 1024 # MB
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
607 def create_blkdebug_file(self
, name
, event
, errno
):
608 file = open(name
, 'w')
627 ''' % (event
, errno
, self
.MIRROR_GRANULARITY
// 512, event
, event
))
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
)
644 os
.remove(target_img
)
645 os
.remove(backing_img
)
646 os
.remove(self
.blkdebug_file
)
648 def test_report_write(self
):
649 self
.assert_no_active_block_jobs()
651 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
652 mode
='existing', target
=self
.target_img
)
653 self
.assert_qmp(result
, 'return', {})
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')
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')
672 self
.assert_no_active_block_jobs()
674 def test_ignore_write(self
):
675 self
.assert_no_active_block_jobs()
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', {})
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()
690 def test_stop_write(self
):
691 self
.assert_no_active_block_jobs()
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', {})
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')
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')
710 result
= self
.vm
.qmp('block-job-resume', device
='drive0')
711 self
.assert_qmp(result
, 'return', {})
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')
717 elif event
['event'] == 'BLOCK_JOB_READY':
718 self
.assertTrue(error
, 'job completed unexpectedly')
719 self
.assert_qmp(event
, 'data/device', 'drive0')
722 self
.complete_and_wait(wait_ready
=False)
723 self
.assert_no_active_block_jobs()
725 class TestSetSpeed(iotests
.QMPTestCase
):
726 image_len
= 80 * 1024 * 1024 # MB
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
)
738 os
.remove(backing_img
)
739 os
.remove(target_img
)
741 def test_set_speed(self
):
742 self
.assert_no_active_block_jobs()
744 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
746 self
.assert_qmp(result
, 'return', {})
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)
753 result
= self
.vm
.qmp('block-job-set-speed', device
='drive0', speed
=8 * 1024 * 1024)
754 self
.assert_qmp(result
, 'return', {})
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)
761 self
.wait_ready_and_cancel()
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', {})
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)
772 self
.wait_ready_and_cancel()
774 def test_set_speed_invalid(self
):
775 self
.assert_no_active_block_jobs()
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')
781 self
.assert_no_active_block_jobs()
783 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
785 self
.assert_qmp(result
, 'return', {})
787 result
= self
.vm
.qmp('block-job-set-speed', device
='drive0', speed
=-1)
788 self
.assert_qmp(result
, 'error/class', 'GenericError')
790 self
.wait_ready_and_cancel()
792 class TestUnbackedSource(iotests
.QMPTestCase
):
793 image_len
= 2 * 1024 * 1024 # MB
796 qemu_img('create', '-f', iotests
.imgfmt
, test_img
,
797 str(TestUnbackedSource
.image_len
))
798 self
.vm
= iotests
.VM()
800 result
= self
.vm
.qmp('blockdev-add', node_name
='drive0',
801 driver
=iotests
.imgfmt
,
804 'filename': test_img
,
806 self
.assert_qmp(result
, 'return', {})
811 os
.remove(target_img
)
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()
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()
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()
840 def test_existing_full(self
):
841 qemu_img('create', '-f', iotests
.imgfmt
, target_img
,
843 qemu_io('-c', 'write -P 42 0 64k', target_img
)
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()
852 result
= self
.vm
.qmp('blockdev-del', node_name
='drive0')
853 self
.assert_qmp(result
, 'return', {})
855 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
856 'target image does not match source after mirroring')
858 def test_blockdev_full(self
):
859 qemu_img('create', '-f', iotests
.imgfmt
, target_img
,
861 qemu_io('-c', 'write -P 42 0 64k', target_img
)
863 result
= self
.vm
.qmp('blockdev-add', node_name
='target',
864 driver
=iotests
.imgfmt
,
867 'filename': target_img
,
869 self
.assert_qmp(result
, 'return', {})
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()
878 result
= self
.vm
.qmp('blockdev-del', node_name
='drive0')
879 self
.assert_qmp(result
, 'return', {})
881 result
= self
.vm
.qmp('blockdev-del', node_name
='target')
882 self
.assert_qmp(result
, 'return', {})
884 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
885 'target image does not match source after mirroring')
887 class TestGranularity(iotests
.QMPTestCase
):
888 image_len
= 10 * 1024 * 1024 # MB
891 qemu_img('create', '-f', iotests
.imgfmt
, test_img
,
892 str(TestGranularity
.image_len
))
893 qemu_io('-c', 'write 0 %d' % (self
.image_len
),
895 self
.vm
= iotests
.VM().add_drive(test_img
)
900 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
901 'target image does not match source after mirroring')
903 os
.remove(target_img
)
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', {})
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)
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()
922 class 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
]
928 @iotests.skip_if_unsupported(['quorum'])
930 self
.vm
= iotests
.VM()
932 if iotests
.qemu_default_machine
== 'pc':
933 self
.vm
.add_drive(None, 'media=cdrom', 'ide')
935 # Add each individual quorum images
936 for i
in self
.IMAGES
:
937 qemu_img('create', '-f', iotests
.imgfmt
, i
,
939 # Assign a node name to each quorum image in order to manipulate
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
)
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', {})
958 for i
in self
.IMAGES
+ [ quorum_repair_img
, quorum_snapshot_file
,
960 # Do a try/except because the test may have deleted some images
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', {})
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')
976 self
.assertTrue(iotests
.compare_images(quorum_img2
, quorum_repair_img
),
977 'target image does not match source after mirroring')
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', {})
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
)
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', {})
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
)
1001 self
.assertTrue(iotests
.compare_images(quorum_img2
, quorum_repair_img
),
1002 'target image does not match source after mirroring')
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', {})
1010 self
.pause_job('job0')
1012 result
= self
.vm
.qmp('query-block-jobs')
1013 offset
= self
.dictpath(result
, 'return[0]/offset')
1016 result
= self
.vm
.qmp('query-block-jobs')
1017 self
.assert_qmp(result
, 'return[0]/offset', offset
)
1019 result
= self
.vm
.qmp('block-job-resume', device
='job0')
1020 self
.assert_qmp(result
, 'return', {})
1022 self
.complete_and_wait(drive
="job0")
1024 self
.assertTrue(iotests
.compare_images(quorum_img2
, quorum_repair_img
),
1025 'target image does not match source after mirroring')
1027 def test_medium_not_found(self
):
1028 if iotests
.qemu_default_machine
!= 'pc':
1031 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='drive0', # CD-ROM
1033 node_name
='repair0',
1035 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
1036 self
.assert_qmp(result
, 'error/class', 'GenericError')
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')
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',
1050 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
1051 self
.assert_qmp(result
, 'error/class', 'GenericError')
1053 def test_wrong_sync_mode(self
):
1054 result
= self
.vm
.qmp('drive-mirror', device
='quorum0', job_id
='job0',
1055 node_name
='repair0',
1057 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
1058 self
.assert_qmp(result
, 'error/class', 'GenericError')
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')
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')
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");
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')
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', {})
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')
1091 def test_with_other_parent(self
):
1093 Check that we cannot replace a Quorum child when it has other
1096 result
= self
.vm
.qmp('nbd-server-start',
1099 'data': {'path': nbd_sock_path
}
1101 self
.assert_qmp(result
, 'return', {})
1103 result
= self
.vm
.qmp('nbd-server-add', device
='img1')
1104 self
.assert_qmp(result
, 'return', {})
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")
1114 def test_with_other_parents_after_mirror_start(self
):
1116 The same as test_with_other_parent(), but add the NBD server
1117 only when the mirror job is already running.
1119 result
= self
.vm
.qmp('nbd-server-start',
1122 'data': {'path': nbd_sock_path
}
1124 self
.assert_qmp(result
, 'return', {})
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', {})
1131 result
= self
.vm
.qmp('nbd-server-add', device
='img1')
1132 self
.assert_qmp(result
, 'return', {})
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')
1138 # Should not have been replaced
1139 self
.vm
.assert_block_path('quorum0', '/children.1', 'img1')
1141 # Check the full error message now
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
)
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")
1155 # Test mirroring with a source that does not have any parents (not even a
1157 class TestOrphanedSource(iotests
.QMPTestCase
):
1159 blk0
= { 'node-name': 'src',
1160 'driver': 'null-co' }
1162 blk1
= { 'node-name': 'dest',
1163 'driver': 'null-co' }
1165 blk2
= { 'node-name': 'dest-ro',
1166 'driver': 'null-co',
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
))
1178 def test_no_job_id(self
):
1179 self
.assert_no_active_block_jobs()
1181 result
= self
.vm
.qmp('blockdev-mirror', device
='src', sync
='full',
1183 self
.assert_qmp(result
, 'error/class', 'GenericError')
1185 def test_success(self
):
1186 self
.assert_no_active_block_jobs()
1188 result
= self
.vm
.qmp('blockdev-mirror', job_id
='job', device
='src',
1189 sync
='full', target
='dest')
1190 self
.assert_qmp(result
, 'return', {})
1192 self
.complete_and_wait('job')
1194 def test_failing_permissions(self
):
1195 self
.assert_no_active_block_jobs()
1197 result
= self
.vm
.qmp('blockdev-mirror', device
='src', sync
='full',
1199 self
.assert_qmp(result
, 'error/class', 'GenericError')
1201 def test_failing_permission_in_complete(self
):
1202 self
.assert_no_active_block_jobs()
1204 # Unshare consistent-read on the target
1205 # (The mirror job does not care)
1206 result
= self
.vm
.qmp('blockdev-add',
1208 node_name
='dest-perm',
1210 unshare_child_perms
=['consistent-read'])
1211 self
.assert_qmp(result
, 'return', {})
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', {})
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',
1223 node_name
='src-perm',
1225 take_child_perms
=['consistent-read'])
1226 self
.assert_qmp(result
, 'return', {})
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')
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
]
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')
1245 # Test cases for @replaces that do not necessarily involve Quorum
1246 class TestReplaces(iotests
.QMPTestCase
):
1247 # Each of these test cases needs their own block graph, so do not
1248 # create any nodes here
1250 self
.vm
= iotests
.VM()
1255 for img
in (test_img
, target_img
):
1261 @iotests.skip_if_unsupported(['copy-on-read'])
1262 def test_replace_filter(self
):
1264 Check that we can replace filter nodes.
1266 result
= self
.vm
.qmp('blockdev-add', **{
1267 'driver': 'copy-on-read',
1268 'node-name': 'filter0',
1270 'driver': 'copy-on-read',
1271 'node-name': 'filter1',
1277 self
.assert_qmp(result
, 'return', {})
1279 result
= self
.vm
.qmp('blockdev-add',
1280 node_name
='target', driver
='null-co')
1281 self
.assert_qmp(result
, 'return', {})
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', {})
1287 self
.complete_and_wait('mirror')
1289 self
.vm
.assert_block_path('filter0', '/file', 'target')
1291 if __name__
== '__main__':
1292 iotests
.main(supported_fmts
=['qcow2', 'qed'],
1293 supported_protocols
=['file'],
1294 supported_platforms
=['linux', 'freebsd', 'netbsd', 'openbsd'])