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 class ImageMirroringTestCase(iotests.QMPTestCase):
32 '''Abstract base class for image mirroring test cases'''
34 def wait_ready(self, drive='drive0'):
35 '''Wait until a block job BLOCK_JOB_READY event'''
38 for event in self.vm.get_qmp_events(wait=True):
39 if event['event'] == 'BLOCK_JOB_READY':
40 self.assert_qmp(event, 'data/type', 'mirror')
41 self.assert_qmp(event, 'data/device', drive)
44 def wait_ready_and_cancel(self, drive='drive0'):
45 self.wait_ready(drive)
46 event = self.cancel_and_wait()
47 self.assertEquals(event['event'], 'BLOCK_JOB_COMPLETED')
48 self.assert_qmp(event, 'data/type', 'mirror')
49 self.assert_qmp(event, 'data/offset', self.image_len)
50 self.assert_qmp(event, 'data/len', self.image_len)
52 def complete_and_wait(self, drive='drive0', wait_ready=True):
53 '''Complete a block job and wait for it to finish'''
57 result = self.vm.qmp('block-job-complete', device=drive)
58 self.assert_qmp(result, 'return', {})
62 for event in self.vm.get_qmp_events(wait=True):
63 if event['event'] == 'BLOCK_JOB_COMPLETED':
64 self.assert_qmp(event, 'data/type', 'mirror')
65 self.assert_qmp(event, 'data/device', drive)
66 self.assert_qmp_absent(event, 'data/error')
67 self.assert_qmp(event, 'data/offset', self.image_len)
68 self.assert_qmp(event, 'data/len', self.image_len)
71 self.assert_no_active_block_jobs()
73 class TestSingleDrive(ImageMirroringTestCase):
74 image_len = 1 * 1024 * 1024 # MB
77 iotests.create_image(backing_img, TestSingleDrive.image_len)
78 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
79 self.vm = iotests.VM().add_drive(test_img)
85 os.remove(backing_img)
91 def test_complete(self):
92 self.assert_no_active_block_jobs()
94 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
96 self.assert_qmp(result, 'return', {})
98 self.complete_and_wait()
99 result = self.vm.qmp('query-block')
100 self.assert_qmp(result, 'return[0]/inserted/file', target_img)
102 self.assertTrue(iotests.compare_images(test_img, target_img),
103 'target image does not match source after mirroring')
105 def test_cancel(self):
106 self.assert_no_active_block_jobs()
108 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
110 self.assert_qmp(result, 'return', {})
112 self.cancel_and_wait(force=True)
113 result = self.vm.qmp('query-block')
114 self.assert_qmp(result, 'return[0]/inserted/file', test_img)
117 def test_cancel_after_ready(self):
118 self.assert_no_active_block_jobs()
120 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
122 self.assert_qmp(result, 'return', {})
124 self.wait_ready_and_cancel()
125 result = self.vm.qmp('query-block')
126 self.assert_qmp(result, 'return[0]/inserted/file', test_img)
128 self.assertTrue(iotests.compare_images(test_img, target_img),
129 'target image does not match source after mirroring')
131 def test_pause(self):
132 self.assert_no_active_block_jobs()
134 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
136 self.assert_qmp(result, 'return', {})
138 result = self.vm.qmp('block-job-pause', device='drive0')
139 self.assert_qmp(result, 'return', {})
142 result = self.vm.qmp('query-block-jobs')
143 offset = self.dictpath(result, 'return[0]/offset')
146 result = self.vm.qmp('query-block-jobs')
147 self.assert_qmp(result, 'return[0]/offset', offset)
149 result = self.vm.qmp('block-job-resume', device='drive0')
150 self.assert_qmp(result, 'return', {})
152 self.complete_and_wait()
154 self.assertTrue(iotests.compare_images(test_img, target_img),
155 'target image does not match source after mirroring')
157 def test_small_buffer(self):
158 self.assert_no_active_block_jobs()
160 # A small buffer is rounded up automatically
161 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
162 buf_size=4096, target=target_img)
163 self.assert_qmp(result, 'return', {})
165 self.complete_and_wait()
166 result = self.vm.qmp('query-block')
167 self.assert_qmp(result, 'return[0]/inserted/file', target_img)
169 self.assertTrue(iotests.compare_images(test_img, target_img),
170 'target image does not match source after mirroring')
172 def test_small_buffer2(self):
173 self.assert_no_active_block_jobs()
175 qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,size=%d'
176 % (TestSingleDrive.image_len, TestSingleDrive.image_len), target_img)
177 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
178 buf_size=65536, mode='existing', target=target_img)
179 self.assert_qmp(result, 'return', {})
181 self.complete_and_wait()
182 result = self.vm.qmp('query-block')
183 self.assert_qmp(result, 'return[0]/inserted/file', target_img)
185 self.assertTrue(iotests.compare_images(test_img, target_img),
186 'target image does not match source after mirroring')
188 def test_large_cluster(self):
189 self.assert_no_active_block_jobs()
191 qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,backing_file=%s'
192 % (TestSingleDrive.image_len, backing_img), target_img)
193 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
194 mode='existing', target=target_img)
195 self.assert_qmp(result, 'return', {})
197 self.complete_and_wait()
198 result = self.vm.qmp('query-block')
199 self.assert_qmp(result, 'return[0]/inserted/file', target_img)
201 self.assertTrue(iotests.compare_images(test_img, target_img),
202 'target image does not match source after mirroring')
204 def test_medium_not_found(self):
205 result = self.vm.qmp('drive-mirror', device='ide1-cd0', sync='full',
207 self.assert_qmp(result, 'error/class', 'GenericError')
209 def test_image_not_found(self):
210 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
211 mode='existing', target=target_img)
212 self.assert_qmp(result, 'error/class', 'GenericError')
214 def test_device_not_found(self):
215 result = self.vm.qmp('drive-mirror', device='nonexistent', sync='full',
217 self.assert_qmp(result, 'error/class', 'DeviceNotFound')
219 class TestMirrorNoBacking(ImageMirroringTestCase):
220 image_len = 2 * 1024 * 1024 # MB
222 def complete_and_wait(self, drive='drive0', wait_ready=True):
223 iotests.create_image(target_backing_img, TestMirrorNoBacking.image_len)
224 return ImageMirroringTestCase.complete_and_wait(self, drive, wait_ready)
226 def compare_images(self, img1, img2):
227 iotests.create_image(target_backing_img, TestMirrorNoBacking.image_len)
228 return iotests.compare_images(img1, img2)
231 iotests.create_image(backing_img, TestMirrorNoBacking.image_len)
232 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
233 self.vm = iotests.VM().add_drive(test_img)
239 os.remove(backing_img)
240 os.remove(target_backing_img)
241 os.remove(target_img)
243 def test_complete(self):
244 self.assert_no_active_block_jobs()
246 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, target_img)
247 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
248 mode='existing', target=target_img)
249 self.assert_qmp(result, 'return', {})
251 self.complete_and_wait()
252 result = self.vm.qmp('query-block')
253 self.assert_qmp(result, 'return[0]/inserted/file', target_img)
255 self.assertTrue(self.compare_images(test_img, target_img),
256 'target image does not match source after mirroring')
258 def test_cancel(self):
259 self.assert_no_active_block_jobs()
261 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, target_img)
262 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
263 mode='existing', target=target_img)
264 self.assert_qmp(result, 'return', {})
266 self.wait_ready_and_cancel()
267 result = self.vm.qmp('query-block')
268 self.assert_qmp(result, 'return[0]/inserted/file', test_img)
270 self.assertTrue(self.compare_images(test_img, target_img),
271 'target image does not match source after mirroring')
273 def test_large_cluster(self):
274 self.assert_no_active_block_jobs()
276 # qemu-img create fails if the image is not there
277 qemu_img('create', '-f', iotests.imgfmt, '-o', 'size=%d'
278 %(TestMirrorNoBacking.image_len), target_backing_img)
279 qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,backing_file=%s'
280 % (TestMirrorNoBacking.image_len, target_backing_img), target_img)
281 os.remove(target_backing_img)
283 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
284 mode='existing', target=target_img)
285 self.assert_qmp(result, 'return', {})
287 self.complete_and_wait()
288 result = self.vm.qmp('query-block')
289 self.assert_qmp(result, 'return[0]/inserted/file', target_img)
291 self.assertTrue(self.compare_images(test_img, target_img),
292 'target image does not match source after mirroring')
294 class TestMirrorResized(ImageMirroringTestCase):
295 backing_len = 1 * 1024 * 1024 # MB
296 image_len = 2 * 1024 * 1024 # MB
299 iotests.create_image(backing_img, TestMirrorResized.backing_len)
300 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
301 qemu_img('resize', test_img, '2M')
302 self.vm = iotests.VM().add_drive(test_img)
308 os.remove(backing_img)
310 os.remove(target_img)
314 def test_complete_top(self):
315 self.assert_no_active_block_jobs()
317 result = self.vm.qmp('drive-mirror', device='drive0', sync='top',
319 self.assert_qmp(result, 'return', {})
321 self.complete_and_wait()
322 result = self.vm.qmp('query-block')
323 self.assert_qmp(result, 'return[0]/inserted/file', target_img)
325 self.assertTrue(iotests.compare_images(test_img, target_img),
326 'target image does not match source after mirroring')
328 def test_complete_full(self):
329 self.assert_no_active_block_jobs()
331 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
333 self.assert_qmp(result, 'return', {})
335 self.complete_and_wait()
336 result = self.vm.qmp('query-block')
337 self.assert_qmp(result, 'return[0]/inserted/file', target_img)
339 self.assertTrue(iotests.compare_images(test_img, target_img),
340 'target image does not match source after mirroring')
342 class TestReadErrors(ImageMirroringTestCase):
343 image_len = 2 * 1024 * 1024 # MB
345 # this should be a multiple of twice the default granularity
346 # so that we hit this offset first in state 1
347 MIRROR_GRANULARITY = 1024 * 1024
349 def create_blkdebug_file(self, name, event, errno):
350 file = open(name, 'w')
369 ''' % (event, errno, self.MIRROR_GRANULARITY / 512, event, event))
373 self.blkdebug_file = backing_img + ".blkdebug"
374 iotests.create_image(backing_img, TestReadErrors.image_len)
375 self.create_blkdebug_file(self.blkdebug_file, "read_aio", 5)
376 qemu_img('create', '-f', iotests.imgfmt,
377 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
378 % (self.blkdebug_file, backing_img),
380 # Write something for tests that use sync='top'
381 qemu_io('-c', 'write %d 512' % (self.MIRROR_GRANULARITY + 65536),
383 self.vm = iotests.VM().add_drive(test_img)
389 os.remove(backing_img)
390 os.remove(self.blkdebug_file)
392 def test_report_read(self):
393 self.assert_no_active_block_jobs()
395 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
397 self.assert_qmp(result, 'return', {})
402 for event in self.vm.get_qmp_events(wait=True):
403 if event['event'] == 'BLOCK_JOB_ERROR':
404 self.assert_qmp(event, 'data/device', 'drive0')
405 self.assert_qmp(event, 'data/operation', 'read')
407 elif event['event'] == 'BLOCK_JOB_READY':
408 self.assertTrue(False, 'job completed unexpectedly')
409 elif event['event'] == 'BLOCK_JOB_COMPLETED':
410 self.assertTrue(error, 'job completed unexpectedly')
411 self.assert_qmp(event, 'data/type', 'mirror')
412 self.assert_qmp(event, 'data/device', 'drive0')
413 self.assert_qmp(event, 'data/error', 'Input/output error')
414 self.assert_qmp(event, 'data/len', self.image_len)
417 self.assert_no_active_block_jobs()
420 def test_ignore_read(self):
421 self.assert_no_active_block_jobs()
423 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
424 target=target_img, on_source_error='ignore')
425 self.assert_qmp(result, 'return', {})
427 event = self.vm.get_qmp_event(wait=True)
428 self.assertEquals(event['event'], 'BLOCK_JOB_ERROR')
429 self.assert_qmp(event, 'data/device', 'drive0')
430 self.assert_qmp(event, 'data/operation', 'read')
431 result = self.vm.qmp('query-block-jobs')
432 self.assert_qmp(result, 'return[0]/paused', False)
433 self.complete_and_wait()
436 def test_large_cluster(self):
437 self.assert_no_active_block_jobs()
439 # Test COW into the target image. The first half of the
440 # cluster at MIRROR_GRANULARITY has to be copied from
441 # backing_img, even though sync='top'.
442 qemu_img('create', '-f', iotests.imgfmt, '-ocluster_size=131072,backing_file=%s' %(backing_img), target_img)
443 result = self.vm.qmp('drive-mirror', device='drive0', sync='top',
444 on_source_error='ignore',
445 mode='existing', target=target_img)
446 self.assert_qmp(result, 'return', {})
448 event = self.vm.get_qmp_event(wait=True)
449 self.assertEquals(event['event'], 'BLOCK_JOB_ERROR')
450 self.assert_qmp(event, 'data/device', 'drive0')
451 self.assert_qmp(event, 'data/operation', 'read')
452 result = self.vm.qmp('query-block-jobs')
453 self.assert_qmp(result, 'return[0]/paused', False)
454 self.complete_and_wait()
457 # Detach blkdebug to compare images successfully
458 qemu_img('rebase', '-f', iotests.imgfmt, '-u', '-b', backing_img, test_img)
459 self.assertTrue(iotests.compare_images(test_img, target_img),
460 'target image does not match source after mirroring')
462 def test_stop_read(self):
463 self.assert_no_active_block_jobs()
465 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
466 target=target_img, on_source_error='stop')
467 self.assert_qmp(result, 'return', {})
472 for event in self.vm.get_qmp_events(wait=True):
473 if event['event'] == 'BLOCK_JOB_ERROR':
474 self.assert_qmp(event, 'data/device', 'drive0')
475 self.assert_qmp(event, 'data/operation', 'read')
477 result = self.vm.qmp('query-block-jobs')
478 self.assert_qmp(result, 'return[0]/paused', True)
479 self.assert_qmp(result, 'return[0]/io-status', 'failed')
481 result = self.vm.qmp('block-job-resume', device='drive0')
482 self.assert_qmp(result, 'return', {})
484 elif event['event'] == 'BLOCK_JOB_READY':
485 self.assertTrue(error, 'job completed unexpectedly')
486 self.assert_qmp(event, 'data/device', 'drive0')
489 result = self.vm.qmp('query-block-jobs')
490 self.assert_qmp(result, 'return[0]/paused', False)
491 self.assert_qmp(result, 'return[0]/io-status', 'ok')
493 self.complete_and_wait(wait_ready=False)
494 self.assert_no_active_block_jobs()
497 class TestWriteErrors(ImageMirroringTestCase):
498 image_len = 2 * 1024 * 1024 # MB
500 # this should be a multiple of twice the default granularity
501 # so that we hit this offset first in state 1
502 MIRROR_GRANULARITY = 1024 * 1024
504 def create_blkdebug_file(self, name, event, errno):
505 file = open(name, 'w')
524 ''' % (event, errno, self.MIRROR_GRANULARITY / 512, event, event))
528 self.blkdebug_file = target_img + ".blkdebug"
529 iotests.create_image(backing_img, TestWriteErrors.image_len)
530 self.create_blkdebug_file(self.blkdebug_file, "write_aio", 5)
531 qemu_img('create', '-f', iotests.imgfmt, '-obacking_file=%s' %(backing_img), test_img)
532 self.vm = iotests.VM().add_drive(test_img)
533 self.target_img = 'blkdebug:%s:%s' % (self.blkdebug_file, target_img)
534 qemu_img('create', '-f', iotests.imgfmt, '-osize=%d' %(TestWriteErrors.image_len), target_img)
540 os.remove(backing_img)
541 os.remove(self.blkdebug_file)
543 def test_report_write(self):
544 self.assert_no_active_block_jobs()
546 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
547 mode='existing', target=self.target_img)
548 self.assert_qmp(result, 'return', {})
553 for event in self.vm.get_qmp_events(wait=True):
554 if event['event'] == 'BLOCK_JOB_ERROR':
555 self.assert_qmp(event, 'data/device', 'drive0')
556 self.assert_qmp(event, 'data/operation', 'write')
558 elif event['event'] == 'BLOCK_JOB_READY':
559 self.assertTrue(False, 'job completed unexpectedly')
560 elif event['event'] == 'BLOCK_JOB_COMPLETED':
561 self.assertTrue(error, 'job completed unexpectedly')
562 self.assert_qmp(event, 'data/type', 'mirror')
563 self.assert_qmp(event, 'data/device', 'drive0')
564 self.assert_qmp(event, 'data/error', 'Input/output error')
565 self.assert_qmp(event, 'data/len', self.image_len)
568 self.assert_no_active_block_jobs()
571 def test_ignore_write(self):
572 self.assert_no_active_block_jobs()
574 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
575 mode='existing', target=self.target_img,
576 on_target_error='ignore')
577 self.assert_qmp(result, 'return', {})
579 event = self.vm.get_qmp_event(wait=True)
580 self.assertEquals(event['event'], 'BLOCK_JOB_ERROR')
581 self.assert_qmp(event, 'data/device', 'drive0')
582 self.assert_qmp(event, 'data/operation', 'write')
583 result = self.vm.qmp('query-block-jobs')
584 self.assert_qmp(result, 'return[0]/paused', False)
585 self.complete_and_wait()
588 def test_stop_write(self):
589 self.assert_no_active_block_jobs()
591 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
592 mode='existing', target=self.target_img,
593 on_target_error='stop')
594 self.assert_qmp(result, 'return', {})
599 for event in self.vm.get_qmp_events(wait=True):
600 if event['event'] == 'BLOCK_JOB_ERROR':
601 self.assert_qmp(event, 'data/device', 'drive0')
602 self.assert_qmp(event, 'data/operation', 'write')
604 result = self.vm.qmp('query-block-jobs')
605 self.assert_qmp(result, 'return[0]/paused', True)
606 self.assert_qmp(result, 'return[0]/io-status', 'failed')
608 result = self.vm.qmp('block-job-resume', device='drive0')
609 self.assert_qmp(result, 'return', {})
611 result = self.vm.qmp('query-block-jobs')
612 self.assert_qmp(result, 'return[0]/paused', False)
613 self.assert_qmp(result, 'return[0]/io-status', 'ok')
615 elif event['event'] == 'BLOCK_JOB_READY':
616 self.assertTrue(error, 'job completed unexpectedly')
617 self.assert_qmp(event, 'data/device', 'drive0')
620 self.complete_and_wait(wait_ready=False)
621 self.assert_no_active_block_jobs()
624 class TestSetSpeed(ImageMirroringTestCase):
625 image_len = 80 * 1024 * 1024 # MB
628 qemu_img('create', backing_img, str(TestSetSpeed.image_len))
629 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
630 self.vm = iotests.VM().add_drive(test_img)
636 os.remove(backing_img)
637 os.remove(target_img)
639 def test_set_speed(self):
640 self.assert_no_active_block_jobs()
642 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
644 self.assert_qmp(result, 'return', {})
647 result = self.vm.qmp('query-block-jobs')
648 self.assert_qmp(result, 'return[0]/device', 'drive0')
649 self.assert_qmp(result, 'return[0]/speed', 0)
651 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024)
652 self.assert_qmp(result, 'return', {})
654 # Ensure the speed we set was accepted
655 result = self.vm.qmp('query-block-jobs')
656 self.assert_qmp(result, 'return[0]/device', 'drive0')
657 self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024)
659 self.wait_ready_and_cancel()
661 # Check setting speed in drive-mirror works
662 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
663 target=target_img, speed=4*1024*1024)
664 self.assert_qmp(result, 'return', {})
666 result = self.vm.qmp('query-block-jobs')
667 self.assert_qmp(result, 'return[0]/device', 'drive0')
668 self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024)
670 self.wait_ready_and_cancel()
672 def test_set_speed_invalid(self):
673 self.assert_no_active_block_jobs()
675 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
676 target=target_img, speed=-1)
677 self.assert_qmp(result, 'error/class', 'GenericError')
679 self.assert_no_active_block_jobs()
681 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
683 self.assert_qmp(result, 'return', {})
685 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1)
686 self.assert_qmp(result, 'error/class', 'GenericError')
688 self.wait_ready_and_cancel()
690 if __name__ == '__main__':
691 iotests.main(supported_fmts=['qcow2', 'qed'])