diff --git a/doc/source/contributor/api_attributes.rst b/doc/source/contributor/api_attributes.rst index 89bf20257..ebddefa67 100644 --- a/doc/source/contributor/api_attributes.rst +++ b/doc/source/contributor/api_attributes.rst @@ -89,3 +89,21 @@ The following are the defined keys for attribute maps: ``enforce_policy`` the attribute is actively part of the policy enforcing mechanism, ie: there might be rules which refer to this attribute ``primary_key`` Mark the attribute as a unique key. ====================== ====== + +When extending existing sub-resources, the sub-attribute map must define all +extension attributes under the ``parameters`` object. This instructs the API +internals to add the attributes to the existing sub-resource rather than +overwrite its existing definition. For example: + +.. code-block:: python + + SUB_RESOURCE_ATTRIBUTE_MAP = { + 'existing_subresource_to_extend': { + 'parameters': { + 'new_attr1': { + 'allow_post': False, + # etc.. + } + } + } + } diff --git a/neutron_lib/api/definitions/_dummy.py b/neutron_lib/api/definitions/_dummy.py index 130014b5b..18729e326 100644 --- a/neutron_lib/api/definitions/_dummy.py +++ b/neutron_lib/api/definitions/_dummy.py @@ -72,7 +72,10 @@ RESOURCE_ATTRIBUTE_MAP = { # The subresource attribute map for the extension. It adds child resources # to main extension's resource. The subresource map must have a parent and # a parameters entry. If an extension does not need such a map, None can -# be specified (mandatory). For example: +# be specified (mandatory). +# Note that if an existing sub-resource is being extended, the +# existing resources to extend the new extension attributes must be +# defined under the 'parameters' key. SUB_RESOURCE_ATTRIBUTE_MAP = { 'subfoo': { 'parent': { diff --git a/neutron_lib/tests/unit/api/definitions/base.py b/neutron_lib/tests/unit/api/definitions/base.py index 2a0ef1a55..5897581d2 100644 --- a/neutron_lib/tests/unit/api/definitions/base.py +++ b/neutron_lib/tests/unit/api/definitions/base.py @@ -132,23 +132,36 @@ class DefinitionBaseTestCase(test_base.BaseTestCase): resource[attribute], keyword, value) + def _assert_subresource(self, subresource): + self.assertIn( + self.subresource_map[subresource]['parent']['collection_name'], + base.KNOWN_RESOURCES + self.extension_resources, + 'Sub-resource parent is unknown, check for typos.') + self.assertIn('member_name', + self.subresource_map[subresource]['parent'], + 'Incorrect parent definition, check for typos.') + self.assertParams(self.subresource_map[subresource]['parameters']) + def test_subresource_map(self): if not self.subresource_map: self.skipTest('API extension has no subresource map.') for subresource in self.subresource_map: self.assertIn( - subresource, self.extension_subresources, + subresource, + self.extension_subresources + base.KNOWN_RESOURCES, 'Sub-resource is unknown, check for typos.') - for attribute in self.subresource_map[subresource]: - self.assertIn(attribute, ('parent', 'parameters')) - self.assertIn( - self.subresource_map[subresource]['parent']['collection_name'], - base.KNOWN_RESOURCES + self.extension_resources, - 'Sub-resource parent is unknown, check for typos.') - self.assertIn('member_name', - self.subresource_map[subresource]['parent'], - 'Incorrect parent definition, check for typos.') - self.assertParams(self.subresource_map[subresource]['parameters']) + sub_attrmap = self.subresource_map[subresource] + if 'parent' in sub_attrmap: + self.assertEqual(2, len(sub_attrmap.keys())) + self.assertIn('parent', sub_attrmap) + self.assertIn('parameters', sub_attrmap) + self._assert_subresource(subresource) + else: + self.assertEqual( + ['parameters'], [p for p in sub_attrmap.keys()], + 'When extending sub-resources only use the parameters ' + 'keyword') + self.assertParams(sub_attrmap['parameters']) def test_action_map(self): self.assertIsInstance(self.action_map, dict)