From 5179896e771c8c4789c605bad3e2ef188808e831 Mon Sep 17 00:00:00 2001 From: Thomas Morin Date: Thu, 12 Apr 2018 15:07:20 +0200 Subject: [PATCH] Allow sub-resources to have standard attributes Prior to this change a DB model with standard attributes could declare that it was mapping to an API resource, but not declare a mapping to a sub-resources (see bug 1763347). This change allows DB models with standard attributes to advertise that they map API *sub*-resources, and modifies the code that extends DB resources to support these specific declarations. Closes-Bug: 1763347 Needed-By: I77ce46c0f33e2a366076d51ce6586fb3008dc6b1 Change-Id: I7630aab5e4f38d0fba862adc2426d4a7ca04a679 --- neutron/db/standard_attr.py | 43 +++++++++++++++---- neutron/extensions/revisions.py | 5 +-- neutron/extensions/standardattrdescription.py | 5 +-- neutron/extensions/stdattrs_common.py | 31 +++++++++++++ neutron/extensions/timestamp.py | 5 +-- neutron/tests/unit/db/test_standard_attr.py | 16 +++++++ 6 files changed, 87 insertions(+), 18 deletions(-) create mode 100644 neutron/extensions/stdattrs_common.py diff --git a/neutron/db/standard_attr.py b/neutron/db/standard_attr.py index 1ce9be6e59f..6f0475d3f78 100644 --- a/neutron/db/standard_attr.py +++ b/neutron/db/standard_attr.py @@ -98,6 +98,22 @@ class HasStandardAttributes(object): return cls.api_collections raise NotImplementedError("%s must define api_collections" % cls) + @classmethod + def get_api_sub_resources(cls): + """Define the API sub-resources this object will appear under. + + This should return a list of API sub-resources that the object + will be exposed under. + + This is used by the standard attr extensions to discover which + sub-resources need to be extended with the standard attr fields + (e.g. created_at/updated_at/etc). + """ + try: + return cls.api_sub_resources + except AttributeError: + return [] + @classmethod def get_collection_resource_map(cls): try: @@ -173,17 +189,26 @@ class HasStandardAttributes(object): self.standard_attr.bump_revision() -def get_standard_attr_resource_model_map(): +def _resource_model_map_helper(rs_map, resource, subclass): + if resource in rs_map: + raise RuntimeError("Model %(sub)s tried to register for API resource " + "%(res)s which conflicts with model %(other)s." % + dict(sub=subclass, + other=rs_map[resource], + res=resource)) + rs_map[resource] = subclass + + +def get_standard_attr_resource_model_map(include_resources=True, + include_sub_resources=True): rs_map = {} for subclass in HasStandardAttributes.__subclasses__(): - for resource in subclass.get_api_collections(): - if resource in rs_map: - raise RuntimeError("Model %(sub)s tried to register for " - "API resource %(res)s which conflicts " - "with model %(other)s." % - dict(sub=subclass, other=rs_map[resource], - res=resource)) - rs_map[resource] = subclass + if include_resources: + for resource in subclass.get_api_collections(): + _resource_model_map_helper(rs_map, resource, subclass) + if include_sub_resources: + for sub_resource in subclass.get_api_sub_resources(): + _resource_model_map_helper(rs_map, sub_resource, subclass) return rs_map diff --git a/neutron/extensions/revisions.py b/neutron/extensions/revisions.py index d8e268d4935..38098026ef2 100644 --- a/neutron/extensions/revisions.py +++ b/neutron/extensions/revisions.py @@ -13,7 +13,7 @@ from neutron_lib.api import extensions -from neutron.db import standard_attr +from neutron.extensions import stdattrs_common REVISION = 'revision_number' @@ -46,5 +46,4 @@ class Revisions(extensions.ExtensionDescriptor): def get_extended_resources(self, version): if version != "2.0": return {} - rs_map = standard_attr.get_standard_attr_resource_model_map() - return {resource: REVISION_BODY for resource in rs_map} + return stdattrs_common.stdattrs_extended_resources(REVISION_BODY) diff --git a/neutron/extensions/standardattrdescription.py b/neutron/extensions/standardattrdescription.py index acbebca91d4..98da8938f3f 100644 --- a/neutron/extensions/standardattrdescription.py +++ b/neutron/extensions/standardattrdescription.py @@ -16,7 +16,7 @@ from neutron_lib.api import extensions from neutron_lib.db import constants as db_const -from neutron.db import standard_attr +from neutron.extensions import stdattrs_common DESCRIPTION_BODY = { @@ -51,5 +51,4 @@ class Standardattrdescription(extensions.ExtensionDescriptor): def get_extended_resources(self, version): if version != "2.0": return {} - rs_map = standard_attr.get_standard_attr_resource_model_map() - return {resource: DESCRIPTION_BODY for resource in rs_map} + return stdattrs_common.stdattrs_extended_resources(DESCRIPTION_BODY) diff --git a/neutron/extensions/stdattrs_common.py b/neutron/extensions/stdattrs_common.py new file mode 100644 index 00000000000..e116a5bd6fc --- /dev/null +++ b/neutron/extensions/stdattrs_common.py @@ -0,0 +1,31 @@ +# Copyright (c) 2018 Orange. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import itertools + +from neutron.db import standard_attr + + +def stdattrs_extended_resources(attributes): + r_map = standard_attr.get_standard_attr_resource_model_map( + include_resources=True, + include_sub_resources=False) + sr_map = standard_attr.get_standard_attr_resource_model_map( + include_resources=False, + include_sub_resources=True) + return dict(itertools.chain( + {r: attributes for r in r_map}.items(), + {sr: {'parameters': attributes} for sr in sr_map}.items() + )) diff --git a/neutron/extensions/timestamp.py b/neutron/extensions/timestamp.py index 41e50409f72..8eacc64c56f 100644 --- a/neutron/extensions/timestamp.py +++ b/neutron/extensions/timestamp.py @@ -14,7 +14,7 @@ from neutron_lib.api import extensions -from neutron.db import standard_attr +from neutron.extensions import stdattrs_common # Attribute Map @@ -57,5 +57,4 @@ class Timestamp(extensions.ExtensionDescriptor): def get_extended_resources(self, version): if version != "2.0": return {} - rs_map = standard_attr.get_standard_attr_resource_model_map() - return {resource: TIMESTAMP_BODY for resource in rs_map} + return stdattrs_common.stdattrs_extended_resources(TIMESTAMP_BODY) diff --git a/neutron/tests/unit/db/test_standard_attr.py b/neutron/tests/unit/db/test_standard_attr.py index 1039ebe4b98..b65101586ca 100644 --- a/neutron/tests/unit/db/test_standard_attr.py +++ b/neutron/tests/unit/db/test_standard_attr.py @@ -42,10 +42,26 @@ class StandardAttrTestCase(base.BaseTestCase): standard_attr.model_base.HasId, base): api_collections = ['my_resource', 'my_resource2'] + api_sub_resources = ['my_subresource'] rs_map = standard_attr.get_standard_attr_resource_model_map() self.assertEqual(MyModel, rs_map['my_resource']) self.assertEqual(MyModel, rs_map['my_resource2']) + self.assertEqual(MyModel, rs_map['my_subresource']) + + sub_rs_map = standard_attr.get_standard_attr_resource_model_map( + include_resources=False, + include_sub_resources=True) + self.assertNotIn('my_resource', sub_rs_map) + self.assertNotIn('my_resource2', sub_rs_map) + self.assertEqual(MyModel, sub_rs_map['my_subresource']) + + nosub_rs_map = standard_attr.get_standard_attr_resource_model_map( + include_resources=True, + include_sub_resources=False) + self.assertEqual(MyModel, nosub_rs_map['my_resource']) + self.assertEqual(MyModel, nosub_rs_map['my_resource2']) + self.assertNotIn('my_subresource', nosub_rs_map) class Dup(standard_attr.HasStandardAttributes, standard_attr.model_base.HasId,