"""Base class for all backup drivers."""
import abc
-import base64
from oslo_config import cfg
from oslo_log import log as logging
return
def export_record(self, backup):
- """Export backup record.
+ """Export driver specific backup record information.
- Default backup driver implementation.
- Serialize the backup record describing the backup into a string.
+ If backup backend needs additional driver specific information to
+ import backup record back into the system it must overwrite this method
+ and return it here as a dictionary so it can be serialized into a
+ string.
- :param backup: backup entry to export
- :returns backup_url - a string describing the backup record
+ Default backup driver implementation has no extra information.
+
+ :param backup: backup object to export
+ :returns driver_info - dictionary with extra information
"""
- retval = jsonutils.dumps(backup)
- if six.PY3:
- retval = retval.encode('utf-8')
- return base64.encodestring(retval)
+ return {}
+
+ def import_record(self, backup, driver_info):
+ """Import driver specific backup record information.
- def import_record(self, backup_url):
- """Import and verify backup record.
+ If backup backend needs additional driver specific information to
+ import backup record back into the system it must overwrite this method
+ since it will be called with the extra information that was provided by
+ export_record when exporting the backup.
- Default backup driver implementation.
- De-serialize the backup record into a dictionary, so we can
- update the database.
+ Default backup driver implementation does nothing since it didn't
+ export any specific data in export_record.
- :param backup_url: driver specific backup record string
- :returns dictionary object with database updates
+ :param backup: backup object to export
+ :param driver_info: dictionary with driver specific backup record
+ information
+ :returns nothing
"""
- return jsonutils.loads(base64.decodestring(backup_url))
+ return
@six.add_metaclass(abc.ABCMeta)
try:
utils.require_driver_initialized(self.driver)
backup_service = self.service.get_backup_driver(context)
- backup_url = backup_service.export_record(backup)
+ driver_info = backup_service.export_record(backup)
+ backup_url = backup.encode_record(driver_info=driver_info)
backup_record['backup_url'] = backup_url
except Exception as err:
msg = six.text_type(err)
else:
# Yes...
try:
+ # Deserialize backup record information
+ backup_options = backup.decode_record(backup_url)
+
+ # Extract driver specific info and pass it to the driver
+ driver_options = backup_options.pop('driver_info', {})
utils.require_driver_initialized(self.driver)
backup_service = self.service.get_backup_driver(context)
- backup_options = backup_service.import_record(backup_url)
+ backup_service.import_record(backup, driver_options)
except Exception as err:
msg = six.text_type(err)
self._update_backup_error(backup, context, msg)
# License for the specific language governing permissions and limitations
# under the License.
+import base64
+import binascii
+
from oslo_config import cfg
from oslo_log import log as logging
+from oslo_serialization import jsonutils
from oslo_versionedobjects import fields
+import six
from cinder import db
from cinder import exception
+from cinder.i18n import _
from cinder import objects
from cinder.objects import base
from cinder import utils
with self.obj_as_admin():
db.backup_destroy(self._context, self.id)
+ @staticmethod
+ def decode_record(backup_url):
+ """Deserialize backup metadata from string into a dictionary.
+
+ :raises: InvalidInput
+ """
+ try:
+ return jsonutils.loads(base64.decodestring(backup_url))
+ except binascii.Error:
+ msg = _("Can't decode backup record.")
+ except ValueError:
+ msg = _("Can't parse backup record.")
+ raise exception.InvalidInput(reason=msg)
+
+ @base.remotable
+ def encode_record(self, **kwargs):
+ """Serialize backup object, with optional extra info, into a string."""
+ kwargs.update(self)
+ retval = jsonutils.dumps(kwargs)
+ if six.PY3:
+ retval = retval.encode('utf-8')
+ return base64.encodestring(retval)
+
@base.CinderObjectRegistry.register
class BackupList(base.ObjectListBase, base.CinderObject):
import mock
from cinder import context
+from cinder import exception
from cinder import objects
from cinder.tests.unit import fake_volume
from cinder.tests.unit import objects as test_objects
self.assertEqual('2', backup.temp_volume_id)
self.assertEqual('3', backup.temp_snapshot_id)
+ def test_import_record(self):
+ backup = objects.Backup(context=self.context, id=1)
+ export_string = backup.encode_record()
+ imported_backup = objects.Backup.decode_record(export_string)
+
+ # Make sure we don't lose data when converting from string
+ self.assertDictEqual(dict(backup), imported_backup)
+
+ def test_import_record_additional_info(self):
+ backup = objects.Backup(context=self.context, id=1)
+ extra_info = {'driver': {'key1': 'value1', 'key2': 'value2'}}
+ extra_info_copy = extra_info.copy()
+ export_string = backup.encode_record(extra_info=extra_info)
+ imported_backup = objects.Backup.decode_record(export_string)
+
+ # Dictionary passed should not be modified
+ self.assertDictEqual(extra_info_copy, extra_info)
+
+ # Make sure we don't lose data when converting from string and that
+ # extra info is still there
+ expected = dict(backup)
+ expected['extra_info'] = extra_info
+ self.assertDictEqual(expected, imported_backup)
+
+ def test_import_record_additional_info_cant_overwrite(self):
+ backup = objects.Backup(context=self.context, id=1)
+ export_string = backup.encode_record(id='fake_id')
+ imported_backup = objects.Backup.decode_record(export_string)
+
+ # Make sure the extra_info can't overwrite basic data
+ self.assertDictEqual(dict(backup), imported_backup)
+
+ def test_import_record_decoding_error(self):
+ export_string = '123456'
+ self.assertRaises(exception.InvalidInput,
+ objects.Backup.decode_record,
+ export_string)
+
+ def test_import_record_parsing_error(self):
+ export_string = ''
+ self.assertRaises(exception.InvalidInput,
+ objects.Backup.decode_record,
+ export_string)
+
class TestBackupList(test_objects.BaseObjectsTestCase):
@mock.patch('cinder.db.backup_get_all', return_value=[fake_backup])
# under the License.
""" Tests for the backup service base driver. """
-import base64
import uuid
import mock
self.driver.put_metadata(self.volume_id, json_metadata)
def test_export_record(self):
- export_string = self.driver.export_record(self.backup)
- export_dict = jsonutils.loads(base64.decodestring(export_string))
- # Make sure we don't lose data when converting to string
- for key in _backup_db_fields:
- self.assertTrue(key in export_dict)
- self.assertEqual(export_dict[key], self.backup[key])
+ export_record = self.driver.export_record(self.backup)
+ self.assertDictEqual({}, export_record)
def test_import_record(self):
- export_string = self.driver.export_record(self.backup)
- imported_backup = self.driver.import_record(export_string)
- # Make sure we don't lose data when converting from string
- for key in _backup_db_fields:
- self.assertTrue(key in imported_backup)
- self.assertEqual(self.backup[key], imported_backup[key])
+ export_record = {'key1': 'value1'}
+ self.assertIsNone(self.driver.import_record(self.backup,
+ export_record))
class BackupMetadataAPITestCase(test.TestCase):