Convert backup_device to OVO
This commit introduces BackupDevice object to formalize data earlier sent over RPC as an undefined dict. This is required to be able to make non-backward compatible changes to data sent as this parameter while maintaining compatibility with previous release - so to support rolling upgrades. Change-Id: Ie57d84e32ec1c5fcfac27a7bb6d4bbb189108a5b Partial-Implements: blueprint cinder-objects
This commit is contained in:
parent
808038053f
commit
a08aa7ad79
|
@ -425,17 +425,18 @@ class BackupManager(manager.SchedulerDependentManager):
|
|||
backup_service = self.service.get_backup_driver(context)
|
||||
|
||||
properties = utils.brick_get_connector_properties()
|
||||
backup_dic = self.volume_rpcapi.get_backup_device(context,
|
||||
backup, volume)
|
||||
try:
|
||||
backup_device = backup_dic.get('backup_device')
|
||||
is_snapshot = backup_dic.get('is_snapshot')
|
||||
attach_info = self._attach_device(context, backup_device,
|
||||
properties, is_snapshot)
|
||||
backup_device = self.volume_rpcapi.get_backup_device(context,
|
||||
backup,
|
||||
volume)
|
||||
attach_info = self._attach_device(context,
|
||||
backup_device.device_obj,
|
||||
properties,
|
||||
backup_device.is_snapshot)
|
||||
try:
|
||||
device_path = attach_info['device']['path']
|
||||
if isinstance(device_path, six.string_types):
|
||||
if backup_dic.get('secure_enabled', False):
|
||||
if backup_device.secure_enabled:
|
||||
with open(device_path) as device_file:
|
||||
backup_service.backup(backup, device_file)
|
||||
else:
|
||||
|
@ -448,8 +449,8 @@ class BackupManager(manager.SchedulerDependentManager):
|
|||
|
||||
finally:
|
||||
self._detach_device(context, attach_info,
|
||||
backup_device, properties,
|
||||
is_snapshot)
|
||||
backup_device.device_obj, properties,
|
||||
backup_device.is_snapshot)
|
||||
finally:
|
||||
backup = objects.Backup.get_by_id(context, backup.id)
|
||||
self._cleanup_temp_volumes_snapshots_when_backup_created(
|
||||
|
|
|
@ -211,3 +211,60 @@ class BackupImport(Backup):
|
|||
|
||||
db_backup = db.backup_create(self._context, updates)
|
||||
self._from_db_object(self._context, self, db_backup)
|
||||
|
||||
|
||||
@base.CinderObjectRegistry.register
|
||||
class BackupDeviceInfo(base.CinderObject, base.CinderObjectDictCompat,
|
||||
base.CinderComparableObject):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
fields = {
|
||||
'volume': fields.ObjectField('Volume', nullable=True),
|
||||
'snapshot': fields.ObjectField('Snapshot', nullable=True),
|
||||
'secure_enabled': fields.BooleanField(default=False),
|
||||
}
|
||||
obj_extra_fields = ['is_snapshot', 'device_obj']
|
||||
|
||||
@property
|
||||
def is_snapshot(self):
|
||||
if self.obj_attr_is_set('snapshot') == self.obj_attr_is_set('volume'):
|
||||
msg = _("Either snapshot or volume field should be set.")
|
||||
raise exception.ProgrammingError(message=msg)
|
||||
return self.obj_attr_is_set('snapshot')
|
||||
|
||||
@property
|
||||
def device_obj(self):
|
||||
return self.snapshot if self.is_snapshot else self.volume
|
||||
|
||||
# FIXME(sborkows): This should go away in early O as we stop supporting
|
||||
# backward compatibility with M.
|
||||
@classmethod
|
||||
def from_primitive(cls, primitive, context, expected_attrs=None):
|
||||
backup_device = BackupDeviceInfo()
|
||||
if primitive['is_snapshot']:
|
||||
if isinstance(primitive['backup_device'], objects.Snapshot):
|
||||
backup_device.snapshot = primitive['backup_device']
|
||||
else:
|
||||
backup_device.snapshot = objects.Snapshot._from_db_object(
|
||||
context, objects.Snapshot(), primitive['backup_device'],
|
||||
expected_attrs=expected_attrs)
|
||||
else:
|
||||
if isinstance(primitive['backup_device'], objects.Volume):
|
||||
backup_device.volume = primitive['backup_device']
|
||||
else:
|
||||
backup_device.volume = objects.Volume._from_db_object(
|
||||
context, objects.Volume(), primitive['backup_device'],
|
||||
expected_attrs=expected_attrs)
|
||||
backup_device.secure_enabled = primitive['secure_enabled']
|
||||
return backup_device
|
||||
|
||||
# FIXME(sborkows): This should go away in early O as we stop supporting
|
||||
# backward compatibility with M.
|
||||
def to_primitive(self, context):
|
||||
backup_device = (db.snapshot_get(context, self.snapshot.id)
|
||||
if self.is_snapshot
|
||||
else db.volume_get(context, self.volume.id))
|
||||
primitive = {'backup_device': backup_device,
|
||||
'secure_enabled': self.secure_enabled,
|
||||
'is_snapshot': self.is_snapshot}
|
||||
return primitive
|
||||
|
|
|
@ -120,6 +120,7 @@ OBJ_VERSIONS.add('1.12', {'VolumeType': '1.3'})
|
|||
OBJ_VERSIONS.add('1.13', {'CleanupRequest': '1.0'})
|
||||
OBJ_VERSIONS.add('1.14', {'VolumeAttachmentList': '1.1'})
|
||||
OBJ_VERSIONS.add('1.15', {'Volume': '1.6', 'Snapshot': '1.2'})
|
||||
OBJ_VERSIONS.add('1.16', {'BackupDeviceInfo': '1.0'})
|
||||
|
||||
|
||||
class CinderObjectRegistry(base.VersionedObjectRegistry):
|
||||
|
|
|
@ -30,7 +30,7 @@ CONF = cfg.CONF
|
|||
|
||||
@base.CinderObjectRegistry.register
|
||||
class Snapshot(cleanable.CinderCleanableObject, base.CinderObject,
|
||||
base.CinderObjectDictCompat):
|
||||
base.CinderObjectDictCompat, base.CinderComparableObject):
|
||||
# Version 1.0: Initial version
|
||||
# Version 1.1: Changed 'status' field to use SnapshotStatusField
|
||||
# Version 1.2: This object is now cleanable (adds rows to workers table)
|
||||
|
|
|
@ -587,9 +587,13 @@ class BackupTestCase(BaseBackupTest):
|
|||
backup = self._create_backup_db_entry(volume_id=vol_id)
|
||||
|
||||
vol = objects.Volume.get_by_id(self.ctxt, vol_id)
|
||||
mock_get_backup_device.return_value = {'backup_device': vol,
|
||||
'secure_enabled': False,
|
||||
'is_snapshot': False, }
|
||||
backup_device_dict = {'backup_device': vol, 'secure_enabled': False,
|
||||
'is_snapshot': False, }
|
||||
mock_get_backup_device.return_value = (
|
||||
objects.BackupDeviceInfo.from_primitive(backup_device_dict,
|
||||
self.ctxt,
|
||||
['admin_metadata',
|
||||
'metadata']))
|
||||
attach_info = {'device': {'path': '/dev/null'}}
|
||||
mock_detach_device = self.mock_object(self.backup_mgr,
|
||||
'_detach_device')
|
||||
|
@ -635,9 +639,11 @@ class BackupTestCase(BaseBackupTest):
|
|||
snap = self._create_snapshot_db_entry(volume_id = vol_id)
|
||||
|
||||
vol = objects.Volume.get_by_id(self.ctxt, vol_id)
|
||||
mock_get_backup_device.return_value = {'backup_device': snap,
|
||||
'secure_enabled': False,
|
||||
'is_snapshot': True, }
|
||||
mock_get_backup_device.return_value = (
|
||||
objects.BackupDeviceInfo.from_primitive({
|
||||
'backup_device': snap, 'secure_enabled': False,
|
||||
'is_snapshot': True, },
|
||||
self.ctxt, expected_attrs=['metadata']))
|
||||
|
||||
# TODO(walter-boring) This is to account for the missing FakeConnector
|
||||
# in os-brick 1.6.0 and >
|
||||
|
|
|
@ -22,6 +22,7 @@ from cinder import exception
|
|||
from cinder import objects
|
||||
from cinder.objects import fields
|
||||
from cinder.tests.unit import fake_constants as fake
|
||||
from cinder.tests.unit import fake_snapshot
|
||||
from cinder.tests.unit import fake_volume
|
||||
from cinder.tests.unit import objects as test_objects
|
||||
from cinder.tests.unit import utils
|
||||
|
@ -43,6 +44,13 @@ fake_backup = {
|
|||
'restore_volume_id': None,
|
||||
}
|
||||
|
||||
vol_props = {'status': 'available', 'size': 1}
|
||||
fake_vol = fake_volume.fake_db_volume(**vol_props)
|
||||
snap_props = {'status': fields.BackupStatus.AVAILABLE,
|
||||
'volume_id': fake_vol['id'],
|
||||
'expected_attrs': ['metadata']}
|
||||
fake_snap = fake_snapshot.fake_db_snapshot(**snap_props)
|
||||
|
||||
|
||||
class TestBackup(test_objects.BaseObjectsTestCase):
|
||||
|
||||
|
@ -237,3 +245,124 @@ class TestBackupList(test_objects.BaseObjectsTestCase):
|
|||
get_all_by_volume.assert_called_once_with(self.context,
|
||||
fake.VOLUME_ID, None)
|
||||
TestBackup._compare(self, fake_backup, backups[0])
|
||||
|
||||
|
||||
class BackupDeviceInfoTestCase(test_objects.BaseObjectsTestCase):
|
||||
def setUp(self):
|
||||
super(BackupDeviceInfoTestCase, self).setUp()
|
||||
self.vol_obj = fake_volume.fake_volume_obj(self.context, **vol_props)
|
||||
self.snap_obj = fake_snapshot.fake_snapshot_obj(self.context,
|
||||
**snap_props)
|
||||
self.backup_device_dict = {'secure_enabled': False,
|
||||
'is_snapshot': False, }
|
||||
|
||||
@mock.patch('cinder.db.volume_get', return_value=fake_vol)
|
||||
def test_from_primitive_with_volume(self, mock_fake_vol):
|
||||
vol_obj = self.vol_obj
|
||||
self.backup_device_dict['backup_device'] = vol_obj
|
||||
backup_device_info = objects.BackupDeviceInfo.from_primitive(
|
||||
self.backup_device_dict, self.context)
|
||||
self.assertFalse(backup_device_info.is_snapshot)
|
||||
self.assertEqual(self.backup_device_dict['secure_enabled'],
|
||||
backup_device_info.secure_enabled)
|
||||
self.assertEqual(vol_obj, backup_device_info.volume)
|
||||
|
||||
self.backup_device_dict['backup_device'] = fake_vol
|
||||
backup_device_info = objects.BackupDeviceInfo.from_primitive(
|
||||
self.backup_device_dict, self.context)
|
||||
vol_obj_from_db = objects.Volume._from_db_object(self.context,
|
||||
objects.Volume(),
|
||||
fake_vol)
|
||||
self.assertEqual(vol_obj_from_db, backup_device_info.volume)
|
||||
|
||||
@mock.patch('cinder.db.snapshot_get', return_value=fake_snap)
|
||||
def test_from_primitive_with_snapshot(self, mock_fake_snap):
|
||||
snap_obj = self.snap_obj
|
||||
self.backup_device_dict['is_snapshot'] = True
|
||||
self.backup_device_dict['backup_device'] = snap_obj
|
||||
backup_device_info = objects.BackupDeviceInfo.from_primitive(
|
||||
self.backup_device_dict, self.context, expected_attrs=['metadata'])
|
||||
self.assertTrue(backup_device_info.is_snapshot)
|
||||
self.assertEqual(self.backup_device_dict['secure_enabled'],
|
||||
backup_device_info.secure_enabled)
|
||||
self.assertEqual(snap_obj, backup_device_info.snapshot)
|
||||
|
||||
self.backup_device_dict['backup_device'] = fake_snap
|
||||
backup_device_info = objects.BackupDeviceInfo.from_primitive(
|
||||
self.backup_device_dict, self.context, expected_attrs=['metadata'])
|
||||
self.assertEqual(snap_obj, backup_device_info.snapshot)
|
||||
|
||||
@mock.patch('cinder.db.volume_get', return_value=fake_vol)
|
||||
def test_to_primitive_with_volume(self, mock_fake_vol):
|
||||
vol_obj = self.vol_obj
|
||||
self.backup_device_dict['backup_device'] = fake_vol
|
||||
backup_device_info = objects.BackupDeviceInfo()
|
||||
backup_device_info.volume = vol_obj
|
||||
backup_device_info.secure_enabled = (
|
||||
self.backup_device_dict['secure_enabled'])
|
||||
|
||||
backup_device_ret_dict = backup_device_info.to_primitive(self.context)
|
||||
self.assertEqual(self.backup_device_dict['secure_enabled'],
|
||||
backup_device_ret_dict['secure_enabled'])
|
||||
self.assertFalse(backup_device_ret_dict['is_snapshot'])
|
||||
self.assertEqual(self.backup_device_dict['backup_device'],
|
||||
backup_device_ret_dict['backup_device'])
|
||||
|
||||
@mock.patch('cinder.db.snapshot_get', return_value=fake_snap)
|
||||
def test_to_primitive_with_snapshot(self, mock_fake_snap):
|
||||
snap_obj = self.snap_obj
|
||||
backup_device_info = objects.BackupDeviceInfo()
|
||||
backup_device_info.snapshot = snap_obj
|
||||
backup_device_info.secure_enabled = (
|
||||
self.backup_device_dict['secure_enabled'])
|
||||
|
||||
backup_device_ret_dict = backup_device_info.to_primitive(self.context)
|
||||
self.assertEqual(self.backup_device_dict['secure_enabled'],
|
||||
backup_device_ret_dict['secure_enabled'])
|
||||
self.assertTrue(backup_device_ret_dict['is_snapshot'])
|
||||
# NOTE(sborkows): since volume in sqlalchemy snapshot is a sqlalchemy
|
||||
# object too, to compare snapshots we need to convert their volumes to
|
||||
# dicts.
|
||||
snap_actual_dict = fake_snap
|
||||
snap_ref_dict = backup_device_ret_dict['backup_device']
|
||||
snap_actual_dict['volume'] = self.vol_obj.obj_to_primitive()
|
||||
snap_ref_dict['volume'] = snap_ref_dict['volume']
|
||||
self.assertEqual(snap_actual_dict, snap_ref_dict)
|
||||
|
||||
def test_is_snapshot_both_volume_and_snapshot_raises_error(self):
|
||||
snap = self.snap_obj
|
||||
vol = self.vol_obj
|
||||
backup_device_info = objects.BackupDeviceInfo()
|
||||
backup_device_info.snapshot = snap
|
||||
backup_device_info.volume = vol
|
||||
backup_device_info.secure_enabled = (
|
||||
self.backup_device_dict['secure_enabled'])
|
||||
self.assertRaises(exception.ProgrammingError, getattr,
|
||||
backup_device_info, 'is_snapshot')
|
||||
|
||||
def test_is_snapshot_neither_volume_nor_snapshot_raises_error(self):
|
||||
backup_device_info = objects.BackupDeviceInfo()
|
||||
backup_device_info.secure_enabled = (
|
||||
self.backup_device_dict['secure_enabled'])
|
||||
self.assertRaises(exception.ProgrammingError, getattr,
|
||||
backup_device_info, 'is_snapshot')
|
||||
|
||||
def test_device_obj_with_volume(self):
|
||||
vol = self.vol_obj
|
||||
backup_device_info = objects.BackupDeviceInfo()
|
||||
backup_device_info.volume = vol
|
||||
backup_device_info.secure_enabled = (
|
||||
self.backup_device_dict['secure_enabled'])
|
||||
backup_device_obj = backup_device_info.device_obj
|
||||
self.assertIsInstance(backup_device_obj, objects.Volume)
|
||||
self.assertEqual(vol, backup_device_obj)
|
||||
|
||||
def test_device_obj_with_snapshot(self):
|
||||
snap = self.snap_obj
|
||||
backup_device_info = objects.BackupDeviceInfo()
|
||||
backup_device_info.snapshot = snap
|
||||
backup_device_info.secure_enabled = (
|
||||
self.backup_device_dict['secure_enabled'])
|
||||
backup_device_obj = backup_device_info.device_obj
|
||||
self.assertIsInstance(backup_device_obj, objects.Snapshot)
|
||||
self.assertEqual(snap, backup_device_obj)
|
||||
|
|
|
@ -24,6 +24,7 @@ from cinder import test
|
|||
# corresponding version bump in the affected objects.
|
||||
object_data = {
|
||||
'Backup': '1.4-c50f7a68bb4c400dd53dd219685b3992',
|
||||
'BackupDeviceInfo': '1.0-74b3950676c690538f4bc6796bd0042e',
|
||||
'BackupImport': '1.4-c50f7a68bb4c400dd53dd219685b3992',
|
||||
'BackupList': '1.0-15ecf022a68ddbb8c2a6739cfc9f8f5e',
|
||||
'CleanupRequest': '1.0-e7c688b893e1d5537ccf65cc3eb10a28',
|
||||
|
|
|
@ -4479,11 +4479,29 @@ class VolumeTestCase(base.BaseVolumeTestCase):
|
|||
|
||||
mock_get_backup.assert_called_once_with(self.context, backup)
|
||||
mock_secure.assert_called_once_with()
|
||||
expected_result = {'backup_device': vol,
|
||||
'secure_enabled': False,
|
||||
expected_result = {'backup_device': vol, 'secure_enabled': False,
|
||||
'is_snapshot': False}
|
||||
self.assertEqual(expected_result, result)
|
||||
|
||||
@mock.patch.object(driver.BaseVD, 'get_backup_device')
|
||||
@mock.patch.object(driver.BaseVD, 'secure_file_operations_enabled')
|
||||
def test_get_backup_device_want_objects(self, mock_secure,
|
||||
mock_get_backup):
|
||||
vol = tests_utils.create_volume(self.context)
|
||||
backup = tests_utils.create_backup(self.context, vol['id'])
|
||||
mock_secure.return_value = False
|
||||
mock_get_backup.return_value = (vol, False)
|
||||
result = self.volume.get_backup_device(self.context,
|
||||
backup, want_objects=True)
|
||||
|
||||
mock_get_backup.assert_called_once_with(self.context, backup)
|
||||
mock_secure.assert_called_once_with()
|
||||
expected_result = objects.BackupDeviceInfo.from_primitive(
|
||||
{'backup_device': vol, 'secure_enabled': False,
|
||||
'is_snapshot': False},
|
||||
self.context)
|
||||
self.assertEqual(expected_result, result)
|
||||
|
||||
def test_backup_use_temp_snapshot_config(self):
|
||||
local_conf = self.volume.driver.configuration.local_conf
|
||||
self.assertFalse(local_conf.backup_use_temp_snapshot)
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
Unit Tests for cinder.volume.rpcapi
|
||||
"""
|
||||
import copy
|
||||
import mock
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_serialization import jsonutils
|
||||
|
@ -206,6 +207,7 @@ class VolumeRpcAPITestCase(test.TestCase):
|
|||
|
||||
def _fake_rpc_method(*args, **kwargs):
|
||||
self.fake_args = args
|
||||
kwargs.pop('want_objects', None)
|
||||
self.fake_kwargs = kwargs
|
||||
if expected_retval is not None:
|
||||
return expected_retval
|
||||
|
@ -567,7 +569,21 @@ class VolumeRpcAPITestCase(test.TestCase):
|
|||
volume=self.fake_volume,
|
||||
version='3.0')
|
||||
|
||||
def test_get_backup_device(self):
|
||||
@mock.patch('oslo_messaging.RPCClient.can_send_version',
|
||||
return_value=True)
|
||||
def test_get_backup_device(self, mock_can_send_version):
|
||||
self._test_volume_api('get_backup_device',
|
||||
rpc_method='call',
|
||||
backup=self.fake_backup_obj,
|
||||
volume=self.fake_volume_obj,
|
||||
version='3.2')
|
||||
|
||||
@mock.patch('oslo_messaging.RPCClient.can_send_version',
|
||||
return_value=False)
|
||||
@mock.patch('cinder.objects.backup.BackupDeviceInfo.from_primitive',
|
||||
return_value={})
|
||||
def test_get_backup_device_old(self, mock_from_primitive,
|
||||
mock_can_send_version):
|
||||
self._test_volume_api('get_backup_device',
|
||||
rpc_method='call',
|
||||
backup=self.fake_backup_obj,
|
||||
|
|
|
@ -4192,14 +4192,18 @@ class VolumeManager(manager.CleanableManager,
|
|||
LOG.debug("Obtained capabilities list: %s.", capabilities)
|
||||
return capabilities
|
||||
|
||||
def get_backup_device(self, ctxt, backup):
|
||||
def get_backup_device(self, ctxt, backup, want_objects=False):
|
||||
(backup_device, is_snapshot) = (
|
||||
self.driver.get_backup_device(ctxt, backup))
|
||||
secure_enabled = self.driver.secure_file_operations_enabled()
|
||||
backup_device_dict = {'backup_device': backup_device,
|
||||
'secure_enabled': secure_enabled,
|
||||
'is_snapshot': is_snapshot, }
|
||||
return backup_device_dict
|
||||
# TODO(sborkows): from_primitive method will be removed in O, so there
|
||||
# is a need to clean here then.
|
||||
return (objects.BackupDeviceInfo.from_primitive(backup_device_dict,
|
||||
ctxt)
|
||||
if want_objects else backup_device_dict)
|
||||
|
||||
def secure_file_operations_enabled(self, ctxt, volume):
|
||||
secure_enabled = self.driver.secure_file_operations_enabled()
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
|
||||
from cinder.common import constants
|
||||
from cinder import objects
|
||||
from cinder import quota
|
||||
from cinder import rpc
|
||||
from cinder.volume import utils
|
||||
|
@ -110,9 +111,11 @@ class VolumeAPI(rpc.RPCAPI):
|
|||
3.1 - Remove promote_replica and reenable_replication. This is
|
||||
non-backward compatible, but the user-facing API was removed
|
||||
back in Mitaka when introducing cheesecake replication.
|
||||
3.2 - Adds support for sending objects over RPC in
|
||||
get_backup_device().
|
||||
"""
|
||||
|
||||
RPC_API_VERSION = '3.1'
|
||||
RPC_API_VERSION = '3.2'
|
||||
RPC_DEFAULT_VERSION = '3.0'
|
||||
TOPIC = constants.VOLUME_TOPIC
|
||||
BINARY = 'cinder-volume'
|
||||
|
@ -293,9 +296,15 @@ class VolumeAPI(rpc.RPCAPI):
|
|||
return cctxt.call(ctxt, 'get_capabilities', discover=discover)
|
||||
|
||||
def get_backup_device(self, ctxt, backup, volume):
|
||||
cctxt = self._get_cctxt(volume.host)
|
||||
backup_dict = cctxt.call(ctxt, 'get_backup_device', backup=backup)
|
||||
return backup_dict
|
||||
cctxt = self._get_cctxt(volume.host, ('3.2', '3.0'))
|
||||
if cctxt.can_send_version('3.2'):
|
||||
backup_obj = cctxt.call(ctxt, 'get_backup_device', backup=backup,
|
||||
want_objects=True)
|
||||
else:
|
||||
backup_dict = cctxt.call(ctxt, 'get_backup_device', backup=backup)
|
||||
backup_obj = objects.BackupDeviceInfo.from_primitive(backup_dict,
|
||||
ctxt)
|
||||
return backup_obj
|
||||
|
||||
def secure_file_operations_enabled(self, ctxt, volume):
|
||||
cctxt = self._get_cctxt(volume.host)
|
||||
|
|
Loading…
Reference in New Issue