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
This commit is contained in:
Michał Dulko 2016-01-14 19:37:18 +01:00
parent 45145e5969
commit 4226d37272
4 changed files with 100 additions and 7 deletions

View File

@ -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)

View File

@ -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):

View File

@ -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

View File

@ -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.')