Clear OVO history and compatibility

The Oslo Versioned Objects history is used to generate the manifests
required to do compatibility changes to OVOs on data serialization
between services running with different OVO history versions.

We haven't updated our OVO history since Train so all the history and
compatibility code (obj_make_compatible method) is no longer necessary.

This patch consolidates the OVO history into a single version reflecting
the current status of the OVO versions and removes the compatibility
code from the OVO classes.

Since we tend to forget to update the obj_make_compatible when we add a
field (like it happened with Volume in version 1.8 when we added
shared_targets) this patch also adds a note next to the "fields"
attribute (except for the list OVOs which are never updated).

Change-Id: Ibfacccfb7c7dc70bc8f8e5ab98cc9c8feae694fb
This commit is contained in:
Gorka Eguileor 2021-04-08 19:16:15 +02:00
parent 28d9bca7d6
commit b78997c2bb
33 changed files with 147 additions and 377 deletions

View File

@ -15,7 +15,6 @@
from oslo_config import cfg
from oslo_serialization import base64
from oslo_serialization import jsonutils
from oslo_utils import versionutils
from oslo_versionedobjects import fields
from cinder import db
@ -45,6 +44,7 @@ class Backup(base.CinderPersistentObject, base.CinderObject,
OPTIONAL_FIELDS = ('metadata', 'parent')
# NOTE: When adding a field obj_make_compatible needs to be updated
fields = {
'id': fields.UUIDField(),
@ -110,17 +110,6 @@ class Backup(base.CinderPersistentObject, base.CinderObject,
def has_dependent_backups(self):
return bool(self.num_dependent_backups)
def obj_make_compatible(self, primitive, target_version):
"""Make an object representation compatible with a target version."""
added_fields = (((1, 7), ('parent',)),)
super(Backup, self).obj_make_compatible(primitive, target_version)
target_version = versionutils.convert_version_to_tuple(target_version)
for version, remove_fields in added_fields:
if target_version < version:
for obj_field in remove_fields:
primitive.pop(obj_field, None)
@classmethod
def _from_db_object(cls, context, backup, db_backup, expected_attrs=None):
if expected_attrs is None:

View File

@ -54,23 +54,45 @@ class CinderObjectVersionsHistory(dict):
#
# Please note that we do not need to add similar entires for each
# release. Liberty is here just for historical reasons.
self.versions = ['liberty']
self['liberty'] = {
'Backup': '1.1',
'BackupImport': '1.1',
self.versions = ['1.38']
self['1.38'] = {
'Backup': '1.7',
'BackupDeviceInfo': '1.0',
'BackupImport': '1.7',
'BackupList': '1.0',
'ConsistencyGroup': '1.1',
'ConsistencyGroupList': '1.0',
'Service': '1.0',
'ServiceList': '1.0',
'Snapshot': '1.0',
'CleanupRequest': '1.0',
'CGSnapshot': '1.1',
'CGSnapshotList': '1.0',
'Cluster': '1.1',
'ClusterList': '1.0',
'ConsistencyGroup': '1.4',
'ConsistencyGroupList': '1.1',
'Group': '1.2',
'GroupList': '1.0',
'GroupSnapshot': '1.0',
'GroupSnapshotList': '1.0',
'GroupType': '1.0',
'GroupTypeList': '1.0',
'LogLevel': '1.0',
'LogLevelList': '1.0',
'ManageableSnapshot': '1.0',
'ManageableSnapshotList': '1.0',
'ManageableVolume': '1.0',
'ManageableVolumeList': '1.0',
'QualityOfServiceSpecs': '1.0',
'QualityOfServiceSpecsList': '1.0',
'RequestSpec': '1.5',
'Service': '1.6',
'ServiceList': '1.1',
'Snapshot': '1.5',
'SnapshotList': '1.0',
'Volume': '1.1',
'VolumeAttachment': '1.0',
'VolumeAttachmentList': '1.0',
'Volume': '1.8',
'VolumeAttachment': '1.3',
'VolumeAttachmentList': '1.1',
'VolumeList': '1.1',
'VolumeType': '1.0',
'VolumeTypeList': '1.0',
'VolumeProperties': '1.1',
'VolumeType': '1.3',
'VolumeTypeList': '1.1',
}
def get_current(self):
@ -92,61 +114,25 @@ class CinderObjectVersionsHistory(dict):
OBJ_VERSIONS = CinderObjectVersionsHistory()
# NOTE(dulek): You should add a new version here each time you bump a version
# of any object. As a second parameter you need to specify only what changed.
# On each release we should drop backward compatibility with -2 release, since
# rolling upgrades only needs to support compatibility with previous release.
# So if we are in N release we can remove history from L and earlier.
# Example of how to keep track of this:
# # TODO: (T release) remove up to next TODO (was added in R release) and
# # update CinderObjectVersionsHistory
# OBJ_VERSIONS.add('1.34', {'VolumeAttachment': '1.3'})
# OBJ_VERSIONS.add('1.35', {'Backup': '1.6', 'BackupImport': '1.6'})
#
# When dropping backward compatibility with an OpenStack release we can rework
# this and remove some history while keeping the versions order.
OBJ_VERSIONS.add('1.0', {'Backup': '1.3', 'BackupImport': '1.3',
'CGSnapshot': '1.0', 'CGSnapshotList': '1.0',
'ConsistencyGroup': '1.2',
'ConsistencyGroupList': '1.1', 'Service': '1.1',
'Volume': '1.3', 'VolumeTypeList': '1.1'})
OBJ_VERSIONS.add('1.1', {'Service': '1.2', 'ServiceList': '1.1'})
OBJ_VERSIONS.add('1.2', {'Backup': '1.4', 'BackupImport': '1.4'})
OBJ_VERSIONS.add('1.3', {'Service': '1.3'})
OBJ_VERSIONS.add('1.4', {'Snapshot': '1.1'})
OBJ_VERSIONS.add('1.5', {'VolumeType': '1.1'})
OBJ_VERSIONS.add('1.6', {'QualityOfServiceSpecs': '1.0',
'QualityOfServiceSpecsList': '1.0',
'VolumeType': '1.2'})
OBJ_VERSIONS.add('1.7', {'Cluster': '1.0', 'ClusterList': '1.0',
'Service': '1.4', 'Volume': '1.4',
'ConsistencyGroup': '1.3'})
OBJ_VERSIONS.add('1.8', {'RequestSpec': '1.0', 'VolumeProperties': '1.0'})
OBJ_VERSIONS.add('1.9', {'GroupType': '1.0', 'GroupTypeList': '1.0'})
OBJ_VERSIONS.add('1.10', {'Group': '1.0', 'GroupList': '1.0', 'Volume': '1.5',
'RequestSpec': '1.1', 'VolumeProperties': '1.1'})
OBJ_VERSIONS.add('1.11', {'GroupSnapshot': '1.0', 'GroupSnapshotList': '1.0',
'Group': '1.1'})
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'})
OBJ_VERSIONS.add('1.17', {'VolumeAttachment': '1.1'})
OBJ_VERSIONS.add('1.18', {'Snapshot': '1.3'})
OBJ_VERSIONS.add('1.19', {'ConsistencyGroup': '1.4', 'CGSnapshot': '1.1'})
OBJ_VERSIONS.add('1.20', {'Cluster': '1.1'})
OBJ_VERSIONS.add('1.21', {'ManageableSnapshot': '1.0',
'ManageableVolume': '1.0',
'ManageableVolumeList': '1.0',
'ManageableSnapshotList': '1.0'})
OBJ_VERSIONS.add('1.22', {'Snapshot': '1.4'})
OBJ_VERSIONS.add('1.23', {'VolumeAttachment': '1.2'})
OBJ_VERSIONS.add('1.24', {'LogLevel': '1.0', 'LogLevelList': '1.0'})
OBJ_VERSIONS.add('1.25', {'Group': '1.2'})
OBJ_VERSIONS.add('1.26', {'Snapshot': '1.5'})
OBJ_VERSIONS.add('1.27', {'Backup': '1.5', 'BackupImport': '1.5'})
OBJ_VERSIONS.add('1.28', {'Service': '1.5'})
OBJ_VERSIONS.add('1.29', {'Service': '1.6'})
OBJ_VERSIONS.add('1.30', {'RequestSpec': '1.2'})
OBJ_VERSIONS.add('1.31', {'Volume': '1.7'})
OBJ_VERSIONS.add('1.32', {'RequestSpec': '1.3'})
OBJ_VERSIONS.add('1.33', {'Volume': '1.8'})
OBJ_VERSIONS.add('1.34', {'VolumeAttachment': '1.3'})
OBJ_VERSIONS.add('1.35', {'Backup': '1.6', 'BackupImport': '1.6'})
OBJ_VERSIONS.add('1.36', {'RequestSpec': '1.4'})
OBJ_VERSIONS.add('1.37', {'RequestSpec': '1.5'})
OBJ_VERSIONS.add('1.38', {'Backup': '1.7', 'BackupImport': '1.7'})
# # TODO: (U release) remove up to next TODO (was added in S release) and
# # update CinderObjectVersionsHistory
# OBJ_VERSIONS.add('1.36', {'RequestSpec': '1.4'})
# OBJ_VERSIONS.add('1.37', {'RequestSpec': '1.5'})
# OBJ_VERSIONS.add('1.38', {'Backup': '1.7', 'BackupImport': '1.7'})
# When we reach T release we remove versions 1.34 and 1.35 and update __init__
# method in CinderObjectVerseionsHistory to bump VolumeAttachment to 1.3,
# Backup to 1.6 and BackupImport to 1.6, and changing the versions list to
# '1.35' and the self['<versioname>'] = { to self['1.35'] = {
class CinderObjectRegistry(base.VersionedObjectRegistry):

View File

@ -30,6 +30,7 @@ class CGSnapshot(base.CinderPersistentObject, base.CinderObject,
OPTIONAL_FIELDS = ['consistencygroup', 'snapshots']
# NOTE: When adding a field obj_make_compatible needs to be updated
fields = {
'id': fields.UUIDField(),
'consistencygroup_id': fields.UUIDField(nullable=True),

View File

@ -24,6 +24,7 @@ class CleanupRequest(base.CinderObject, base.ClusteredObject):
# Version 1.0: Initial version
VERSION = '1.0'
# NOTE: When adding a field obj_make_compatible needs to be updated
fields = {
'service_id': fields.IntegerField(nullable=True),
'cluster_name': fields.StringField(nullable=True),

View File

@ -42,9 +42,7 @@ class Cluster(base.CinderPersistentObject, base.CinderObject,
VERSION = '1.1'
OPTIONAL_FIELDS = ('num_hosts', 'num_down_hosts', 'services')
# NOTE(geguileo): We don't want to expose race_preventer field at the OVO
# layer since it is only meant for the DB layer internal mechanism to
# prevent races.
# NOTE: When adding a field obj_make_compatible needs to be updated
fields = {
'id': fields.IntegerField(),
'name': fields.StringField(nullable=False),
@ -60,20 +58,11 @@ class Cluster(base.CinderPersistentObject, base.CinderObject,
'replication_status': c_fields.ReplicationStatusField(nullable=True),
'frozen': fields.BooleanField(default=False),
'active_backend_id': fields.StringField(nullable=True),
# Don't add race_preventer field, as it's a DB layer internal mechanism
# piece to prevent races and should not be touched by other layers.
}
def obj_make_compatible(self, primitive, target_version):
"""Make a cluster representation compatible with a target version."""
# Convert all related objects
super(Cluster, self).obj_make_compatible(primitive, target_version)
# Before v1.1 we didn't have relication fields so we have to remove
# them.
if target_version == '1.0':
for obj_field in ('replication_status', 'frozen',
'active_backend_id'):
primitive.pop(obj_field, None)
@classmethod
def _get_expected_attrs(cls, context, *args, **kwargs):
"""Return expected attributes when getting a cluster.

View File

@ -12,7 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_utils import versionutils
from oslo_versionedobjects import fields
from cinder import db
@ -54,18 +53,6 @@ class ConsistencyGroup(base.CinderPersistentObject, base.CinderObject,
'volumes': fields.ObjectField('VolumeList', nullable=True),
}
def obj_make_compatible(self, primitive, target_version):
"""Make a CG representation compatible with a target version."""
# Convert all related objects
super(ConsistencyGroup, self).obj_make_compatible(primitive,
target_version)
target_version = versionutils.convert_version_to_tuple(target_version)
# Before v1.3 we didn't have cluster fields so we have to remove them.
if target_version < (1, 3):
for obj_field in ('cluster', 'cluster_name'):
primitive.pop(obj_field, None)
@classmethod
def _from_db_object(cls, context, consistencygroup, db_consistencygroup,
expected_attrs=None):

View File

@ -24,6 +24,7 @@ class LogLevel(base.CinderObject):
# Version 1.0: Initial version
VERSION = '1.0'
# NOTE: When adding a field obj_make_compatible needs to be updated
fields = {
'prefix': fields.StringField(nullable=True),
'level': fields.StringField(nullable=True),

View File

@ -12,7 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_utils import versionutils
from oslo_versionedobjects import fields
from cinder import db
@ -35,6 +34,7 @@ class Group(base.CinderPersistentObject, base.CinderObject,
OPTIONAL_FIELDS = ['volumes', 'volume_types', 'group_snapshots']
# NOTE: When adding a field obj_make_compatible needs to be updated
fields = {
'id': fields.UUIDField(),
'user_id': fields.StringField(),
@ -57,17 +57,6 @@ class Group(base.CinderPersistentObject, base.CinderObject,
nullable=True),
}
def obj_make_compatible(self, primitive, target_version):
"""Make an object representation compatible with target version."""
super(Group, self).obj_make_compatible(primitive, target_version)
target_version = versionutils.convert_version_to_tuple(target_version)
if target_version < (1, 1):
for key in ('group_snapshot_id', 'source_group_id',
'group_snapshots'):
primitive.pop(key, None)
if target_version < (1, 2):
primitive.pop('replication_status', None)
@staticmethod
def _from_db_object(context, group, db_group,
expected_attrs=None):

View File

@ -28,6 +28,7 @@ class GroupSnapshot(base.CinderPersistentObject, base.CinderObject,
OPTIONAL_FIELDS = ['group', 'snapshots']
# NOTE: When adding a field obj_make_compatible needs to be updated
fields = {
'id': fields.UUIDField(),
'group_id': fields.UUIDField(nullable=False),

View File

@ -29,6 +29,7 @@ class GroupType(base.CinderPersistentObject, base.CinderObject,
OPTIONAL_FIELDS = ['group_specs', 'projects']
# NOTE: When adding a field obj_make_compatible needs to be updated
fields = {
'id': fields.UUIDField(),
'name': fields.StringField(nullable=True),

View File

@ -19,6 +19,7 @@ from cinder.objects import base
class ManageableObject(object):
# NOTE: When adding a field obj_make_compatible needs to be updated
fields = {
'reference': fields.DictOfNullableStringsField(nullable=False),
'size': fields.IntegerField(nullable=True),
@ -58,6 +59,7 @@ class ManageableSnapshot(base.CinderObject, base.CinderObjectDictCompat,
# Version 1.0: Initial version
VERSION = '1.0'
# NOTE: When adding a field obj_make_compatible needs to be updated
fields = {
'source_reference': fields.DictOfNullableStringsField(),
}

View File

@ -35,6 +35,7 @@ class QualityOfServiceSpecs(base.CinderPersistentObject,
OPTIONAL_FIELDS = ['volume_types']
# NOTE: When adding a field obj_make_compatible needs to be updated
fields = {
'id': fields.UUIDField(),
'name': fields.StringField(),

View File

@ -12,7 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_utils import versionutils
from oslo_versionedobjects import fields
from cinder import objects
@ -30,6 +29,7 @@ class RequestSpec(base.CinderObject, base.CinderObjectDictCompat,
# Version 1.5: Added 'availability_zones'
VERSION = '1.5'
# NOTE: When adding a field obj_make_compatible needs to be updated
fields = {
'consistencygroup_id': fields.UUIDField(nullable=True),
'group_id': fields.UUIDField(nullable=True),
@ -95,20 +95,6 @@ class RequestSpec(base.CinderObject, base.CinderObjectDictCompat,
return spec_obj
def obj_make_compatible(self, primitive, target_version):
"""Make an object representation compatible with target version."""
super(RequestSpec, self).obj_make_compatible(primitive, target_version)
target_version = versionutils.convert_version_to_tuple(target_version)
added_fields = (((1, 1), ('group_id', 'group_backend')),
((1, 2), ('resource_backend')),
((1, 3), ('backup_id')),
((1, 4), ('operation')),
((1, 5), ('availability_zones')))
for version, remove_fields in added_fields:
if target_version < version:
for obj_field in remove_fields:
primitive.pop(obj_field, None)
@base.CinderObjectRegistry.register
class VolumeProperties(base.CinderObject, base.CinderObjectDictCompat):

View File

@ -44,6 +44,7 @@ class Service(base.CinderPersistentObject, base.CinderObject,
OPTIONAL_FIELDS = ('cluster',)
# NOTE: When adding a field obj_make_compatible needs to be updated
fields = {
'id': fields.IntegerField(),
'host': fields.StringField(nullable=True),
@ -70,19 +71,6 @@ class Service(base.CinderPersistentObject, base.CinderObject,
'uuid': fields.StringField(),
}
def obj_make_compatible(self, primitive, target_version):
"""Make a service representation compatible with a target version."""
# Convert all related objects
super(Service, self).obj_make_compatible(primitive, target_version)
target_version = versionutils.convert_version_to_tuple(target_version)
# Before v1.4 we didn't have cluster fields so we have to remove them.
if target_version < (1, 4):
for obj_field in ('cluster', 'cluster_name'):
primitive.pop(obj_field, None)
if target_version < (1, 5) and 'uuid' in primitive:
del primitive['uuid']
@staticmethod
def _from_db_object(context, service, db_service, expected_attrs=None):
expected_attrs = expected_attrs or []

View File

@ -13,7 +13,6 @@
# under the License.
from oslo_config import cfg
from oslo_utils import versionutils
from oslo_versionedobjects import fields
from cinder import db
@ -45,6 +44,7 @@ class Snapshot(cleanable.CinderCleanableObject, base.CinderObject,
# are typically the relationship in the sqlalchemy object.
OPTIONAL_FIELDS = ('volume', 'metadata', 'cgsnapshot', 'group_snapshot')
# NOTE: When adding a field obj_make_compatible needs to be updated
fields = {
'id': fields.UUIDField(),
@ -116,25 +116,6 @@ class Snapshot(cleanable.CinderCleanableObject, base.CinderObject,
return changes
def obj_make_compatible(self, primitive, target_version):
"""Make an object representation compatible with a target version."""
super(Snapshot, self).obj_make_compatible(primitive, target_version)
target_version = versionutils.convert_version_to_tuple(target_version)
backport_statuses = (((1, 3),
(c_fields.SnapshotStatus.UNMANAGING,
c_fields.SnapshotStatus.DELETING)),
((1, 4),
(c_fields.SnapshotStatus.BACKING_UP,
c_fields.SnapshotStatus.AVAILABLE)),
((1, 5),
(c_fields.SnapshotStatus.RESTORING,
c_fields.SnapshotStatus.AVAILABLE)))
for version, status in backport_statuses:
if target_version < version:
if primitive.get('status') == status[0]:
primitive['status'] = status[1]
@classmethod
def _from_db_object(cls, context, snapshot, db_snapshot,
expected_attrs=None):

View File

@ -13,7 +13,6 @@
# under the License.
from oslo_config import cfg
from oslo_utils import versionutils
from oslo_versionedobjects import fields
from cinder import db
@ -69,6 +68,7 @@ class Volume(cleanable.CinderCleanableObject, base.CinderObject,
'volume_type', 'volume_attachment', 'consistencygroup',
'snapshots', 'cluster', 'group')
# NOTE: When adding a field obj_make_compatible needs to be updated
fields = {
'id': fields.UUIDField(),
'_name_id': fields.UUIDField(nullable=True),
@ -239,21 +239,6 @@ class Volume(cleanable.CinderCleanableObject, base.CinderObject,
return changes
def obj_make_compatible(self, primitive, target_version):
"""Make a Volume representation compatible with a target version."""
added_fields = (((1, 4), ('cluster', 'cluster_name')),
((1, 5), ('group', 'group_id')),
((1, 7), ('service_uuid')))
# Convert all related objects
super(Volume, self).obj_make_compatible(primitive, target_version)
target_version = versionutils.convert_version_to_tuple(target_version)
for version, remove_fields in added_fields:
if target_version < version:
for obj_field in remove_fields:
primitive.pop(obj_field, None)
@classmethod
def _from_db_object(cls, context, volume, db_volume, expected_attrs=None):
if expected_attrs is None:

View File

@ -13,7 +13,6 @@
# under the License.
from oslo_serialization import jsonutils
from oslo_utils import versionutils
from oslo_versionedobjects import fields
from cinder import db
@ -37,6 +36,7 @@ class VolumeAttachment(base.CinderPersistentObject, base.CinderObject,
OPTIONAL_FIELDS = ['volume']
obj_extra_fields = ['project_id', 'volume_host']
# NOTE: When adding a field obj_make_compatible needs to be updated
fields = {
'id': fields.UUIDField(),
'volume_id': fields.UUIDField(),
@ -67,16 +67,6 @@ class VolumeAttachment(base.CinderPersistentObject, base.CinderObject,
def _get_expected_attrs(cls, context, *args, **kwargs):
return ['volume']
def obj_make_compatible(self, primitive, target_version):
"""Make an object representation compatible with target version."""
super(VolumeAttachment, self).obj_make_compatible(primitive,
target_version)
target_version = versionutils.convert_version_to_tuple(target_version)
if target_version < (1, 3):
primitive.pop('connector', None)
if target_version < (1, 2):
primitive.pop('connection_info', None)
@classmethod
def _from_db_object(cls, context, attachment, db_attachment,
expected_attrs=None):

View File

@ -12,7 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_utils import versionutils
from oslo_versionedobjects import fields
from cinder import db
@ -35,6 +34,7 @@ class VolumeType(base.CinderPersistentObject, base.CinderObject,
OPTIONAL_FIELDS = ('extra_specs', 'projects', 'qos_specs')
# NOTE: When adding a field obj_make_compatible needs to be updated
fields = {
'id': fields.UUIDField(),
'name': fields.StringField(nullable=True),
@ -47,21 +47,6 @@ class VolumeType(base.CinderPersistentObject, base.CinderObject,
nullable=True),
}
def obj_make_compatible(self, primitive, target_version):
super(VolumeType, self).obj_make_compatible(primitive, target_version)
target_version = versionutils.convert_version_to_tuple(target_version)
if target_version < (1, 1):
if primitive.get('extra_specs'):
# Before 1.1 extra_specs field didn't allowed None values. To
# make sure we won't explode on receiver side - change Nones to
# empty string.
for k, v in primitive['extra_specs'].items():
if v is None:
primitive['extra_specs'][k] = ''
if target_version < (1, 3):
primitive.pop('qos_specs_id', None)
@classmethod
def _get_expected_attrs(cls, context, *args, **kwargs):
return 'extra_specs', 'projects'

View File

@ -356,19 +356,22 @@ class BackupTestCase(BaseBackupTest):
@mock.patch('cinder.objects.service.Service.get_minimum_obj_version')
@mock.patch('cinder.rpc.LAST_RPC_VERSIONS', {'cinder-backup': '1.3',
'cinder-volume': '1.7'})
@mock.patch('cinder.rpc.LAST_OBJ_VERSIONS', {'cinder-backup': '1.2',
'cinder-volume': '1.4'})
def test_reset(self, get_min_obj, get_min_rpc):
get_min_obj.return_value = 'liberty'
backup_mgr = manager.BackupManager()
old_version = objects.base.OBJ_VERSIONS.versions[-2]
with mock.patch('cinder.rpc.LAST_OBJ_VERSIONS',
{'cinder-volume': old_version,
'cinder-scheduler': old_version,
'cinder-backup': old_version}):
backup_mgr = manager.BackupManager()
backup_rpcapi = backup_mgr.backup_rpcapi
volume_rpcapi = backup_mgr.volume_rpcapi
self.assertEqual('1.3', backup_rpcapi.client.version_cap)
self.assertEqual('1.2',
self.assertEqual(old_version,
backup_rpcapi.client.serializer._base.version_cap)
self.assertEqual('1.7', volume_rpcapi.client.version_cap)
self.assertEqual('1.4',
self.assertEqual(old_version,
volume_rpcapi.client.serializer._base.version_cap)
get_min_obj.return_value = objects.base.OBJ_VERSIONS.get_current()
backup_mgr.reset()

View File

@ -36,6 +36,7 @@ from cinder.tests.unit import test
class TestCinderObjectVersionHistory(test_objects.BaseObjectsTestCase):
def test_add(self):
history = test_objects.obj_base.CinderObjectVersionsHistory()
first_version = history.versions[0]
v10 = {'Backup': '2.0'}
v11 = {'Backup': '2.1'}
history.add('1.0', v10)
@ -43,9 +44,9 @@ class TestCinderObjectVersionHistory(test_objects.BaseObjectsTestCase):
# We have 3 elements because we have the liberty version by default
self.assertEqual(2 + 1, len(history))
expected_v10 = history['liberty'].copy()
expected_v10 = history[first_version].copy()
expected_v10.update(v10)
expected_v11 = history['liberty'].copy()
expected_v11 = history[first_version].copy()
expected_v11.update(v11)
self.assertEqual('1.1', history.get_current())

View File

@ -19,7 +19,6 @@ from unittest import mock
from cinder import context
from cinder import exception
from cinder.objects import cleanable
from cinder import rpc
from cinder import service
from cinder.tests.unit import objects as test_objects
from cinder.volume import rpcapi
@ -35,7 +34,7 @@ class Backup(cleanable.CinderCleanableObject):
@staticmethod
def _is_cleanable(status, obj_version):
if obj_version and obj_version <= 1003:
if obj_version and obj_version < 1003:
return False
return status == 'cleanable'
@ -53,46 +52,52 @@ class TestCleanable(test_objects.BaseObjectsTestCase):
vol_rpcapi = cleanable.CinderCleanableObject.get_rpc_api()
self.assertEqual(rpcapi.VolumeAPI, vol_rpcapi)
def set_version(self, version):
self.patch('cinder.volume.rpcapi.VolumeAPI.determine_obj_version_cap',
mock.Mock(return_value='1.0'))
self.patch('cinder.objects.base.OBJ_VERSIONS',
{'1.0': {'Backup': version}})
def test_get_pinned_version(self):
"""Test that we get the pinned version for this specific object."""
rpc.LAST_OBJ_VERSIONS[Backup.get_rpc_api().BINARY] = '1.0'
self.set_version('1.3')
version = Backup.get_pinned_version()
self.assertEqual(1003, version)
def test_is_cleanable_pinned_pinned_too_old(self):
"""Test is_cleanable with pinned version with uncleanable version."""
rpc.LAST_OBJ_VERSIONS[Backup.get_rpc_api().BINARY] = '1.0'
self.set_version('1.0')
backup = Backup(status='cleanable')
self.assertFalse(backup.is_cleanable(pinned=True))
def test_is_cleanable_pinned_result_true(self):
"""Test with pinned version with cleanable version and status."""
rpc.LAST_OBJ_VERSIONS[Backup.get_rpc_api().BINARY] = '1.3'
self.set_version('1.3')
backup = Backup(status='cleanable')
self.assertTrue(backup.is_cleanable(pinned=True))
def test_is_cleanable_pinned_result_false(self):
"""Test with pinned version with cleanable version but not status."""
rpc.LAST_OBJ_VERSIONS[Backup.get_rpc_api().BINARY] = '1.3'
self.set_version('1.0')
backup = Backup(status='not_cleanable')
self.assertFalse(backup.is_cleanable(pinned=True))
def test_is_cleanable_unpinned_result_false(self):
"""Test unpinned version with old version and non cleanable status."""
rpc.LAST_OBJ_VERSIONS[Backup.get_rpc_api().BINARY] = '1.0'
self.set_version('1.0')
backup = Backup(status='not_cleanable')
self.assertFalse(backup.is_cleanable(pinned=False))
def test_is_cleanable_unpinned_result_true(self):
"""Test unpinned version with old version and cleanable status."""
rpc.LAST_OBJ_VERSIONS[Backup.get_rpc_api().BINARY] = '1.0'
self.set_version('1.0')
backup = Backup(status='cleanable')
self.assertTrue(backup.is_cleanable(pinned=False))
@mock.patch('cinder.db.worker_create', autospec=True)
def test_create_worker(self, mock_create):
"""Test worker creation as if it were from an rpc call."""
rpc.LAST_OBJ_VERSIONS[Backup.get_rpc_api().BINARY] = '1.3'
self.set_version('1.3')
mock_create.return_value = mock.sentinel.worker
backup = Backup(_context=self.context, status='cleanable',
id=mock.sentinel.id)
@ -106,7 +111,7 @@ class TestCleanable(test_objects.BaseObjectsTestCase):
@mock.patch('cinder.db.worker_create', autospec=True)
def test_create_worker_pinned_too_old(self, mock_create):
"""Test worker creation when we are pinnned with an old version."""
rpc.LAST_OBJ_VERSIONS[Backup.get_rpc_api().BINARY] = '1.0'
self.set_version('1.0')
mock_create.return_value = mock.sentinel.worker
backup = Backup(_context=self.context, status='cleanable',
id=mock.sentinel.id)
@ -117,7 +122,7 @@ class TestCleanable(test_objects.BaseObjectsTestCase):
@mock.patch('cinder.db.worker_create', autospec=True)
def test_create_worker_non_cleanable(self, mock_create):
"""Test worker creation when status is non cleanable."""
rpc.LAST_OBJ_VERSIONS[Backup.get_rpc_api().BINARY] = '1.3'
self.set_version('1.3')
mock_create.return_value = mock.sentinel.worker
backup = Backup(_context=self.context, status='non_cleanable',
id=mock.sentinel.id)
@ -129,7 +134,7 @@ class TestCleanable(test_objects.BaseObjectsTestCase):
@mock.patch('cinder.db.worker_create', autospec=True)
def test_create_worker_already_exists(self, mock_create, mock_update):
"""Test worker creation when a worker for the resource exists."""
rpc.LAST_OBJ_VERSIONS[Backup.get_rpc_api().BINARY] = '1.3'
self.set_version('1.3')
mock_create.side_effect = exception.WorkerExists(type='type', id='id')
backup = Backup(_context=self.context, status='cleanable',
@ -152,7 +157,7 @@ class TestCleanable(test_objects.BaseObjectsTestCase):
that the entry gets removed from the DB between our failure to create
it and our try to update the entry.
"""
rpc.LAST_OBJ_VERSIONS[Backup.get_rpc_api().BINARY] = '1.3'
self.set_version('1.3')
mock_create.side_effect = [
exception.WorkerExists(type='type', id='id'), mock.sentinel.worker]
mock_update.side_effect = exception.WorkerNotFound

View File

@ -124,19 +124,6 @@ class TestCluster(test_objects.BaseObjectsTestCase):
'active_backend_id': None},
{'cluster_name': cluster.name})
@ddt.data('1.0', '1.1')
def tests_obj_make_compatible(self, version):
new_fields = {'replication_status': 'error', 'frozen': True,
'active_backend_id': 'replication'}
cluster = objects.Cluster(self.context, **new_fields)
primitive = cluster.obj_to_primitive(version)
converted_cluster = objects.Cluster.obj_from_primitive(primitive)
for key, value in new_fields.items():
if version == '1.0':
self.assertFalse(converted_cluster.obj_attr_is_set(key))
else:
self.assertEqual(value, getattr(converted_cluster, key))
class TestClusterList(test_objects.BaseObjectsTestCase):
"""Test ClusterList Versioned Object methods."""

View File

@ -18,7 +18,6 @@ import ddt
from cinder import exception
from cinder import objects
from cinder.objects import base as ovo_base
from cinder.objects import fields
from cinder.tests.unit import fake_constants as fake
from cinder.tests.unit import fake_volume
@ -159,22 +158,6 @@ class TestGroup(test_objects.BaseObjectsTestCase):
self.assertEqual(len(db_volumes), len(group.volumes))
self._compare(self, db_volumes[0], group.volumes[0])
@ddt.data('1.10', '1.11')
def test_obj_make_compatible(self, version):
extra_data = {'group_snapshot_id': fake.GROUP_SNAPSHOT_ID,
'source_group_id': fake.GROUP_ID,
'group_snapshots': objects.GroupSnapshotList()}
group = objects.Group(self.context, name='name', **extra_data)
serializer = ovo_base.CinderObjectSerializer(version)
primitive = serializer.serialize_entity(self.context, group)
converted_group = objects.Group.obj_from_primitive(primitive)
is_set = version == '1.11'
for key in extra_data:
self.assertEqual(is_set, converted_group.obj_attr_is_set(key))
self.assertEqual('name', converted_group.name)
@mock.patch('cinder.volume.group_types.get_group_type_specs')
def test_is_replicated_true(self, mock_get_specs):
mock_get_specs.return_value = '<is> True'

View File

@ -80,6 +80,10 @@ class TestObjectVersions(test.TestCase):
"and we just have to change the hash in this module.")
def test_versions_history(self):
# If we inserted a fake element in history, remove it so we don't fail
if base.OBJ_VERSIONS.get_current() == self.FAKE_OVO_HISTORY_VERSION:
fake_version = base.OBJ_VERSIONS.versions.pop(-1)
del base.OBJ_VERSIONS[fake_version]
classes = base.CinderObjectRegistry.obj_classes()
versions = base.OBJ_VERSIONS.get_current_versions()
expected = {}

View File

@ -229,30 +229,6 @@ class TestSnapshot(test_objects.BaseObjectsTestCase):
mock.call(self.context,
fake.SNAPSHOT_ID)])
@ddt.data('1.1', '1.3')
def test_obj_make_compatible_1_3(self, version):
snapshot = objects.Snapshot(context=self.context)
snapshot.status = fields.SnapshotStatus.UNMANAGING
primitive = snapshot.obj_to_primitive(version)
snapshot = objects.Snapshot.obj_from_primitive(primitive)
if version == '1.3':
status = fields.SnapshotStatus.UNMANAGING
else:
status = fields.SnapshotStatus.DELETING
self.assertEqual(status, snapshot.status)
@ddt.data('1.3', '1.4')
def test_obj_make_compatible_1_4(self, version):
snapshot = objects.Snapshot(context=self.context)
snapshot.status = fields.SnapshotStatus.BACKING_UP
primitive = snapshot.obj_to_primitive(version)
snapshot = objects.Snapshot.obj_from_primitive(primitive)
if version == '1.4':
status = fields.SnapshotStatus.BACKING_UP
else:
status = fields.SnapshotStatus.AVAILABLE
self.assertEqual(status, snapshot.status)
class TestSnapshotList(test_objects.BaseObjectsTestCase):
@mock.patch('cinder.objects.volume.Volume.get_by_id')

View File

@ -21,7 +21,6 @@ import pytz
from cinder import context
from cinder import exception
from cinder import objects
from cinder.objects import base as ovo_base
from cinder.objects import fields
from cinder.tests.unit.consistencygroup import fake_consistencygroup
from cinder.tests.unit import fake_constants as fake
@ -532,36 +531,6 @@ class TestVolume(test_objects.BaseObjectsTestCase):
self.assertEqual({}, volume.cinder_obj_get_changes())
self.assertFalse(volume_attachment_get.called)
@ddt.data('1.6', '1.7')
def test_obj_make_compatible_cluster_added(self, version):
extra_data = {'cluster_name': 'cluster_name',
'cluster': objects.Cluster()}
volume = objects.Volume(self.context, host='host', **extra_data)
serializer = ovo_base.CinderObjectSerializer(version)
primitive = serializer.serialize_entity(self.context, volume)
converted_volume = objects.Volume.obj_from_primitive(primitive)
is_set = version == '1.7'
for key in extra_data:
self.assertEqual(is_set, converted_volume.obj_attr_is_set(key))
self.assertEqual('host', converted_volume.host)
@ddt.data('1.9', '1.10')
def test_obj_make_compatible_groups_added(self, version):
extra_data = {'group_id': fake.GROUP_ID,
'group': objects.Group()}
volume = objects.Volume(self.context, host='host', **extra_data)
serializer = ovo_base.CinderObjectSerializer(version)
primitive = serializer.serialize_entity(self.context, volume)
converted_volume = objects.Volume.obj_from_primitive(primitive)
is_set = version == '1.10'
for key in extra_data:
self.assertEqual(is_set, converted_volume.obj_attr_is_set(key))
self.assertEqual('host', converted_volume.host)
@ddt.data(True, False)
def test_is_replicated(self, result):
volume_type = fake_volume.fake_volume_type_obj(self.context)

View File

@ -123,26 +123,6 @@ class TestVolumeAttachment(test_objects.BaseObjectsTestCase):
self.assertEqual(fields.VolumeAttachStatus.ATTACHED,
attachment.attach_status)
@ddt.data('1.0', '1.1', '1.2', '1.3')
def test_obj_make_compatible(self, version):
connection_info = {'field': 'value'}
connector = {'host': '127.0.0.1'}
vol_attach = objects.VolumeAttachment(self.context,
connection_info=connection_info,
connector=connector)
primitive = vol_attach.obj_to_primitive(version)
converted_vol_attach = objects.VolumeAttachment.obj_from_primitive(
primitive)
if version == '1.3':
self.assertEqual(connector, converted_vol_attach.connector)
elif version == '1.2':
self.assertEqual(connection_info,
converted_vol_attach.connection_info)
else:
self.assertNotIn('connector', converted_vol_attach)
self.assertFalse(converted_vol_attach.obj_attr_is_set(
'connection_info'))
def test_migrate_attachment_specs(self):
# Create an attachment.
attachment = objects.VolumeAttachment(

View File

@ -75,18 +75,6 @@ class TestVolumeType(test_objects.BaseObjectsTestCase):
self.context, fake.VOLUME_TYPE_ID)
self._compare(self, db_volume_type, volume_type)
@ddt.data('1.0', '1.1')
def test_obj_make_compatible(self, version):
volume_type = objects.VolumeType(context=self.context)
volume_type.extra_specs = {'foo': None, 'bar': 'baz'}
volume_type.qos_specs_id = fake.QOS_SPEC_ID
primitive = volume_type.obj_to_primitive(version)
volume_type = objects.VolumeType.obj_from_primitive(primitive)
foo = '' if version == '1.0' else None
self.assertEqual(foo, volume_type.extra_specs['foo'])
self.assertEqual('baz', volume_type.extra_specs['bar'])
self.assertFalse(volume_type.obj_attr_is_set('qos_specs_id'))
@mock.patch('cinder.volume.volume_types.create')
def test_create(self, volume_type_create):
db_volume_type = fake_volume.fake_db_volume_type()

View File

@ -120,17 +120,20 @@ class SchedulerManagerTestCase(test.TestCase):
@mock.patch('cinder.objects.service.Service.get_minimum_rpc_version')
@mock.patch('cinder.objects.service.Service.get_minimum_obj_version')
@mock.patch('cinder.rpc.LAST_RPC_VERSIONS', {'cinder-volume': '1.3'})
@mock.patch('cinder.rpc.LAST_OBJ_VERSIONS', {'cinder-volume': '1.4',
'cinder-scheduler': '1.4',
'cinder-backup': '1.5'})
def test_reset(self, get_min_obj, get_min_rpc):
mgr = self.manager_cls()
old_version = objects.base.OBJ_VERSIONS.versions[-2]
with mock.patch('cinder.rpc.LAST_OBJ_VERSIONS',
{'cinder-volume': old_version,
'cinder-scheduler': old_version,
'cinder-backup': old_version}):
mgr = self.manager_cls()
volume_rpcapi = mgr.driver.volume_rpcapi
self.assertEqual('1.3', volume_rpcapi.client.version_cap)
self.assertEqual('1.4',
self.assertEqual(old_version,
volume_rpcapi.client.serializer._base.version_cap)
get_min_obj.return_value = objects.base.OBJ_VERSIONS.get_current()
get_min_obj.return_value = self.latest_ovo_version
mgr.reset()
volume_rpcapi = mgr.driver.volume_rpcapi

View File

@ -112,8 +112,10 @@ class TestCase(testtools.TestCase):
RESOURCE_FILTER_FILENAME)
MOCK_WORKER = True
MOCK_TOOZ = True
FAKE_OVO_HISTORY_VERSION = '9999.999'
def __init__(self, *args, **kwargs):
super(TestCase, self).__init__(*args, **kwargs)
# Suppress some log messages during test runs
@ -301,6 +303,16 @@ class TestCase(testtools.TestCase):
self.vt = volume_types.get_default_volume_type()
# Create fake RPC history if we don't have enough to do tests
obj_versions = objects_base.OBJ_VERSIONS
if len(obj_versions) == 1:
vol_vers = obj_versions.get_current_versions()['Volume'].split('.')
new_volume_version = '%s.%s' % (vol_vers[0], int(vol_vers[1]) + 1)
obj_versions.add(self.FAKE_OVO_HISTORY_VERSION,
{'Volume': new_volume_version})
self.latest_ovo_version = obj_versions.get_current()
def _restore_obj_registry(self):
objects_base.CinderObjectRegistry._registry._obj_classes = \
self._base_test_obj_backup

View File

@ -39,16 +39,16 @@ class RPCAPITestCase(test.TestCase):
@mock.patch('cinder.objects.Service.get_minimum_rpc_version',
return_value='1.2')
@mock.patch('cinder.objects.Service.get_minimum_obj_version',
return_value='1.4')
@mock.patch('cinder.objects.Service.get_minimum_obj_version')
@mock.patch('cinder.rpc.get_client')
def test_init(self, get_client, get_min_obj, get_min_rpc):
def fake_get_client(target, version_cap, serializer):
self.assertEqual(FakeAPI.TOPIC, target.topic)
self.assertEqual(FakeAPI.RPC_API_VERSION, target.version)
self.assertEqual('1.2', version_cap)
self.assertEqual('1.4', serializer.version_cap)
self.assertEqual(self.latest_ovo_version, serializer.version_cap)
get_min_obj.return_value = self.latest_ovo_version
get_client.side_effect = fake_get_client
FakeAPI()
@ -73,19 +73,20 @@ class RPCAPITestCase(test.TestCase):
@mock.patch('cinder.objects.Service.get_minimum_obj_version')
@mock.patch('cinder.rpc.get_client')
@mock.patch('cinder.rpc.LAST_RPC_VERSIONS', {'cinder-scheduler': '1.4'})
@mock.patch('cinder.rpc.LAST_OBJ_VERSIONS', {'cinder-scheduler': '1.3'})
def test_init_cached_caps(self, get_client, get_min_obj, get_min_rpc):
def fake_get_client(target, version_cap, serializer):
self.assertEqual(FakeAPI.TOPIC, target.topic)
self.assertEqual(FakeAPI.RPC_API_VERSION, target.version)
self.assertEqual('1.4', version_cap)
self.assertEqual('1.3', serializer.version_cap)
self.assertEqual(self.latest_ovo_version, serializer.version_cap)
get_client.side_effect = fake_get_client
FakeAPI()
with mock.patch('cinder.rpc.LAST_OBJ_VERSIONS',
{'cinder-scheduler': self.latest_ovo_version}):
FakeAPI()
self.assertFalse(get_min_obj.called)
self.assertFalse(get_min_rpc.called)
get_min_obj.assert_not_called()
get_min_rpc.assert_not_called()
@ddt.data([], ['noop'], ['noop', 'noop'])
@mock.patch('oslo_messaging.JsonPayloadSerializer', wraps=True)

View File

@ -380,19 +380,11 @@ class ServiceTestCase(test.TestCase):
app.stop()
@mock.patch('cinder.objects.Service.get_minimum_obj_version',
return_value='1.6')
def test_start_rpc_and_init_host_no_cluster(self, is_upgrading_mock):
"""Test that without cluster we don't create rpc service."""
app = service.Service.create(host=self.host,
binary=constants.VOLUME_BINARY,
cluster=None, topic=self.topic)
self._check_rpc_servers_and_init_host(app, False, None)
@mock.patch('cinder.objects.Service.get_minimum_obj_version')
def test_start_rpc_and_init_host_cluster(self, get_min_obj_mock):
"""Test that with cluster we create the rpc service."""
get_min_obj_mock.return_value = '1.7'
# cluster was introduced in 1.7, so latest will be enough to test this
get_min_obj_mock.return_value = self.latest_ovo_version
cluster = 'cluster@backend#pool'
self.host = 'host@backend#pool'
app = service.Service.create(host=self.host,

View File

@ -125,15 +125,18 @@ class VolumeTestCase(base.BaseVolumeTestCase):
@mock.patch('cinder.objects.service.Service.get_minimum_rpc_version')
@mock.patch('cinder.objects.service.Service.get_minimum_obj_version')
@mock.patch('cinder.rpc.LAST_RPC_VERSIONS', {'cinder-scheduler': '1.3'})
@mock.patch('cinder.rpc.LAST_OBJ_VERSIONS', {'cinder-scheduler': '1.4'})
def test_reset(self, get_min_obj, get_min_rpc):
vol_mgr = vol_manager.VolumeManager()
old_version = objects.base.OBJ_VERSIONS.versions[-2]
with mock.patch('cinder.rpc.LAST_OBJ_VERSIONS',
{'cinder-scheduler': old_version}):
vol_mgr = vol_manager.VolumeManager()
scheduler_rpcapi = vol_mgr.scheduler_rpcapi
self.assertEqual('1.3', scheduler_rpcapi.client.version_cap)
self.assertEqual('1.4',
self.assertEqual(old_version,
scheduler_rpcapi.client.serializer._base.version_cap)
get_min_obj.return_value = objects.base.OBJ_VERSIONS.get_current()
get_min_obj.return_value = self.latest_ovo_version
vol_mgr.reset()
scheduler_rpcapi = vol_mgr.scheduler_rpcapi