Ensure endpoints returned is filtered correctly

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.

Change-Id: I56f4eb6fc524650677b627295dd4338d55164c39
Closes-Bug: #1516469
This commit is contained in:
Dave Chen 2015-11-26 05:39:59 +08:00
parent c7aff6590c
commit f86448a311
5 changed files with 107 additions and 44 deletions

View File

@ -463,22 +463,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.catalog_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.catalog_api.list_endpoints_for_project(
project_id)
return EndpointV3.wrap_collection(
context, [v for v in six.itervalues(filtered_endpoints)])

View File

@ -119,6 +119,7 @@ def check_endpoint_url(url):
@dependency.provider('catalog_api')
@dependency.requires('resource_api')
class Manager(manager.Manager):
"""Default pivot point for the Catalog backend.
@ -321,6 +322,67 @@ class Manager(manager.Manager):
endpoint_group_id, project_id)
COMPUTED_CATALOG_REGION.invalidate()
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.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.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 CatalogDriverV8(object):

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.catalog_api.list_endpoints_for_project(project_id)
dict_of_endpoint_refs = (self.catalog_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.catalog_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

@ -1090,6 +1090,15 @@ class EndpointGroupCRUDTestCase(EndpointFilterTestCase):
endpoints = self.assertValidEndpointListResponse(r)
self.assertEqual(2, len(endpoints))
# 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)
@ -1104,6 +1113,11 @@ class EndpointGroupCRUDTestCase(EndpointFilterTestCase):
endpoints = self.assertValidEndpointListResponse(r)
self.assertEqual(1, len(endpoints))
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(

View File

@ -0,0 +1,7 @@
---
fixes:
- >
[`bug 1516469 <https://bugs.launchpad.net/keystone/+bug/1516469>`_]
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.