]> git.proxmox.com Git - mirror_qemu.git/blob - tests/qemu-iotests/124
Merge remote-tracking branch 'remotes/jasowang/tags/net-pull-request' into staging
[mirror_qemu.git] / tests / qemu-iotests / 124
1 #!/usr/bin/env python
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
23 import os
24 import iotests
25
26
27 def io_write_patterns(img, patterns):
28 for pattern in patterns:
29 iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img)
30
31
32 def try_remove(img):
33 try:
34 os.remove(img)
35 except OSError:
36 pass
37
38
39 def transaction_action(action, **kwargs):
40 return {
41 'type': action,
42 'data': dict((k.replace('_', '-'), v) for k, v in kwargs.iteritems())
43 }
44
45
46 def transaction_bitmap_clear(node, name, **kwargs):
47 return transaction_action('block-dirty-bitmap-clear',
48 node=node, name=name, **kwargs)
49
50
51 def transaction_drive_backup(device, target, **kwargs):
52 return transaction_action('drive-backup', device=device, target=target,
53 **kwargs)
54
55
56 class 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
94 class TestIncrementalBackup(iotests.QMPTestCase):
95 def setUp(self):
96 self.bitmaps = list()
97 self.files = list()
98 self.drives = list()
99 self.vm = iotests.VM()
100 self.err_img = os.path.join(iotests.test_dir, 'err.%s' % iotests.imgfmt)
101
102 # Create a base image with a distinctive patterning
103 drive0 = self.add_node('drive0')
104 self.img_create(drive0['file'], drive0['fmt'])
105 self.vm.add_drive(drive0['file'])
106 io_write_patterns(drive0['file'], (('0x41', 0, 512),
107 ('0xd5', '1M', '32k'),
108 ('0xdc', '32M', '124k')))
109 self.vm.launch()
110
111
112 def add_node(self, node_id, fmt=iotests.imgfmt, path=None, backup=None):
113 if path is None:
114 path = os.path.join(iotests.test_dir, '%s.%s' % (node_id, fmt))
115 if backup is None:
116 backup = os.path.join(iotests.test_dir,
117 '%s.full.backup.%s' % (node_id, fmt))
118
119 self.drives.append({
120 'id': node_id,
121 'file': path,
122 'backup': backup,
123 'fmt': fmt })
124 return self.drives[-1]
125
126
127 def img_create(self, img, fmt=iotests.imgfmt, size='64M',
128 parent=None, parentFormat=None):
129 if parent:
130 if parentFormat is None:
131 parentFormat = fmt
132 iotests.qemu_img('create', '-f', fmt, img, size,
133 '-b', parent, '-F', parentFormat)
134 else:
135 iotests.qemu_img('create', '-f', fmt, img, size)
136 self.files.append(img)
137
138
139 def do_qmp_backup(self, error='Input/output error', **kwargs):
140 res = self.vm.qmp('drive-backup', **kwargs)
141 self.assert_qmp(res, 'return', {})
142 return self.wait_qmp_backup(kwargs['device'], error)
143
144
145 def wait_qmp_backup(self, device, error='Input/output error'):
146 event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED",
147 match={'data': {'device': device}})
148 self.assertNotEqual(event, None)
149
150 try:
151 failure = self.dictpath(event, 'data/error')
152 except AssertionError:
153 # Backup succeeded.
154 self.assert_qmp(event, 'data/offset', event['data']['len'])
155 return True
156 else:
157 # Backup failed.
158 self.assert_qmp(event, 'data/error', error)
159 return False
160
161
162 def wait_qmp_backup_cancelled(self, device):
163 event = self.vm.event_wait(name='BLOCK_JOB_CANCELLED',
164 match={'data': {'device': device}})
165 self.assertNotEqual(event, None)
166
167
168 def create_anchor_backup(self, drive=None):
169 if drive is None:
170 drive = self.drives[-1]
171 res = self.do_qmp_backup(device=drive['id'], sync='full',
172 format=drive['fmt'], target=drive['backup'])
173 self.assertTrue(res)
174 self.files.append(drive['backup'])
175 return drive['backup']
176
177
178 def make_reference_backup(self, bitmap=None):
179 if bitmap is None:
180 bitmap = self.bitmaps[-1]
181 _, reference = bitmap.last_target()
182 res = self.do_qmp_backup(device=bitmap.drive['id'], sync='full',
183 format=bitmap.drive['fmt'], target=reference)
184 self.assertTrue(res)
185
186
187 def add_bitmap(self, name, drive, **kwargs):
188 bitmap = Bitmap(name, drive)
189 self.bitmaps.append(bitmap)
190 result = self.vm.qmp('block-dirty-bitmap-add', node=drive['id'],
191 name=bitmap.name, **kwargs)
192 self.assert_qmp(result, 'return', {})
193 return bitmap
194
195
196 def prepare_backup(self, bitmap=None, parent=None):
197 if bitmap is None:
198 bitmap = self.bitmaps[-1]
199 if parent is None:
200 parent, _ = bitmap.last_target()
201
202 target, _ = bitmap.new_target()
203 self.img_create(target, bitmap.drive['fmt'], parent=parent)
204 return target
205
206
207 def create_incremental(self, bitmap=None, parent=None,
208 parentFormat=None, validate=True):
209 if bitmap is None:
210 bitmap = self.bitmaps[-1]
211 if parent is None:
212 parent, _ = bitmap.last_target()
213
214 target = self.prepare_backup(bitmap, parent)
215 res = self.do_qmp_backup(device=bitmap.drive['id'],
216 sync='incremental', bitmap=bitmap.name,
217 format=bitmap.drive['fmt'], target=target,
218 mode='existing')
219 if not res:
220 bitmap.del_target();
221 self.assertFalse(validate)
222 else:
223 self.make_reference_backup(bitmap)
224 return res
225
226
227 def check_backups(self):
228 for bitmap in self.bitmaps:
229 for incremental, reference in bitmap.backups:
230 self.assertTrue(iotests.compare_images(incremental, reference))
231 last = bitmap.last_target()[0]
232 self.assertTrue(iotests.compare_images(last, bitmap.drive['file']))
233
234
235 def hmp_io_writes(self, drive, patterns):
236 for pattern in patterns:
237 self.vm.hmp_qemu_io(drive, 'write -P%s %s %s' % pattern)
238 self.vm.hmp_qemu_io(drive, 'flush')
239
240
241 def do_incremental_simple(self, **kwargs):
242 self.create_anchor_backup()
243 self.add_bitmap('bitmap0', self.drives[0], **kwargs)
244
245 # Sanity: Create a "hollow" incremental backup
246 self.create_incremental()
247 # Three writes: One complete overwrite, one new segment,
248 # and one partial overlap.
249 self.hmp_io_writes(self.drives[0]['id'], (('0xab', 0, 512),
250 ('0xfe', '16M', '256k'),
251 ('0x64', '32736k', '64k')))
252 self.create_incremental()
253 # Three more writes, one of each kind, like above
254 self.hmp_io_writes(self.drives[0]['id'], (('0x9a', 0, 512),
255 ('0x55', '8M', '352k'),
256 ('0x78', '15872k', '1M')))
257 self.create_incremental()
258 self.vm.shutdown()
259 self.check_backups()
260
261
262 def test_incremental_simple(self):
263 '''
264 Test: Create and verify three incremental backups.
265
266 Create a bitmap and a full backup before VM execution begins,
267 then create a series of three incremental backups "during execution,"
268 i.e.; after IO requests begin modifying the drive.
269 '''
270 return self.do_incremental_simple()
271
272
273 def test_small_granularity(self):
274 '''
275 Test: Create and verify backups made with a small granularity bitmap.
276
277 Perform the same test as test_incremental_simple, but with a granularity
278 of only 32KiB instead of the present default of 64KiB.
279 '''
280 return self.do_incremental_simple(granularity=32768)
281
282
283 def test_large_granularity(self):
284 '''
285 Test: Create and verify backups made with a large granularity bitmap.
286
287 Perform the same test as test_incremental_simple, but with a granularity
288 of 128KiB instead of the present default of 64KiB.
289 '''
290 return self.do_incremental_simple(granularity=131072)
291
292
293 def test_incremental_transaction(self):
294 '''Test: Verify backups made from transactionally created bitmaps.
295
296 Create a bitmap "before" VM execution begins, then create a second
297 bitmap AFTER writes have already occurred. Use transactions to create
298 a full backup and synchronize both bitmaps to this backup.
299 Create an incremental backup through both bitmaps and verify that
300 both backups match the current drive0 image.
301 '''
302
303 drive0 = self.drives[0]
304 bitmap0 = self.add_bitmap('bitmap0', drive0)
305 self.hmp_io_writes(drive0['id'], (('0xab', 0, 512),
306 ('0xfe', '16M', '256k'),
307 ('0x64', '32736k', '64k')))
308 bitmap1 = self.add_bitmap('bitmap1', drive0)
309
310 result = self.vm.qmp('transaction', actions=[
311 transaction_bitmap_clear(bitmap0.drive['id'], bitmap0.name),
312 transaction_bitmap_clear(bitmap1.drive['id'], bitmap1.name),
313 transaction_drive_backup(drive0['id'], drive0['backup'],
314 sync='full', format=drive0['fmt'])
315 ])
316 self.assert_qmp(result, 'return', {})
317 self.wait_until_completed(drive0['id'])
318 self.files.append(drive0['backup'])
319
320 self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512),
321 ('0x55', '8M', '352k'),
322 ('0x78', '15872k', '1M')))
323 # Both bitmaps should be correctly in sync.
324 self.create_incremental(bitmap0)
325 self.create_incremental(bitmap1)
326 self.vm.shutdown()
327 self.check_backups()
328
329
330 def test_incremental_failure(self):
331 '''Test: Verify backups made after a failure are correct.
332
333 Simulate a failure during an incremental backup block job,
334 emulate additional writes, then create another incremental backup
335 afterwards and verify that the backup created is correct.
336 '''
337
338 # Create a blkdebug interface to this img as 'drive1',
339 # but don't actually create a new image.
340 drive1 = self.add_node('drive1', self.drives[0]['fmt'],
341 path=self.drives[0]['file'],
342 backup=self.drives[0]['backup'])
343 result = self.vm.qmp('blockdev-add', options={
344 'id': drive1['id'],
345 'driver': drive1['fmt'],
346 'file': {
347 'driver': 'blkdebug',
348 'image': {
349 'driver': 'file',
350 'filename': drive1['file']
351 },
352 'set-state': [{
353 'event': 'flush_to_disk',
354 'state': 1,
355 'new_state': 2
356 }],
357 'inject-error': [{
358 'event': 'read_aio',
359 'errno': 5,
360 'state': 2,
361 'immediately': False,
362 'once': True
363 }],
364 }
365 })
366 self.assert_qmp(result, 'return', {})
367
368 self.create_anchor_backup(self.drives[0])
369 self.add_bitmap('bitmap0', drive1)
370 # Note: at this point, during a normal execution,
371 # Assume that the VM resumes and begins issuing IO requests here.
372
373 self.hmp_io_writes(drive1['id'], (('0xab', 0, 512),
374 ('0xfe', '16M', '256k'),
375 ('0x64', '32736k', '64k')))
376
377 result = self.create_incremental(validate=False)
378 self.assertFalse(result)
379 self.hmp_io_writes(drive1['id'], (('0x9a', 0, 512),
380 ('0x55', '8M', '352k'),
381 ('0x78', '15872k', '1M')))
382 self.create_incremental()
383 self.vm.shutdown()
384 self.check_backups()
385
386
387 def test_transaction_failure(self):
388 '''Test: Verify backups made from a transaction that partially fails.
389
390 Add a second drive with its own unique pattern, and add a bitmap to each
391 drive. Use blkdebug to interfere with the backup on just one drive and
392 attempt to create a coherent incremental backup across both drives.
393
394 verify a failure in one but not both, then delete the failed stubs and
395 re-run the same transaction.
396
397 verify that both incrementals are created successfully.
398 '''
399
400 # Create a second drive, with pattern:
401 drive1 = self.add_node('drive1')
402 self.img_create(drive1['file'], drive1['fmt'])
403 io_write_patterns(drive1['file'], (('0x14', 0, 512),
404 ('0x5d', '1M', '32k'),
405 ('0xcd', '32M', '124k')))
406
407 # Create a blkdebug interface to this img as 'drive1'
408 result = self.vm.qmp('blockdev-add', options={
409 'id': drive1['id'],
410 'driver': drive1['fmt'],
411 'file': {
412 'driver': 'blkdebug',
413 'image': {
414 'driver': 'file',
415 'filename': drive1['file']
416 },
417 'set-state': [{
418 'event': 'flush_to_disk',
419 'state': 1,
420 'new_state': 2
421 }],
422 'inject-error': [{
423 'event': 'read_aio',
424 'errno': 5,
425 'state': 2,
426 'immediately': False,
427 'once': True
428 }],
429 }
430 })
431 self.assert_qmp(result, 'return', {})
432
433 # Create bitmaps and full backups for both drives
434 drive0 = self.drives[0]
435 dr0bm0 = self.add_bitmap('bitmap0', drive0)
436 dr1bm0 = self.add_bitmap('bitmap0', drive1)
437 self.create_anchor_backup(drive0)
438 self.create_anchor_backup(drive1)
439 self.assert_no_active_block_jobs()
440 self.assertFalse(self.vm.get_qmp_events(wait=False))
441
442 # Emulate some writes
443 self.hmp_io_writes(drive0['id'], (('0xab', 0, 512),
444 ('0xfe', '16M', '256k'),
445 ('0x64', '32736k', '64k')))
446 self.hmp_io_writes(drive1['id'], (('0xba', 0, 512),
447 ('0xef', '16M', '256k'),
448 ('0x46', '32736k', '64k')))
449
450 # Create incremental backup targets
451 target0 = self.prepare_backup(dr0bm0)
452 target1 = self.prepare_backup(dr1bm0)
453
454 # Ask for a new incremental backup per-each drive,
455 # expecting drive1's backup to fail:
456 transaction = [
457 transaction_drive_backup(drive0['id'], target0, sync='incremental',
458 format=drive0['fmt'], mode='existing',
459 bitmap=dr0bm0.name),
460 transaction_drive_backup(drive1['id'], target1, sync='incremental',
461 format=drive1['fmt'], mode='existing',
462 bitmap=dr1bm0.name)
463 ]
464 result = self.vm.qmp('transaction', actions=transaction,
465 properties={'completion-mode': 'grouped'} )
466 self.assert_qmp(result, 'return', {})
467
468 # Observe that drive0's backup is cancelled and drive1 completes with
469 # an error.
470 self.wait_qmp_backup_cancelled(drive0['id'])
471 self.assertFalse(self.wait_qmp_backup(drive1['id']))
472 error = self.vm.event_wait('BLOCK_JOB_ERROR')
473 self.assert_qmp(error, 'data', {'device': drive1['id'],
474 'action': 'report',
475 'operation': 'read'})
476 self.assertFalse(self.vm.get_qmp_events(wait=False))
477 self.assert_no_active_block_jobs()
478
479 # Delete drive0's successful target and eliminate our record of the
480 # unsuccessful drive1 target. Then re-run the same transaction.
481 dr0bm0.del_target()
482 dr1bm0.del_target()
483 target0 = self.prepare_backup(dr0bm0)
484 target1 = self.prepare_backup(dr1bm0)
485
486 # Re-run the exact same transaction.
487 result = self.vm.qmp('transaction', actions=transaction,
488 properties={'completion-mode':'grouped'})
489 self.assert_qmp(result, 'return', {})
490
491 # Both should complete successfully this time.
492 self.assertTrue(self.wait_qmp_backup(drive0['id']))
493 self.assertTrue(self.wait_qmp_backup(drive1['id']))
494 self.make_reference_backup(dr0bm0)
495 self.make_reference_backup(dr1bm0)
496 self.assertFalse(self.vm.get_qmp_events(wait=False))
497 self.assert_no_active_block_jobs()
498
499 # And the images should of course validate.
500 self.vm.shutdown()
501 self.check_backups()
502
503
504 def test_sync_dirty_bitmap_missing(self):
505 self.assert_no_active_block_jobs()
506 self.files.append(self.err_img)
507 result = self.vm.qmp('drive-backup', device=self.drives[0]['id'],
508 sync='incremental', format=self.drives[0]['fmt'],
509 target=self.err_img)
510 self.assert_qmp(result, 'error/class', 'GenericError')
511
512
513 def test_sync_dirty_bitmap_not_found(self):
514 self.assert_no_active_block_jobs()
515 self.files.append(self.err_img)
516 result = self.vm.qmp('drive-backup', device=self.drives[0]['id'],
517 sync='incremental', bitmap='unknown',
518 format=self.drives[0]['fmt'], target=self.err_img)
519 self.assert_qmp(result, 'error/class', 'GenericError')
520
521
522 def test_sync_dirty_bitmap_bad_granularity(self):
523 '''
524 Test: Test what happens if we provide an improper granularity.
525
526 The granularity must always be a power of 2.
527 '''
528 self.assert_no_active_block_jobs()
529 self.assertRaises(AssertionError, self.add_bitmap,
530 'bitmap0', self.drives[0],
531 granularity=64000)
532
533
534 def tearDown(self):
535 self.vm.shutdown()
536 for bitmap in self.bitmaps:
537 bitmap.cleanup()
538 for filename in self.files:
539 try_remove(filename)
540
541
542 if __name__ == '__main__':
543 iotests.main(supported_fmts=['qcow2'])