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:
Enrique Garcia Navalon 2015-05-13 15:00:10 +02:00 committed by Samuel de Medeiros Queiroz
parent 4c88af5ae1
commit 2cc2f1081f
6 changed files with 329 additions and 12 deletions

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

View File

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

View File

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

View File

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

View File

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

View 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.