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'])