summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--keystoneclient/tests/functional/v3/test_endpoint_filters.py86
-rw-r--r--keystoneclient/tests/functional/v3/test_endpoint_groups.py5
-rw-r--r--keystoneclient/tests/functional/v3/test_projects.py21
-rw-r--r--keystoneclient/tests/unit/v3/test_endpoint_filter.py144
-rw-r--r--keystoneclient/v3/contrib/endpoint_filter.py77
-rw-r--r--releasenotes/notes/bug-1641674-4862454115265e76.yaml8
6 files changed, 329 insertions, 12 deletions
diff --git a/keystoneclient/tests/functional/v3/test_endpoint_filters.py b/keystoneclient/tests/functional/v3/test_endpoint_filters.py
new file mode 100644
index 0000000..d8956be
--- /dev/null
+++ b/keystoneclient/tests/functional/v3/test_endpoint_filters.py
@@ -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)
diff --git a/keystoneclient/tests/functional/v3/test_endpoint_groups.py b/keystoneclient/tests/functional/v3/test_endpoint_groups.py
index 10eccfb..52fcf72 100644
--- a/keystoneclient/tests/functional/v3/test_endpoint_groups.py
+++ b/keystoneclient/tests/functional/v3/test_endpoint_groups.py
@@ -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,
diff --git a/keystoneclient/tests/functional/v3/test_projects.py b/keystoneclient/tests/functional/v3/test_projects.py
index 0fa631d..4b8d749 100644
--- a/keystoneclient/tests/functional/v3/test_projects.py
+++ b/keystoneclient/tests/functional/v3/test_projects.py
@@ -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,
diff --git a/keystoneclient/tests/unit/v3/test_endpoint_filter.py b/keystoneclient/tests/unit/v3/test_endpoint_filter.py
index 2eed705..62e89cb 100644
--- a/keystoneclient/tests/unit/v3/test_endpoint_filter.py
+++ b/keystoneclient/tests/unit/v3/test_endpoint_filter.py
@@ -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)
diff --git a/keystoneclient/v3/contrib/endpoint_filter.py b/keystoneclient/v3/contrib/endpoint_filter.py
index 586a74a..26d5a87 100644
--- a/keystoneclient/v3/contrib/endpoint_filter.py
+++ b/keystoneclient/v3/contrib/endpoint_filter.py
@@ -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)
diff --git a/releasenotes/notes/bug-1641674-4862454115265e76.yaml b/releasenotes/notes/bug-1641674-4862454115265e76.yaml
new file mode 100644
index 0000000..19c8ecc
--- /dev/null
+++ b/releasenotes/notes/bug-1641674-4862454115265e76.yaml
@@ -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.