Require List objects to be able to backlevel their contents

Right now, a client declares its supported version of a given object
automatically in the remoted calls it makes to conductor. However,
in the case of things like InstanceList.get_by_foo(), they are
reporting the version of their InstanceList object, not their
Instance object. Conductor fills a version-matching InstanceList
object with brand new Instance objects, which the client, of course,
barfs on.

There may be a better way to handle this going forward, but for now,
stop the bleeding by requiring a version bump to the corresponding
List object whenever the object type it contains takes a version
bump. This adds a test to validate that all the objects registered
have a suitable mapping for the current version in the tree.

Since this actually caused a breakage in the Instance object
recently, this also bumps the InstanceList version so that
conductors running this commit (or later) will properly send
version 1.9 Instance objects to Havana clients and version 1.10+
to newer ones.

Change-Id: I2668dead4784fbd0411d1b6372a9a8006eeb2e84
Related-Bug: #1258256
Closes-Bug: #1254902
This commit is contained in:
Dan Smith 2013-11-25 14:48:26 -08:00
parent bfd8378c6a
commit ff221c6cc6
15 changed files with 126 additions and 1 deletions

View File

@ -147,11 +147,17 @@ class Aggregate(base.NovaPersistentObject, base.NovaObject):
class AggregateList(base.ObjectListBase, base.NovaObject):
# Version 1.0: Initial version
# Version 1.1: Added key argument to get_by_host()
# Aggregate <= version 1.1
VERSION = '1.1'
fields = {
'objects': fields.ListOfObjectsField('Aggregate'),
}
child_versions = {
'1.0': '1.1',
'1.1': '1.1',
# NOTE(danms): Aggregate was at 1.1 before we added this
}
@base.remotable_classmethod
def get_all(cls, context):

View File

