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