Add support for endpoint group filtering
The following API calls are made available: - GET /OS-EP-FILTER/projects/{project_id}/endpoint_groups - GET /OS-EP-FILTER/endpoint_groups/{endpoint_group_id}/projects - PUT /OS-EP-FILTER/endpoint_groups/{endpoint_group}/projects/{project_id} - HEAD /OS-EP-FILTER/endpoint_groups/{endpoint_group}/projects/{project_id} - DELETE /OS-EP-FILTER/endpoint_groups/{endpoint_group}/projects/{project_id} Co-Authored-By: Samuel de Medeiros Queiroz <samueldmq@gmail.com> Closes-Bug: #1641674 Change-Id: Idf938267479b5b8c50c9aa141c3c2770c2d69839
This commit is contained in:
parent
4c88af5ae1
commit
2cc2f1081f
86
keystoneclient/tests/functional/v3/test_endpoint_filters.py
Normal file
86
keystoneclient/tests/functional/v3/test_endpoint_filters.py
Normal file
@ -0,0 +1,86 @@
|
||||
# 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 keystoneauth1.exceptions import http
|
||||
|
||||
from keystoneclient.tests.functional import base
|
||||
from keystoneclient.tests.functional.v3 import client_fixtures as fixtures
|
||||
from keystoneclient.tests.functional.v3 import test_endpoint_groups
|
||||
from keystoneclient.tests.functional.v3 import test_projects
|
||||
|
||||
|
||||
class EndpointFiltersTestCase(base.V3ClientTestCase,
|
||||
test_endpoint_groups.EndpointGroupsTestMixin,
|
||||
test_projects.ProjectsTestMixin):
|
||||
|
||||
def setUp(self):
|
||||
super(EndpointFiltersTestCase, self).setUp()
|
||||
|
||||
self.project = fixtures.Project(self.client)
|
||||
self.endpoint_group = fixtures.EndpointGroup(self.client)
|
||||
self.useFixture(self.project)
|
||||
self.useFixture(self.endpoint_group)
|
||||
|
||||
self.client.endpoint_filter.add_endpoint_group_to_project(
|
||||
self.endpoint_group, self.project)
|
||||
|
||||
def test_add_endpoint_group_to_project(self):
|
||||
project = fixtures.Project(self.client)
|
||||
endpoint_group = fixtures.EndpointGroup(self.client)
|
||||
self.useFixture(project)
|
||||
self.useFixture(endpoint_group)
|
||||
|
||||
self.client.endpoint_filter.add_endpoint_group_to_project(
|
||||
endpoint_group, project)
|
||||
self.client.endpoint_filter.check_endpoint_group_in_project(
|
||||
endpoint_group, project)
|
||||
|
||||
def test_delete_endpoint_group_from_project(self):
|
||||
self.client.endpoint_filter.delete_endpoint_group_from_project(
|
||||
self.endpoint_group, self.project)
|
||||
self.assertRaises(
|
||||
http.NotFound,
|
||||
self.client.endpoint_filter.check_endpoint_group_in_project,
|
||||
self.endpoint_group, self.project)
|
||||
|
||||
def test_list_endpoint_groups_for_project(self):
|
||||
endpoint_group_two = fixtures.EndpointGroup(self.client)
|
||||
self.useFixture(endpoint_group_two)
|
||||
self.client.endpoint_filter.add_endpoint_group_to_project(
|
||||
endpoint_group_two, self.project)
|
||||
|
||||
endpoint_groups = (
|
||||
self.client.endpoint_filter.list_endpoint_groups_for_project(
|
||||
self.project
|
||||
)
|
||||
)
|
||||
|
||||
for endpoint_group in endpoint_groups:
|
||||
self.check_endpoint_group(endpoint_group)
|
||||
|
||||
self.assertIn(self.endpoint_group.entity, endpoint_groups)
|
||||
self.assertIn(endpoint_group_two.entity, endpoint_groups)
|
||||
|
||||
def test_list_projects_for_endpoint_group(self):
|
||||
project_two = fixtures.Project(self.client)
|
||||
self.useFixture(project_two)
|
||||
self.client.endpoint_filter.add_endpoint_group_to_project(
|
||||
self.endpoint_group, project_two)
|
||||
|
||||
f = self.client.endpoint_filter.list_projects_for_endpoint_group
|
||||
projects = f(self.endpoint_group)
|
||||
|
||||
for project in projects:
|
||||
self.check_project(project)
|
||||
|
||||
self.assertIn(self.project.entity, projects)
|
||||
self.assertIn(project_two.entity, projects)
|
@ -18,7 +18,7 @@ from keystoneclient.tests.functional import base
|
||||
from keystoneclient.tests.functional.v3 import client_fixtures as fixtures
|
||||
|
||||
|
||||
class EndpointGroupsTestCase(base.V3ClientTestCase):
|
||||
class EndpointGroupsTestMixin(object):
|
||||
|
||||
def check_endpoint_group(self, endpoint_group, endpoint_group_ref=None):
|
||||
self.assertIsNotNone(endpoint_group.id)
|
||||
@ -40,6 +40,9 @@ class EndpointGroupsTestCase(base.V3ClientTestCase):
|
||||
self.assertIsNotNone(endpoint_group.name)
|
||||
self.assertIsNotNone(endpoint_group.filters)
|
||||
|
||||
|
||||
class EndpointGroupsTestCase(base.V3ClientTestCase, EndpointGroupsTestMixin):
|
||||
|
||||
def test_create_endpoint_group(self):
|
||||
endpoint_group_ref = {
|
||||
'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex,
|
||||
|
@ -18,15 +18,7 @@ from keystoneclient.tests.functional import base
|
||||
from keystoneclient.tests.functional.v3 import client_fixtures as fixtures
|
||||
|
||||
|
||||
class ProjectsTestCase(base.V3ClientTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ProjectsTestCase, self).setUp()
|
||||
self.test_domain = fixtures.Domain(self.client)
|
||||
self.useFixture(self.test_domain)
|
||||
|
||||
self.test_project = fixtures.Project(self.client, self.test_domain.id)
|
||||
self.useFixture(self.test_project)
|
||||
class ProjectsTestMixin(object):
|
||||
|
||||
def check_project(self, project, project_ref=None):
|
||||
self.assertIsNotNone(project.id)
|
||||
@ -51,6 +43,17 @@ class ProjectsTestCase(base.V3ClientTestCase):
|
||||
self.assertIsNotNone(project.domain_id)
|
||||
self.assertIsNotNone(project.enabled)
|
||||
|
||||
|
||||
class ProjectsTestCase(base.V3ClientTestCase, ProjectsTestMixin):
|
||||
|
||||
def setUp(self):
|
||||
super(ProjectsTestCase, self).setUp()
|
||||
self.test_domain = fixtures.Domain(self.client)
|
||||
self.useFixture(self.test_domain)
|
||||
|
||||
self.test_project = fixtures.Project(self.client, self.test_domain.id)
|
||||
self.useFixture(self.test_project)
|
||||
|
||||
def test_create_subproject(self):
|
||||
project_ref = {
|
||||
'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex,
|
||||
|
@ -36,6 +36,13 @@ class EndpointTestUtils(object):
|
||||
kwargs.setdefault('url', uuid.uuid4().hex)
|
||||
return kwargs
|
||||
|
||||
def new_endpoint_group_ref(self, **kwargs):
|
||||
kwargs.setdefault('id', uuid.uuid4().hex)
|
||||
kwargs.setdefault('name', uuid.uuid4().hex)
|
||||
kwargs.setdefault('description', uuid.uuid4().hex)
|
||||
kwargs.setdefault('filters')
|
||||
return kwargs
|
||||
|
||||
|
||||
class EndpointFilterTests(utils.ClientTestCase, EndpointTestUtils):
|
||||
"""Test project-endpoint associations (a.k.a. EndpointFilter Extension).
|
||||
@ -147,3 +154,140 @@ class EndpointFilterTests(utils.ClientTestCase, EndpointTestUtils):
|
||||
project['id'] for project in projects['projects']]
|
||||
actual_project_ids = [project.id for project in projects_resp]
|
||||
self.assertEqual(expected_project_ids, actual_project_ids)
|
||||
|
||||
def test_list_projects_for_endpoint_group(self):
|
||||
endpoint_group_id = uuid.uuid4().hex
|
||||
projects = {'projects': [self.new_project_ref(),
|
||||
self.new_project_ref()]}
|
||||
self.stub_url('GET',
|
||||
[self.manager.OS_EP_FILTER_EXT, 'endpoint_groups',
|
||||
endpoint_group_id, 'projects'],
|
||||
json=projects,
|
||||
status_code=200)
|
||||
|
||||
projects_resp = self.manager.list_projects_for_endpoint_group(
|
||||
endpoint_group=endpoint_group_id)
|
||||
|
||||
expected_project_ids = [
|
||||
project['id'] for project in projects['projects']]
|
||||
actual_project_ids = [project.id for project in projects_resp]
|
||||
self.assertEqual(expected_project_ids, actual_project_ids)
|
||||
|
||||
def test_list_projects_for_endpoint_group_value_error(self):
|
||||
self.assertRaises(ValueError,
|
||||
self.manager.list_projects_for_endpoint_group,
|
||||
endpoint_group='')
|
||||
self.assertRaises(ValueError,
|
||||
self.manager.list_projects_for_endpoint_group,
|
||||
endpoint_group=None)
|
||||
|
||||
def test_list_endpoint_groups_for_project(self):
|
||||
project_id = uuid.uuid4().hex
|
||||
endpoint_groups = {
|
||||
'endpoint_groups': [self.new_endpoint_group_ref(),
|
||||
self.new_endpoint_group_ref()]}
|
||||
self.stub_url('GET',
|
||||
[self.manager.OS_EP_FILTER_EXT, 'projects',
|
||||
project_id, 'endpoint_groups'],
|
||||
json=endpoint_groups,
|
||||
status_code=200)
|
||||
|
||||
endpoint_groups_resp = self.manager.list_endpoint_groups_for_project(
|
||||
project=project_id)
|
||||
|
||||
expected_endpoint_group_ids = [
|
||||
endpoint_group['id'] for endpoint_group
|
||||
in endpoint_groups['endpoint_groups']
|
||||
]
|
||||
actual_endpoint_group_ids = [
|
||||
endpoint_group.id for endpoint_group in endpoint_groups_resp
|
||||
]
|
||||
self.assertEqual(expected_endpoint_group_ids,
|
||||
actual_endpoint_group_ids)
|
||||
|
||||
def test_list_endpoint_groups_for_project_value_error(self):
|
||||
for value in ('', None):
|
||||
self.assertRaises(ValueError,
|
||||
self.manager.list_endpoint_groups_for_project,
|
||||
project=value)
|
||||
|
||||
def test_add_endpoint_group_to_project(self):
|
||||
endpoint_group_id = uuid.uuid4().hex
|
||||
project_id = uuid.uuid4().hex
|
||||
|
||||
self.stub_url('PUT',
|
||||
[self.manager.OS_EP_FILTER_EXT, 'endpoint_groups',
|
||||
endpoint_group_id, 'projects', project_id],
|
||||
status_code=201)
|
||||
|
||||
self.manager.add_endpoint_group_to_project(
|
||||
project=project_id, endpoint_group=endpoint_group_id)
|
||||
|
||||
def test_add_endpoint_group_to_project_value_error(self):
|
||||
for value in ('', None):
|
||||
self.assertRaises(ValueError,
|
||||
self.manager.add_endpoint_group_to_project,
|
||||
project=value,
|
||||
endpoint_group=value)
|
||||
self.assertRaises(ValueError,
|
||||
self.manager.add_endpoint_group_to_project,
|
||||
project=uuid.uuid4().hex,
|
||||
endpoint_group=value)
|
||||
self.assertRaises(ValueError,
|
||||
self.manager.add_endpoint_group_to_project,
|
||||
project=value,
|
||||
endpoint_group=uuid.uuid4().hex)
|
||||
|
||||
def test_check_endpoint_group_in_project(self):
|
||||
endpoint_group_id = uuid.uuid4().hex
|
||||
project_id = uuid.uuid4().hex
|
||||
|
||||
self.stub_url('HEAD',
|
||||
[self.manager.OS_EP_FILTER_EXT, 'endpoint_groups',
|
||||
endpoint_group_id, 'projects', project_id],
|
||||
status_code=201)
|
||||
|
||||
self.manager.check_endpoint_group_in_project(
|
||||
project=project_id, endpoint_group=endpoint_group_id)
|
||||
|
||||
def test_check_endpoint_group_in_project_value_error(self):
|
||||
for value in ('', None):
|
||||
self.assertRaises(ValueError,
|
||||
self.manager.check_endpoint_group_in_project,
|
||||
project=value,
|
||||
endpoint_group=value)
|
||||
self.assertRaises(ValueError,
|
||||
self.manager.check_endpoint_group_in_project,
|
||||
project=uuid.uuid4().hex,
|
||||
endpoint_group=value)
|
||||
self.assertRaises(ValueError,
|
||||
self.manager.check_endpoint_group_in_project,
|
||||
project=value,
|
||||
endpoint_group=uuid.uuid4().hex)
|
||||
|
||||
def test_delete_endpoint_group_from_project(self):
|
||||
endpoint_group_id = uuid.uuid4().hex
|
||||
project_id = uuid.uuid4().hex
|
||||
|
||||
self.stub_url('DELETE',
|
||||
[self.manager.OS_EP_FILTER_EXT, 'endpoint_groups',
|
||||
endpoint_group_id, 'projects', project_id],
|
||||
status_code=201)
|
||||
|
||||
self.manager.delete_endpoint_group_from_project(
|
||||
project=project_id, endpoint_group=endpoint_group_id)
|
||||
|
||||
def test_delete_endpoint_group_from_project_value_error(self):
|
||||
for value in ('', None):
|
||||
self.assertRaises(ValueError,
|
||||
self.manager.delete_endpoint_group_from_project,
|
||||
project=value,
|
||||
endpoint_group=value)
|
||||
self.assertRaises(ValueError,
|
||||
self.manager.delete_endpoint_group_from_project,
|
||||
project=uuid.uuid4().hex,
|
||||
endpoint_group=value)
|
||||
self.assertRaises(ValueError,
|
||||
self.manager.delete_endpoint_group_from_project,
|
||||
project=value,
|
||||
endpoint_group=uuid.uuid4().hex)
|
||||
|
@ -15,12 +15,18 @@
|
||||
from keystoneclient import base
|
||||
from keystoneclient import exceptions
|
||||
from keystoneclient.i18n import _
|
||||
from keystoneclient.v3 import endpoint_groups
|
||||
from keystoneclient.v3 import endpoints
|
||||
from keystoneclient.v3 import projects
|
||||
|
||||
|
||||
class EndpointFilterManager(base.Manager):
|
||||
"""Manager class for manipulating project-endpoint associations."""
|
||||
"""Manager class for manipulating project-endpoint associations.
|
||||
|
||||
Project-endpoint associations can be with endpoints directly or via
|
||||
endpoint groups.
|
||||
|
||||
"""
|
||||
|
||||
OS_EP_FILTER_EXT = '/OS-EP-FILTER'
|
||||
|
||||
@ -40,6 +46,23 @@ class EndpointFilterManager(base.Manager):
|
||||
|
||||
return self.OS_EP_FILTER_EXT + api_path
|
||||
|
||||
def _build_group_base_url(self, project=None, endpoint_group=None):
|
||||
project_id = base.getid(project)
|
||||
endpoint_group_id = base.getid(endpoint_group)
|
||||
|
||||
if project_id and endpoint_group_id:
|
||||
api_path = '/endpoint_groups/%s/projects/%s' % (
|
||||
endpoint_group_id, project_id)
|
||||
elif project_id:
|
||||
api_path = '/projects/%s/endpoint_groups' % (project_id)
|
||||
elif endpoint_group_id:
|
||||
api_path = '/endpoint_groups/%s/projects' % (endpoint_group_id)
|
||||
else:
|
||||
msg = _('Must specify a project, an endpoint group, or both')
|
||||
raise exceptions.ValidationError(msg)
|
||||
|
||||
return self.OS_EP_FILTER_EXT + api_path
|
||||
|
||||
def add_endpoint_to_project(self, project, endpoint):
|
||||
"""Create a project-endpoint association."""
|
||||
if not (project and endpoint):
|
||||
@ -59,7 +82,7 @@ class EndpointFilterManager(base.Manager):
|
||||
return super(EndpointFilterManager, self)._delete(url=base_url)
|
||||
|
||||
def check_endpoint_in_project(self, project, endpoint):
|
||||
"""Check if project-endpoint association exist."""
|
||||
"""Check if project-endpoint association exists."""
|
||||
if not (project and endpoint):
|
||||
raise ValueError(_('project and endpoint are required'))
|
||||
|
||||
@ -88,3 +111,53 @@ class EndpointFilterManager(base.Manager):
|
||||
base_url,
|
||||
projects.ProjectManager.collection_key,
|
||||
obj_class=projects.ProjectManager.resource_class)
|
||||
|
||||
def add_endpoint_group_to_project(self, endpoint_group, project):
|
||||
"""Create a project-endpoint group association."""
|
||||
if not (project and endpoint_group):
|
||||
raise ValueError(_('project and endpoint_group are required'))
|
||||
|
||||
base_url = self._build_group_base_url(project=project,
|
||||
endpoint_group=endpoint_group)
|
||||
return super(EndpointFilterManager, self)._put(url=base_url)
|
||||
|
||||
def delete_endpoint_group_from_project(self, endpoint_group, project):
|
||||
"""Remove a project-endpoint group association."""
|
||||
if not (project and endpoint_group):
|
||||
raise ValueError(_('project and endpoint_group are required'))
|
||||
|
||||
base_url = self._build_group_base_url(project=project,
|
||||
endpoint_group=endpoint_group)
|
||||
return super(EndpointFilterManager, self)._delete(url=base_url)
|
||||
|
||||
def check_endpoint_group_in_project(self, endpoint_group, project):
|
||||
"""Check if project-endpoint group association exists."""
|
||||
if not (project and endpoint_group):
|
||||
raise ValueError(_('project and endpoint_group are required'))
|
||||
|
||||
base_url = self._build_group_base_url(project=project,
|
||||
endpoint_group=endpoint_group)
|
||||
return super(EndpointFilterManager, self)._head(url=base_url)
|
||||
|
||||
def list_endpoint_groups_for_project(self, project):
|
||||
"""List all endpoint groups for a given project."""
|
||||
if not project:
|
||||
raise ValueError(_('project is required'))
|
||||
|
||||
base_url = self._build_group_base_url(project=project)
|
||||
|
||||
return super(EndpointFilterManager, self)._list(
|
||||
base_url,
|
||||
'endpoint_groups',
|
||||
obj_class=endpoint_groups.EndpointGroupManager.resource_class)
|
||||
|
||||
def list_projects_for_endpoint_group(self, endpoint_group):
|
||||
"""List all projects associated with a given endpoint group."""
|
||||
if not endpoint_group:
|
||||
raise ValueError(_('endpoint_group is required'))
|
||||
|
||||
base_url = self._build_group_base_url(endpoint_group=endpoint_group)
|
||||
return super(EndpointFilterManager, self)._list(
|
||||
base_url,
|
||||
projects.ProjectManager.collection_key,
|
||||
obj_class=projects.ProjectManager.resource_class)
|
||||
|
8
releasenotes/notes/bug-1641674-4862454115265e76.yaml
Normal file
8
releasenotes/notes/bug-1641674-4862454115265e76.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
prelude: >
|
||||
Keystone Client now supports endpoint group filtering.
|
||||
features:
|
||||
- |
|
||||
Support for handling the relationship between endpoint groups and projects
|
||||
has been added. It is now possible to list, associate, check and
|
||||
disassociate endpoint groups that have access to a project.
|
Loading…
Reference in New Issue
Block a user