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
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 class ImageMirroringTestCase(iotests.QMPTestCase):
33 '''Abstract base class for image mirroring test cases'''
35 def assert_no_active_mirrors(self):
36 result = self.vm.qmp('query-block-jobs')
37 self.assert_qmp(result, 'return', [])
39 def cancel_and_wait(self, drive='drive0', wait_ready=True):
40 '''Cancel a block job and wait for it to finish'''
44 for event in self.vm.get_qmp_events(wait=True):
45 if event['event'] == 'BLOCK_JOB_READY':
46 self.assert_qmp(event, 'data/type', 'mirror')
47 self.assert_qmp(event, 'data/device', drive)
50 result = self.vm.qmp('block-job-cancel', device=drive,
52 self.assert_qmp(result, 'return', {})
56 for event in self.vm.get_qmp_events(wait=True):
57 if event['event'] == 'BLOCK_JOB_COMPLETED' or \
58 event['event'] == 'BLOCK_JOB_CANCELLED':
59 self.assert_qmp(event, 'data/type', 'mirror')
60 self.assert_qmp(event, 'data/device', drive)
62 self.assertEquals(event['event'], 'BLOCK_JOB_COMPLETED')
63 self.assert_qmp(event, 'data/offset', self.image_len)
64 self.assert_qmp(event, 'data/len', self.image_len)
67 self.assert_no_active_mirrors()
69 def complete_and_wait(self, drive='drive0', wait_ready=True):
70 '''Complete a block job and wait for it to finish'''
74 for event in self.vm.get_qmp_events(wait=True):
75 if event['event'] == 'BLOCK_JOB_READY':
76 self.assert_qmp(event, 'data/type', 'mirror')
77 self.assert_qmp(event, 'data/device', drive)
80 result = self.vm.qmp('block-job-complete', device=drive)
81 self.assert_qmp(result, 'return', {})
85 for event in self.vm.get_qmp_events(wait=True):
86 if event['event'] == 'BLOCK_JOB_COMPLETED':
87 self.assert_qmp(event, 'data/type', 'mirror')
88 self.assert_qmp(event, 'data/device', drive)
89 self.assert_qmp_absent(event, 'data/error')
90 self.assert_qmp(event, 'data/offset', self.image_len)
91 self.assert_qmp(event, 'data/len', self.image_len)
94 self.assert_no_active_mirrors()
96 def create_image(self, name, size):
97 file = open(name, 'w')
100 sector = struct.pack('>l504xl', i / 512, i / 512)
105 def compare_images(self, img1, img2):
107 qemu_img('convert', '-f', iotests.imgfmt, '-O', 'raw', img1, img1 + '.raw')
108 qemu_img('convert', '-f', iotests.imgfmt, '-O', 'raw', img2, img2 + '.raw')
109 file1 = open(img1 + '.raw', 'r')
110 file2 = open(img2 + '.raw', 'r')
111 return file1.read() == file2.read()
113 if file1 is not None:
115 if file2 is not None:
118 os.remove(img1 + '.raw')
122 os.remove(img2 + '.raw')
126 class TestSingleDrive(ImageMirroringTestCase):
127 image_len = 1 * 1024 * 1024 # MB
130 self.create_image(backing_img, TestSingleDrive.image_len)
131 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
132 self.vm = iotests.VM().add_drive(test_img)
138 os.remove(backing_img)
140 os.remove(target_img)
144 def test_complete(self):
145 self.assert_no_active_mirrors()
147 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
149 self.assert_qmp(result, 'return', {})
151 self.complete_and_wait()
152 result = self.vm.qmp('query-block')
153 self.assert_qmp(result, 'return[0]/inserted/file', target_img)
155 self.assertTrue(self.compare_images(test_img, target_img),
156 'target image does not match source after mirroring')
158 def test_cancel(self):
159 self.assert_no_active_mirrors()
161 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
163 self.assert_qmp(result, 'return', {})
165 self.cancel_and_wait(wait_ready=False)
166 result = self.vm.qmp('query-block')
167 self.assert_qmp(result, 'return[0]/inserted/file', test_img)
170 def test_cancel_after_ready(self):
171 self.assert_no_active_mirrors()
173 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
175 self.assert_qmp(result, 'return', {})
177 self.cancel_and_wait()
178 result = self.vm.qmp('query-block')
179 self.assert_qmp(result, 'return[0]/inserted/file', test_img)
181 self.assertTrue(self.compare_images(test_img, target_img),
182 'target image does not match source after mirroring')
184 def test_pause(self):
185 self.assert_no_active_mirrors()
187 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
189 self.assert_qmp(result, 'return', {})
191 result = self.vm.qmp('block-job-pause', device='drive0')
192 self.assert_qmp(result, 'return', {})
195 result = self.vm.qmp('query-block-jobs')
196 offset = self.dictpath(result, 'return[0]/offset')
199 result = self.vm.qmp('query-block-jobs')
200 self.assert_qmp(result, 'return[0]/offset', offset)
202 result = self.vm.qmp('block-job-resume', device='drive0')
203 self.assert_qmp(result, 'return', {})
205 self.complete_and_wait()
207 self.assertTrue(self.compare_images(test_img, target_img),
208 'target image does not match source after mirroring')
210 def test_large_cluster(self):
211 self.assert_no_active_mirrors()
213 qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,backing_file=%s'
214 % (TestSingleDrive.image_len, backing_img), target_img)
215 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
216 mode='existing', target=target_img)
217 self.assert_qmp(result, 'return', {})
219 self.complete_and_wait()
220 result = self.vm.qmp('query-block')
221 self.assert_qmp(result, 'return[0]/inserted/file', target_img)
223 self.assertTrue(self.compare_images(test_img, target_img),
224 'target image does not match source after mirroring')
226 def test_medium_not_found(self):
227 result = self.vm.qmp('drive-mirror', device='ide1-cd0', sync='full',
229 self.assert_qmp(result, 'error/class', 'GenericError')
231 def test_image_not_found(self):
232 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
233 mode='existing', target=target_img)
234 self.assert_qmp(result, 'error/class', 'GenericError')
236 def test_device_not_found(self):
237 result = self.vm.qmp('drive-mirror', device='nonexistent', sync='full',
239 self.assert_qmp(result, 'error/class', 'DeviceNotFound')
241 class TestMirrorNoBacking(ImageMirroringTestCase):
242 image_len = 2 * 1024 * 1024 # MB
244 def complete_and_wait(self, drive='drive0', wait_ready=True):
245 self.create_image(target_backing_img, TestMirrorNoBacking.image_len)
246 return ImageMirroringTestCase.complete_and_wait(self, drive, wait_ready)
248 def compare_images(self, img1, img2):
249 self.create_image(target_backing_img, TestMirrorNoBacking.image_len)
250 return ImageMirroringTestCase.compare_images(self, img1, img2)
253 self.create_image(backing_img, TestMirrorNoBacking.image_len)
254 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
255 self.vm = iotests.VM().add_drive(test_img)
261 os.remove(backing_img)
262 os.remove(target_backing_img)
263 os.remove(target_img)
265 def test_complete(self):
266 self.assert_no_active_mirrors()
268 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, target_img)
269 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
270 mode='existing', target=target_img)
271 self.assert_qmp(result, 'return', {})
273 self.complete_and_wait()
274 result = self.vm.qmp('query-block')
275 self.assert_qmp(result, 'return[0]/inserted/file', target_img)
277 self.assertTrue(self.compare_images(test_img, target_img),
278 'target image does not match source after mirroring')
280 def test_cancel(self):
281 self.assert_no_active_mirrors()
283 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, target_img)
284 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
285 mode='existing', target=target_img)
286 self.assert_qmp(result, 'return', {})
288 self.cancel_and_wait()
289 result = self.vm.qmp('query-block')
290 self.assert_qmp(result, 'return[0]/inserted/file', test_img)
292 self.assertTrue(self.compare_images(test_img, target_img),
293 'target image does not match source after mirroring')
295 class TestReadErrors(ImageMirroringTestCase):
296 image_len = 2 * 1024 * 1024 # MB
298 # this should be a multiple of twice the default granularity
299 # so that we hit this offset first in state 1
300 MIRROR_GRANULARITY = 1024 * 1024
302 def create_blkdebug_file(self, name, event, errno):
303 file = open(name, 'w')
322 ''' % (event, errno, self.MIRROR_GRANULARITY / 512, event, event))
326 self.blkdebug_file = backing_img + ".blkdebug"
327 self.create_image(backing_img, TestReadErrors.image_len)
328 self.create_blkdebug_file(self.blkdebug_file, "read_aio", 5)
329 qemu_img('create', '-f', iotests.imgfmt,
330 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
331 % (self.blkdebug_file, backing_img),
333 self.vm = iotests.VM().add_drive(test_img)
339 os.remove(backing_img)
340 os.remove(self.blkdebug_file)
342 def test_report_read(self):
343 self.assert_no_active_mirrors()
345 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
347 self.assert_qmp(result, 'return', {})
352 for event in self.vm.get_qmp_events(wait=True):
353 if event['event'] == 'BLOCK_JOB_ERROR':
354 self.assert_qmp(event, 'data/device', 'drive0')
355 self.assert_qmp(event, 'data/operation', 'read')
357 elif event['event'] == 'BLOCK_JOB_READY':
358 self.assertTrue(False, 'job completed unexpectedly')
359 elif event['event'] == 'BLOCK_JOB_COMPLETED':
360 self.assertTrue(error, 'job completed unexpectedly')
361 self.assert_qmp(event, 'data/type', 'mirror')
362 self.assert_qmp(event, 'data/device', 'drive0')
363 self.assert_qmp(event, 'data/error', 'Input/output error')
364 self.assert_qmp(event, 'data/len', self.image_len)
367 self.assert_no_active_mirrors()
370 def test_ignore_read(self):
371 self.assert_no_active_mirrors()
373 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
374 target=target_img, on_source_error='ignore')
375 self.assert_qmp(result, 'return', {})
377 event = self.vm.get_qmp_event(wait=True)
378 self.assertEquals(event['event'], 'BLOCK_JOB_ERROR')
379 self.assert_qmp(event, 'data/device', 'drive0')
380 self.assert_qmp(event, 'data/operation', 'read')
381 result = self.vm.qmp('query-block-jobs')
382 self.assert_qmp(result, 'return[0]/paused', False)
383 self.complete_and_wait()
386 def test_stop_read(self):
387 self.assert_no_active_mirrors()
389 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
390 target=target_img, on_source_error='stop')
391 self.assert_qmp(result, 'return', {})
396 for event in self.vm.get_qmp_events(wait=True):
397 if event['event'] == 'BLOCK_JOB_ERROR':
398 self.assert_qmp(event, 'data/device', 'drive0')
399 self.assert_qmp(event, 'data/operation', 'read')
401 result = self.vm.qmp('query-block-jobs')
402 self.assert_qmp(result, 'return[0]/paused', True)
403 self.assert_qmp(result, 'return[0]/io-status', 'failed')
405 result = self.vm.qmp('block-job-resume', device='drive0')
406 self.assert_qmp(result, 'return', {})
408 elif event['event'] == 'BLOCK_JOB_READY':
409 self.assertTrue(error, 'job completed unexpectedly')
410 self.assert_qmp(event, 'data/device', 'drive0')
413 result = self.vm.qmp('query-block-jobs')
414 self.assert_qmp(result, 'return[0]/paused', False)
415 self.assert_qmp(result, 'return[0]/io-status', 'ok')
417 self.complete_and_wait(wait_ready=False)
418 self.assert_no_active_mirrors()
421 class TestWriteErrors(ImageMirroringTestCase):
422 image_len = 2 * 1024 * 1024 # MB
424 # this should be a multiple of twice the default granularity
425 # so that we hit this offset first in state 1
426 MIRROR_GRANULARITY = 1024 * 1024
428 def create_blkdebug_file(self, name, event, errno):
429 file = open(name, 'w')
448 ''' % (event, errno, self.MIRROR_GRANULARITY / 512, event, event))
452 self.blkdebug_file = target_img + ".blkdebug"
453 self.create_image(backing_img, TestWriteErrors.image_len)
454 self.create_blkdebug_file(self.blkdebug_file, "write_aio", 5)
455 qemu_img('create', '-f', iotests.imgfmt, '-obacking_file=%s' %(backing_img), test_img)
456 self.vm = iotests.VM().add_drive(test_img)
457 self.target_img = 'blkdebug:%s:%s' % (self.blkdebug_file, target_img)
458 qemu_img('create', '-f', iotests.imgfmt, '-osize=%d' %(TestWriteErrors.image_len), target_img)
464 os.remove(backing_img)
465 os.remove(self.blkdebug_file)
467 def test_report_write(self):
468 self.assert_no_active_mirrors()
470 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
471 mode='existing', target=self.target_img)
472 self.assert_qmp(result, 'return', {})
477 for event in self.vm.get_qmp_events(wait=True):
478 if event['event'] == 'BLOCK_JOB_ERROR':
479 self.assert_qmp(event, 'data/device', 'drive0')
480 self.assert_qmp(event, 'data/operation', 'write')
482 elif event['event'] == 'BLOCK_JOB_READY':
483 self.assertTrue(False, 'job completed unexpectedly')
484 elif event['event'] == 'BLOCK_JOB_COMPLETED':
485 self.assertTrue(error, 'job completed unexpectedly')
486 self.assert_qmp(event, 'data/type', 'mirror')
487 self.assert_qmp(event, 'data/device', 'drive0')
488 self.assert_qmp(event, 'data/error', 'Input/output error')
489 self.assert_qmp(event, 'data/len', self.image_len)
492 self.assert_no_active_mirrors()
495 def test_ignore_write(self):
496 self.assert_no_active_mirrors()
498 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
499 mode='existing', target=self.target_img,
500 on_target_error='ignore')
501 self.assert_qmp(result, 'return', {})
503 event = self.vm.get_qmp_event(wait=True)
504 self.assertEquals(event['event'], 'BLOCK_JOB_ERROR')
505 self.assert_qmp(event, 'data/device', 'drive0')
506 self.assert_qmp(event, 'data/operation', 'write')
507 result = self.vm.qmp('query-block-jobs')
508 self.assert_qmp(result, 'return[0]/paused', False)
509 self.complete_and_wait()
512 def test_stop_write(self):
513 self.assert_no_active_mirrors()
515 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
516 mode='existing', target=self.target_img,
517 on_target_error='stop')
518 self.assert_qmp(result, 'return', {})
523 for event in self.vm.get_qmp_events(wait=True):
524 if event['event'] == 'BLOCK_JOB_ERROR':
525 self.assert_qmp(event, 'data/device', 'drive0')
526 self.assert_qmp(event, 'data/operation', 'write')
528 result = self.vm.qmp('query-block-jobs')
529 self.assert_qmp(result, 'return[0]/paused', True)
530 self.assert_qmp(result, 'return[0]/io-status', 'failed')
532 result = self.vm.qmp('block-job-resume', device='drive0')
533 self.assert_qmp(result, 'return', {})
535 result = self.vm.qmp('query-block-jobs')
536 self.assert_qmp(result, 'return[0]/paused', False)
537 self.assert_qmp(result, 'return[0]/io-status', 'ok')
539 elif event['event'] == 'BLOCK_JOB_READY':
540 self.assertTrue(error, 'job completed unexpectedly')
541 self.assert_qmp(event, 'data/device', 'drive0')
544 self.complete_and_wait(wait_ready=False)
545 self.assert_no_active_mirrors()
548 class TestSetSpeed(ImageMirroringTestCase):
549 image_len = 80 * 1024 * 1024 # MB
552 qemu_img('create', backing_img, str(TestSetSpeed.image_len))
553 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
554 self.vm = iotests.VM().add_drive(test_img)
560 os.remove(backing_img)
561 os.remove(target_img)
563 def test_set_speed(self):
564 self.assert_no_active_mirrors()
566 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
568 self.assert_qmp(result, 'return', {})
571 result = self.vm.qmp('query-block-jobs')
572 self.assert_qmp(result, 'return[0]/device', 'drive0')
573 self.assert_qmp(result, 'return[0]/speed', 0)
575 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024)
576 self.assert_qmp(result, 'return', {})
578 # Ensure the speed we set was accepted
579 result = self.vm.qmp('query-block-jobs')
580 self.assert_qmp(result, 'return[0]/device', 'drive0')
581 self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024)
583 self.cancel_and_wait()
585 # Check setting speed in drive-mirror works
586 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
587 target=target_img, speed=4*1024*1024)
588 self.assert_qmp(result, 'return', {})
590 result = self.vm.qmp('query-block-jobs')
591 self.assert_qmp(result, 'return[0]/device', 'drive0')
592 self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024)
594 self.cancel_and_wait()
596 def test_set_speed_invalid(self):
597 self.assert_no_active_mirrors()
599 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
600 target=target_img, speed=-1)
601 self.assert_qmp(result, 'error/class', 'GenericError')
603 self.assert_no_active_mirrors()
605 result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
607 self.assert_qmp(result, 'return', {})
609 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1)
610 self.assert_qmp(result, 'error/class', 'GenericError')
612 self.cancel_and_wait()
614 if __name__ == '__main__':
615 iotests.main(supported_fmts=['qcow2', 'qed'])