diff --git a/neutron/objects/base.py b/neutron/objects/base.py index fac944e7f39..485243edc5b 100644 --- a/neutron/objects/base.py +++ b/neutron/objects/base.py @@ -68,6 +68,10 @@ class NeutronSyntheticFieldMultipleForeignKeys(exceptions.NeutronException): "foreign key") +class NeutronSyntheticFieldsForeignKeysNotFound(exceptions.NeutronException): + message = _("%(child)s does not define a foreign key for %(parent)s") + + def get_updatable_fields(cls, fields): fields = fields.copy() for field in cls.fields_no_update: @@ -445,6 +449,7 @@ class NeutronDbObject(NeutronObject): This method doesn't take care of loading synthetic fields that aren't stored in the DB, e.g. 'shared' in RBAC policy. """ + clsname = self.__class__.__name__ # TODO(rossella_s) Find a way to handle ObjectFields with # subclasses=True @@ -462,7 +467,11 @@ class NeutronDbObject(NeutronObject): # QosRule continue objclass = objclasses[0] - if len(objclass.foreign_keys.keys()) > 1: + foreign_keys = objclass.foreign_keys.get(clsname) + if not foreign_keys: + raise NeutronSyntheticFieldsForeignKeysNotFound( + parent=clsname, child=objclass.__name__) + if len(foreign_keys.keys()) > 1: raise NeutronSyntheticFieldMultipleForeignKeys(field=field) synthetic_field_db_name = ( @@ -482,7 +491,7 @@ class NeutronDbObject(NeutronObject): synth_objs = objclass.get_objects( self.obj_context, **{ k: getattr(self, v) - for k, v in objclass.foreign_keys.items()}) + for k, v in foreign_keys.items()}) if isinstance(self.fields[field], obj_fields.ObjectField): setattr(self, field, synth_objs[0] if synth_objs else None) else: diff --git a/neutron/objects/network/network_segment.py b/neutron/objects/network/network_segment.py index 9f8c0c6d4f4..9283a4b315d 100644 --- a/neutron/objects/network/network_segment.py +++ b/neutron/objects/network/network_segment.py @@ -34,4 +34,4 @@ class NetworkSegment(base.NeutronDbObject): 'segment_index': obj_fields.IntegerField(default=0) } - foreign_keys = {'network_id': 'id'} + foreign_keys = {'Network': {'network_id': 'id'}} diff --git a/neutron/objects/securitygroup.py b/neutron/objects/securitygroup.py index fcd17e37dff..129b36c56a7 100644 --- a/neutron/objects/securitygroup.py +++ b/neutron/objects/securitygroup.py @@ -106,7 +106,7 @@ class SecurityGroupRule(base.NeutronDbObject): 'remote_ip_prefix': obj_fields.IPNetworkField(nullable=True), } - foreign_keys = {'security_group_id': 'id'} + foreign_keys = {'SecurityGroup': {'security_group_id': 'id'}} fields_no_update = ['project_id', 'security_group_id'] diff --git a/neutron/objects/subnet.py b/neutron/objects/subnet.py index 6d17039243c..b57a2eb343f 100644 --- a/neutron/objects/subnet.py +++ b/neutron/objects/subnet.py @@ -32,7 +32,7 @@ class DNSNameServer(base.NeutronDbObject): primary_keys = ['address', 'subnet_id'] - foreign_keys = {'subnet_id': 'id'} + foreign_keys = {'Subnet': {'subnet_id': 'id'}} fields = { 'address': obj_fields.StringField(), @@ -62,7 +62,7 @@ class Route(base.NeutronDbObject): primary_keys = ['destination', 'nexthop', 'subnet_id'] - foreign_keys = {'subnet_id': 'id'} + foreign_keys = {'Subnet': {'subnet_id': 'id'}} fields = { 'subnet_id': obj_fields.UUIDField(), @@ -99,7 +99,7 @@ class IPAllocationPool(base.NeutronDbObject): db_model = models_v2.IPAllocationPool - foreign_keys = {'subnet_id': 'id'} + foreign_keys = {'Subnet': {'subnet_id': 'id'}} fields_need_translation = { 'start': 'first_ip', @@ -172,7 +172,7 @@ class Subnet(base.NeutronDbObject): synthetic_fields = ['allocation_pools', 'dns_nameservers', 'host_routes', 'shared'] - foreign_keys = {'network_id': 'id'} + foreign_keys = {'Network': {'network_id': 'id'}} fields_no_update = ['project_id'] diff --git a/neutron/objects/trunk.py b/neutron/objects/trunk.py index 96476bc6d69..e74015c8999 100644 --- a/neutron/objects/trunk.py +++ b/neutron/objects/trunk.py @@ -32,7 +32,7 @@ class SubPort(base.NeutronDbObject): db_model = models.SubPort primary_keys = ['port_id'] - foreign_keys = {'trunk_id': 'id'} + foreign_keys = {'Trunk': {'trunk_id': 'id'}} fields = { 'port_id': obj_fields.UUIDField(), diff --git a/neutron/tests/unit/objects/test_base.py b/neutron/tests/unit/objects/test_base.py index deb1f26fbca..2629ef01463 100644 --- a/neutron/tests/unit/objects/test_base.py +++ b/neutron/tests/unit/objects/test_base.py @@ -67,7 +67,32 @@ class FakeSmallNeutronObject(base.NeutronDbObject): primary_keys = ['field1'] - foreign_keys = {'field1': 'id'} + foreign_keys = { + 'FakeNeutronObjectCompositePrimaryKeyWithId': {'field1': 'id'}, + 'FakeNeutronDbObject': {'field2': 'id'}, + 'FakeNeutronObjectUniqueKey': {'field3': 'id'}, + } + + fields = { + 'field1': obj_fields.UUIDField(), + 'field2': obj_fields.UUIDField(), + 'field3': obj_fields.UUIDField(), + } + + +@obj_base.VersionedObjectRegistry.register_if(False) +class FakeSmallNeutronObjectWithMultipleParents(base.NeutronDbObject): + # Version 1.0: Initial version + VERSION = '1.0' + + db_model = ObjectFieldsModel + + primary_keys = ['field1', 'field2'] + + foreign_keys = { + 'FakeParent': {'field1': 'id'}, + 'FakeParent2': {'field2': 'id'}, + } fields = { 'field1': obj_fields.UUIDField(), @@ -75,6 +100,25 @@ class FakeSmallNeutronObject(base.NeutronDbObject): } +@obj_base.VersionedObjectRegistry.register_if(False) +class FakeParent(base.NeutronDbObject): + # Version 1.0: Initial version + VERSION = '1.0' + + db_model = ObjectFieldsModel + + primary_keys = ['field1', 'field2'] + + fields = { + 'id': obj_fields.UUIDField(), + 'children': obj_fields.ListOfObjectsField( + 'FakeSmallNeutronObjectWithMultipleParents', + nullable=True) + } + + synthetic_fields = ['children'] + + @obj_base.VersionedObjectRegistry.register_if(False) class FakeWeirdKeySmallNeutronObject(base.NeutronDbObject): # Version 1.0: Initial version @@ -84,7 +128,10 @@ class FakeWeirdKeySmallNeutronObject(base.NeutronDbObject): primary_keys = ['field1'] - foreign_keys = {'field1': 'weird_key'} + foreign_keys = { + 'FakeNeutronObjectNonStandardPrimaryKey': {'field1': 'weird_key'}, + 'FakeNeutronObjectCompositePrimaryKey': {'field2': 'weird_key'}, + } fields = { 'field1': obj_fields.UUIDField(), @@ -225,7 +272,9 @@ class FakeNeutronObjectMultipleForeignKeys(base.NeutronDbObject): db_model = ObjectFieldsModel - foreign_keys = {'field1': 'id', 'field2': 'id'} + foreign_keys = { + 'FakeNeutronObjectSyntheticField': {'field1': 'id', 'field2': 'id'}, + } fields = { 'field1': obj_fields.UUIDField(), @@ -494,12 +543,14 @@ class BaseObjectIfaceTestCase(_BaseObjectTestCase, test_base.BaseTestCase): if self._test_class.is_object_field(field): obj_class = self._get_ovo_object_class(self._test_class, field) + foreign_keys = obj_class.foreign_keys.get( + self._test_class.__name__) mock_calls.append( mock.call( self.context, obj_class.db_model, _pager=self.pager_map[obj_class.obj_name()], **{k: db_obj[v] - for k, v in obj_class.foreign_keys.items()})) + for k, v in foreign_keys.items()})) return mock_calls def test_get_objects(self): @@ -904,6 +955,31 @@ class BaseDbObjectMultipleForeignKeysTestCase(_BaseObjectTestCase, obj.load_synthetic_db_fields) +class BaseDbObjectMultipleParentsForForeignKeysTestCase( + _BaseObjectTestCase, + test_base.BaseTestCase): + + _test_class = FakeParent + + def test_load_synthetic_db_fields_with_multiple_parents(self): + child_cls = FakeSmallNeutronObjectWithMultipleParents + self.obj_registry.register(child_cls) + self.obj_registry.register(FakeParent) + obj = self._test_class(self.context, **self.obj_fields[0]) + fake_children = [ + child_cls( + self.context, **child_cls.modify_fields_from_db( + self.get_random_fields(obj_cls=child_cls)) + ) + for _ in range(5) + ] + with mock.patch.object(child_cls, 'get_objects', + return_value=fake_children) as get_objects: + obj.load_synthetic_db_fields() + get_objects.assert_called_once_with(self.context, field1=obj.id) + self.assertEqual(fake_children, obj.children) + + class BaseDbObjectTestCase(_BaseObjectTestCase, test_db_base_plugin_v2.DbOperationBoundMixin): def setUp(self): @@ -1130,7 +1206,8 @@ class BaseDbObjectTestCase(_BaseObjectTestCase, db_obj[field][0]) # make sure children point to the base object - for local_field, foreign_key in objclass.foreign_keys.items(): + foreign_keys = objclass.foreign_keys.get(obj.__class__.__name__) + for local_field, foreign_key in foreign_keys.items(): objclass_fields[local_field] = obj.get(foreign_key) synth_field_obj = objclass(self.context, **objclass_fields)