From f86448a3113fc594e78d3d9410f44c1f64a9ad58 Mon Sep 17 00:00:00 2001 From: Dave Chen Date: Thu, 26 Nov 2015 05:39:59 +0800 Subject: [PATCH] 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 --- keystone/catalog/controllers.py | 18 +----- keystone/catalog/core.py | 62 +++++++++++++++++++ .../endpoint_filter/backends/catalog_sql.py | 50 +++++++-------- ...st_associate_project_endpoint_extension.py | 14 +++++ ...-project-association-7271fba600322fb6.yaml | 7 +++ 5 files changed, 107 insertions(+), 44 deletions(-) create mode 100644 releasenotes/notes/endpoints-from-endpoint_group-project-association-7271fba600322fb6.yaml diff --git a/keystone/catalog/controllers.py b/keystone/catalog/controllers.py index ec9da7d514..27c091b579 100644 --- a/keystone/catalog/controllers.py +++ b/keystone/catalog/controllers.py @@ -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)]) diff --git a/keystone/catalog/core.py b/keystone/catalog/core.py index 39c5544285..e724e10c58 100644 --- a/keystone/catalog/core.py +++ b/keystone/catalog/core.py @@ -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): diff --git a/keystone/contrib/endpoint_filter/backends/catalog_sql.py b/keystone/contrib/endpoint_filter/backends/catalog_sql.py index 1f3daa7cc5..27844dfa18 100644 --- a/keystone/contrib/endpoint_filter/backends/catalog_sql.py +++ b/keystone/contrib/endpoint_filter/backends/catalog_sql.py @@ -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 = [] diff --git a/keystone/tests/unit/test_associate_project_endpoint_extension.py b/keystone/tests/unit/test_associate_project_endpoint_extension.py index ac74451171..63cb5da629 100644 --- a/keystone/tests/unit/test_associate_project_endpoint_extension.py +++ b/keystone/tests/unit/test_associate_project_endpoint_extension.py @@ -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( diff --git a/releasenotes/notes/endpoints-from-endpoint_group-project-association-7271fba600322fb6.yaml b/releasenotes/notes/endpoints-from-endpoint_group-project-association-7271fba600322fb6.yaml new file mode 100644 index 0000000000..ce820a165c --- /dev/null +++ b/releasenotes/notes/endpoints-from-endpoint_group-project-association-7271fba600322fb6.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - > + [`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.