xref: /openbmc/qemu/tests/qemu-iotests/124 (revision a3d71595)
19f7264f5SJohn Snow#!/usr/bin/env python
29f7264f5SJohn Snow#
39f7264f5SJohn Snow# Tests for incremental drive-backup
49f7264f5SJohn Snow#
59f7264f5SJohn Snow# Copyright (C) 2015 John Snow for Red Hat, Inc.
69f7264f5SJohn Snow#
79f7264f5SJohn Snow# Based on 056.
89f7264f5SJohn Snow#
99f7264f5SJohn Snow# This program is free software; you can redistribute it and/or modify
109f7264f5SJohn Snow# it under the terms of the GNU General Public License as published by
119f7264f5SJohn Snow# the Free Software Foundation; either version 2 of the License, or
129f7264f5SJohn Snow# (at your option) any later version.
139f7264f5SJohn Snow#
149f7264f5SJohn Snow# This program is distributed in the hope that it will be useful,
159f7264f5SJohn Snow# but WITHOUT ANY WARRANTY; without even the implied warranty of
169f7264f5SJohn Snow# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
179f7264f5SJohn Snow# GNU General Public License for more details.
189f7264f5SJohn Snow#
199f7264f5SJohn Snow# You should have received a copy of the GNU General Public License
209f7264f5SJohn Snow# along with this program.  If not, see <http://www.gnu.org/licenses/>.
219f7264f5SJohn Snow#
229f7264f5SJohn Snow
239f7264f5SJohn Snowimport os
249f7264f5SJohn Snowimport iotests
259f7264f5SJohn Snow
269f7264f5SJohn Snow
279f7264f5SJohn Snowdef io_write_patterns(img, patterns):
289f7264f5SJohn Snow    for pattern in patterns:
299f7264f5SJohn Snow        iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img)
309f7264f5SJohn Snow
319f7264f5SJohn Snow
32*a3d71595SJohn Snowdef try_remove(img):
33*a3d71595SJohn Snow    try:
34*a3d71595SJohn Snow        os.remove(img)
35*a3d71595SJohn Snow    except OSError:
36*a3d71595SJohn Snow        pass
37*a3d71595SJohn Snow
38*a3d71595SJohn Snow
39*a3d71595SJohn Snowclass Bitmap:
40*a3d71595SJohn Snow    def __init__(self, name, drive):
41*a3d71595SJohn Snow        self.name = name
42*a3d71595SJohn Snow        self.drive = drive
43*a3d71595SJohn Snow        self.num = 0
44*a3d71595SJohn Snow        self.backups = list()
45*a3d71595SJohn Snow
46*a3d71595SJohn Snow    def base_target(self):
47*a3d71595SJohn Snow        return (self.drive['backup'], None)
48*a3d71595SJohn Snow
49*a3d71595SJohn Snow    def new_target(self, num=None):
50*a3d71595SJohn Snow        if num is None:
51*a3d71595SJohn Snow            num = self.num
52*a3d71595SJohn Snow        self.num = num + 1
53*a3d71595SJohn Snow        base = os.path.join(iotests.test_dir,
54*a3d71595SJohn Snow                            "%s.%s." % (self.drive['id'], self.name))
55*a3d71595SJohn Snow        suff = "%i.%s" % (num, self.drive['fmt'])
56*a3d71595SJohn Snow        target = base + "inc" + suff
57*a3d71595SJohn Snow        reference = base + "ref" + suff
58*a3d71595SJohn Snow        self.backups.append((target, reference))
59*a3d71595SJohn Snow        return (target, reference)
60*a3d71595SJohn Snow
61*a3d71595SJohn Snow    def last_target(self):
62*a3d71595SJohn Snow        if self.backups:
63*a3d71595SJohn Snow            return self.backups[-1]
64*a3d71595SJohn Snow        return self.base_target()
65*a3d71595SJohn Snow
66*a3d71595SJohn Snow    def del_target(self):
67*a3d71595SJohn Snow        for image in self.backups.pop():
68*a3d71595SJohn Snow            try_remove(image)
69*a3d71595SJohn Snow        self.num -= 1
70*a3d71595SJohn Snow
71*a3d71595SJohn Snow    def cleanup(self):
72*a3d71595SJohn Snow        for backup in self.backups:
73*a3d71595SJohn Snow            for image in backup:
74*a3d71595SJohn Snow                try_remove(image)
75*a3d71595SJohn Snow
76*a3d71595SJohn Snow
779f7264f5SJohn Snowclass TestIncrementalBackup(iotests.QMPTestCase):
789f7264f5SJohn Snow    def setUp(self):
799f7264f5SJohn Snow        self.bitmaps = list()
809f7264f5SJohn Snow        self.files = list()
819f7264f5SJohn Snow        self.drives = list()
829f7264f5SJohn Snow        self.vm = iotests.VM()
839f7264f5SJohn Snow        self.err_img = os.path.join(iotests.test_dir, 'err.%s' % iotests.imgfmt)
849f7264f5SJohn Snow
859f7264f5SJohn Snow        # Create a base image with a distinctive patterning
869f7264f5SJohn Snow        drive0 = self.add_node('drive0')
879f7264f5SJohn Snow        self.img_create(drive0['file'], drive0['fmt'])
889f7264f5SJohn Snow        self.vm.add_drive(drive0['file'])
899f7264f5SJohn Snow        io_write_patterns(drive0['file'], (('0x41', 0, 512),
909f7264f5SJohn Snow                                           ('0xd5', '1M', '32k'),
919f7264f5SJohn Snow                                           ('0xdc', '32M', '124k')))
929f7264f5SJohn Snow        self.vm.launch()
939f7264f5SJohn Snow
949f7264f5SJohn Snow
959f7264f5SJohn Snow    def add_node(self, node_id, fmt=iotests.imgfmt, path=None, backup=None):
969f7264f5SJohn Snow        if path is None:
979f7264f5SJohn Snow            path = os.path.join(iotests.test_dir, '%s.%s' % (node_id, fmt))
989f7264f5SJohn Snow        if backup is None:
999f7264f5SJohn Snow            backup = os.path.join(iotests.test_dir,
1009f7264f5SJohn Snow                                  '%s.full.backup.%s' % (node_id, fmt))
1019f7264f5SJohn Snow
1029f7264f5SJohn Snow        self.drives.append({
1039f7264f5SJohn Snow            'id': node_id,
1049f7264f5SJohn Snow            'file': path,
1059f7264f5SJohn Snow            'backup': backup,
1069f7264f5SJohn Snow            'fmt': fmt })
1079f7264f5SJohn Snow        return self.drives[-1]
1089f7264f5SJohn Snow
1099f7264f5SJohn Snow
1109f7264f5SJohn Snow    def img_create(self, img, fmt=iotests.imgfmt, size='64M',
1119f7264f5SJohn Snow                   parent=None, parentFormat=None):
1129f7264f5SJohn Snow        if parent:
1139f7264f5SJohn Snow            if parentFormat is None:
1149f7264f5SJohn Snow                parentFormat = fmt
1159f7264f5SJohn Snow            iotests.qemu_img('create', '-f', fmt, img, size,
1169f7264f5SJohn Snow                             '-b', parent, '-F', parentFormat)
1179f7264f5SJohn Snow        else:
1189f7264f5SJohn Snow            iotests.qemu_img('create', '-f', fmt, img, size)
1199f7264f5SJohn Snow        self.files.append(img)
1209f7264f5SJohn Snow
121*a3d71595SJohn Snow
122*a3d71595SJohn Snow    def do_qmp_backup(self, error='Input/output error', **kwargs):
123*a3d71595SJohn Snow        res = self.vm.qmp('drive-backup', **kwargs)
124*a3d71595SJohn Snow        self.assert_qmp(res, 'return', {})
125*a3d71595SJohn Snow
126*a3d71595SJohn Snow        event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED",
127*a3d71595SJohn Snow                                   match={'data': {'device': kwargs['device']}})
128*a3d71595SJohn Snow        self.assertIsNotNone(event)
129*a3d71595SJohn Snow
130*a3d71595SJohn Snow        try:
131*a3d71595SJohn Snow            failure = self.dictpath(event, 'data/error')
132*a3d71595SJohn Snow        except AssertionError:
133*a3d71595SJohn Snow            # Backup succeeded.
134*a3d71595SJohn Snow            self.assert_qmp(event, 'data/offset', event['data']['len'])
135*a3d71595SJohn Snow            return True
136*a3d71595SJohn Snow        else:
137*a3d71595SJohn Snow            # Backup failed.
138*a3d71595SJohn Snow            self.assert_qmp(event, 'data/error', error)
139*a3d71595SJohn Snow            return False
140*a3d71595SJohn Snow
141*a3d71595SJohn Snow
142*a3d71595SJohn Snow    def create_anchor_backup(self, drive=None):
143*a3d71595SJohn Snow        if drive is None:
144*a3d71595SJohn Snow            drive = self.drives[-1]
145*a3d71595SJohn Snow        res = self.do_qmp_backup(device=drive['id'], sync='full',
146*a3d71595SJohn Snow                                 format=drive['fmt'], target=drive['backup'])
147*a3d71595SJohn Snow        self.assertTrue(res)
148*a3d71595SJohn Snow        self.files.append(drive['backup'])
149*a3d71595SJohn Snow        return drive['backup']
150*a3d71595SJohn Snow
151*a3d71595SJohn Snow
152*a3d71595SJohn Snow    def make_reference_backup(self, bitmap=None):
153*a3d71595SJohn Snow        if bitmap is None:
154*a3d71595SJohn Snow            bitmap = self.bitmaps[-1]
155*a3d71595SJohn Snow        _, reference = bitmap.last_target()
156*a3d71595SJohn Snow        res = self.do_qmp_backup(device=bitmap.drive['id'], sync='full',
157*a3d71595SJohn Snow                                 format=bitmap.drive['fmt'], target=reference)
158*a3d71595SJohn Snow        self.assertTrue(res)
159*a3d71595SJohn Snow
160*a3d71595SJohn Snow
161*a3d71595SJohn Snow    def add_bitmap(self, name, drive):
162*a3d71595SJohn Snow        bitmap = Bitmap(name, drive)
163*a3d71595SJohn Snow        self.bitmaps.append(bitmap)
164*a3d71595SJohn Snow        result = self.vm.qmp('block-dirty-bitmap-add', node=drive['id'],
165*a3d71595SJohn Snow                             name=bitmap.name)
166*a3d71595SJohn Snow        self.assert_qmp(result, 'return', {})
167*a3d71595SJohn Snow        return bitmap
168*a3d71595SJohn Snow
169*a3d71595SJohn Snow
170*a3d71595SJohn Snow    def prepare_backup(self, bitmap=None, parent=None):
171*a3d71595SJohn Snow        if bitmap is None:
172*a3d71595SJohn Snow            bitmap = self.bitmaps[-1]
173*a3d71595SJohn Snow        if parent is None:
174*a3d71595SJohn Snow            parent, _ = bitmap.last_target()
175*a3d71595SJohn Snow
176*a3d71595SJohn Snow        target, _ = bitmap.new_target()
177*a3d71595SJohn Snow        self.img_create(target, bitmap.drive['fmt'], parent=parent)
178*a3d71595SJohn Snow        return target
179*a3d71595SJohn Snow
180*a3d71595SJohn Snow
181*a3d71595SJohn Snow    def create_incremental(self, bitmap=None, parent=None,
182*a3d71595SJohn Snow                           parentFormat=None, validate=True):
183*a3d71595SJohn Snow        if bitmap is None:
184*a3d71595SJohn Snow            bitmap = self.bitmaps[-1]
185*a3d71595SJohn Snow        if parent is None:
186*a3d71595SJohn Snow            parent, _ = bitmap.last_target()
187*a3d71595SJohn Snow
188*a3d71595SJohn Snow        target = self.prepare_backup(bitmap, parent)
189*a3d71595SJohn Snow        res = self.do_qmp_backup(device=bitmap.drive['id'],
190*a3d71595SJohn Snow                                 sync='dirty-bitmap', bitmap=bitmap.name,
191*a3d71595SJohn Snow                                 format=bitmap.drive['fmt'], target=target,
192*a3d71595SJohn Snow                                 mode='existing')
193*a3d71595SJohn Snow        if not res:
194*a3d71595SJohn Snow            bitmap.del_target();
195*a3d71595SJohn Snow            self.assertFalse(validate)
196*a3d71595SJohn Snow        else:
197*a3d71595SJohn Snow            self.make_reference_backup(bitmap)
198*a3d71595SJohn Snow        return res
199*a3d71595SJohn Snow
200*a3d71595SJohn Snow
201*a3d71595SJohn Snow    def check_backups(self):
202*a3d71595SJohn Snow        for bitmap in self.bitmaps:
203*a3d71595SJohn Snow            for incremental, reference in bitmap.backups:
204*a3d71595SJohn Snow                self.assertTrue(iotests.compare_images(incremental, reference))
205*a3d71595SJohn Snow            last = bitmap.last_target()[0]
206*a3d71595SJohn Snow            self.assertTrue(iotests.compare_images(last, bitmap.drive['file']))
207*a3d71595SJohn Snow
208*a3d71595SJohn Snow
209*a3d71595SJohn Snow    def hmp_io_writes(self, drive, patterns):
210*a3d71595SJohn Snow        for pattern in patterns:
211*a3d71595SJohn Snow            self.vm.hmp_qemu_io(drive, 'write -P%s %s %s' % pattern)
212*a3d71595SJohn Snow        self.vm.hmp_qemu_io(drive, 'flush')
213*a3d71595SJohn Snow
214*a3d71595SJohn Snow
215*a3d71595SJohn Snow    def test_incremental_simple(self):
216*a3d71595SJohn Snow        '''
217*a3d71595SJohn Snow        Test: Create and verify three incremental backups.
218*a3d71595SJohn Snow
219*a3d71595SJohn Snow        Create a bitmap and a full backup before VM execution begins,
220*a3d71595SJohn Snow        then create a series of three incremental backups "during execution,"
221*a3d71595SJohn Snow        i.e.; after IO requests begin modifying the drive.
222*a3d71595SJohn Snow        '''
223*a3d71595SJohn Snow        self.create_anchor_backup()
224*a3d71595SJohn Snow        self.add_bitmap('bitmap0', self.drives[0])
225*a3d71595SJohn Snow
226*a3d71595SJohn Snow        # Sanity: Create a "hollow" incremental backup
227*a3d71595SJohn Snow        self.create_incremental()
228*a3d71595SJohn Snow        # Three writes: One complete overwrite, one new segment,
229*a3d71595SJohn Snow        # and one partial overlap.
230*a3d71595SJohn Snow        self.hmp_io_writes(self.drives[0]['id'], (('0xab', 0, 512),
231*a3d71595SJohn Snow                                                  ('0xfe', '16M', '256k'),
232*a3d71595SJohn Snow                                                  ('0x64', '32736k', '64k')))
233*a3d71595SJohn Snow        self.create_incremental()
234*a3d71595SJohn Snow        # Three more writes, one of each kind, like above
235*a3d71595SJohn Snow        self.hmp_io_writes(self.drives[0]['id'], (('0x9a', 0, 512),
236*a3d71595SJohn Snow                                                  ('0x55', '8M', '352k'),
237*a3d71595SJohn Snow                                                  ('0x78', '15872k', '1M')))
238*a3d71595SJohn Snow        self.create_incremental()
239*a3d71595SJohn Snow        self.vm.shutdown()
240*a3d71595SJohn Snow        self.check_backups()
241*a3d71595SJohn Snow
242*a3d71595SJohn Snow
2439f7264f5SJohn Snow    def test_sync_dirty_bitmap_missing(self):
2449f7264f5SJohn Snow        self.assert_no_active_block_jobs()
2459f7264f5SJohn Snow        self.files.append(self.err_img)
2469f7264f5SJohn Snow        result = self.vm.qmp('drive-backup', device=self.drives[0]['id'],
2479f7264f5SJohn Snow                             sync='dirty-bitmap', format=self.drives[0]['fmt'],
2489f7264f5SJohn Snow                             target=self.err_img)
2499f7264f5SJohn Snow        self.assert_qmp(result, 'error/class', 'GenericError')
2509f7264f5SJohn Snow
2519f7264f5SJohn Snow
2529f7264f5SJohn Snow    def test_sync_dirty_bitmap_not_found(self):
2539f7264f5SJohn Snow        self.assert_no_active_block_jobs()
2549f7264f5SJohn Snow        self.files.append(self.err_img)
2559f7264f5SJohn Snow        result = self.vm.qmp('drive-backup', device=self.drives[0]['id'],
2569f7264f5SJohn Snow                             sync='dirty-bitmap', bitmap='unknown',
2579f7264f5SJohn Snow                             format=self.drives[0]['fmt'], target=self.err_img)
2589f7264f5SJohn Snow        self.assert_qmp(result, 'error/class', 'GenericError')
2599f7264f5SJohn Snow
2609f7264f5SJohn Snow
2619f7264f5SJohn Snow    def tearDown(self):
2629f7264f5SJohn Snow        self.vm.shutdown()
263*a3d71595SJohn Snow        for bitmap in self.bitmaps:
264*a3d71595SJohn Snow            bitmap.cleanup()
2659f7264f5SJohn Snow        for filename in self.files:
266*a3d71595SJohn Snow            try_remove(filename)
2679f7264f5SJohn Snow
2689f7264f5SJohn Snow
2699f7264f5SJohn Snowif __name__ == '__main__':
2709f7264f5SJohn Snow    iotests.main(supported_fmts=['qcow2'])
271