Merge "Add identity v3 project tags client"
This commit is contained in:
commit
9c48584e01
@ -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
|
66
tempest/api/identity/admin/v3/test_project_tags.py
Normal file
66
tempest/api/identity/admin/v3/test_project_tags.py
Normal 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)
|
@ -228,6 +228,7 @@ class BaseIdentityV3AdminTest(BaseIdentityV3Test):
|
|||||||
cls.domain_config_client = cls.os_admin.domain_config_client
|
cls.domain_config_client = cls.os_admin.domain_config_client
|
||||||
cls.endpoint_filter_client = cls.os_admin.endpoint_filter_client
|
cls.endpoint_filter_client = cls.os_admin.endpoint_filter_client
|
||||||
cls.endpoint_groups_client = cls.os_admin.endpoint_groups_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:
|
if CONF.identity.admin_domain_scope:
|
||||||
# NOTE(andreaf) When keystone policy requires it, the identity
|
# NOTE(andreaf) When keystone policy requires it, the identity
|
||||||
|
@ -197,6 +197,8 @@ class Manager(clients.ServiceClients):
|
|||||||
self.endpoint_groups_client = self.identity_v3.EndPointGroupsClient(
|
self.endpoint_groups_client = self.identity_v3.EndPointGroupsClient(
|
||||||
**params_v3)
|
**params_v3)
|
||||||
self.catalog_client = self.identity_v3.CatalogClient(**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.
|
# 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
|
# They read auth_url, so they should only be set if the corresponding
|
||||||
|
@ -33,6 +33,8 @@ from tempest.lib.services.identity.v3.oauth_consumers_client import \
|
|||||||
from tempest.lib.services.identity.v3.oauth_token_client import \
|
from tempest.lib.services.identity.v3.oauth_token_client import \
|
||||||
OAUTHTokenClient
|
OAUTHTokenClient
|
||||||
from tempest.lib.services.identity.v3.policies_client import PoliciesClient
|
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.projects_client import ProjectsClient
|
||||||
from tempest.lib.services.identity.v3.regions_client import RegionsClient
|
from tempest.lib.services.identity.v3.regions_client import RegionsClient
|
||||||
from tempest.lib.services.identity.v3.role_assignments_client import \
|
from tempest.lib.services.identity.v3.role_assignments_client import \
|
||||||
@ -49,6 +51,6 @@ __all__ = ['CatalogClient', 'CredentialsClient', 'DomainsClient',
|
|||||||
'EndPointsClient', 'EndPointsFilterClient', 'GroupsClient',
|
'EndPointsClient', 'EndPointsFilterClient', 'GroupsClient',
|
||||||
'IdentityClient', 'InheritedRolesClient', 'OAUTHConsumerClient',
|
'IdentityClient', 'InheritedRolesClient', 'OAUTHConsumerClient',
|
||||||
'OAUTHTokenClient', 'PoliciesClient', 'ProjectsClient',
|
'OAUTHTokenClient', 'PoliciesClient', 'ProjectsClient',
|
||||||
'RegionsClient', 'RoleAssignmentsClient', 'RolesClient',
|
'ProjectTagsClient', 'RegionsClient', 'RoleAssignmentsClient',
|
||||||
'ServicesClient', 'V3TokenClient', 'TrustsClient', 'UsersClient',
|
'RolesClient', 'ServicesClient', 'V3TokenClient', 'TrustsClient',
|
||||||
'VersionsClient']
|
'UsersClient', 'VersionsClient']
|
||||||
|
80
tempest/lib/services/identity/v3/project_tags_client.py
Normal file
80
tempest/lib/services/identity/v3/project_tags_client.py
Normal 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)
|
@ -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)
|
Loading…
Reference in New Issue
Block a user