From 1177942f0eb4724a3585b77bbcb6e2b9b27b0a40 Mon Sep 17 00:00:00 2001 From: Felipe Monteiro Date: Thu, 18 May 2017 06:10:26 +0100 Subject: [PATCH] Add network tags client This patch creates the network v2.0 tags client. Unlike most network clients, this client cannot use update_resource for create_tag (which does self.put) because create_tag returns 201 but upstate_resource asserts that 200 was returned. Similarly, check_tag_existence cannot use "check_resource" in ``BaseNetworkClient`` because it doesn't exist. This patch also adds unit tests for the new ``tags_client`` and API tests for the network tag extension. To make this patch easier to review, tests for the network tag-ext extension have not been added. The difference between tag and tag-ext is that tag only supports the network resource and the tag-ext supports other resources like subnets, routers, etc. [0]. [0] https://developer.openstack.org/api-ref/networking/v2/#tag-extension-tags Change-Id: Icfff444ee7638a3220d228330f9162044673636c --- .../network-tag-client-f4614029af7927f0.yaml | 8 ++ tempest/api/network/base.py | 1 + tempest/api/network/test_tags.py | 90 +++++++++++++ tempest/clients.py | 1 + tempest/lib/services/network/__init__.py | 9 +- tempest/lib/services/network/tags_client.py | 88 +++++++++++++ .../lib/services/network/test_tags_client.py | 123 ++++++++++++++++++ 7 files changed, 316 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/network-tag-client-f4614029af7927f0.yaml create mode 100644 tempest/api/network/test_tags.py create mode 100644 tempest/lib/services/network/tags_client.py create mode 100644 tempest/tests/lib/services/network/test_tags_client.py diff --git a/releasenotes/notes/network-tag-client-f4614029af7927f0.yaml b/releasenotes/notes/network-tag-client-f4614029af7927f0.yaml new file mode 100644 index 0000000000..9af57b117b --- /dev/null +++ b/releasenotes/notes/network-tag-client-f4614029af7927f0.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Define v2.0 ``tags_client`` for the network service as a library + interface, allowing other projects to use this module as a stable + library without maintenance changes. + + * tags_client(v2.0) diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py index 877549575f..6bec0d7e48 100644 --- a/tempest/api/network/base.py +++ b/tempest/api/network/base.py @@ -83,6 +83,7 @@ class BaseNetworkTest(tempest.test.BaseTestCase): cls.os_primary.security_group_rules_client) cls.network_versions_client = cls.os_primary.network_versions_client cls.service_providers_client = cls.os_primary.service_providers_client + cls.tags_client = cls.os_primary.tags_client @classmethod def resource_setup(cls): diff --git a/tempest/api/network/test_tags.py b/tempest/api/network/test_tags.py new file mode 100644 index 0000000000..1f3a7c4ce2 --- /dev/null +++ b/tempest/api/network/test_tags.py @@ -0,0 +1,90 @@ +# Copyright 2017 AT&T Corporation. +# 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. + +from tempest.api.network import base +from tempest.lib.common.utils import data_utils +from tempest.lib import decorators +from tempest.lib import exceptions as lib_exc +from tempest import test + + +class TagsTest(base.BaseNetworkTest): + """Tests the following operations in the tags API: + + Update all tags. + Delete all tags. + Check tag existence. + Create a tag. + List tags. + Remove a tag. + + v2.0 of the Neutron API is assumed. The tag extension allows users to set + tags on their networks. The extension supports networks only. + """ + + @classmethod + def skip_checks(cls): + super(TagsTest, cls).skip_checks() + if not test.is_extension_enabled('tag', 'network'): + msg = "tag extension not enabled." + raise cls.skipException(msg) + + @classmethod + def resource_setup(cls): + super(TagsTest, cls).resource_setup() + cls.network = cls.create_network() + + @decorators.idempotent_id('ee76bfaf-ac94-4d74-9ecc-4bbd4c583cb1') + def test_create_list_show_update_delete_tags(self): + # Validate that creating a tag on a network resource works. + tag_name = data_utils.rand_name(self.__class__.__name__ + '-Tag') + self.tags_client.create_tag('networks', self.network['id'], tag_name) + self.addCleanup(self.tags_client.delete_all_tags, 'networks', + self.network['id']) + self.tags_client.check_tag_existence('networks', self.network['id'], + tag_name) + + # Validate that listing tags on a network resource works. + retrieved_tags = self.tags_client.list_tags( + 'networks', self.network['id'])['tags'] + self.assertEqual([tag_name], retrieved_tags) + + # Generate 3 new tag names. + replace_tags = [data_utils.rand_name( + self.__class__.__name__ + '-Tag') for _ in range(3)] + + # Replace the current tag with the 3 new tags and validate that the + # network resource has the 3 new tags. + updated_tags = self.tags_client.update_all_tags( + 'networks', self.network['id'], replace_tags)['tags'] + self.assertEqual(3, len(updated_tags)) + self.assertEqual(set(replace_tags), set(updated_tags)) + + # Delete the first tag and check that it has been removed. + self.tags_client.delete_tag( + 'networks', self.network['id'], replace_tags[0]) + self.assertRaises(lib_exc.NotFound, + self.tags_client.check_tag_existence, 'networks', + self.network['id'], replace_tags[0]) + for i in range(1, 3): + self.tags_client.check_tag_existence( + 'networks', self.network['id'], replace_tags[i]) + + # Delete all the remaining tags and check that they have been removed. + self.tags_client.delete_all_tags('networks', self.network['id']) + for i in range(1, 3): + self.assertRaises(lib_exc.NotFound, + self.tags_client.check_tag_existence, 'networks', + self.network['id'], replace_tags[i]) diff --git a/tempest/clients.py b/tempest/clients.py index 7b6cc19ec9..a941301837 100644 --- a/tempest/clients.py +++ b/tempest/clients.py @@ -79,6 +79,7 @@ class Manager(clients.ServiceClients): self.security_groups_client = self.network.SecurityGroupsClient() self.network_versions_client = self.network.NetworkVersionsClient() self.service_providers_client = self.network.ServiceProvidersClient() + self.tags_client = self.network.TagsClient() def _set_image_clients(self): if CONF.service_available.glance: diff --git a/tempest/lib/services/network/__init__.py b/tempest/lib/services/network/__init__.py index 19e5463867..419e593935 100644 --- a/tempest/lib/services/network/__init__.py +++ b/tempest/lib/services/network/__init__.py @@ -31,11 +31,12 @@ from tempest.lib.services.network.service_providers_client import \ ServiceProvidersClient from tempest.lib.services.network.subnetpools_client import SubnetpoolsClient from tempest.lib.services.network.subnets_client import SubnetsClient +from tempest.lib.services.network.tags_client import TagsClient from tempest.lib.services.network.versions_client import NetworkVersionsClient __all__ = ['AgentsClient', 'ExtensionsClient', 'FloatingIPsClient', 'MeteringLabelRulesClient', 'MeteringLabelsClient', - 'NetworksClient', 'PortsClient', 'QuotasClient', 'RoutersClient', - 'SecurityGroupRulesClient', 'SecurityGroupsClient', - 'ServiceProvidersClient', 'SubnetpoolsClient', 'SubnetsClient', - 'NetworkVersionsClient'] + 'NetworksClient', 'NetworkVersionsClient', 'PortsClient', + 'QuotasClient', 'RoutersClient', 'SecurityGroupRulesClient', + 'SecurityGroupsClient', 'ServiceProvidersClient', + 'SubnetpoolsClient', 'SubnetsClient', 'TagsClient'] diff --git a/tempest/lib/services/network/tags_client.py b/tempest/lib/services/network/tags_client.py new file mode 100644 index 0000000000..20c2c11974 --- /dev/null +++ b/tempest/lib/services/network/tags_client.py @@ -0,0 +1,88 @@ +# Copyright 2017 AT&T Corporation. +# 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. + +from oslo_serialization import jsonutils as json + +from tempest.lib.common import rest_client +from tempest.lib.services.network import base + + +class TagsClient(base.BaseNetworkClient): + + def create_tag(self, resource_type, resource_id, tag): + """Adds a tag on the resource. + + For more information, please refer to the official API reference: + http://developer.openstack.org/api-ref/networking/v2/index.html#add-a-tag + """ + # NOTE(felipemonteiro): Cannot use ``update_resource`` method because + # this API requires self.put but returns 201 instead of 200 expected + # by ``update_resource``. + uri = '%s/%s/%s/tags/%s' % ( + self.uri_prefix, resource_type, resource_id, tag) + resp, _ = self.put(uri, json.dumps({})) + self.expected_success(201, resp.status) + return rest_client.ResponseBody(resp) + + def check_tag_existence(self, resource_type, resource_id, tag): + """Confirm that a given tag is set on the resource. + + For more information, please refer to the official API reference: + http://developer.openstack.org/api-ref/networking/v2/index.html#confirm-a-tag + """ + # TODO(felipemonteiro): Use the "check_resource" method in + # ``BaseNetworkClient`` once it has been implemented. + uri = '%s/%s/%s/tags/%s' % ( + self.uri_prefix, resource_type, resource_id, tag) + resp, _ = self.get(uri) + self.expected_success(204, resp.status) + return rest_client.ResponseBody(resp) + + def update_all_tags(self, resource_type, resource_id, tags): + """Replace all tags on the resource. + + For more information, please refer to the official API reference: + http://developer.openstack.org/api-ref/networking/v2/index.html#replace-all-tags + """ + uri = '/%s/%s/tags' % (resource_type, resource_id) + put_body = {"tags": tags} + return self.update_resource(uri, put_body) + + def delete_tag(self, resource_type, resource_id, tag): + """Removes a tag on the resource. + + For more information, please refer to the official API reference: + http://developer.openstack.org/api-ref/networking/v2/index.html#remove-a-tag + """ + uri = '/%s/%s/tags/%s' % (resource_type, resource_id, tag) + return self.delete_resource(uri) + + def delete_all_tags(self, resource_type, resource_id): + """Removes all tags on the resource. + + For more information, please refer to the official API reference: + http://developer.openstack.org/api-ref/networking/v2/index.html#remove-all-tags + """ + uri = '/%s/%s/tags' % (resource_type, resource_id) + return self.delete_resource(uri) + + def list_tags(self, resource_type, resource_id): + """Retrieves the tags for a resource. + + For more information, please refer to the official API reference: + http://developer.openstack.org/api-ref/networking/v2/index.html#obtain-tag-list + """ + uri = '/%s/%s/tags' % (resource_type, resource_id) + return self.list_resources(uri) diff --git a/tempest/tests/lib/services/network/test_tags_client.py b/tempest/tests/lib/services/network/test_tags_client.py new file mode 100644 index 0000000000..dbe50a0458 --- /dev/null +++ b/tempest/tests/lib/services/network/test_tags_client.py @@ -0,0 +1,123 @@ +# Copyright 2017 AT&T Corporation. +# 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. + + +from tempest.lib.services.network import tags_client +from tempest.tests.lib import fake_auth_provider +from tempest.tests.lib.services import base + + +class TestTagsClient(base.BaseServiceTest): + + FAKE_TAGS = { + "tags": [ + "red", + "blue" + ] + } + + FAKE_RESOURCE_TYPE = 'network' + + FAKE_RESOURCE_ID = '7a8f904b-c1ed-4446-a87d-60440c02934b' + + def setUp(self): + super(TestTagsClient, self).setUp() + fake_auth = fake_auth_provider.FakeAuthProvider() + self.client = tags_client.TagsClient( + fake_auth, 'network', 'regionOne') + + def _test_update_all_tags(self, bytes_body=False): + self.check_service_client_function( + self.client.update_all_tags, + 'tempest.lib.common.rest_client.RestClient.put', + self.FAKE_TAGS, + bytes_body, + resource_type=self.FAKE_RESOURCE_TYPE, + resource_id=self.FAKE_RESOURCE_ID, + tags=self.FAKE_TAGS) + + def _test_check_tag_existence(self, bytes_body=False): + self.check_service_client_function( + self.client.check_tag_existence, + 'tempest.lib.common.rest_client.RestClient.get', + {}, + bytes_body, + resource_type=self.FAKE_RESOURCE_TYPE, + resource_id=self.FAKE_RESOURCE_ID, + tag=self.FAKE_TAGS['tags'][0], + status=204) + + def _test_create_tag(self, bytes_body=False): + self.check_service_client_function( + self.client.create_tag, + 'tempest.lib.common.rest_client.RestClient.put', + {}, + bytes_body, + resource_type=self.FAKE_RESOURCE_TYPE, + resource_id=self.FAKE_RESOURCE_ID, + tag=self.FAKE_TAGS['tags'][0], + status=201) + + def _test_list_tags(self, bytes_body=False): + self.check_service_client_function( + self.client.list_tags, + 'tempest.lib.common.rest_client.RestClient.get', + self.FAKE_TAGS, + bytes_body, + resource_type=self.FAKE_RESOURCE_TYPE, + resource_id=self.FAKE_RESOURCE_ID) + + def test_update_all_tags_with_str_body(self): + self._test_update_all_tags() + + def test_update_all_tags_with_bytes_body(self): + self._test_update_all_tags(bytes_body=True) + + def test_delete_all_tags(self): + self.check_service_client_function( + self.client.delete_all_tags, + 'tempest.lib.common.rest_client.RestClient.delete', + {}, + resource_type=self.FAKE_RESOURCE_TYPE, + resource_id=self.FAKE_RESOURCE_ID, + status=204) + + def test_check_tag_existence_with_str_body(self): + self._test_check_tag_existence() + + def test_check_tag_existence_with_bytes_body(self): + self._test_check_tag_existence(bytes_body=True) + + def test_create_tag_with_str_body(self): + self._test_create_tag() + + def test_create_tag_with_bytes_body(self): + self._test_create_tag(bytes_body=True) + + def test_list_tags_with_str_body(self): + self._test_list_tags() + + def test_list_tags_with_bytes_body(self): + self._test_list_tags(bytes_body=True) + + def test_delete_tag(self): + self.check_service_client_function( + self.client.delete_tag, + 'tempest.lib.common.rest_client.RestClient.delete', + {}, + resource_type=self.FAKE_RESOURCE_TYPE, + resource_id=self.FAKE_RESOURCE_ID, + tag=self.FAKE_TAGS['tags'][0], + status=204)