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.sock_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, 48 '-o', 'backing_file=%s' % backing_img, '-F', 'raw', test_img) 49 self.vm = iotests.VM().add_drive(test_img, "node-name=top,backing.node-name=base") 50 if iotests.qemu_default_machine == 'pc': 51 self.vm.add_drive(None, 'media=cdrom', 'ide') 52 self.vm.launch() 53 54 def tearDown(self): 55 self.vm.shutdown() 56 os.remove(test_img) 57 os.remove(backing_img) 58 try: 59 os.remove(target_img) 60 except OSError: 61 pass 62 63 def test_complete(self): 64 self.assert_no_active_block_jobs() 65 66 result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', 67 target=self.qmp_target) 68 self.assert_qmp(result, 'return', {}) 69 70 self.complete_and_wait() 71 result = self.vm.qmp('query-block') 72 self.assert_qmp(result, 'return[0]/inserted/file', target_img) 73 self.vm.shutdown() 74 self.assertTrue(iotests.compare_images(test_img, target_img), 75 'target image does not match source after mirroring') 76 77 def test_cancel(self): 78 self.assert_no_active_block_jobs() 79 80 result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', 81 target=self.qmp_target) 82 self.assert_qmp(result, 'return', {}) 83 84 self.cancel_and_wait(force=True) 85 result = self.vm.qmp('query-block') 86 self.assert_qmp(result, 'return[0]/inserted/file', test_img) 87 88 def test_cancel_after_ready(self): 89 self.assert_no_active_block_jobs() 90 91 result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', 92 target=self.qmp_target) 93 self.assert_qmp(result, 'return', {}) 94 95 self.wait_ready_and_cancel() 96 result = self.vm.qmp('query-block') 97 self.assert_qmp(result, 'return[0]/inserted/file', test_img) 98 self.vm.shutdown() 99 self.assertTrue(iotests.compare_images(test_img, target_img), 100 'target image does not match source after mirroring') 101 102 def test_pause(self): 103 self.assert_no_active_block_jobs() 104 105 result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', 106 target=self.qmp_target) 107 self.assert_qmp(result, 'return', {}) 108 109 self.pause_job('drive0') 110 111 result = self.vm.qmp('query-block-jobs') 112 offset = self.dictpath(result, 'return[0]/offset') 113 114 time.sleep(0.5) 115 result = self.vm.qmp('query-block-jobs') 116 self.assert_qmp(result, 'return[0]/offset', offset) 117 118 result = self.vm.qmp('block-job-resume', device='drive0') 119 self.assert_qmp(result, 'return', {}) 120 121 self.complete_and_wait() 122 self.vm.shutdown() 123 self.assertTrue(iotests.compare_images(test_img, target_img), 124 'target image does not match source after mirroring') 125 126 def test_small_buffer(self): 127 self.assert_no_active_block_jobs() 128 129 # A small buffer is rounded up automatically 130 result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', 131 buf_size=4096, target=self.qmp_target) 132 self.assert_qmp(result, 'return', {}) 133 134 self.complete_and_wait() 135 result = self.vm.qmp('query-block') 136 self.assert_qmp(result, 'return[0]/inserted/file', target_img) 137 self.vm.shutdown() 138 self.assertTrue(iotests.compare_images(test_img, target_img), 139 'target image does not match source after mirroring') 140 141 def test_small_buffer2(self): 142 self.assert_no_active_block_jobs() 143 144 qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,size=%d' 145 % (self.image_len, self.image_len), target_img) 146 result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', 147 buf_size=65536, mode='existing', target=self.qmp_target) 148 self.assert_qmp(result, 'return', {}) 149 150 self.complete_and_wait() 151 result = self.vm.qmp('query-block') 152 self.assert_qmp(result, 'return[0]/inserted/file', target_img) 153 self.vm.shutdown() 154 self.assertTrue(iotests.compare_images(test_img, target_img), 155 'target image does not match source after mirroring') 156 157 def test_large_cluster(self): 158 self.assert_no_active_block_jobs() 159 160 qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,backing_file=%s' 161 % (self.image_len, backing_img), 162 '-F', 'raw', target_img) 163 result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', 164 mode='existing', target=self.qmp_target) 165 self.assert_qmp(result, 'return', {}) 166 167 self.complete_and_wait() 168 result = self.vm.qmp('query-block') 169 self.assert_qmp(result, 'return[0]/inserted/file', target_img) 170 self.vm.shutdown() 171 self.assertTrue(iotests.compare_images(test_img, target_img), 172 'target image does not match source after mirroring') 173 174 # Tests that the insertion of the mirror_top filter node doesn't make a 175 # difference to query-block 176 def test_implicit_node(self): 177 self.assert_no_active_block_jobs() 178 179 result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', 180 target=self.qmp_target) 181 self.assert_qmp(result, 'return', {}) 182 183 result = self.vm.qmp('query-block') 184 self.assert_qmp(result, 'return[0]/inserted/file', test_img) 185 self.assert_qmp(result, 'return[0]/inserted/drv', iotests.imgfmt) 186 self.assert_qmp(result, 'return[0]/inserted/backing_file', backing_img) 187 self.assert_qmp(result, 'return[0]/inserted/backing_file_depth', 1) 188 self.assert_qmp(result, 'return[0]/inserted/image/filename', test_img) 189 self.assert_qmp(result, 'return[0]/inserted/image/backing-image/filename', backing_img) 190 191 result = self.vm.qmp('query-blockstats') 192 self.assert_qmp(result, 'return[0]/node-name', 'top') 193 self.assert_qmp(result, 'return[0]/backing/node-name', 'base') 194 195 self.cancel_and_wait(force=True) 196 result = self.vm.qmp('query-block') 197 self.assert_qmp(result, 'return[0]/inserted/file', test_img) 198 self.assert_qmp(result, 'return[0]/inserted/drv', iotests.imgfmt) 199 self.assert_qmp(result, 'return[0]/inserted/backing_file', backing_img) 200 self.assert_qmp(result, 'return[0]/inserted/backing_file_depth', 1) 201 self.assert_qmp(result, 'return[0]/inserted/image/filename', test_img) 202 self.assert_qmp(result, 'return[0]/inserted/image/backing-image/filename', backing_img) 203 204 result = self.vm.qmp('query-blockstats') 205 self.assert_qmp(result, 'return[0]/node-name', 'top') 206 self.assert_qmp(result, 'return[0]/backing/node-name', 'base') 207 208 def test_medium_not_found(self): 209 if iotests.qemu_default_machine != 'pc': 210 return 211 212 result = self.vm.qmp(self.qmp_cmd, device='ide1-cd0', sync='full', 213 target=self.qmp_target) 214 self.assert_qmp(result, 'error/class', 'GenericError') 215 216 def test_image_not_found(self): 217 result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', 218 mode='existing', target=self.qmp_target) 219 self.assert_qmp(result, 'error/class', 'GenericError') 220 221 def test_device_not_found(self): 222 result = self.vm.qmp(self.qmp_cmd, device='nonexistent', sync='full', 223 target=self.qmp_target) 224 self.assert_qmp(result, 'error/class', 'GenericError') 225 226class TestSingleBlockdev(TestSingleDrive): 227 qmp_cmd = 'blockdev-mirror' 228 qmp_target = 'node1' 229 230 def setUp(self): 231 TestSingleDrive.setUp(self) 232 qemu_img('create', '-f', iotests.imgfmt, 233 '-o', 'backing_file=%s' % backing_img, '-F', 'raw', target_img) 234 args = {'driver': iotests.imgfmt, 235 'node-name': self.qmp_target, 236 'file': { 'filename': target_img, 'driver': 'file' } } 237 result = self.vm.qmp("blockdev-add", **args) 238 self.assert_qmp(result, 'return', {}) 239 240 def test_mirror_to_self(self): 241 result = self.vm.qmp(self.qmp_cmd, job_id='job0', 242 device=self.qmp_target, sync='full', 243 target=self.qmp_target) 244 self.assert_qmp(result, 'error/class', 'GenericError') 245 246 def do_test_resize(self, device, node): 247 def pre_finalize(): 248 if device: 249 result = self.vm.qmp('block_resize', device=device, size=65536) 250 self.assert_qmp(result, 'error/class', 'GenericError') 251 252 result = self.vm.qmp('block_resize', node_name=node, size=65536) 253 self.assert_qmp(result, 'error/class', 'GenericError') 254 255 result = self.vm.qmp(self.qmp_cmd, job_id='job0', device='drive0', 256 sync='full', target=self.qmp_target, 257 auto_finalize=False, auto_dismiss=False) 258 self.assert_qmp(result, 'return', {}) 259 260 result = self.vm.run_job('job0', auto_finalize=False, 261 pre_finalize=pre_finalize) 262 self.assertEqual(result, None) 263 264 def test_source_resize(self): 265 self.do_test_resize('drive0', 'top') 266 267 def test_target_resize(self): 268 self.do_test_resize(None, self.qmp_target) 269 270 def do_test_target_size(self, size): 271 result = self.vm.qmp('block_resize', node_name=self.qmp_target, 272 size=size) 273 self.assert_qmp(result, 'return', {}) 274 275 result = self.vm.qmp(self.qmp_cmd, job_id='job0', 276 device='drive0', sync='full', auto_dismiss=False, 277 target=self.qmp_target) 278 self.assert_qmp(result, 'return', {}) 279 280 result = self.vm.run_job('job0') 281 self.assertEqual(result, 'Source and target image have different sizes') 282 283 # qed does not support shrinking 284 @iotests.skip_for_formats(('qed')) 285 def test_small_target(self): 286 self.do_test_target_size(self.image_len // 2) 287 288 def test_large_target(self): 289 self.do_test_target_size(self.image_len * 2) 290 291 test_large_cluster = None 292 test_image_not_found = None 293 test_small_buffer2 = None 294 295class TestSingleDriveZeroLength(TestSingleDrive): 296 image_len = 0 297 test_small_buffer2 = None 298 test_large_cluster = None 299 300class TestSingleBlockdevZeroLength(TestSingleBlockdev): 301 image_len = 0 302 test_small_target = None 303 test_large_target = None 304 305class TestSingleDriveUnalignedLength(TestSingleDrive): 306 image_len = 1025 * 1024 307 test_small_buffer2 = None 308 test_large_cluster = None 309 310class TestSingleBlockdevUnalignedLength(TestSingleBlockdev): 311 image_len = 1025 * 1024 312 313class TestMirrorNoBacking(iotests.QMPTestCase): 314 image_len = 2 * 1024 * 1024 # MB 315 316 def setUp(self): 317 iotests.create_image(backing_img, TestMirrorNoBacking.image_len) 318 qemu_img('create', '-f', iotests.imgfmt, 319 '-o', 'backing_file=%s' % backing_img, '-F', 'raw', test_img) 320 self.vm = iotests.VM().add_drive(test_img) 321 self.vm.launch() 322 323 def tearDown(self): 324 self.vm.shutdown() 325 os.remove(test_img) 326 os.remove(backing_img) 327 try: 328 os.remove(target_backing_img) 329 except: 330 pass 331 os.remove(target_img) 332 333 def test_complete(self): 334 self.assert_no_active_block_jobs() 335 336 qemu_img('create', '-f', iotests.imgfmt, 337 '-o', 'backing_file=%s' % backing_img, '-F', 'raw', target_img) 338 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 339 mode='existing', target=target_img) 340 self.assert_qmp(result, 'return', {}) 341 342 self.complete_and_wait() 343 result = self.vm.qmp('query-block') 344 self.assert_qmp(result, 'return[0]/inserted/file', target_img) 345 self.vm.shutdown() 346 self.assertTrue(iotests.compare_images(test_img, target_img), 347 'target image does not match source after mirroring') 348 349 def test_cancel(self): 350 self.assert_no_active_block_jobs() 351 352 qemu_img('create', '-f', iotests.imgfmt, 353 '-o', 'backing_file=%s' % backing_img, '-F', 'raw', target_img) 354 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 355 mode='existing', target=target_img) 356 self.assert_qmp(result, 'return', {}) 357 358 self.wait_ready_and_cancel() 359 result = self.vm.qmp('query-block') 360 self.assert_qmp(result, 'return[0]/inserted/file', test_img) 361 self.vm.shutdown() 362 self.assertTrue(iotests.compare_images(test_img, target_img), 363 'target image does not match source after mirroring') 364 365 def test_large_cluster(self): 366 self.assert_no_active_block_jobs() 367 368 # qemu-img create fails if the image is not there 369 qemu_img('create', '-f', iotests.imgfmt, '-o', 'size=%d' 370 %(TestMirrorNoBacking.image_len), target_backing_img) 371 qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,backing_file=%s' 372 % (TestMirrorNoBacking.image_len, target_backing_img), 373 '-F', iotests.imgfmt, target_img) 374 375 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 376 mode='existing', target=target_img) 377 self.assert_qmp(result, 'return', {}) 378 379 self.complete_and_wait() 380 result = self.vm.qmp('query-block') 381 self.assert_qmp(result, 'return[0]/inserted/file', target_img) 382 self.vm.shutdown() 383 self.assertTrue(iotests.compare_images(test_img, target_img), 384 'target image does not match source after mirroring') 385 386class TestMirrorResized(iotests.QMPTestCase): 387 backing_len = 1 * 1024 * 1024 # MB 388 image_len = 2 * 1024 * 1024 # MB 389 390 def setUp(self): 391 iotests.create_image(backing_img, TestMirrorResized.backing_len) 392 qemu_img('create', '-f', iotests.imgfmt, 393 '-o', 'backing_file=%s' % backing_img, '-F', 'raw', test_img) 394 qemu_img('resize', test_img, '2M') 395 self.vm = iotests.VM().add_drive(test_img) 396 self.vm.launch() 397 398 def tearDown(self): 399 self.vm.shutdown() 400 os.remove(test_img) 401 os.remove(backing_img) 402 try: 403 os.remove(target_img) 404 except OSError: 405 pass 406 407 def test_complete_top(self): 408 self.assert_no_active_block_jobs() 409 410 result = self.vm.qmp('drive-mirror', device='drive0', sync='top', 411 target=target_img) 412 self.assert_qmp(result, 'return', {}) 413 414 self.complete_and_wait() 415 result = self.vm.qmp('query-block') 416 self.assert_qmp(result, 'return[0]/inserted/file', target_img) 417 self.vm.shutdown() 418 self.assertTrue(iotests.compare_images(test_img, target_img), 419 'target image does not match source after mirroring') 420 421 def test_complete_full(self): 422 self.assert_no_active_block_jobs() 423 424 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 425 target=target_img) 426 self.assert_qmp(result, 'return', {}) 427 428 self.complete_and_wait() 429 result = self.vm.qmp('query-block') 430 self.assert_qmp(result, 'return[0]/inserted/file', target_img) 431 self.vm.shutdown() 432 self.assertTrue(iotests.compare_images(test_img, target_img), 433 'target image does not match source after mirroring') 434 435class TestReadErrors(iotests.QMPTestCase): 436 image_len = 2 * 1024 * 1024 # MB 437 438 # this should be a multiple of twice the default granularity 439 # so that we hit this offset first in state 1 440 MIRROR_GRANULARITY = 1024 * 1024 441 442 def create_blkdebug_file(self, name, event, errno): 443 file = open(name, 'w') 444 file.write(''' 445[inject-error] 446state = "1" 447event = "%s" 448errno = "%d" 449immediately = "off" 450once = "on" 451sector = "%d" 452 453[set-state] 454state = "1" 455event = "%s" 456new_state = "2" 457 458[set-state] 459state = "2" 460event = "%s" 461new_state = "1" 462''' % (event, errno, self.MIRROR_GRANULARITY // 512, event, event)) 463 file.close() 464 465 def setUp(self): 466 self.blkdebug_file = backing_img + ".blkdebug" 467 iotests.create_image(backing_img, TestReadErrors.image_len) 468 self.create_blkdebug_file(self.blkdebug_file, "read_aio", 5) 469 qemu_img('create', '-f', iotests.imgfmt, 470 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw' 471 % (self.blkdebug_file, backing_img), 472 test_img) 473 # Write something for tests that use sync='top' 474 qemu_io('-c', 'write %d 512' % (self.MIRROR_GRANULARITY + 65536), 475 test_img) 476 self.vm = iotests.VM().add_drive(test_img) 477 self.vm.launch() 478 479 def tearDown(self): 480 self.vm.shutdown() 481 os.remove(test_img) 482 os.remove(target_img) 483 os.remove(backing_img) 484 os.remove(self.blkdebug_file) 485 486 def test_report_read(self): 487 self.assert_no_active_block_jobs() 488 489 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 490 target=target_img) 491 self.assert_qmp(result, 'return', {}) 492 493 completed = False 494 error = False 495 while not completed: 496 for event in self.vm.get_qmp_events(wait=True): 497 if event['event'] == 'BLOCK_JOB_ERROR': 498 self.assert_qmp(event, 'data/device', 'drive0') 499 self.assert_qmp(event, 'data/operation', 'read') 500 error = True 501 elif event['event'] == 'BLOCK_JOB_READY': 502 self.assertTrue(False, 'job completed unexpectedly') 503 elif event['event'] == 'BLOCK_JOB_COMPLETED': 504 self.assertTrue(error, 'job completed unexpectedly') 505 self.assert_qmp(event, 'data/type', 'mirror') 506 self.assert_qmp(event, 'data/device', 'drive0') 507 self.assert_qmp(event, 'data/error', 'Input/output error') 508 completed = True 509 elif event['event'] == 'JOB_STATUS_CHANGE': 510 self.assert_qmp(event, 'data/id', 'drive0') 511 512 self.assert_no_active_block_jobs() 513 514 def test_ignore_read(self): 515 self.assert_no_active_block_jobs() 516 517 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 518 target=target_img, on_source_error='ignore') 519 self.assert_qmp(result, 'return', {}) 520 521 event = self.vm.get_qmp_event(wait=True) 522 while event['event'] == 'JOB_STATUS_CHANGE': 523 self.assert_qmp(event, 'data/id', 'drive0') 524 event = self.vm.get_qmp_event(wait=True) 525 526 self.assertEqual(event['event'], 'BLOCK_JOB_ERROR') 527 self.assert_qmp(event, 'data/device', 'drive0') 528 self.assert_qmp(event, 'data/operation', 'read') 529 result = self.vm.qmp('query-block-jobs') 530 self.assert_qmp(result, 'return[0]/paused', False) 531 self.complete_and_wait() 532 533 def test_large_cluster(self): 534 self.assert_no_active_block_jobs() 535 536 # Test COW into the target image. The first half of the 537 # cluster at MIRROR_GRANULARITY has to be copied from 538 # backing_img, even though sync='top'. 539 qemu_img('create', '-f', iotests.imgfmt, 540 '-ocluster_size=131072,backing_file=%s' %(backing_img), 541 '-F', 'raw', target_img) 542 result = self.vm.qmp('drive-mirror', device='drive0', sync='top', 543 on_source_error='ignore', 544 mode='existing', target=target_img) 545 self.assert_qmp(result, 'return', {}) 546 547 event = self.vm.get_qmp_event(wait=True) 548 while event['event'] == 'JOB_STATUS_CHANGE': 549 self.assert_qmp(event, 'data/id', 'drive0') 550 event = self.vm.get_qmp_event(wait=True) 551 552 self.assertEqual(event['event'], 'BLOCK_JOB_ERROR') 553 self.assert_qmp(event, 'data/device', 'drive0') 554 self.assert_qmp(event, 'data/operation', 'read') 555 result = self.vm.qmp('query-block-jobs') 556 self.assert_qmp(result, 'return[0]/paused', False) 557 self.complete_and_wait() 558 self.vm.shutdown() 559 560 # Detach blkdebug to compare images successfully 561 qemu_img('rebase', '-f', iotests.imgfmt, '-u', '-b', backing_img, 562 '-F', 'raw', test_img) 563 self.assertTrue(iotests.compare_images(test_img, target_img), 564 'target image does not match source after mirroring') 565 566 def test_stop_read(self): 567 self.assert_no_active_block_jobs() 568 569 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 570 target=target_img, on_source_error='stop') 571 self.assert_qmp(result, 'return', {}) 572 573 error = False 574 ready = False 575 while not ready: 576 for event in self.vm.get_qmp_events(wait=True): 577 if event['event'] == 'BLOCK_JOB_ERROR': 578 self.assert_qmp(event, 'data/device', 'drive0') 579 self.assert_qmp(event, 'data/operation', 'read') 580 581 result = self.vm.qmp('query-block-jobs') 582 self.assert_qmp(result, 'return[0]/paused', True) 583 self.assert_qmp(result, 'return[0]/io-status', 'failed') 584 585 result = self.vm.qmp('block-job-resume', device='drive0') 586 self.assert_qmp(result, 'return', {}) 587 error = True 588 elif event['event'] == 'BLOCK_JOB_READY': 589 self.assertTrue(error, 'job completed unexpectedly') 590 self.assert_qmp(event, 'data/device', 'drive0') 591 ready = True 592 593 result = self.vm.qmp('query-block-jobs') 594 self.assert_qmp(result, 'return[0]/paused', False) 595 self.assert_qmp(result, 'return[0]/io-status', 'ok') 596 597 self.complete_and_wait(wait_ready=False) 598 self.assert_no_active_block_jobs() 599 600class TestWriteErrors(iotests.QMPTestCase): 601 image_len = 2 * 1024 * 1024 # MB 602 603 # this should be a multiple of twice the default granularity 604 # so that we hit this offset first in state 1 605 MIRROR_GRANULARITY = 1024 * 1024 606 607 def create_blkdebug_file(self, name, event, errno): 608 file = open(name, 'w') 609 file.write(''' 610[inject-error] 611state = "1" 612event = "%s" 613errno = "%d" 614immediately = "off" 615once = "on" 616sector = "%d" 617 618[set-state] 619state = "1" 620event = "%s" 621new_state = "2" 622 623[set-state] 624state = "2" 625event = "%s" 626new_state = "1" 627''' % (event, errno, self.MIRROR_GRANULARITY // 512, event, event)) 628 file.close() 629 630 def setUp(self): 631 self.blkdebug_file = target_img + ".blkdebug" 632 iotests.create_image(backing_img, TestWriteErrors.image_len) 633 self.create_blkdebug_file(self.blkdebug_file, "write_aio", 5) 634 qemu_img('create', '-f', iotests.imgfmt, 635 '-obacking_file=%s' %(backing_img), '-F', 'raw', test_img) 636 self.vm = iotests.VM().add_drive(test_img) 637 self.target_img = 'blkdebug:%s:%s' % (self.blkdebug_file, target_img) 638 qemu_img('create', '-f', iotests.imgfmt, '-osize=%d' %(TestWriteErrors.image_len), target_img) 639 self.vm.launch() 640 641 def tearDown(self): 642 self.vm.shutdown() 643 os.remove(test_img) 644 os.remove(target_img) 645 os.remove(backing_img) 646 os.remove(self.blkdebug_file) 647 648 def test_report_write(self): 649 self.assert_no_active_block_jobs() 650 651 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 652 mode='existing', target=self.target_img) 653 self.assert_qmp(result, 'return', {}) 654 655 completed = False 656 error = False 657 while not completed: 658 for event in self.vm.get_qmp_events(wait=True): 659 if event['event'] == 'BLOCK_JOB_ERROR': 660 self.assert_qmp(event, 'data/device', 'drive0') 661 self.assert_qmp(event, 'data/operation', 'write') 662 error = True 663 elif event['event'] == 'BLOCK_JOB_READY': 664 self.assertTrue(False, 'job completed unexpectedly') 665 elif event['event'] == 'BLOCK_JOB_COMPLETED': 666 self.assertTrue(error, 'job completed unexpectedly') 667 self.assert_qmp(event, 'data/type', 'mirror') 668 self.assert_qmp(event, 'data/device', 'drive0') 669 self.assert_qmp(event, 'data/error', 'Input/output error') 670 completed = True 671 672 self.assert_no_active_block_jobs() 673 674 def test_ignore_write(self): 675 self.assert_no_active_block_jobs() 676 677 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 678 mode='existing', target=self.target_img, 679 on_target_error='ignore') 680 self.assert_qmp(result, 'return', {}) 681 682 event = self.vm.event_wait(name='BLOCK_JOB_ERROR') 683 self.assertEqual(event['event'], 'BLOCK_JOB_ERROR') 684 self.assert_qmp(event, 'data/device', 'drive0') 685 self.assert_qmp(event, 'data/operation', 'write') 686 result = self.vm.qmp('query-block-jobs') 687 self.assert_qmp(result, 'return[0]/paused', False) 688 self.complete_and_wait() 689 690 def test_stop_write(self): 691 self.assert_no_active_block_jobs() 692 693 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 694 mode='existing', target=self.target_img, 695 on_target_error='stop') 696 self.assert_qmp(result, 'return', {}) 697 698 error = False 699 ready = False 700 while not ready: 701 for event in self.vm.get_qmp_events(wait=True): 702 if event['event'] == 'BLOCK_JOB_ERROR': 703 self.assert_qmp(event, 'data/device', 'drive0') 704 self.assert_qmp(event, 'data/operation', 'write') 705 706 result = self.vm.qmp('query-block-jobs') 707 self.assert_qmp(result, 'return[0]/paused', True) 708 self.assert_qmp(result, 'return[0]/io-status', 'failed') 709 710 result = self.vm.qmp('block-job-resume', device='drive0') 711 self.assert_qmp(result, 'return', {}) 712 713 result = self.vm.qmp('query-block-jobs') 714 self.assert_qmp(result, 'return[0]/paused', False) 715 self.assert_qmp(result, 'return[0]/io-status', 'ok') 716 error = True 717 elif event['event'] == 'BLOCK_JOB_READY': 718 self.assertTrue(error, 'job completed unexpectedly') 719 self.assert_qmp(event, 'data/device', 'drive0') 720 ready = True 721 722 self.complete_and_wait(wait_ready=False) 723 self.assert_no_active_block_jobs() 724 725class TestSetSpeed(iotests.QMPTestCase): 726 image_len = 80 * 1024 * 1024 # MB 727 728 def setUp(self): 729 qemu_img('create', backing_img, str(TestSetSpeed.image_len)) 730 qemu_img('create', '-f', iotests.imgfmt, 731 '-o', 'backing_file=%s' % backing_img, '-F', 'raw', test_img) 732 self.vm = iotests.VM().add_drive(test_img) 733 self.vm.launch() 734 735 def tearDown(self): 736 self.vm.shutdown() 737 os.remove(test_img) 738 os.remove(backing_img) 739 os.remove(target_img) 740 741 def test_set_speed(self): 742 self.assert_no_active_block_jobs() 743 744 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 745 target=target_img) 746 self.assert_qmp(result, 'return', {}) 747 748 # Default speed is 0 749 result = self.vm.qmp('query-block-jobs') 750 self.assert_qmp(result, 'return[0]/device', 'drive0') 751 self.assert_qmp(result, 'return[0]/speed', 0) 752 753 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) 754 self.assert_qmp(result, 'return', {}) 755 756 # Ensure the speed we set was accepted 757 result = self.vm.qmp('query-block-jobs') 758 self.assert_qmp(result, 'return[0]/device', 'drive0') 759 self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024) 760 761 self.wait_ready_and_cancel() 762 763 # Check setting speed in drive-mirror works 764 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 765 target=target_img, speed=4*1024*1024) 766 self.assert_qmp(result, 'return', {}) 767 768 result = self.vm.qmp('query-block-jobs') 769 self.assert_qmp(result, 'return[0]/device', 'drive0') 770 self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024) 771 772 self.wait_ready_and_cancel() 773 774 def test_set_speed_invalid(self): 775 self.assert_no_active_block_jobs() 776 777 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 778 target=target_img, speed=-1) 779 self.assert_qmp(result, 'error/class', 'GenericError') 780 781 self.assert_no_active_block_jobs() 782 783 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 784 target=target_img) 785 self.assert_qmp(result, 'return', {}) 786 787 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1) 788 self.assert_qmp(result, 'error/class', 'GenericError') 789 790 self.wait_ready_and_cancel() 791 792class TestUnbackedSource(iotests.QMPTestCase): 793 image_len = 2 * 1024 * 1024 # MB 794 795 def setUp(self): 796 qemu_img('create', '-f', iotests.imgfmt, test_img, 797 str(TestUnbackedSource.image_len)) 798 self.vm = iotests.VM() 799 self.vm.launch() 800 result = self.vm.qmp('blockdev-add', node_name='drive0', 801 driver=iotests.imgfmt, 802 file={ 803 'driver': 'file', 804 'filename': test_img, 805 }) 806 self.assert_qmp(result, 'return', {}) 807 808 def tearDown(self): 809 self.vm.shutdown() 810 os.remove(test_img) 811 os.remove(target_img) 812 813 def test_absolute_paths_full(self): 814 self.assert_no_active_block_jobs() 815 result = self.vm.qmp('drive-mirror', job_id='drive0', device='drive0', 816 sync='full', target=target_img, 817 mode='absolute-paths') 818 self.assert_qmp(result, 'return', {}) 819 self.complete_and_wait() 820 self.assert_no_active_block_jobs() 821 822 def test_absolute_paths_top(self): 823 self.assert_no_active_block_jobs() 824 result = self.vm.qmp('drive-mirror', job_id='drive0', device='drive0', 825 sync='top', target=target_img, 826 mode='absolute-paths') 827 self.assert_qmp(result, 'return', {}) 828 self.complete_and_wait() 829 self.assert_no_active_block_jobs() 830 831 def test_absolute_paths_none(self): 832 self.assert_no_active_block_jobs() 833 result = self.vm.qmp('drive-mirror', job_id='drive0', device='drive0', 834 sync='none', target=target_img, 835 mode='absolute-paths') 836 self.assert_qmp(result, 'return', {}) 837 self.complete_and_wait() 838 self.assert_no_active_block_jobs() 839 840 def test_existing_full(self): 841 qemu_img('create', '-f', iotests.imgfmt, target_img, 842 str(self.image_len)) 843 qemu_io('-c', 'write -P 42 0 64k', target_img) 844 845 self.assert_no_active_block_jobs() 846 result = self.vm.qmp('drive-mirror', job_id='drive0', device='drive0', 847 sync='full', target=target_img, mode='existing') 848 self.assert_qmp(result, 'return', {}) 849 self.complete_and_wait() 850 self.assert_no_active_block_jobs() 851 852 result = self.vm.qmp('blockdev-del', node_name='drive0') 853 self.assert_qmp(result, 'return', {}) 854 855 self.assertTrue(iotests.compare_images(test_img, target_img), 856 'target image does not match source after mirroring') 857 858 def test_blockdev_full(self): 859 qemu_img('create', '-f', iotests.imgfmt, target_img, 860 str(self.image_len)) 861 qemu_io('-c', 'write -P 42 0 64k', target_img) 862 863 result = self.vm.qmp('blockdev-add', node_name='target', 864 driver=iotests.imgfmt, 865 file={ 866 'driver': 'file', 867 'filename': target_img, 868 }) 869 self.assert_qmp(result, 'return', {}) 870 871 self.assert_no_active_block_jobs() 872 result = self.vm.qmp('blockdev-mirror', job_id='drive0', device='drive0', 873 sync='full', target='target') 874 self.assert_qmp(result, 'return', {}) 875 self.complete_and_wait() 876 self.assert_no_active_block_jobs() 877 878 result = self.vm.qmp('blockdev-del', node_name='drive0') 879 self.assert_qmp(result, 'return', {}) 880 881 result = self.vm.qmp('blockdev-del', node_name='target') 882 self.assert_qmp(result, 'return', {}) 883 884 self.assertTrue(iotests.compare_images(test_img, target_img), 885 'target image does not match source after mirroring') 886 887class TestGranularity(iotests.QMPTestCase): 888 image_len = 10 * 1024 * 1024 # MB 889 890 def setUp(self): 891 qemu_img('create', '-f', iotests.imgfmt, test_img, 892 str(TestGranularity.image_len)) 893 qemu_io('-c', 'write 0 %d' % (self.image_len), 894 test_img) 895 self.vm = iotests.VM().add_drive(test_img) 896 self.vm.launch() 897 898 def tearDown(self): 899 self.vm.shutdown() 900 self.assertTrue(iotests.compare_images(test_img, target_img), 901 'target image does not match source after mirroring') 902 os.remove(test_img) 903 os.remove(target_img) 904 905 def test_granularity(self): 906 self.assert_no_active_block_jobs() 907 result = self.vm.qmp('drive-mirror', device='drive0', 908 sync='full', target=target_img, 909 mode='absolute-paths', granularity=8192) 910 self.assert_qmp(result, 'return', {}) 911 912 event = self.vm.get_qmp_event(wait=60.0) 913 while event['event'] == 'JOB_STATUS_CHANGE': 914 self.assert_qmp(event, 'data/id', 'drive0') 915 event = self.vm.get_qmp_event(wait=60.0) 916 917 # Failures will manifest as COMPLETED/ERROR. 918 self.assert_qmp(event, 'event', 'BLOCK_JOB_READY') 919 self.complete_and_wait(drive='drive0', wait_ready=False) 920 self.assert_no_active_block_jobs() 921 922class TestRepairQuorum(iotests.QMPTestCase): 923 """ This class test quorum file repair using drive-mirror. 924 It's mostly a fork of TestSingleDrive """ 925 image_len = 1 * 1024 * 1024 # MB 926 IMAGES = [ quorum_img1, quorum_img2, quorum_img3 ] 927 928 @iotests.skip_if_unsupported(['quorum']) 929 def setUp(self): 930 self.vm = iotests.VM() 931 932 if iotests.qemu_default_machine == 'pc': 933 self.vm.add_drive(None, 'media=cdrom', 'ide') 934 935 # Add each individual quorum images 936 for i in self.IMAGES: 937 qemu_img('create', '-f', iotests.imgfmt, i, 938 str(self.image_len)) 939 # Assign a node name to each quorum image in order to manipulate 940 # them 941 opts = "node-name=img%i" % self.IMAGES.index(i) 942 opts += ',driver=%s' % iotests.imgfmt 943 opts += ',file.driver=file' 944 opts += ',file.filename=%s' % i 945 self.vm = self.vm.add_blockdev(opts) 946 947 self.vm.launch() 948 949 #assemble the quorum block device from the individual files 950 args = { "driver": "quorum", "node-name": "quorum0", 951 "vote-threshold": 2, "children": [ "img0", "img1", "img2" ] } 952 result = self.vm.qmp("blockdev-add", **args) 953 self.assert_qmp(result, 'return', {}) 954 955 956 def tearDown(self): 957 self.vm.shutdown() 958 for i in self.IMAGES + [ quorum_repair_img, quorum_snapshot_file, 959 nbd_sock_path ]: 960 # Do a try/except because the test may have deleted some images 961 try: 962 os.remove(i) 963 except OSError: 964 pass 965 966 def test_complete(self): 967 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 968 sync='full', node_name="repair0", replaces="img1", 969 target=quorum_repair_img, format=iotests.imgfmt) 970 self.assert_qmp(result, 'return', {}) 971 972 self.complete_and_wait(drive="job0") 973 self.assert_has_block_node("repair0", quorum_repair_img) 974 self.vm.assert_block_path('quorum0', '/children.1', 'repair0') 975 self.vm.shutdown() 976 self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img), 977 'target image does not match source after mirroring') 978 979 def test_cancel(self): 980 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 981 sync='full', node_name="repair0", replaces="img1", 982 target=quorum_repair_img, format=iotests.imgfmt) 983 self.assert_qmp(result, 'return', {}) 984 985 self.cancel_and_wait(drive="job0", force=True) 986 # here we check that the last registered quorum file has not been 987 # swapped out and unref 988 self.assert_has_block_node(None, quorum_img3) 989 990 def test_cancel_after_ready(self): 991 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 992 sync='full', node_name="repair0", replaces="img1", 993 target=quorum_repair_img, format=iotests.imgfmt) 994 self.assert_qmp(result, 'return', {}) 995 996 self.wait_ready_and_cancel(drive="job0") 997 # here we check that the last registered quorum file has not been 998 # swapped out and unref 999 self.assert_has_block_node(None, quorum_img3) 1000 self.vm.shutdown() 1001 self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img), 1002 'target image does not match source after mirroring') 1003 1004 def test_pause(self): 1005 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 1006 sync='full', node_name="repair0", replaces="img1", 1007 target=quorum_repair_img, format=iotests.imgfmt) 1008 self.assert_qmp(result, 'return', {}) 1009 1010 self.pause_job('job0') 1011 1012 result = self.vm.qmp('query-block-jobs') 1013 offset = self.dictpath(result, 'return[0]/offset') 1014 1015 time.sleep(0.5) 1016 result = self.vm.qmp('query-block-jobs') 1017 self.assert_qmp(result, 'return[0]/offset', offset) 1018 1019 result = self.vm.qmp('block-job-resume', device='job0') 1020 self.assert_qmp(result, 'return', {}) 1021 1022 self.complete_and_wait(drive="job0") 1023 self.vm.shutdown() 1024 self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img), 1025 'target image does not match source after mirroring') 1026 1027 def test_medium_not_found(self): 1028 if iotests.qemu_default_machine != 'pc': 1029 return 1030 1031 result = self.vm.qmp('drive-mirror', job_id='job0', device='drive0', # CD-ROM 1032 sync='full', 1033 node_name='repair0', 1034 replaces='img1', 1035 target=quorum_repair_img, format=iotests.imgfmt) 1036 self.assert_qmp(result, 'error/class', 'GenericError') 1037 1038 def test_image_not_found(self): 1039 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 1040 sync='full', node_name='repair0', replaces='img1', 1041 mode='existing', target=quorum_repair_img, 1042 format=iotests.imgfmt) 1043 self.assert_qmp(result, 'error/class', 'GenericError') 1044 1045 def test_device_not_found(self): 1046 result = self.vm.qmp('drive-mirror', job_id='job0', 1047 device='nonexistent', sync='full', 1048 node_name='repair0', 1049 replaces='img1', 1050 target=quorum_repair_img, format=iotests.imgfmt) 1051 self.assert_qmp(result, 'error/class', 'GenericError') 1052 1053 def test_wrong_sync_mode(self): 1054 result = self.vm.qmp('drive-mirror', device='quorum0', job_id='job0', 1055 node_name='repair0', 1056 replaces='img1', 1057 target=quorum_repair_img, format=iotests.imgfmt) 1058 self.assert_qmp(result, 'error/class', 'GenericError') 1059 1060 def test_no_node_name(self): 1061 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 1062 sync='full', replaces='img1', 1063 target=quorum_repair_img, format=iotests.imgfmt) 1064 self.assert_qmp(result, 'error/class', 'GenericError') 1065 1066 def test_nonexistent_replaces(self): 1067 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 1068 sync='full', node_name='repair0', replaces='img77', 1069 target=quorum_repair_img, format=iotests.imgfmt) 1070 self.assert_qmp(result, 'error/class', 'GenericError') 1071 1072 def test_after_a_quorum_snapshot(self): 1073 result = self.vm.qmp('blockdev-snapshot-sync', node_name='img1', 1074 snapshot_file=quorum_snapshot_file, 1075 snapshot_node_name="snap1"); 1076 1077 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 1078 sync='full', node_name='repair0', replaces="img1", 1079 target=quorum_repair_img, format=iotests.imgfmt) 1080 self.assert_qmp(result, 'error/class', 'GenericError') 1081 1082 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 1083 sync='full', node_name='repair0', replaces="snap1", 1084 target=quorum_repair_img, format=iotests.imgfmt) 1085 self.assert_qmp(result, 'return', {}) 1086 1087 self.complete_and_wait('job0') 1088 self.assert_has_block_node("repair0", quorum_repair_img) 1089 self.vm.assert_block_path('quorum0', '/children.1', 'repair0') 1090 1091 def test_with_other_parent(self): 1092 """ 1093 Check that we cannot replace a Quorum child when it has other 1094 parents. 1095 """ 1096 result = self.vm.qmp('nbd-server-start', 1097 addr={ 1098 'type': 'unix', 1099 'data': {'path': nbd_sock_path} 1100 }) 1101 self.assert_qmp(result, 'return', {}) 1102 1103 result = self.vm.qmp('nbd-server-add', device='img1') 1104 self.assert_qmp(result, 'return', {}) 1105 1106 result = self.vm.qmp('drive-mirror', job_id='mirror', device='quorum0', 1107 sync='full', node_name='repair0', replaces='img1', 1108 target=quorum_repair_img, format=iotests.imgfmt) 1109 self.assert_qmp(result, 'error/desc', 1110 "Cannot replace 'img1' by a node mirrored from " 1111 "'quorum0', because it cannot be guaranteed that doing " 1112 "so would not lead to an abrupt change of visible data") 1113 1114 def test_with_other_parents_after_mirror_start(self): 1115 """ 1116 The same as test_with_other_parent(), but add the NBD server 1117 only when the mirror job is already running. 1118 """ 1119 result = self.vm.qmp('nbd-server-start', 1120 addr={ 1121 'type': 'unix', 1122 'data': {'path': nbd_sock_path} 1123 }) 1124 self.assert_qmp(result, 'return', {}) 1125 1126 result = self.vm.qmp('drive-mirror', job_id='mirror', device='quorum0', 1127 sync='full', node_name='repair0', replaces='img1', 1128 target=quorum_repair_img, format=iotests.imgfmt) 1129 self.assert_qmp(result, 'return', {}) 1130 1131 result = self.vm.qmp('nbd-server-add', device='img1') 1132 self.assert_qmp(result, 'return', {}) 1133 1134 # The full error message goes to stderr, we will check it later 1135 self.complete_and_wait('mirror', 1136 completion_error='Operation not permitted') 1137 1138 # Should not have been replaced 1139 self.vm.assert_block_path('quorum0', '/children.1', 'img1') 1140 1141 # Check the full error message now 1142 self.vm.shutdown() 1143 log = self.vm.get_log() 1144 log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) 1145 log = re.sub(r'^Formatting.*\n', '', log) 1146 log = re.sub(r'\n\[I \+\d+\.\d+\] CLOSED\n?$', '', log) 1147 log = re.sub(r'^%s: ' % os.path.basename(iotests.qemu_prog), '', log) 1148 1149 self.assertEqual(log, 1150 "Can no longer replace 'img1' by 'repair0', because " + 1151 "it can no longer be guaranteed that doing so would " + 1152 "not lead to an abrupt change of visible data") 1153 1154 1155# Test mirroring with a source that does not have any parents (not even a 1156# BlockBackend) 1157class TestOrphanedSource(iotests.QMPTestCase): 1158 def setUp(self): 1159 blk0 = { 'node-name': 'src', 1160 'driver': 'null-co' } 1161 1162 blk1 = { 'node-name': 'dest', 1163 'driver': 'null-co' } 1164 1165 blk2 = { 'node-name': 'dest-ro', 1166 'driver': 'null-co', 1167 'read-only': 'on' } 1168 1169 self.vm = iotests.VM() 1170 self.vm.add_blockdev(self.vm.qmp_to_opts(blk0)) 1171 self.vm.add_blockdev(self.vm.qmp_to_opts(blk1)) 1172 self.vm.add_blockdev(self.vm.qmp_to_opts(blk2)) 1173 self.vm.launch() 1174 1175 def tearDown(self): 1176 self.vm.shutdown() 1177 1178 def test_no_job_id(self): 1179 self.assert_no_active_block_jobs() 1180 1181 result = self.vm.qmp('blockdev-mirror', device='src', sync='full', 1182 target='dest') 1183 self.assert_qmp(result, 'error/class', 'GenericError') 1184 1185 def test_success(self): 1186 self.assert_no_active_block_jobs() 1187 1188 result = self.vm.qmp('blockdev-mirror', job_id='job', device='src', 1189 sync='full', target='dest') 1190 self.assert_qmp(result, 'return', {}) 1191 1192 self.complete_and_wait('job') 1193 1194 def test_failing_permissions(self): 1195 self.assert_no_active_block_jobs() 1196 1197 result = self.vm.qmp('blockdev-mirror', device='src', sync='full', 1198 target='dest-ro') 1199 self.assert_qmp(result, 'error/class', 'GenericError') 1200 1201 def test_failing_permission_in_complete(self): 1202 self.assert_no_active_block_jobs() 1203 1204 # Unshare consistent-read on the target 1205 # (The mirror job does not care) 1206 result = self.vm.qmp('blockdev-add', 1207 driver='blkdebug', 1208 node_name='dest-perm', 1209 image='dest', 1210 unshare_child_perms=['consistent-read']) 1211 self.assert_qmp(result, 'return', {}) 1212 1213 result = self.vm.qmp('blockdev-mirror', job_id='job', device='src', 1214 sync='full', target='dest', 1215 filter_node_name='mirror-filter') 1216 self.assert_qmp(result, 'return', {}) 1217 1218 # Require consistent-read on the source 1219 # (We can only add this node once the job has started, or it 1220 # will complain that it does not want to run on non-root nodes) 1221 result = self.vm.qmp('blockdev-add', 1222 driver='blkdebug', 1223 node_name='src-perm', 1224 image='src', 1225 take_child_perms=['consistent-read']) 1226 self.assert_qmp(result, 'return', {}) 1227 1228 # While completing, mirror will attempt to replace src by 1229 # dest, which must fail because src-perm requires 1230 # consistent-read but dest-perm does not share it; thus 1231 # aborting the job when it is supposed to complete 1232 self.complete_and_wait('job', 1233 completion_error='Operation not permitted') 1234 1235 # Assert that all of our nodes are still there (except for the 1236 # mirror filter, which should be gone despite the failure) 1237 nodes = self.vm.qmp('query-named-block-nodes')['return'] 1238 nodes = [node['node-name'] for node in nodes] 1239 1240 for expect in ('src', 'src-perm', 'dest', 'dest-perm'): 1241 self.assertTrue(expect in nodes, '%s disappeared' % expect) 1242 self.assertFalse('mirror-filter' in nodes, 1243 'Mirror filter node did not disappear') 1244 1245# Test cases for @replaces that do not necessarily involve Quorum 1246class TestReplaces(iotests.QMPTestCase): 1247 # Each of these test cases needs their own block graph, so do not 1248 # create any nodes here 1249 def setUp(self): 1250 self.vm = iotests.VM() 1251 self.vm.launch() 1252 1253 def tearDown(self): 1254 self.vm.shutdown() 1255 for img in (test_img, target_img): 1256 try: 1257 os.remove(img) 1258 except OSError: 1259 pass 1260 1261 @iotests.skip_if_unsupported(['copy-on-read']) 1262 def test_replace_filter(self): 1263 """ 1264 Check that we can replace filter nodes. 1265 """ 1266 result = self.vm.qmp('blockdev-add', **{ 1267 'driver': 'copy-on-read', 1268 'node-name': 'filter0', 1269 'file': { 1270 'driver': 'copy-on-read', 1271 'node-name': 'filter1', 1272 'file': { 1273 'driver': 'null-co' 1274 } 1275 } 1276 }) 1277 self.assert_qmp(result, 'return', {}) 1278 1279 result = self.vm.qmp('blockdev-add', 1280 node_name='target', driver='null-co') 1281 self.assert_qmp(result, 'return', {}) 1282 1283 result = self.vm.qmp('blockdev-mirror', job_id='mirror', device='filter0', 1284 target='target', sync='full', replaces='filter1') 1285 self.assert_qmp(result, 'return', {}) 1286 1287 self.complete_and_wait('mirror') 1288 1289 self.vm.assert_block_path('filter0', '/file', 'target') 1290 1291if __name__ == '__main__': 1292 iotests.main(supported_fmts=['qcow2', 'qed'], 1293 supported_protocols=['file'], 1294 supported_platforms=['linux', 'freebsd', 'netbsd', 'openbsd']) 1295