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