3 # Tests for incremental drive-backup
5 # Copyright (C) 2015 John Snow for Red Hat, Inc.
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.
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.
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/>.
27 def io_write_patterns(img
, patterns
):
28 for pattern
in patterns
:
29 iotests
.qemu_io('-c', 'write -P%s %s %s' % pattern
, img
)
40 def __init__(self
, name
, drive
):
46 def base_target(self
):
47 return (self
.drive
['backup'], None)
49 def new_target(self
, num
=None):
53 base
= os
.path
.join(iotests
.test_dir
,
54 "%s.%s." % (self
.drive
['id'], self
.name
))
55 suff
= "%i.%s" % (num
, self
.drive
['fmt'])
56 target
= base
+ "inc" + suff
57 reference
= base
+ "ref" + suff
58 self
.backups
.append((target
, reference
))
59 return (target
, reference
)
61 def last_target(self
):
63 return self
.backups
[-1]
64 return self
.base_target()
67 for image
in self
.backups
.pop():
72 for backup
in self
.backups
:
77 class TestIncrementalBackup(iotests
.QMPTestCase
):
82 self
.vm
= iotests
.VM()
83 self
.err_img
= os
.path
.join(iotests
.test_dir
, 'err.%s' % iotests
.imgfmt
)
85 # Create a base image with a distinctive patterning
86 drive0
= self
.add_node('drive0')
87 self
.img_create(drive0
['file'], drive0
['fmt'])
88 self
.vm
.add_drive(drive0
['file'])
89 io_write_patterns(drive0
['file'], (('0x41', 0, 512),
90 ('0xd5', '1M', '32k'),
91 ('0xdc', '32M', '124k')))
95 def add_node(self
, node_id
, fmt
=iotests
.imgfmt
, path
=None, backup
=None):
97 path
= os
.path
.join(iotests
.test_dir
, '%s.%s' % (node_id
, fmt
))
99 backup
= os
.path
.join(iotests
.test_dir
,
100 '%s.full.backup.%s' % (node_id
, fmt
))
107 return self
.drives
[-1]
110 def img_create(self
, img
, fmt
=iotests
.imgfmt
, size
='64M',
111 parent
=None, parentFormat
=None):
113 if parentFormat
is None:
115 iotests
.qemu_img('create', '-f', fmt
, img
, size
,
116 '-b', parent
, '-F', parentFormat
)
118 iotests
.qemu_img('create', '-f', fmt
, img
, size
)
119 self
.files
.append(img
)
122 def do_qmp_backup(self
, error
='Input/output error', **kwargs
):
123 res
= self
.vm
.qmp('drive-backup', **kwargs
)
124 self
.assert_qmp(res
, 'return', {})
126 event
= self
.vm
.event_wait(name
="BLOCK_JOB_COMPLETED",
127 match
={'data': {'device': kwargs
['device']}})
128 self
.assertNotEqual(event
, None)
131 failure
= self
.dictpath(event
, 'data/error')
132 except AssertionError:
134 self
.assert_qmp(event
, 'data/offset', event
['data']['len'])
138 self
.assert_qmp(event
, 'data/error', error
)
142 def create_anchor_backup(self
, drive
=None):
144 drive
= self
.drives
[-1]
145 res
= self
.do_qmp_backup(device
=drive
['id'], sync
='full',
146 format
=drive
['fmt'], target
=drive
['backup'])
148 self
.files
.append(drive
['backup'])
149 return drive
['backup']
152 def make_reference_backup(self
, bitmap
=None):
154 bitmap
= self
.bitmaps
[-1]
155 _
, reference
= bitmap
.last_target()
156 res
= self
.do_qmp_backup(device
=bitmap
.drive
['id'], sync
='full',
157 format
=bitmap
.drive
['fmt'], target
=reference
)
161 def add_bitmap(self
, name
, drive
, **kwargs
):
162 bitmap
= Bitmap(name
, drive
)
163 self
.bitmaps
.append(bitmap
)
164 result
= self
.vm
.qmp('block-dirty-bitmap-add', node
=drive
['id'],
165 name
=bitmap
.name
, **kwargs
)
166 self
.assert_qmp(result
, 'return', {})
170 def prepare_backup(self
, bitmap
=None, parent
=None):
172 bitmap
= self
.bitmaps
[-1]
174 parent
, _
= bitmap
.last_target()
176 target
, _
= bitmap
.new_target()
177 self
.img_create(target
, bitmap
.drive
['fmt'], parent
=parent
)
181 def create_incremental(self
, bitmap
=None, parent
=None,
182 parentFormat
=None, validate
=True):
184 bitmap
= self
.bitmaps
[-1]
186 parent
, _
= bitmap
.last_target()
188 target
= self
.prepare_backup(bitmap
, parent
)
189 res
= self
.do_qmp_backup(device
=bitmap
.drive
['id'],
190 sync
='incremental', bitmap
=bitmap
.name
,
191 format
=bitmap
.drive
['fmt'], target
=target
,
195 self
.assertFalse(validate
)
197 self
.make_reference_backup(bitmap
)
201 def check_backups(self
):
202 for bitmap
in self
.bitmaps
:
203 for incremental
, reference
in bitmap
.backups
:
204 self
.assertTrue(iotests
.compare_images(incremental
, reference
))
205 last
= bitmap
.last_target()[0]
206 self
.assertTrue(iotests
.compare_images(last
, bitmap
.drive
['file']))
209 def hmp_io_writes(self
, drive
, patterns
):
210 for pattern
in patterns
:
211 self
.vm
.hmp_qemu_io(drive
, 'write -P%s %s %s' % pattern
)
212 self
.vm
.hmp_qemu_io(drive
, 'flush')
215 def do_incremental_simple(self
, **kwargs
):
216 self
.create_anchor_backup()
217 self
.add_bitmap('bitmap0', self
.drives
[0], **kwargs
)
219 # Sanity: Create a "hollow" incremental backup
220 self
.create_incremental()
221 # Three writes: One complete overwrite, one new segment,
222 # and one partial overlap.
223 self
.hmp_io_writes(self
.drives
[0]['id'], (('0xab', 0, 512),
224 ('0xfe', '16M', '256k'),
225 ('0x64', '32736k', '64k')))
226 self
.create_incremental()
227 # Three more writes, one of each kind, like above
228 self
.hmp_io_writes(self
.drives
[0]['id'], (('0x9a', 0, 512),
229 ('0x55', '8M', '352k'),
230 ('0x78', '15872k', '1M')))
231 self
.create_incremental()
236 def test_incremental_simple(self
):
238 Test: Create and verify three incremental backups.
240 Create a bitmap and a full backup before VM execution begins,
241 then create a series of three incremental backups "during execution,"
242 i.e.; after IO requests begin modifying the drive.
244 return self
.do_incremental_simple()
247 def test_small_granularity(self
):
249 Test: Create and verify backups made with a small granularity bitmap.
251 Perform the same test as test_incremental_simple, but with a granularity
252 of only 32KiB instead of the present default of 64KiB.
254 return self
.do_incremental_simple(granularity
=32768)
257 def test_large_granularity(self
):
259 Test: Create and verify backups made with a large granularity bitmap.
261 Perform the same test as test_incremental_simple, but with a granularity
262 of 128KiB instead of the present default of 64KiB.
264 return self
.do_incremental_simple(granularity
=131072)
267 def test_incremental_failure(self
):
268 '''Test: Verify backups made after a failure are correct.
270 Simulate a failure during an incremental backup block job,
271 emulate additional writes, then create another incremental backup
272 afterwards and verify that the backup created is correct.
275 # Create a blkdebug interface to this img as 'drive1',
276 # but don't actually create a new image.
277 drive1
= self
.add_node('drive1', self
.drives
[0]['fmt'],
278 path
=self
.drives
[0]['file'],
279 backup
=self
.drives
[0]['backup'])
280 result
= self
.vm
.qmp('blockdev-add', options
={
282 'driver': drive1
['fmt'],
284 'driver': 'blkdebug',
287 'filename': drive1
['file']
290 'event': 'flush_to_disk',
298 'immediately': False,
303 self
.assert_qmp(result
, 'return', {})
305 self
.create_anchor_backup(self
.drives
[0])
306 self
.add_bitmap('bitmap0', drive1
)
307 # Note: at this point, during a normal execution,
308 # Assume that the VM resumes and begins issuing IO requests here.
310 self
.hmp_io_writes(drive1
['id'], (('0xab', 0, 512),
311 ('0xfe', '16M', '256k'),
312 ('0x64', '32736k', '64k')))
314 result
= self
.create_incremental(validate
=False)
315 self
.assertFalse(result
)
316 self
.hmp_io_writes(drive1
['id'], (('0x9a', 0, 512),
317 ('0x55', '8M', '352k'),
318 ('0x78', '15872k', '1M')))
319 self
.create_incremental()
324 def test_sync_dirty_bitmap_missing(self
):
325 self
.assert_no_active_block_jobs()
326 self
.files
.append(self
.err_img
)
327 result
= self
.vm
.qmp('drive-backup', device
=self
.drives
[0]['id'],
328 sync
='incremental', format
=self
.drives
[0]['fmt'],
330 self
.assert_qmp(result
, 'error/class', 'GenericError')
333 def test_sync_dirty_bitmap_not_found(self
):
334 self
.assert_no_active_block_jobs()
335 self
.files
.append(self
.err_img
)
336 result
= self
.vm
.qmp('drive-backup', device
=self
.drives
[0]['id'],
337 sync
='incremental', bitmap
='unknown',
338 format
=self
.drives
[0]['fmt'], target
=self
.err_img
)
339 self
.assert_qmp(result
, 'error/class', 'GenericError')
342 def test_sync_dirty_bitmap_bad_granularity(self
):
344 Test: Test what happens if we provide an improper granularity.
346 The granularity must always be a power of 2.
348 self
.assert_no_active_block_jobs()
349 self
.assertRaises(AssertionError, self
.add_bitmap
,
350 'bitmap0', self
.drives
[0],
356 for bitmap
in self
.bitmaps
:
358 for filename
in self
.files
:
362 if __name__
== '__main__':
363 iotests
.main(supported_fmts
=['qcow2'])