From 5a558b7d132b6d5cdda2720a1b345643e08246e2 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Sat, 20 Jul 2024 20:01:40 +0000 Subject: [PATCH] Add new "tagging" API method: create (POST) This new method allows to create multiple tags for a single resource. The tags are passed as arguments in the ``POST`` call. That solves the issue with the usage of URI reserved characters in the name of the tags. Bumped neutron-lib library to version 3.15.0, that contains [1]. [1]https://review.opendev.org/c/openstack/neutron-lib/+/924700 APIImpact add create method for service pluging "tagging" Closes-Bug: #2073836 Change-Id: I9709da13c321695f324fe8d6c1cdc03756660a03 --- neutron/common/ovn/extensions.py | 2 + neutron/conf/policies/floatingip.py | 10 ++++ neutron/conf/policies/network.py | 10 ++++ .../conf/policies/network_segment_range.py | 10 ++++ neutron/conf/policies/port.py | 13 +++++ neutron/conf/policies/router.py | 10 ++++ neutron/conf/policies/security_group.py | 10 ++++ neutron/conf/policies/segment.py | 10 ++++ neutron/conf/policies/subnet.py | 13 +++++ neutron/conf/policies/subnetpool.py | 10 ++++ neutron/conf/policies/trunk.py | 10 ++++ neutron/extensions/tag_creation.py | 20 +++++++ neutron/extensions/tagging.py | 17 ++++-- neutron/services/tag/tag_plugin.py | 21 ++++++- .../unit/conf/policies/test_floatingip.py | 45 +++++++++++++++ .../tests/unit/conf/policies/test_network.py | 38 +++++++++++++ .../policies/test_network_segment_range.py | 23 ++++++++ neutron/tests/unit/conf/policies/test_port.py | 36 ++++++++++++ .../tests/unit/conf/policies/test_router.py | 41 ++++++++++++++ .../unit/conf/policies/test_security_group.py | 43 +++++++++++++++ .../tests/unit/conf/policies/test_segment.py | 22 ++++++++ .../tests/unit/conf/policies/test_subnet.py | 55 +++++++++++++++++++ .../unit/conf/policies/test_subnetpool.py | 42 ++++++++++++++ .../tests/unit/conf/policies/test_trunk.py | 41 ++++++++++++++ ...-pluggin-post-method-c0bc38f1a8b93861.yaml | 8 +++ requirements.txt | 2 +- 26 files changed, 556 insertions(+), 6 deletions(-) create mode 100644 neutron/extensions/tag_creation.py create mode 100644 releasenotes/notes/tag-pluggin-post-method-c0bc38f1a8b93861.yaml diff --git a/neutron/common/ovn/extensions.py b/neutron/common/ovn/extensions.py index e3df5eda9a7..41bda33b25d 100644 --- a/neutron/common/ovn/extensions.py +++ b/neutron/common/ovn/extensions.py @@ -88,6 +88,7 @@ from neutron_lib.api.definitions import subnet_dns_publish_fixed_ip from neutron_lib.api.definitions import subnet_external_network from neutron_lib.api.definitions import subnet_service_types from neutron_lib.api.definitions import subnetpool_prefix_ops +from neutron_lib.api.definitions import tag_creation from neutron_lib.api.definitions import tap_mirror from neutron_lib.api.definitions import trunk from neutron_lib.api.definitions import uplink_status_propagation @@ -189,6 +190,7 @@ ML2_SUPPORTED_API_EXTENSIONS = [ subnetpool_prefix_ops.ALIAS, subnet_external_network.ALIAS, subnet_service_types.ALIAS, + tag_creation.ALIAS, trunk.ALIAS, seg_def.ALIAS, expose_port_forwarding_in_fip.ALIAS, diff --git a/neutron/conf/policies/floatingip.py b/neutron/conf/policies/floatingip.py index 100c7a14ed2..a1584874320 100644 --- a/neutron/conf/policies/floatingip.py +++ b/neutron/conf/policies/floatingip.py @@ -30,6 +30,9 @@ ACTION_PUT_TAGS = [ {'method': 'PUT', 'path': TAGS_PATH}, {'method': 'PUT', 'path': TAG_PATH}, ] +ACTION_POST_TAGS = [ + {'method': 'POST', 'path': TAGS_PATH}, +] ACTION_DELETE_TAGS = [ {'method': 'DELETE', 'path': TAGS_PATH}, {'method': 'DELETE', 'path': TAG_PATH}, @@ -73,6 +76,13 @@ rules = [ deprecated_reason=DEPRECATION_REASON, deprecated_since=versionutils.deprecated.WALLABY) ), + policy.DocumentedRuleDefault( + name='create_floatingips_tags', + check_str=base.ADMIN_OR_PROJECT_MEMBER, + description='Create the floating IP tags', + operations=ACTION_POST_TAGS, + scope_types=['project'], + ), policy.DocumentedRuleDefault( name='get_floatingip', check_str=base.ADMIN_OR_PROJECT_READER, diff --git a/neutron/conf/policies/network.py b/neutron/conf/policies/network.py index 147a03a47eb..628f7dda74e 100644 --- a/neutron/conf/policies/network.py +++ b/neutron/conf/policies/network.py @@ -46,6 +46,9 @@ ACTION_PUT_TAGS = [ {'method': 'PUT', 'path': TAGS_PATH}, {'method': 'PUT', 'path': TAG_PATH}, ] +ACTION_POST_TAGS = [ + {'method': 'POST', 'path': TAGS_PATH}, +] ACTION_DELETE_TAGS = [ {'method': 'DELETE', 'path': TAGS_PATH}, {'method': 'DELETE', 'path': TAG_PATH}, @@ -177,6 +180,13 @@ rules = [ deprecated_reason=DEPRECATED_REASON, deprecated_since=versionutils.deprecated.WALLABY) ), + policy.DocumentedRuleDefault( + name='create_networks_tags', + check_str=base.ADMIN_OR_PROJECT_MEMBER, + scope_types=['project'], + description='Create the network tags', + operations=ACTION_POST_TAGS, + ), policy.DocumentedRuleDefault( name='get_network', diff --git a/neutron/conf/policies/network_segment_range.py b/neutron/conf/policies/network_segment_range.py index 01b1c5b813e..cffcd7b2736 100644 --- a/neutron/conf/policies/network_segment_range.py +++ b/neutron/conf/policies/network_segment_range.py @@ -36,6 +36,9 @@ ACTION_PUT_TAGS = [ {'method': 'PUT', 'path': TAGS_PATH}, {'method': 'PUT', 'path': TAG_PATH}, ] +ACTION_POST_TAGS = [ + {'method': 'POST', 'path': TAGS_PATH}, +] ACTION_DELETE_TAGS = [ {'method': 'DELETE', 'path': TAGS_PATH}, {'method': 'DELETE', 'path': TAG_PATH}, @@ -60,6 +63,13 @@ rules = [ deprecated_reason=DEPRECATED_REASON, deprecated_since=versionutils.deprecated.WALLABY) ), + policy.DocumentedRuleDefault( + name='create_network_segment_ranges_tags', + check_str=base.ADMIN, + scope_types=['project'], + description='Create the network segment range tags', + operations=ACTION_POST_TAGS, + ), policy.DocumentedRuleDefault( name='get_network_segment_range', diff --git a/neutron/conf/policies/port.py b/neutron/conf/policies/port.py index 4cc47b7fb15..670c2855bf6 100644 --- a/neutron/conf/policies/port.py +++ b/neutron/conf/policies/port.py @@ -46,6 +46,9 @@ ACTION_PUT_TAGS = [ {'method': 'PUT', 'path': TAGS_PATH}, {'method': 'PUT', 'path': TAG_PATH}, ] +ACTION_POST_TAGS = [ + {'method': 'POST', 'path': TAGS_PATH}, +] ACTION_DELETE_TAGS = [ {'method': 'DELETE', 'path': TAGS_PATH}, {'method': 'DELETE', 'path': TAG_PATH}, @@ -306,6 +309,16 @@ rules = [ ), operations=ACTION_POST, ), + policy.DocumentedRuleDefault( + name='create_ports_tags', + check_str=neutron_policy.policy_or( + base.ADMIN_OR_PROJECT_MEMBER, + neutron_policy.RULE_ADVSVC + ), + scope_types=['project'], + description='Create the port tags', + operations=ACTION_POST_TAGS, + ), policy.DocumentedRuleDefault( name='get_port', diff --git a/neutron/conf/policies/router.py b/neutron/conf/policies/router.py index 566d2ea0861..159ff037f59 100644 --- a/neutron/conf/policies/router.py +++ b/neutron/conf/policies/router.py @@ -45,6 +45,9 @@ ACTION_PUT_TAGS = [ {'method': 'PUT', 'path': TAGS_PATH}, {'method': 'PUT', 'path': TAG_PATH}, ] +ACTION_POST_TAGS = [ + {'method': 'POST', 'path': TAGS_PATH}, +] ACTION_DELETE_TAGS = [ {'method': 'DELETE', 'path': TAGS_PATH}, {'method': 'DELETE', 'path': TAG_PATH}, @@ -157,6 +160,13 @@ rules = [ ' creating a router'), operations=ACTION_POST, ), + policy.DocumentedRuleDefault( + name='create_routers_tags', + check_str=base.ADMIN_OR_PROJECT_MEMBER, + scope_types=['project'], + description='Create the router tags', + operations=ACTION_POST_TAGS, + ), policy.DocumentedRuleDefault( name='get_router', diff --git a/neutron/conf/policies/security_group.py b/neutron/conf/policies/security_group.py index dc9cbaf9833..9848245e2b9 100644 --- a/neutron/conf/policies/security_group.py +++ b/neutron/conf/policies/security_group.py @@ -41,6 +41,9 @@ SG_ACTION_PUT_TAGS = [ {'method': 'PUT', 'path': SG_TAGS_PATH}, {'method': 'PUT', 'path': SG_TAG_PATH}, ] +SG_ACTION_POST_TAGS = [ + {'method': 'POST', 'path': SG_TAGS_PATH}, +] SG_ACTION_DELETE_TAGS = [ {'method': 'DELETE', 'path': SG_TAGS_PATH}, {'method': 'DELETE', 'path': SG_TAG_PATH}, @@ -92,6 +95,13 @@ rules = [ deprecated_reason=DEPRECATED_REASON, deprecated_since=versionutils.deprecated.WALLABY) ), + policy.DocumentedRuleDefault( + name='create_security_groups_tags', + check_str=base.ADMIN_OR_PROJECT_MEMBER, + scope_types=['project'], + description='Create the security group tags', + operations=SG_ACTION_POST_TAGS, + ), policy.DocumentedRuleDefault( name='get_security_group', check_str=neutron_policy.policy_or( diff --git a/neutron/conf/policies/segment.py b/neutron/conf/policies/segment.py index a15a4a232d0..287b4e2a4d9 100644 --- a/neutron/conf/policies/segment.py +++ b/neutron/conf/policies/segment.py @@ -32,6 +32,9 @@ ACTION_PUT_TAGS = [ {'method': 'PUT', 'path': TAGS_PATH}, {'method': 'PUT', 'path': TAG_PATH}, ] +ACTION_POST_TAGS = [ + {'method': 'POST', 'path': TAGS_PATH}, +] ACTION_DELETE_TAGS = [ {'method': 'DELETE', 'path': TAGS_PATH}, {'method': 'DELETE', 'path': TAG_PATH}, @@ -56,6 +59,13 @@ rules = [ deprecated_reason=DEPRECATED_REASON, deprecated_since=versionutils.deprecated.WALLABY) ), + policy.DocumentedRuleDefault( + name='create_segments_tags', + check_str=base.ADMIN, + scope_types=['project'], + description='Create the segment tags', + operations=ACTION_POST_TAGS, + ), policy.DocumentedRuleDefault( name='get_segment', check_str=base.ADMIN, diff --git a/neutron/conf/policies/subnet.py b/neutron/conf/policies/subnet.py index ce7a38ec8b4..05bbfa11b9a 100644 --- a/neutron/conf/policies/subnet.py +++ b/neutron/conf/policies/subnet.py @@ -45,6 +45,9 @@ ACTION_PUT_TAGS = [ {'method': 'PUT', 'path': TAGS_PATH}, {'method': 'PUT', 'path': TAG_PATH}, ] +ACTION_POST_TAGS = [ + {'method': 'POST', 'path': TAGS_PATH}, +] ACTION_DELETE_TAGS = [ {'method': 'DELETE', 'path': TAGS_PATH}, {'method': 'DELETE', 'path': TAG_PATH}, @@ -98,6 +101,16 @@ rules = [ deprecated_reason=DEPRECATED_REASON, deprecated_since=versionutils.deprecated.WALLABY) ), + policy.DocumentedRuleDefault( + name='create_subnets_tags', + check_str=neutron_policy.policy_or( + base.PROJECT_MEMBER, + base.ADMIN_OR_NET_OWNER_MEMBER, + ), + scope_types=['project'], + description='Create the subnet tags', + operations=ACTION_POST_TAGS, + ), policy.DocumentedRuleDefault( name='get_subnet', check_str=neutron_policy.policy_or( diff --git a/neutron/conf/policies/subnetpool.py b/neutron/conf/policies/subnetpool.py index e1ada3c0da8..a8f00d648e4 100644 --- a/neutron/conf/policies/subnetpool.py +++ b/neutron/conf/policies/subnetpool.py @@ -35,6 +35,9 @@ ACTION_PUT_TAGS = [ {'method': 'PUT', 'path': TAGS_PATH}, {'method': 'PUT', 'path': TAG_PATH}, ] +ACTION_POST_TAGS = [ + {'method': 'POST', 'path': TAGS_PATH}, +] ACTION_DELETE_TAGS = [ {'method': 'DELETE', 'path': TAGS_PATH}, {'method': 'DELETE', 'path': TAG_PATH}, @@ -100,6 +103,13 @@ rules = [ deprecated_reason=DEPRECATED_REASON, deprecated_since=versionutils.deprecated.WALLABY) ), + policy.DocumentedRuleDefault( + name='create_subnetpools_tags', + check_str=base.ADMIN_OR_PROJECT_MEMBER, + scope_types=['project'], + description='Create the subnetpool tags', + operations=ACTION_POST_TAGS + ), policy.DocumentedRuleDefault( name='get_subnetpool', check_str=neutron_policy.policy_or( diff --git a/neutron/conf/policies/trunk.py b/neutron/conf/policies/trunk.py index 83882862237..6acb42d313d 100644 --- a/neutron/conf/policies/trunk.py +++ b/neutron/conf/policies/trunk.py @@ -30,6 +30,9 @@ ACTION_PUT_TAGS = [ {'method': 'PUT', 'path': TAGS_PATH}, {'method': 'PUT', 'path': TAG_PATH}, ] +ACTION_POST_TAGS = [ + {'method': 'POST', 'path': TAGS_PATH}, +] ACTION_DELETE_TAGS = [ {'method': 'DELETE', 'path': TAGS_PATH}, {'method': 'DELETE', 'path': TAG_PATH}, @@ -57,6 +60,13 @@ rules = [ deprecated_reason=DEPRECATED_REASON, deprecated_since=versionutils.deprecated.WALLABY) ), + policy.DocumentedRuleDefault( + name='create_trunks_tags', + check_str=base.ADMIN_OR_PROJECT_MEMBER, + scope_types=['project'], + description='Create the trunk tags', + operations=ACTION_POST_TAGS + ), policy.DocumentedRuleDefault( name='get_trunk', check_str=base.ADMIN_OR_PROJECT_READER, diff --git a/neutron/extensions/tag_creation.py b/neutron/extensions/tag_creation.py new file mode 100644 index 00000000000..ad076af79ad --- /dev/null +++ b/neutron/extensions/tag_creation.py @@ -0,0 +1,20 @@ +# Copyright (c) 2024 Red Hat, Inc. +# +# 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. + +from neutron_lib.api.definitions import tag_creation +from neutron_lib.api import extensions as api_extensions + + +class Tag_creation(api_extensions.APIExtensionDescriptor): + api_definition = tag_creation diff --git a/neutron/extensions/tagging.py b/neutron/extensions/tagging.py index 432677ebe67..fe8065239be 100644 --- a/neutron/extensions/tagging.py +++ b/neutron/extensions/tagging.py @@ -24,7 +24,6 @@ from neutron_lib import exceptions from neutron_lib.plugins import directory from neutron_lib import rpc as n_rpc from neutron_lib.services import base as service_base -import webob.exc from neutron._i18n import _ from neutron.api import extensions @@ -158,10 +157,20 @@ class TaggingController(object): policy.enforce(ctx, 'get_%s_%s' % (res, TAGS), target) return self.plugin.get_tag(ctx, res, res_id, id) - def create(self, request, **kwargs): - # not supported + @_policy_init + def create(self, request, body, **kwargs): # POST /v2.0/{parent_resource}/{parent_resource_id}/tags - raise webob.exc.HTTPNotFound("not supported") + # body: {"tags": ["aaa", "bbb"]} + validate_tags(body) + ctx = request.context + res, res_id, p_res, p_res_id = self._get_parent_resource_and_id( + ctx, kwargs) + target = self._get_target(ctx, res_id, p_res, p_res_id) + policy.enforce(ctx, 'create_%s_%s' % (res, TAGS), target) + notify_tag_action(ctx, 'create.start', res, res_id, body['tags']) + result = self.plugin.create_tags(ctx, res, res_id, body) + notify_tag_action(ctx, 'create.end', res, res_id, body['tags']) + return result @_policy_init def update(self, request, id, **kwargs): diff --git a/neutron/services/tag/tag_plugin.py b/neutron/services/tag/tag_plugin.py index 3c163b5c743..17d50469b68 100644 --- a/neutron/services/tag/tag_plugin.py +++ b/neutron/services/tag/tag_plugin.py @@ -12,6 +12,7 @@ # under the License. # +from neutron_lib.api.definitions import tag_creation from neutron_lib.db import api as db_api from neutron_lib.db import model_query from neutron_lib.db import resource_extend @@ -33,7 +34,9 @@ resource_model_map = standard_attr.get_standard_attr_resource_model_map() class TagPlugin(tagging.TagPluginBase): """Implementation of the Neutron Tag Service Plugin.""" - supported_extension_aliases = ['standard-attr-tag'] + supported_extension_aliases = ['standard-attr-tag', + tag_creation.ALIAS, + ] __filter_validation_support = True @@ -81,6 +84,22 @@ class TagPlugin(tagging.TagPluginBase): if not any(tag == tag_db.tag for tag_db in res.standard_attr.tags): raise tagging.TagNotFound(tag=tag) + @log_helpers.log_method_call + @db_api.retry_if_session_inactive() + @db_api.CONTEXT_WRITER + def create_tags(self, context, resource, resource_id, body): + """Create new tags for a resource + + This method will create the non-existent tags of a resource. If + present, the tags will be omitted. This method is idempotent. + """ + res = self._get_resource(context, resource, resource_id) + new_tags = set(body['tags']) + old_tags = {tag_db.tag for tag_db in res.standard_attr.tags} + tags_added = new_tags - old_tags + self.add_tags(context, res.standard_attr_id, tags_added) + return body + @log_helpers.log_method_call @db_api.retry_if_session_inactive() def update_tags(self, context, resource, resource_id, body): diff --git a/neutron/tests/unit/conf/policies/test_floatingip.py b/neutron/tests/unit/conf/policies/test_floatingip.py index 3a4b9caeba1..5b47b71fb99 100644 --- a/neutron/tests/unit/conf/policies/test_floatingip.py +++ b/neutron/tests/unit/conf/policies/test_floatingip.py @@ -59,6 +59,18 @@ class SystemAdminTests(FloatingIPAPITestCase): self.context, "create_floatingip:floating_ip_address", self.alt_target) + def test_create_floatingips_tags(self): + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, + self.context, "create_floatingips_tags", + self.target) + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, + self.context, "create_floatingips_tags", + self.alt_target) + def test_get_floatingip(self): self.assertRaises( base_policy.InvalidScope, @@ -146,6 +158,14 @@ class AdminTests(FloatingIPAPITestCase): self.context, "create_floatingip:floating_ip_address", self.alt_target)) + def test_create_floatingips_tags(self): + self.assertTrue( + policy.enforce(self.context, "create_floatingips_tags", + self.target)) + self.assertTrue( + policy.enforce(self.context, "create_floatingips_tags", + self.alt_target)) + def test_get_floatingip(self): self.assertTrue( policy.enforce(self.context, "get_floatingip", self.target)) @@ -203,6 +223,15 @@ class ProjectManagerTests(AdminTests): self.context, "create_floatingip:floating_ip_address", self.alt_target) + def test_create_floatingips_tags(self): + self.assertTrue( + policy.enforce(self.context, "create_floatingips_tags", + self.target)) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, "create_floatingips_tags", self.alt_target) + def test_get_floatingip(self): self.assertTrue( policy.enforce(self.context, "get_floatingip", self.target)) @@ -277,6 +306,16 @@ class ProjectReaderTests(ProjectMemberTests): policy.enforce, self.context, "create_floatingip", self.alt_target) + def test_create_floatingips_tags(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, "create_floatingips_tags", self.target) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, "create_floatingips_tags", self.alt_target) + def test_update_floatingip(self): self.assertRaises( base_policy.PolicyNotAuthorized, @@ -327,6 +366,12 @@ class ServiceRoleTests(FloatingIPAPITestCase): self.context, "create_floatingip:floating_ip_address", self.target) + def test_create_floatingips_tags(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, "create_floatingips_tags", self.target) + def test_get_floatingip(self): self.assertRaises( base_policy.PolicyNotAuthorized, diff --git a/neutron/tests/unit/conf/policies/test_network.py b/neutron/tests/unit/conf/policies/test_network.py index c4fdd6dc238..716088ebdd3 100644 --- a/neutron/tests/unit/conf/policies/test_network.py +++ b/neutron/tests/unit/conf/policies/test_network.py @@ -128,6 +128,15 @@ class SystemAdminTests(NetworkAPITestCase): self.context, 'create_network:provider:segmentation_id', self.alt_target) + def test_create_networks_tags(self): + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, self.context, 'create_networks_tags', self.target) + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, self.context, 'create_networks_tags', + self.alt_target) + def test_get_network(self): self.assertRaises( base_policy.InvalidScope, @@ -415,6 +424,13 @@ class AdminTests(NetworkAPITestCase): 'create_network:provider:segmentation_id', self.alt_target)) + def test_create_networks_tags(self): + self.assertTrue( + policy.enforce(self.context, 'create_networks_tags', self.target)) + self.assertTrue( + policy.enforce(self.context, 'create_networks_tags', + self.alt_target)) + def test_get_network(self): self.assertTrue( policy.enforce(self.context, 'get_network', self.target)) @@ -655,6 +671,14 @@ class ProjectManagerTests(AdminTests): self.context, 'create_network:provider:segmentation_id', self.alt_target) + def test_create_networks_tags(self): + self.assertTrue( + policy.enforce(self.context, 'create_networks_tags', self.target)) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_networks_tags', self.alt_target) + def test_get_network(self): self.assertTrue( policy.enforce(self.context, 'get_network', self.target)) @@ -867,6 +891,15 @@ class ProjectReaderTests(ProjectMemberTests): self.context, 'create_network:port_security_enabled', self.alt_target) + def test_create_networks_tags(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, self.context, 'create_networks_tags', self.target) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, self.context, 'create_networks_tags', + self.alt_target) + def test_update_network(self): self.assertRaises( base_policy.PolicyNotAuthorized, @@ -976,6 +1009,11 @@ class ServiceRoleTests(NetworkAPITestCase): self.context, 'create_network:provider:segmentation_id', self.target) + def test_create_networks_tags(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, self.context, 'create_networks_tags', self.target) + def test_get_network(self): self.assertTrue( policy.enforce(self.context, 'get_network', self.target)) diff --git a/neutron/tests/unit/conf/policies/test_network_segment_range.py b/neutron/tests/unit/conf/policies/test_network_segment_range.py index 0e69de3f8fa..28054c8cbd1 100644 --- a/neutron/tests/unit/conf/policies/test_network_segment_range.py +++ b/neutron/tests/unit/conf/policies/test_network_segment_range.py @@ -38,6 +38,12 @@ class SystemAdminTests(NetworkSegmentRangeAPITestCase): policy.enforce, self.context, 'create_network_segment_range', self.target) + def test_create_network_segment_ranges_tags(self): + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, + self.context, 'create_network_segment_ranges_tags', self.target) + def test_get_network_segment_range(self): self.assertRaises( base_policy.InvalidScope, @@ -100,6 +106,11 @@ class AdminTests(NetworkSegmentRangeAPITestCase): policy.enforce(self.context, 'create_network_segment_range', self.target)) + def test_create_network_segment_ranges_tags(self): + self.assertTrue( + policy.enforce(self.context, + 'create_network_segment_ranges_tags', self.target)) + def test_get_network_segment_range(self): self.assertTrue( policy.enforce(self.context, @@ -143,6 +154,12 @@ class ProjectManagerTests(AdminTests): policy.enforce, self.context, 'create_network_segment_range', self.target) + def test_create_network_segment_ranges_tags(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_network_segment_ranges_tags', self.target) + def test_get_network_segment_range(self): self.assertRaises( base_policy.PolicyNotAuthorized, @@ -206,6 +223,12 @@ class ServiceRoleTests(NetworkSegmentRangeAPITestCase): policy.enforce, self.context, 'create_network_segment_range', self.target) + def test_create_network_segment_ranges_tags(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_network_segment_ranges_tags', self.target) + def test_get_network_segment_range(self): self.assertRaises( base_policy.PolicyNotAuthorized, diff --git a/neutron/tests/unit/conf/policies/test_port.py b/neutron/tests/unit/conf/policies/test_port.py index d3a5374e60f..c1a69803349 100644 --- a/neutron/tests/unit/conf/policies/test_port.py +++ b/neutron/tests/unit/conf/policies/test_port.py @@ -188,6 +188,14 @@ class SystemAdminTests(PortAPITestCase): self.context, 'create_port:allowed_address_pairs:ip_address', self.alt_target) + def test_create_ports_tags(self): + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, self.context, 'create_ports_tags', self.target) + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, self.context, 'create_ports_tags', self.alt_target) + def test_get_port(self): self.assertRaises( base_policy.InvalidScope, @@ -562,6 +570,12 @@ class AdminTests(PortAPITestCase): 'create_port:trusted', self.alt_target)) + def test_create_ports_tags(self): + self.assertTrue( + policy.enforce(self.context, 'create_ports_tags', self.target)) + self.assertTrue( + policy.enforce(self.context, 'create_ports_tags', self.alt_target)) + def test_get_port(self): self.assertTrue( policy.enforce(self.context, 'get_port', self.target)) @@ -939,6 +953,13 @@ class ProjectManagerTests(AdminTests): self.context, 'create_port:trusted', self.alt_target) + def test_create_ports_tags(self): + self.assertTrue( + policy.enforce(self.context, 'create_ports_tags', self.target)) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, self.context, 'create_ports_tags', self.alt_target) + def test_get_port(self): self.assertTrue( policy.enforce(self.context, 'get_port', self.target)) @@ -1426,6 +1447,14 @@ class ProjectReaderTests(ProjectMemberTests): policy.enforce, self.context, 'create_port:binding:vnic_type', self.alt_target) + def test_create_ports_tags(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, self.context, 'create_ports_tags', self.target) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, self.context, 'create_ports_tags', self.alt_target) + def test_update_port(self): self.assertRaises( base_policy.PolicyNotAuthorized, @@ -1538,6 +1567,13 @@ class ServiceRoleTests(PortAPITestCase): self.context, 'create_port:allowed_address_pairs:ip_address', self.target) + def test_create_ports_tags(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_ports_tags', + self.target) + def test_get_port(self): self.assertTrue( policy.enforce(self.context, 'get_port', self.target)) diff --git a/neutron/tests/unit/conf/policies/test_router.py b/neutron/tests/unit/conf/policies/test_router.py index 2fc6b6f2c20..6bb3d04f067 100644 --- a/neutron/tests/unit/conf/policies/test_router.py +++ b/neutron/tests/unit/conf/policies/test_router.py @@ -138,6 +138,16 @@ class SystemAdminTests(RouterAPITestCase): self.context, 'create_router:enable_default_route_ecmp', self.alt_target) + def test_create_routers_tags(self): + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, + self.context, 'create_routers_tags', self.target) + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, + self.context, 'create_routers_tags', self.alt_target) + def test_get_router(self): self.assertRaises( base_policy.InvalidScope, @@ -415,6 +425,13 @@ class AdminTests(RouterAPITestCase): 'create_router:external_gateway_info:external_fixed_ips', self.alt_target)) + def test_create_routers_tags(self): + self.assertTrue( + policy.enforce(self.context, 'create_routers_tags', self.target)) + self.assertTrue( + policy.enforce(self.context, 'create_routers_tags', + self.alt_target)) + def test_update_router_enable_default_route_bfd(self): self.assertTrue( policy.enforce( @@ -646,6 +663,14 @@ class ProjectManagerTests(AdminTests): 'create_router:external_gateway_info:external_fixed_ips', self.alt_target) + def test_create_routers_tags(self): + self.assertTrue( + policy.enforce(self.context, 'create_routers_tags', self.target)) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_routers_tags', self.alt_target) + def test_update_router_enable_default_route_bfd(self): self.assertRaises( base_policy.PolicyNotAuthorized, @@ -876,6 +901,16 @@ class ProjectReaderTests(ProjectMemberTests): self.context, 'create_router:external_gateway_info:network_id', self.alt_target) + def test_create_routers_tags(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_routers_tags', self.target) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_routers_tags', self.alt_target) + def test_update_router(self): self.assertRaises( base_policy.PolicyNotAuthorized, @@ -1143,6 +1178,12 @@ class ServiceRoleTests(RouterAPITestCase): 'create_router:external_gateway_info:external_fixed_ips', self.target) + def test_create_routers_tags(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_routers_tags', self.target) + def test_get_router(self): self.assertRaises( base_policy.PolicyNotAuthorized, diff --git a/neutron/tests/unit/conf/policies/test_security_group.py b/neutron/tests/unit/conf/policies/test_security_group.py index d9cdfc3576b..d077b78fa88 100644 --- a/neutron/tests/unit/conf/policies/test_security_group.py +++ b/neutron/tests/unit/conf/policies/test_security_group.py @@ -46,6 +46,16 @@ class SystemAdminSecurityGroupTests(SecurityGroupAPITestCase): policy.enforce, self.context, 'create_security_group', self.alt_target) + def test_create_security_groups_tags(self): + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, + self.context, 'create_security_groups_tags', self.target) + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, + self.context, 'create_security_groups_tags', self.alt_target) + def test_get_security_group(self): self.assertRaises( base_policy.InvalidScope, @@ -134,6 +144,14 @@ class AdminSecurityGroupTests(SecurityGroupAPITestCase): policy.enforce( self.context, 'create_security_group', self.alt_target)) + def test_create_security_groups_tags(self): + self.assertTrue( + policy.enforce(self.context, 'create_security_groups_tags', + self.target)) + self.assertTrue( + policy.enforce(self.context, 'create_security_groups_tags', + self.alt_target)) + def test_get_security_group(self): self.assertTrue( policy.enforce(self.context, 'get_security_group', self.target)) @@ -194,6 +212,15 @@ class ProjectManagerSecurityGroupTests(AdminSecurityGroupTests): policy.enforce, self.context, 'create_security_group', self.alt_target) + def test_create_security_groups_tags(self): + self.assertTrue( + policy.enforce(self.context, 'create_security_groups_tags', + self.target)) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_security_groups_tags', self.alt_target) + def test_get_security_group(self): self.assertTrue( policy.enforce(self.context, 'get_security_group', self.target)) @@ -267,6 +294,16 @@ class ProjectReaderSecurityGroupTests(ProjectMemberSecurityGroupTests): policy.enforce, self.context, 'create_security_group', self.alt_target) + def test_create_security_groups_tags(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_security_groups_tags', self.target) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_security_groups_tags', self.alt_target) + def test_update_security_group(self): self.assertRaises( base_policy.PolicyNotAuthorized, @@ -320,6 +357,12 @@ class ServiceRoleSecurityGroupTests(SecurityGroupAPITestCase): policy.enforce, self.context, 'create_security_group', self.target) + def test_create_security_groups_tags(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_security_groups_tags', self.target) + def test_get_security_group(self): self.assertRaises( base_policy.PolicyNotAuthorized, diff --git a/neutron/tests/unit/conf/policies/test_segment.py b/neutron/tests/unit/conf/policies/test_segment.py index 98487e843a7..b01c4629638 100644 --- a/neutron/tests/unit/conf/policies/test_segment.py +++ b/neutron/tests/unit/conf/policies/test_segment.py @@ -38,6 +38,12 @@ class SystemAdminTests(SegmentAPITestCase): policy.enforce, self.context, 'create_segment', self.target) + def test_create_segments_tags(self): + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, + self.context, 'create_segments_tags', self.target) + def test_get_segment(self): self.assertRaises( base_policy.InvalidScope, @@ -99,6 +105,10 @@ class AdminTests(SegmentAPITestCase): self.assertTrue( policy.enforce(self.context, 'create_segment', self.target)) + def test_create_segments_tags(self): + self.assertTrue( + policy.enforce(self.context, 'create_segments_tags', self.target)) + def test_get_segment(self): self.assertTrue( policy.enforce(self.context, 'get_segment', self.target)) @@ -136,6 +146,12 @@ class ProjectManagerTests(AdminTests): policy.enforce, self.context, 'create_segment', self.target) + def test_create_segments_tags(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_segments_tags', self.target) + def test_get_segment(self): self.assertRaises( base_policy.PolicyNotAuthorized, @@ -199,6 +215,12 @@ class ServiceRoleTests(SegmentAPITestCase): policy.enforce, self.context, 'create_segment', self.target) + def test_create_segments_tags(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_segments_tags', self.target) + def test_get_segment(self): self.assertRaises( base_policy.PolicyNotAuthorized, diff --git a/neutron/tests/unit/conf/policies/test_subnet.py b/neutron/tests/unit/conf/policies/test_subnet.py index 609492f0adb..365176bc84e 100644 --- a/neutron/tests/unit/conf/policies/test_subnet.py +++ b/neutron/tests/unit/conf/policies/test_subnet.py @@ -140,6 +140,20 @@ class SystemAdminTests(SubnetAPITestCase): policy.enforce, self.context, 'create_subnet:service_types', self.alt_target) + def test_create_subnets_tags(self): + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, + self.context, 'create_subnets_tags', self.target) + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, + self.context, 'create_subnets_tags', self.target_net_alt_target) + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, + self.context, 'create_subnets_tags', self.alt_target) + def test_get_subnet(self): self.assertRaises( base_policy.InvalidScope, @@ -330,6 +344,16 @@ class AdminTests(SubnetAPITestCase): policy.enforce( self.context, 'create_subnet:service_types', self.alt_target)) + def test_create_subnets_tags(self): + self.assertTrue( + policy.enforce(self.context, 'create_subnets_tags', self.target)) + self.assertTrue( + policy.enforce(self.context, 'create_subnets_tags', + self.target_net_alt_target)) + self.assertTrue( + policy.enforce(self.context, 'create_subnets_tags', + self.alt_target)) + def test_get_subnet(self): self.assertTrue( policy.enforce(self.context, 'get_subnet', self.target)) @@ -475,6 +499,17 @@ class ProjectManagerTests(AdminTests): policy.enforce, self.context, 'create_subnet:service_types', self.alt_target) + def test_create_subnets_tags(self): + self.assertTrue( + policy.enforce(self.context, 'create_subnets_tags', self.target)) + self.assertTrue( + policy.enforce(self.context, 'create_subnets_tags', + self.target_net_alt_target)) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_subnets_tags', self.alt_target) + def test_get_subnet(self): self.assertTrue( policy.enforce(self.context, 'get_subnet', self.target)) @@ -619,6 +654,20 @@ class ProjectReaderTests(ProjectMemberTests): policy.enforce, self.context, 'create_subnet', self.alt_target) + def test_create_subnets_tags(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_subnets_tags', self.target) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_subnets_tags', self.target_net_alt_target) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_subnets_tags', self.alt_target) + def test_update_subnet(self): self.assertRaises( base_policy.PolicyNotAuthorized, @@ -700,6 +749,12 @@ class ServiceRoleTests(SubnetAPITestCase): policy.enforce, self.context, 'create_subnet:service_types', self.target) + def test_create_subnets_tags(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_subnets_tags', self.target) + def test_get_subnet(self): self.assertRaises( base_policy.PolicyNotAuthorized, diff --git a/neutron/tests/unit/conf/policies/test_subnetpool.py b/neutron/tests/unit/conf/policies/test_subnetpool.py index 6e369ecda38..f47ff093234 100644 --- a/neutron/tests/unit/conf/policies/test_subnetpool.py +++ b/neutron/tests/unit/conf/policies/test_subnetpool.py @@ -63,6 +63,16 @@ class SystemAdminTests(SubnetpoolAPITestCase): policy.enforce, self.context, 'create_subnetpool:is_default', self.alt_target) + def test_create_subnetpools_tags(self): + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, + self.context, 'create_subnetpools_tags', self.target) + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, + self.context, 'create_subnetpools_tags', self.alt_target) + def test_get_subnetpool(self): self.assertRaises( base_policy.InvalidScope, @@ -206,6 +216,13 @@ class AdminTests(SubnetpoolAPITestCase): policy.enforce( self.context, 'create_subnetpool:default', self.alt_target)) + def test_create_subnetpools_tags(self): + self.assertTrue( + policy.enforce(self.context, 'create_subnetpools_tags', + self.target)) + self.assertTrue(policy.enforce(self.context, 'create_subnetpools_tags', + self.alt_target)) + def test_get_subnetpool(self): self.assertTrue( policy.enforce(self.context, 'get_subnetpool', self.target)) @@ -310,6 +327,15 @@ class ProjectManagerTests(AdminTests): policy.enforce, self.context, 'create_subnetpool:is_default', self.alt_target) + def test_create_subnetpools_tags(self): + self.assertTrue( + policy.enforce(self.context, 'create_subnetpools_tags', + self.target)) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_subnetpools_tags', self.alt_target) + def test_get_subnetpool(self): self.assertTrue( policy.enforce(self.context, 'get_subnetpool', self.target)) @@ -419,6 +445,16 @@ class ProjectReaderTests(ProjectMemberTests): policy.enforce, self.context, 'create_subnetpool', self.alt_target) + def test_create_subnetpools_tags(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_subnetpools_tags', self.target) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_subnetpools_tags', self.alt_target) + def test_update_subnetpool(self): self.assertRaises( base_policy.PolicyNotAuthorized, @@ -502,6 +538,12 @@ class ServiceRoleTests(SubnetpoolAPITestCase): policy.enforce, self.context, 'create_subnetpool', self.target) + def test_create_subnetpools_tags(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_subnetpools_tags', self.target) + def test_create_subnetpool_shared(self): self.assertRaises( base_policy.PolicyNotAuthorized, diff --git a/neutron/tests/unit/conf/policies/test_trunk.py b/neutron/tests/unit/conf/policies/test_trunk.py index 121cc9abb57..1bf178c9093 100644 --- a/neutron/tests/unit/conf/policies/test_trunk.py +++ b/neutron/tests/unit/conf/policies/test_trunk.py @@ -43,6 +43,16 @@ class SystemAdminTests(TrunkAPITestCase): policy.enforce, self.context, 'create_trunk', self.alt_target) + def test_create_trunks_tags(self): + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, + self.context, 'create_trunks_tags', self.target) + self.assertRaises( + base_policy.InvalidScope, + policy.enforce, + self.context, 'create_trunks_tags', self.alt_target) + def test_get_trunk(self): self.assertRaises( base_policy.InvalidScope, @@ -160,6 +170,13 @@ class AdminTests(TrunkAPITestCase): self.assertTrue( policy.enforce(self.context, 'create_trunk', self.alt_target)) + def test_create_trunks_tags(self): + self.assertTrue( + policy.enforce(self.context, 'create_trunks_tags', self.target)) + self.assertTrue( + policy.enforce(self.context, 'create_trunks_tags', + self.alt_target)) + def test_get_trunk(self): self.assertTrue( policy.enforce(self.context, 'get_trunk', self.target)) @@ -211,6 +228,14 @@ class ProjectManagerTests(AdminTests): policy.enforce, self.context, 'create_trunk', self.alt_target) + def test_create_trunks_tags(self): + self.assertTrue( + policy.enforce(self.context, 'create_trunks_tags', self.target)) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_trunks_tags', self.alt_target) + def test_get_trunk(self): self.assertTrue( policy.enforce(self.context, 'get_trunk', self.target)) @@ -283,6 +308,16 @@ class ProjectReaderTests(ProjectMemberTests): policy.enforce, self.context, 'create_trunk', self.alt_target) + def test_create_trunks_tags(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_trunks_tags', self.target) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_trunks_tags', self.alt_target) + def test_update_trunk(self): self.assertRaises( base_policy.PolicyNotAuthorized, @@ -336,6 +371,12 @@ class ServiceRoleTests(TrunkAPITestCase): policy.enforce, self.context, 'create_trunk', self.target) + def test_create_trunks_tags(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_trunks_tags', self.target) + def test_get_trunk(self): self.assertRaises( base_policy.PolicyNotAuthorized, diff --git a/releasenotes/notes/tag-pluggin-post-method-c0bc38f1a8b93861.yaml b/releasenotes/notes/tag-pluggin-post-method-c0bc38f1a8b93861.yaml new file mode 100644 index 00000000000..69ccafb172f --- /dev/null +++ b/releasenotes/notes/tag-pluggin-post-method-c0bc38f1a8b93861.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Added a new shim extension ``tag-creation``. This extension informs about + a new API definition in the tag plugin that allows to send ``POST`` + requests. Now it is possible to create new tags, passing the value of the + tags as an argument of the call. That resolves formatting issues when + using URI reserved characters. diff --git a/requirements.txt b/requirements.txt index 77921711771..96991315f05 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,7 @@ requests>=2.18.0 # Apache-2.0 Jinja2>=2.10 # BSD License (3 clause) keystonemiddleware>=5.1.0 # Apache-2.0 netaddr>=0.7.18 # BSD -neutron-lib>=3.14.0 # Apache-2.0 +neutron-lib>=3.15.0 # Apache-2.0 python-neutronclient>=7.8.0 # Apache-2.0 tenacity>=6.0.0 # Apache-2.0 SQLAlchemy>=1.4.23 # MIT