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().add_drive(test_img) 745 self.vm.launch() 746 747 def tearDown(self): 748 self.vm.shutdown() 749 os.remove(test_img) 750 os.remove(target_img) 751 752 def test_absolute_paths_full(self): 753 self.assert_no_active_block_jobs() 754 result = self.vm.qmp('drive-mirror', device='drive0', 755 sync='full', target=target_img, 756 mode='absolute-paths') 757 self.assert_qmp(result, 'return', {}) 758 self.complete_and_wait() 759 self.assert_no_active_block_jobs() 760 761 def test_absolute_paths_top(self): 762 self.assert_no_active_block_jobs() 763 result = self.vm.qmp('drive-mirror', device='drive0', 764 sync='top', target=target_img, 765 mode='absolute-paths') 766 self.assert_qmp(result, 'return', {}) 767 self.complete_and_wait() 768 self.assert_no_active_block_jobs() 769 770 def test_absolute_paths_none(self): 771 self.assert_no_active_block_jobs() 772 result = self.vm.qmp('drive-mirror', device='drive0', 773 sync='none', target=target_img, 774 mode='absolute-paths') 775 self.assert_qmp(result, 'return', {}) 776 self.complete_and_wait() 777 self.assert_no_active_block_jobs() 778 779class TestGranularity(iotests.QMPTestCase): 780 image_len = 10 * 1024 * 1024 # MB 781 782 def setUp(self): 783 qemu_img('create', '-f', iotests.imgfmt, test_img, 784 str(TestGranularity.image_len)) 785 qemu_io('-c', 'write 0 %d' % (self.image_len), 786 test_img) 787 self.vm = iotests.VM().add_drive(test_img) 788 self.vm.launch() 789 790 def tearDown(self): 791 self.vm.shutdown() 792 self.assertTrue(iotests.compare_images(test_img, target_img), 793 'target image does not match source after mirroring') 794 os.remove(test_img) 795 os.remove(target_img) 796 797 def test_granularity(self): 798 self.assert_no_active_block_jobs() 799 result = self.vm.qmp('drive-mirror', device='drive0', 800 sync='full', target=target_img, 801 mode='absolute-paths', granularity=8192) 802 self.assert_qmp(result, 'return', {}) 803 804 event = self.vm.get_qmp_event(wait=60.0) 805 while event['event'] == 'JOB_STATUS_CHANGE': 806 self.assert_qmp(event, 'data/id', 'drive0') 807 event = self.vm.get_qmp_event(wait=60.0) 808 809 # Failures will manifest as COMPLETED/ERROR. 810 self.assert_qmp(event, 'event', 'BLOCK_JOB_READY') 811 self.complete_and_wait(drive='drive0', wait_ready=False) 812 self.assert_no_active_block_jobs() 813 814class TestRepairQuorum(iotests.QMPTestCase): 815 """ This class test quorum file repair using drive-mirror. 816 It's mostly a fork of TestSingleDrive """ 817 image_len = 1 * 1024 * 1024 # MB 818 IMAGES = [ quorum_img1, quorum_img2, quorum_img3 ] 819 820 def setUp(self): 821 self.vm = iotests.VM() 822 823 if iotests.qemu_default_machine == 'pc': 824 self.vm.add_drive(None, 'media=cdrom', 'ide') 825 826 # Add each individual quorum images 827 for i in self.IMAGES: 828 qemu_img('create', '-f', iotests.imgfmt, i, 829 str(TestSingleDrive.image_len)) 830 # Assign a node name to each quorum image in order to manipulate 831 # them 832 opts = "node-name=img%i" % self.IMAGES.index(i) 833 self.vm = self.vm.add_drive(i, opts) 834 835 self.vm.launch() 836 837 #assemble the quorum block device from the individual files 838 args = { "driver": "quorum", "node-name": "quorum0", 839 "vote-threshold": 2, "children": [ "img0", "img1", "img2" ] } 840 if iotests.supports_quorum(): 841 result = self.vm.qmp("blockdev-add", **args) 842 self.assert_qmp(result, 'return', {}) 843 844 845 def tearDown(self): 846 self.vm.shutdown() 847 for i in self.IMAGES + [ quorum_repair_img, quorum_snapshot_file ]: 848 # Do a try/except because the test may have deleted some images 849 try: 850 os.remove(i) 851 except OSError: 852 pass 853 854 def test_complete(self): 855 if not iotests.supports_quorum(): 856 return 857 858 self.assert_no_active_block_jobs() 859 860 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 861 sync='full', node_name="repair0", replaces="img1", 862 target=quorum_repair_img, format=iotests.imgfmt) 863 self.assert_qmp(result, 'return', {}) 864 865 self.complete_and_wait(drive="job0") 866 self.assert_has_block_node("repair0", quorum_repair_img) 867 # TODO: a better test requiring some QEMU infrastructure will be added 868 # to check that this file is really driven by quorum 869 self.vm.shutdown() 870 self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img), 871 'target image does not match source after mirroring') 872 873 def test_cancel(self): 874 if not iotests.supports_quorum(): 875 return 876 877 self.assert_no_active_block_jobs() 878 879 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 880 sync='full', node_name="repair0", replaces="img1", 881 target=quorum_repair_img, format=iotests.imgfmt) 882 self.assert_qmp(result, 'return', {}) 883 884 self.cancel_and_wait(drive="job0", force=True) 885 # here we check that the last registered quorum file has not been 886 # swapped out and unref 887 self.assert_has_block_node(None, quorum_img3) 888 self.vm.shutdown() 889 890 def test_cancel_after_ready(self): 891 if not iotests.supports_quorum(): 892 return 893 894 self.assert_no_active_block_jobs() 895 896 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 897 sync='full', node_name="repair0", replaces="img1", 898 target=quorum_repair_img, format=iotests.imgfmt) 899 self.assert_qmp(result, 'return', {}) 900 901 self.wait_ready_and_cancel(drive="job0") 902 # here we check that the last registered quorum file has not been 903 # swapped out and unref 904 self.assert_has_block_node(None, quorum_img3) 905 self.vm.shutdown() 906 self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img), 907 'target image does not match source after mirroring') 908 909 def test_pause(self): 910 if not iotests.supports_quorum(): 911 return 912 913 self.assert_no_active_block_jobs() 914 915 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 916 sync='full', node_name="repair0", replaces="img1", 917 target=quorum_repair_img, format=iotests.imgfmt) 918 self.assert_qmp(result, 'return', {}) 919 920 self.pause_job('job0') 921 922 result = self.vm.qmp('query-block-jobs') 923 offset = self.dictpath(result, 'return[0]/offset') 924 925 time.sleep(0.5) 926 result = self.vm.qmp('query-block-jobs') 927 self.assert_qmp(result, 'return[0]/offset', offset) 928 929 result = self.vm.qmp('block-job-resume', device='job0') 930 self.assert_qmp(result, 'return', {}) 931 932 self.complete_and_wait(drive="job0") 933 self.vm.shutdown() 934 self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img), 935 'target image does not match source after mirroring') 936 937 def test_medium_not_found(self): 938 if not iotests.supports_quorum(): 939 return 940 941 if iotests.qemu_default_machine != 'pc': 942 return 943 944 result = self.vm.qmp('drive-mirror', job_id='job0', device='drive0', # CD-ROM 945 sync='full', 946 node_name='repair0', 947 replaces='img1', 948 target=quorum_repair_img, format=iotests.imgfmt) 949 self.assert_qmp(result, 'error/class', 'GenericError') 950 951 def test_image_not_found(self): 952 if not iotests.supports_quorum(): 953 return 954 955 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 956 sync='full', node_name='repair0', replaces='img1', 957 mode='existing', target=quorum_repair_img, 958 format=iotests.imgfmt) 959 self.assert_qmp(result, 'error/class', 'GenericError') 960 961 def test_device_not_found(self): 962 if not iotests.supports_quorum(): 963 return 964 965 result = self.vm.qmp('drive-mirror', job_id='job0', 966 device='nonexistent', sync='full', 967 node_name='repair0', 968 replaces='img1', 969 target=quorum_repair_img, format=iotests.imgfmt) 970 self.assert_qmp(result, 'error/class', 'GenericError') 971 972 def test_wrong_sync_mode(self): 973 if not iotests.supports_quorum(): 974 return 975 976 result = self.vm.qmp('drive-mirror', device='quorum0', job_id='job0', 977 node_name='repair0', 978 replaces='img1', 979 target=quorum_repair_img, format=iotests.imgfmt) 980 self.assert_qmp(result, 'error/class', 'GenericError') 981 982 def test_no_node_name(self): 983 if not iotests.supports_quorum(): 984 return 985 986 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 987 sync='full', replaces='img1', 988 target=quorum_repair_img, format=iotests.imgfmt) 989 self.assert_qmp(result, 'error/class', 'GenericError') 990 991 def test_nonexistent_replaces(self): 992 if not iotests.supports_quorum(): 993 return 994 995 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 996 sync='full', node_name='repair0', replaces='img77', 997 target=quorum_repair_img, format=iotests.imgfmt) 998 self.assert_qmp(result, 'error/class', 'GenericError') 999 1000 def test_after_a_quorum_snapshot(self): 1001 if not iotests.supports_quorum(): 1002 return 1003 1004 result = self.vm.qmp('blockdev-snapshot-sync', node_name='img1', 1005 snapshot_file=quorum_snapshot_file, 1006 snapshot_node_name="snap1"); 1007 1008 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 1009 sync='full', node_name='repair0', replaces="img1", 1010 target=quorum_repair_img, format=iotests.imgfmt) 1011 self.assert_qmp(result, 'error/class', 'GenericError') 1012 1013 result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', 1014 sync='full', node_name='repair0', replaces="snap1", 1015 target=quorum_repair_img, format=iotests.imgfmt) 1016 self.assert_qmp(result, 'return', {}) 1017 1018 self.complete_and_wait('job0') 1019 self.assert_has_block_node("repair0", quorum_repair_img) 1020 # TODO: a better test requiring some QEMU infrastructure will be added 1021 # to check that this file is really driven by quorum 1022 self.vm.shutdown() 1023 1024# Test mirroring with a source that does not have any parents (not even a 1025# BlockBackend) 1026class TestOrphanedSource(iotests.QMPTestCase): 1027 def setUp(self): 1028 blk0 = { 'node-name': 'src', 1029 'driver': 'null-co' } 1030 1031 blk1 = { 'node-name': 'dest', 1032 'driver': 'null-co' } 1033 1034 blk2 = { 'node-name': 'dest-ro', 1035 'driver': 'null-co', 1036 'read-only': 'on' } 1037 1038 self.vm = iotests.VM() 1039 self.vm.add_blockdev(self.vm.qmp_to_opts(blk0)) 1040 self.vm.add_blockdev(self.vm.qmp_to_opts(blk1)) 1041 self.vm.add_blockdev(self.vm.qmp_to_opts(blk2)) 1042 self.vm.launch() 1043 1044 def tearDown(self): 1045 self.vm.shutdown() 1046 1047 def test_no_job_id(self): 1048 self.assert_no_active_block_jobs() 1049 1050 result = self.vm.qmp('blockdev-mirror', device='src', sync='full', 1051 target='dest') 1052 self.assert_qmp(result, 'error/class', 'GenericError') 1053 1054 def test_success(self): 1055 self.assert_no_active_block_jobs() 1056 1057 result = self.vm.qmp('blockdev-mirror', job_id='job', device='src', 1058 sync='full', target='dest') 1059 self.assert_qmp(result, 'return', {}) 1060 1061 self.complete_and_wait('job') 1062 1063 def test_failing_permissions(self): 1064 self.assert_no_active_block_jobs() 1065 1066 result = self.vm.qmp('blockdev-mirror', device='src', sync='full', 1067 target='dest-ro') 1068 self.assert_qmp(result, 'error/class', 'GenericError') 1069 1070if __name__ == '__main__': 1071 iotests.main(supported_fmts=['qcow2', 'qed']) 1072