From: MichaƂ Dulko Date: Thu, 14 Jan 2016 18:37:18 +0000 (+0100) Subject: Report RPC and objects versions X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=4226d372723808e58d7b9311165d9717fe2b2936;p=openstack-build%2Fcinder-build.git Report RPC and objects versions While doing a rolling upgrade we will have services running in various versions. In order to determine how to downgrade the RPC request and payload (objects) to the lowest common version we need to actually report versions of RPC servers (managers). This commit implements such reporting in generic cinder.service module. It is using DB columns that were merged in Liberty to save this information. To have a single version string identify a set of o.vo versions we need to have dictionary with objects versions history. In that purpose a dict-like CinderObjectVersionsHistory class and OBJ_VERSIONS instance of it is added to cinder.objects.base. A unit test enforcing bumping the versions is also included with the patch. Change-Id: Ic3b57450e9d6f155a7eb805d224389f5f09eae18 Partial-Implements: blueprint rpc-object-compatibility --- diff --git a/cinder/objects/base.py b/cinder/objects/base.py index ef8c1150a..f55ea4c6f 100644 --- a/cinder/objects/base.py +++ b/cinder/objects/base.py @@ -33,6 +33,71 @@ remotable_classmethod = base.remotable_classmethod obj_make_list = base.obj_make_list +class CinderObjectVersionsHistory(dict): + """Helper class that maintains objects version history. + + Current state of object versions is aggregated in a single version number + that explicitily identifies a set of object versions. That way a service + is able to report what objects it supports using a single string and all + the newer services will know exactly what that mean for a single object. + """ + + def __init__(self): + super(CinderObjectVersionsHistory, self).__init__() + # NOTE(dulek): This is our pre-history and a starting point - Liberty. + # We want Mitaka to be able to talk to Liberty services, so we need to + # handle backporting to these objects versions (although I don't expect + # we've made a lot of incompatible changes inside the objects). + # + # If an object doesn't exist in Liberty, RPC API compatibility layer + # shouldn't send it or convert it to a dictionary. + # + # 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', + 'BackupList': '1.0', + 'ConsistencyGroup': '1.1', + 'ConsistencyGroupList': '1.0', + 'Service': '1.0', + 'ServiceList': '1.0', + 'Snapshot': '1.0', + 'SnapshotList': '1.0', + 'Volume': '1.1', + 'VolumeAttachment': '1.0', + 'VolumeAttachmentList': '1.0', + 'VolumeList': '1.1', + 'VolumeType': '1.0', + 'VolumeTypeList': '1.0', + } + + def get_current(self): + return self.versions[-1] + + def get_current_versions(self): + return self[self.get_current()] + + def add(self, ver, updates): + self[ver] = self[self.get_current()].copy() + self.versions.append(ver) + self[ver].update(updates) + + +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. +# +# 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'}) + + class CinderObjectRegistry(base.VersionedObjectRegistry): def registration_hook(self, cls, index): setattr(objects, cls.obj_name(), cls) diff --git a/cinder/objects/service.py b/cinder/objects/service.py index bc4f0d848..ab4847db3 100644 --- a/cinder/objects/service.py +++ b/cinder/objects/service.py @@ -32,7 +32,8 @@ class Service(base.CinderPersistentObject, base.CinderObject, base.CinderObjectDictCompat, base.CinderComparableObject): # Version 1.0: Initial version - VERSION = '1.0' + # Version 1.1: Add rpc_current_version and object_current_version fields + VERSION = '1.1' fields = { 'id': fields.IntegerField(), @@ -46,6 +47,8 @@ class Service(base.CinderPersistentObject, base.CinderObject, 'disabled_reason': fields.StringField(nullable=True), 'modified_at': fields.DateTimeField(nullable=True), + 'rpc_current_version': fields.StringField(nullable=True), + 'object_current_version': fields.StringField(nullable=True), } def obj_make_compatible(self, primitive, target_version): diff --git a/cinder/service.py b/cinder/service.py index 66c015e21..4871b524a 100644 --- a/cinder/service.py +++ b/cinder/service.py @@ -150,6 +150,10 @@ class Service(service.Service): try: service_ref = objects.Service.get_by_args( ctxt, self.host, self.binary) + service_ref.rpc_current_version = self.manager.RPC_API_VERSION + obj_version = objects_base.OBJ_VERSIONS.get_current() + service_ref.object_current_version = obj_version + service_ref.save() self.service_id = service_ref.id except exception.NotFound: self._create_service_ref(ctxt) @@ -203,11 +207,15 @@ class Service(service.Service): def _create_service_ref(self, context): zone = CONF.storage_availability_zone - kwargs = {'host': self.host, - 'binary': self.binary, - 'topic': self.topic, - 'report_count': 0, - 'availability_zone': zone} + kwargs = { + 'host': self.host, + 'binary': self.binary, + 'topic': self.topic, + 'report_count': 0, + 'availability_zone': zone, + 'rpc_current_version': self.manager.RPC_API_VERSION, + 'object_current_version': objects_base.OBJ_VERSIONS.get_current(), + } service_ref = objects.Service(context=context, **kwargs) service_ref.create() self.service_id = service_ref.id diff --git a/cinder/tests/unit/objects/test_objects.py b/cinder/tests/unit/objects/test_objects.py index bda91f30b..8908c25f9 100644 --- a/cinder/tests/unit/objects/test_objects.py +++ b/cinder/tests/unit/objects/test_objects.py @@ -28,7 +28,7 @@ object_data = { 'CGSnapshotList': '1.0-e8c3f4078cd0ee23487b34d173eec776', 'ConsistencyGroup': '1.2-ed7f90a6871991a19af716ade7337fc9', 'ConsistencyGroupList': '1.1-73916823b697dfa0c7f02508d87e0f28', - 'Service': '1.0-64baeb4911dbab1153064dd1c87edb9f', + 'Service': '1.1-9eb00cbd8e2bfb7371343429af54d6e8', 'ServiceList': '1.0-d242d3384b68e5a5a534e090ff1d5161', 'Snapshot': '1.0-a6c33eefeadefb324d79f72f66c54e9a', 'SnapshotList': '1.0-71661e7180ef6cc51501704a9bea4bf1', @@ -51,3 +51,20 @@ class TestObjectVersions(test.TestCase): 'Some objects have changed; please make sure the ' 'versions have been bumped, and then update their ' 'hashes in the object_data map in this test module.') + + def test_versions_history(self): + classes = base.CinderObjectRegistry.obj_classes() + versions = base.OBJ_VERSIONS.get_current_versions() + expected = {} + actual = {} + for name, cls in classes.items(): + if name not in versions: + expected[name] = cls[0].VERSION + elif cls[0].VERSION != versions[name]: + expected[name] = cls[0].VERSION + actual[name] = versions[name] + + self.assertEqual(expected, actual, + 'Some objects versions have changed; please make ' + 'sure a new objects history version was added in ' + 'cinder.objects.base.OBJ_VERSIONS.')