def _diff_restore_allowed(self, base_name, backup, volume, volume_file,
rados_client):
- """Determine whether a differential restore is possible/allowed.
+ """Determine if differential restore is possible and restore point.
+
+ Determine whether a differential restore is possible/allowed,
+ and find out the restore point if backup base is diff-format.
In order for a differential restore to be performed we need:
* destination volume must be RBD
* destination volume must have zero extents
* backup base image must exist
* backup must have a restore point
+ * target volume is different from source volume of backup
Returns True if differential restore is allowed, False otherwise.
+ Return the restore point if back base is diff-format.
"""
- not_allowed = (False, None)
-
- if self._file_is_rbd(volume_file):
- # NOTE(dosaboy): base_name here must be diff format.
- rbd_exists, base_name = self._rbd_image_exists(base_name,
- backup['volume_id'],
- rados_client)
+ # NOTE(dosaboy): base_name here must be diff format.
+ rbd_exists, base_name = self._rbd_image_exists(base_name,
+ backup['volume_id'],
+ rados_client)
+
+ if not rbd_exists:
+ return False, None
+
+ # Get the restore point. If no restore point is found, we assume
+ # that the backup was not performed using diff/incremental methods
+ # so we enforce full copy.
+ restore_point = self._get_restore_point(base_name, backup['id'])
+
+ if restore_point:
+ if self._file_is_rbd(volume_file):
+
+ # If the volume we are restoring to is the volume the backup
+ # was made from, force a full restore since a diff will not
+ # work in this case.
+ if volume['id'] == backup['volume_id']:
+ LOG.debug("Destination volume is same as backup source "
+ "volume %s - forcing full copy.", volume['id'])
+ return False, restore_point
- if not rbd_exists:
- return not_allowed
-
- # Get the restore point. If no restore point is found, we assume
- # that the backup was not performed using diff/incremental methods
- # so we enforce full copy.
- restore_point = self._get_restore_point(base_name, backup['id'])
-
- # If the volume we are restoring to is the volume the backup was
- # made from, force a full restore since a diff will not work in
- # this case.
- if volume['id'] == backup['volume_id']:
- LOG.debug("Destination volume is same as backup source volume "
- "%s - forcing full copy.", volume['id'])
- return False, restore_point
-
- if restore_point:
# If the destination volume has extents we cannot allow a diff
# restore.
if self._rbd_has_extents(volume_file.rbd_image):
return False, restore_point
return True, restore_point
- else:
- LOG.info(_LI("No restore point found for "
- "backup='%(backup)s' of "
- "volume %(volume)s - forcing full copy."),
- {'backup': backup['id'],
- 'volume': backup['volume_id']})
-
- return not_allowed
+ else:
+ LOG.info(_LI("No restore point found for backup="
+ "'%(backup)s' of volume %(volume)s "
+ "although base image is found - "
+ "forcing full copy."),
+ {'backup': backup['id'],
+ 'volume': backup['volume_id']})
+ return False, restore_point
def _restore_volume(self, backup, volume, volume_file):
"""Restore volume from backup using diff transfer if possible.
self.assertEqual(RAISED_EXCEPTIONS, [MockImageNotFoundException])
@common_mocks
- def test_diff_restore_allowed(self):
+ def test_diff_restore_allowed_with_image_not_exists(self):
+ '''Test diff restore not allowed when backup not diff-format.'''
not_allowed = (False, None)
backup_base = 'backup.base'
- restore_point = 'backup.snap.1'
rbd_io = self._get_wrapped_rbd_io(self.service.rbd.Image())
args_vols_different = [backup_base, self.backup, self.alt_volume,
rbd_io, self.mock_rados]
- args_vols_same = [backup_base, self.backup, self.volume, rbd_io,
- self.mock_rados]
- # 1. destination volume is not an rbd
- with mock.patch.object(self.service, '_file_is_rbd') as \
- mock_file_is_rbd:
- mock_file_is_rbd.return_value = False
+ with mock.patch.object(self.service, '_rbd_image_exists') as \
+ mock_rbd_image_exists:
+ mock_rbd_image_exists.return_value = (False, backup_base)
+
resp = self.service._diff_restore_allowed(*args_vols_different)
- self.assertEqual(resp, not_allowed)
- self.assertTrue(mock_file_is_rbd.called)
- # 1. destination volume is an rbd
- # 2. backup base is not diff-format
- with mock.patch.object(self.service, '_file_is_rbd') as \
- mock_file_is_rbd:
- mock_file_is_rbd.return_value = True
- with mock.patch.object(self.service, '_rbd_image_exists') as \
- mock_rbd_image_exists:
- mock_rbd_image_exists.return_value = (False, backup_base)
- resp = self.service._diff_restore_allowed(*args_vols_different)
- self.assertEqual(resp, not_allowed)
- self.assertTrue(mock_file_is_rbd.called)
+ self.assertEqual(not_allowed, resp)
+ mock_rbd_image_exists.assert_called_once_with(
+ backup_base,
+ self.backup['volume_id'],
+ self.mock_rados)
+
+ @common_mocks
+ def test_diff_restore_allowed_with_no_restore_point(self):
+ '''Test diff restore not allowed when no restore point found.
+
+ Detail conditions:
+ 1. backup base is diff-format
+ 2. restore point does not exist
+ '''
+ not_allowed = (False, None)
+ backup_base = 'backup.base'
+ rbd_io = self._get_wrapped_rbd_io(self.service.rbd.Image())
+ args_vols_different = [backup_base, self.backup, self.alt_volume,
+ rbd_io, self.mock_rados]
+
+ with mock.patch.object(self.service, '_rbd_image_exists') as \
+ mock_rbd_image_exists:
+ mock_rbd_image_exists.return_value = (True, backup_base)
+ with mock.patch.object(self.service, '_get_restore_point') as \
+ mock_get_restore_point:
+ mock_get_restore_point.return_value = None
+
+ args = args_vols_different
+ resp = self.service._diff_restore_allowed(*args)
+
+ self.assertEqual(not_allowed, resp)
self.assertTrue(mock_rbd_image_exists.called)
+ mock_get_restore_point.assert_called_once_with(
+ backup_base,
+ self.backup['id'])
- # 1. destination volume is an rbd
- # 2. backup base is diff-format
- # 3. restore point does not exist
- # 4. source and destination volumes are different
- with mock.patch.object(self.service, '_file_is_rbd') as \
- mock_file_is_rbd:
- mock_file_is_rbd.return_value = True
- with mock.patch.object(self.service, '_rbd_image_exists') as \
- mock_rbd_image_exists:
- mock_rbd_image_exists.return_value = (True, backup_base)
- with mock.patch.object(self.service, '_get_restore_point') as \
- mock_get_restore_point:
- mock_get_restore_point.return_value = None
+ @common_mocks
+ def test_diff_restore_allowed_with_not_rbd(self):
+ '''Test diff restore not allowed when destination volume is not rbd.
+
+ Detail conditions:
+ 1. backup base is diff-format
+ 2. restore point exists
+ 3. destination volume is not an rbd.
+ '''
+ backup_base = 'backup.base'
+ restore_point = 'backup.snap.1'
+ rbd_io = self._get_wrapped_rbd_io(self.service.rbd.Image())
+ args_vols_different = [backup_base, self.backup, self.alt_volume,
+ rbd_io, self.mock_rados]
+
+ with mock.patch.object(self.service, '_rbd_image_exists') as \
+ mock_rbd_image_exists:
+ mock_rbd_image_exists.return_value = (True, backup_base)
+ with mock.patch.object(self.service, '_get_restore_point') as \
+ mock_get_restore_point:
+ mock_get_restore_point.return_value = restore_point
+ with mock.patch.object(self.service, '_file_is_rbd') as \
+ mock_file_is_rbd:
+ mock_file_is_rbd.return_value = False
args = args_vols_different
resp = self.service._diff_restore_allowed(*args)
- self.assertEqual(resp, not_allowed)
- self.assertTrue(mock_file_is_rbd.called)
+ self.assertEqual((False, restore_point), resp)
self.assertTrue(mock_rbd_image_exists.called)
self.assertTrue(mock_get_restore_point.called)
+ mock_file_is_rbd.assert_called_once_with(
+ rbd_io)
- # 1. destination volume is an rbd
- # 2. backup base is diff-format
- # 3. restore point does not exist
- # 4. source and destination volumes are the same
- with mock.patch.object(self.service, '_file_is_rbd') as \
- mock_file_is_rbd:
- mock_file_is_rbd.return_value = True
- with mock.patch.object(self.service, '_rbd_image_exists') as \
- mock_rbd_image_exists:
- mock_rbd_image_exists.return_value = (True, backup_base)
- with mock.patch.object(self.service, '_get_restore_point') as \
- mock_get_restore_point:
- mock_get_restore_point.return_value = None
+ @common_mocks
+ def test_diff_restore_allowed_with_same_volume(self):
+ '''Test diff restore not allowed when volumes are same.
+
+ Detail conditions:
+ 1. backup base is diff-format
+ 2. restore point exists
+ 3. destination volume is an rbd
+ 4. source and destination volumes are the same
+ '''
+ backup_base = 'backup.base'
+ restore_point = 'backup.snap.1'
+ rbd_io = self._get_wrapped_rbd_io(self.service.rbd.Image())
+ args_vols_same = [backup_base, self.backup, self.volume, rbd_io,
+ self.mock_rados]
+
+ with mock.patch.object(self.service, '_rbd_image_exists') as \
+ mock_rbd_image_exists:
+ mock_rbd_image_exists.return_value = (True, backup_base)
+ with mock.patch.object(self.service, '_get_restore_point') as \
+ mock_get_restore_point:
+ mock_get_restore_point.return_value = restore_point
+ with mock.patch.object(self.service, '_file_is_rbd') as \
+ mock_file_is_rbd:
+ mock_file_is_rbd.return_value = True
resp = self.service._diff_restore_allowed(*args_vols_same)
- self.assertEqual(resp, not_allowed)
- self.assertTrue(mock_file_is_rbd.called)
+ self.assertEqual((False, restore_point), resp)
self.assertTrue(mock_rbd_image_exists.called)
self.assertTrue(mock_get_restore_point.called)
+ self.assertTrue(mock_file_is_rbd.called)
- # 1. destination volume is an rbd
- # 2. backup base is diff-format
- # 3. restore point exists
- # 4. source and destination volumes are different
- # 5. destination volume has data on it - full copy is mandated
- with mock.patch.object(self.service, '_file_is_rbd') as \
- mock_file_is_rbd:
- mock_file_is_rbd.return_value = True
- with mock.patch.object(self.service, '_rbd_image_exists') as \
- mock_rbd_image_exists:
- mock_rbd_image_exists.return_value = (True, backup_base)
- with mock.patch.object(self.service, '_get_restore_point') as \
- mock_get_restore_point:
- mock_get_restore_point.return_value = restore_point
+ @common_mocks
+ def test_diff_restore_allowed_with_has_extents(self):
+ '''Test diff restore not allowed when destination volume has data.
+
+ Detail conditions:
+ 1. backup base is diff-format
+ 2. restore point exists
+ 3. destination volume is an rbd
+ 4. source and destination volumes are different
+ 5. destination volume has data on it - full copy is mandated
+ '''
+ backup_base = 'backup.base'
+ restore_point = 'backup.snap.1'
+ rbd_io = self._get_wrapped_rbd_io(self.service.rbd.Image())
+ args_vols_different = [backup_base, self.backup, self.alt_volume,
+ rbd_io, self.mock_rados]
+
+ with mock.patch.object(self.service, '_rbd_image_exists') as \
+ mock_rbd_image_exists:
+ mock_rbd_image_exists.return_value = (True, backup_base)
+ with mock.patch.object(self.service, '_get_restore_point') as \
+ mock_get_restore_point:
+ mock_get_restore_point.return_value = restore_point
+ with mock.patch.object(self.service, '_file_is_rbd') as \
+ mock_file_is_rbd:
+ mock_file_is_rbd.return_value = True
with mock.patch.object(self.service, '_rbd_has_extents') \
as mock_rbd_has_extents:
mock_rbd_has_extents.return_value = True
args = args_vols_different
resp = self.service._diff_restore_allowed(*args)
- self.assertEqual(resp, (False, restore_point))
- self.assertTrue(mock_file_is_rbd.called)
+ self.assertEqual((False, restore_point), resp)
self.assertTrue(mock_rbd_image_exists.called)
self.assertTrue(mock_get_restore_point.called)
- self.assertTrue(mock_rbd_has_extents.called)
+ self.assertTrue(mock_file_is_rbd.called)
+ mock_rbd_has_extents.assert_called_once_with(
+ rbd_io.rbd_image)
- # 1. destination volume is an rbd
- # 2. backup base is diff-format
- # 3. restore point exists
- # 4. source and destination volumes are different
- # 5. destination volume no data on it
- with mock.patch.object(self.service, '_file_is_rbd') as \
- mock_file_is_rbd:
- mock_file_is_rbd.return_value = True
- with mock.patch.object(self.service, '_rbd_image_exists') as \
- mock_rbd_image_exists:
- mock_rbd_image_exists.return_value = (True, backup_base)
- with mock.patch.object(self.service, '_get_restore_point') as \
- mock_restore_point:
- mock_restore_point.return_value = restore_point
+ @common_mocks
+ def test_diff_restore_allowed_with_no_extents(self):
+ '''Test diff restore allowed when no data in destination volume.
+
+ Detail conditions:
+ 1. backup base is diff-format
+ 2. restore point exists
+ 3. destination volume is an rbd
+ 4. source and destination volumes are different
+ 5. destination volume no data on it
+ '''
+ backup_base = 'backup.base'
+ restore_point = 'backup.snap.1'
+ rbd_io = self._get_wrapped_rbd_io(self.service.rbd.Image())
+ args_vols_different = [backup_base, self.backup, self.alt_volume,
+ rbd_io, self.mock_rados]
+
+ with mock.patch.object(self.service, '_rbd_image_exists') as \
+ mock_rbd_image_exists:
+ mock_rbd_image_exists.return_value = (True, backup_base)
+ with mock.patch.object(self.service, '_get_restore_point') as \
+ mock_get_restore_point:
+ mock_get_restore_point.return_value = restore_point
+ with mock.patch.object(self.service, '_file_is_rbd') as \
+ mock_file_is_rbd:
+ mock_file_is_rbd.return_value = True
with mock.patch.object(self.service, '_rbd_has_extents') \
as mock_rbd_has_extents:
mock_rbd_has_extents.return_value = False
args = args_vols_different
resp = self.service._diff_restore_allowed(*args)
- self.assertEqual(resp, (True, restore_point))
- self.assertTrue(mock_restore_point.called)
- self.assertTrue(mock_rbd_has_extents.called)
+ self.assertEqual((True, restore_point), resp)
self.assertTrue(mock_rbd_image_exists.called)
+ self.assertTrue(mock_get_restore_point.called)
self.assertTrue(mock_file_is_rbd.called)
+ self.assertTrue(mock_rbd_has_extents.called)
@common_mocks
@mock.patch('fcntl.fcntl', spec=True)