]>
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/>.
25 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 quorum_img1
= os
.path
.join(iotests
.test_dir
, 'quorum1.img')
33 quorum_img2
= os
.path
.join(iotests
.test_dir
, 'quorum2.img')
34 quorum_img3
= os
.path
.join(iotests
.test_dir
, 'quorum3.img')
35 quorum_repair_img
= os
.path
.join(iotests
.test_dir
, 'quorum_repair.img')
36 quorum_snapshot_file
= os
.path
.join(iotests
.test_dir
, 'quorum_snapshot.img')
38 nbd_sock_path
= os
.path
.join(iotests
.sock_dir
, 'nbd.sock')
40 class TestSingleDrive(iotests
.QMPTestCase
):
41 image_len
= 1 * 1024 * 1024 # MB
42 qmp_cmd
= 'drive-mirror'
43 qmp_target
= target_img
46 iotests
.create_image(backing_img
, self
.image_len
)
47 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'backing_file=%s' % backing_img
, test_img
)
48 self
.vm
= iotests
.VM().add_drive(test_img
, "node-name=top,backing.node-name=base")
49 if iotests
.qemu_default_machine
== 'pc':
50 self
.vm
.add_drive(None, 'media=cdrom', 'ide')
56 os
.remove(backing_img
)
62 def test_complete(self
):
63 self
.assert_no_active_block_jobs()
65 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
66 target
=self
.qmp_target
)
67 self
.assert_qmp(result
, 'return', {})
69 self
.complete_and_wait()
70 result
= self
.vm
.qmp('query-block')
71 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
73 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
74 'target image does not match source after mirroring')
76 def test_cancel(self
):
77 self
.assert_no_active_block_jobs()
79 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
80 target
=self
.qmp_target
)
81 self
.assert_qmp(result
, 'return', {})
83 self
.cancel_and_wait(force
=True)
84 result
= self
.vm
.qmp('query-block')
85 self
.assert_qmp(result
, 'return[0]/inserted/file', test_img
)
87 def test_cancel_after_ready(self
):
88 self
.assert_no_active_block_jobs()
90 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
91 target
=self
.qmp_target
)
92 self
.assert_qmp(result
, 'return', {})
94 self
.wait_ready_and_cancel()
95 result
= self
.vm
.qmp('query-block')
96 self
.assert_qmp(result
, 'return[0]/inserted/file', test_img
)
98 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
99 'target image does not match source after mirroring')
101 def test_pause(self
):
102 self
.assert_no_active_block_jobs()
104 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
105 target
=self
.qmp_target
)
106 self
.assert_qmp(result
, 'return', {})
108 self
.pause_job('drive0')
110 result
= self
.vm
.qmp('query-block-jobs')
111 offset
= self
.dictpath(result
, 'return[0]/offset')
114 result
= self
.vm
.qmp('query-block-jobs')
115 self
.assert_qmp(result
, 'return[0]/offset', offset
)
117 result
= self
.vm
.qmp('block-job-resume', device
='drive0')
118 self
.assert_qmp(result
, 'return', {})
120 self
.complete_and_wait()
122 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
123 'target image does not match source after mirroring')
125 def test_small_buffer(self
):
126 self
.assert_no_active_block_jobs()
128 # A small buffer is rounded up automatically
129 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
130 buf_size
=4096, target
=self
.qmp_target
)
131 self
.assert_qmp(result
, 'return', {})
133 self
.complete_and_wait()
134 result
= self
.vm
.qmp('query-block')
135 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
137 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
138 'target image does not match source after mirroring')
140 def test_small_buffer2(self
):
141 self
.assert_no_active_block_jobs()
143 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'cluster_size=%d,size=%d'
144 % (self
.image_len
, self
.image_len
), target_img
)
145 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
146 buf_size
=65536, mode
='existing', target
=self
.qmp_target
)
147 self
.assert_qmp(result
, 'return', {})
149 self
.complete_and_wait()
150 result
= self
.vm
.qmp('query-block')
151 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
153 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
154 'target image does not match source after mirroring')
156 def test_large_cluster(self
):
157 self
.assert_no_active_block_jobs()
159 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'cluster_size=%d,backing_file=%s'
160 % (self
.image_len
, backing_img
), target_img
)
161 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
162 mode
='existing', target
=self
.qmp_target
)
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 # Tests that the insertion of the mirror_top filter node doesn't make a
173 # difference to query-block
174 def test_implicit_node(self
):
175 self
.assert_no_active_block_jobs()
177 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
178 target
=self
.qmp_target
)
179 self
.assert_qmp(result
, 'return', {})
181 result
= self
.vm
.qmp('query-block')
182 self
.assert_qmp(result
, 'return[0]/inserted/file', test_img
)
183 self
.assert_qmp(result
, 'return[0]/inserted/drv', iotests
.imgfmt
)
184 self
.assert_qmp(result
, 'return[0]/inserted/backing_file', backing_img
)
185 self
.assert_qmp(result
, 'return[0]/inserted/backing_file_depth', 1)
186 self
.assert_qmp(result
, 'return[0]/inserted/image/filename', test_img
)
187 self
.assert_qmp(result
, 'return[0]/inserted/image/backing-image/filename', backing_img
)
189 result
= self
.vm
.qmp('query-blockstats')
190 self
.assert_qmp(result
, 'return[0]/node-name', 'top')
191 self
.assert_qmp(result
, 'return[0]/backing/node-name', 'base')
193 self
.cancel_and_wait(force
=True)
194 result
= self
.vm
.qmp('query-block')
195 self
.assert_qmp(result
, 'return[0]/inserted/file', test_img
)
196 self
.assert_qmp(result
, 'return[0]/inserted/drv', iotests
.imgfmt
)
197 self
.assert_qmp(result
, 'return[0]/inserted/backing_file', backing_img
)
198 self
.assert_qmp(result
, 'return[0]/inserted/backing_file_depth', 1)
199 self
.assert_qmp(result
, 'return[0]/inserted/image/filename', test_img
)
200 self
.assert_qmp(result
, 'return[0]/inserted/image/backing-image/filename', backing_img
)
202 result
= self
.vm
.qmp('query-blockstats')
203 self
.assert_qmp(result
, 'return[0]/node-name', 'top')
204 self
.assert_qmp(result
, 'return[0]/backing/node-name', 'base')
206 def test_medium_not_found(self
):
207 if iotests
.qemu_default_machine
!= 'pc':
210 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='ide1-cd0', sync
='full',
211 target
=self
.qmp_target
)
212 self
.assert_qmp(result
, 'error/class', 'GenericError')
214 def test_image_not_found(self
):
215 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='drive0', sync
='full',
216 mode
='existing', target
=self
.qmp_target
)
217 self
.assert_qmp(result
, 'error/class', 'GenericError')
219 def test_device_not_found(self
):
220 result
= self
.vm
.qmp(self
.qmp_cmd
, device
='nonexistent', sync
='full',
221 target
=self
.qmp_target
)
222 self
.assert_qmp(result
, 'error/class', 'GenericError')
224 class TestSingleBlockdev(TestSingleDrive
):
225 qmp_cmd
= 'blockdev-mirror'
229 TestSingleDrive
.setUp(self
)
230 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'backing_file=%s' % backing_img
, target_img
)
231 args
= {'driver': iotests
.imgfmt
,
232 'node-name': self
.qmp_target
,
233 'file': { 'filename': target_img
, 'driver': 'file' } }
234 result
= self
.vm
.qmp("blockdev-add", **args
)
235 self
.assert_qmp(result
, 'return', {})
237 def test_mirror_to_self(self
):
238 result
= self
.vm
.qmp(self
.qmp_cmd
, job_id
='job0',
239 device
=self
.qmp_target
, sync
='full',
240 target
=self
.qmp_target
)
241 self
.assert_qmp(result
, 'error/class', 'GenericError')
243 test_large_cluster
= None
244 test_image_not_found
= None
245 test_small_buffer2
= None
247 class TestSingleDriveZeroLength(TestSingleDrive
):
249 test_small_buffer2
= None
250 test_large_cluster
= None
252 class TestSingleBlockdevZeroLength(TestSingleBlockdev
):
255 class TestSingleDriveUnalignedLength(TestSingleDrive
):
256 image_len
= 1025 * 1024
257 test_small_buffer2
= None
258 test_large_cluster
= None
260 class TestSingleBlockdevUnalignedLength(TestSingleBlockdev
):
261 image_len
= 1025 * 1024
263 class TestMirrorNoBacking(iotests
.QMPTestCase
):
264 image_len
= 2 * 1024 * 1024 # MB
267 iotests
.create_image(backing_img
, TestMirrorNoBacking
.image_len
)
268 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'backing_file=%s' % backing_img
, test_img
)
269 self
.vm
= iotests
.VM().add_drive(test_img
)
275 os
.remove(backing_img
)
277 os
.remove(target_backing_img
)
280 os
.remove(target_img
)
282 def test_complete(self
):
283 self
.assert_no_active_block_jobs()
285 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'backing_file=%s' % backing_img
, target_img
)
286 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
287 mode
='existing', target
=target_img
)
288 self
.assert_qmp(result
, 'return', {})
290 self
.complete_and_wait()
291 result
= self
.vm
.qmp('query-block')
292 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
294 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
295 'target image does not match source after mirroring')
297 def test_cancel(self
):
298 self
.assert_no_active_block_jobs()
300 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'backing_file=%s' % backing_img
, target_img
)
301 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
302 mode
='existing', target
=target_img
)
303 self
.assert_qmp(result
, 'return', {})
305 self
.wait_ready_and_cancel()
306 result
= self
.vm
.qmp('query-block')
307 self
.assert_qmp(result
, 'return[0]/inserted/file', test_img
)
309 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
310 'target image does not match source after mirroring')
312 def test_large_cluster(self
):
313 self
.assert_no_active_block_jobs()
315 # qemu-img create fails if the image is not there
316 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'size=%d'
317 %(TestMirrorNoBacking
.image_len
), target_backing_img
)
318 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'cluster_size=%d,backing_file=%s'
319 % (TestMirrorNoBacking
.image_len
, target_backing_img
), target_img
)
321 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
322 mode
='existing', target
=target_img
)
323 self
.assert_qmp(result
, 'return', {})
325 self
.complete_and_wait()
326 result
= self
.vm
.qmp('query-block')
327 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
329 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
330 'target image does not match source after mirroring')
332 class TestMirrorResized(iotests
.QMPTestCase
):
333 backing_len
= 1 * 1024 * 1024 # MB
334 image_len
= 2 * 1024 * 1024 # MB
337 iotests
.create_image(backing_img
, TestMirrorResized
.backing_len
)
338 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'backing_file=%s' % backing_img
, test_img
)
339 qemu_img('resize', test_img
, '2M')
340 self
.vm
= iotests
.VM().add_drive(test_img
)
346 os
.remove(backing_img
)
348 os
.remove(target_img
)
352 def test_complete_top(self
):
353 self
.assert_no_active_block_jobs()
355 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='top',
357 self
.assert_qmp(result
, 'return', {})
359 self
.complete_and_wait()
360 result
= self
.vm
.qmp('query-block')
361 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
363 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
364 'target image does not match source after mirroring')
366 def test_complete_full(self
):
367 self
.assert_no_active_block_jobs()
369 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
371 self
.assert_qmp(result
, 'return', {})
373 self
.complete_and_wait()
374 result
= self
.vm
.qmp('query-block')
375 self
.assert_qmp(result
, 'return[0]/inserted/file', target_img
)
377 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
378 'target image does not match source after mirroring')
380 class TestReadErrors(iotests
.QMPTestCase
):
381 image_len
= 2 * 1024 * 1024 # MB
383 # this should be a multiple of twice the default granularity
384 # so that we hit this offset first in state 1
385 MIRROR_GRANULARITY
= 1024 * 1024
387 def create_blkdebug_file(self
, name
, event
, errno
):
388 file = open(name
, 'w')
407 ''' % (event
, errno
, self
.MIRROR_GRANULARITY
// 512, event
, event
))
411 self
.blkdebug_file
= backing_img
+ ".blkdebug"
412 iotests
.create_image(backing_img
, TestReadErrors
.image_len
)
413 self
.create_blkdebug_file(self
.blkdebug_file
, "read_aio", 5)
414 qemu_img('create', '-f', iotests
.imgfmt
,
415 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
416 % (self
.blkdebug_file
, backing_img
),
418 # Write something for tests that use sync='top'
419 qemu_io('-c', 'write %d 512' % (self
.MIRROR_GRANULARITY
+ 65536),
421 self
.vm
= iotests
.VM().add_drive(test_img
)
427 os
.remove(target_img
)
428 os
.remove(backing_img
)
429 os
.remove(self
.blkdebug_file
)
431 def test_report_read(self
):
432 self
.assert_no_active_block_jobs()
434 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
436 self
.assert_qmp(result
, 'return', {})
441 for event
in self
.vm
.get_qmp_events(wait
=True):
442 if event
['event'] == 'BLOCK_JOB_ERROR':
443 self
.assert_qmp(event
, 'data/device', 'drive0')
444 self
.assert_qmp(event
, 'data/operation', 'read')
446 elif event
['event'] == 'BLOCK_JOB_READY':
447 self
.assertTrue(False, 'job completed unexpectedly')
448 elif event
['event'] == 'BLOCK_JOB_COMPLETED':
449 self
.assertTrue(error
, 'job completed unexpectedly')
450 self
.assert_qmp(event
, 'data/type', 'mirror')
451 self
.assert_qmp(event
, 'data/device', 'drive0')
452 self
.assert_qmp(event
, 'data/error', 'Input/output error')
454 elif event
['event'] == 'JOB_STATUS_CHANGE':
455 self
.assert_qmp(event
, 'data/id', 'drive0')
457 self
.assert_no_active_block_jobs()
459 def test_ignore_read(self
):
460 self
.assert_no_active_block_jobs()
462 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
463 target
=target_img
, on_source_error
='ignore')
464 self
.assert_qmp(result
, 'return', {})
466 event
= self
.vm
.get_qmp_event(wait
=True)
467 while event
['event'] == 'JOB_STATUS_CHANGE':
468 self
.assert_qmp(event
, 'data/id', 'drive0')
469 event
= self
.vm
.get_qmp_event(wait
=True)
471 self
.assertEqual(event
['event'], 'BLOCK_JOB_ERROR')
472 self
.assert_qmp(event
, 'data/device', 'drive0')
473 self
.assert_qmp(event
, 'data/operation', 'read')
474 result
= self
.vm
.qmp('query-block-jobs')
475 self
.assert_qmp(result
, 'return[0]/paused', False)
476 self
.complete_and_wait()
478 def test_large_cluster(self
):
479 self
.assert_no_active_block_jobs()
481 # Test COW into the target image. The first half of the
482 # cluster at MIRROR_GRANULARITY has to be copied from
483 # backing_img, even though sync='top'.
484 qemu_img('create', '-f', iotests
.imgfmt
, '-ocluster_size=131072,backing_file=%s' %(backing_img), target_img
)
485 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='top',
486 on_source_error
='ignore',
487 mode
='existing', target
=target_img
)
488 self
.assert_qmp(result
, 'return', {})
490 event
= self
.vm
.get_qmp_event(wait
=True)
491 while event
['event'] == 'JOB_STATUS_CHANGE':
492 self
.assert_qmp(event
, 'data/id', 'drive0')
493 event
= self
.vm
.get_qmp_event(wait
=True)
495 self
.assertEqual(event
['event'], 'BLOCK_JOB_ERROR')
496 self
.assert_qmp(event
, 'data/device', 'drive0')
497 self
.assert_qmp(event
, 'data/operation', 'read')
498 result
= self
.vm
.qmp('query-block-jobs')
499 self
.assert_qmp(result
, 'return[0]/paused', False)
500 self
.complete_and_wait()
503 # Detach blkdebug to compare images successfully
504 qemu_img('rebase', '-f', iotests
.imgfmt
, '-u', '-b', backing_img
, test_img
)
505 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
506 'target image does not match source after mirroring')
508 def test_stop_read(self
):
509 self
.assert_no_active_block_jobs()
511 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
512 target
=target_img
, on_source_error
='stop')
513 self
.assert_qmp(result
, 'return', {})
518 for event
in self
.vm
.get_qmp_events(wait
=True):
519 if event
['event'] == 'BLOCK_JOB_ERROR':
520 self
.assert_qmp(event
, 'data/device', 'drive0')
521 self
.assert_qmp(event
, 'data/operation', 'read')
523 result
= self
.vm
.qmp('query-block-jobs')
524 self
.assert_qmp(result
, 'return[0]/paused', True)
525 self
.assert_qmp(result
, 'return[0]/io-status', 'failed')
527 result
= self
.vm
.qmp('block-job-resume', device
='drive0')
528 self
.assert_qmp(result
, 'return', {})
530 elif event
['event'] == 'BLOCK_JOB_READY':
531 self
.assertTrue(error
, 'job completed unexpectedly')
532 self
.assert_qmp(event
, 'data/device', 'drive0')
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 self
.complete_and_wait(wait_ready
=False)
540 self
.assert_no_active_block_jobs()
542 class TestWriteErrors(iotests
.QMPTestCase
):
543 image_len
= 2 * 1024 * 1024 # MB
545 # this should be a multiple of twice the default granularity
546 # so that we hit this offset first in state 1
547 MIRROR_GRANULARITY
= 1024 * 1024
549 def create_blkdebug_file(self
, name
, event
, errno
):
550 file = open(name
, 'w')
569 ''' % (event
, errno
, self
.MIRROR_GRANULARITY
// 512, event
, event
))
573 self
.blkdebug_file
= target_img
+ ".blkdebug"
574 iotests
.create_image(backing_img
, TestWriteErrors
.image_len
)
575 self
.create_blkdebug_file(self
.blkdebug_file
, "write_aio", 5)
576 qemu_img('create', '-f', iotests
.imgfmt
, '-obacking_file=%s' %(backing_img), test_img
)
577 self
.vm
= iotests
.VM().add_drive(test_img
)
578 self
.target_img
= 'blkdebug:%s:%s' % (self
.blkdebug_file
, target_img
)
579 qemu_img('create', '-f', iotests
.imgfmt
, '-osize=%d' %(TestWriteErrors
.image_len
), target_img
)
585 os
.remove(target_img
)
586 os
.remove(backing_img
)
587 os
.remove(self
.blkdebug_file
)
589 def test_report_write(self
):
590 self
.assert_no_active_block_jobs()
592 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
593 mode
='existing', target
=self
.target_img
)
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 elif event
['event'] == 'BLOCK_JOB_READY':
605 self
.assertTrue(False, 'job completed unexpectedly')
606 elif event
['event'] == 'BLOCK_JOB_COMPLETED':
607 self
.assertTrue(error
, 'job completed unexpectedly')
608 self
.assert_qmp(event
, 'data/type', 'mirror')
609 self
.assert_qmp(event
, 'data/device', 'drive0')
610 self
.assert_qmp(event
, 'data/error', 'Input/output error')
613 self
.assert_no_active_block_jobs()
615 def test_ignore_write(self
):
616 self
.assert_no_active_block_jobs()
618 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
619 mode
='existing', target
=self
.target_img
,
620 on_target_error
='ignore')
621 self
.assert_qmp(result
, 'return', {})
623 event
= self
.vm
.event_wait(name
='BLOCK_JOB_ERROR')
624 self
.assertEqual(event
['event'], 'BLOCK_JOB_ERROR')
625 self
.assert_qmp(event
, 'data/device', 'drive0')
626 self
.assert_qmp(event
, 'data/operation', 'write')
627 result
= self
.vm
.qmp('query-block-jobs')
628 self
.assert_qmp(result
, 'return[0]/paused', False)
629 self
.complete_and_wait()
631 def test_stop_write(self
):
632 self
.assert_no_active_block_jobs()
634 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
635 mode
='existing', target
=self
.target_img
,
636 on_target_error
='stop')
637 self
.assert_qmp(result
, 'return', {})
642 for event
in self
.vm
.get_qmp_events(wait
=True):
643 if event
['event'] == 'BLOCK_JOB_ERROR':
644 self
.assert_qmp(event
, 'data/device', 'drive0')
645 self
.assert_qmp(event
, 'data/operation', 'write')
647 result
= self
.vm
.qmp('query-block-jobs')
648 self
.assert_qmp(result
, 'return[0]/paused', True)
649 self
.assert_qmp(result
, 'return[0]/io-status', 'failed')
651 result
= self
.vm
.qmp('block-job-resume', device
='drive0')
652 self
.assert_qmp(result
, 'return', {})
654 result
= self
.vm
.qmp('query-block-jobs')
655 self
.assert_qmp(result
, 'return[0]/paused', False)
656 self
.assert_qmp(result
, 'return[0]/io-status', 'ok')
658 elif event
['event'] == 'BLOCK_JOB_READY':
659 self
.assertTrue(error
, 'job completed unexpectedly')
660 self
.assert_qmp(event
, 'data/device', 'drive0')
663 self
.complete_and_wait(wait_ready
=False)
664 self
.assert_no_active_block_jobs()
666 class TestSetSpeed(iotests
.QMPTestCase
):
667 image_len
= 80 * 1024 * 1024 # MB
670 qemu_img('create', backing_img
, str(TestSetSpeed
.image_len
))
671 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'backing_file=%s' % backing_img
, test_img
)
672 self
.vm
= iotests
.VM().add_drive(test_img
)
678 os
.remove(backing_img
)
679 os
.remove(target_img
)
681 def test_set_speed(self
):
682 self
.assert_no_active_block_jobs()
684 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
686 self
.assert_qmp(result
, 'return', {})
689 result
= self
.vm
.qmp('query-block-jobs')
690 self
.assert_qmp(result
, 'return[0]/device', 'drive0')
691 self
.assert_qmp(result
, 'return[0]/speed', 0)
693 result
= self
.vm
.qmp('block-job-set-speed', device
='drive0', speed
=8 * 1024 * 1024)
694 self
.assert_qmp(result
, 'return', {})
696 # Ensure the speed we set was accepted
697 result
= self
.vm
.qmp('query-block-jobs')
698 self
.assert_qmp(result
, 'return[0]/device', 'drive0')
699 self
.assert_qmp(result
, 'return[0]/speed', 8 * 1024 * 1024)
701 self
.wait_ready_and_cancel()
703 # Check setting speed in drive-mirror works
704 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
705 target
=target_img
, speed
=4*1024*1024)
706 self
.assert_qmp(result
, 'return', {})
708 result
= self
.vm
.qmp('query-block-jobs')
709 self
.assert_qmp(result
, 'return[0]/device', 'drive0')
710 self
.assert_qmp(result
, 'return[0]/speed', 4 * 1024 * 1024)
712 self
.wait_ready_and_cancel()
714 def test_set_speed_invalid(self
):
715 self
.assert_no_active_block_jobs()
717 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
718 target
=target_img
, speed
=-1)
719 self
.assert_qmp(result
, 'error/class', 'GenericError')
721 self
.assert_no_active_block_jobs()
723 result
= self
.vm
.qmp('drive-mirror', device
='drive0', sync
='full',
725 self
.assert_qmp(result
, 'return', {})
727 result
= self
.vm
.qmp('block-job-set-speed', device
='drive0', speed
=-1)
728 self
.assert_qmp(result
, 'error/class', 'GenericError')
730 self
.wait_ready_and_cancel()
732 class TestUnbackedSource(iotests
.QMPTestCase
):
733 image_len
= 2 * 1024 * 1024 # MB
736 qemu_img('create', '-f', iotests
.imgfmt
, test_img
,
737 str(TestUnbackedSource
.image_len
))
738 self
.vm
= iotests
.VM()
740 result
= self
.vm
.qmp('blockdev-add', node_name
='drive0',
741 driver
=iotests
.imgfmt
,
744 'filename': test_img
,
746 self
.assert_qmp(result
, 'return', {})
751 os
.remove(target_img
)
753 def test_absolute_paths_full(self
):
754 self
.assert_no_active_block_jobs()
755 result
= self
.vm
.qmp('drive-mirror', job_id
='drive0', device
='drive0',
756 sync
='full', target
=target_img
,
757 mode
='absolute-paths')
758 self
.assert_qmp(result
, 'return', {})
759 self
.complete_and_wait()
760 self
.assert_no_active_block_jobs()
762 def test_absolute_paths_top(self
):
763 self
.assert_no_active_block_jobs()
764 result
= self
.vm
.qmp('drive-mirror', job_id
='drive0', device
='drive0',
765 sync
='top', target
=target_img
,
766 mode
='absolute-paths')
767 self
.assert_qmp(result
, 'return', {})
768 self
.complete_and_wait()
769 self
.assert_no_active_block_jobs()
771 def test_absolute_paths_none(self
):
772 self
.assert_no_active_block_jobs()
773 result
= self
.vm
.qmp('drive-mirror', job_id
='drive0', device
='drive0',
774 sync
='none', target
=target_img
,
775 mode
='absolute-paths')
776 self
.assert_qmp(result
, 'return', {})
777 self
.complete_and_wait()
778 self
.assert_no_active_block_jobs()
780 def test_existing_full(self
):
781 qemu_img('create', '-f', iotests
.imgfmt
, target_img
,
783 qemu_io('-c', 'write -P 42 0 64k', target_img
)
785 self
.assert_no_active_block_jobs()
786 result
= self
.vm
.qmp('drive-mirror', job_id
='drive0', device
='drive0',
787 sync
='full', target
=target_img
, mode
='existing')
788 self
.assert_qmp(result
, 'return', {})
789 self
.complete_and_wait()
790 self
.assert_no_active_block_jobs()
792 result
= self
.vm
.qmp('blockdev-del', node_name
='drive0')
793 self
.assert_qmp(result
, 'return', {})
795 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
796 'target image does not match source after mirroring')
798 def test_blockdev_full(self
):
799 qemu_img('create', '-f', iotests
.imgfmt
, target_img
,
801 qemu_io('-c', 'write -P 42 0 64k', target_img
)
803 result
= self
.vm
.qmp('blockdev-add', node_name
='target',
804 driver
=iotests
.imgfmt
,
807 'filename': target_img
,
809 self
.assert_qmp(result
, 'return', {})
811 self
.assert_no_active_block_jobs()
812 result
= self
.vm
.qmp('blockdev-mirror', job_id
='drive0', device
='drive0',
813 sync
='full', target
='target')
814 self
.assert_qmp(result
, 'return', {})
815 self
.complete_and_wait()
816 self
.assert_no_active_block_jobs()
818 result
= self
.vm
.qmp('blockdev-del', node_name
='drive0')
819 self
.assert_qmp(result
, 'return', {})
821 result
= self
.vm
.qmp('blockdev-del', node_name
='target')
822 self
.assert_qmp(result
, 'return', {})
824 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
825 'target image does not match source after mirroring')
827 class TestGranularity(iotests
.QMPTestCase
):
828 image_len
= 10 * 1024 * 1024 # MB
831 qemu_img('create', '-f', iotests
.imgfmt
, test_img
,
832 str(TestGranularity
.image_len
))
833 qemu_io('-c', 'write 0 %d' % (self
.image_len
),
835 self
.vm
= iotests
.VM().add_drive(test_img
)
840 self
.assertTrue(iotests
.compare_images(test_img
, target_img
),
841 'target image does not match source after mirroring')
843 os
.remove(target_img
)
845 def test_granularity(self
):
846 self
.assert_no_active_block_jobs()
847 result
= self
.vm
.qmp('drive-mirror', device
='drive0',
848 sync
='full', target
=target_img
,
849 mode
='absolute-paths', granularity
=8192)
850 self
.assert_qmp(result
, 'return', {})
852 event
= self
.vm
.get_qmp_event(wait
=60.0)
853 while event
['event'] == 'JOB_STATUS_CHANGE':
854 self
.assert_qmp(event
, 'data/id', 'drive0')
855 event
= self
.vm
.get_qmp_event(wait
=60.0)
857 # Failures will manifest as COMPLETED/ERROR.
858 self
.assert_qmp(event
, 'event', 'BLOCK_JOB_READY')
859 self
.complete_and_wait(drive
='drive0', wait_ready
=False)
860 self
.assert_no_active_block_jobs()
862 class TestRepairQuorum(iotests
.QMPTestCase
):
863 """ This class test quorum file repair using drive-mirror.
864 It's mostly a fork of TestSingleDrive """
865 image_len
= 1 * 1024 * 1024 # MB
866 IMAGES
= [ quorum_img1
, quorum_img2
, quorum_img3
]
868 @iotests.skip_if_unsupported(['quorum'])
870 self
.vm
= iotests
.VM()
872 if iotests
.qemu_default_machine
== 'pc':
873 self
.vm
.add_drive(None, 'media=cdrom', 'ide')
875 # Add each individual quorum images
876 for i
in self
.IMAGES
:
877 qemu_img('create', '-f', iotests
.imgfmt
, i
,
879 # Assign a node name to each quorum image in order to manipulate
881 opts
= "node-name=img%i" % self
.IMAGES
.index(i
)
882 opts
+= ',driver=%s' % iotests
.imgfmt
883 opts
+= ',file.driver=file'
884 opts
+= ',file.filename=%s' % i
885 self
.vm
= self
.vm
.add_blockdev(opts
)
889 #assemble the quorum block device from the individual files
890 args
= { "driver": "quorum", "node-name": "quorum0",
891 "vote-threshold": 2, "children": [ "img0", "img1", "img2" ] }
892 result
= self
.vm
.qmp("blockdev-add", **args
)
893 self
.assert_qmp(result
, 'return', {})
898 for i
in self
.IMAGES
+ [ quorum_repair_img
, quorum_snapshot_file
,
900 # Do a try/except because the test may have deleted some images
906 def test_complete(self
):
907 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
908 sync
='full', node_name
="repair0", replaces
="img1",
909 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
910 self
.assert_qmp(result
, 'return', {})
912 self
.complete_and_wait(drive
="job0")
913 self
.assert_has_block_node("repair0", quorum_repair_img
)
914 self
.vm
.assert_block_path('quorum0', '/children.1', 'repair0')
916 self
.assertTrue(iotests
.compare_images(quorum_img2
, quorum_repair_img
),
917 'target image does not match source after mirroring')
919 def test_cancel(self
):
920 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
921 sync
='full', node_name
="repair0", replaces
="img1",
922 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
923 self
.assert_qmp(result
, 'return', {})
925 self
.cancel_and_wait(drive
="job0", force
=True)
926 # here we check that the last registered quorum file has not been
927 # swapped out and unref
928 self
.assert_has_block_node(None, quorum_img3
)
930 def test_cancel_after_ready(self
):
931 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
932 sync
='full', node_name
="repair0", replaces
="img1",
933 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
934 self
.assert_qmp(result
, 'return', {})
936 self
.wait_ready_and_cancel(drive
="job0")
937 # here we check that the last registered quorum file has not been
938 # swapped out and unref
939 self
.assert_has_block_node(None, quorum_img3
)
941 self
.assertTrue(iotests
.compare_images(quorum_img2
, quorum_repair_img
),
942 'target image does not match source after mirroring')
944 def test_pause(self
):
945 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
946 sync
='full', node_name
="repair0", replaces
="img1",
947 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
948 self
.assert_qmp(result
, 'return', {})
950 self
.pause_job('job0')
952 result
= self
.vm
.qmp('query-block-jobs')
953 offset
= self
.dictpath(result
, 'return[0]/offset')
956 result
= self
.vm
.qmp('query-block-jobs')
957 self
.assert_qmp(result
, 'return[0]/offset', offset
)
959 result
= self
.vm
.qmp('block-job-resume', device
='job0')
960 self
.assert_qmp(result
, 'return', {})
962 self
.complete_and_wait(drive
="job0")
964 self
.assertTrue(iotests
.compare_images(quorum_img2
, quorum_repair_img
),
965 'target image does not match source after mirroring')
967 def test_medium_not_found(self
):
968 if iotests
.qemu_default_machine
!= 'pc':
971 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='drive0', # CD-ROM
975 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
976 self
.assert_qmp(result
, 'error/class', 'GenericError')
978 def test_image_not_found(self
):
979 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
980 sync
='full', node_name
='repair0', replaces
='img1',
981 mode
='existing', target
=quorum_repair_img
,
982 format
=iotests
.imgfmt
)
983 self
.assert_qmp(result
, 'error/class', 'GenericError')
985 def test_device_not_found(self
):
986 result
= self
.vm
.qmp('drive-mirror', job_id
='job0',
987 device
='nonexistent', sync
='full',
990 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
991 self
.assert_qmp(result
, 'error/class', 'GenericError')
993 def test_wrong_sync_mode(self
):
994 result
= self
.vm
.qmp('drive-mirror', device
='quorum0', job_id
='job0',
997 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
998 self
.assert_qmp(result
, 'error/class', 'GenericError')
1000 def test_no_node_name(self
):
1001 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
1002 sync
='full', replaces
='img1',
1003 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
1004 self
.assert_qmp(result
, 'error/class', 'GenericError')
1006 def test_nonexistent_replaces(self
):
1007 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
1008 sync
='full', node_name
='repair0', replaces
='img77',
1009 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
1010 self
.assert_qmp(result
, 'error/class', 'GenericError')
1012 def test_after_a_quorum_snapshot(self
):
1013 result
= self
.vm
.qmp('blockdev-snapshot-sync', node_name
='img1',
1014 snapshot_file
=quorum_snapshot_file
,
1015 snapshot_node_name
="snap1");
1017 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
1018 sync
='full', node_name
='repair0', replaces
="img1",
1019 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
1020 self
.assert_qmp(result
, 'error/class', 'GenericError')
1022 result
= self
.vm
.qmp('drive-mirror', job_id
='job0', device
='quorum0',
1023 sync
='full', node_name
='repair0', replaces
="snap1",
1024 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
1025 self
.assert_qmp(result
, 'return', {})
1027 self
.complete_and_wait('job0')
1028 self
.assert_has_block_node("repair0", quorum_repair_img
)
1029 self
.vm
.assert_block_path('quorum0', '/children.1', 'repair0')
1031 def test_with_other_parent(self
):
1033 Check that we cannot replace a Quorum child when it has other
1036 result
= self
.vm
.qmp('nbd-server-start',
1039 'data': {'path': nbd_sock_path
}
1041 self
.assert_qmp(result
, 'return', {})
1043 result
= self
.vm
.qmp('nbd-server-add', device
='img1')
1044 self
.assert_qmp(result
, 'return', {})
1046 result
= self
.vm
.qmp('drive-mirror', job_id
='mirror', device
='quorum0',
1047 sync
='full', node_name
='repair0', replaces
='img1',
1048 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
1049 self
.assert_qmp(result
, 'error/desc',
1050 "Cannot replace 'img1' by a node mirrored from "
1051 "'quorum0', because it cannot be guaranteed that doing "
1052 "so would not lead to an abrupt change of visible data")
1054 def test_with_other_parents_after_mirror_start(self
):
1056 The same as test_with_other_parent(), but add the NBD server
1057 only when the mirror job is already running.
1059 result
= self
.vm
.qmp('nbd-server-start',
1062 'data': {'path': nbd_sock_path
}
1064 self
.assert_qmp(result
, 'return', {})
1066 result
= self
.vm
.qmp('drive-mirror', job_id
='mirror', device
='quorum0',
1067 sync
='full', node_name
='repair0', replaces
='img1',
1068 target
=quorum_repair_img
, format
=iotests
.imgfmt
)
1069 self
.assert_qmp(result
, 'return', {})
1071 result
= self
.vm
.qmp('nbd-server-add', device
='img1')
1072 self
.assert_qmp(result
, 'return', {})
1074 # The full error message goes to stderr, we will check it later
1075 self
.complete_and_wait('mirror',
1076 completion_error
='Operation not permitted')
1078 # Should not have been replaced
1079 self
.vm
.assert_block_path('quorum0', '/children.1', 'img1')
1081 # Check the full error message now
1083 log
= self
.vm
.get_log()
1084 log
= re
.sub(r
'^\[I \d+\.\d+\] OPENED\n', '', log
)
1085 log
= re
.sub(r
'^Formatting.*\n', '', log
)
1086 log
= re
.sub(r
'\n\[I \+\d+\.\d+\] CLOSED\n?$', '', log
)
1087 log
= re
.sub(r
'^%s: ' % os
.path
.basename(iotests
.qemu_prog
), '', log
)
1089 self
.assertEqual(log
,
1090 "Can no longer replace 'img1' by 'repair0', because " +
1091 "it can no longer be guaranteed that doing so would " +
1092 "not lead to an abrupt change of visible data")
1095 # Test mirroring with a source that does not have any parents (not even a
1097 class TestOrphanedSource(iotests
.QMPTestCase
):
1099 blk0
= { 'node-name': 'src',
1100 'driver': 'null-co' }
1102 blk1
= { 'node-name': 'dest',
1103 'driver': 'null-co' }
1105 blk2
= { 'node-name': 'dest-ro',
1106 'driver': 'null-co',
1109 self
.vm
= iotests
.VM()
1110 self
.vm
.add_blockdev(self
.vm
.qmp_to_opts(blk0
))
1111 self
.vm
.add_blockdev(self
.vm
.qmp_to_opts(blk1
))
1112 self
.vm
.add_blockdev(self
.vm
.qmp_to_opts(blk2
))
1118 def test_no_job_id(self
):
1119 self
.assert_no_active_block_jobs()
1121 result
= self
.vm
.qmp('blockdev-mirror', device
='src', sync
='full',
1123 self
.assert_qmp(result
, 'error/class', 'GenericError')
1125 def test_success(self
):
1126 self
.assert_no_active_block_jobs()
1128 result
= self
.vm
.qmp('blockdev-mirror', job_id
='job', device
='src',
1129 sync
='full', target
='dest')
1130 self
.assert_qmp(result
, 'return', {})
1132 self
.complete_and_wait('job')
1134 def test_failing_permissions(self
):
1135 self
.assert_no_active_block_jobs()
1137 result
= self
.vm
.qmp('blockdev-mirror', device
='src', sync
='full',
1139 self
.assert_qmp(result
, 'error/class', 'GenericError')
1141 def test_failing_permission_in_complete(self
):
1142 self
.assert_no_active_block_jobs()
1144 # Unshare consistent-read on the target
1145 # (The mirror job does not care)
1146 result
= self
.vm
.qmp('blockdev-add',
1148 node_name
='dest-perm',
1150 unshare_child_perms
=['consistent-read'])
1151 self
.assert_qmp(result
, 'return', {})
1153 result
= self
.vm
.qmp('blockdev-mirror', job_id
='job', device
='src',
1154 sync
='full', target
='dest',
1155 filter_node_name
='mirror-filter')
1156 self
.assert_qmp(result
, 'return', {})
1158 # Require consistent-read on the source
1159 # (We can only add this node once the job has started, or it
1160 # will complain that it does not want to run on non-root nodes)
1161 result
= self
.vm
.qmp('blockdev-add',
1163 node_name
='src-perm',
1165 take_child_perms
=['consistent-read'])
1166 self
.assert_qmp(result
, 'return', {})
1168 # While completing, mirror will attempt to replace src by
1169 # dest, which must fail because src-perm requires
1170 # consistent-read but dest-perm does not share it; thus
1171 # aborting the job when it is supposed to complete
1172 self
.complete_and_wait('job',
1173 completion_error
='Operation not permitted')
1175 # Assert that all of our nodes are still there (except for the
1176 # mirror filter, which should be gone despite the failure)
1177 nodes
= self
.vm
.qmp('query-named-block-nodes')['return']
1178 nodes
= [node
['node-name'] for node
in nodes
]
1180 for expect
in ('src', 'src-perm', 'dest', 'dest-perm'):
1181 self
.assertTrue(expect
in nodes
, '%s disappeared' % expect
)
1182 self
.assertFalse('mirror-filter' in nodes
,
1183 'Mirror filter node did not disappear')
1185 # Test cases for @replaces that do not necessarily involve Quorum
1186 class TestReplaces(iotests
.QMPTestCase
):
1187 # Each of these test cases needs their own block graph, so do not
1188 # create any nodes here
1190 self
.vm
= iotests
.VM()
1195 for img
in (test_img
, target_img
):
1201 @iotests.skip_if_unsupported(['copy-on-read'])
1202 def test_replace_filter(self
):
1204 Check that we can replace filter nodes.
1206 result
= self
.vm
.qmp('blockdev-add', **{
1207 'driver': 'copy-on-read',
1208 'node-name': 'filter0',
1210 'driver': 'copy-on-read',
1211 'node-name': 'filter1',
1217 self
.assert_qmp(result
, 'return', {})
1219 result
= self
.vm
.qmp('blockdev-add',
1220 node_name
='target', driver
='null-co')
1221 self
.assert_qmp(result
, 'return', {})
1223 result
= self
.vm
.qmp('blockdev-mirror', job_id
='mirror', device
='filter0',
1224 target
='target', sync
='full', replaces
='filter1')
1225 self
.assert_qmp(result
, 'return', {})
1227 self
.complete_and_wait('mirror')
1229 self
.vm
.assert_block_path('filter0', '/file', 'target')
1231 if __name__
== '__main__':
1232 iotests
.main(supported_fmts
=['qcow2', 'qed'],
1233 supported_protocols
=['file'],
1234 supported_platforms
=['linux', 'freebsd', 'netbsd', 'openbsd'])