Skip to content

Commit

Permalink
swift backup service checks version during restore
Browse files Browse the repository at this point in the history
Modified swift backup service to check metadata version during restore
and raise an error if the backup version isn't a version that the
service knows how to handle. The versions which can be handled are
described in a dictionary mapping versions to methods which can handle
them. This will facilitate graceful handling of newer backup formats by
the swift backup service when we introduce changes.

Fixes bug: 1136174

Change-Id: Id7d05848fd448ce21f641e5cd6945477702cbe38
  • Loading branch information
Stephen Mulcahy committed Mar 4, 2013
1 parent f211c15 commit 15962a4
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 22 deletions.
55 changes: 34 additions & 21 deletions cinder/backup/services/swift.py
Expand Up @@ -78,6 +78,7 @@ class SwiftBackupService(base.Base):
"""Provides backup, restore and delete of backup objects within Swift."""

SERVICE_VERSION = '1.0.0'
SERVICE_VERSION_MAPPING = {'1.0.0': '_restore_v1'}

def _get_compressor(self, algorithm):
try:
Expand Down Expand Up @@ -194,11 +195,10 @@ def _read_metadata(self, backup):
(resp, body) = self.conn.get_object(container, filename)
metadata = json.loads(body)
LOG.debug(_('_read_metadata finished (%s)') % metadata)
return metadata['objects']
return metadata

def backup(self, backup, volume_file):
"""Backup the given volume to swift using the given backup metadata.
"""
"""Backup the given volume to swift using the given backup metadata."""
backup_id = backup['id']
volume_id = backup['volume_id']
volume = self.db.volume_get(self.context, volume_id)
Expand Down Expand Up @@ -275,32 +275,20 @@ def backup(self, backup, volume_file):
object_id})
LOG.debug(_('backup %s finished.') % backup_id)

def restore(self, backup, volume_id, volume_file):
"""Restore the given volume backup from swift.
"""
def _restore_v1(self, backup, volume_id, metadata, volume_file):
"""Restore a v1 swift volume backup from swift."""
backup_id = backup['id']
LOG.debug(_('v1 swift volume backup restore of %s started'), backup_id)
container = backup['container']
volume = self.db.volume_get(self.context, volume_id)
volume_size = volume['size']
backup_size = backup['size']

object_prefix = backup['service_metadata']
LOG.debug(_('starting restore of backup %(object_prefix)s from swift'
' container: %(container)s, to volume %(volume_id)s, '
'backup: %(backup_id)s') % locals())
swift_object_names = self._generate_object_names(backup)
try:
metadata_objects = self._read_metadata(backup)
except socket.error as err:
raise exception.SwiftConnectionFailed(reason=str(err))
metadata_objects = metadata['objects']
metadata_object_names = []
for metadata_object in metadata_objects:
metadata_object_names.extend(metadata_object.keys())
LOG.debug(_('metadata_object_names = %s') % metadata_object_names)
prune_list = [self._metadata_filename(backup)]
swift_object_names = [swift_object_name for swift_object_name in
swift_object_names if swift_object_name
not in prune_list]
self._generate_object_names(backup)
if swift_object_name not in prune_list]
if sorted(swift_object_names) != sorted(metadata_object_names):
err = _('restore_backup aborted, actual swift object list in '
'swift does not match object list stored in metadata')
Expand Down Expand Up @@ -332,6 +320,31 @@ def restore(self, backup, volume_id, volume_file):
# threads can run, allowing for among other things the service
# status to be updated
eventlet.sleep(0)
LOG.debug(_('v1 swift volume backup restore of %s finished'),
backup_id)

def restore(self, backup, volume_id, volume_file):
"""Restore the given volume backup from swift."""
backup_id = backup['id']
container = backup['container']
object_prefix = backup['service_metadata']
LOG.debug(_('starting restore of backup %(object_prefix)s from swift'
' container: %(container)s, to volume %(volume_id)s, '
'backup: %(backup_id)s') % locals())
try:
metadata = self._read_metadata(backup)
except socket.error as err:
raise exception.SwiftConnectionFailed(reason=str(err))
metadata_version = metadata['version']
LOG.debug(_('Restoring swift backup version %s'), metadata_version)
try:
restore_func = getattr(self, self.SERVICE_VERSION_MAPPING.get(
metadata_version))
except TypeError:
err = (_('No support to restore swift backup version %s')
% metadata_version)
raise exception.InvalidBackup(reason=err)
restore_func(backup, volume_id, metadata, volume_file)
LOG.debug(_('restore %(backup_id)s to %(volume_id)s finished.') %
locals())

Expand Down
5 changes: 4 additions & 1 deletion cinder/tests/backup/fake_swift_client.py
Expand Up @@ -76,7 +76,10 @@ def get_object(self, container, name):
if 'metadata' in name:
fake_object_header = None
metadata = {}
metadata['version'] = '1.0.0'
if container == 'unsupported_version':
metadata['version'] = '9.9.9'
else:
metadata['version'] = '1.0.0'
metadata['backup_id'] = 123
metadata['volume_id'] = 123
metadata['backup_name'] = 'fake backup'
Expand Down
11 changes: 11 additions & 0 deletions cinder/tests/test_backup_swift.py
Expand Up @@ -161,6 +161,17 @@ def test_restore_wraps_socket_error(self):
service.restore,
backup, '1234-5678-1234-8888', volume_file)

def test_restore_unsupported_version(self):
container_name = 'unsupported_version'
self._create_backup_db_entry(container=container_name)
service = SwiftBackupService(self.ctxt)

with tempfile.NamedTemporaryFile() as volume_file:
backup = db.backup_get(self.ctxt, 123)
self.assertRaises(exception.InvalidBackup,
service.restore,
backup, '1234-5678-1234-8888', volume_file)

def test_delete(self):
self._create_backup_db_entry()
service = SwiftBackupService(self.ctxt)
Expand Down

0 comments on commit 15962a4

Please sign in to comment.