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/>.
24 from iotests
import qemu_img
, qemu_io
26 backing_img
= os
.path
.join(iotests
.test_dir
, 'backing.img')
27 target_backing_img
= os
.path
.join(iotests
.test_dir
, 'target-backing.img')
28 test_img
= os
.path
.join(iotests
.test_dir
, 'test.img')
29 target_img
= os
.path
.join(iotests
.test_dir
, 'target.img')
31 quorum_img1
= os
.path
.join(iotests
.test_dir
, 'quorum1.img')
32 quorum_img2
= os
.path
.join(iotests
.test_dir
, 'quorum2.img')
33 quorum_img3
= os
.path
.join(iotests
.test_dir
, 'quorum3.img')
34 quorum_repair_img
= os
.path
.join(iotests
.test_dir
, 'quorum_repair.img')
35 quorum_snapshot_file
= os
.path
.join(iotests
.test_dir
, 'quorum_snapshot.img')
37 class TestSingleDrive(iotests
.QMPTestCase
):
38 image_len
= 1 * 1024 * 1024 # MB
39 qmp_cmd
= 'drive-mirror'
40 qmp_target
= target_img
43 iotests
.create_image(backing_img
, self
.image_len
)
44 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'backing_file=%s' % backing_img
, test_img
)
45 self
.vm
= iotests
.VM().add_drive(test_img
, "node-name=top,backing.node-name=base")
46 if iotests
.qemu_default_machine
== 'pc':
47 self
.vm
.add_drive(None, 'media=cdrom', 'ide')
53 os
.remove(backing_img
)
59 def test_complete(self
):
60 self
.assert_no_active_block_jobs()
62 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
63 target
=self
.qmp_target
)
64 self
.assert_qmp(result
, 'return', {})
66 self
.complete_and_wait()
67 result
= self
.vm
.qmp('query-block')
68 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
70 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
71 'target image does not match source after mirroring')
73 def test_cancel(self
):
74 self
.assert_no_active_block_jobs()
76 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
77 target
=self
.qmp_target
)
78 self
.assert_qmp(result
, 'return', {})
80 self
.cancel_and_wait(force
=True)
81 result
= self
.vm
.qmp('query-block')
82 self
.assert_qmp(result
, 'return[0]/inserted/file', test_img
)
85 def test_cancel_after_ready(self
):
86 self
.assert_no_active_block_jobs()
88 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
89 target
=self
.qmp_target
)
90 self
.assert_qmp(result
, 'return', {})
92 self
.wait_ready_and_cancel()
93 result
= self
.vm
.qmp('query-block')
94 self
.assert_qmp(result
, 'return[0]/inserted/file', test_img
)
96 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
97 'target image does not match source after mirroring')
100 self
.assert_no_active_block_jobs()
102 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
103 target
=self
.qmp_target
)
104 self
.assert_qmp(result
, 'return', {})
106 self
.pause_job('drive0')
108 result
= self
.vm
.qmp('query-block-jobs')
109 offset
= self
.dictpath(result
, 'return[0]/offset')
112 result
= self
.vm
.qmp('query-block-jobs')
113 self
.assert_qmp(result
, 'return[0]/offset', offset
)
115 result
= self
.vm
.qmp('block-job-resume', device
='drive0')
116 self
.assert_qmp(result
, 'return', {})
118 self
.complete_and_wait()
120 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
121 'target image does not match source after mirroring')
123 def test_small_buffer(self
):
124 self
.assert_no_active_block_jobs()
126 # A small buffer is rounded up automatically
127 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
128 buf_size
=4096, target
=self
.qmp_target
)
129 self
.assert_qmp(result
, 'return', {})
131 self
.complete_and_wait()
132 result
= self
.vm
.qmp('query-block')
133 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
135 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
136 'target image does not match source after mirroring')
138 def test_small_buffer2(self
):
139 self
.assert_no_active_block_jobs()
141 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'cluster_size=%d,size=%d'
142 % (self
.image_len
, self
.image_len
), target_img
)
143 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
144 buf_size
=65536, mode
='existing', target
=self
.qmp_target
)
145 self
.assert_qmp(result
, 'return', {})
147 self
.complete_and_wait()
148 result
= self
.vm
.qmp('query-block')
149 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
151 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
152 'target image does not match source after mirroring')
154 def test_large_cluster(self
):
155 self
.assert_no_active_block_jobs()
157 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'cluster_size=%d,backing_file=%s'
158 % (self
.image_len
, backing_img
), target_img
)
159 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
160 mode
='existing', target
=self
.qmp_target
)
161 self
.assert_qmp(result
, 'return', {})
163 self
.complete_and_wait()
164 result
= self
.vm
.qmp('query-block')
165 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
167 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
168 'target image does not match source after mirroring')
170 # Tests that the insertion of the mirror_top filter node doesn't make a
171 # difference to query-block
172 def test_implicit_node(self
):
173 self
.assert_no_active_block_jobs()
175 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
176 target
=self
.qmp_target
)
177 self
.assert_qmp(result
, 'return', {})
179 result
= self
.vm
.qmp('query-block')
180 self
.assert_qmp(result
, 'return[0]/inserted/file', test_img
)
181 self
.assert_qmp(result
, 'return[0]/inserted/drv', iotests
.imgfmt
)
182 self
.assert_qmp(result
, 'return[0]/inserted/backing_file', backing_img
)
183 self
.assert_qmp(result
, 'return[0]/inserted/backing_file_depth', 1)
184 self
.assert_qmp(result
, 'return[0]/inserted/image/filename', test_img
)
185 self
.assert_qmp(result
, 'return[0]/inserted/image/backing-image/filename', backing_img
)
187 result
= self
.vm
.qmp('query-blockstats')
188 self
.assert_qmp(result
, 'return[0]/node-name', 'top')
189 self
.assert_qmp(result
, 'return[0]/backing/node-name', 'base')
191 self
.cancel_and_wait(force
=True)
192 result
= self
.vm
.qmp('query-block')
193 self
.assert_qmp(result
, 'return[0]/inserted/file', test_img
)
194 self
.assert_qmp(result
, 'return[0]/inserted/drv', iotests
.imgfmt
)
195 self
.assert_qmp(result
, 'return[0]/inserted/backing_file', backing_img
)
196 self
.assert_qmp(result
, 'return[0]/inserted/backing_file_depth', 1)
197 self
.assert_qmp(result
, 'return[0]/inserted/image/filename', test_img
)
198 self
.assert_qmp(result
, 'return[0]/inserted/image/backing-image/filename', backing_img
)
200 result
= self
.vm
.qmp('query-blockstats')
201 self
.assert_qmp(result
, 'return[0]/node-name', 'top')
202 self
.assert_qmp(result
, 'return[0]/backing/node-name', 'base')
206 def test_medium_not_found(self
):
207 if iotests
.qemu_default_machine
!= 'pc':
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')
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')
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')
224 class TestSingleBlockdev(TestSingleDrive
):
225 qmp_cmd
= 'blockdev-mirror'
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', {})
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')
243 test_large_cluster
= None
244 test_image_not_found
= None
245 test_small_buffer2
= None
247 class TestSingleDriveZeroLength(TestSingleDrive
):
249 test_small_buffer2
= None
250 test_large_cluster
= None
252 class TestSingleBlockdevZeroLength(TestSingleBlockdev
):
255 class TestSingleDriveUnalignedLength(TestSingleDrive
):
256 image_len
= 1025 * 1024
257 test_small_buffer2
= None
258 test_large_cluster
= None
260 class TestSingleBlockdevUnalignedLength(TestSingleBlockdev
):
261 image_len
= 1025 * 1024
263 class TestMirrorNoBacking(iotests
.QMPTestCase
):
264 image_len
= 2 * 1024 * 1024 # MB
267 iotests
.create_image(backing_img
, TestMirrorNoBacking
.image_len
)
268 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'backing_file=%s' % backing_img
, test_img
)
269 self
.vm
= iotests
.VM().add_drive(test_img
)
275 os
.remove(backing_img
)
277 os
.remove(target_backing_img
)
280 os
.remove(target_img
)
282 def test_complete(self
):
283 self
.assert_no_active_block_jobs()
285 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'backing_file=%s' % backing_img
, target_img
)
286 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
287 mode
='existing', target
=target_img
)
288 self
.assert_qmp(result
, 'return', {})
290 self
.complete_and_wait()
291 result
= self
.vm
.qmp('query-block')
292 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
294 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
295 'target image does not match source after mirroring')
297 def test_cancel(self
):
298 self
.assert_no_active_block_jobs()
300 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'backing_file=%s' % backing_img
, target_img
)
301 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
302 mode
='existing', target
=target_img
)
303 self
.assert_qmp(result
, 'return', {})
305 self
.wait_ready_and_cancel()
306 result
= self
.vm
.qmp('query-block')
307 self
.assert_qmp(result
, 'return[0]/inserted/file', test_img
)
309 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
310 'target image does not match source after mirroring')
312 def test_large_cluster(self
):
313 self
.assert_no_active_block_jobs()
315 # qemu-img create fails if the image is not there
316 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'size=%d'
317 %(TestMirrorNoBacking
.image_len
), target_backing_img
)
318 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'cluster_size=%d,backing_file=%s'
319 % (TestMirrorNoBacking
.image_len
, target_backing_img
), target_img
)
321 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
322 mode
='existing', target
=target_img
)
323 self
.assert_qmp(result
, 'return', {})
325 self
.complete_and_wait()
326 result
= self
.vm
.qmp('query-block')
327 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
329 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
330 'target image does not match source after mirroring')
332 class TestMirrorResized(iotests
.QMPTestCase
):
333 backing_len
= 1 * 1024 * 1024 # MB
334 image_len
= 2 * 1024 * 1024 # MB
337 iotests
.create_image(backing_img
, TestMirrorResized
.backing_len
)
338 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'backing_file=%s' % backing_img
, test_img
)
339 qemu_img('resize', test_img
, '2M')
340 self
.vm
= iotests
.VM().add_drive(test_img
)
346 os
.remove(backing_img
)
348 os
.remove(target_img
)
352 def test_complete_top(self
):
353 self
.assert_no_active_block_jobs()
355 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='top',
357 self
.assert_qmp(result
, 'return', {})
359 self
.complete_and_wait()
360 result
= self
.vm
.qmp('query-block')
361 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
363 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
364 'target image does not match source after mirroring')
366 def test_complete_full(self
):
367 self
.assert_no_active_block_jobs()
369 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
371 self
.assert_qmp(result
, 'return', {})
373 self
.complete_and_wait()
374 result
= self
.vm
.qmp('query-block')
375 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
377 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
378 'target image does not match source after mirroring')
380 class TestReadErrors(iotests
.QMPTestCase
):
381 image_len
= 2 * 1024 * 1024 # MB
383 # this should be a multiple of twice the default granularity
384 # so that we hit this offset first in state 1
385 MIRROR_GRANULARITY
= 1024 * 1024
387 def create_blkdebug_file(self
, name
, event
, errno
):
388 file = open(name
, 'w')
407 ''' % (event
, errno
, self
.MIRROR_GRANULARITY
// 512, event
, event
))
411 self
.blkdebug_file
= backing_img
+ ".blkdebug"
412 iotests
.create_image(backing_img
, TestReadErrors
.image_len
)
413 self
.create_blkdebug_file(self
.blkdebug_file
, "read_aio", 5)
414 qemu_img('create', '-f', iotests
.imgfmt
,
415 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
416 % (self
.blkdebug_file
, backing_img
),
418 # Write something for tests that use sync='top'
419 qemu_io('-c', 'write %d 512' % (self
.MIRROR_GRANULARITY
+ 65536),
421 self
.vm
= iotests
.VM().add_drive(test_img
)
427 os
.remove(target_img
)
428 os
.remove(backing_img
)
429 os
.remove(self
.blkdebug_file
)
431 def test_report_read(self
):
432 self
.assert_no_active_block_jobs()
434 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
436 self
.assert_qmp(result
, 'return', {})
441 for event
in self
.vm
.get_qmp_events(wait
=True):
442 if event
['event'] == 'BLOCK_JOB_ERROR':
443 self
.assert_qmp(event
, 'data/device', 'drive0')
444 self
.assert_qmp(event
, 'data/operation', 'read')
446 elif event
['event'] == 'BLOCK_JOB_READY':
447 self
.assertTrue(False, 'job completed unexpectedly')
448 elif event
['event'] == 'BLOCK_JOB_COMPLETED':
449 self
.assertTrue(error
, 'job completed unexpectedly')
450 self
.assert_qmp(event
, 'data/type', 'mirror')
451 self
.assert_qmp(event
, 'data/device', 'drive0')
452 self
.assert_qmp(event
, 'data/error', 'Input/output error')
454 elif event
['event'] == 'JOB_STATUS_CHANGE':
455 self
.assert_qmp(event
, 'data/id', 'drive0')
457 self
.assert_no_active_block_jobs()
460 def test_ignore_read(self
):
461 self
.assert_no_active_block_jobs()
463 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
464 target
=target_img
, on_source_error
='ignore')
465 self
.assert_qmp(result
, 'return', {})
467 event
= self
.vm
.get_qmp_event(wait
=True)
468 while event
['event'] == 'JOB_STATUS_CHANGE':
469 self
.assert_qmp(event
, 'data/id', 'drive0')
470 event
= self
.vm
.get_qmp_event(wait
=True)
472 self
.assertEqual(event
['event'], 'BLOCK_JOB_ERROR')
473 self
.assert_qmp(event
, 'data/device', 'drive0')
474 self
.assert_qmp(event
, 'data/operation', 'read')
475 result
= self
.vm
.qmp('query-block-jobs')
476 self
.assert_qmp(result
, 'return[0]/paused', False)
477 self
.complete_and_wait()
480 def test_large_cluster(self
):
481 self
.assert_no_active_block_jobs()
483 # Test COW into the target image. The first half of the
484 # cluster at MIRROR_GRANULARITY has to be copied from
485 # backing_img, even though sync='top'.
486 qemu_img('create', '-f', iotests
.imgfmt
, '-ocluster_size=131072,backing_file=%s' %(backing_img), target_img
)
487 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='top',
488 on_source_error
='ignore',
489 mode
='existing', target
=target_img
)
490 self
.assert_qmp(result
, 'return', {})
492 event
= self
.vm
.get_qmp_event(wait
=True)
493 while event
['event'] == 'JOB_STATUS_CHANGE':
494 self
.assert_qmp(event
, 'data/id', 'drive0')
495 event
= self
.vm
.get_qmp_event(wait
=True)
497 self
.assertEqual(event
['event'], 'BLOCK_JOB_ERROR')
498 self
.assert_qmp(event
, 'data/device', 'drive0')
499 self
.assert_qmp(event
, 'data/operation', 'read')
500 result
= self
.vm
.qmp('query-block-jobs')
501 self
.assert_qmp(result
, 'return[0]/paused', False)
502 self
.complete_and_wait()
505 # Detach blkdebug to compare images successfully
506 qemu_img('rebase', '-f', iotests
.imgfmt
, '-u', '-b', backing_img
, test_img
)
507 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
508 'target image does not match source after mirroring')
510 def test_stop_read(self
):
511 self
.assert_no_active_block_jobs()
513 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
514 target
=target_img
, on_source_error
='stop')
515 self
.assert_qmp(result
, 'return', {})
520 for event
in self
.vm
.get_qmp_events(wait
=True):
521 if event
['event'] == 'BLOCK_JOB_ERROR':
522 self
.assert_qmp(event
, 'data/device', 'drive0')
523 self
.assert_qmp(event
, 'data/operation', 'read')
525 result
= self
.vm
.qmp('query-block-jobs')
526 self
.assert_qmp(result
, 'return[0]/paused', True)
527 self
.assert_qmp(result
, 'return[0]/io-status', 'failed')
529 result
= self
.vm
.qmp('block-job-resume', device
='drive0')
530 self
.assert_qmp(result
, 'return', {})
532 elif event
['event'] == 'BLOCK_JOB_READY':
533 self
.assertTrue(error
, 'job completed unexpectedly')
534 self
.assert_qmp(event
, 'data/device', 'drive0')
537 result
= self
.vm
.qmp('query-block-jobs')
538 self
.assert_qmp(result
, 'return[0]/paused', False)
539 self
.assert_qmp(result
, 'return[0]/io-status', 'ok')
541 self
.complete_and_wait(wait_ready
=False)
542 self
.assert_no_active_block_jobs()
545 class TestWriteErrors(iotests
.QMPTestCase
):
546 image_len
= 2 * 1024 * 1024 # MB
548 # this should be a multiple of twice the default granularity
549 # so that we hit this offset first in state 1
550 MIRROR_GRANULARITY
= 1024 * 1024
552 def create_blkdebug_file(self
, name
, event
, errno
):
553 file = open(name
, 'w')
572 ''' % (event
, errno
, self
.MIRROR_GRANULARITY
// 512, event
, event
))
576 self
.blkdebug_file
= target_img
+ ".blkdebug"
577 iotests
.create_image(backing_img
, TestWriteErrors
.image_len
)
578 self
.create_blkdebug_file(self
.blkdebug_file
, "write_aio", 5)
579 qemu_img('create', '-f', iotests
.imgfmt
, '-obacking_file=%s' %(backing_img), test_img
)
580 self
.vm
= iotests
.VM().add_drive(test_img
)
581 self
.target_img
= 'blkdebug:%s:%s' % (self
.blkdebug_file
, target_img
)
582 qemu_img('create', '-f', iotests
.imgfmt
, '-osize=%d' %(TestWriteErrors
.image_len
), target_img
)
588 os
.remove(target_img
)
589 os
.remove(backing_img
)
590 os
.remove(self
.blkdebug_file
)
592 def test_report_write(self
):
593 self
.assert_no_active_block_jobs()
595 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
596 mode
='existing', target
=self
.target_img
)
597 self
.assert_qmp(result
, 'return', {})
602 for event
in self
.vm
.get_qmp_events(wait
=True):
603 if event
['event'] == 'BLOCK_JOB_ERROR':
604 self
.assert_qmp(event
, 'data/device', 'drive0')
605 self
.assert_qmp(event
, 'data/operation', 'write')
607 elif event
['event'] == 'BLOCK_JOB_READY':
608 self
.assertTrue(False, 'job completed unexpectedly')
609 elif event
['event'] == 'BLOCK_JOB_COMPLETED':
610 self
.assertTrue(error
, 'job completed unexpectedly')
611 self
.assert_qmp(event
, 'data/type', 'mirror')
612 self
.assert_qmp(event
, 'data/device', 'drive0')
613 self
.assert_qmp(event
, 'data/error', 'Input/output error')
616 self
.assert_no_active_block_jobs()
619 def test_ignore_write(self
):
620 self
.assert_no_active_block_jobs()
622 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
623 mode
='existing', target
=self
.target_img
,
624 on_target_error
='ignore')
625 self
.assert_qmp(result
, 'return', {})
627 event
= self
.vm
.event_wait(name
='BLOCK_JOB_ERROR')
628 self
.assertEqual(event
['event'], 'BLOCK_JOB_ERROR')
629 self
.assert_qmp(event
, 'data/device', 'drive0')
630 self
.assert_qmp(event
, 'data/operation', 'write')
631 result
= self
.vm
.qmp('query-block-jobs')
632 self
.assert_qmp(result
, 'return[0]/paused', False)
633 self
.complete_and_wait()
636 def test_stop_write(self
):
637 self
.assert_no_active_block_jobs()
639 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
640 mode
='existing', target
=self
.target_img
,
641 on_target_error
='stop')
642 self
.assert_qmp(result
, 'return', {})
647 for event
in self
.vm
.get_qmp_events(wait
=True):
648 if event
['event'] == 'BLOCK_JOB_ERROR':
649 self
.assert_qmp(event
, 'data/device', 'drive0')
650 self
.assert_qmp(event
, 'data/operation', 'write')
652 result
= self
.vm
.qmp('query-block-jobs')
653 self
.assert_qmp(result
, 'return[0]/paused', True)
654 self
.assert_qmp(result
, 'return[0]/io-status', 'failed')
656 result
= self
.vm
.qmp('block-job-resume', device
='drive0')
657 self
.assert_qmp(result
, 'return', {})
659 result
= self
.vm
.qmp('query-block-jobs')
660 self
.assert_qmp(result
, 'return[0]/paused', False)
661 self
.assert_qmp(result
, 'return[0]/io-status', 'ok')
663 elif event
['event'] == 'BLOCK_JOB_READY':
664 self
.assertTrue(error
, 'job completed unexpectedly')
665 self
.assert_qmp(event
, 'data/device', 'drive0')
668 self
.complete_and_wait(wait_ready
=False)
669 self
.assert_no_active_block_jobs()
672 class TestSetSpeed(iotests
.QMPTestCase
):
673 image_len
= 80 * 1024 * 1024 # MB
676 qemu_img('create', backing_img
, str(TestSetSpeed
.image_len
))
677 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'backing_file=%s' % backing_img
, test_img
)
678 self
.vm
= iotests
.VM().add_drive(test_img
)
684 os
.remove(backing_img
)
685 os
.remove(target_img
)
687 def test_set_speed(self
):
688 self
.assert_no_active_block_jobs()
690 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
692 self
.assert_qmp(result
, 'return', {})
695 result
= self
.vm
.qmp('query-block-jobs')
696 self
.assert_qmp(result
, 'return[0]/device', 'drive0')
697 self
.assert_qmp(result
, 'return[0]/speed', 0)
699 result
= self
.vm
.qmp('block-job-set-speed', device
='drive0', speed
=8 * 1024 * 1024)
700 self
.assert_qmp(result
, 'return', {})
702 # Ensure the speed we set was accepted
703 result
= self
.vm
.qmp('query-block-jobs')
704 self
.assert_qmp(result
, 'return[0]/device', 'drive0')
705 self
.assert_qmp(result
, 'return[0]/speed', 8 * 1024 * 1024)
707 self
.wait_ready_and_cancel()
709 # Check setting speed in drive-mirror works
710 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
711 target
=target_img
, speed
=4*1024*1024)
712 self
.assert_qmp(result
, 'return', {})
714 result
= self
.vm
.qmp('query-block-jobs')
715 self
.assert_qmp(result
, 'return[0]/device', 'drive0')
716 self
.assert_qmp(result
, 'return[0]/speed', 4 * 1024 * 1024)
718 self
.wait_ready_and_cancel()
720 def test_set_speed_invalid(self
):
721 self
.assert_no_active_block_jobs()
723 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
724 target
=target_img
, speed
=-1)
725 self
.assert_qmp(result
, 'error/class', 'GenericError')
727 self
.assert_no_active_block_jobs()
729 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
731 self
.assert_qmp(result
, 'return', {})
733 result
= self
.vm
.qmp('block-job-set-speed', device
='drive0', speed
=-1)
734 self
.assert_qmp(result
, 'error/class', 'GenericError')
736 self
.wait_ready_and_cancel()
738 class TestUnbackedSource(iotests
.QMPTestCase
):
739 image_len
= 2 * 1024 * 1024 # MB
742 qemu_img('create', '-f', iotests
.imgfmt
, test_img
,
743 str(TestUnbackedSource
.image_len
))
744 self
.vm
= iotests
.VM()
746 result
= self
.vm
.qmp('blockdev-add', node_name
='drive0',
747 driver
=iotests
.imgfmt
,
750 'filename': test_img
,
752 self
.assert_qmp(result
, 'return', {})
757 os
.remove(target_img
)
759 def test_absolute_paths_full(self
):
760 self
.assert_no_active_block_jobs()
761 result
= self
.vm
.qmp('drive-mirror', job_id
='drive0', device
='drive0',
762 sync
='full', target
=target_img
,
763 mode
='absolute-paths')
764 self
.assert_qmp(result
, 'return', {})
765 self
.complete_and_wait()
766 self
.assert_no_active_block_jobs()
768 def test_absolute_paths_top(self
):
769 self
.assert_no_active_block_jobs()
770 result
= self
.vm
.qmp('drive-mirror', job_id
='drive0', device
='drive0',
771 sync
='top', target
=target_img
,
772 mode
='absolute-paths')
773 self
.assert_qmp(result
, 'return', {})
774 self
.complete_and_wait()
775 self
.assert_no_active_block_jobs()
777 def test_absolute_paths_none(self
):
778 self
.assert_no_active_block_jobs()
779 result
= self
.vm
.qmp('drive-mirror', job_id
='drive0', device
='drive0',
780 sync
='none', target
=target_img
,
781 mode
='absolute-paths')
782 self
.assert_qmp(result
, 'return', {})
783 self
.complete_and_wait()
784 self
.assert_no_active_block_jobs()
786 def test_existing_full(self
):
787 qemu_img('create', '-f', iotests
.imgfmt
, target_img
,
789 qemu_io('-c', 'write -P 42 0 64k', target_img
)
791 self
.assert_no_active_block_jobs()
792 result
= self
.vm
.qmp('drive-mirror', job_id
='drive0', device
='drive0',
793 sync
='full', target
=target_img
, mode
='existing')
794 self
.assert_qmp(result
, 'return', {})
795 self
.complete_and_wait()
796 self
.assert_no_active_block_jobs()
798 result
= self
.vm
.qmp('blockdev-del', node_name
='drive0')
799 self
.assert_qmp(result
, 'return', {})
801 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
802 'target image does not match source after mirroring')
804 def test_blockdev_full(self
):
805 qemu_img('create', '-f', iotests
.imgfmt
, target_img
,
807 qemu_io('-c', 'write -P 42 0 64k', target_img
)
809 result
= self
.vm
.qmp('blockdev-add', node_name
='target',
810 driver
=iotests
.imgfmt
,
813 'filename': target_img
,
815 self
.assert_qmp(result
, 'return', {})
817 self
.assert_no_active_block_jobs()
818 result
= self
.vm
.qmp('blockdev-mirror', job_id
='drive0', device
='drive0',
819 sync
='full', target
='target')
820 self
.assert_qmp(result
, 'return', {})
821 self
.complete_and_wait()
822 self
.assert_no_active_block_jobs()
824 result
= self
.vm
.qmp('blockdev-del', node_name
='drive0')
825 self
.assert_qmp(result
, 'return', {})
827 result
= self
.vm
.qmp('blockdev-del', node_name
='target')
828 self
.assert_qmp(result
, 'return', {})
830 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
831 'target image does not match source after mirroring')
833 class TestGranularity(iotests
.QMPTestCase
):
834 image_len
= 10 * 1024 * 1024 # MB
837 qemu_img('create', '-f', iotests
.imgfmt
, test_img
,
838 str(TestGranularity
.image_len
))
839 qemu_io('-c', 'write 0 %d' % (self
.image_len
),
841 self
.vm
= iotests
.VM().add_drive(test_img
)
846 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
847 'target image does not match source after mirroring')
849 os
.remove(target_img
)
851 def test_granularity(self
):
852 self
.assert_no_active_block_jobs()
853 result
= self
.vm
.qmp('drive-mirror', device
='drive0',
854 sync
='full', target
=target_img
,
855 mode
='absolute-paths', granularity
=8192)
856 self
.assert_qmp(result
, 'return', {})
858 event
= self
.vm
.get_qmp_event(wait
=60.0)
859 while event
['event'] == 'JOB_STATUS_CHANGE':
860 self
.assert_qmp(event
, 'data/id', 'drive0')
861 event
= self
.vm
.get_qmp_event(wait
=60.0)
863 # Failures will manifest as COMPLETED/ERROR.
864 self
.assert_qmp(event
, 'event', 'BLOCK_JOB_READY')
865 self
.complete_and_wait(drive
='drive0', wait_ready
=False)
866 self
.assert_no_active_block_jobs()
868 class TestRepairQuorum(iotests
.QMPTestCase
):
869 """ This class test quorum file repair using drive-mirror.
870 It's mostly a fork of TestSingleDrive """
871 image_len
= 1 * 1024 * 1024 # MB
872 IMAGES
= [ quorum_img1
, quorum_img2
, quorum_img3
]
875 self
.vm
= iotests
.VM()
877 if iotests
.qemu_default_machine
== 'pc':
878 self
.vm
.add_drive(None, 'media=cdrom', 'ide')
880 # Add each individual quorum images
881 for i
in self
.IMAGES
:
882 qemu_img('create', '-f', iotests
.imgfmt
, i
,
883 str(TestSingleDrive
.image_len
))
884 # Assign a node name to each quorum image in order to manipulate
886 opts
= "node-name=img%i" % self
.IMAGES
.index(i
)
887 self
.vm
= self
.vm
.add_drive(i
, opts
)
891 #assemble the quorum block device from the individual files
892 args
= { "driver": "quorum", "node-name": "quorum0",
893 "vote-threshold": 2, "children": [ "img0", "img1", "img2" ] }
894 if iotests
.supports_quorum():
895 result
= self
.vm
.qmp("blockdev-add", **args
)
896 self
.assert_qmp(result
, 'return', {})
901 for i
in self
.IMAGES
+ [ quorum_repair_img
, quorum_snapshot_file
]:
902 # Do a try/except because the test may have deleted some images
908 def test_complete(self
):
909 if not iotests
.supports_quorum():
912 self
.assert_no_active_block_jobs()
914 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
915 sync
='full', node_name
="repair0", replaces
="img1",
916 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
917 self
.assert_qmp(result
, 'return', {})
919 self
.complete_and_wait(drive
="job0")
920 self
.assert_has_block_node("repair0", quorum_repair_img
)
921 # TODO: a better test requiring some QEMU infrastructure will be added
922 # to check that this file is really driven by quorum
924 self
.assertTrue(iotests
.compare_images(quorum_img2
, quorum_repair_img
),
925 'target image does not match source after mirroring')
927 def test_cancel(self
):
928 if not iotests
.supports_quorum():
931 self
.assert_no_active_block_jobs()
933 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
934 sync
='full', node_name
="repair0", replaces
="img1",
935 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
936 self
.assert_qmp(result
, 'return', {})
938 self
.cancel_and_wait(drive
="job0", force
=True)
939 # here we check that the last registered quorum file has not been
940 # swapped out and unref
941 self
.assert_has_block_node(None, quorum_img3
)
944 def test_cancel_after_ready(self
):
945 if not iotests
.supports_quorum():
948 self
.assert_no_active_block_jobs()
950 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
951 sync
='full', node_name
="repair0", replaces
="img1",
952 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
953 self
.assert_qmp(result
, 'return', {})
955 self
.wait_ready_and_cancel(drive
="job0")
956 # here we check that the last registered quorum file has not been
957 # swapped out and unref
958 self
.assert_has_block_node(None, quorum_img3
)
960 self
.assertTrue(iotests
.compare_images(quorum_img2
, quorum_repair_img
),
961 'target image does not match source after mirroring')
963 def test_pause(self
):
964 if not iotests
.supports_quorum():
967 self
.assert_no_active_block_jobs()
969 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
970 sync
='full', node_name
="repair0", replaces
="img1",
971 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
972 self
.assert_qmp(result
, 'return', {})
974 self
.pause_job('job0')
976 result
= self
.vm
.qmp('query-block-jobs')
977 offset
= self
.dictpath(result
, 'return[0]/offset')
980 result
= self
.vm
.qmp('query-block-jobs')
981 self
.assert_qmp(result
, 'return[0]/offset', offset
)
983 result
= self
.vm
.qmp('block-job-resume', device
='job0')
984 self
.assert_qmp(result
, 'return', {})
986 self
.complete_and_wait(drive
="job0")
988 self
.assertTrue(iotests
.compare_images(quorum_img2
, quorum_repair_img
),
989 'target image does not match source after mirroring')
991 def test_medium_not_found(self
):
992 if not iotests
.supports_quorum():
995 if iotests
.qemu_default_machine
!= 'pc':
998 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='drive0', # CD-ROM
1000 node_name
='repair0',
1002 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
1003 self
.assert_qmp(result
, 'error/class', 'GenericError')
1005 def test_image_not_found(self
):
1006 if not iotests
.supports_quorum():
1009 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
1010 sync
='full', node_name
='repair0', replaces
='img1',
1011 mode
='existing', target
=quorum_repair_img
,
1012 format
=iotests
.imgfmt
)
1013 self
.assert_qmp(result
, 'error/class', 'GenericError')
1015 def test_device_not_found(self
):
1016 if not iotests
.supports_quorum():
1019 result
= self
.vm
.qmp('drive-mirror', job_id
='job0',
1020 device
='nonexistent', sync
='full',
1021 node_name
='repair0',
1023 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
1024 self
.assert_qmp(result
, 'error/class', 'GenericError')
1026 def test_wrong_sync_mode(self
):
1027 if not iotests
.supports_quorum():
1030 result
= self
.vm
.qmp('drive-mirror', device
='quorum0', job_id
='job0',
1031 node_name
='repair0',
1033 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
1034 self
.assert_qmp(result
, 'error/class', 'GenericError')
1036 def test_no_node_name(self
):
1037 if not iotests
.supports_quorum():
1040 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
1041 sync
='full', replaces
='img1',
1042 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
1043 self
.assert_qmp(result
, 'error/class', 'GenericError')
1045 def test_nonexistent_replaces(self
):
1046 if not iotests
.supports_quorum():
1049 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
1050 sync
='full', node_name
='repair0', replaces
='img77',
1051 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
1052 self
.assert_qmp(result
, 'error/class', 'GenericError')
1054 def test_after_a_quorum_snapshot(self
):
1055 if not iotests
.supports_quorum():
1058 result
= self
.vm
.qmp('blockdev-snapshot-sync', node_name
='img1',
1059 snapshot_file
=quorum_snapshot_file
,
1060 snapshot_node_name
="snap1");
1062 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
1063 sync
='full', node_name
='repair0', replaces
="img1",
1064 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
1065 self
.assert_qmp(result
, 'error/class', 'GenericError')
1067 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
1068 sync
='full', node_name
='repair0', replaces
="snap1",
1069 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
1070 self
.assert_qmp(result
, 'return', {})
1072 self
.complete_and_wait('job0')
1073 self
.assert_has_block_node("repair0", quorum_repair_img
)
1074 # TODO: a better test requiring some QEMU infrastructure will be added
1075 # to check that this file is really driven by quorum
1078 # Test mirroring with a source that does not have any parents (not even a
1080 class TestOrphanedSource(iotests
.QMPTestCase
):
1082 blk0
= { 'node-name': 'src',
1083 'driver': 'null-co' }
1085 blk1
= { 'node-name': 'dest',
1086 'driver': 'null-co' }
1088 blk2
= { 'node-name': 'dest-ro',
1089 'driver': 'null-co',
1092 self
.vm
= iotests
.VM()
1093 self
.vm
.add_blockdev(self
.vm
.qmp_to_opts(blk0
))
1094 self
.vm
.add_blockdev(self
.vm
.qmp_to_opts(blk1
))
1095 self
.vm
.add_blockdev(self
.vm
.qmp_to_opts(blk2
))
1101 def test_no_job_id(self
):
1102 self
.assert_no_active_block_jobs()
1104 result
= self
.vm
.qmp('blockdev-mirror', device
='src', sync
='full',
1106 self
.assert_qmp(result
, 'error/class', 'GenericError')
1108 def test_success(self
):
1109 self
.assert_no_active_block_jobs()
1111 result
= self
.vm
.qmp('blockdev-mirror', job_id
='job', device
='src',
1112 sync
='full', target
='dest')
1113 self
.assert_qmp(result
, 'return', {})
1115 self
.complete_and_wait('job')
1117 def test_failing_permissions(self
):
1118 self
.assert_no_active_block_jobs()
1120 result
= self
.vm
.qmp('blockdev-mirror', device
='src', sync
='full',
1122 self
.assert_qmp(result
, 'error/class', 'GenericError')
1124 if __name__
== '__main__':
1125 iotests
.main(supported_fmts
=['qcow2', 'qed'],
1126 supported_protocols
=['file'])