]>
git.proxmox.com Git - mirror_qemu.git/blob - tests/qemu-iotests/041
3 # Tests for image mirroring.
5 # Copyright (C) 2012 Red Hat, Inc.
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
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
)
84 def test_cancel_after_ready(self
):
85 self
.assert_no_active_block_jobs()
87 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
88 target
=self
.qmp_target
)
89 self
.assert_qmp(result
, 'return', {})
91 self
.wait_ready_and_cancel()
92 result
= self
.vm
.qmp('query-block')
93 self
.assert_qmp(result
, 'return[0]/inserted/file', test_img
)
95 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
96 'target image does not match source after mirroring')
99 self
.assert_no_active_block_jobs()
101 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
102 target
=self
.qmp_target
)
103 self
.assert_qmp(result
, 'return', {})
105 self
.pause_job('drive0')
107 result
= self
.vm
.qmp('query-block-jobs')
108 offset
= self
.dictpath(result
, 'return[0]/offset')
111 result
= self
.vm
.qmp('query-block-jobs')
112 self
.assert_qmp(result
, 'return[0]/offset', offset
)
114 result
= self
.vm
.qmp('block-job-resume', device
='drive0')
115 self
.assert_qmp(result
, 'return', {})
117 self
.complete_and_wait()
119 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
120 'target image does not match source after mirroring')
122 def test_small_buffer(self
):
123 self
.assert_no_active_block_jobs()
125 # A small buffer is rounded up automatically
126 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
127 buf_size
=4096, target
=self
.qmp_target
)
128 self
.assert_qmp(result
, 'return', {})
130 self
.complete_and_wait()
131 result
= self
.vm
.qmp('query-block')
132 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
134 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
135 'target image does not match source after mirroring')
137 def test_small_buffer2(self
):
138 self
.assert_no_active_block_jobs()
140 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'cluster_size=%d,size=%d'
141 % (self
.image_len
, self
.image_len
), target_img
)
142 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
143 buf_size
=65536, mode
='existing', target
=self
.qmp_target
)
144 self
.assert_qmp(result
, 'return', {})
146 self
.complete_and_wait()
147 result
= self
.vm
.qmp('query-block')
148 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
150 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
151 'target image does not match source after mirroring')
153 def test_large_cluster(self
):
154 self
.assert_no_active_block_jobs()
156 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'cluster_size=%d,backing_file=%s'
157 % (self
.image_len
, backing_img
), target_img
)
158 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
159 mode
='existing', target
=self
.qmp_target
)
160 self
.assert_qmp(result
, 'return', {})
162 self
.complete_and_wait()
163 result
= self
.vm
.qmp('query-block')
164 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
166 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
167 'target image does not match source after mirroring')
169 # Tests that the insertion of the mirror_top filter node doesn't make a
170 # difference to query-block
171 def test_implicit_node(self
):
172 self
.assert_no_active_block_jobs()
174 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
175 target
=self
.qmp_target
)
176 self
.assert_qmp(result
, 'return', {})
178 result
= self
.vm
.qmp('query-block')
179 self
.assert_qmp(result
, 'return[0]/inserted/file', test_img
)
180 self
.assert_qmp(result
, 'return[0]/inserted/drv', iotests
.imgfmt
)
181 self
.assert_qmp(result
, 'return[0]/inserted/backing_file', backing_img
)
182 self
.assert_qmp(result
, 'return[0]/inserted/backing_file_depth', 1)
183 self
.assert_qmp(result
, 'return[0]/inserted/image/filename', test_img
)
184 self
.assert_qmp(result
, 'return[0]/inserted/image/backing-image/filename', backing_img
)
186 result
= self
.vm
.qmp('query-blockstats')
187 self
.assert_qmp(result
, 'return[0]/node-name', 'top')
188 self
.assert_qmp(result
, 'return[0]/backing/node-name', 'base')
190 self
.cancel_and_wait(force
=True)
191 result
= self
.vm
.qmp('query-block')
192 self
.assert_qmp(result
, 'return[0]/inserted/file', test_img
)
193 self
.assert_qmp(result
, 'return[0]/inserted/drv', iotests
.imgfmt
)
194 self
.assert_qmp(result
, 'return[0]/inserted/backing_file', backing_img
)
195 self
.assert_qmp(result
, 'return[0]/inserted/backing_file_depth', 1)
196 self
.assert_qmp(result
, 'return[0]/inserted/image/filename', test_img
)
197 self
.assert_qmp(result
, 'return[0]/inserted/image/backing-image/filename', backing_img
)
199 result
= self
.vm
.qmp('query-blockstats')
200 self
.assert_qmp(result
, 'return[0]/node-name', 'top')
201 self
.assert_qmp(result
, 'return[0]/backing/node-name', 'base')
203 def test_medium_not_found(self
):
204 if iotests
.qemu_default_machine
!= 'pc':
207 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='ide1-cd0', sync
='full',
208 target
=self
.qmp_target
)
209 self
.assert_qmp(result
, 'error/class', 'GenericError')
211 def test_image_not_found(self
):
212 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
213 mode
='existing', target
=self
.qmp_target
)
214 self
.assert_qmp(result
, 'error/class', 'GenericError')
216 def test_device_not_found(self
):
217 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='nonexistent', sync
='full',
218 target
=self
.qmp_target
)
219 self
.assert_qmp(result
, 'error/class', 'GenericError')
221 class TestSingleBlockdev(TestSingleDrive
):
222 qmp_cmd
= 'blockdev-mirror'
226 TestSingleDrive
.setUp(self
)
227 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'backing_file=%s' % backing_img
, target_img
)
228 args
= {'driver': iotests
.imgfmt
,
229 'node-name': self
.qmp_target
,
230 'file': { 'filename': target_img
, 'driver': 'file' } }
231 result
= self
.vm
.qmp("blockdev-add", **args
)
232 self
.assert_qmp(result
, 'return', {})
234 def test_mirror_to_self(self
):
235 result
= self
.vm
.qmp(self
.qmp_cmd
, job_id
='job0',
236 device
=self
.qmp_target
, sync
='full',
237 target
=self
.qmp_target
)
238 self
.assert_qmp(result
, 'error/class', 'GenericError')
240 test_large_cluster
= None
241 test_image_not_found
= None
242 test_small_buffer2
= None
244 class TestSingleDriveZeroLength(TestSingleDrive
):
246 test_small_buffer2
= None
247 test_large_cluster
= None
249 class TestSingleBlockdevZeroLength(TestSingleBlockdev
):
252 class TestSingleDriveUnalignedLength(TestSingleDrive
):
253 image_len
= 1025 * 1024
254 test_small_buffer2
= None
255 test_large_cluster
= None
257 class TestSingleBlockdevUnalignedLength(TestSingleBlockdev
):
258 image_len
= 1025 * 1024
260 class TestMirrorNoBacking(iotests
.QMPTestCase
):
261 image_len
= 2 * 1024 * 1024 # MB
264 iotests
.create_image(backing_img
, TestMirrorNoBacking
.image_len
)
265 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'backing_file=%s' % backing_img
, test_img
)
266 self
.vm
= iotests
.VM().add_drive(test_img
)
272 os
.remove(backing_img
)
274 os
.remove(target_backing_img
)
277 os
.remove(target_img
)
279 def test_complete(self
):
280 self
.assert_no_active_block_jobs()
282 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'backing_file=%s' % backing_img
, target_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(iotests
.compare_images(test_img
, target_img
),
292 'target image does not match source after mirroring')
294 def test_cancel(self
):
295 self
.assert_no_active_block_jobs()
297 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'backing_file=%s' % backing_img
, target_img
)
298 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
299 mode
='existing', target
=target_img
)
300 self
.assert_qmp(result
, 'return', {})
302 self
.wait_ready_and_cancel()
303 result
= self
.vm
.qmp('query-block')
304 self
.assert_qmp(result
, 'return[0]/inserted/file', test_img
)
306 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
307 'target image does not match source after mirroring')
309 def test_large_cluster(self
):
310 self
.assert_no_active_block_jobs()
312 # qemu-img create fails if the image is not there
313 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'size=%d'
314 %(TestMirrorNoBacking
.image_len
), target_backing_img
)
315 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'cluster_size=%d,backing_file=%s'
316 % (TestMirrorNoBacking
.image_len
, target_backing_img
), target_img
)
318 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
319 mode
='existing', target
=target_img
)
320 self
.assert_qmp(result
, 'return', {})
322 self
.complete_and_wait()
323 result
= self
.vm
.qmp('query-block')
324 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
326 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
327 'target image does not match source after mirroring')
329 class TestMirrorResized(iotests
.QMPTestCase
):
330 backing_len
= 1 * 1024 * 1024 # MB
331 image_len
= 2 * 1024 * 1024 # MB
334 iotests
.create_image(backing_img
, TestMirrorResized
.backing_len
)
335 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'backing_file=%s' % backing_img
, test_img
)
336 qemu_img('resize', test_img
, '2M')
337 self
.vm
= iotests
.VM().add_drive(test_img
)
343 os
.remove(backing_img
)
345 os
.remove(target_img
)
349 def test_complete_top(self
):
350 self
.assert_no_active_block_jobs()
352 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='top',
354 self
.assert_qmp(result
, 'return', {})
356 self
.complete_and_wait()
357 result
= self
.vm
.qmp('query-block')
358 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
360 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
361 'target image does not match source after mirroring')
363 def test_complete_full(self
):
364 self
.assert_no_active_block_jobs()
366 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
368 self
.assert_qmp(result
, 'return', {})
370 self
.complete_and_wait()
371 result
= self
.vm
.qmp('query-block')
372 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
374 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
375 'target image does not match source after mirroring')
377 class TestReadErrors(iotests
.QMPTestCase
):
378 image_len
= 2 * 1024 * 1024 # MB
380 # this should be a multiple of twice the default granularity
381 # so that we hit this offset first in state 1
382 MIRROR_GRANULARITY
= 1024 * 1024
384 def create_blkdebug_file(self
, name
, event
, errno
):
385 file = open(name
, 'w')
404 ''' % (event
, errno
, self
.MIRROR_GRANULARITY
// 512, event
, event
))
408 self
.blkdebug_file
= backing_img
+ ".blkdebug"
409 iotests
.create_image(backing_img
, TestReadErrors
.image_len
)
410 self
.create_blkdebug_file(self
.blkdebug_file
, "read_aio", 5)
411 qemu_img('create', '-f', iotests
.imgfmt
,
412 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
413 % (self
.blkdebug_file
, backing_img
),
415 # Write something for tests that use sync='top'
416 qemu_io('-c', 'write %d 512' % (self
.MIRROR_GRANULARITY
+ 65536),
418 self
.vm
= iotests
.VM().add_drive(test_img
)
424 os
.remove(target_img
)
425 os
.remove(backing_img
)
426 os
.remove(self
.blkdebug_file
)
428 def test_report_read(self
):
429 self
.assert_no_active_block_jobs()
431 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
433 self
.assert_qmp(result
, 'return', {})
438 for event
in self
.vm
.get_qmp_events(wait
=True):
439 if event
['event'] == 'BLOCK_JOB_ERROR':
440 self
.assert_qmp(event
, 'data/device', 'drive0')
441 self
.assert_qmp(event
, 'data/operation', 'read')
443 elif event
['event'] == 'BLOCK_JOB_READY':
444 self
.assertTrue(False, 'job completed unexpectedly')
445 elif event
['event'] == 'BLOCK_JOB_COMPLETED':
446 self
.assertTrue(error
, 'job completed unexpectedly')
447 self
.assert_qmp(event
, 'data/type', 'mirror')
448 self
.assert_qmp(event
, 'data/device', 'drive0')
449 self
.assert_qmp(event
, 'data/error', 'Input/output error')
451 elif event
['event'] == 'JOB_STATUS_CHANGE':
452 self
.assert_qmp(event
, 'data/id', 'drive0')
454 self
.assert_no_active_block_jobs()
456 def test_ignore_read(self
):
457 self
.assert_no_active_block_jobs()
459 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
460 target
=target_img
, on_source_error
='ignore')
461 self
.assert_qmp(result
, 'return', {})
463 event
= self
.vm
.get_qmp_event(wait
=True)
464 while event
['event'] == 'JOB_STATUS_CHANGE':
465 self
.assert_qmp(event
, 'data/id', 'drive0')
466 event
= self
.vm
.get_qmp_event(wait
=True)
468 self
.assertEqual(event
['event'], 'BLOCK_JOB_ERROR')
469 self
.assert_qmp(event
, 'data/device', 'drive0')
470 self
.assert_qmp(event
, 'data/operation', 'read')
471 result
= self
.vm
.qmp('query-block-jobs')
472 self
.assert_qmp(result
, 'return[0]/paused', False)
473 self
.complete_and_wait()
475 def test_large_cluster(self
):
476 self
.assert_no_active_block_jobs()
478 # Test COW into the target image. The first half of the
479 # cluster at MIRROR_GRANULARITY has to be copied from
480 # backing_img, even though sync='top'.
481 qemu_img('create', '-f', iotests
.imgfmt
, '-ocluster_size=131072,backing_file=%s' %(backing_img), target_img
)
482 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='top',
483 on_source_error
='ignore',
484 mode
='existing', target
=target_img
)
485 self
.assert_qmp(result
, 'return', {})
487 event
= self
.vm
.get_qmp_event(wait
=True)
488 while event
['event'] == 'JOB_STATUS_CHANGE':
489 self
.assert_qmp(event
, 'data/id', 'drive0')
490 event
= self
.vm
.get_qmp_event(wait
=True)
492 self
.assertEqual(event
['event'], 'BLOCK_JOB_ERROR')
493 self
.assert_qmp(event
, 'data/device', 'drive0')
494 self
.assert_qmp(event
, 'data/operation', 'read')
495 result
= self
.vm
.qmp('query-block-jobs')
496 self
.assert_qmp(result
, 'return[0]/paused', False)
497 self
.complete_and_wait()
500 # Detach blkdebug to compare images successfully
501 qemu_img('rebase', '-f', iotests
.imgfmt
, '-u', '-b', backing_img
, test_img
)
502 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
503 'target image does not match source after mirroring')
505 def test_stop_read(self
):
506 self
.assert_no_active_block_jobs()
508 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
509 target
=target_img
, on_source_error
='stop')
510 self
.assert_qmp(result
, 'return', {})
515 for event
in self
.vm
.get_qmp_events(wait
=True):
516 if event
['event'] == 'BLOCK_JOB_ERROR':
517 self
.assert_qmp(event
, 'data/device', 'drive0')
518 self
.assert_qmp(event
, 'data/operation', 'read')
520 result
= self
.vm
.qmp('query-block-jobs')
521 self
.assert_qmp(result
, 'return[0]/paused', True)
522 self
.assert_qmp(result
, 'return[0]/io-status', 'failed')
524 result
= self
.vm
.qmp('block-job-resume', device
='drive0')
525 self
.assert_qmp(result
, 'return', {})
527 elif event
['event'] == 'BLOCK_JOB_READY':
528 self
.assertTrue(error
, 'job completed unexpectedly')
529 self
.assert_qmp(event
, 'data/device', 'drive0')
532 result
= self
.vm
.qmp('query-block-jobs')
533 self
.assert_qmp(result
, 'return[0]/paused', False)
534 self
.assert_qmp(result
, 'return[0]/io-status', 'ok')
536 self
.complete_and_wait(wait_ready
=False)
537 self
.assert_no_active_block_jobs()
539 class TestWriteErrors(iotests
.QMPTestCase
):
540 image_len
= 2 * 1024 * 1024 # MB
542 # this should be a multiple of twice the default granularity
543 # so that we hit this offset first in state 1
544 MIRROR_GRANULARITY
= 1024 * 1024
546 def create_blkdebug_file(self
, name
, event
, errno
):
547 file = open(name
, 'w')
566 ''' % (event
, errno
, self
.MIRROR_GRANULARITY
// 512, event
, event
))
570 self
.blkdebug_file
= target_img
+ ".blkdebug"
571 iotests
.create_image(backing_img
, TestWriteErrors
.image_len
)
572 self
.create_blkdebug_file(self
.blkdebug_file
, "write_aio", 5)
573 qemu_img('create', '-f', iotests
.imgfmt
, '-obacking_file=%s' %(backing_img), test_img
)
574 self
.vm
= iotests
.VM().add_drive(test_img
)
575 self
.target_img
= 'blkdebug:%s:%s' % (self
.blkdebug_file
, target_img
)
576 qemu_img('create', '-f', iotests
.imgfmt
, '-osize=%d' %(TestWriteErrors
.image_len
), target_img
)
582 os
.remove(target_img
)
583 os
.remove(backing_img
)
584 os
.remove(self
.blkdebug_file
)
586 def test_report_write(self
):
587 self
.assert_no_active_block_jobs()
589 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
590 mode
='existing', target
=self
.target_img
)
591 self
.assert_qmp(result
, 'return', {})
596 for event
in self
.vm
.get_qmp_events(wait
=True):
597 if event
['event'] == 'BLOCK_JOB_ERROR':
598 self
.assert_qmp(event
, 'data/device', 'drive0')
599 self
.assert_qmp(event
, 'data/operation', 'write')
601 elif event
['event'] == 'BLOCK_JOB_READY':
602 self
.assertTrue(False, 'job completed unexpectedly')
603 elif event
['event'] == 'BLOCK_JOB_COMPLETED':
604 self
.assertTrue(error
, 'job completed unexpectedly')
605 self
.assert_qmp(event
, 'data/type', 'mirror')
606 self
.assert_qmp(event
, 'data/device', 'drive0')
607 self
.assert_qmp(event
, 'data/error', 'Input/output error')
610 self
.assert_no_active_block_jobs()
612 def test_ignore_write(self
):
613 self
.assert_no_active_block_jobs()
615 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
616 mode
='existing', target
=self
.target_img
,
617 on_target_error
='ignore')
618 self
.assert_qmp(result
, 'return', {})
620 event
= self
.vm
.event_wait(name
='BLOCK_JOB_ERROR')
621 self
.assertEqual(event
['event'], 'BLOCK_JOB_ERROR')
622 self
.assert_qmp(event
, 'data/device', 'drive0')
623 self
.assert_qmp(event
, 'data/operation', 'write')
624 result
= self
.vm
.qmp('query-block-jobs')
625 self
.assert_qmp(result
, 'return[0]/paused', False)
626 self
.complete_and_wait()
628 def test_stop_write(self
):
629 self
.assert_no_active_block_jobs()
631 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
632 mode
='existing', target
=self
.target_img
,
633 on_target_error
='stop')
634 self
.assert_qmp(result
, 'return', {})
639 for event
in self
.vm
.get_qmp_events(wait
=True):
640 if event
['event'] == 'BLOCK_JOB_ERROR':
641 self
.assert_qmp(event
, 'data/device', 'drive0')
642 self
.assert_qmp(event
, 'data/operation', 'write')
644 result
= self
.vm
.qmp('query-block-jobs')
645 self
.assert_qmp(result
, 'return[0]/paused', True)
646 self
.assert_qmp(result
, 'return[0]/io-status', 'failed')
648 result
= self
.vm
.qmp('block-job-resume', device
='drive0')
649 self
.assert_qmp(result
, 'return', {})
651 result
= self
.vm
.qmp('query-block-jobs')
652 self
.assert_qmp(result
, 'return[0]/paused', False)
653 self
.assert_qmp(result
, 'return[0]/io-status', 'ok')
655 elif event
['event'] == 'BLOCK_JOB_READY':
656 self
.assertTrue(error
, 'job completed unexpectedly')
657 self
.assert_qmp(event
, 'data/device', 'drive0')
660 self
.complete_and_wait(wait_ready
=False)
661 self
.assert_no_active_block_jobs()
663 class TestSetSpeed(iotests
.QMPTestCase
):
664 image_len
= 80 * 1024 * 1024 # MB
667 qemu_img('create', backing_img
, str(TestSetSpeed
.image_len
))
668 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'backing_file=%s' % backing_img
, test_img
)
669 self
.vm
= iotests
.VM().add_drive(test_img
)
675 os
.remove(backing_img
)
676 os
.remove(target_img
)
678 def test_set_speed(self
):
679 self
.assert_no_active_block_jobs()
681 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
683 self
.assert_qmp(result
, 'return', {})
686 result
= self
.vm
.qmp('query-block-jobs')
687 self
.assert_qmp(result
, 'return[0]/device', 'drive0')
688 self
.assert_qmp(result
, 'return[0]/speed', 0)
690 result
= self
.vm
.qmp('block-job-set-speed', device
='drive0', speed
=8 * 1024 * 1024)
691 self
.assert_qmp(result
, 'return', {})
693 # Ensure the speed we set was accepted
694 result
= self
.vm
.qmp('query-block-jobs')
695 self
.assert_qmp(result
, 'return[0]/device', 'drive0')
696 self
.assert_qmp(result
, 'return[0]/speed', 8 * 1024 * 1024)
698 self
.wait_ready_and_cancel()
700 # Check setting speed in drive-mirror works
701 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
702 target
=target_img
, speed
=4*1024*1024)
703 self
.assert_qmp(result
, 'return', {})
705 result
= self
.vm
.qmp('query-block-jobs')
706 self
.assert_qmp(result
, 'return[0]/device', 'drive0')
707 self
.assert_qmp(result
, 'return[0]/speed', 4 * 1024 * 1024)
709 self
.wait_ready_and_cancel()
711 def test_set_speed_invalid(self
):
712 self
.assert_no_active_block_jobs()
714 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
715 target
=target_img
, speed
=-1)
716 self
.assert_qmp(result
, 'error/class', 'GenericError')
718 self
.assert_no_active_block_jobs()
720 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
722 self
.assert_qmp(result
, 'return', {})
724 result
= self
.vm
.qmp('block-job-set-speed', device
='drive0', speed
=-1)
725 self
.assert_qmp(result
, 'error/class', 'GenericError')
727 self
.wait_ready_and_cancel()
729 class TestUnbackedSource(iotests
.QMPTestCase
):
730 image_len
= 2 * 1024 * 1024 # MB
733 qemu_img('create', '-f', iotests
.imgfmt
, test_img
,
734 str(TestUnbackedSource
.image_len
))
735 self
.vm
= iotests
.VM()
737 result
= self
.vm
.qmp('blockdev-add', node_name
='drive0',
738 driver
=iotests
.imgfmt
,
741 'filename': test_img
,
743 self
.assert_qmp(result
, 'return', {})
748 os
.remove(target_img
)
750 def test_absolute_paths_full(self
):
751 self
.assert_no_active_block_jobs()
752 result
= self
.vm
.qmp('drive-mirror', job_id
='drive0', device
='drive0',
753 sync
='full', target
=target_img
,
754 mode
='absolute-paths')
755 self
.assert_qmp(result
, 'return', {})
756 self
.complete_and_wait()
757 self
.assert_no_active_block_jobs()
759 def test_absolute_paths_top(self
):
760 self
.assert_no_active_block_jobs()
761 result
= self
.vm
.qmp('drive-mirror', job_id
='drive0', device
='drive0',
762 sync
='top', 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_none(self
):
769 self
.assert_no_active_block_jobs()
770 result
= self
.vm
.qmp('drive-mirror', job_id
='drive0', device
='drive0',
771 sync
='none', 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_existing_full(self
):
778 qemu_img('create', '-f', iotests
.imgfmt
, target_img
,
780 qemu_io('-c', 'write -P 42 0 64k', target_img
)
782 self
.assert_no_active_block_jobs()
783 result
= self
.vm
.qmp('drive-mirror', job_id
='drive0', device
='drive0',
784 sync
='full', target
=target_img
, mode
='existing')
785 self
.assert_qmp(result
, 'return', {})
786 self
.complete_and_wait()
787 self
.assert_no_active_block_jobs()
789 result
= self
.vm
.qmp('blockdev-del', node_name
='drive0')
790 self
.assert_qmp(result
, 'return', {})
792 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
793 'target image does not match source after mirroring')
795 def test_blockdev_full(self
):
796 qemu_img('create', '-f', iotests
.imgfmt
, target_img
,
798 qemu_io('-c', 'write -P 42 0 64k', target_img
)
800 result
= self
.vm
.qmp('blockdev-add', node_name
='target',
801 driver
=iotests
.imgfmt
,
804 'filename': target_img
,
806 self
.assert_qmp(result
, 'return', {})
808 self
.assert_no_active_block_jobs()
809 result
= self
.vm
.qmp('blockdev-mirror', job_id
='drive0', device
='drive0',
810 sync
='full', target
='target')
811 self
.assert_qmp(result
, 'return', {})
812 self
.complete_and_wait()
813 self
.assert_no_active_block_jobs()
815 result
= self
.vm
.qmp('blockdev-del', node_name
='drive0')
816 self
.assert_qmp(result
, 'return', {})
818 result
= self
.vm
.qmp('blockdev-del', node_name
='target')
819 self
.assert_qmp(result
, 'return', {})
821 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
822 'target image does not match source after mirroring')
824 class TestGranularity(iotests
.QMPTestCase
):
825 image_len
= 10 * 1024 * 1024 # MB
828 qemu_img('create', '-f', iotests
.imgfmt
, test_img
,
829 str(TestGranularity
.image_len
))
830 qemu_io('-c', 'write 0 %d' % (self
.image_len
),
832 self
.vm
= iotests
.VM().add_drive(test_img
)
837 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
838 'target image does not match source after mirroring')
840 os
.remove(target_img
)
842 def test_granularity(self
):
843 self
.assert_no_active_block_jobs()
844 result
= self
.vm
.qmp('drive-mirror', device
='drive0',
845 sync
='full', target
=target_img
,
846 mode
='absolute-paths', granularity
=8192)
847 self
.assert_qmp(result
, 'return', {})
849 event
= self
.vm
.get_qmp_event(wait
=60.0)
850 while event
['event'] == 'JOB_STATUS_CHANGE':
851 self
.assert_qmp(event
, 'data/id', 'drive0')
852 event
= self
.vm
.get_qmp_event(wait
=60.0)
854 # Failures will manifest as COMPLETED/ERROR.
855 self
.assert_qmp(event
, 'event', 'BLOCK_JOB_READY')
856 self
.complete_and_wait(drive
='drive0', wait_ready
=False)
857 self
.assert_no_active_block_jobs()
859 class TestRepairQuorum(iotests
.QMPTestCase
):
860 """ This class test quorum file repair using drive-mirror.
861 It's mostly a fork of TestSingleDrive """
862 image_len
= 1 * 1024 * 1024 # MB
863 IMAGES
= [ quorum_img1
, quorum_img2
, quorum_img3
]
865 @iotests.skip_if_unsupported(['quorum'])
867 self
.vm
= iotests
.VM()
869 if iotests
.qemu_default_machine
== 'pc':
870 self
.vm
.add_drive(None, 'media=cdrom', 'ide')
872 # Add each individual quorum images
873 for i
in self
.IMAGES
:
874 qemu_img('create', '-f', iotests
.imgfmt
, i
,
875 str(TestSingleDrive
.image_len
))
876 # Assign a node name to each quorum image in order to manipulate
878 opts
= "node-name=img%i" % self
.IMAGES
.index(i
)
879 opts
+= ',driver=%s' % iotests
.imgfmt
880 opts
+= ',file.driver=file'
881 opts
+= ',file.filename=%s' % i
882 self
.vm
= self
.vm
.add_blockdev(opts
)
886 #assemble the quorum block device from the individual files
887 args
= { "driver": "quorum", "node-name": "quorum0",
888 "vote-threshold": 2, "children": [ "img0", "img1", "img2" ] }
889 result
= self
.vm
.qmp("blockdev-add", **args
)
890 self
.assert_qmp(result
, 'return', {})
895 for i
in self
.IMAGES
+ [ quorum_repair_img
, quorum_snapshot_file
]:
896 # Do a try/except because the test may have deleted some images
902 def test_complete(self
):
903 self
.assert_no_active_block_jobs()
905 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
906 sync
='full', node_name
="repair0", replaces
="img1",
907 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
908 self
.assert_qmp(result
, 'return', {})
910 self
.complete_and_wait(drive
="job0")
911 self
.assert_has_block_node("repair0", quorum_repair_img
)
912 # TODO: a better test requiring some QEMU infrastructure will be added
913 # to check that this file is really driven by quorum
915 self
.assertTrue(iotests
.compare_images(quorum_img2
, quorum_repair_img
),
916 'target image does not match source after mirroring')
918 def test_cancel(self
):
919 self
.assert_no_active_block_jobs()
921 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
922 sync
='full', node_name
="repair0", replaces
="img1",
923 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
924 self
.assert_qmp(result
, 'return', {})
926 self
.cancel_and_wait(drive
="job0", force
=True)
927 # here we check that the last registered quorum file has not been
928 # swapped out and unref
929 self
.assert_has_block_node(None, quorum_img3
)
931 def test_cancel_after_ready(self
):
932 self
.assert_no_active_block_jobs()
934 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
935 sync
='full', node_name
="repair0", replaces
="img1",
936 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
937 self
.assert_qmp(result
, 'return', {})
939 self
.wait_ready_and_cancel(drive
="job0")
940 # here we check that the last registered quorum file has not been
941 # swapped out and unref
942 self
.assert_has_block_node(None, quorum_img3
)
944 self
.assertTrue(iotests
.compare_images(quorum_img2
, quorum_repair_img
),
945 'target image does not match source after mirroring')
947 def test_pause(self
):
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
.pause_job('job0')
957 result
= self
.vm
.qmp('query-block-jobs')
958 offset
= self
.dictpath(result
, 'return[0]/offset')
961 result
= self
.vm
.qmp('query-block-jobs')
962 self
.assert_qmp(result
, 'return[0]/offset', offset
)
964 result
= self
.vm
.qmp('block-job-resume', device
='job0')
965 self
.assert_qmp(result
, 'return', {})
967 self
.complete_and_wait(drive
="job0")
969 self
.assertTrue(iotests
.compare_images(quorum_img2
, quorum_repair_img
),
970 'target image does not match source after mirroring')
972 def test_medium_not_found(self
):
973 if iotests
.qemu_default_machine
!= 'pc':
976 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='drive0', # CD-ROM
980 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
981 self
.assert_qmp(result
, 'error/class', 'GenericError')
983 def test_image_not_found(self
):
984 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
985 sync
='full', node_name
='repair0', replaces
='img1',
986 mode
='existing', target
=quorum_repair_img
,
987 format
=iotests
.imgfmt
)
988 self
.assert_qmp(result
, 'error/class', 'GenericError')
990 def test_device_not_found(self
):
991 result
= self
.vm
.qmp('drive-mirror', job_id
='job0',
992 device
='nonexistent', sync
='full',
995 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
996 self
.assert_qmp(result
, 'error/class', 'GenericError')
998 def test_wrong_sync_mode(self
):
999 result
= self
.vm
.qmp('drive-mirror', device
='quorum0', job_id
='job0',
1000 node_name
='repair0',
1002 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
1003 self
.assert_qmp(result
, 'error/class', 'GenericError')
1005 def test_no_node_name(self
):
1006 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
1007 sync
='full', replaces
='img1',
1008 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
1009 self
.assert_qmp(result
, 'error/class', 'GenericError')
1011 def test_nonexistent_replaces(self
):
1012 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
1013 sync
='full', node_name
='repair0', replaces
='img77',
1014 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
1015 self
.assert_qmp(result
, 'error/class', 'GenericError')
1017 def test_after_a_quorum_snapshot(self
):
1018 result
= self
.vm
.qmp('blockdev-snapshot-sync', node_name
='img1',
1019 snapshot_file
=quorum_snapshot_file
,
1020 snapshot_node_name
="snap1");
1022 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
1023 sync
='full', node_name
='repair0', replaces
="img1",
1024 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
1025 self
.assert_qmp(result
, 'error/class', 'GenericError')
1027 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
1028 sync
='full', node_name
='repair0', replaces
="snap1",
1029 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
1030 self
.assert_qmp(result
, 'return', {})
1032 self
.complete_and_wait('job0')
1033 self
.assert_has_block_node("repair0", quorum_repair_img
)
1034 # TODO: a better test requiring some QEMU infrastructure will be added
1035 # to check that this file is really driven by quorum
1037 # Test mirroring with a source that does not have any parents (not even a
1039 class TestOrphanedSource(iotests
.QMPTestCase
):
1041 blk0
= { 'node-name': 'src',
1042 'driver': 'null-co' }
1044 blk1
= { 'node-name': 'dest',
1045 'driver': 'null-co' }
1047 blk2
= { 'node-name': 'dest-ro',
1048 'driver': 'null-co',
1051 self
.vm
= iotests
.VM()
1052 self
.vm
.add_blockdev(self
.vm
.qmp_to_opts(blk0
))
1053 self
.vm
.add_blockdev(self
.vm
.qmp_to_opts(blk1
))
1054 self
.vm
.add_blockdev(self
.vm
.qmp_to_opts(blk2
))
1060 def test_no_job_id(self
):
1061 self
.assert_no_active_block_jobs()
1063 result
= self
.vm
.qmp('blockdev-mirror', device
='src', sync
='full',
1065 self
.assert_qmp(result
, 'error/class', 'GenericError')
1067 def test_success(self
):
1068 self
.assert_no_active_block_jobs()
1070 result
= self
.vm
.qmp('blockdev-mirror', job_id
='job', device
='src',
1071 sync
='full', target
='dest')
1072 self
.assert_qmp(result
, 'return', {})
1074 self
.complete_and_wait('job')
1076 def test_failing_permissions(self
):
1077 self
.assert_no_active_block_jobs()
1079 result
= self
.vm
.qmp('blockdev-mirror', device
='src', sync
='full',
1081 self
.assert_qmp(result
, 'error/class', 'GenericError')
1083 def test_failing_permission_in_complete(self
):
1084 self
.assert_no_active_block_jobs()
1086 # Unshare consistent-read on the target
1087 # (The mirror job does not care)
1088 result
= self
.vm
.qmp('blockdev-add',
1090 node_name
='dest-perm',
1092 unshare_child_perms
=['consistent-read'])
1093 self
.assert_qmp(result
, 'return', {})
1095 result
= self
.vm
.qmp('blockdev-mirror', job_id
='job', device
='src',
1096 sync
='full', target
='dest',
1097 filter_node_name
='mirror-filter')
1098 self
.assert_qmp(result
, 'return', {})
1100 # Require consistent-read on the source
1101 # (We can only add this node once the job has started, or it
1102 # will complain that it does not want to run on non-root nodes)
1103 result
= self
.vm
.qmp('blockdev-add',
1105 node_name
='src-perm',
1107 take_child_perms
=['consistent-read'])
1108 self
.assert_qmp(result
, 'return', {})
1110 # While completing, mirror will attempt to replace src by
1111 # dest, which must fail because src-perm requires
1112 # consistent-read but dest-perm does not share it; thus
1113 # aborting the job when it is supposed to complete
1114 self
.complete_and_wait('job',
1115 completion_error
='Operation not permitted')
1117 # Assert that all of our nodes are still there (except for the
1118 # mirror filter, which should be gone despite the failure)
1119 nodes
= self
.vm
.qmp('query-named-block-nodes')['return']
1120 nodes
= [node
['node-name'] for node
in nodes
]
1122 for expect
in ('src', 'src-perm', 'dest', 'dest-perm'):
1123 self
.assertTrue(expect
in nodes
, '%s disappeared' % expect
)
1124 self
.assertFalse('mirror-filter' in nodes
,
1125 'Mirror filter node did not disappear')
1127 if __name__
== '__main__':
1128 iotests
.main(supported_fmts
=['qcow2', 'qed'],
1129 supported_protocols
=['file'],
1130 supported_platforms
=['linux', 'freebsd', 'netbsd', 'openbsd'])