Ensure endpoints returned is filtered correctly

Conflicts:
	keystone/catalog/controllers.py
	keystone/catalog/core.py
	keystone/contrib/endpoint_filter/backends/catalog_sql.py
This patch move some logic to manager layer, so that endpoints
filtered by endpoint_group project association will be included
in catalog when issue a project scoped token and using
`endpoint_filter.sql` as catalog's backend driver.

This make sure that call `list_endpoints_for_project` API has
the same endpoints with that in catalog returned for project
scoped token.

The difference between this cherry pick and the patch on the master
branch is massive since the endpoint filter extension has been
consolidated with keystone catalog on master branch, all the change
made in the keystone catalog should be made in keystone endpoint filter
extension instead.

Closes-Bug: #1516469
(cherry picked from commit f86448a311)

Change-Id: I56f4eb6fc524650677b627295dd4338d55164c39
This commit is contained in:
Dave Chen 2015-11-26 05:39:59 +08:00 committed by Dave Chen
parent d100184a15
commit 9262bb8dcb
4 changed files with 100 additions and 44 deletions

View File

@ -17,7 +17,6 @@ from oslo_config import cfg
from keystone.catalog.backends import sql
from keystone.catalog import core as catalog_core
from keystone.common import dependency
from keystone import exception
CONF = cfg.CONF
@ -31,38 +30,33 @@ class EndpointFilterCatalog(sql.Catalog):
services = {}
refs = self.endpoint_filter_api.list_endpoints_for_project(project_id)
dict_of_endpoint_refs = (self.endpoint_filter_api.
list_endpoints_for_project(project_id))
if (not refs and
if (not dict_of_endpoint_refs and
CONF.endpoint_filter.return_all_endpoints_if_no_filter):
return super(EndpointFilterCatalog, self).get_v3_catalog(
user_id, project_id)
for entry in refs:
try:
endpoint = self.get_endpoint(entry['endpoint_id'])
if not endpoint['enabled']:
# Skip disabled endpoints.
continue
service_id = endpoint['service_id']
services.setdefault(
service_id,
self.get_service(service_id))
service = services[service_id]
del endpoint['service_id']
del endpoint['enabled']
del endpoint['legacy_endpoint_id']
endpoint['url'] = catalog_core.format_url(
endpoint['url'], substitutions)
# populate filtered endpoints
if 'endpoints' in services[service_id]:
service['endpoints'].append(endpoint)
else:
service['endpoints'] = [endpoint]
except exception.EndpointNotFound:
# remove bad reference from association
self.endpoint_filter_api.remove_endpoint_from_project(
entry['endpoint_id'], project_id)
for endpoint_id, endpoint in dict_of_endpoint_refs.items():
if not endpoint['enabled']:
# Skip disabled endpoints.
continue
service_id = endpoint['service_id']
services.setdefault(
service_id,
self.get_service(service_id))
service = services[service_id]
del endpoint['service_id']
del endpoint['enabled']
del endpoint['legacy_endpoint_id']
endpoint['url'] = catalog_core.format_url(
endpoint['url'], substitutions)
# populate filtered endpoints
if 'endpoints' in services[service_id]:
service['endpoints'].append(endpoint)
else:
service['endpoints'] = [endpoint]
# format catalog
catalog = []

View File

@ -103,22 +103,8 @@ class EndpointFilterV3Controller(_ControllerBase):
def list_endpoints_for_project(self, context, project_id):
"""List all endpoints currently associated with a given project."""
self.resource_api.get_project(project_id)
refs = self.endpoint_filter_api.list_endpoints_for_project(project_id)
filtered_endpoints = {ref['endpoint_id']:
self.catalog_api.get_endpoint(ref['endpoint_id'])
for ref in refs}
# need to recover endpoint_groups associated with project
# then for each endpoint group return the endpoints.
endpoint_groups = self._get_endpoint_groups_for_project(project_id)
for endpoint_group in endpoint_groups:
endpoint_refs = self._get_endpoints_filtered_by_endpoint_group(
endpoint_group['id'])
# now check if any endpoints for current endpoint group are not
# contained in the list of filtered endpoints
for endpoint_ref in endpoint_refs:
if endpoint_ref['id'] not in filtered_endpoints:
filtered_endpoints[endpoint_ref['id']] = endpoint_ref
filtered_endpoints = (self.endpoint_filter_api.
list_endpoints_for_project(project_id))
return catalog_controllers.EndpointV3.wrap_collection(
context, [v for v in six.itervalues(filtered_endpoints)])

View File

@ -50,6 +50,7 @@ extension.register_admin_extension(extension_data['alias'], extension_data)
@dependency.provider('endpoint_filter_api')
@dependency.requires('catalog_api', 'resource_api')
class Manager(manager.Manager):
"""Default pivot point for the Endpoint Filter backend.
@ -63,6 +64,67 @@ class Manager(manager.Manager):
def __init__(self):
super(Manager, self).__init__(CONF.endpoint_filter.driver)
def _get_endpoint_groups_for_project(self, project_id):
# recover the project endpoint group memberships and for each
# membership recover the endpoint group
self.resource_api.get_project(project_id)
try:
refs = self.driver.list_endpoint_groups_for_project(
project_id)
endpoint_groups = [self.driver.get_endpoint_group(
ref['endpoint_group_id']) for ref in refs]
return endpoint_groups
except exception.EndpointGroupNotFound:
return []
def _get_endpoints_filtered_by_endpoint_group(self, endpoint_group_id):
endpoints = self.catalog_api.list_endpoints()
filters = self.driver.get_endpoint_group(endpoint_group_id)['filters']
filtered_endpoints = []
for endpoint in endpoints:
is_candidate = True
for key, value in filters.items():
if endpoint[key] != value:
is_candidate = False
break
if is_candidate:
filtered_endpoints.append(endpoint)
return filtered_endpoints
def list_endpoints_for_project(self, project_id):
"""List all endpoints associated with a project.
:param project_id: project identifier to check
:type project_id: string
:returns: a list of endpoint ids or an empty list.
"""
refs = self.driver.list_endpoints_for_project(project_id)
filtered_endpoints = {}
for ref in refs:
try:
endpoint = self.catalog_api.get_endpoint(ref['endpoint_id'])
filtered_endpoints.update({ref['endpoint_id']: endpoint})
except exception.EndpointNotFound:
# remove bad reference from association
self.remove_endpoint_from_project(ref['endpoint_id'],
project_id)
# need to recover endpoint_groups associated with project
# then for each endpoint group return the endpoints.
endpoint_groups = self._get_endpoint_groups_for_project(project_id)
for endpoint_group in endpoint_groups:
endpoint_refs = self._get_endpoints_filtered_by_endpoint_group(
endpoint_group['id'])
# now check if any endpoints for current endpoint group are not
# contained in the list of filtered endpoints
for endpoint_ref in endpoint_refs:
if endpoint_ref['id'] not in filtered_endpoints:
filtered_endpoints[endpoint_ref['id']] = endpoint_ref
return filtered_endpoints
@six.add_metaclass(abc.ABCMeta)
class EndpointFilterDriverV8(object):

View File

@ -959,6 +959,15 @@ class EndpointGroupCRUDTestCase(TestExtensionCase):
endpoints = self.assertValidEndpointListResponse(r)
self.assertEqual(len(endpoints), 2)
# Ensure catalog includes the endpoints from endpoint_group project
# association, this is needed when a project scoped token is issued
# and "endpoint_filter.sql" backend driver is in place.
user_id = uuid.uuid4().hex
catalog_list = self.catalog_api.get_v3_catalog(
user_id,
self.default_domain_project_id)
self.assertEqual(2, len(catalog_list))
# Now remove project endpoint group association
url = self._get_project_endpoint_group_url(
endpoint_group_id, self.default_domain_project_id)
@ -973,6 +982,11 @@ class EndpointGroupCRUDTestCase(TestExtensionCase):
endpoints = self.assertValidEndpointListResponse(r)
self.assertEqual(len(endpoints), 1)
catalog_list = self.catalog_api.get_v3_catalog(
user_id,
self.default_domain_project_id)
self.assertEqual(1, len(catalog_list))
def test_endpoint_group_project_cleanup_with_project(self):
# create endpoint group
endpoint_group_id = self._create_valid_endpoint_group(