1#!/usr/bin/env python3 2# group: rw backing 3# 4# Tests for incremental drive-backup 5# 6# Copyright (C) 2015 John Snow for Red Hat, Inc. 7# 8# Based on 056. 9# 10# This program is free software; you can redistribute it and/or modify 11# it under the terms of the GNU General Public License as published by 12# the Free Software Foundation; either version 2 of the License, or 13# (at your option) any later version. 14# 15# This program is distributed in the hope that it will be useful, 16# but WITHOUT ANY WARRANTY; without even the implied warranty of 17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18# GNU General Public License for more details. 19# 20# You should have received a copy of the GNU General Public License 21# along with this program. If not, see <http://www.gnu.org/licenses/>. 22# 23 24import os 25import iotests 26from iotests import try_remove 27from qemu.qmp.qmp_client import ExecuteError 28 29 30def io_write_patterns(img, patterns): 31 for pattern in patterns: 32 iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img) 33 34 35def transaction_action(action, **kwargs): 36 return { 37 'type': action, 38 'data': dict((k.replace('_', '-'), v) for k, v in kwargs.items()) 39 } 40 41 42def transaction_bitmap_clear(node, name, **kwargs): 43 return transaction_action('block-dirty-bitmap-clear', 44 node=node, name=name, **kwargs) 45 46 47def transaction_drive_backup(device, target, **kwargs): 48 return transaction_action('drive-backup', job_id=device, device=device, 49 target=target, **kwargs) 50 51 52class Bitmap: 53 def __init__(self, name, drive): 54 self.name = name 55 self.drive = drive 56 self.num = 0 57 self.backups = list() 58 59 def base_target(self): 60 return (self.drive['backup'], None) 61 62 def new_target(self, num=None): 63 if num is None: 64 num = self.num 65 self.num = num + 1 66 base = os.path.join(iotests.test_dir, 67 "%s.%s." % (self.drive['id'], self.name)) 68 suff = "%i.%s" % (num, self.drive['fmt']) 69 target = base + "inc" + suff 70 reference = base + "ref" + suff 71 self.backups.append((target, reference)) 72 return (target, reference) 73 74 def last_target(self): 75 if self.backups: 76 return self.backups[-1] 77 return self.base_target() 78 79 def del_target(self): 80 for image in self.backups.pop(): 81 try_remove(image) 82 self.num -= 1 83 84 def cleanup(self): 85 for backup in self.backups: 86 for image in backup: 87 try_remove(image) 88 89 90class TestIncrementalBackupBase(iotests.QMPTestCase): 91 def __init__(self, *args): 92 super(TestIncrementalBackupBase, self).__init__(*args) 93 self.bitmaps = list() 94 self.files = list() 95 self.drives = list() 96 self.vm = iotests.VM() 97 self.err_img = os.path.join(iotests.test_dir, 'err.%s' % iotests.imgfmt) 98 99 100 def setUp(self): 101 # Create a base image with a distinctive patterning 102 drive0 = self.add_node('drive0') 103 self.img_create(drive0['file'], drive0['fmt']) 104 self.vm.add_drive(drive0['file'], opts='node-name=node0') 105 self.write_default_pattern(drive0['file']) 106 self.vm.launch() 107 108 109 def write_default_pattern(self, target): 110 io_write_patterns(target, (('0x41', 0, 512), 111 ('0xd5', '1M', '32k'), 112 ('0xdc', '32M', '124k'))) 113 114 115 def add_node(self, node_id, fmt=iotests.imgfmt, path=None, backup=None): 116 if path is None: 117 path = os.path.join(iotests.test_dir, '%s.%s' % (node_id, fmt)) 118 if backup is None: 119 backup = os.path.join(iotests.test_dir, 120 '%s.full.backup.%s' % (node_id, fmt)) 121 122 self.drives.append({ 123 'id': node_id, 124 'file': path, 125 'backup': backup, 126 'fmt': fmt }) 127 return self.drives[-1] 128 129 130 def img_create(self, img, fmt=iotests.imgfmt, size='64M', 131 parent=None, parentFormat=None, **kwargs): 132 optargs = [] 133 for k,v in kwargs.items(): 134 optargs = optargs + ['-o', '%s=%s' % (k,v)] 135 args = ['create', '-f', fmt] + optargs + [img, size] 136 if parent: 137 if parentFormat is None: 138 parentFormat = fmt 139 args = args + ['-b', parent, '-F', parentFormat] 140 iotests.qemu_img(*args) 141 self.files.append(img) 142 143 144 def do_qmp_backup(self, error='Input/output error', **kwargs): 145 self.vm.cmd('drive-backup', **kwargs) 146 return self.wait_qmp_backup(kwargs['device'], error) 147 148 149 def ignore_job_status_change_events(self): 150 while True: 151 e = self.vm.event_wait(name="JOB_STATUS_CHANGE") 152 if e['data']['status'] == 'null': 153 break 154 155 def wait_qmp_backup(self, device, error='Input/output error'): 156 event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED", 157 match={'data': {'device': device}}) 158 self.assertNotEqual(event, None) 159 self.ignore_job_status_change_events() 160 161 try: 162 failure = self.dictpath(event, 'data/error') 163 except AssertionError: 164 # Backup succeeded. 165 self.assert_qmp(event, 'data/offset', event['data']['len']) 166 return True 167 else: 168 # Backup failed. 169 self.assert_qmp(event, 'data/error', error) 170 return False 171 172 173 def wait_qmp_backup_cancelled(self, device): 174 event = self.vm.event_wait(name='BLOCK_JOB_CANCELLED', 175 match={'data': {'device': device}}) 176 self.assertNotEqual(event, None) 177 self.ignore_job_status_change_events() 178 179 180 def create_anchor_backup(self, drive=None): 181 if drive is None: 182 drive = self.drives[-1] 183 res = self.do_qmp_backup(job_id=drive['id'], 184 device=drive['id'], sync='full', 185 format=drive['fmt'], target=drive['backup']) 186 self.assertTrue(res) 187 self.files.append(drive['backup']) 188 return drive['backup'] 189 190 191 def make_reference_backup(self, bitmap=None): 192 if bitmap is None: 193 bitmap = self.bitmaps[-1] 194 _, reference = bitmap.last_target() 195 res = self.do_qmp_backup(job_id=bitmap.drive['id'], 196 device=bitmap.drive['id'], sync='full', 197 format=bitmap.drive['fmt'], target=reference) 198 self.assertTrue(res) 199 200 201 def add_bitmap(self, name, drive, **kwargs): 202 bitmap = Bitmap(name, drive) 203 self.bitmaps.append(bitmap) 204 self.vm.cmd('block-dirty-bitmap-add', node=drive['id'], 205 name=bitmap.name, **kwargs) 206 return bitmap 207 208 209 def prepare_backup(self, bitmap=None, parent=None, **kwargs): 210 if bitmap is None: 211 bitmap = self.bitmaps[-1] 212 if parent is None: 213 parent, _ = bitmap.last_target() 214 215 target, _ = bitmap.new_target() 216 self.img_create(target, bitmap.drive['fmt'], parent=parent, 217 **kwargs) 218 return target 219 220 221 def create_incremental(self, bitmap=None, parent=None, 222 parentFormat=None, validate=True, 223 target=None): 224 if bitmap is None: 225 bitmap = self.bitmaps[-1] 226 if parent is None: 227 parent, _ = bitmap.last_target() 228 229 if target is None: 230 target = self.prepare_backup(bitmap, parent) 231 res = self.do_qmp_backup(job_id=bitmap.drive['id'], 232 device=bitmap.drive['id'], 233 sync='incremental', bitmap=bitmap.name, 234 format=bitmap.drive['fmt'], target=target, 235 mode='existing') 236 if not res: 237 bitmap.del_target(); 238 self.assertFalse(validate) 239 else: 240 self.make_reference_backup(bitmap) 241 return res 242 243 244 def check_backups(self): 245 for bitmap in self.bitmaps: 246 for incremental, reference in bitmap.backups: 247 self.assertTrue(iotests.compare_images(incremental, reference)) 248 last = bitmap.last_target()[0] 249 self.assertTrue(iotests.compare_images(last, bitmap.drive['file'])) 250 251 252 def hmp_io_writes(self, drive, patterns): 253 for pattern in patterns: 254 self.vm.hmp_qemu_io(drive, 'write -P%s %s %s' % pattern) 255 self.vm.hmp_qemu_io(drive, 'flush') 256 257 258 def do_incremental_simple(self, **kwargs): 259 self.create_anchor_backup() 260 self.add_bitmap('bitmap0', self.drives[0], **kwargs) 261 262 # Sanity: Create a "hollow" incremental backup 263 self.create_incremental() 264 # Three writes: One complete overwrite, one new segment, 265 # and one partial overlap. 266 self.hmp_io_writes(self.drives[0]['id'], (('0xab', 0, 512), 267 ('0xfe', '16M', '256k'), 268 ('0x64', '32736k', '64k'))) 269 self.create_incremental() 270 # Three more writes, one of each kind, like above 271 self.hmp_io_writes(self.drives[0]['id'], (('0x9a', 0, 512), 272 ('0x55', '8M', '352k'), 273 ('0x78', '15872k', '1M'))) 274 self.create_incremental() 275 self.vm.shutdown() 276 self.check_backups() 277 278 279 def tearDown(self): 280 self.vm.shutdown() 281 for bitmap in self.bitmaps: 282 bitmap.cleanup() 283 for filename in self.files: 284 try_remove(filename) 285 286 287 288class TestIncrementalBackup(TestIncrementalBackupBase): 289 def test_incremental_simple(self): 290 ''' 291 Test: Create and verify three incremental backups. 292 293 Create a bitmap and a full backup before VM execution begins, 294 then create a series of three incremental backups "during execution," 295 i.e.; after IO requests begin modifying the drive. 296 ''' 297 return self.do_incremental_simple() 298 299 300 def test_small_granularity(self): 301 ''' 302 Test: Create and verify backups made with a small granularity bitmap. 303 304 Perform the same test as test_incremental_simple, but with a granularity 305 of only 32KiB instead of the present default of 64KiB. 306 ''' 307 return self.do_incremental_simple(granularity=32768) 308 309 310 def test_large_granularity(self): 311 ''' 312 Test: Create and verify backups made with a large granularity bitmap. 313 314 Perform the same test as test_incremental_simple, but with a granularity 315 of 128KiB instead of the present default of 64KiB. 316 ''' 317 return self.do_incremental_simple(granularity=131072) 318 319 320 def test_larger_cluster_target(self): 321 ''' 322 Test: Create and verify backups made to a larger cluster size target. 323 324 With a default granularity of 64KiB, verify that backups made to a 325 larger cluster size target of 128KiB without a backing file works. 326 ''' 327 drive0 = self.drives[0] 328 329 # Create a cluster_size=128k full backup / "anchor" backup 330 self.img_create(drive0['backup'], cluster_size='128k') 331 self.assertTrue(self.do_qmp_backup(device=drive0['id'], sync='full', 332 format=drive0['fmt'], 333 target=drive0['backup'], 334 mode='existing')) 335 336 # Create bitmap and dirty it with some new writes. 337 # overwrite [32736, 32799] which will dirty bitmap clusters at 338 # 32M-64K and 32M. 32M+64K will be left undirtied. 339 bitmap0 = self.add_bitmap('bitmap0', drive0) 340 self.hmp_io_writes(drive0['id'], 341 (('0xab', 0, 512), 342 ('0xfe', '16M', '256k'), 343 ('0x64', '32736k', '64k'))) 344 # Check the dirty bitmap stats 345 self.assertTrue(self.vm.check_bitmap_status( 346 'node0', bitmap0.name, { 347 'name': 'bitmap0', 348 'count': 458752, 349 'granularity': 65536, 350 'persistent': False 351 })) 352 353 # Prepare a cluster_size=128k backup target without a backing file. 354 (target, _) = bitmap0.new_target() 355 self.img_create(target, bitmap0.drive['fmt'], cluster_size='128k') 356 357 # Perform Incremental Backup 358 self.assertTrue(self.do_qmp_backup(device=bitmap0.drive['id'], 359 sync='incremental', 360 bitmap=bitmap0.name, 361 format=bitmap0.drive['fmt'], 362 target=target, 363 mode='existing')) 364 self.make_reference_backup(bitmap0) 365 366 # Add the backing file, then compare and exit. 367 iotests.qemu_img('rebase', '-f', drive0['fmt'], '-u', '-b', 368 drive0['backup'], '-F', drive0['fmt'], target) 369 self.vm.shutdown() 370 self.check_backups() 371 372 373 def test_incremental_transaction(self): 374 '''Test: Verify backups made from transactionally created bitmaps. 375 376 Create a bitmap "before" VM execution begins, then create a second 377 bitmap AFTER writes have already occurred. Use transactions to create 378 a full backup and synchronize both bitmaps to this backup. 379 Create an incremental backup through both bitmaps and verify that 380 both backups match the current drive0 image. 381 ''' 382 383 drive0 = self.drives[0] 384 bitmap0 = self.add_bitmap('bitmap0', drive0) 385 self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), 386 ('0xfe', '16M', '256k'), 387 ('0x64', '32736k', '64k'))) 388 bitmap1 = self.add_bitmap('bitmap1', drive0) 389 390 self.vm.cmd('transaction', actions=[ 391 transaction_bitmap_clear(bitmap0.drive['id'], bitmap0.name), 392 transaction_bitmap_clear(bitmap1.drive['id'], bitmap1.name), 393 transaction_drive_backup(drive0['id'], drive0['backup'], 394 sync='full', format=drive0['fmt']) 395 ]) 396 self.wait_until_completed(drive0['id']) 397 self.files.append(drive0['backup']) 398 399 self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512), 400 ('0x55', '8M', '352k'), 401 ('0x78', '15872k', '1M'))) 402 # Both bitmaps should be correctly in sync. 403 self.create_incremental(bitmap0) 404 self.create_incremental(bitmap1) 405 self.vm.shutdown() 406 self.check_backups() 407 408 409 def do_transaction_failure_test(self, race=False): 410 # Create a second drive, with pattern: 411 drive1 = self.add_node('drive1') 412 self.img_create(drive1['file'], drive1['fmt']) 413 io_write_patterns(drive1['file'], (('0x14', 0, 512), 414 ('0x5d', '1M', '32k'), 415 ('0xcd', '32M', '124k'))) 416 417 # Create a blkdebug interface to this img as 'drive1' 418 self.vm.cmd('blockdev-add', 419 node_name=drive1['id'], 420 driver=drive1['fmt'], 421 file={ 422 'driver': 'blkdebug', 423 'image': { 424 'driver': 'file', 425 'filename': drive1['file'] 426 }, 427 'set-state': [{ 428 'event': 'flush_to_disk', 429 'state': 1, 430 'new_state': 2 431 }], 432 'inject-error': [{ 433 'event': 'read_aio', 434 'errno': 5, 435 'state': 2, 436 'immediately': False, 437 'once': True 438 }], 439 } 440 ) 441 442 # Create bitmaps and full backups for both drives 443 drive0 = self.drives[0] 444 dr0bm0 = self.add_bitmap('bitmap0', drive0) 445 dr1bm0 = self.add_bitmap('bitmap0', drive1) 446 self.create_anchor_backup(drive0) 447 self.create_anchor_backup(drive1) 448 self.assert_no_active_block_jobs() 449 self.assertFalse(self.vm.get_qmp_events(wait=False)) 450 451 # Emulate some writes 452 if not race: 453 self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), 454 ('0xfe', '16M', '256k'), 455 ('0x64', '32736k', '64k'))) 456 self.hmp_io_writes(drive1['id'], (('0xba', 0, 512), 457 ('0xef', '16M', '256k'), 458 ('0x46', '32736k', '64k'))) 459 460 # Create incremental backup targets 461 target0 = self.prepare_backup(dr0bm0) 462 target1 = self.prepare_backup(dr1bm0) 463 464 # Ask for a new incremental backup per-each drive, 465 # expecting drive1's backup to fail. In the 'race' test, 466 # we expect drive1 to attempt to cancel the empty drive0 job. 467 transaction = [ 468 transaction_drive_backup(drive0['id'], target0, sync='incremental', 469 format=drive0['fmt'], mode='existing', 470 bitmap=dr0bm0.name), 471 transaction_drive_backup(drive1['id'], target1, sync='incremental', 472 format=drive1['fmt'], mode='existing', 473 bitmap=dr1bm0.name) 474 ] 475 self.vm.cmd('transaction', actions=transaction, 476 properties={'completion-mode': 'grouped'} ) 477 478 # Observe that drive0's backup is cancelled and drive1 completes with 479 # an error. 480 self.wait_qmp_backup_cancelled(drive0['id']) 481 self.assertFalse(self.wait_qmp_backup(drive1['id'])) 482 error = self.vm.event_wait('BLOCK_JOB_ERROR') 483 self.assert_qmp(error, 'data', {'device': drive1['id'], 484 'action': 'report', 485 'operation': 'read'}) 486 self.assertFalse(self.vm.get_qmp_events(wait=False)) 487 self.assert_no_active_block_jobs() 488 489 # Delete drive0's successful target and eliminate our record of the 490 # unsuccessful drive1 target. 491 dr0bm0.del_target() 492 dr1bm0.del_target() 493 if race: 494 # Don't re-run the transaction, we only wanted to test the race. 495 self.vm.shutdown() 496 return 497 498 # Re-run the same transaction: 499 target0 = self.prepare_backup(dr0bm0) 500 target1 = self.prepare_backup(dr1bm0) 501 502 # Re-run the exact same transaction. 503 self.vm.cmd('transaction', actions=transaction, 504 properties={'completion-mode':'grouped'}) 505 506 # Both should complete successfully this time. 507 self.assertTrue(self.wait_qmp_backup(drive0['id'])) 508 self.assertTrue(self.wait_qmp_backup(drive1['id'])) 509 self.make_reference_backup(dr0bm0) 510 self.make_reference_backup(dr1bm0) 511 self.assertFalse(self.vm.get_qmp_events(wait=False)) 512 self.assert_no_active_block_jobs() 513 514 # And the images should of course validate. 515 self.vm.shutdown() 516 self.check_backups() 517 518 def test_transaction_failure(self): 519 '''Test: Verify backups made from a transaction that partially fails. 520 521 Add a second drive with its own unique pattern, and add a bitmap to each 522 drive. Use blkdebug to interfere with the backup on just one drive and 523 attempt to create a coherent incremental backup across both drives. 524 525 verify a failure in one but not both, then delete the failed stubs and 526 re-run the same transaction. 527 528 verify that both incrementals are created successfully. 529 ''' 530 self.do_transaction_failure_test() 531 532 def test_transaction_failure_race(self): 533 '''Test: Verify that transactions with jobs that have no data to 534 transfer do not cause race conditions in the cancellation of the entire 535 transaction job group. 536 ''' 537 self.do_transaction_failure_test(race=True) 538 539 540 def test_sync_dirty_bitmap_missing(self): 541 self.assert_no_active_block_jobs() 542 self.files.append(self.err_img) 543 result = self.vm.qmp('drive-backup', device=self.drives[0]['id'], 544 sync='incremental', format=self.drives[0]['fmt'], 545 target=self.err_img) 546 self.assert_qmp(result, 'error/class', 'GenericError') 547 548 549 def test_sync_dirty_bitmap_not_found(self): 550 self.assert_no_active_block_jobs() 551 self.files.append(self.err_img) 552 result = self.vm.qmp('drive-backup', device=self.drives[0]['id'], 553 sync='incremental', bitmap='unknown', 554 format=self.drives[0]['fmt'], target=self.err_img) 555 self.assert_qmp(result, 'error/class', 'GenericError') 556 557 558 def test_sync_dirty_bitmap_bad_granularity(self): 559 ''' 560 Test: Test what happens if we provide an improper granularity. 561 562 The granularity must always be a power of 2. 563 ''' 564 self.assert_no_active_block_jobs() 565 self.assertRaises(ExecuteError, self.add_bitmap, 566 'bitmap0', self.drives[0], 567 granularity=64000) 568 569 def test_growing_before_backup(self): 570 ''' 571 Test: Add a bitmap, truncate the image, write past the old 572 end, do a backup. 573 574 Incremental backup should not ignore dirty bits past the old 575 image end. 576 ''' 577 self.assert_no_active_block_jobs() 578 579 self.create_anchor_backup() 580 581 self.add_bitmap('bitmap0', self.drives[0]) 582 583 self.vm.cmd('block_resize', device=self.drives[0]['id'], 584 size=(65 * 1048576)) 585 586 # Dirty the image past the old end 587 self.vm.hmp_qemu_io(self.drives[0]['id'], 'write 64M 64k') 588 589 target = self.prepare_backup(size='65M') 590 self.create_incremental(target=target) 591 592 self.vm.shutdown() 593 self.check_backups() 594 595 596class TestIncrementalBackupBlkdebug(TestIncrementalBackupBase): 597 '''Incremental backup tests that utilize a BlkDebug filter on drive0.''' 598 599 def setUp(self): 600 drive0 = self.add_node('drive0') 601 self.img_create(drive0['file'], drive0['fmt']) 602 self.write_default_pattern(drive0['file']) 603 self.vm.launch() 604 605 def test_incremental_failure(self): 606 '''Test: Verify backups made after a failure are correct. 607 608 Simulate a failure during an incremental backup block job, 609 emulate additional writes, then create another incremental backup 610 afterwards and verify that the backup created is correct. 611 ''' 612 613 drive0 = self.drives[0] 614 self.vm.cmd('blockdev-add', 615 node_name=drive0['id'], 616 driver=drive0['fmt'], 617 file={ 618 'driver': 'blkdebug', 619 'image': { 620 'driver': 'file', 621 'filename': drive0['file'] 622 }, 623 'set-state': [{ 624 'event': 'flush_to_disk', 625 'state': 1, 626 'new_state': 2 627 }], 628 'inject-error': [{ 629 'event': 'read_aio', 630 'errno': 5, 631 'state': 2, 632 'immediately': False, 633 'once': True 634 }], 635 } 636 ) 637 638 self.create_anchor_backup(drive0) 639 self.add_bitmap('bitmap0', drive0) 640 # Note: at this point, during a normal execution, 641 # Assume that the VM resumes and begins issuing IO requests here. 642 643 self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), 644 ('0xfe', '16M', '256k'), 645 ('0x64', '32736k', '64k'))) 646 647 result = self.create_incremental(validate=False) 648 self.assertFalse(result) 649 self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512), 650 ('0x55', '8M', '352k'), 651 ('0x78', '15872k', '1M'))) 652 self.create_incremental() 653 self.vm.shutdown() 654 self.check_backups() 655 656 def test_incremental_pause(self): 657 """ 658 Test an incremental backup that errors into a pause and is resumed. 659 """ 660 661 drive0 = self.drives[0] 662 # NB: The blkdebug script here looks for a "flush, read" pattern. 663 # The flush occurs in hmp_io_writes, and the read during the block job. 664 self.vm.cmd('blockdev-add', 665 node_name=drive0['id'], 666 driver=drive0['fmt'], 667 file={ 668 'driver': 'blkdebug', 669 'image': { 670 'driver': 'file', 671 'filename': drive0['file'] 672 }, 673 'set-state': [{ 674 'event': 'flush_to_disk', 675 'state': 1, 676 'new_state': 2 677 }], 678 'inject-error': [{ 679 'event': 'read_aio', 680 'errno': 5, 681 'state': 2, 682 'immediately': False, 683 'once': True 684 }], 685 }) 686 self.create_anchor_backup(drive0) 687 bitmap = self.add_bitmap('bitmap0', drive0) 688 689 # Emulate guest activity 690 self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), 691 ('0xfe', '16M', '256k'), 692 ('0x64', '32736k', '64k'))) 693 694 # Bitmap Status Check 695 self.assertTrue(self.vm.check_bitmap_status( 696 drive0['id'], bitmap.name, { 697 'count': 458752, 698 'granularity': 65536, 699 'busy': False, 700 'recording': True 701 })) 702 703 # Start backup 704 parent, _ = bitmap.last_target() 705 target = self.prepare_backup(bitmap, parent) 706 self.vm.cmd('drive-backup', 707 job_id=bitmap.drive['id'], 708 device=bitmap.drive['id'], 709 sync='incremental', 710 bitmap=bitmap.name, 711 format=bitmap.drive['fmt'], 712 target=target, 713 mode='existing', 714 on_source_error='stop') 715 716 # Wait for the error 717 event = self.vm.event_wait(name="BLOCK_JOB_ERROR", 718 match={"data":{"device":bitmap.drive['id']}}) 719 self.assert_qmp(event, 'data', {'device': bitmap.drive['id'], 720 'action': 'stop', 721 'operation': 'read'}) 722 723 # Bitmap Status Check 724 self.assertTrue(self.vm.check_bitmap_status( 725 drive0['id'], bitmap.name, { 726 'count': 458752, 727 'granularity': 65536, 728 'busy': True, 729 'recording': True 730 })) 731 732 # Resume and check incremental backup for consistency 733 self.vm.cmd('block-job-resume', device=bitmap.drive['id']) 734 self.wait_qmp_backup(bitmap.drive['id']) 735 736 # Bitmap Status Check 737 self.assertTrue(self.vm.check_bitmap_status( 738 drive0['id'], bitmap.name, { 739 'count': 0, 740 'granularity': 65536, 741 'busy': False, 742 'recording': True 743 })) 744 745 # Finalize / Cleanup 746 self.make_reference_backup(bitmap) 747 self.vm.shutdown() 748 self.check_backups() 749 750 751if __name__ == '__main__': 752 iotests.main(supported_fmts=['qcow2'], 753 supported_protocols=['file']) 754