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 iotests 24from iotests import qemu_img, qemu_io 25 26backing_img = os.path.join(iotests.test_dir, 'backing.img') 27target_backing_img = os.path.join(iotests.test_dir, 'target-backing.img') 28test_img = os.path.join(iotests.test_dir, 'test.img') 29target_img = os.path.join(iotests.test_dir, 'target.img') 30 31quorum_img1 = os.path.join(iotests.test_dir, 'quorum1.img') 32quorum_img2 = os.path.join(iotests.test_dir, 'quorum2.img') 33quorum_img3 = os.path.join(iotests.test_dir, 'quorum3.img') 34quorum_repair_img = os.path.join(iotests.test_dir, 'quorum_repair.img') 35quorum_snapshot_file = os.path.join(iotests.test_dir, 'quorum_snapshot.img') 36 37class TestSingleDrive(iotests.QMPTestCase): 38 image_len = 1 * 1024 * 1024 # MB 39 qmp_cmd = 'drive-mirror' 40 qmp_target = target_img 41 42 def setUp(self): 43 iotests.create_image(backing_img, self.image_len) 44 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img) 45 self.vm = iotests.VM().add_drive(test_img, "node-name=top,backing.node-name=base") 46 if iotests.qemu_default_machine == 'pc': 47 self.vm.add_drive(None, 'media=cdrom', 'ide') 48 self.vm.launch() 49 50 def tearDown(self): 51 self.vm.shutdown() 52 os.remove(test_img) 53 os.remove(backing_img) 54 try: 55 os.remove(target_img) 56 except OSError: 57 pass 58 59 def test_complete(self): 60 self.assert_no_active_block_jobs() 61 62 result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', 63 target=self.qmp_target) 64 self.assert_qmp(result, 'return', {}) 65 66 self.complete_and_wait() 67 result = self.vm.qmp('query-block') 68 self.assert_qmp(result, 'return[0]/inserted/file', target_img) 69 self.vm.shutdown() 70 self.assertTrue(iotests.compare_images(test_img, target_img), 71 'target image does not match source after mirroring') 72 73 def test_cancel(self): 74 self.assert_no_active_block_jobs() 75 76 result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', 77 target=self.qmp_target) 78 self.assert_qmp(result, 'return', {}) 79 80 self.cancel_and_wait(force=True) 81 result = self.vm.qmp('query-block') 82 self.assert_qmp(result, 'return[0]/inserted/file', test_img) 83 self.vm.shutdown() 84 85 def test_cancel_after_ready(self): 86 self.assert_no_active_block_jobs() 87 88 result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', 89 target=self.qmp_target) 90 self.assert_qmp(result, 'return', {}) 91 92 self.wait_ready_and_cancel() 93 result = self.vm.qmp('query-block') 94 self.assert_qmp(result, 'return[0]/inserted/file', test_img) 95 self.vm.shutdown() 96 self.assertTrue(iotests.compare_images(test_img, target_img), 97 'target image does not match source after mirroring') 98 99 def test_pause(self): 100 self.assert_no_active_block_jobs() 101 102 result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', 103 target=self.qmp_target) 104 self.assert_qmp(result, 'return', {}) 105 106 self.pause_job('drive0') 107 108 result = self.vm.qmp('query-block-jobs') 109 offset = self.dictpath(result, 'return[0]/offset') 110 111 time.sleep(0.5) 112 result = self.vm.qmp('query-block-jobs') 113 self.assert_qmp(result, 'return[0]/offset', offset) 114 115 result = self.vm.qmp('block-job-resume', device='drive0') 116 self.assert_qmp(result, 'return', {}) 117 118 self.complete_and_wait() 119 self.vm.shutdown() 120 self.assertTrue(iotests.compare_images(test_img, target_img), 121 'target image does not match source after mirroring') 122 123 def test_small_buffer(self): 124 self.assert_no_active_block_jobs() 125 126 # A small buffer is rounded up automatically 127 result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', 128 buf_size=4096, target=self.qmp_target) 129 self.assert_qmp(result, 'return', {}) 130 131 self.complete_and_wait() 132 result = self.vm.qmp('query-block') 133 self.assert_qmp(result, 'return[0]/inserted/file', target_img) 134 self.vm.shutdown() 135 self.assertTrue(iotests.compare_images(test_img, target_img), 136 'target image does not match source after mirroring') 137 138 def test_small_buffer2(self): 139 self.assert_no_active_block_jobs() 140 141 qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,size=%d' 142 % (self.image_len, self.image_len), target_img) 143 result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', 144 buf_size=65536, mode='existing', target=self.qmp_target) 145 self.assert_qmp(result, 'return', {}) 146 147 self.complete_and_wait() 148 result = self.vm.qmp('query-block') 149 self.assert_qmp(result, 'return[0]/inserted/file', target_img) 150 self.vm.shutdown() 151 self.assertTrue(iotests.compare_images(test_img, target_img), 152 'target image does not match source after mirroring') 153 154 def test_large_cluster(self): 155 self.assert_no_active_block_jobs() 156 157 qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,backing_file=%s' 158 % (self.image_len, backing_img), target_img) 159 result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', 160 mode='existing', target=self.qmp_target) 161 self.assert_qmp(result, 'return', {}) 162 163 self.complete_and_wait() 164 result = self.vm.qmp('query-block') 165 self.assert_qmp(result, 'return[0]/inserted/file', target_img) 166 self.vm.shutdown() 167 self.assertTrue(iotests.compare_images(test_img, target_img), 168 'target image does not match source after mirroring') 169 170 # Tests that the insertion of the mirror_top filter node doesn't make a 171 # difference to query-block 172 def test_implicit_node(self): 173 self.assert_no_active_block_jobs() 174 175 result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', 176 target=self.qmp_target) 177 self.assert_qmp(result, 'return', {}) 178 179 result = self.vm.qmp('query-block') 180 self.assert_qmp(result, 'return[0]/inserted/file', test_img) 181 self.assert_qmp(result, 'return[0]/inserted/drv', iotests.imgfmt) 182 self.assert_qmp(result, 'return[0]/inserted/backing_file', backing_img) 183 self.assert_qmp(result, 'return[0]/inserted/backing_file_depth', 1) 184 self.assert_qmp(result, 'return[0]/inserted/image/filename', test_img) 185 self.assert_qmp(result, 'return[0]/inserted/image/backing-image/filename', backing_img) 186 187 result = self.vm.qmp('query-blockstats') 188 self.assert_qmp(result, 'return[0]/node-name', 'top') 189 self.assert_qmp(result, 'return[0]/backing/node-name', 'base') 190 191 self.cancel_and_wait(force=True) 192 result = self.vm.qmp('query-block') 193 self.assert_qmp(result, 'return[0]/inserted/file', test_img) 194 self.assert_qmp(result, 'return[0]/inserted/drv', iotests.imgfmt) 195 self.assert_qmp(result, 'return[0]/inserted/backing_file', backing_img) 196 self.assert_qmp(result, 'return[0]/inserted/backing_file_depth', 1) 197 self.assert_qmp(result, 'return[0]/inserted/image/filename', test_img) 198 self.assert_qmp(result, 'return[0]/inserted/image/backing-image/filename', backing_img) 199 200 result = self.vm.qmp('query-blockstats') 201 self.assert_qmp(result, 'return[0]/node-name', 'top') 202 self.assert_qmp(result, 'return[0]/backing/node-name', 'base') 203 204 self.vm.shutdown() 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 self.vm.shutdown() 459 460 def test_ignore_read(self): 461 self.assert_no_active_block_jobs() 462 463 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 464 target=target_img, on_source_error='ignore') 465 self.assert_qmp(result, 'return', {}) 466 467 event = self.vm.get_qmp_event(wait=True) 468 while event['event'] == 'JOB_STATUS_CHANGE': 469 self.assert_qmp(event, 'data/id', 'drive0') 470 event = self.vm.get_qmp_event(wait=True) 471 472 self.assertEqual(event['event'], 'BLOCK_JOB_ERROR') 473 self.assert_qmp(event, 'data/device', 'drive0') 474 self.assert_qmp(event, 'data/operation', 'read') 475 result = self.vm.qmp('query-block-jobs') 476 self.assert_qmp(result, 'return[0]/paused', False) 477 self.complete_and_wait() 478 self.vm.shutdown() 479 480 def test_large_cluster(self): 481 self.assert_no_active_block_jobs() 482 483 # Test COW into the target image. The first half of the 484 # cluster at MIRROR_GRANULARITY has to be copied from 485 # backing_img, even though sync='top'. 486 qemu_img('create', '-f', iotests.imgfmt, '-ocluster_size=131072,backing_file=%s' %(backing_img), target_img) 487 result = self.vm.qmp('drive-mirror', device='drive0', sync='top', 488 on_source_error='ignore', 489 mode='existing', target=target_img) 490 self.assert_qmp(result, 'return', {}) 491 492 event = self.vm.get_qmp_event(wait=True) 493 while event['event'] == 'JOB_STATUS_CHANGE': 494 self.assert_qmp(event, 'data/id', 'drive0') 495 event = self.vm.get_qmp_event(wait=True) 496 497 self.assertEqual(event['event'], 'BLOCK_JOB_ERROR') 498 self.assert_qmp(event, 'data/device', 'drive0') 499 self.assert_qmp(event, 'data/operation', 'read') 500 result = self.vm.qmp('query-block-jobs') 501 self.assert_qmp(result, 'return[0]/paused', False) 502 self.complete_and_wait() 503 self.vm.shutdown() 504 505 # Detach blkdebug to compare images successfully 506 qemu_img('rebase', '-f', iotests.imgfmt, '-u', '-b', backing_img, test_img) 507 self.assertTrue(iotests.compare_images(test_img, target_img), 508 'target image does not match source after mirroring') 509 510 def test_stop_read(self): 511 self.assert_no_active_block_jobs() 512 513 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 514 target=target_img, on_source_error='stop') 515 self.assert_qmp(result, 'return', {}) 516 517 error = False 518 ready = False 519 while not ready: 520 for event in self.vm.get_qmp_events(wait=True): 521 if event['event'] == 'BLOCK_JOB_ERROR': 522 self.assert_qmp(event, 'data/device', 'drive0') 523 self.assert_qmp(event, 'data/operation', 'read') 524 525 result = self.vm.qmp('query-block-jobs') 526 self.assert_qmp(result, 'return[0]/paused', True) 527 self.assert_qmp(result, 'return[0]/io-status', 'failed') 528 529 result = self.vm.qmp('block-job-resume', device='drive0') 530 self.assert_qmp(result, 'return', {}) 531 error = True 532 elif event['event'] == 'BLOCK_JOB_READY': 533 self.assertTrue(error, 'job completed unexpectedly') 534 self.assert_qmp(event, 'data/device', 'drive0') 535 ready = True 536 537 result = self.vm.qmp('query-block-jobs') 538 self.assert_qmp(result, 'return[0]/paused', False) 539 self.assert_qmp(result, 'return[0]/io-status', 'ok') 540 541 self.complete_and_wait(wait_ready=False) 542 self.assert_no_active_block_jobs() 543 self.vm.shutdown() 544 545class TestWriteErrors(iotests.QMPTestCase): 546 image_len = 2 * 1024 * 1024 # MB 547 548 # this should be a multiple of twice the default granularity 549 # so that we hit this offset first in state 1 550 MIRROR_GRANULARITY = 1024 * 1024 551 552 def create_blkdebug_file(self, name, event, errno): 553 file = open(name, 'w') 554 file.write(''' 555[inject-error] 556state = "1" 557event = "%s" 558errno = "%d" 559immediately = "off" 560once = "on" 561sector = "%d" 562 563[set-state] 564state = "1" 565event = "%s" 566new_state = "2" 567 568[set-state] 569state = "2" 570event = "%s" 571new_state = "1" 572''' % (event, errno, self.MIRROR_GRANULARITY // 512, event, event)) 573 file.close() 574 575 def setUp(self): 576 self.blkdebug_file = target_img + ".blkdebug" 577 iotests.create_image(backing_img, TestWriteErrors.image_len) 578 self.create_blkdebug_file(self.blkdebug_file, "write_aio", 5) 579 qemu_img('create', '-f', iotests.imgfmt, '-obacking_file=%s' %(backing_img), test_img) 580 self.vm = iotests.VM().add_drive(test_img) 581 self.target_img = 'blkdebug:%s:%s' % (self.blkdebug_file, target_img) 582 qemu_img('create', '-f', iotests.imgfmt, '-osize=%d' %(TestWriteErrors.image_len), target_img) 583 self.vm.launch() 584 585 def tearDown(self): 586 self.vm.shutdown() 587 os.remove(test_img) 588 os.remove(target_img) 589 os.remove(backing_img) 590 os.remove(self.blkdebug_file) 591 592 def test_report_write(self): 593 self.assert_no_active_block_jobs() 594 595 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 596 mode='existing', target=self.target_img) 597 self.assert_qmp(result, 'return', {}) 598 599 completed = False 600 error = False 601 while not completed: 602 for event in self.vm.get_qmp_events(wait=True): 603 if event['event'] == 'BLOCK_JOB_ERROR': 604 self.assert_qmp(event, 'data/device', 'drive0') 605 self.assert_qmp(event, 'data/operation', 'write') 606 error = True 607 elif event['event'] == 'BLOCK_JOB_READY': 608 self.assertTrue(False, 'job completed unexpectedly') 609 elif event['event'] == 'BLOCK_JOB_COMPLETED': 610 self.assertTrue(error, 'job completed unexpectedly') 611 self.assert_qmp(event, 'data/type', 'mirror') 612 self.assert_qmp(event, 'data/device', 'drive0') 613 self.assert_qmp(event, 'data/error', 'Input/output error') 614 completed = True 615 616 self.assert_no_active_block_jobs() 617 self.vm.shutdown() 618 619 def test_ignore_write(self): 620 self.assert_no_active_block_jobs() 621 622 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 623 mode='existing', target=self.target_img, 624 on_target_error='ignore') 625 self.assert_qmp(result, 'return', {}) 626 627 event = self.vm.event_wait(name='BLOCK_JOB_ERROR') 628 self.assertEqual(event['event'], 'BLOCK_JOB_ERROR') 629 self.assert_qmp(event, 'data/device', 'drive0') 630 self.assert_qmp(event, 'data/operation', 'write') 631 result = self.vm.qmp('query-block-jobs') 632 self.assert_qmp(result, 'return[0]/paused', False) 633 self.complete_and_wait() 634 self.vm.shutdown() 635 636 def test_stop_write(self): 637 self.assert_no_active_block_jobs() 638 639 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 640 mode='existing', target=self.target_img, 641 on_target_error='stop') 642 self.assert_qmp(result, 'return', {}) 643 644 error = False 645 ready = False 646 while not ready: 647 for event in self.vm.get_qmp_events(wait=True): 648 if event['event'] == 'BLOCK_JOB_ERROR': 649 self.assert_qmp(event, 'data/device', 'drive0') 650 self.assert_qmp(event, 'data/operation', 'write') 651 652 result = self.vm.qmp('query-block-jobs') 653 self.assert_qmp(result, 'return[0]/paused', True) 654 self.assert_qmp(result, 'return[0]/io-status', 'failed') 655 656 result = self.vm.qmp('block-job-resume', device='drive0') 657 self.assert_qmp(result, 'return', {}) 658 659 result = self.vm.qmp('query-block-jobs') 660 self.assert_qmp(result, 'return[0]/paused', False) 661 self.assert_qmp(result, 'return[0]/io-status', 'ok') 662 error = True 663 elif event['event'] == 'BLOCK_JOB_READY': 664 self.assertTrue(error, 'job completed unexpectedly') 665 self.assert_qmp(event, 'data/device', 'drive0') 666 ready = True 667 668 self.complete_and_wait(wait_ready=False) 669 self.assert_no_active_block_jobs() 670 self.vm.shutdown() 671 672class TestSetSpeed(iotests.QMPTestCase): 673 image_len = 80 * 1024 * 1024 # MB 674 675 def setUp(self): 676 qemu_img('create', backing_img, str(TestSetSpeed.image_len)) 677 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img) 678 self.vm = iotests.VM().add_drive(test_img) 679 self.vm.launch() 680 681 def tearDown(self): 682 self.vm.shutdown() 683 os.remove(test_img) 684 os.remove(backing_img) 685 os.remove(target_img) 686 687 def test_set_speed(self): 688 self.assert_no_active_block_jobs() 689 690 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 691 target=target_img) 692 self.assert_qmp(result, 'return', {}) 693 694 # Default speed is 0 695 result = self.vm.qmp('query-block-jobs') 696 self.assert_qmp(result, 'return[0]/device', 'drive0') 697 self.assert_qmp(result, 'return[0]/speed', 0) 698 699 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) 700 self.assert_qmp(result, 'return', {}) 701 702 # Ensure the speed we set was accepted 703 result = self.vm.qmp('query-block-jobs') 704 self.assert_qmp(result, 'return[0]/device', 'drive0') 705 self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024) 706 707 self.wait_ready_and_cancel() 708 709 # Check setting speed in drive-mirror works 710 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 711 target=target_img, speed=4*1024*1024) 712 self.assert_qmp(result, 'return', {}) 713 714 result = self.vm.qmp('query-block-jobs') 715 self.assert_qmp(result, 'return[0]/device', 'drive0') 716 self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024) 717 718 self.wait_ready_and_cancel() 719 720 def test_set_speed_invalid(self): 721 self.assert_no_active_block_jobs() 722 723 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 724 target=target_img, speed=-1) 725 self.assert_qmp(result, 'error/class', 'GenericError') 726 727 self.assert_no_active_block_jobs() 728 729 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 730 target=target_img) 731 self.assert_qmp(result, 'return', {}) 732 733 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1) 734 self.assert_qmp(result, 'error/class', 'GenericError') 735 736 self.wait_ready_and_cancel() 737 738class TestUnbackedSource(iotests.QMPTestCase): 739 image_len = 2 * 1024 * 1024 # MB 740 741 def setUp(self): 742 qemu_img('create', '-f', iotests.imgfmt, test_img, 743 str(TestUnbackedSource.image_len)) 744 self.vm = iotests.VM() 745 self.vm.launch() 746 result = self.vm.qmp('blockdev-add', node_name='drive0', 747 driver=iotests.imgfmt, 748 file={ 749 'driver': 'file', 750 'filename': test_img, 751 }) 752 self.assert_qmp(result, 'return', {}) 753 754 def tearDown(self): 755 self.vm.shutdown() 756 os.remove(test_img) 757 os.remove(target_img) 758 759 def test_absolute_paths_full(self): 760 self.assert_no_active_block_jobs() 761 result = self.vm.qmp('drive-mirror', job_id='drive0', device='drive0', 762 sync='full', target=target_img, 763 mode='absolute-paths') 764 self.assert_qmp(result, 'return', {}) 765 self.complete_and_wait() 766 self.assert_no_active_block_jobs() 767 768 def test_absolute_paths_top(self): 769 self.assert_no_active_block_jobs() 770 result = self.vm.qmp('drive-mirror', job_id='drive0', device='drive0', 771 sync='top', target=target_img, 772 mode='absolute-paths') 773 self.assert_qmp(result, 'return', {}) 774 self.complete_and_wait() 775 self.assert_no_active_block_jobs() 776 777 def test_absolute_paths_none(self): 778 self.assert_no_active_block_jobs() 779 result = self.vm.qmp('drive-mirror', job_id='drive0', device='drive0', 780 sync='none', target=target_img, 781 mode='absolute-paths') 782 self.assert_qmp(result, 'return', {}) 783 self.complete_and_wait() 784 self.assert_no_active_block_jobs() 785 786 def test_existing_full(self): 787 qemu_img('create', '-f', iotests.imgfmt, target_img, 788 str(self.image_len)) 789 qemu_io('-c', 'write -P 42 0 64k', target_img) 790 791 self.assert_no_active_block_jobs() 792 result = self.vm.qmp('drive-mirror', job_id='drive0', device='drive0', 793 sync='full', target=target_img, mode='existing') 794 self.assert_qmp(result, 'return', {}) 795 self.complete_and_wait() 796 self.assert_no_active_block_jobs() 797 798 result = self.vm.qmp('blockdev-del', node_name='drive0') 799 self.assert_qmp(result, 'return', {}) 800 801 self.assertTrue(iotests.compare_images(test_img, target_img), 802 'target image does not match source after mirroring') 803 804 def test_blockdev_full(self): 805 qemu_img('create', '-f', iotests.imgfmt, target_img, 806 str(self.image_len)) 807 qemu_io('-c', 'write -P 42 0 64k', target_img) 808 809 result = self.vm.qmp('blockdev-add', node_name='target', 810 driver=iotests.imgfmt, 811 file={ 812 'driver': 'file', 813 'filename': target_img, 814 }) 815 self.assert_qmp(result, 'return', {}) 816 817 self.assert_no_active_block_jobs() 818 result = self.vm.qmp('blockdev-mirror', job_id='drive0', device='drive0', 819 sync='full', target='target') 820 self.assert_qmp(result, 'return', {}) 821 self.complete_and_wait() 822 self.assert_no_active_block_jobs() 823 824 result = self.vm.qmp('blockdev-del', node_name='drive0') 825 self.assert_qmp(result, 'return', {}) 826 827 result = self.vm.qmp('blockdev-del', node_name='target') 828 self.assert_qmp(result, 'return', {}) 829 830 self.assertTrue(iotests.compare_images(test_img, target_img), 831 'target image does not match source after mirroring') 832 833class TestGranularity(iotests.QMPTestCase): 834 image_len = 10 * 1024 * 1024 # MB 835 836 def setUp(self): 837 qemu_img('create', '-f', iotests.imgfmt, test_img, 838 str(TestGranularity.image_len)) 839 qemu_io('-c', 'write 0 %d' % (self.image_len), 840 test_img) 841 self.vm = iotests.VM().add_drive(test_img) 842 self.vm.launch() 843 844 def tearDown(self): 845 self.vm.shutdown() 846 self.assertTrue(iotests.compare_images(test_img, target_img), 847 'target image does not match source after mirroring') 848 os.remove(test_img) 849 os.remove(target_img) 850 851 def test_granularity(self): 852 self.assert_no_active_block_jobs() 853 result = self.vm.qmp('drive-mirror', device='drive0', 854 sync='full', target=target_img, 855 mode='absolute-paths', granularity=8192) 856 self.assert_qmp(result, 'return', {}) 857 858 event = self.vm.get_qmp_event(wait=60.0) 859 while event['event'] == 'JOB_STATUS_CHANGE': 860 self.assert_qmp(event, 'data/id', 'drive0') 861 event = self.vm.get_qmp_event(wait=60.0) 862 863 # Failures will manifest as COMPLETED/ERROR. 864 self.assert_qmp(event, 'event', 'BLOCK_JOB_READY') 865 self.complete_and_wait(drive='drive0', wait_ready=False) 866 self.assert_no_active_block_jobs() 867 868class TestRepairQuorum(iotests.QMPTestCase): 869 """ This class test quorum file repair using drive-mirror. 870 It's mostly a fork of TestSingleDrive """ 871 image_len = 1 * 1024 * 1024 # MB 872 IMAGES = [ quorum_img1, quorum_img2, quorum_img3 ] 873 874 @iotests.skip_if_unsupported(['quorum']) 875 def setUp(self): 876 self.vm = iotests.VM() 877 878 if iotests.qemu_default_machine == 'pc': 879 self.vm.add_drive(None, 'media=cdrom', 'ide') 880 881 # Add each individual quorum images 882 for i in self.IMAGES: 883 qemu_img('create', '-f', iotests.imgfmt, i, 884 str(TestSingleDrive.image_len)) 885 # Assign a node name to each quorum image in order to manipulate 886 # them 887 opts = "node-name=img%i" % self.IMAGES.index(i) 888 self.vm = self.vm.add_drive(i, opts) 889 890 self.vm.launch() 891 892 #assemble the quorum block device from the individual files 893 args = { "driver": "quorum", "node-name": "quorum0", 894 "vote-threshold": 2, "children": [ "img0", "img1", "img2" ] } 895 result = self.vm.qmp("blockdev-add", **args) 896 self.assert_qmp(result, 'return', {}) 897 898 899 def tearDown(self): 900 self.vm.shutdown() 901 for i in self.IMAGES + [ quorum_repair_img, quorum_snapshot_file ]: 902 # Do a try/except because the test may have deleted some images 903 try: 904 os.remove(i) 905 except OSError: 906 pass 907 908 def test_complete(self): 909 self.assert_no_active_block_jobs() 910 911 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 912 sync='full', node_name="repair0", replaces="img1", 913 target=quorum_repair_img, format=iotests.imgfmt) 914 self.assert_qmp(result, 'return', {}) 915 916 self.complete_and_wait(drive="job0") 917 self.assert_has_block_node("repair0", quorum_repair_img) 918 # TODO: a better test requiring some QEMU infrastructure will be added 919 # to check that this file is really driven by quorum 920 self.vm.shutdown() 921 self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img), 922 'target image does not match source after mirroring') 923 924 def test_cancel(self): 925 self.assert_no_active_block_jobs() 926 927 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 928 sync='full', node_name="repair0", replaces="img1", 929 target=quorum_repair_img, format=iotests.imgfmt) 930 self.assert_qmp(result, 'return', {}) 931 932 self.cancel_and_wait(drive="job0", force=True) 933 # here we check that the last registered quorum file has not been 934 # swapped out and unref 935 self.assert_has_block_node(None, quorum_img3) 936 self.vm.shutdown() 937 938 def test_cancel_after_ready(self): 939 self.assert_no_active_block_jobs() 940 941 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 942 sync='full', node_name="repair0", replaces="img1", 943 target=quorum_repair_img, format=iotests.imgfmt) 944 self.assert_qmp(result, 'return', {}) 945 946 self.wait_ready_and_cancel(drive="job0") 947 # here we check that the last registered quorum file has not been 948 # swapped out and unref 949 self.assert_has_block_node(None, quorum_img3) 950 self.vm.shutdown() 951 self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img), 952 'target image does not match source after mirroring') 953 954 def test_pause(self): 955 self.assert_no_active_block_jobs() 956 957 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 958 sync='full', node_name="repair0", replaces="img1", 959 target=quorum_repair_img, format=iotests.imgfmt) 960 self.assert_qmp(result, 'return', {}) 961 962 self.pause_job('job0') 963 964 result = self.vm.qmp('query-block-jobs') 965 offset = self.dictpath(result, 'return[0]/offset') 966 967 time.sleep(0.5) 968 result = self.vm.qmp('query-block-jobs') 969 self.assert_qmp(result, 'return[0]/offset', offset) 970 971 result = self.vm.qmp('block-job-resume', device='job0') 972 self.assert_qmp(result, 'return', {}) 973 974 self.complete_and_wait(drive="job0") 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_medium_not_found(self): 980 if iotests.qemu_default_machine != 'pc': 981 return 982 983 result = self.vm.qmp('drive-mirror', job_id='job0', device='drive0', # CD-ROM 984 sync='full', 985 node_name='repair0', 986 replaces='img1', 987 target=quorum_repair_img, format=iotests.imgfmt) 988 self.assert_qmp(result, 'error/class', 'GenericError') 989 990 def test_image_not_found(self): 991 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 992 sync='full', node_name='repair0', replaces='img1', 993 mode='existing', target=quorum_repair_img, 994 format=iotests.imgfmt) 995 self.assert_qmp(result, 'error/class', 'GenericError') 996 997 def test_device_not_found(self): 998 result = self.vm.qmp('drive-mirror', job_id='job0', 999 device='nonexistent', sync='full', 1000 node_name='repair0', 1001 replaces='img1', 1002 target=quorum_repair_img, format=iotests.imgfmt) 1003 self.assert_qmp(result, 'error/class', 'GenericError') 1004 1005 def test_wrong_sync_mode(self): 1006 result = self.vm.qmp('drive-mirror', device='quorum0', job_id='job0', 1007 node_name='repair0', 1008 replaces='img1', 1009 target=quorum_repair_img, format=iotests.imgfmt) 1010 self.assert_qmp(result, 'error/class', 'GenericError') 1011 1012 def test_no_node_name(self): 1013 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 1014 sync='full', replaces='img1', 1015 target=quorum_repair_img, format=iotests.imgfmt) 1016 self.assert_qmp(result, 'error/class', 'GenericError') 1017 1018 def test_nonexistent_replaces(self): 1019 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 1020 sync='full', node_name='repair0', replaces='img77', 1021 target=quorum_repair_img, format=iotests.imgfmt) 1022 self.assert_qmp(result, 'error/class', 'GenericError') 1023 1024 def test_after_a_quorum_snapshot(self): 1025 result = self.vm.qmp('blockdev-snapshot-sync', node_name='img1', 1026 snapshot_file=quorum_snapshot_file, 1027 snapshot_node_name="snap1"); 1028 1029 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 1030 sync='full', node_name='repair0', replaces="img1", 1031 target=quorum_repair_img, format=iotests.imgfmt) 1032 self.assert_qmp(result, 'error/class', 'GenericError') 1033 1034 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 1035 sync='full', node_name='repair0', replaces="snap1", 1036 target=quorum_repair_img, format=iotests.imgfmt) 1037 self.assert_qmp(result, 'return', {}) 1038 1039 self.complete_and_wait('job0') 1040 self.assert_has_block_node("repair0", quorum_repair_img) 1041 # TODO: a better test requiring some QEMU infrastructure will be added 1042 # to check that this file is really driven by quorum 1043 self.vm.shutdown() 1044 1045# Test mirroring with a source that does not have any parents (not even a 1046# BlockBackend) 1047class TestOrphanedSource(iotests.QMPTestCase): 1048 def setUp(self): 1049 blk0 = { 'node-name': 'src', 1050 'driver': 'null-co' } 1051 1052 blk1 = { 'node-name': 'dest', 1053 'driver': 'null-co' } 1054 1055 blk2 = { 'node-name': 'dest-ro', 1056 'driver': 'null-co', 1057 'read-only': 'on' } 1058 1059 self.vm = iotests.VM() 1060 self.vm.add_blockdev(self.vm.qmp_to_opts(blk0)) 1061 self.vm.add_blockdev(self.vm.qmp_to_opts(blk1)) 1062 self.vm.add_blockdev(self.vm.qmp_to_opts(blk2)) 1063 self.vm.launch() 1064 1065 def tearDown(self): 1066 self.vm.shutdown() 1067 1068 def test_no_job_id(self): 1069 self.assert_no_active_block_jobs() 1070 1071 result = self.vm.qmp('blockdev-mirror', device='src', sync='full', 1072 target='dest') 1073 self.assert_qmp(result, 'error/class', 'GenericError') 1074 1075 def test_success(self): 1076 self.assert_no_active_block_jobs() 1077 1078 result = self.vm.qmp('blockdev-mirror', job_id='job', device='src', 1079 sync='full', target='dest') 1080 self.assert_qmp(result, 'return', {}) 1081 1082 self.complete_and_wait('job') 1083 1084 def test_failing_permissions(self): 1085 self.assert_no_active_block_jobs() 1086 1087 result = self.vm.qmp('blockdev-mirror', device='src', sync='full', 1088 target='dest-ro') 1089 self.assert_qmp(result, 'error/class', 'GenericError') 1090 1091 def test_failing_permission_in_complete(self): 1092 self.assert_no_active_block_jobs() 1093 1094 # Unshare consistent-read on the target 1095 # (The mirror job does not care) 1096 result = self.vm.qmp('blockdev-add', 1097 driver='blkdebug', 1098 node_name='dest-perm', 1099 image='dest', 1100 unshare_child_perms=['consistent-read']) 1101 self.assert_qmp(result, 'return', {}) 1102 1103 result = self.vm.qmp('blockdev-mirror', job_id='job', device='src', 1104 sync='full', target='dest', 1105 filter_node_name='mirror-filter') 1106 self.assert_qmp(result, 'return', {}) 1107 1108 # Require consistent-read on the source 1109 # (We can only add this node once the job has started, or it 1110 # will complain that it does not want to run on non-root nodes) 1111 result = self.vm.qmp('blockdev-add', 1112 driver='blkdebug', 1113 node_name='src-perm', 1114 image='src', 1115 take_child_perms=['consistent-read']) 1116 self.assert_qmp(result, 'return', {}) 1117 1118 # While completing, mirror will attempt to replace src by 1119 # dest, which must fail because src-perm requires 1120 # consistent-read but dest-perm does not share it; thus 1121 # aborting the job when it is supposed to complete 1122 self.complete_and_wait('job', 1123 completion_error='Operation not permitted') 1124 1125 # Assert that all of our nodes are still there (except for the 1126 # mirror filter, which should be gone despite the failure) 1127 nodes = self.vm.qmp('query-named-block-nodes')['return'] 1128 nodes = [node['node-name'] for node in nodes] 1129 1130 for expect in ('src', 'src-perm', 'dest', 'dest-perm'): 1131 self.assertTrue(expect in nodes, '%s disappeared' % expect) 1132 self.assertFalse('mirror-filter' in nodes, 1133 'Mirror filter node did not disappear') 1134 1135if __name__ == '__main__': 1136 iotests.main(supported_fmts=['qcow2', 'qed'], 1137 supported_protocols=['file'], 1138 supported_platforms=['linux', 'freebsd', 'netbsd', 'openbsd']) 1139