1#!/usr/bin/env python 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 def setUp(self): 875 self.vm = iotests.VM() 876 877 if iotests.qemu_default_machine == 'pc': 878 self.vm.add_drive(None, 'media=cdrom', 'ide') 879 880 # Add each individual quorum images 881 for i in self.IMAGES: 882 qemu_img('create', '-f', iotests.imgfmt, i, 883 str(TestSingleDrive.image_len)) 884 # Assign a node name to each quorum image in order to manipulate 885 # them 886 opts = "node-name=img%i" % self.IMAGES.index(i) 887 self.vm = self.vm.add_drive(i, opts) 888 889 self.vm.launch() 890 891 #assemble the quorum block device from the individual files 892 args = { "driver": "quorum", "node-name": "quorum0", 893 "vote-threshold": 2, "children": [ "img0", "img1", "img2" ] } 894 if iotests.supports_quorum(): 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 if not iotests.supports_quorum(): 910 return 911 912 self.assert_no_active_block_jobs() 913 914 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 915 sync='full', node_name="repair0", replaces="img1", 916 target=quorum_repair_img, format=iotests.imgfmt) 917 self.assert_qmp(result, 'return', {}) 918 919 self.complete_and_wait(drive="job0") 920 self.assert_has_block_node("repair0", quorum_repair_img) 921 # TODO: a better test requiring some QEMU infrastructure will be added 922 # to check that this file is really driven by quorum 923 self.vm.shutdown() 924 self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img), 925 'target image does not match source after mirroring') 926 927 def test_cancel(self): 928 if not iotests.supports_quorum(): 929 return 930 931 self.assert_no_active_block_jobs() 932 933 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 934 sync='full', node_name="repair0", replaces="img1", 935 target=quorum_repair_img, format=iotests.imgfmt) 936 self.assert_qmp(result, 'return', {}) 937 938 self.cancel_and_wait(drive="job0", force=True) 939 # here we check that the last registered quorum file has not been 940 # swapped out and unref 941 self.assert_has_block_node(None, quorum_img3) 942 self.vm.shutdown() 943 944 def test_cancel_after_ready(self): 945 if not iotests.supports_quorum(): 946 return 947 948 self.assert_no_active_block_jobs() 949 950 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 951 sync='full', node_name="repair0", replaces="img1", 952 target=quorum_repair_img, format=iotests.imgfmt) 953 self.assert_qmp(result, 'return', {}) 954 955 self.wait_ready_and_cancel(drive="job0") 956 # here we check that the last registered quorum file has not been 957 # swapped out and unref 958 self.assert_has_block_node(None, quorum_img3) 959 self.vm.shutdown() 960 self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img), 961 'target image does not match source after mirroring') 962 963 def test_pause(self): 964 if not iotests.supports_quorum(): 965 return 966 967 self.assert_no_active_block_jobs() 968 969 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 970 sync='full', node_name="repair0", replaces="img1", 971 target=quorum_repair_img, format=iotests.imgfmt) 972 self.assert_qmp(result, 'return', {}) 973 974 self.pause_job('job0') 975 976 result = self.vm.qmp('query-block-jobs') 977 offset = self.dictpath(result, 'return[0]/offset') 978 979 time.sleep(0.5) 980 result = self.vm.qmp('query-block-jobs') 981 self.assert_qmp(result, 'return[0]/offset', offset) 982 983 result = self.vm.qmp('block-job-resume', device='job0') 984 self.assert_qmp(result, 'return', {}) 985 986 self.complete_and_wait(drive="job0") 987 self.vm.shutdown() 988 self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img), 989 'target image does not match source after mirroring') 990 991 def test_medium_not_found(self): 992 if not iotests.supports_quorum(): 993 return 994 995 if iotests.qemu_default_machine != 'pc': 996 return 997 998 result = self.vm.qmp('drive-mirror', job_id='job0', device='drive0', # CD-ROM 999 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_image_not_found(self): 1006 if not iotests.supports_quorum(): 1007 return 1008 1009 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 1010 sync='full', node_name='repair0', replaces='img1', 1011 mode='existing', target=quorum_repair_img, 1012 format=iotests.imgfmt) 1013 self.assert_qmp(result, 'error/class', 'GenericError') 1014 1015 def test_device_not_found(self): 1016 if not iotests.supports_quorum(): 1017 return 1018 1019 result = self.vm.qmp('drive-mirror', job_id='job0', 1020 device='nonexistent', sync='full', 1021 node_name='repair0', 1022 replaces='img1', 1023 target=quorum_repair_img, format=iotests.imgfmt) 1024 self.assert_qmp(result, 'error/class', 'GenericError') 1025 1026 def test_wrong_sync_mode(self): 1027 if not iotests.supports_quorum(): 1028 return 1029 1030 result = self.vm.qmp('drive-mirror', device='quorum0', job_id='job0', 1031 node_name='repair0', 1032 replaces='img1', 1033 target=quorum_repair_img, format=iotests.imgfmt) 1034 self.assert_qmp(result, 'error/class', 'GenericError') 1035 1036 def test_no_node_name(self): 1037 if not iotests.supports_quorum(): 1038 return 1039 1040 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 1041 sync='full', replaces='img1', 1042 target=quorum_repair_img, format=iotests.imgfmt) 1043 self.assert_qmp(result, 'error/class', 'GenericError') 1044 1045 def test_nonexistent_replaces(self): 1046 if not iotests.supports_quorum(): 1047 return 1048 1049 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 1050 sync='full', node_name='repair0', replaces='img77', 1051 target=quorum_repair_img, format=iotests.imgfmt) 1052 self.assert_qmp(result, 'error/class', 'GenericError') 1053 1054 def test_after_a_quorum_snapshot(self): 1055 if not iotests.supports_quorum(): 1056 return 1057 1058 result = self.vm.qmp('blockdev-snapshot-sync', node_name='img1', 1059 snapshot_file=quorum_snapshot_file, 1060 snapshot_node_name="snap1"); 1061 1062 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 1063 sync='full', node_name='repair0', replaces="img1", 1064 target=quorum_repair_img, format=iotests.imgfmt) 1065 self.assert_qmp(result, 'error/class', 'GenericError') 1066 1067 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 1068 sync='full', node_name='repair0', replaces="snap1", 1069 target=quorum_repair_img, format=iotests.imgfmt) 1070 self.assert_qmp(result, 'return', {}) 1071 1072 self.complete_and_wait('job0') 1073 self.assert_has_block_node("repair0", quorum_repair_img) 1074 # TODO: a better test requiring some QEMU infrastructure will be added 1075 # to check that this file is really driven by quorum 1076 self.vm.shutdown() 1077 1078# Test mirroring with a source that does not have any parents (not even a 1079# BlockBackend) 1080class TestOrphanedSource(iotests.QMPTestCase): 1081 def setUp(self): 1082 blk0 = { 'node-name': 'src', 1083 'driver': 'null-co' } 1084 1085 blk1 = { 'node-name': 'dest', 1086 'driver': 'null-co' } 1087 1088 blk2 = { 'node-name': 'dest-ro', 1089 'driver': 'null-co', 1090 'read-only': 'on' } 1091 1092 self.vm = iotests.VM() 1093 self.vm.add_blockdev(self.vm.qmp_to_opts(blk0)) 1094 self.vm.add_blockdev(self.vm.qmp_to_opts(blk1)) 1095 self.vm.add_blockdev(self.vm.qmp_to_opts(blk2)) 1096 self.vm.launch() 1097 1098 def tearDown(self): 1099 self.vm.shutdown() 1100 1101 def test_no_job_id(self): 1102 self.assert_no_active_block_jobs() 1103 1104 result = self.vm.qmp('blockdev-mirror', device='src', sync='full', 1105 target='dest') 1106 self.assert_qmp(result, 'error/class', 'GenericError') 1107 1108 def test_success(self): 1109 self.assert_no_active_block_jobs() 1110 1111 result = self.vm.qmp('blockdev-mirror', job_id='job', device='src', 1112 sync='full', target='dest') 1113 self.assert_qmp(result, 'return', {}) 1114 1115 self.complete_and_wait('job') 1116 1117 def test_failing_permissions(self): 1118 self.assert_no_active_block_jobs() 1119 1120 result = self.vm.qmp('blockdev-mirror', device='src', sync='full', 1121 target='dest-ro') 1122 self.assert_qmp(result, 'error/class', 'GenericError') 1123 1124 def test_failing_permission_in_complete(self): 1125 self.assert_no_active_block_jobs() 1126 1127 # Unshare consistent-read on the target 1128 # (The mirror job does not care) 1129 result = self.vm.qmp('blockdev-add', 1130 driver='blkdebug', 1131 node_name='dest-perm', 1132 image='dest', 1133 unshare_child_perms=['consistent-read']) 1134 self.assert_qmp(result, 'return', {}) 1135 1136 result = self.vm.qmp('blockdev-mirror', job_id='job', device='src', 1137 sync='full', target='dest', 1138 filter_node_name='mirror-filter') 1139 self.assert_qmp(result, 'return', {}) 1140 1141 # Require consistent-read on the source 1142 # (We can only add this node once the job has started, or it 1143 # will complain that it does not want to run on non-root nodes) 1144 result = self.vm.qmp('blockdev-add', 1145 driver='blkdebug', 1146 node_name='src-perm', 1147 image='src', 1148 take_child_perms=['consistent-read']) 1149 self.assert_qmp(result, 'return', {}) 1150 1151 # While completing, mirror will attempt to replace src by 1152 # dest, which must fail because src-perm requires 1153 # consistent-read but dest-perm does not share it; thus 1154 # aborting the job when it is supposed to complete 1155 self.complete_and_wait('job', 1156 completion_error='Operation not permitted') 1157 1158 # Assert that all of our nodes are still there (except for the 1159 # mirror filter, which should be gone despite the failure) 1160 nodes = self.vm.qmp('query-named-block-nodes')['return'] 1161 nodes = [node['node-name'] for node in nodes] 1162 1163 for expect in ('src', 'src-perm', 'dest', 'dest-perm'): 1164 self.assertTrue(expect in nodes, '%s disappeared' % expect) 1165 self.assertFalse('mirror-filter' in nodes, 1166 'Mirror filter node did not disappear') 1167 1168if __name__ == '__main__': 1169 iotests.main(supported_fmts=['qcow2', 'qed'], 1170 supported_protocols=['file']) 1171