@ -435,6 +435,11 @@ class ObjectListBase(object):
'objects': fields.ListOfObjectsField('NovaObject'),
}
# This is a dictionary of my_version:child_version mappings so that
# we can support backleveling our contents based on the version
# requested of the list object.
child_versions = {}
def __iter__(self):
"""List iterator interface."""
return iter(self.objects)
@ -478,6 +483,15 @@ class ObjectListBase(object):
objects.append(obj)
return objects
def obj_make_compatible(self, primitive, target_version):
primitives = primitive['objects']
child_target_version = self.child_versions.get(target_version, '1.0')
for index, item in enumerate(self.objects):
self.objects[index].obj_make_compatible(
primitives[index]['nova_object.data'],
child_target_version)
primitives[index]['nova_object.version'] = child_target_version
class NovaObjectSerializer(nova.openstack.common.rpc.serializer.Serializer):
"""A NovaObject-aware Serializer.

View File

@ -91,9 +91,16 @@ class ComputeNode(base.NovaPersistentObject, base.NovaObject):
class ComputeNodeList(base.ObjectListBase, base.NovaObject):
# Version 1.0: Initial version
# ComputeNode <= version 1.2
VERSION = '1.0'
fields = {
'objects': fields.ListOfObjectsField('ComputeNode'),
}
child_versions = {
'1.0': '1.2',
# NOTE(danms): ComputeNode was at 1.2 before we added this
}
@base.remotable_classmethod
def get_all(cls, context):

View File

@ -168,6 +168,9 @@ class FlavorList(base.ObjectListBase, base.NovaObject):
fields = {
'objects': fields.ListOfObjectsField('Flavor'),
}
child_versions = {
'1.0': '1.0',
}
@base.remotable_classmethod
def get_all(cls, context, inactive=False, filters=None,

View File

@ -531,11 +531,18 @@ def _make_instance_list(context, inst_list, db_inst_list, expected_attrs):
class InstanceList(base.ObjectListBase, base.NovaObject):
# Version 1.0: Initial version
# Version 1.1: Added use_slave to get_by_host
VERSION = '1.1'
# Instance <= version 1.9
# Version 1.2: Instance <= version 1.11
VERSION = '1.2'
fields = {
'objects': fields.ListOfObjectsField('Instance'),
}
child_versions = {
'1.1': '1.9',
# NOTE(danms): Instance was at 1.9 before we added this
'1.2': '1.11',
}
@base.remotable_classmethod
def get_by_filters(cls, context, filters,

View File

@ -77,9 +77,16 @@ class InstanceAction(base.NovaPersistentObject, base.NovaObject):
class InstanceActionList(base.ObjectListBase, base.NovaObject):
# Version 1.0: Initial version
# InstanceAction <= version 1.1
VERSION = '1.0'
fields = {
'objects': fields.ListOfObjectsField('InstanceAction'),
}
child_versions = {
'1.0': '1.1',
# NOTE(danms): InstanceAction was at 1.1 before we added this
}
@base.remotable_classmethod
def get_by_instance_uuid(cls, context, instance_uuid):
@ -157,6 +164,9 @@ class InstanceActionEventList(base.ObjectListBase, base.NovaObject):
fields = {
'objects': fields.ListOfObjectsField('InstanceActionEvent'),
}
child_versions = {
'1.0': '1.0',
}
@base.remotable_classmethod
def get_by_action(cls, context, action_id):

View File

@ -58,9 +58,17 @@ def _make_fault_list(faultlist, db_faultlist):
class InstanceFaultList(base.ObjectListBase, base.NovaObject):
# Version 1.0: Initial version
# InstanceFault <= version 1.1
VERSION = '1.0'
fields = {
'objects': fields.ListOfObjectsField('InstanceFault'),
}
child_versions = {
'1.0': '1.1',
# NOTE(danms): InstanceFault was at 1.1 before we added this
}
@base.remotable_classmethod
def get_by_instance_uuids(cls, context, instance_uuids):

View File

@ -123,9 +123,17 @@ def _make_instance_group_list(context, inst_list, db_list):
class InstanceGroupList(base.ObjectListBase, base.NovaObject):
# Version 1.0: Initial version
# InstanceGroup <= version 1.3
VERSION = '1.0'
fields = {
'objects': fields.ListOfObjectsField('InstanceGroup'),
}
child_versions = {
'1.0': '1.3',
# NOTE(danms): InstanceGroup was at 1.3 before we added this
}
@base.remotable_classmethod
def get_by_project_id(cls, context, project_id):

View File

@ -59,9 +59,17 @@ class KeyPair(base.NovaPersistentObject, base.NovaObject):
class KeyPairList(base.ObjectListBase, base.NovaObject):
# Version 1.0: Initial version
# KeyPair <= version 1.1
VERSION = '1.0'
fields = {
'objects': fields.ListOfObjectsField('KeyPair'),
}
child_versions = {
'1.0': '1.1',
# NOTE(danms): KeyPair was at 1.1 before we added this
}
@base.remotable_classmethod
def get_by_user(cls, context, user_id):

View File

@ -87,12 +87,18 @@ def _make_list(context, list_obj, item_cls, db_list):
class MigrationList(base.ObjectListBase, base.NovaObject):
# Version 1.0: Initial version
# Migration <= 1.1
# Version 1.1: Added use_slave to get_unconfirmed_by_dest_compute
VERSION = '1.1'
fields = {
'objects': fields.ListOfObjectsField('Migration'),
}
child_versions = {
'1.0': '1.1',
# NOTE(danms): Migration was at 1.1 before we added this
'1.1': '1.1',
}
@base.remotable_classmethod
def get_unconfirmed_by_dest_compute(cls, context, confirm_window,

View File

@ -251,9 +251,17 @@ def _make_pci_list(context, pci_list, db_list):
class PciDeviceList(base.ObjectListBase, base.NovaObject):
# Version 1.0: Initial version
# PciDevice <= 1.1
VERSION = '1.0'
fields = {
'objects': fields.ListOfObjectsField('PciDevice'),
}
child_versions = {
'1.0': '1.1',
# NOTE(danms): PciDevice was at 1.1 before we added this
}
def __init__(self):
super(PciDeviceList, self).__init__()

View File

@ -82,9 +82,17 @@ def _make_secgroup_list(context, secgroup_list, db_secgroup_list):
class SecurityGroupList(base.ObjectListBase, base.NovaObject):
# Version 1.0: Initial version
# SecurityGroup <= version 1.1
VERSION = '1.0'
fields = {
'objects': fields.ListOfObjectsField('SecurityGroup'),
}
child_versions = {
'1.0': '1.1',
# NOTE(danms): SecurityGroup was at 1.1 before we added this
}
def __init__(self):
super(SecurityGroupList, self).__init__()

View File

@ -64,6 +64,9 @@ class SecurityGroupRuleList(base.ObjectListBase, base.NovaObject):
fields = {
'objects': fields.ListOfObjectsField('SecurityGroupRule'),
}
child_versions = {
'1.0': '1.0',
}
@base.remotable_classmethod
def get_by_security_group_id(cls, context, secgroup_id):

View File

@ -124,9 +124,17 @@ class Service(base.NovaPersistentObject, base.NovaObject):
class ServiceList(base.ObjectListBase, base.NovaObject):
# Version 1.0: Initial version
# Service <= version 1.2
VERSION = '1.0'
fields = {
'objects': fields.ListOfObjectsField('Service'),
}
child_versions = {
'1.0': '1.2',
# NOTE(danms): Service was at 1.2 before we added this
}
@base.remotable_classmethod
def get_by_topic(cls, context, topic):

View File

@ -721,6 +721,27 @@ class TestObjectListBase(test.TestCase):
self.assertEqual([x.foo for x in obj],
[y.foo for y in obj2])
def _test_object_list_version_mappings(self, list_obj_class):
# Figure out what sort of object this list is for
list_field = list_obj_class.fields['objects']
item_obj_field = list_field._type._element_type
item_obj_name = item_obj_field._type._obj_name
# Look through all object classes of this type and make sure that
# the versions we find are covered by the parent list class
for item_class in base.NovaObject._obj_classes[item_obj_name]:
self.assertIn(
item_class.VERSION,
list_obj_class.child_versions.values())
def test_object_version_mappings(self):
# Find all object list classes and make sure that they at least handle
# all the current object versions
for obj_classes in base.NovaObject._obj_classes.values():
for obj_class in obj_classes:
if issubclass(obj_class, base.ObjectListBase):
self._test_object_list_version_mappings(obj_class)
class TestObjectSerializer(_BaseTestCase):
def test_serialize_entity_primitive(self):