From a3b2d8e1b2320c0418ef5cd95d11a018b92cd2a1 Mon Sep 17 00:00:00 2001 From: Felipe Monteiro Date: Thu, 15 Mar 2018 04:47:52 +0000 Subject: [PATCH] Add identity v3 project tags client This PS adds ``project_tags_client`` to the identity v3 library. This feature enables the possibility of invoking the following API actions: * update_project_tag * list_project_tags * update_all_project_tags * check_project_tag_existence * delete_project_tag * delete_all_project_tags Change-Id: Iad6b3a88639bb4a0dc3aea5af2ba0162dfa19f96 Depends-On: Iec6b34c10ea1bd7103720c773b48ce130643115d --- ...-project-tags-client-36683c6a8644e54b.yaml | 12 ++ .../identity/admin/v3/test_project_tags.py | 66 +++++++++++ tempest/api/identity/base.py | 1 + tempest/clients.py | 2 + tempest/lib/services/identity/v3/__init__.py | 8 +- .../identity/v3/project_tags_client.py | 80 ++++++++++++++ .../identity/v3/test_project_tags_client.py | 104 ++++++++++++++++++ 7 files changed, 270 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/identity-v3-project-tags-client-36683c6a8644e54b.yaml create mode 100644 tempest/api/identity/admin/v3/test_project_tags.py create mode 100644 tempest/lib/services/identity/v3/project_tags_client.py create mode 100644 tempest/tests/lib/services/identity/v3/test_project_tags_client.py diff --git a/releasenotes/notes/identity-v3-project-tags-client-36683c6a8644e54b.yaml b/releasenotes/notes/identity-v3-project-tags-client-36683c6a8644e54b.yaml new file mode 100644 index 0000000000..dfbcc7d9bf --- /dev/null +++ b/releasenotes/notes/identity-v3-project-tags-client-36683c6a8644e54b.yaml @@ -0,0 +1,12 @@ +--- +features: + - | + Add ``project_tags_client`` to the identity v3 library. This feature + enables the possibility of invoking the following API actions: + + * update_project_tag + * list_project_tags + * update_all_project_tags + * check_project_tag_existence + * delete_project_tag + * delete_all_project_tags diff --git a/tempest/api/identity/admin/v3/test_project_tags.py b/tempest/api/identity/admin/v3/test_project_tags.py new file mode 100644 index 0000000000..d05173b083 --- /dev/null +++ b/tempest/api/identity/admin/v3/test_project_tags.py @@ -0,0 +1,66 @@ +# Copyright 2018 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. + +import testtools + +from tempest.api.identity import base +from tempest import config +from tempest.lib.common.utils import data_utils +from tempest.lib import decorators +from tempest.lib import exceptions as lib_exc + +CONF = config.CONF + + +class IdentityV3ProjectTagsTest(base.BaseIdentityV3AdminTest): + + @decorators.idempotent_id('7c123aac-999d-416a-a0fb-84b915ab10de') + @testtools.skipUnless(CONF.identity_feature_enabled.project_tags, + 'Project tags not available.') + def test_list_update_delete_project_tags(self): + project = self.setup_test_project() + + # Create a tag for testing. + tag = data_utils.rand_name('tag') + # NOTE(felipemonteiro): The response body for create is empty. + self.project_tags_client.update_project_tag(project['id'], tag) + + # Verify that the tag was created. + self.project_tags_client.check_project_tag_existence( + project['id'], tag) + + # Verify that updating the project tags works. + tags_to_update = [data_utils.rand_name('tag') for _ in range(3)] + updated_tags = self.project_tags_client.update_all_project_tags( + project['id'], tags_to_update)['tags'] + self.assertEqual(sorted(tags_to_update), sorted(updated_tags)) + + # Verify that listing project tags works. + retrieved_tags = self.project_tags_client.list_project_tags( + project['id'])['tags'] + self.assertEqual(sorted(tags_to_update), sorted(retrieved_tags)) + + # Verify that deleting a project tag works. + self.project_tags_client.delete_project_tag( + project['id'], tags_to_update[0]) + self.assertRaises(lib_exc.NotFound, + self.project_tags_client.check_project_tag_existence, + project['id'], tags_to_update[0]) + + # Verify that deleting all project tags works. + self.project_tags_client.delete_all_project_tags(project['id']) + retrieved_tags = self.project_tags_client.list_project_tags( + project['id'])['tags'] + self.assertEmpty(retrieved_tags) diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py index 9edccbb45f..6edb8f3b18 100644 --- a/tempest/api/identity/base.py +++ b/tempest/api/identity/base.py @@ -228,6 +228,7 @@ class BaseIdentityV3AdminTest(BaseIdentityV3Test): cls.domain_config_client = cls.os_admin.domain_config_client cls.endpoint_filter_client = cls.os_admin.endpoint_filter_client cls.endpoint_groups_client = cls.os_admin.endpoint_groups_client + cls.project_tags_client = cls.os_admin.project_tags_client if CONF.identity.admin_domain_scope: # NOTE(andreaf) When keystone policy requires it, the identity diff --git a/tempest/clients.py b/tempest/clients.py index b06eafbe08..d75a7122fa 100644 --- a/tempest/clients.py +++ b/tempest/clients.py @@ -197,6 +197,8 @@ class Manager(clients.ServiceClients): self.endpoint_groups_client = self.identity_v3.EndPointGroupsClient( **params_v3) self.catalog_client = self.identity_v3.CatalogClient(**params_v3) + self.project_tags_client = self.identity_v3.ProjectTagsClient( + **params_v3) # Token clients do not use the catalog. They only need default_params. # They read auth_url, so they should only be set if the corresponding diff --git a/tempest/lib/services/identity/v3/__init__.py b/tempest/lib/services/identity/v3/__init__.py index a539d08c0d..f302455c83 100644 --- a/tempest/lib/services/identity/v3/__init__.py +++ b/tempest/lib/services/identity/v3/__init__.py @@ -33,6 +33,8 @@ from tempest.lib.services.identity.v3.oauth_consumers_client import \ from tempest.lib.services.identity.v3.oauth_token_client import \ OAUTHTokenClient from tempest.lib.services.identity.v3.policies_client import PoliciesClient +from tempest.lib.services.identity.v3.project_tags_client import \ + ProjectTagsClient from tempest.lib.services.identity.v3.projects_client import ProjectsClient from tempest.lib.services.identity.v3.regions_client import RegionsClient from tempest.lib.services.identity.v3.role_assignments_client import \ @@ -49,6 +51,6 @@ __all__ = ['CatalogClient', 'CredentialsClient', 'DomainsClient', 'EndPointsClient', 'EndPointsFilterClient', 'GroupsClient', 'IdentityClient', 'InheritedRolesClient', 'OAUTHConsumerClient', 'OAUTHTokenClient', 'PoliciesClient', 'ProjectsClient', - 'RegionsClient', 'RoleAssignmentsClient', 'RolesClient', - 'ServicesClient', 'V3TokenClient', 'TrustsClient', 'UsersClient', - 'VersionsClient'] + 'ProjectTagsClient', 'RegionsClient', 'RoleAssignmentsClient', + 'RolesClient', 'ServicesClient', 'V3TokenClient', 'TrustsClient', + 'UsersClient', 'VersionsClient'] diff --git a/tempest/lib/services/identity/v3/project_tags_client.py b/tempest/lib/services/identity/v3/project_tags_client.py new file mode 100644 index 0000000000..dd1a2a50ba --- /dev/null +++ b/tempest/lib/services/identity/v3/project_tags_client.py @@ -0,0 +1,80 @@ +# Copyright 2018 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 + + +class ProjectTagsClient(rest_client.RestClient): + api_version = "v3" + + def update_project_tag(self, project_id, tag): + """Updates the specified tag and adds it to the project's list of tags. + + """ + url = 'projects/%s/tags/%s' % (project_id, tag) + resp, body = self.put(url, '{}') + # NOTE(felipemonteiro): This API endpoint returns 201 AND an empty + # response body, which is consistent with the spec: + # https://specs.openstack.org/openstack/api-wg/guidelines/tags.html#addressing-individual-tags + self.expected_success(201, resp.status) + return rest_client.ResponseBody(resp, body) + + def list_project_tags(self, project_id): + """List tags for a project.""" + url = "projects/%s/tags" % project_id + resp, body = self.get(url) + self.expected_success(200, resp.status) + body = json.loads(body) + return rest_client.ResponseBody(resp, body) + + def update_all_project_tags(self, project_id, tags, **kwargs): + """Updates all the tags for a project. + + Any existing tags not specified will be deleted. + + For a full list of available parameters, please refer to the official + API reference: + https://developer.openstack.org/api-ref/identity/v3/#modify-tag-list-for-a-project + """ + body = {'tags': tags} + if kwargs: + body.update(kwargs) + put_body = json.dumps(body) + resp, body = self.put('projects/%s/tags' % project_id, put_body) + self.expected_success(200, resp.status) + body = json.loads(body) + return rest_client.ResponseBody(resp, body) + + def check_project_tag_existence(self, project_id, tag): + """Check if a project contains a tag.""" + url = 'projects/%s/tags/%s' % (project_id, tag) + resp, body = self.get(url) + self.expected_success(204, resp.status) + return rest_client.ResponseBody(resp, body) + + def delete_project_tag(self, project_id, tag): + """Delete a project tag.""" + url = 'projects/%s/tags/%s' % (project_id, tag) + resp, body = self.delete(url) + self.expected_success(204, resp.status) + return rest_client.ResponseBody(resp, body) + + def delete_all_project_tags(self, project_id): + """Delete all tags from a project.""" + resp, body = self.delete('projects/%s/tags' % project_id) + self.expected_success(204, resp.status) + return rest_client.ResponseBody(resp, body) diff --git a/tempest/tests/lib/services/identity/v3/test_project_tags_client.py b/tempest/tests/lib/services/identity/v3/test_project_tags_client.py new file mode 100644 index 0000000000..2d65a296af --- /dev/null +++ b/tempest/tests/lib/services/identity/v3/test_project_tags_client.py @@ -0,0 +1,104 @@ +# Copyright 2018 AT&T Corporation. +# +# 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.identity.v3 import project_tags_client +from tempest.tests.lib import fake_auth_provider +from tempest.tests.lib.services import base + + +class TestProjectTagsClient(base.BaseServiceTest): + + FAKE_PROJECT_ID = "0c4e939acacf4376bdcd1129f1a054ad" + + FAKE_PROJECT_TAG = "foo" + + FAKE_PROJECT_TAGS = ["foo", "bar"] + + def setUp(self): + super(TestProjectTagsClient, self).setUp() + fake_auth = fake_auth_provider.FakeAuthProvider() + self.client = project_tags_client.ProjectTagsClient(fake_auth, + 'identity', + 'regionOne') + + def _test_update_project_tag(self, bytes_body=False): + self.check_service_client_function( + self.client.update_project_tag, + 'tempest.lib.common.rest_client.RestClient.put', + {}, + bytes_body, + project_id=self.FAKE_PROJECT_ID, + tag=self.FAKE_PROJECT_TAG, + status=201) + + def _test_list_project_tags(self, bytes_body=False): + self.check_service_client_function( + self.client.list_project_tags, + 'tempest.lib.common.rest_client.RestClient.get', + {"tags": self.FAKE_PROJECT_TAGS}, + bytes_body, + project_id=self.FAKE_PROJECT_ID) + + def _test_update_all_project_tags(self, bytes_body=False): + self.check_service_client_function( + self.client.update_all_project_tags, + 'tempest.lib.common.rest_client.RestClient.put', + {"tags": self.FAKE_PROJECT_TAGS}, + bytes_body, + project_id=self.FAKE_PROJECT_ID, + tags=self.FAKE_PROJECT_TAGS) + + def test_update_project_tag_with_str_body(self): + self._test_update_project_tag() + + def test_update_project_tag_with_bytes_body(self): + self._test_update_project_tag(bytes_body=True) + + def test_list_project_tags_with_str_body(self): + self._test_list_project_tags() + + def test_list_project_tags_with_bytes_body(self): + self._test_list_project_tags(bytes_body=True) + + def test_update_all_project_tags_with_str_body(self): + self._test_update_all_project_tags() + + def test_update_all_project_tags_with_bytes_body(self): + self._test_update_all_project_tags(bytes_body=True) + + def test_check_project_project_tag_existence(self): + self.check_service_client_function( + self.client.check_project_tag_existence, + 'tempest.lib.common.rest_client.RestClient.get', + {}, + project_id=self.FAKE_PROJECT_ID, + tag=self.FAKE_PROJECT_TAG, + status=204) + + def test_delete_project_tag(self): + self.check_service_client_function( + self.client.delete_project_tag, + 'tempest.lib.common.rest_client.RestClient.delete', + {}, + project_id=self.FAKE_PROJECT_ID, + tag=self.FAKE_PROJECT_TAG, + status=204) + + def test_delete_all_project_tags(self): + self.check_service_client_function( + self.client.delete_all_project_tags, + 'tempest.lib.common.rest_client.RestClient.delete', + {}, + project_id=self.FAKE_PROJECT_ID, + status=204)