]> git.proxmox.com Git - mirror_qemu.git/blame - tests/qemu-iotests/124
iotests: fix 097 when run with qcow
[mirror_qemu.git] / tests / qemu-iotests / 124
CommitLineData
9f7264f5
JS
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
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
a3d71595
JS
32def try_remove(img):
33 try:
34 os.remove(img)
35 except OSError:
36 pass
37
38
749ad5e8
JS
39def transaction_action(action, **kwargs):
40 return {
41 'type': action,
fc6c796f 42 'data': dict((k.replace('_', '-'), v) for k, v in kwargs.iteritems())
749ad5e8
JS
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):
eed87583
KW
52 return transaction_action('drive-backup', job_id=device, device=device,
53 target=target, **kwargs)
749ad5e8
JS
54
55
a3d71595
JS
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
1b19bb9d
JS
94class TestIncrementalBackupBase(iotests.QMPTestCase):
95 def __init__(self, *args):
96 super(TestIncrementalBackupBase, self).__init__(*args)
9f7264f5
JS
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
1b19bb9d
JS
103
104 def setUp(self):
9f7264f5
JS
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'])
1b19bb9d 109 self.write_default_pattern(drive0['file'])
9f7264f5
JS
110 self.vm.launch()
111
112
1b19bb9d
JS
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
9f7264f5
JS
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',
cc199b16
JS
135 parent=None, parentFormat=None, **kwargs):
136 optargs = []
137 for k,v in kwargs.iteritems():
138 optargs = optargs + ['-o', '%s=%s' % (k,v)]
139 args = ['create', '-f', fmt] + optargs + [img, size]
9f7264f5
JS
140 if parent:
141 if parentFormat is None:
142 parentFormat = fmt
cc199b16
JS
143 args = args + ['-b', parent, '-F', parentFormat]
144 iotests.qemu_img(*args)
9f7264f5
JS
145 self.files.append(img)
146
a3d71595
JS
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', {})
fc6c796f 151 return self.wait_qmp_backup(kwargs['device'], error)
a3d71595 152
fc6c796f
JS
153
154 def wait_qmp_backup(self, device, error='Input/output error'):
a3d71595 155 event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED",
fc6c796f 156 match={'data': {'device': device}})
ff793890 157 self.assertNotEqual(event, None)
a3d71595
JS
158
159 try:
160 failure = self.dictpath(event, 'data/error')
161 except AssertionError:
162 # Backup succeeded.
163 self.assert_qmp(event, 'data/offset', event['data']['len'])
164 return True
165 else:
166 # Backup failed.
167 self.assert_qmp(event, 'data/error', error)
168 return False
169
170
fc6c796f
JS
171 def wait_qmp_backup_cancelled(self, device):
172 event = self.vm.event_wait(name='BLOCK_JOB_CANCELLED',
173 match={'data': {'device': device}})
174 self.assertNotEqual(event, None)
175
176
a3d71595
JS
177 def create_anchor_backup(self, drive=None):
178 if drive is None:
179 drive = self.drives[-1]
eed87583
KW
180 res = self.do_qmp_backup(job_id=drive['id'],
181 device=drive['id'], sync='full',
a3d71595
JS
182 format=drive['fmt'], target=drive['backup'])
183 self.assertTrue(res)
184 self.files.append(drive['backup'])
185 return drive['backup']
186
187
188 def make_reference_backup(self, bitmap=None):
189 if bitmap is None:
190 bitmap = self.bitmaps[-1]
191 _, reference = bitmap.last_target()
eed87583
KW
192 res = self.do_qmp_backup(job_id=bitmap.drive['id'],
193 device=bitmap.drive['id'], sync='full',
a3d71595
JS
194 format=bitmap.drive['fmt'], target=reference)
195 self.assertTrue(res)
196
197
59fc5d84 198 def add_bitmap(self, name, drive, **kwargs):
a3d71595
JS
199 bitmap = Bitmap(name, drive)
200 self.bitmaps.append(bitmap)
201 result = self.vm.qmp('block-dirty-bitmap-add', node=drive['id'],
59fc5d84 202 name=bitmap.name, **kwargs)
a3d71595
JS
203 self.assert_qmp(result, 'return', {})
204 return bitmap
205
206
207 def prepare_backup(self, bitmap=None, parent=None):
208 if bitmap is None:
209 bitmap = self.bitmaps[-1]
210 if parent is None:
211 parent, _ = bitmap.last_target()
212
213 target, _ = bitmap.new_target()
214 self.img_create(target, bitmap.drive['fmt'], parent=parent)
215 return target
216
217
218 def create_incremental(self, bitmap=None, parent=None,
219 parentFormat=None, validate=True):
220 if bitmap is None:
221 bitmap = self.bitmaps[-1]
222 if parent is None:
223 parent, _ = bitmap.last_target()
224
225 target = self.prepare_backup(bitmap, parent)
eed87583
KW
226 res = self.do_qmp_backup(job_id=bitmap.drive['id'],
227 device=bitmap.drive['id'],
4b80ab2b 228 sync='incremental', bitmap=bitmap.name,
a3d71595
JS
229 format=bitmap.drive['fmt'], target=target,
230 mode='existing')
231 if not res:
232 bitmap.del_target();
233 self.assertFalse(validate)
234 else:
235 self.make_reference_backup(bitmap)
236 return res
237
238
239 def check_backups(self):
240 for bitmap in self.bitmaps:
241 for incremental, reference in bitmap.backups:
242 self.assertTrue(iotests.compare_images(incremental, reference))
243 last = bitmap.last_target()[0]
244 self.assertTrue(iotests.compare_images(last, bitmap.drive['file']))
245
246
247 def hmp_io_writes(self, drive, patterns):
248 for pattern in patterns:
249 self.vm.hmp_qemu_io(drive, 'write -P%s %s %s' % pattern)
250 self.vm.hmp_qemu_io(drive, 'flush')
251
252
59fc5d84 253 def do_incremental_simple(self, **kwargs):
a3d71595 254 self.create_anchor_backup()
59fc5d84 255 self.add_bitmap('bitmap0', self.drives[0], **kwargs)
a3d71595
JS
256
257 # Sanity: Create a "hollow" incremental backup
258 self.create_incremental()
259 # Three writes: One complete overwrite, one new segment,
260 # and one partial overlap.
261 self.hmp_io_writes(self.drives[0]['id'], (('0xab', 0, 512),
262 ('0xfe', '16M', '256k'),
263 ('0x64', '32736k', '64k')))
264 self.create_incremental()
265 # Three more writes, one of each kind, like above
266 self.hmp_io_writes(self.drives[0]['id'], (('0x9a', 0, 512),
267 ('0x55', '8M', '352k'),
268 ('0x78', '15872k', '1M')))
269 self.create_incremental()
270 self.vm.shutdown()
271 self.check_backups()
272
273
1b19bb9d
JS
274 def tearDown(self):
275 self.vm.shutdown()
276 for bitmap in self.bitmaps:
277 bitmap.cleanup()
278 for filename in self.files:
279 try_remove(filename)
280
281
282
283class TestIncrementalBackup(TestIncrementalBackupBase):
59fc5d84
JS
284 def test_incremental_simple(self):
285 '''
286 Test: Create and verify three incremental backups.
287
288 Create a bitmap and a full backup before VM execution begins,
289 then create a series of three incremental backups "during execution,"
290 i.e.; after IO requests begin modifying the drive.
291 '''
292 return self.do_incremental_simple()
293
294
295 def test_small_granularity(self):
296 '''
297 Test: Create and verify backups made with a small granularity bitmap.
298
299 Perform the same test as test_incremental_simple, but with a granularity
300 of only 32KiB instead of the present default of 64KiB.
301 '''
302 return self.do_incremental_simple(granularity=32768)
303
304
305 def test_large_granularity(self):
306 '''
307 Test: Create and verify backups made with a large granularity bitmap.
308
309 Perform the same test as test_incremental_simple, but with a granularity
310 of 128KiB instead of the present default of 64KiB.
311 '''
312 return self.do_incremental_simple(granularity=131072)
313
314
cc199b16
JS
315 def test_larger_cluster_target(self):
316 '''
317 Test: Create and verify backups made to a larger cluster size target.
318
319 With a default granularity of 64KiB, verify that backups made to a
320 larger cluster size target of 128KiB without a backing file works.
321 '''
322 drive0 = self.drives[0]
323
324 # Create a cluster_size=128k full backup / "anchor" backup
325 self.img_create(drive0['backup'], cluster_size='128k')
326 self.assertTrue(self.do_qmp_backup(device=drive0['id'], sync='full',
327 format=drive0['fmt'],
328 target=drive0['backup'],
329 mode='existing'))
330
331 # Create bitmap and dirty it with some new writes.
332 # overwrite [32736, 32799] which will dirty bitmap clusters at
333 # 32M-64K and 32M. 32M+64K will be left undirtied.
334 bitmap0 = self.add_bitmap('bitmap0', drive0)
335 self.hmp_io_writes(drive0['id'],
336 (('0xab', 0, 512),
337 ('0xfe', '16M', '256k'),
338 ('0x64', '32736k', '64k')))
339
340
341 # Prepare a cluster_size=128k backup target without a backing file.
342 (target, _) = bitmap0.new_target()
343 self.img_create(target, bitmap0.drive['fmt'], cluster_size='128k')
344
345 # Perform Incremental Backup
346 self.assertTrue(self.do_qmp_backup(device=bitmap0.drive['id'],
347 sync='incremental',
348 bitmap=bitmap0.name,
349 format=bitmap0.drive['fmt'],
350 target=target,
351 mode='existing'))
352 self.make_reference_backup(bitmap0)
353
354 # Add the backing file, then compare and exit.
355 iotests.qemu_img('rebase', '-f', drive0['fmt'], '-u', '-b',
356 drive0['backup'], '-F', drive0['fmt'], target)
357 self.vm.shutdown()
358 self.check_backups()
359
360
749ad5e8
JS
361 def test_incremental_transaction(self):
362 '''Test: Verify backups made from transactionally created bitmaps.
363
364 Create a bitmap "before" VM execution begins, then create a second
365 bitmap AFTER writes have already occurred. Use transactions to create
366 a full backup and synchronize both bitmaps to this backup.
367 Create an incremental backup through both bitmaps and verify that
368 both backups match the current drive0 image.
369 '''
370
371 drive0 = self.drives[0]
372 bitmap0 = self.add_bitmap('bitmap0', drive0)
373 self.hmp_io_writes(drive0['id'], (('0xab', 0, 512),
374 ('0xfe', '16M', '256k'),
375 ('0x64', '32736k', '64k')))
376 bitmap1 = self.add_bitmap('bitmap1', drive0)
377
378 result = self.vm.qmp('transaction', actions=[
379 transaction_bitmap_clear(bitmap0.drive['id'], bitmap0.name),
380 transaction_bitmap_clear(bitmap1.drive['id'], bitmap1.name),
381 transaction_drive_backup(drive0['id'], drive0['backup'],
382 sync='full', format=drive0['fmt'])
383 ])
384 self.assert_qmp(result, 'return', {})
385 self.wait_until_completed(drive0['id'])
386 self.files.append(drive0['backup'])
387
388 self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512),
389 ('0x55', '8M', '352k'),
390 ('0x78', '15872k', '1M')))
391 # Both bitmaps should be correctly in sync.
392 self.create_incremental(bitmap0)
393 self.create_incremental(bitmap1)
394 self.vm.shutdown()
395 self.check_backups()
396
397
0aef09b9 398 def do_transaction_failure_test(self, race=False):
fc6c796f
JS
399 # Create a second drive, with pattern:
400 drive1 = self.add_node('drive1')
401 self.img_create(drive1['file'], drive1['fmt'])
402 io_write_patterns(drive1['file'], (('0x14', 0, 512),
403 ('0x5d', '1M', '32k'),
404 ('0xcd', '32M', '124k')))
405
406 # Create a blkdebug interface to this img as 'drive1'
0153d2f5
KW
407 result = self.vm.qmp('blockdev-add',
408 node_name=drive1['id'],
409 driver=drive1['fmt'],
410 file={
fc6c796f
JS
411 'driver': 'blkdebug',
412 'image': {
413 'driver': 'file',
414 'filename': drive1['file']
415 },
416 'set-state': [{
417 'event': 'flush_to_disk',
418 'state': 1,
419 'new_state': 2
420 }],
421 'inject-error': [{
422 'event': 'read_aio',
423 'errno': 5,
424 'state': 2,
425 'immediately': False,
426 'once': True
427 }],
428 }
0153d2f5 429 )
fc6c796f
JS
430 self.assert_qmp(result, 'return', {})
431
432 # Create bitmaps and full backups for both drives
433 drive0 = self.drives[0]
434 dr0bm0 = self.add_bitmap('bitmap0', drive0)
435 dr1bm0 = self.add_bitmap('bitmap0', drive1)
436 self.create_anchor_backup(drive0)
437 self.create_anchor_backup(drive1)
438 self.assert_no_active_block_jobs()
439 self.assertFalse(self.vm.get_qmp_events(wait=False))
440
441 # Emulate some writes
0aef09b9
JS
442 if not race:
443 self.hmp_io_writes(drive0['id'], (('0xab', 0, 512),
444 ('0xfe', '16M', '256k'),
445 ('0x64', '32736k', '64k')))
fc6c796f
JS
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,
0aef09b9
JS
455 # expecting drive1's backup to fail. In the 'race' test,
456 # we expect drive1 to attempt to cancel the empty drive0 job.
fc6c796f
JS
457 transaction = [
458 transaction_drive_backup(drive0['id'], target0, sync='incremental',
459 format=drive0['fmt'], mode='existing',
460 bitmap=dr0bm0.name),
461 transaction_drive_backup(drive1['id'], target1, sync='incremental',
462 format=drive1['fmt'], mode='existing',
463 bitmap=dr1bm0.name)
464 ]
465 result = self.vm.qmp('transaction', actions=transaction,
466 properties={'completion-mode': 'grouped'} )
467 self.assert_qmp(result, 'return', {})
468
469 # Observe that drive0's backup is cancelled and drive1 completes with
470 # an error.
471 self.wait_qmp_backup_cancelled(drive0['id'])
472 self.assertFalse(self.wait_qmp_backup(drive1['id']))
473 error = self.vm.event_wait('BLOCK_JOB_ERROR')
474 self.assert_qmp(error, 'data', {'device': drive1['id'],
475 'action': 'report',
476 'operation': 'read'})
477 self.assertFalse(self.vm.get_qmp_events(wait=False))
478 self.assert_no_active_block_jobs()
479
480 # Delete drive0's successful target and eliminate our record of the
0aef09b9 481 # unsuccessful drive1 target.
fc6c796f
JS
482 dr0bm0.del_target()
483 dr1bm0.del_target()
0aef09b9
JS
484 if race:
485 # Don't re-run the transaction, we only wanted to test the race.
486 self.vm.shutdown()
487 return
488
489 # Re-run the same transaction:
fc6c796f
JS
490 target0 = self.prepare_backup(dr0bm0)
491 target1 = self.prepare_backup(dr1bm0)
492
493 # Re-run the exact same transaction.
494 result = self.vm.qmp('transaction', actions=transaction,
495 properties={'completion-mode':'grouped'})
496 self.assert_qmp(result, 'return', {})
497
498 # Both should complete successfully this time.
499 self.assertTrue(self.wait_qmp_backup(drive0['id']))
500 self.assertTrue(self.wait_qmp_backup(drive1['id']))
501 self.make_reference_backup(dr0bm0)
502 self.make_reference_backup(dr1bm0)
503 self.assertFalse(self.vm.get_qmp_events(wait=False))
504 self.assert_no_active_block_jobs()
505
506 # And the images should of course validate.
507 self.vm.shutdown()
508 self.check_backups()
509
0aef09b9
JS
510 def test_transaction_failure(self):
511 '''Test: Verify backups made from a transaction that partially fails.
512
513 Add a second drive with its own unique pattern, and add a bitmap to each
514 drive. Use blkdebug to interfere with the backup on just one drive and
515 attempt to create a coherent incremental backup across both drives.
516
517 verify a failure in one but not both, then delete the failed stubs and
518 re-run the same transaction.
519
520 verify that both incrementals are created successfully.
521 '''
522 self.do_transaction_failure_test()
523
524 def test_transaction_failure_race(self):
525 '''Test: Verify that transactions with jobs that have no data to
526 transfer do not cause race conditions in the cancellation of the entire
527 transaction job group.
528 '''
529 self.do_transaction_failure_test(race=True)
530
fc6c796f 531
9f7264f5
JS
532 def test_sync_dirty_bitmap_missing(self):
533 self.assert_no_active_block_jobs()
534 self.files.append(self.err_img)
535 result = self.vm.qmp('drive-backup', device=self.drives[0]['id'],
4b80ab2b 536 sync='incremental', format=self.drives[0]['fmt'],
9f7264f5
JS
537 target=self.err_img)
538 self.assert_qmp(result, 'error/class', 'GenericError')
539
540
541 def test_sync_dirty_bitmap_not_found(self):
542 self.assert_no_active_block_jobs()
543 self.files.append(self.err_img)
544 result = self.vm.qmp('drive-backup', device=self.drives[0]['id'],
4b80ab2b 545 sync='incremental', bitmap='unknown',
9f7264f5
JS
546 format=self.drives[0]['fmt'], target=self.err_img)
547 self.assert_qmp(result, 'error/class', 'GenericError')
548
549
59fc5d84
JS
550 def test_sync_dirty_bitmap_bad_granularity(self):
551 '''
552 Test: Test what happens if we provide an improper granularity.
553
554 The granularity must always be a power of 2.
555 '''
556 self.assert_no_active_block_jobs()
557 self.assertRaises(AssertionError, self.add_bitmap,
558 'bitmap0', self.drives[0],
559 granularity=64000)
560
561
ce2cbc49
JS
562class TestIncrementalBackupBlkdebug(TestIncrementalBackupBase):
563 '''Incremental backup tests that utilize a BlkDebug filter on drive0.'''
564
35cea223
JS
565 def setUp(self):
566 drive0 = self.add_node('drive0')
567 self.img_create(drive0['file'], drive0['fmt'])
568 self.write_default_pattern(drive0['file'])
569 self.vm.launch()
570
ce2cbc49
JS
571 def test_incremental_failure(self):
572 '''Test: Verify backups made after a failure are correct.
573
574 Simulate a failure during an incremental backup block job,
575 emulate additional writes, then create another incremental backup
576 afterwards and verify that the backup created is correct.
577 '''
578
35cea223 579 drive0 = self.drives[0]
0153d2f5
KW
580 result = self.vm.qmp('blockdev-add',
581 node_name=drive0['id'],
582 driver=drive0['fmt'],
583 file={
ce2cbc49
JS
584 'driver': 'blkdebug',
585 'image': {
586 'driver': 'file',
35cea223 587 'filename': drive0['file']
ce2cbc49
JS
588 },
589 'set-state': [{
590 'event': 'flush_to_disk',
591 'state': 1,
592 'new_state': 2
593 }],
594 'inject-error': [{
595 'event': 'read_aio',
596 'errno': 5,
597 'state': 2,
598 'immediately': False,
599 'once': True
600 }],
601 }
0153d2f5 602 )
ce2cbc49
JS
603 self.assert_qmp(result, 'return', {})
604
35cea223
JS
605 self.create_anchor_backup(drive0)
606 self.add_bitmap('bitmap0', drive0)
ce2cbc49
JS
607 # Note: at this point, during a normal execution,
608 # Assume that the VM resumes and begins issuing IO requests here.
609
35cea223 610 self.hmp_io_writes(drive0['id'], (('0xab', 0, 512),
ce2cbc49
JS
611 ('0xfe', '16M', '256k'),
612 ('0x64', '32736k', '64k')))
613
614 result = self.create_incremental(validate=False)
615 self.assertFalse(result)
35cea223 616 self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512),
ce2cbc49
JS
617 ('0x55', '8M', '352k'),
618 ('0x78', '15872k', '1M')))
619 self.create_incremental()
620 self.vm.shutdown()
621 self.check_backups()
622
623
9f7264f5
JS
624if __name__ == '__main__':
625 iotests.main(supported_fmts=['qcow2'])