Break out the child version calculation logic from obj_make_compatible()
This pulls out the child version logic from the compatibility routine, so that it can be used in other contexts. I left the tests that verify the versions pointing at the caller to show that this doesn't break anything. Moving them to actually test the core function would be good. However, now the inner version calculation logic is a little more precise, which changed one of the test cases simply because it was not considering the actual child version properly. That same issue plagued other cases because we were not properly calling each case on a clean copy of the primitive (which didn't matter before). Change-Id: Id4071b6bc6e9d4419e9142fa339095f04b182d92 Related-Bug: #1431201
This commit is contained in:
parent
3ae32a84b1
commit
dddd37d091
|
@ -372,6 +372,42 @@ class NovaObject(object):
|
|||
"""Create a copy."""
|
||||
return copy.deepcopy(self)
|
||||
|
||||
def obj_calculate_child_version(self, target_version, child):
|
||||
"""Calculate the appropriate version for a child object.
|
||||
|
||||
This is to be used when backporting an object for an older client.
|
||||
A sub-object will need to be backported to a suitable version for
|
||||
the client as well, and this method will calculate what that
|
||||
version should be, based on obj_relationships.
|
||||
|
||||
:param target_version: Version this object is being backported to
|
||||
:param child: The child field for which the appropriate version
|
||||
is to be calculated
|
||||
:returns: None if the child should be omitted from the backport,
|
||||
otherwise, the version to which the child should be
|
||||
backported
|
||||
"""
|
||||
target_version = utils.convert_version_to_tuple(target_version)
|
||||
for index, versions in enumerate(self.obj_relationships[child]):
|
||||
my_version, child_version = versions
|
||||
my_version = utils.convert_version_to_tuple(my_version)
|
||||
if target_version < my_version:
|
||||
if index == 0:
|
||||
# We're backporting to a version from before this
|
||||
# subobject was added: delete it from the primitive.
|
||||
return None
|
||||
else:
|
||||
# We're in the gap between index-1 and index, so
|
||||
# backport to the older version
|
||||
return self.obj_relationships[child][index - 1][1]
|
||||
elif target_version == my_version:
|
||||
# This is the first mapping that satisfies the
|
||||
# target_version request: backport the object.
|
||||
return child_version
|
||||
# No need to backport, as far as we know, so return the latest
|
||||
# version of the sub-object we know about
|
||||
return self.obj_relationships[child][-1][1]
|
||||
|
||||
def _obj_make_obj_compatible(self, primitive, target_version, field):
|
||||
"""Backlevel a sub-object based on our versioning rules.
|
||||
|
||||
|
@ -392,9 +428,10 @@ class NovaObject(object):
|
|||
if not obj:
|
||||
return
|
||||
if isinstance(obj, NovaObject):
|
||||
obj.obj_make_compatible(
|
||||
primitive[field]['nova_object.data'],
|
||||
to_version)
|
||||
if to_version != primitive[field]['nova_object.version']:
|
||||
obj.obj_make_compatible(
|
||||
primitive[field]['nova_object.data'],
|
||||
to_version)
|
||||
primitive[field]['nova_object.version'] = to_version
|
||||
elif isinstance(obj, list):
|
||||
for i, element in enumerate(obj):
|
||||
|
@ -403,27 +440,11 @@ class NovaObject(object):
|
|||
to_version)
|
||||
primitive[field][i]['nova_object.version'] = to_version
|
||||
|
||||
target_version = utils.convert_version_to_tuple(target_version)
|
||||
for index, versions in enumerate(self.obj_relationships[field]):
|
||||
my_version, child_version = versions
|
||||
my_version = utils.convert_version_to_tuple(my_version)
|
||||
if target_version < my_version:
|
||||
if index == 0:
|
||||
# We're backporting to a version from before this
|
||||
# subobject was added: delete it from the primitive.
|
||||
del primitive[field]
|
||||
else:
|
||||
# We're in the gap between index-1 and index, so
|
||||
# backport to the older version
|
||||
last_child_version = \
|
||||
self.obj_relationships[field][index - 1][1]
|
||||
_do_backport(last_child_version)
|
||||
return
|
||||
elif target_version == my_version:
|
||||
# This is the first mapping that satisfies the
|
||||
# target_version request: backport the object.
|
||||
_do_backport(child_version)
|
||||
return
|
||||
child_version = self.obj_calculate_child_version(target_version, field)
|
||||
if child_version is None:
|
||||
del primitive[field]
|
||||
else:
|
||||
_do_backport(child_version)
|
||||
|
||||
def obj_make_compatible(self, primitive, target_version):
|
||||
"""Make an object representation compatible with a target version.
|
||||
|
|
|
@ -821,45 +821,43 @@ class _TestObject(object):
|
|||
|
||||
def test_obj_make_obj_compatible(self):
|
||||
subobj = MyOwnedObject(baz=1)
|
||||
subobj.VERSION = '1.2'
|
||||
obj = MyObj(rel_object=subobj)
|
||||
obj.obj_relationships = {
|
||||
'rel_object': [('1.5', '1.1'), ('1.7', '1.2')],
|
||||
}
|
||||
primitive = obj.obj_to_primitive()['nova_object.data']
|
||||
orig_primitive = obj.obj_to_primitive()['nova_object.data']
|
||||
with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat:
|
||||
obj._obj_make_obj_compatible(copy.copy(primitive), '1.8',
|
||||
'rel_object')
|
||||
primitive = copy.deepcopy(orig_primitive)
|
||||
obj._obj_make_obj_compatible(primitive, '1.8', 'rel_object')
|
||||
self.assertFalse(mock_compat.called)
|
||||
|
||||
with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat:
|
||||
obj._obj_make_obj_compatible(copy.copy(primitive),
|
||||
'1.7', 'rel_object')
|
||||
mock_compat.assert_called_once_with(
|
||||
primitive['rel_object']['nova_object.data'], '1.2')
|
||||
self.assertEqual('1.2',
|
||||
primitive['rel_object']['nova_object.version'])
|
||||
primitive = copy.deepcopy(orig_primitive)
|
||||
obj._obj_make_obj_compatible(primitive, '1.7', 'rel_object')
|
||||
self.assertFalse(mock_compat.called)
|
||||
|
||||
with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat:
|
||||
obj._obj_make_obj_compatible(copy.copy(primitive),
|
||||
'1.6', 'rel_object')
|
||||
primitive = copy.deepcopy(orig_primitive)
|
||||
obj._obj_make_obj_compatible(primitive, '1.6', 'rel_object')
|
||||
mock_compat.assert_called_once_with(
|
||||
primitive['rel_object']['nova_object.data'], '1.1')
|
||||
self.assertEqual('1.1',
|
||||
primitive['rel_object']['nova_object.version'])
|
||||
|
||||
with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat:
|
||||
obj._obj_make_obj_compatible(copy.copy(primitive), '1.5',
|
||||
'rel_object')
|
||||
primitive = copy.deepcopy(orig_primitive)
|
||||
obj._obj_make_obj_compatible(primitive, '1.5', 'rel_object')
|
||||
mock_compat.assert_called_once_with(
|
||||
primitive['rel_object']['nova_object.data'], '1.1')
|
||||
self.assertEqual('1.1',
|
||||
primitive['rel_object']['nova_object.version'])
|
||||
|
||||
with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat:
|
||||
_prim = copy.copy(primitive)
|
||||
obj._obj_make_obj_compatible(_prim, '1.4', 'rel_object')
|
||||
primitive = copy.deepcopy(orig_primitive)
|
||||
obj._obj_make_obj_compatible(primitive, '1.4', 'rel_object')
|
||||
self.assertFalse(mock_compat.called)
|
||||
self.assertNotIn('rel_object', _prim)
|
||||
self.assertNotIn('rel_object', primitive)
|
||||
|
||||
def test_obj_make_compatible_hits_sub_objects(self):
|
||||
subobj = MyOwnedObject(baz=1)
|
||||
|
|
Loading…
Reference in New Issue