def transaction_drive_backup(device, target, **kwargs):
- return transaction_action('drive-backup', device=device, target=target,
- **kwargs)
+ return transaction_action('drive-backup', job_id=device, device=device,
+ target=target, **kwargs)
class Bitmap:
return self.wait_qmp_backup(kwargs['device'], error)
+ def ignore_job_status_change_events(self):
+ while True:
+ e = self.vm.event_wait(name="JOB_STATUS_CHANGE")
+ if e['data']['status'] == 'null':
+ break
+
def wait_qmp_backup(self, device, error='Input/output error'):
event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED",
match={'data': {'device': device}})
self.assertNotEqual(event, None)
+ self.ignore_job_status_change_events()
try:
failure = self.dictpath(event, 'data/error')
event = self.vm.event_wait(name='BLOCK_JOB_CANCELLED',
match={'data': {'device': device}})
self.assertNotEqual(event, None)
+ self.ignore_job_status_change_events()
def create_anchor_backup(self, drive=None):
if drive is None:
drive = self.drives[-1]
- res = self.do_qmp_backup(device=drive['id'], sync='full',
+ res = self.do_qmp_backup(job_id=drive['id'],
+ device=drive['id'], sync='full',
format=drive['fmt'], target=drive['backup'])
self.assertTrue(res)
self.files.append(drive['backup'])
if bitmap is None:
bitmap = self.bitmaps[-1]
_, reference = bitmap.last_target()
- res = self.do_qmp_backup(device=bitmap.drive['id'], sync='full',
+ res = self.do_qmp_backup(job_id=bitmap.drive['id'],
+ device=bitmap.drive['id'], sync='full',
format=bitmap.drive['fmt'], target=reference)
self.assertTrue(res)
parent, _ = bitmap.last_target()
target = self.prepare_backup(bitmap, parent)
- res = self.do_qmp_backup(device=bitmap.drive['id'],
+ res = self.do_qmp_backup(job_id=bitmap.drive['id'],
+ device=bitmap.drive['id'],
sync='incremental', bitmap=bitmap.name,
format=bitmap.drive['fmt'], target=target,
mode='existing')
(('0xab', 0, 512),
('0xfe', '16M', '256k'),
('0x64', '32736k', '64k')))
-
+ # Check the dirty bitmap stats
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/dirty-bitmaps[0]/name', 'bitmap0')
+ self.assert_qmp(result, 'return[0]/dirty-bitmaps[0]/count', 458752)
+ self.assert_qmp(result, 'return[0]/dirty-bitmaps[0]/granularity', 65536)
+ self.assert_qmp(result, 'return[0]/dirty-bitmaps[0]/status', 'active')
# Prepare a cluster_size=128k backup target without a backing file.
(target, _) = bitmap0.new_target()
self.check_backups()
- def test_transaction_failure(self):
- '''Test: Verify backups made from a transaction that partially fails.
-
- Add a second drive with its own unique pattern, and add a bitmap to each
- drive. Use blkdebug to interfere with the backup on just one drive and
- attempt to create a coherent incremental backup across both drives.
-
- verify a failure in one but not both, then delete the failed stubs and
- re-run the same transaction.
-
- verify that both incrementals are created successfully.
- '''
-
+ def do_transaction_failure_test(self, race=False):
# Create a second drive, with pattern:
drive1 = self.add_node('drive1')
self.img_create(drive1['file'], drive1['fmt'])
('0xcd', '32M', '124k')))
# Create a blkdebug interface to this img as 'drive1'
- result = self.vm.qmp('blockdev-add', options={
- 'id': drive1['id'],
- 'driver': drive1['fmt'],
- 'file': {
+ result = self.vm.qmp('blockdev-add',
+ node_name=drive1['id'],
+ driver=drive1['fmt'],
+ file={
'driver': 'blkdebug',
'image': {
'driver': 'file',
'once': True
}],
}
- })
+ )
self.assert_qmp(result, 'return', {})
# Create bitmaps and full backups for both drives
self.assertFalse(self.vm.get_qmp_events(wait=False))
# Emulate some writes
- self.hmp_io_writes(drive0['id'], (('0xab', 0, 512),
- ('0xfe', '16M', '256k'),
- ('0x64', '32736k', '64k')))
+ if not race:
+ self.hmp_io_writes(drive0['id'], (('0xab', 0, 512),
+ ('0xfe', '16M', '256k'),
+ ('0x64', '32736k', '64k')))
self.hmp_io_writes(drive1['id'], (('0xba', 0, 512),
('0xef', '16M', '256k'),
('0x46', '32736k', '64k')))
target1 = self.prepare_backup(dr1bm0)
# Ask for a new incremental backup per-each drive,
- # expecting drive1's backup to fail:
+ # expecting drive1's backup to fail. In the 'race' test,
+ # we expect drive1 to attempt to cancel the empty drive0 job.
transaction = [
transaction_drive_backup(drive0['id'], target0, sync='incremental',
format=drive0['fmt'], mode='existing',
self.assert_no_active_block_jobs()
# Delete drive0's successful target and eliminate our record of the
- # unsuccessful drive1 target. Then re-run the same transaction.
+ # unsuccessful drive1 target.
dr0bm0.del_target()
dr1bm0.del_target()
+ if race:
+ # Don't re-run the transaction, we only wanted to test the race.
+ self.vm.shutdown()
+ return
+
+ # Re-run the same transaction:
target0 = self.prepare_backup(dr0bm0)
target1 = self.prepare_backup(dr1bm0)
self.vm.shutdown()
self.check_backups()
+ def test_transaction_failure(self):
+ '''Test: Verify backups made from a transaction that partially fails.
+
+ Add a second drive with its own unique pattern, and add a bitmap to each
+ drive. Use blkdebug to interfere with the backup on just one drive and
+ attempt to create a coherent incremental backup across both drives.
+
+ verify a failure in one but not both, then delete the failed stubs and
+ re-run the same transaction.
+
+ verify that both incrementals are created successfully.
+ '''
+ self.do_transaction_failure_test()
+
+ def test_transaction_failure_race(self):
+ '''Test: Verify that transactions with jobs that have no data to
+ transfer do not cause race conditions in the cancellation of the entire
+ transaction job group.
+ '''
+ self.do_transaction_failure_test(race=True)
+
def test_sync_dirty_bitmap_missing(self):
self.assert_no_active_block_jobs()
'''
drive0 = self.drives[0]
- result = self.vm.qmp('blockdev-add', options={
- 'id': drive0['id'],
- 'driver': drive0['fmt'],
- 'file': {
+ result = self.vm.qmp('blockdev-add',
+ node_name=drive0['id'],
+ driver=drive0['fmt'],
+ file={
'driver': 'blkdebug',
'image': {
'driver': 'file',
'once': True
}],
}
- })
+ )
self.assert_qmp(result, 'return', {})
self.create_anchor_backup(drive0)