1#!/usr/bin/env python3 2# 3# Tests for image mirroring. 4# 5# Copyright (C) 2012 Red Hat, Inc. 6# 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. 11# 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. 16# 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/>. 19# 20 21import time 22import os 23import re 24import iotests 25from iotests import qemu_img, qemu_io 26 27backing_img = os.path.join(iotests.test_dir, 'backing.img') 28target_backing_img = os.path.join(iotests.test_dir, 'target-backing.img') 29test_img = os.path.join(iotests.test_dir, 'test.img') 30target_img = os.path.join(iotests.test_dir, 'target.img') 31 32quorum_img1 = os.path.join(iotests.test_dir, 'quorum1.img') 33quorum_img2 = os.path.join(iotests.test_dir, 'quorum2.img') 34quorum_img3 = os.path.join(iotests.test_dir, 'quorum3.img') 35quorum_repair_img = os.path.join(iotests.test_dir, 'quorum_repair.img') 36quorum_snapshot_file = os.path.join(iotests.test_dir, 'quorum_snapshot.img') 37 38nbd_sock_path = os.path.join(iotests.test_dir, 'nbd.sock') 39 40class TestSingleDrive(iotests.QMPTestCase): 41 image_len = 1 * 1024 * 1024 # MB 42 qmp_cmd = 'drive-mirror' 43 qmp_target = target_img 44 45 def setUp(self): 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') 51 self.vm.launch() 52 53 def tearDown(self): 54 self.vm.shutdown() 55 os.remove(test_img) 56 os.remove(backing_img) 57 try: 58 os.remove(target_img) 59 except OSError: 60 pass 61 62 def test_complete(self): 63 self.assert_no_active_block_jobs() 64 65 result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', 66 target=self.qmp_target) 67 self.assert_qmp(result, 'return', {}) 68 69 self.complete_and_wait() 70 result = self.vm.qmp('query-block') 71 self.assert_qmp(result, 'return[0]/inserted/file', target_img) 72 self.vm.shutdown() 73 self.assertTrue(iotests.compare_images(test_img, target_img), 74 'target image does not match source after mirroring') 75 76 def test_cancel(self): 77 self.assert_no_active_block_jobs() 78 79 result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', 80 target=self.qmp_target) 81 self.assert_qmp(result, 'return', {}) 82 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) 86 87 def test_cancel_after_ready(self): 88 self.assert_no_active_block_jobs() 89 90 result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', 91 target=self.qmp_target) 92 self.assert_qmp(result, 'return', {}) 93 94 self.wait_ready_and_cancel() 95 result = self.vm.qmp('query-block') 96 self.assert_qmp(result, 'return[0]/inserted/file', test_img) 97 self.vm.shutdown() 98 self.assertTrue(iotests.compare_images(test_img, target_img), 99 'target image does not match source after mirroring') 100 101 def test_pause(self): 102 self.assert_no_active_block_jobs() 103 104 result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', 105 target=self.qmp_target) 106 self.assert_qmp(result, 'return', {}) 107 108 self.pause_job('drive0') 109 110 result = self.vm.qmp('query-block-jobs') 111 offset = self.dictpath(result, 'return[0]/offset') 112 113 time.sleep(0.5) 114 result = self.vm.qmp('query-block-jobs') 115 self.assert_qmp(result, 'return[0]/offset', offset) 116 117 result = self.vm.qmp('block-job-resume', device='drive0') 118 self.assert_qmp(result, 'return', {}) 119 120 self.complete_and_wait() 121 self.vm.shutdown() 122 self.assertTrue(iotests.compare_images(test_img, target_img), 123 'target image does not match source after mirroring') 124 125 def test_small_buffer(self): 126 self.assert_no_active_block_jobs() 127 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', {}) 132 133 self.complete_and_wait() 134 result = self.vm.qmp('query-block') 135 self.assert_qmp(result, 'return[0]/inserted/file', target_img) 136 self.vm.shutdown() 137 self.assertTrue(iotests.compare_images(test_img, target_img), 138 'target image does not match source after mirroring') 139 140 def test_small_buffer2(self): 141 self.assert_no_active_block_jobs() 142 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', {}) 148 149 self.complete_and_wait() 150 result = self.vm.qmp('query-block') 151 self.assert_qmp(result, 'return[0]/inserted/file', target_img) 152 self.vm.shutdown() 153 self.assertTrue(iotests.compare_images(test_img, target_img), 154 'target image does not match source after mirroring') 155 156 def test_large_cluster(self): 157 self.assert_no_active_block_jobs() 158 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', {}) 164 165 self.complete_and_wait() 166 result = self.vm.qmp('query-block') 167 self.assert_qmp(result, 'return[0]/inserted/file', target_img) 168 self.vm.shutdown() 169 self.assertTrue(iotests.compare_images(test_img, target_img), 170 'target image does not match source after mirroring') 171 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() 176 177 result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', 178 target=self.qmp_target) 179 self.assert_qmp(result, 'return', {}) 180 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) 188 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') 192 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) 201 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') 205 206 def test_medium_not_found(self): 207 if iotests.qemu_default_machine != 'pc': 208 return 209 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') 213 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') 218 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') 223 224class TestSingleBlockdev(TestSingleDrive): 225 qmp_cmd = 'blockdev-mirror' 226 qmp_target = 'node1' 227 228 def setUp(self): 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', {}) 236 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') 242 243 test_large_cluster = None 244 test_image_not_found = None 245 test_small_buffer2 = None 246 247class TestSingleDriveZeroLength(TestSingleDrive): 248 image_len = 0 249 test_small_buffer2 = None 250 test_large_cluster = None 251 252class TestSingleBlockdevZeroLength(TestSingleBlockdev): 253 image_len = 0 254 255class TestSingleDriveUnalignedLength(TestSingleDrive): 256 image_len = 1025 * 1024 257 test_small_buffer2 = None 258 test_large_cluster = None 259 260class TestSingleBlockdevUnalignedLength(TestSingleBlockdev): 261 image_len = 1025 * 1024 262 263class TestMirrorNoBacking(iotests.QMPTestCase): 264 image_len = 2 * 1024 * 1024 # MB 265 266 def setUp(self): 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) 270 self.vm.launch() 271 272 def tearDown(self): 273 self.vm.shutdown() 274 os.remove(test_img) 275 os.remove(backing_img) 276 try: 277 os.remove(target_backing_img) 278 except: 279 pass 280 os.remove(target_img) 281 282 def test_complete(self): 283 self.assert_no_active_block_jobs() 284 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', {}) 289 290 self.complete_and_wait() 291 result = self.vm.qmp('query-block') 292 self.assert_qmp(result, 'return[0]/inserted/file', target_img) 293 self.vm.shutdown() 294 self.assertTrue(iotests.compare_images(test_img, target_img), 295 'target image does not match source after mirroring') 296 297 def test_cancel(self): 298 self.assert_no_active_block_jobs() 299 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', {}) 304 305 self.wait_ready_and_cancel() 306 result = self.vm.qmp('query-block') 307 self.assert_qmp(result, 'return[0]/inserted/file', test_img) 308 self.vm.shutdown() 309 self.assertTrue(iotests.compare_images(test_img, target_img), 310 'target image does not match source after mirroring') 311 312 def test_large_cluster(self): 313 self.assert_no_active_block_jobs() 314 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) 320 321 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 322 mode='existing', target=target_img) 323 self.assert_qmp(result, 'return', {}) 324 325 self.complete_and_wait() 326 result = self.vm.qmp('query-block') 327 self.assert_qmp(result, 'return[0]/inserted/file', target_img) 328 self.vm.shutdown() 329 self.assertTrue(iotests.compare_images(test_img, target_img), 330 'target image does not match source after mirroring') 331 332class TestMirrorResized(iotests.QMPTestCase): 333 backing_len = 1 * 1024 * 1024 # MB 334 image_len = 2 * 1024 * 1024 # MB 335 336 def setUp(self): 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) 341 self.vm.launch() 342 343 def tearDown(self): 344 self.vm.shutdown() 345 os.remove(test_img) 346 os.remove(backing_img) 347 try: 348 os.remove(target_img) 349 except OSError: 350 pass 351 352 def test_complete_top(self): 353 self.assert_no_active_block_jobs() 354 355 result = self.vm.qmp('drive-mirror', device='drive0', sync='top', 356 target=target_img) 357 self.assert_qmp(result, 'return', {}) 358 359 self.complete_and_wait() 360 result = self.vm.qmp('query-block') 361 self.assert_qmp(result, 'return[0]/inserted/file', target_img) 362 self.vm.shutdown() 363 self.assertTrue(iotests.compare_images(test_img, target_img), 364 'target image does not match source after mirroring') 365 366 def test_complete_full(self): 367 self.assert_no_active_block_jobs() 368 369 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 370 target=target_img) 371 self.assert_qmp(result, 'return', {}) 372 373 self.complete_and_wait() 374 result = self.vm.qmp('query-block') 375 self.assert_qmp(result, 'return[0]/inserted/file', target_img) 376 self.vm.shutdown() 377 self.assertTrue(iotests.compare_images(test_img, target_img), 378 'target image does not match source after mirroring') 379 380class TestReadErrors(iotests.QMPTestCase): 381 image_len = 2 * 1024 * 1024 # MB 382 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 386 387 def create_blkdebug_file(self, name, event, errno): 388 file = open(name, 'w') 389 file.write(''' 390[inject-error] 391state = "1" 392event = "%s" 393errno = "%d" 394immediately = "off" 395once = "on" 396sector = "%d" 397 398[set-state] 399state = "1" 400event = "%s" 401new_state = "2" 402 403[set-state] 404state = "2" 405event = "%s" 406new_state = "1" 407''' % (event, errno, self.MIRROR_GRANULARITY // 512, event, event)) 408 file.close() 409 410 def setUp(self): 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), 417 test_img) 418 # Write something for tests that use sync='top' 419 qemu_io('-c', 'write %d 512' % (self.MIRROR_GRANULARITY + 65536), 420 test_img) 421 self.vm = iotests.VM().add_drive(test_img) 422 self.vm.launch() 423 424 def tearDown(self): 425 self.vm.shutdown() 426 os.remove(test_img) 427 os.remove(target_img) 428 os.remove(backing_img) 429 os.remove(self.blkdebug_file) 430 431 def test_report_read(self): 432 self.assert_no_active_block_jobs() 433 434 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 435 target=target_img) 436 self.assert_qmp(result, 'return', {}) 437 438 completed = False 439 error = False 440 while not completed: 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') 445 error = True 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') 453 completed = True 454 elif event['event'] == 'JOB_STATUS_CHANGE': 455 self.assert_qmp(event, 'data/id', 'drive0') 456 457 self.assert_no_active_block_jobs() 458 459 def test_ignore_read(self): 460 self.assert_no_active_block_jobs() 461 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', {}) 465 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) 470 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() 477 478 def test_large_cluster(self): 479 self.assert_no_active_block_jobs() 480 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', {}) 489 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) 494 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() 501 self.vm.shutdown() 502 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') 507 508 def test_stop_read(self): 509 self.assert_no_active_block_jobs() 510 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', {}) 514 515 error = False 516 ready = False 517 while not ready: 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') 522 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') 526 527 result = self.vm.qmp('block-job-resume', device='drive0') 528 self.assert_qmp(result, 'return', {}) 529 error = True 530 elif event['event'] == 'BLOCK_JOB_READY': 531 self.assertTrue(error, 'job completed unexpectedly') 532 self.assert_qmp(event, 'data/device', 'drive0') 533 ready = True 534 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') 538 539 self.complete_and_wait(wait_ready=False) 540 self.assert_no_active_block_jobs() 541 542class TestWriteErrors(iotests.QMPTestCase): 543 image_len = 2 * 1024 * 1024 # MB 544 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 548 549 def create_blkdebug_file(self, name, event, errno): 550 file = open(name, 'w') 551 file.write(''' 552[inject-error] 553state = "1" 554event = "%s" 555errno = "%d" 556immediately = "off" 557once = "on" 558sector = "%d" 559 560[set-state] 561state = "1" 562event = "%s" 563new_state = "2" 564 565[set-state] 566state = "2" 567event = "%s" 568new_state = "1" 569''' % (event, errno, self.MIRROR_GRANULARITY // 512, event, event)) 570 file.close() 571 572 def setUp(self): 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) 580 self.vm.launch() 581 582 def tearDown(self): 583 self.vm.shutdown() 584 os.remove(test_img) 585 os.remove(target_img) 586 os.remove(backing_img) 587 os.remove(self.blkdebug_file) 588 589 def test_report_write(self): 590 self.assert_no_active_block_jobs() 591 592 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 593 mode='existing', target=self.target_img) 594 self.assert_qmp(result, 'return', {}) 595 596 completed = False 597 error = False 598 while not completed: 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') 603 error = True 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') 611 completed = True 612 613 self.assert_no_active_block_jobs() 614 615 def test_ignore_write(self): 616 self.assert_no_active_block_jobs() 617 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', {}) 622 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() 630 631 def test_stop_write(self): 632 self.assert_no_active_block_jobs() 633 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', {}) 638 639 error = False 640 ready = False 641 while not ready: 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') 646 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') 650 651 result = self.vm.qmp('block-job-resume', device='drive0') 652 self.assert_qmp(result, 'return', {}) 653 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') 657 error = True 658 elif event['event'] == 'BLOCK_JOB_READY': 659 self.assertTrue(error, 'job completed unexpectedly') 660 self.assert_qmp(event, 'data/device', 'drive0') 661 ready = True 662 663 self.complete_and_wait(wait_ready=False) 664 self.assert_no_active_block_jobs() 665 666class TestSetSpeed(iotests.QMPTestCase): 667 image_len = 80 * 1024 * 1024 # MB 668 669 def setUp(self): 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) 673 self.vm.launch() 674 675 def tearDown(self): 676 self.vm.shutdown() 677 os.remove(test_img) 678 os.remove(backing_img) 679 os.remove(target_img) 680 681 def test_set_speed(self): 682 self.assert_no_active_block_jobs() 683 684 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 685 target=target_img) 686 self.assert_qmp(result, 'return', {}) 687 688 # Default speed is 0 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) 692 693 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) 694 self.assert_qmp(result, 'return', {}) 695 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) 700 701 self.wait_ready_and_cancel() 702 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', {}) 707 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) 711 712 self.wait_ready_and_cancel() 713 714 def test_set_speed_invalid(self): 715 self.assert_no_active_block_jobs() 716 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') 720 721 self.assert_no_active_block_jobs() 722 723 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 724 target=target_img) 725 self.assert_qmp(result, 'return', {}) 726 727 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1) 728 self.assert_qmp(result, 'error/class', 'GenericError') 729 730 self.wait_ready_and_cancel() 731 732class TestUnbackedSource(iotests.QMPTestCase): 733 image_len = 2 * 1024 * 1024 # MB 734 735 def setUp(self): 736 qemu_img('create', '-f', iotests.imgfmt, test_img, 737 str(TestUnbackedSource.image_len)) 738 self.vm = iotests.VM() 739 self.vm.launch() 740 result = self.vm.qmp('blockdev-add', node_name='drive0', 741 driver=iotests.imgfmt, 742 file={ 743 'driver': 'file', 744 'filename': test_img, 745 }) 746 self.assert_qmp(result, 'return', {}) 747 748 def tearDown(self): 749 self.vm.shutdown() 750 os.remove(test_img) 751 os.remove(target_img) 752 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() 761 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() 770 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() 779 780 def test_existing_full(self): 781 qemu_img('create', '-f', iotests.imgfmt, target_img, 782 str(self.image_len)) 783 qemu_io('-c', 'write -P 42 0 64k', target_img) 784 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() 791 792 result = self.vm.qmp('blockdev-del', node_name='drive0') 793 self.assert_qmp(result, 'return', {}) 794 795 self.assertTrue(iotests.compare_images(test_img, target_img), 796 'target image does not match source after mirroring') 797 798 def test_blockdev_full(self): 799 qemu_img('create', '-f', iotests.imgfmt, target_img, 800 str(self.image_len)) 801 qemu_io('-c', 'write -P 42 0 64k', target_img) 802 803 result = self.vm.qmp('blockdev-add', node_name='target', 804 driver=iotests.imgfmt, 805 file={ 806 'driver': 'file', 807 'filename': target_img, 808 }) 809 self.assert_qmp(result, 'return', {}) 810 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() 817 818 result = self.vm.qmp('blockdev-del', node_name='drive0') 819 self.assert_qmp(result, 'return', {}) 820 821 result = self.vm.qmp('blockdev-del', node_name='target') 822 self.assert_qmp(result, 'return', {}) 823 824 self.assertTrue(iotests.compare_images(test_img, target_img), 825 'target image does not match source after mirroring') 826 827class TestGranularity(iotests.QMPTestCase): 828 image_len = 10 * 1024 * 1024 # MB 829 830 def setUp(self): 831 qemu_img('create', '-f', iotests.imgfmt, test_img, 832 str(TestGranularity.image_len)) 833 qemu_io('-c', 'write 0 %d' % (self.image_len), 834 test_img) 835 self.vm = iotests.VM().add_drive(test_img) 836 self.vm.launch() 837 838 def tearDown(self): 839 self.vm.shutdown() 840 self.assertTrue(iotests.compare_images(test_img, target_img), 841 'target image does not match source after mirroring') 842 os.remove(test_img) 843 os.remove(target_img) 844 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', {}) 851 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) 856 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() 861 862class 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 ] 867 868 @iotests.skip_if_unsupported(['quorum']) 869 def setUp(self): 870 self.vm = iotests.VM() 871 872 if iotests.qemu_default_machine == 'pc': 873 self.vm.add_drive(None, 'media=cdrom', 'ide') 874 875 # Add each individual quorum images 876 for i in self.IMAGES: 877 qemu_img('create', '-f', iotests.imgfmt, i, 878 str(self.image_len)) 879 # Assign a node name to each quorum image in order to manipulate 880 # them 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) 886 887 self.vm.launch() 888 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', {}) 894 895 896 def tearDown(self): 897 self.vm.shutdown() 898 for i in self.IMAGES + [ quorum_repair_img, quorum_snapshot_file, 899 nbd_sock_path ]: 900 # Do a try/except because the test may have deleted some images 901 try: 902 os.remove(i) 903 except OSError: 904 pass 905 906 def test_complete(self): 907 self.assert_no_active_block_jobs() 908 909 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 910 sync='full', node_name="repair0", replaces="img1", 911 target=quorum_repair_img, format=iotests.imgfmt) 912 self.assert_qmp(result, 'return', {}) 913 914 self.complete_and_wait(drive="job0") 915 self.assert_has_block_node("repair0", quorum_repair_img) 916 self.vm.assert_block_path('quorum0', '/children.1', 'repair0') 917 self.vm.shutdown() 918 self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img), 919 'target image does not match source after mirroring') 920 921 def test_cancel(self): 922 self.assert_no_active_block_jobs() 923 924 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 925 sync='full', node_name="repair0", replaces="img1", 926 target=quorum_repair_img, format=iotests.imgfmt) 927 self.assert_qmp(result, 'return', {}) 928 929 self.cancel_and_wait(drive="job0", force=True) 930 # here we check that the last registered quorum file has not been 931 # swapped out and unref 932 self.assert_has_block_node(None, quorum_img3) 933 934 def test_cancel_after_ready(self): 935 self.assert_no_active_block_jobs() 936 937 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 938 sync='full', node_name="repair0", replaces="img1", 939 target=quorum_repair_img, format=iotests.imgfmt) 940 self.assert_qmp(result, 'return', {}) 941 942 self.wait_ready_and_cancel(drive="job0") 943 # here we check that the last registered quorum file has not been 944 # swapped out and unref 945 self.assert_has_block_node(None, quorum_img3) 946 self.vm.shutdown() 947 self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img), 948 'target image does not match source after mirroring') 949 950 def test_pause(self): 951 self.assert_no_active_block_jobs() 952 953 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 954 sync='full', node_name="repair0", replaces="img1", 955 target=quorum_repair_img, format=iotests.imgfmt) 956 self.assert_qmp(result, 'return', {}) 957 958 self.pause_job('job0') 959 960 result = self.vm.qmp('query-block-jobs') 961 offset = self.dictpath(result, 'return[0]/offset') 962 963 time.sleep(0.5) 964 result = self.vm.qmp('query-block-jobs') 965 self.assert_qmp(result, 'return[0]/offset', offset) 966 967 result = self.vm.qmp('block-job-resume', device='job0') 968 self.assert_qmp(result, 'return', {}) 969 970 self.complete_and_wait(drive="job0") 971 self.vm.shutdown() 972 self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img), 973 'target image does not match source after mirroring') 974 975 def test_medium_not_found(self): 976 if iotests.qemu_default_machine != 'pc': 977 return 978 979 result = self.vm.qmp('drive-mirror', job_id='job0', device='drive0', # CD-ROM 980 sync='full', 981 node_name='repair0', 982 replaces='img1', 983 target=quorum_repair_img, format=iotests.imgfmt) 984 self.assert_qmp(result, 'error/class', 'GenericError') 985 986 def test_image_not_found(self): 987 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 988 sync='full', node_name='repair0', replaces='img1', 989 mode='existing', target=quorum_repair_img, 990 format=iotests.imgfmt) 991 self.assert_qmp(result, 'error/class', 'GenericError') 992 993 def test_device_not_found(self): 994 result = self.vm.qmp('drive-mirror', job_id='job0', 995 device='nonexistent', sync='full', 996 node_name='repair0', 997 replaces='img1', 998 target=quorum_repair_img, format=iotests.imgfmt) 999 self.assert_qmp(result, 'error/class', 'GenericError') 1000 1001 def test_wrong_sync_mode(self): 1002 result = self.vm.qmp('drive-mirror', device='quorum0', job_id='job0', 1003 node_name='repair0', 1004 replaces='img1', 1005 target=quorum_repair_img, format=iotests.imgfmt) 1006 self.assert_qmp(result, 'error/class', 'GenericError') 1007 1008 def test_no_node_name(self): 1009 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 1010 sync='full', replaces='img1', 1011 target=quorum_repair_img, format=iotests.imgfmt) 1012 self.assert_qmp(result, 'error/class', 'GenericError') 1013 1014 def test_nonexistent_replaces(self): 1015 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 1016 sync='full', node_name='repair0', replaces='img77', 1017 target=quorum_repair_img, format=iotests.imgfmt) 1018 self.assert_qmp(result, 'error/class', 'GenericError') 1019 1020 def test_after_a_quorum_snapshot(self): 1021 result = self.vm.qmp('blockdev-snapshot-sync', node_name='img1', 1022 snapshot_file=quorum_snapshot_file, 1023 snapshot_node_name="snap1"); 1024 1025 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 1026 sync='full', node_name='repair0', replaces="img1", 1027 target=quorum_repair_img, format=iotests.imgfmt) 1028 self.assert_qmp(result, 'error/class', 'GenericError') 1029 1030 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 1031 sync='full', node_name='repair0', replaces="snap1", 1032 target=quorum_repair_img, format=iotests.imgfmt) 1033 self.assert_qmp(result, 'return', {}) 1034 1035 self.complete_and_wait('job0') 1036 self.assert_has_block_node("repair0", quorum_repair_img) 1037 self.vm.assert_block_path('quorum0', '/children.1', 'repair0') 1038 1039 def test_with_other_parent(self): 1040 """ 1041 Check that we cannot replace a Quorum child when it has other 1042 parents. 1043 """ 1044 result = self.vm.qmp('nbd-server-start', 1045 addr={ 1046 'type': 'unix', 1047 'data': {'path': nbd_sock_path} 1048 }) 1049 self.assert_qmp(result, 'return', {}) 1050 1051 result = self.vm.qmp('nbd-server-add', device='img1') 1052 self.assert_qmp(result, 'return', {}) 1053 1054 result = self.vm.qmp('drive-mirror', job_id='mirror', device='quorum0', 1055 sync='full', node_name='repair0', replaces='img1', 1056 target=quorum_repair_img, format=iotests.imgfmt) 1057 self.assert_qmp(result, 'error/desc', 1058 "Cannot replace 'img1' by a node mirrored from " 1059 "'quorum0', because it cannot be guaranteed that doing " 1060 "so would not lead to an abrupt change of visible data") 1061 1062 def test_with_other_parents_after_mirror_start(self): 1063 """ 1064 The same as test_with_other_parent(), but add the NBD server 1065 only when the mirror job is already running. 1066 """ 1067 result = self.vm.qmp('nbd-server-start', 1068 addr={ 1069 'type': 'unix', 1070 'data': {'path': nbd_sock_path} 1071 }) 1072 self.assert_qmp(result, 'return', {}) 1073 1074 result = self.vm.qmp('drive-mirror', job_id='mirror', device='quorum0', 1075 sync='full', node_name='repair0', replaces='img1', 1076 target=quorum_repair_img, format=iotests.imgfmt) 1077 self.assert_qmp(result, 'return', {}) 1078 1079 result = self.vm.qmp('nbd-server-add', device='img1') 1080 self.assert_qmp(result, 'return', {}) 1081 1082 # The full error message goes to stderr, we will check it later 1083 self.complete_and_wait('mirror', 1084 completion_error='Operation not permitted') 1085 1086 # Should not have been replaced 1087 self.vm.assert_block_path('quorum0', '/children.1', 'img1') 1088 1089 # Check the full error message now 1090 self.vm.shutdown() 1091 log = self.vm.get_log() 1092 log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) 1093 log = re.sub(r'^Formatting.*\n', '', log) 1094 log = re.sub(r'\n\[I \+\d+\.\d+\] CLOSED\n?$', '', log) 1095 log = re.sub(r'^%s: ' % os.path.basename(iotests.qemu_prog), '', log) 1096 1097 self.assertEqual(log, 1098 "Can no longer replace 'img1' by 'repair0', because " + 1099 "it can no longer be guaranteed that doing so would " + 1100 "not lead to an abrupt change of visible data") 1101 1102 1103# Test mirroring with a source that does not have any parents (not even a 1104# BlockBackend) 1105class TestOrphanedSource(iotests.QMPTestCase): 1106 def setUp(self): 1107 blk0 = { 'node-name': 'src', 1108 'driver': 'null-co' } 1109 1110 blk1 = { 'node-name': 'dest', 1111 'driver': 'null-co' } 1112 1113 blk2 = { 'node-name': 'dest-ro', 1114 'driver': 'null-co', 1115 'read-only': 'on' } 1116 1117 self.vm = iotests.VM() 1118 self.vm.add_blockdev(self.vm.qmp_to_opts(blk0)) 1119 self.vm.add_blockdev(self.vm.qmp_to_opts(blk1)) 1120 self.vm.add_blockdev(self.vm.qmp_to_opts(blk2)) 1121 self.vm.launch() 1122 1123 def tearDown(self): 1124 self.vm.shutdown() 1125 1126 def test_no_job_id(self): 1127 self.assert_no_active_block_jobs() 1128 1129 result = self.vm.qmp('blockdev-mirror', device='src', sync='full', 1130 target='dest') 1131 self.assert_qmp(result, 'error/class', 'GenericError') 1132 1133 def test_success(self): 1134 self.assert_no_active_block_jobs() 1135 1136 result = self.vm.qmp('blockdev-mirror', job_id='job', device='src', 1137 sync='full', target='dest') 1138 self.assert_qmp(result, 'return', {}) 1139 1140 self.complete_and_wait('job') 1141 1142 def test_failing_permissions(self): 1143 self.assert_no_active_block_jobs() 1144 1145 result = self.vm.qmp('blockdev-mirror', device='src', sync='full', 1146 target='dest-ro') 1147 self.assert_qmp(result, 'error/class', 'GenericError') 1148 1149 def test_failing_permission_in_complete(self): 1150 self.assert_no_active_block_jobs() 1151 1152 # Unshare consistent-read on the target 1153 # (The mirror job does not care) 1154 result = self.vm.qmp('blockdev-add', 1155 driver='blkdebug', 1156 node_name='dest-perm', 1157 image='dest', 1158 unshare_child_perms=['consistent-read']) 1159 self.assert_qmp(result, 'return', {}) 1160 1161 result = self.vm.qmp('blockdev-mirror', job_id='job', device='src', 1162 sync='full', target='dest', 1163 filter_node_name='mirror-filter') 1164 self.assert_qmp(result, 'return', {}) 1165 1166 # Require consistent-read on the source 1167 # (We can only add this node once the job has started, or it 1168 # will complain that it does not want to run on non-root nodes) 1169 result = self.vm.qmp('blockdev-add', 1170 driver='blkdebug', 1171 node_name='src-perm', 1172 image='src', 1173 take_child_perms=['consistent-read']) 1174 self.assert_qmp(result, 'return', {}) 1175 1176 # While completing, mirror will attempt to replace src by 1177 # dest, which must fail because src-perm requires 1178 # consistent-read but dest-perm does not share it; thus 1179 # aborting the job when it is supposed to complete 1180 self.complete_and_wait('job', 1181 completion_error='Operation not permitted') 1182 1183 # Assert that all of our nodes are still there (except for the 1184 # mirror filter, which should be gone despite the failure) 1185 nodes = self.vm.qmp('query-named-block-nodes')['return'] 1186 nodes = [node['node-name'] for node in nodes] 1187 1188 for expect in ('src', 'src-perm', 'dest', 'dest-perm'): 1189 self.assertTrue(expect in nodes, '%s disappeared' % expect) 1190 self.assertFalse('mirror-filter' in nodes, 1191 'Mirror filter node did not disappear') 1192 1193# Test cases for @replaces that do not necessarily involve Quorum 1194class TestReplaces(iotests.QMPTestCase): 1195 # Each of these test cases needs their own block graph, so do not 1196 # create any nodes here 1197 def setUp(self): 1198 self.vm = iotests.VM() 1199 self.vm.launch() 1200 1201 def tearDown(self): 1202 self.vm.shutdown() 1203 for img in (test_img, target_img): 1204 try: 1205 os.remove(img) 1206 except OSError: 1207 pass 1208 1209 @iotests.skip_if_unsupported(['copy-on-read']) 1210 def test_replace_filter(self): 1211 """ 1212 Check that we can replace filter nodes. 1213 """ 1214 result = self.vm.qmp('blockdev-add', **{ 1215 'driver': 'copy-on-read', 1216 'node-name': 'filter0', 1217 'file': { 1218 'driver': 'copy-on-read', 1219 'node-name': 'filter1', 1220 'file': { 1221 'driver': 'null-co' 1222 } 1223 } 1224 }) 1225 self.assert_qmp(result, 'return', {}) 1226 1227 result = self.vm.qmp('blockdev-add', 1228 node_name='target', driver='null-co') 1229 self.assert_qmp(result, 'return', {}) 1230 1231 result = self.vm.qmp('blockdev-mirror', job_id='mirror', device='filter0', 1232 target='target', sync='full', replaces='filter1') 1233 self.assert_qmp(result, 'return', {}) 1234 1235 self.complete_and_wait('mirror') 1236 1237 self.vm.assert_block_path('filter0', '/file', 'target') 1238 1239if __name__ == '__main__': 1240 iotests.main(supported_fmts=['qcow2', 'qed'], 1241 supported_protocols=['file'], 1242 supported_platforms=['linux', 'freebsd', 'netbsd', 'openbsd']) 1243