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