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
This commit is contained in:
Felipe Monteiro 2018-03-15 04:47:52 +00:00
parent cfb3a73d2b
commit a3b2d8e1b2
7 changed files with 270 additions and 3 deletions

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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']

View File

@ -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)

View File

@ -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)