swift backup service checks version during restore

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
This commit is contained in:
Stephen Mulcahy 2013-03-04 16:10:34 +00:00
parent f211c150cb
commit 15962a4d13
3 changed files with 49 additions and 22 deletions

View File

@ -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:
@ -194,11 +195,10 @@ class SwiftBackupService(base.Base):
(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)
@ -275,32 +275,20 @@ class SwiftBackupService(base.Base):
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')
@ -332,6 +320,31 @@ class SwiftBackupService(base.Base):
# 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())

View File

@ -76,7 +76,10 @@ class FakeSwiftConnection(object):
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'

View File

@ -161,6 +161,17 @@ class BackupSwiftTestCase(test.TestCase):
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)