diff --git a/etc/keystone-paste.ini b/etc/keystone-paste.ini index 7adedcb57c..dfd865dd36 100644 --- a/etc/keystone-paste.ini +++ b/etc/keystone-paste.ini @@ -33,9 +33,6 @@ use = egg:keystone#ec2_extension_v3 [filter:s3_extension] use = egg:keystone#s3_extension -[filter:endpoint_filter_extension] -use = egg:keystone#endpoint_filter_extension - [filter:simple_cert_extension] use = egg:keystone#simple_cert_extension @@ -67,7 +64,7 @@ pipeline = sizelimit url_normalize request_id build_auth_context token_auth admi [pipeline:api_v3] # The last item in this pipeline must be service_v3 or an equivalent # application. It cannot be a filter. -pipeline = sizelimit url_normalize request_id build_auth_context token_auth admin_token_auth json_body ec2_extension_v3 s3_extension simple_cert_extension endpoint_filter_extension service_v3 +pipeline = sizelimit url_normalize request_id build_auth_context token_auth admin_token_auth json_body ec2_extension_v3 s3_extension simple_cert_extension service_v3 [app:public_version_service] use = egg:keystone#public_version_service diff --git a/keystone/catalog/backends/kvs.py b/keystone/catalog/backends/kvs.py index fe975d9d12..f1f9ade482 100644 --- a/keystone/catalog/backends/kvs.py +++ b/keystone/catalog/backends/kvs.py @@ -152,3 +152,213 @@ class Catalog(kvs.Base, catalog.CatalogDriverV8): def _create_catalog(self, user_id, tenant_id, data): self.db.set('catalog-%s-%s' % (tenant_id, user_id), data) return data + + # TODO(davechen): Apparently, these methods are not implemented but + # we cannot raise exception.NotImplemented() just because the notification + # to those resource will break some testcases, will look into CADF to + # see if there is any better way to do this. + def add_endpoint_to_project(self, endpoint_id, project_id): + """Create an endpoint to project association. + + :param endpoint_id: identity of endpoint to associate + :type endpoint_id: string + :param project_id: identity of the project to be associated with + :type project_id: string + :raises: keystone.exception.Conflict, + :returns: None. + + """ + pass + + def remove_endpoint_from_project(self, endpoint_id, project_id): + """Removes an endpoint to project association. + + :param endpoint_id: identity of endpoint to remove + :type endpoint_id: string + :param project_id: identity of the project associated with + :type project_id: string + :raises: exception.NotFound + :returns: None. + + """ + pass + + def check_endpoint_in_project(self, endpoint_id, project_id): + """Checks if an endpoint is associated with a project. + + :param endpoint_id: identity of endpoint to check + :type endpoint_id: string + :param project_id: identity of the project associated with + :type project_id: string + :raises: exception.NotFound + :returns: None. + + """ + pass + + def list_endpoints_for_project(self, project_id): + """List all endpoints associated with a project. + + :param project_id: identity of the project to check + :type project_id: string + :returns: a list of identity endpoint ids or an empty list. + + """ + pass + + def list_projects_for_endpoint(self, endpoint_id): + """List all projects associated with an endpoint. + + :param endpoint_id: identity of endpoint to check + :type endpoint_id: string + :returns: a list of projects or an empty list. + + """ + pass + + def delete_association_by_endpoint(self, endpoint_id): + """Removes all the endpoints to project association with endpoint. + + :param endpoint_id: identity of endpoint to check + :type endpoint_id: string + :returns: None + + """ + pass + + def delete_association_by_project(self, project_id): + """Removes all the endpoints to project association with project. + + :param project_id: identity of the project to check + :type project_id: string + :returns: None + + """ + pass + + def create_endpoint_group(self, endpoint_group): + """Create an endpoint group. + + :param endpoint_group: endpoint group to create + :type endpoint_group: dictionary + :raises: keystone.exception.Conflict, + :returns: an endpoint group representation. + + """ + pass + + def get_endpoint_group(self, endpoint_group_id): + """Get an endpoint group. + + :param endpoint_group_id: identity of endpoint group to retrieve + :type endpoint_group_id: string + :raises: exception.NotFound + :returns: an endpoint group representation. + + """ + pass + + def update_endpoint_group(self, endpoint_group_id, endpoint_group): + """Update an endpoint group. + + :param endpoint_group_id: identity of endpoint group to retrieve + :type endpoint_group_id: string + :param endpoint_group: A full or partial endpoint_group + :type endpoint_group: dictionary + :raises: exception.NotFound + :returns: an endpoint group representation. + + """ + pass + + def delete_endpoint_group(self, endpoint_group_id): + """Delete an endpoint group. + + :param endpoint_group_id: identity of endpoint group to delete + :type endpoint_group_id: string + :raises: exception.NotFound + :returns: None. + + """ + pass + + def add_endpoint_group_to_project(self, endpoint_group_id, project_id): + """Adds an endpoint group to project association. + + :param endpoint_group_id: identity of endpoint to associate + :type endpoint_group_id: string + :param project_id: identity of project to associate + :type project_id: string + :raises: keystone.exception.Conflict, + :returns: None. + + """ + pass + + def get_endpoint_group_in_project(self, endpoint_group_id, project_id): + """Get endpoint group to project association. + + :param endpoint_group_id: identity of endpoint group to retrieve + :type endpoint_group_id: string + :param project_id: identity of project to associate + :type project_id: string + :raises: exception.NotFound + :returns: a project endpoint group representation. + + """ + pass + + def list_endpoint_groups(self): + """List all endpoint groups. + + :raises: exception.NotFound + :returns: None. + + """ + pass + + def list_endpoint_groups_for_project(self, project_id): + """List all endpoint group to project associations for a project. + + :param project_id: identity of project to associate + :type project_id: string + :raises: exception.NotFound + :returns: None. + + """ + pass + + def list_projects_associated_with_endpoint_group(self, endpoint_group_id): + """List all projects associated with endpoint group. + + :param endpoint_group_id: identity of endpoint to associate + :type endpoint_group_id: string + :raises: exception.NotFound + :returns: None. + + """ + pass + + def remove_endpoint_group_from_project(self, endpoint_group_id, + project_id): + """Remove an endpoint to project association. + + :param endpoint_group_id: identity of endpoint to associate + :type endpoint_group_id: string + :param project_id: identity of project to associate + :type project_id: string + :raises: exception.NotFound + :returns: None. + + """ + pass + + def delete_endpoint_group_association_by_project(self, project_id): + """Remove endpoint group to project associations. + + :param project_id: identity of the project to check + :type project_id: string + :returns: None + + """ + pass diff --git a/keystone/catalog/backends/sql.py b/keystone/catalog/backends/sql.py index fe69db5820..7911277475 100644 --- a/keystone/catalog/backends/sql.py +++ b/keystone/catalog/backends/sql.py @@ -23,6 +23,7 @@ from keystone import catalog from keystone.catalog import core from keystone.common import sql from keystone import exception +from keystone.i18n import _ CONF = cfg.CONF @@ -381,3 +382,210 @@ class Catalog(catalog.CatalogDriverV8): return service return [make_v3_service(svc) for svc in services] + + @sql.handle_conflicts(conflict_type='project_endpoint') + def add_endpoint_to_project(self, endpoint_id, project_id): + session = sql.get_session() + with session.begin(): + endpoint_filter_ref = ProjectEndpoint(endpoint_id=endpoint_id, + project_id=project_id) + session.add(endpoint_filter_ref) + + def _get_project_endpoint_ref(self, session, endpoint_id, project_id): + endpoint_filter_ref = session.query(ProjectEndpoint).get( + (endpoint_id, project_id)) + if endpoint_filter_ref is None: + msg = _('Endpoint %(endpoint_id)s not found in project ' + '%(project_id)s') % {'endpoint_id': endpoint_id, + 'project_id': project_id} + raise exception.NotFound(msg) + return endpoint_filter_ref + + def check_endpoint_in_project(self, endpoint_id, project_id): + session = sql.get_session() + self._get_project_endpoint_ref(session, endpoint_id, project_id) + + def remove_endpoint_from_project(self, endpoint_id, project_id): + session = sql.get_session() + endpoint_filter_ref = self._get_project_endpoint_ref( + session, endpoint_id, project_id) + with session.begin(): + session.delete(endpoint_filter_ref) + + def list_endpoints_for_project(self, project_id): + session = sql.get_session() + query = session.query(ProjectEndpoint) + query = query.filter_by(project_id=project_id) + endpoint_filter_refs = query.all() + return [ref.to_dict() for ref in endpoint_filter_refs] + + def list_projects_for_endpoint(self, endpoint_id): + session = sql.get_session() + query = session.query(ProjectEndpoint) + query = query.filter_by(endpoint_id=endpoint_id) + endpoint_filter_refs = query.all() + return [ref.to_dict() for ref in endpoint_filter_refs] + + def delete_association_by_endpoint(self, endpoint_id): + session = sql.get_session() + with session.begin(): + query = session.query(ProjectEndpoint) + query = query.filter_by(endpoint_id=endpoint_id) + query.delete(synchronize_session=False) + + def delete_association_by_project(self, project_id): + session = sql.get_session() + with session.begin(): + query = session.query(ProjectEndpoint) + query = query.filter_by(project_id=project_id) + query.delete(synchronize_session=False) + + def create_endpoint_group(self, endpoint_group_id, endpoint_group): + session = sql.get_session() + with session.begin(): + endpoint_group_ref = EndpointGroup.from_dict(endpoint_group) + session.add(endpoint_group_ref) + return endpoint_group_ref.to_dict() + + def _get_endpoint_group(self, session, endpoint_group_id): + endpoint_group_ref = session.query(EndpointGroup).get( + endpoint_group_id) + if endpoint_group_ref is None: + raise exception.EndpointGroupNotFound( + endpoint_group_id=endpoint_group_id) + return endpoint_group_ref + + def get_endpoint_group(self, endpoint_group_id): + session = sql.get_session() + endpoint_group_ref = self._get_endpoint_group(session, + endpoint_group_id) + return endpoint_group_ref.to_dict() + + def update_endpoint_group(self, endpoint_group_id, endpoint_group): + session = sql.get_session() + with session.begin(): + endpoint_group_ref = self._get_endpoint_group(session, + endpoint_group_id) + old_endpoint_group = endpoint_group_ref.to_dict() + old_endpoint_group.update(endpoint_group) + new_endpoint_group = EndpointGroup.from_dict(old_endpoint_group) + for attr in EndpointGroup.mutable_attributes: + setattr(endpoint_group_ref, attr, + getattr(new_endpoint_group, attr)) + return endpoint_group_ref.to_dict() + + def delete_endpoint_group(self, endpoint_group_id): + session = sql.get_session() + endpoint_group_ref = self._get_endpoint_group(session, + endpoint_group_id) + with session.begin(): + self._delete_endpoint_group_association_by_endpoint_group( + session, endpoint_group_id) + session.delete(endpoint_group_ref) + + def get_endpoint_group_in_project(self, endpoint_group_id, project_id): + session = sql.get_session() + ref = self._get_endpoint_group_in_project(session, + endpoint_group_id, + project_id) + return ref.to_dict() + + @sql.handle_conflicts(conflict_type='project_endpoint_group') + def add_endpoint_group_to_project(self, endpoint_group_id, project_id): + session = sql.get_session() + + with session.begin(): + # Create a new Project Endpoint group entity + endpoint_group_project_ref = ProjectEndpointGroupMembership( + endpoint_group_id=endpoint_group_id, project_id=project_id) + session.add(endpoint_group_project_ref) + + def _get_endpoint_group_in_project(self, session, + endpoint_group_id, project_id): + endpoint_group_project_ref = session.query( + ProjectEndpointGroupMembership).get((endpoint_group_id, + project_id)) + if endpoint_group_project_ref is None: + msg = _('Endpoint Group Project Association not found') + raise exception.NotFound(msg) + else: + return endpoint_group_project_ref + + def list_endpoint_groups(self): + session = sql.get_session() + query = session.query(EndpointGroup) + endpoint_group_refs = query.all() + return [e.to_dict() for e in endpoint_group_refs] + + def list_endpoint_groups_for_project(self, project_id): + session = sql.get_session() + query = session.query(ProjectEndpointGroupMembership) + query = query.filter_by(project_id=project_id) + endpoint_group_refs = query.all() + return [ref.to_dict() for ref in endpoint_group_refs] + + def remove_endpoint_group_from_project(self, endpoint_group_id, + project_id): + session = sql.get_session() + endpoint_group_project_ref = self._get_endpoint_group_in_project( + session, endpoint_group_id, project_id) + with session.begin(): + session.delete(endpoint_group_project_ref) + + def list_projects_associated_with_endpoint_group(self, endpoint_group_id): + session = sql.get_session() + query = session.query(ProjectEndpointGroupMembership) + query = query.filter_by(endpoint_group_id=endpoint_group_id) + endpoint_group_refs = query.all() + return [ref.to_dict() for ref in endpoint_group_refs] + + def _delete_endpoint_group_association_by_endpoint_group( + self, session, endpoint_group_id): + query = session.query(ProjectEndpointGroupMembership) + query = query.filter_by(endpoint_group_id=endpoint_group_id) + query.delete() + + def delete_endpoint_group_association_by_project(self, project_id): + session = sql.get_session() + with session.begin(): + query = session.query(ProjectEndpointGroupMembership) + query = query.filter_by(project_id=project_id) + query.delete() + + +class ProjectEndpoint(sql.ModelBase, sql.ModelDictMixin): + """project-endpoint relationship table.""" + + __tablename__ = 'project_endpoint' + attributes = ['endpoint_id', 'project_id'] + endpoint_id = sql.Column(sql.String(64), + primary_key=True, + nullable=False) + project_id = sql.Column(sql.String(64), + primary_key=True, + nullable=False) + + +class EndpointGroup(sql.ModelBase, sql.ModelDictMixin): + """Endpoint Groups table.""" + + __tablename__ = 'endpoint_group' + attributes = ['id', 'name', 'description', 'filters'] + mutable_attributes = frozenset(['name', 'description', 'filters']) + id = sql.Column(sql.String(64), primary_key=True) + name = sql.Column(sql.String(255), nullable=False) + description = sql.Column(sql.Text, nullable=True) + filters = sql.Column(sql.JsonBlob(), nullable=False) + + +class ProjectEndpointGroupMembership(sql.ModelBase, sql.ModelDictMixin): + """Project to Endpoint group relationship table.""" + + __tablename__ = 'project_endpoint_group' + attributes = ['endpoint_group_id', 'project_id'] + endpoint_group_id = sql.Column(sql.String(64), + sql.ForeignKey('endpoint_group.id'), + nullable=False) + project_id = sql.Column(sql.String(64), nullable=False) + __table_args__ = (sql.PrimaryKeyConstraint('endpoint_group_id', + 'project_id'), {}) diff --git a/keystone/catalog/controllers.py b/keystone/catalog/controllers.py index e14b268a6d..ec9da7d514 100644 --- a/keystone/catalog/controllers.py +++ b/keystone/catalog/controllers.py @@ -15,6 +15,8 @@ import uuid +import six + from keystone.catalog import core from keystone.catalog import schema from keystone.common import controller @@ -24,6 +26,7 @@ from keystone.common import wsgi from keystone import exception from keystone.i18n import _ from keystone import notifications +from keystone import resource INTERFACES = ['public', 'internal', 'admin'] @@ -379,3 +382,277 @@ class EndpointV3(controller.V3Controller): def delete_endpoint(self, context, endpoint_id): initiator = notifications._get_request_audit_info(context) return self.catalog_api.delete_endpoint(endpoint_id, initiator) + + +@dependency.requires('catalog_api', 'resource_api') +class _ControllerBase(controller.V3Controller): + """Base behaviors for endpoint filter controllers.""" + + 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.catalog_api.list_endpoint_groups_for_project( + project_id) + endpoint_groups = [self.catalog_api.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.catalog_api.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 + + +class EndpointFilterV3Controller(_ControllerBase): + + def __init__(self): + super(EndpointFilterV3Controller, self).__init__() + notifications.register_event_callback( + notifications.ACTIONS.deleted, 'project', + self._on_project_or_endpoint_delete) + notifications.register_event_callback( + notifications.ACTIONS.deleted, 'endpoint', + self._on_project_or_endpoint_delete) + + def _on_project_or_endpoint_delete(self, service, resource_type, operation, + payload): + project_or_endpoint_id = payload['resource_info'] + if resource_type == 'project': + self.catalog_api.delete_association_by_project( + project_or_endpoint_id) + else: + self.catalog_api.delete_association_by_endpoint( + project_or_endpoint_id) + + @controller.protected() + def add_endpoint_to_project(self, context, project_id, endpoint_id): + """Establishes an association between an endpoint and a project.""" + # NOTE(gyee): we just need to make sure endpoint and project exist + # first. We don't really care whether if project is disabled. + # The relationship can still be established even with a disabled + # project as there are no security implications. + self.catalog_api.get_endpoint(endpoint_id) + self.resource_api.get_project(project_id) + self.catalog_api.add_endpoint_to_project(endpoint_id, + project_id) + + @controller.protected() + def check_endpoint_in_project(self, context, project_id, endpoint_id): + """Verifies endpoint is currently associated with given project.""" + self.catalog_api.get_endpoint(endpoint_id) + self.resource_api.get_project(project_id) + self.catalog_api.check_endpoint_in_project(endpoint_id, + project_id) + + @controller.protected() + 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 + + return EndpointV3.wrap_collection( + context, [v for v in six.itervalues(filtered_endpoints)]) + + @controller.protected() + def remove_endpoint_from_project(self, context, project_id, endpoint_id): + """Remove the endpoint from the association with given project.""" + self.catalog_api.remove_endpoint_from_project(endpoint_id, + project_id) + + @controller.protected() + def list_projects_for_endpoint(self, context, endpoint_id): + """Return a list of projects associated with the endpoint.""" + self.catalog_api.get_endpoint(endpoint_id) + refs = self.catalog_api.list_projects_for_endpoint(endpoint_id) + + projects = [self.resource_api.get_project( + ref['project_id']) for ref in refs] + return resource.controllers.ProjectV3.wrap_collection(context, + projects) + + +class EndpointGroupV3Controller(_ControllerBase): + collection_name = 'endpoint_groups' + member_name = 'endpoint_group' + + VALID_FILTER_KEYS = ['service_id', 'region_id', 'interface'] + + def __init__(self): + super(EndpointGroupV3Controller, self).__init__() + + @classmethod + def base_url(cls, context, path=None): + """Construct a path and pass it to V3Controller.base_url method.""" + path = '/OS-EP-FILTER/' + cls.collection_name + return super(EndpointGroupV3Controller, cls).base_url(context, + path=path) + + @controller.protected() + @validation.validated(schema.endpoint_group_create, 'endpoint_group') + def create_endpoint_group(self, context, endpoint_group): + """Creates an Endpoint Group with the associated filters.""" + ref = self._assign_unique_id(self._normalize_dict(endpoint_group)) + self._require_attribute(ref, 'filters') + self._require_valid_filter(ref) + ref = self.catalog_api.create_endpoint_group(ref['id'], ref) + return EndpointGroupV3Controller.wrap_member(context, ref) + + def _require_valid_filter(self, endpoint_group): + filters = endpoint_group.get('filters') + for key in six.iterkeys(filters): + if key not in self.VALID_FILTER_KEYS: + raise exception.ValidationError( + attribute=self._valid_filter_keys(), + target='endpoint_group') + + def _valid_filter_keys(self): + return ' or '.join(self.VALID_FILTER_KEYS) + + @controller.protected() + def get_endpoint_group(self, context, endpoint_group_id): + """Retrieve the endpoint group associated with the id if exists.""" + ref = self.catalog_api.get_endpoint_group(endpoint_group_id) + return EndpointGroupV3Controller.wrap_member( + context, ref) + + @controller.protected() + @validation.validated(schema.endpoint_group_update, 'endpoint_group') + def update_endpoint_group(self, context, endpoint_group_id, + endpoint_group): + """Update fixed values and/or extend the filters.""" + if 'filters' in endpoint_group: + self._require_valid_filter(endpoint_group) + ref = self.catalog_api.update_endpoint_group(endpoint_group_id, + endpoint_group) + return EndpointGroupV3Controller.wrap_member( + context, ref) + + @controller.protected() + def delete_endpoint_group(self, context, endpoint_group_id): + """Delete endpoint_group.""" + self.catalog_api.delete_endpoint_group(endpoint_group_id) + + @controller.protected() + def list_endpoint_groups(self, context): + """List all endpoint groups.""" + refs = self.catalog_api.list_endpoint_groups() + return EndpointGroupV3Controller.wrap_collection( + context, refs) + + @controller.protected() + def list_endpoint_groups_for_project(self, context, project_id): + """List all endpoint groups associated with a given project.""" + return EndpointGroupV3Controller.wrap_collection( + context, self._get_endpoint_groups_for_project(project_id)) + + @controller.protected() + def list_projects_associated_with_endpoint_group(self, + context, + endpoint_group_id): + """List all projects associated with endpoint group.""" + endpoint_group_refs = (self.catalog_api. + list_projects_associated_with_endpoint_group( + endpoint_group_id)) + projects = [] + for endpoint_group_ref in endpoint_group_refs: + project = self.resource_api.get_project( + endpoint_group_ref['project_id']) + if project: + projects.append(project) + return resource.controllers.ProjectV3.wrap_collection(context, + projects) + + @controller.protected() + def list_endpoints_associated_with_endpoint_group(self, + context, + endpoint_group_id): + """List all the endpoints filtered by a specific endpoint group.""" + filtered_endpoints = self._get_endpoints_filtered_by_endpoint_group( + endpoint_group_id) + return EndpointV3.wrap_collection(context, filtered_endpoints) + + +class ProjectEndpointGroupV3Controller(_ControllerBase): + collection_name = 'project_endpoint_groups' + member_name = 'project_endpoint_group' + + def __init__(self): + super(ProjectEndpointGroupV3Controller, self).__init__() + notifications.register_event_callback( + notifications.ACTIONS.deleted, 'project', + self._on_project_delete) + + def _on_project_delete(self, service, resource_type, + operation, payload): + project_id = payload['resource_info'] + (self.catalog_api. + delete_endpoint_group_association_by_project( + project_id)) + + @controller.protected() + def get_endpoint_group_in_project(self, context, endpoint_group_id, + project_id): + """Retrieve the endpoint group associated with the id if exists.""" + self.resource_api.get_project(project_id) + self.catalog_api.get_endpoint_group(endpoint_group_id) + ref = self.catalog_api.get_endpoint_group_in_project( + endpoint_group_id, project_id) + return ProjectEndpointGroupV3Controller.wrap_member( + context, ref) + + @controller.protected() + def add_endpoint_group_to_project(self, context, endpoint_group_id, + project_id): + """Creates an association between an endpoint group and project.""" + self.resource_api.get_project(project_id) + self.catalog_api.get_endpoint_group(endpoint_group_id) + self.catalog_api.add_endpoint_group_to_project( + endpoint_group_id, project_id) + + @controller.protected() + def remove_endpoint_group_from_project(self, context, endpoint_group_id, + project_id): + """Remove the endpoint group from associated project.""" + self.resource_api.get_project(project_id) + self.catalog_api.get_endpoint_group(endpoint_group_id) + self.catalog_api.remove_endpoint_group_from_project( + endpoint_group_id, project_id) + + @classmethod + def _add_self_referential_link(cls, context, ref): + url = ('/OS-EP-FILTER/endpoint_groups/%(endpoint_group_id)s' + '/projects/%(project_id)s' % { + 'endpoint_group_id': ref['endpoint_group_id'], + 'project_id': ref['project_id']}) + ref.setdefault('links', {}) + ref['links']['self'] = url diff --git a/keystone/catalog/core.py b/keystone/catalog/core.py index 3dea98623d..de0c9a96b2 100644 --- a/keystone/catalog/core.py +++ b/keystone/catalog/core.py @@ -302,6 +302,25 @@ class Manager(manager.Manager): def get_v3_catalog(self, user_id, tenant_id): return self.driver.get_v3_catalog(user_id, tenant_id) + def add_endpoint_to_project(self, endpoint_id, project_id): + self.driver.add_endpoint_to_project(endpoint_id, project_id) + COMPUTED_CATALOG_REGION.invalidate() + + def remove_endpoint_from_project(self, endpoint_id, project_id): + self.driver.remove_endpoint_from_project(endpoint_id, project_id) + COMPUTED_CATALOG_REGION.invalidate() + + def add_endpoint_group_to_project(self, endpoint_group_id, project_id): + self.driver.add_endpoint_group_to_project( + endpoint_group_id, project_id) + COMPUTED_CATALOG_REGION.invalidate() + + def remove_endpoint_group_from_project(self, endpoint_group_id, + project_id): + self.driver.remove_endpoint_group_from_project( + endpoint_group_id, project_id) + COMPUTED_CATALOG_REGION.invalidate() + @six.add_metaclass(abc.ABCMeta) class CatalogDriverV8(object): @@ -570,5 +589,228 @@ class CatalogDriverV8(object): return v3_catalog + @abc.abstractmethod + def add_endpoint_to_project(self, endpoint_id, project_id): + """Create an endpoint to project association. + + :param endpoint_id: identity of endpoint to associate + :type endpoint_id: string + :param project_id: identity of the project to be associated with + :type project_id: string + :raises: keystone.exception.Conflict, + :returns: None. + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def remove_endpoint_from_project(self, endpoint_id, project_id): + """Removes an endpoint to project association. + + :param endpoint_id: identity of endpoint to remove + :type endpoint_id: string + :param project_id: identity of the project associated with + :type project_id: string + :raises: exception.NotFound + :returns: None. + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def check_endpoint_in_project(self, endpoint_id, project_id): + """Checks if an endpoint is associated with a project. + + :param endpoint_id: identity of endpoint to check + :type endpoint_id: string + :param project_id: identity of the project associated with + :type project_id: string + :raises: exception.NotFound + :returns: None. + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def list_endpoints_for_project(self, project_id): + """List all endpoints associated with a project. + + :param project_id: identity of the project to check + :type project_id: string + :returns: a list of identity endpoint ids or an empty list. + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def list_projects_for_endpoint(self, endpoint_id): + """List all projects associated with an endpoint. + + :param endpoint_id: identity of endpoint to check + :type endpoint_id: string + :returns: a list of projects or an empty list. + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def delete_association_by_endpoint(self, endpoint_id): + """Removes all the endpoints to project association with endpoint. + + :param endpoint_id: identity of endpoint to check + :type endpoint_id: string + :returns: None + + """ + raise exception.NotImplemented() + + @abc.abstractmethod + def delete_association_by_project(self, project_id): + """Removes all the endpoints to project association with project. + + :param project_id: identity of the project to check + :type project_id: string + :returns: None + + """ + raise exception.NotImplemented() + + @abc.abstractmethod + def create_endpoint_group(self, endpoint_group): + """Create an endpoint group. + + :param endpoint_group: endpoint group to create + :type endpoint_group: dictionary + :raises: keystone.exception.Conflict, + :returns: an endpoint group representation. + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def get_endpoint_group(self, endpoint_group_id): + """Get an endpoint group. + + :param endpoint_group_id: identity of endpoint group to retrieve + :type endpoint_group_id: string + :raises: exception.NotFound + :returns: an endpoint group representation. + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def update_endpoint_group(self, endpoint_group_id, endpoint_group): + """Update an endpoint group. + + :param endpoint_group_id: identity of endpoint group to retrieve + :type endpoint_group_id: string + :param endpoint_group: A full or partial endpoint_group + :type endpoint_group: dictionary + :raises: exception.NotFound + :returns: an endpoint group representation. + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def delete_endpoint_group(self, endpoint_group_id): + """Delete an endpoint group. + + :param endpoint_group_id: identity of endpoint group to delete + :type endpoint_group_id: string + :raises: exception.NotFound + :returns: None. + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def add_endpoint_group_to_project(self, endpoint_group_id, project_id): + """Adds an endpoint group to project association. + + :param endpoint_group_id: identity of endpoint to associate + :type endpoint_group_id: string + :param project_id: identity of project to associate + :type project_id: string + :raises: keystone.exception.Conflict, + :returns: None. + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def get_endpoint_group_in_project(self, endpoint_group_id, project_id): + """Get endpoint group to project association. + + :param endpoint_group_id: identity of endpoint group to retrieve + :type endpoint_group_id: string + :param project_id: identity of project to associate + :type project_id: string + :raises: exception.NotFound + :returns: a project endpoint group representation. + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def list_endpoint_groups(self): + """List all endpoint groups. + + :raises: exception.NotFound + :returns: None. + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def list_endpoint_groups_for_project(self, project_id): + """List all endpoint group to project associations for a project. + + :param project_id: identity of project to associate + :type project_id: string + :raises: exception.NotFound + :returns: None. + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def list_projects_associated_with_endpoint_group(self, endpoint_group_id): + """List all projects associated with endpoint group. + + :param endpoint_group_id: identity of endpoint to associate + :type endpoint_group_id: string + :raises: exception.NotFound + :returns: None. + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def remove_endpoint_group_from_project(self, endpoint_group_id, + project_id): + """Remove an endpoint to project association. + + :param endpoint_group_id: identity of endpoint to associate + :type endpoint_group_id: string + :param project_id: identity of project to associate + :type project_id: string + :raises: exception.NotFound + :returns: None. + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def delete_endpoint_group_association_by_project(self, project_id): + """Remove endpoint group to project associations. + + :param project_id: identity of the project to check + :type project_id: string + :returns: None + + """ + raise exception.NotImplemented() # pragma: no cover Driver = manager.create_legacy_driver(CatalogDriverV8) diff --git a/keystone/catalog/routers.py b/keystone/catalog/routers.py index f3bd988bf9..8c6e96f01c 100644 --- a/keystone/catalog/routers.py +++ b/keystone/catalog/routers.py @@ -12,15 +12,72 @@ # License for the specific language governing permissions and limitations # under the License. +import functools + from keystone.catalog import controllers +from keystone.common import json_home from keystone.common import router from keystone.common import wsgi +build_resource_relation = functools.partial( + json_home.build_v3_extension_resource_relation, + extension_name='OS-EP-FILTER', extension_version='1.0') + +build_parameter_relation = functools.partial( + json_home.build_v3_extension_parameter_relation, + extension_name='OS-EP-FILTER', extension_version='1.0') + +ENDPOINT_GROUP_PARAMETER_RELATION = build_parameter_relation( + parameter_name='endpoint_group_id') + + class Routers(wsgi.RoutersBase): + """API for the keystone catalog. + + The API Endpoint Filter looks like:: + + PUT /OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id} + GET /OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id} + HEAD /OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id} + DELETE /OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id} + GET /OS-EP-FILTER/endpoints/{endpoint_id}/projects + GET /OS-EP-FILTER/projects/{project_id}/endpoints + GET /OS-EP-FILTER/projects/{project_id}/endpoint_groups + + GET /OS-EP-FILTER/endpoint_groups + POST /OS-EP-FILTER/endpoint_groups + GET /OS-EP-FILTER/endpoint_groups/{endpoint_group_id} + HEAD /OS-EP-FILTER/endpoint_groups/{endpoint_group_id} + PATCH /OS-EP-FILTER/endpoint_groups/{endpoint_group_id} + DELETE /OS-EP-FILTER/endpoint_groups/{endpoint_group_id} + + GET /OS-EP-FILTER/endpoint_groups/{endpoint_group_id}/projects + GET /OS-EP-FILTER/endpoint_groups/{endpoint_group_id}/endpoints + + PUT /OS-EP-FILTER/endpoint_groups/{endpoint_group}/projects/ + {project_id} + GET /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} + + """ + + PATH_PREFIX = '/OS-EP-FILTER' + PATH_PROJECT_ENDPOINT = '/projects/{project_id}/endpoints/{endpoint_id}' + PATH_ENDPOINT_GROUPS = '/endpoint_groups/{endpoint_group_id}' + PATH_ENDPOINT_GROUP_PROJECTS = PATH_ENDPOINT_GROUPS + ( + '/projects/{project_id}') def append_v3_routers(self, mapper, routers): regions_controller = controllers.RegionV3() + endpoint_filter_controller = controllers.EndpointFilterV3Controller() + endpoint_group_controller = controllers.EndpointGroupV3Controller() + project_endpoint_group_controller = ( + controllers.ProjectEndpointGroupV3Controller()) routers.append(router.Router(regions_controller, 'regions', 'region', resource_descriptions=self.v3_resources)) @@ -38,3 +95,88 @@ class Routers(wsgi.RoutersBase): routers.append(router.Router(controllers.EndpointV3(), 'endpoints', 'endpoint', resource_descriptions=self.v3_resources)) + + self._add_resource( + mapper, endpoint_filter_controller, + path=self.PATH_PREFIX + '/endpoints/{endpoint_id}/projects', + get_action='list_projects_for_endpoint', + rel=build_resource_relation(resource_name='endpoint_projects'), + path_vars={ + 'endpoint_id': json_home.Parameters.ENDPOINT_ID, + }) + self._add_resource( + mapper, endpoint_filter_controller, + path=self.PATH_PREFIX + self.PATH_PROJECT_ENDPOINT, + get_head_action='check_endpoint_in_project', + put_action='add_endpoint_to_project', + delete_action='remove_endpoint_from_project', + rel=build_resource_relation(resource_name='project_endpoint'), + path_vars={ + 'endpoint_id': json_home.Parameters.ENDPOINT_ID, + 'project_id': json_home.Parameters.PROJECT_ID, + }) + self._add_resource( + mapper, endpoint_filter_controller, + path=self.PATH_PREFIX + '/projects/{project_id}/endpoints', + get_action='list_endpoints_for_project', + rel=build_resource_relation(resource_name='project_endpoints'), + path_vars={ + 'project_id': json_home.Parameters.PROJECT_ID, + }) + self._add_resource( + mapper, endpoint_group_controller, + path=self.PATH_PREFIX + '/projects/{project_id}/endpoint_groups', + get_action='list_endpoint_groups_for_project', + rel=build_resource_relation( + resource_name='project_endpoint_groups'), + path_vars={ + 'project_id': json_home.Parameters.PROJECT_ID, + }) + self._add_resource( + mapper, endpoint_group_controller, + path=self.PATH_PREFIX + '/endpoint_groups', + get_action='list_endpoint_groups', + post_action='create_endpoint_group', + rel=build_resource_relation(resource_name='endpoint_groups')) + self._add_resource( + mapper, endpoint_group_controller, + path=self.PATH_PREFIX + self.PATH_ENDPOINT_GROUPS, + get_head_action='get_endpoint_group', + patch_action='update_endpoint_group', + delete_action='delete_endpoint_group', + rel=build_resource_relation(resource_name='endpoint_group'), + path_vars={ + 'endpoint_group_id': ENDPOINT_GROUP_PARAMETER_RELATION + }) + self._add_resource( + mapper, project_endpoint_group_controller, + path=self.PATH_PREFIX + self.PATH_ENDPOINT_GROUP_PROJECTS, + get_head_action='get_endpoint_group_in_project', + put_action='add_endpoint_group_to_project', + delete_action='remove_endpoint_group_from_project', + rel=build_resource_relation( + resource_name='endpoint_group_to_project_association'), + path_vars={ + 'project_id': json_home.Parameters.PROJECT_ID, + 'endpoint_group_id': ENDPOINT_GROUP_PARAMETER_RELATION + }) + self._add_resource( + mapper, endpoint_group_controller, + path=self.PATH_PREFIX + self.PATH_ENDPOINT_GROUPS + ( + '/projects'), + get_action='list_projects_associated_with_endpoint_group', + rel=build_resource_relation( + resource_name='projects_associated_with_endpoint_group'), + path_vars={ + 'endpoint_group_id': ENDPOINT_GROUP_PARAMETER_RELATION + }) + self._add_resource( + mapper, endpoint_group_controller, + path=self.PATH_PREFIX + self.PATH_ENDPOINT_GROUPS + ( + '/endpoints'), + get_action='list_endpoints_associated_with_endpoint_group', + rel=build_resource_relation( + resource_name='endpoints_in_endpoint_group'), + path_vars={ + 'endpoint_group_id': ENDPOINT_GROUP_PARAMETER_RELATION + }) diff --git a/keystone/catalog/schema.py b/keystone/catalog/schema.py index 671f1233f5..b9643131e5 100644 --- a/keystone/catalog/schema.py +++ b/keystone/catalog/schema.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +from keystone.common import validation from keystone.common.validation import parameter_types @@ -96,3 +97,23 @@ endpoint_update = { 'minProperties': 1, 'additionalProperties': True } + +_endpoint_group_properties = { + 'description': validation.nullable(parameter_types.description), + 'filters': { + 'type': 'object' + }, + 'name': parameter_types.name +} + +endpoint_group_create = { + 'type': 'object', + 'properties': _endpoint_group_properties, + 'required': ['name', 'filters'] +} + +endpoint_group_update = { + 'type': 'object', + 'properties': _endpoint_group_properties, + 'minProperties': 1 +} diff --git a/keystone/common/config.py b/keystone/common/config.py index 0f5cf8cf26..6c2445a1a8 100644 --- a/keystone/common/config.py +++ b/keystone/common/config.py @@ -472,6 +472,9 @@ FILE_OPTIONS = { 'in a policy collection.'), ], 'endpoint_filter': [ + cfg.BoolOpt('enabled', + default=True, + help='Enable endpoint filtering functionality.'), cfg.StrOpt('driver', default='sql', help='Entrypoint for the endpoint filter backend driver in ' diff --git a/keystone/contrib/endpoint_filter/__init__.py b/keystone/contrib/endpoint_filter/__init__.py index 72508c3e01..e69de29bb2 100644 --- a/keystone/contrib/endpoint_filter/__init__.py +++ b/keystone/contrib/endpoint_filter/__init__.py @@ -1,15 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# -# 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 keystone.contrib.endpoint_filter.core import * # noqa diff --git a/keystone/contrib/endpoint_filter/backends/catalog_sql.py b/keystone/contrib/endpoint_filter/backends/catalog_sql.py index 22d5796a09..1f3daa7cc5 100644 --- a/keystone/contrib/endpoint_filter/backends/catalog_sql.py +++ b/keystone/contrib/endpoint_filter/backends/catalog_sql.py @@ -23,7 +23,7 @@ from keystone import exception CONF = cfg.CONF -@dependency.requires('endpoint_filter_api') +@dependency.requires('catalog_api') class EndpointFilterCatalog(sql.Catalog): def get_v3_catalog(self, user_id, project_id): substitutions = dict(CONF.items()) @@ -31,7 +31,7 @@ class EndpointFilterCatalog(sql.Catalog): services = {} - refs = self.endpoint_filter_api.list_endpoints_for_project(project_id) + refs = self.catalog_api.list_endpoints_for_project(project_id) if (not refs and CONF.endpoint_filter.return_all_endpoints_if_no_filter): @@ -61,7 +61,7 @@ class EndpointFilterCatalog(sql.Catalog): service['endpoints'] = [endpoint] except exception.EndpointNotFound: # remove bad reference from association - self.endpoint_filter_api.remove_endpoint_from_project( + self.catalog_api.remove_endpoint_from_project( entry['endpoint_id'], project_id) # format catalog diff --git a/keystone/contrib/endpoint_filter/backends/sql.py b/keystone/contrib/endpoint_filter/backends/sql.py index 6926101a18..484934bb07 100644 --- a/keystone/contrib/endpoint_filter/backends/sql.py +++ b/keystone/contrib/endpoint_filter/backends/sql.py @@ -12,217 +12,19 @@ # License for the specific language governing permissions and limitations # under the License. -from keystone.common import sql -from keystone.contrib import endpoint_filter -from keystone import exception -from keystone.i18n import _ +from oslo_log import versionutils + +from keystone.catalog.backends import sql + +_OLD = 'keystone.contrib.endpoint_filter.backends.sql.EndpointFilter' +_NEW = 'sql' -class ProjectEndpoint(sql.ModelBase, sql.ModelDictMixin): - """project-endpoint relationship table.""" - - __tablename__ = 'project_endpoint' - attributes = ['endpoint_id', 'project_id'] - endpoint_id = sql.Column(sql.String(64), - primary_key=True, - nullable=False) - project_id = sql.Column(sql.String(64), - primary_key=True, - nullable=False) - - -class EndpointGroup(sql.ModelBase, sql.ModelDictMixin): - """Endpoint Groups table.""" - - __tablename__ = 'endpoint_group' - attributes = ['id', 'name', 'description', 'filters'] - mutable_attributes = frozenset(['name', 'description', 'filters']) - id = sql.Column(sql.String(64), primary_key=True) - name = sql.Column(sql.String(255), nullable=False) - description = sql.Column(sql.Text, nullable=True) - filters = sql.Column(sql.JsonBlob(), nullable=False) - - -class ProjectEndpointGroupMembership(sql.ModelBase, sql.ModelDictMixin): - """Project to Endpoint group relationship table.""" - - __tablename__ = 'project_endpoint_group' - attributes = ['endpoint_group_id', 'project_id'] - endpoint_group_id = sql.Column(sql.String(64), - sql.ForeignKey('endpoint_group.id'), - nullable=False) - project_id = sql.Column(sql.String(64), nullable=False) - __table_args__ = (sql.PrimaryKeyConstraint('endpoint_group_id', - 'project_id'), {}) - - -class EndpointFilter(endpoint_filter.EndpointFilterDriverV8): - - @sql.handle_conflicts(conflict_type='project_endpoint') - def add_endpoint_to_project(self, endpoint_id, project_id): - session = sql.get_session() - with session.begin(): - endpoint_filter_ref = ProjectEndpoint(endpoint_id=endpoint_id, - project_id=project_id) - session.add(endpoint_filter_ref) - - def _get_project_endpoint_ref(self, session, endpoint_id, project_id): - endpoint_filter_ref = session.query(ProjectEndpoint).get( - (endpoint_id, project_id)) - if endpoint_filter_ref is None: - msg = _('Endpoint %(endpoint_id)s not found in project ' - '%(project_id)s') % {'endpoint_id': endpoint_id, - 'project_id': project_id} - raise exception.NotFound(msg) - return endpoint_filter_ref - - def check_endpoint_in_project(self, endpoint_id, project_id): - session = sql.get_session() - self._get_project_endpoint_ref(session, endpoint_id, project_id) - - def remove_endpoint_from_project(self, endpoint_id, project_id): - session = sql.get_session() - endpoint_filter_ref = self._get_project_endpoint_ref( - session, endpoint_id, project_id) - with session.begin(): - session.delete(endpoint_filter_ref) - - def list_endpoints_for_project(self, project_id): - session = sql.get_session() - query = session.query(ProjectEndpoint) - query = query.filter_by(project_id=project_id) - endpoint_filter_refs = query.all() - return [ref.to_dict() for ref in endpoint_filter_refs] - - def list_projects_for_endpoint(self, endpoint_id): - session = sql.get_session() - query = session.query(ProjectEndpoint) - query = query.filter_by(endpoint_id=endpoint_id) - endpoint_filter_refs = query.all() - return [ref.to_dict() for ref in endpoint_filter_refs] - - def delete_association_by_endpoint(self, endpoint_id): - session = sql.get_session() - with session.begin(): - query = session.query(ProjectEndpoint) - query = query.filter_by(endpoint_id=endpoint_id) - query.delete(synchronize_session=False) - - def delete_association_by_project(self, project_id): - session = sql.get_session() - with session.begin(): - query = session.query(ProjectEndpoint) - query = query.filter_by(project_id=project_id) - query.delete(synchronize_session=False) - - def create_endpoint_group(self, endpoint_group_id, endpoint_group): - session = sql.get_session() - with session.begin(): - endpoint_group_ref = EndpointGroup.from_dict(endpoint_group) - session.add(endpoint_group_ref) - return endpoint_group_ref.to_dict() - - def _get_endpoint_group(self, session, endpoint_group_id): - endpoint_group_ref = session.query(EndpointGroup).get( - endpoint_group_id) - if endpoint_group_ref is None: - raise exception.EndpointGroupNotFound( - endpoint_group_id=endpoint_group_id) - return endpoint_group_ref - - def get_endpoint_group(self, endpoint_group_id): - session = sql.get_session() - endpoint_group_ref = self._get_endpoint_group(session, - endpoint_group_id) - return endpoint_group_ref.to_dict() - - def update_endpoint_group(self, endpoint_group_id, endpoint_group): - session = sql.get_session() - with session.begin(): - endpoint_group_ref = self._get_endpoint_group(session, - endpoint_group_id) - old_endpoint_group = endpoint_group_ref.to_dict() - old_endpoint_group.update(endpoint_group) - new_endpoint_group = EndpointGroup.from_dict(old_endpoint_group) - for attr in EndpointGroup.mutable_attributes: - setattr(endpoint_group_ref, attr, - getattr(new_endpoint_group, attr)) - return endpoint_group_ref.to_dict() - - def delete_endpoint_group(self, endpoint_group_id): - session = sql.get_session() - endpoint_group_ref = self._get_endpoint_group(session, - endpoint_group_id) - with session.begin(): - self._delete_endpoint_group_association_by_endpoint_group( - session, endpoint_group_id) - session.delete(endpoint_group_ref) - - def get_endpoint_group_in_project(self, endpoint_group_id, project_id): - session = sql.get_session() - ref = self._get_endpoint_group_in_project(session, - endpoint_group_id, - project_id) - return ref.to_dict() - - @sql.handle_conflicts(conflict_type='project_endpoint_group') - def add_endpoint_group_to_project(self, endpoint_group_id, project_id): - session = sql.get_session() - - with session.begin(): - # Create a new Project Endpoint group entity - endpoint_group_project_ref = ProjectEndpointGroupMembership( - endpoint_group_id=endpoint_group_id, project_id=project_id) - session.add(endpoint_group_project_ref) - - def _get_endpoint_group_in_project(self, session, - endpoint_group_id, project_id): - endpoint_group_project_ref = session.query( - ProjectEndpointGroupMembership).get((endpoint_group_id, - project_id)) - if endpoint_group_project_ref is None: - msg = _('Endpoint Group Project Association not found') - raise exception.NotFound(msg) - else: - return endpoint_group_project_ref - - def list_endpoint_groups(self): - session = sql.get_session() - query = session.query(EndpointGroup) - endpoint_group_refs = query.all() - return [e.to_dict() for e in endpoint_group_refs] - - def list_endpoint_groups_for_project(self, project_id): - session = sql.get_session() - query = session.query(ProjectEndpointGroupMembership) - query = query.filter_by(project_id=project_id) - endpoint_group_refs = query.all() - return [ref.to_dict() for ref in endpoint_group_refs] - - def remove_endpoint_group_from_project(self, endpoint_group_id, - project_id): - session = sql.get_session() - endpoint_group_project_ref = self._get_endpoint_group_in_project( - session, endpoint_group_id, project_id) - with session.begin(): - session.delete(endpoint_group_project_ref) - - def list_projects_associated_with_endpoint_group(self, endpoint_group_id): - session = sql.get_session() - query = session.query(ProjectEndpointGroupMembership) - query = query.filter_by(endpoint_group_id=endpoint_group_id) - endpoint_group_refs = query.all() - return [ref.to_dict() for ref in endpoint_group_refs] - - def _delete_endpoint_group_association_by_endpoint_group( - self, session, endpoint_group_id): - query = session.query(ProjectEndpointGroupMembership) - query = query.filter_by(endpoint_group_id=endpoint_group_id) - query.delete() - - def delete_endpoint_group_association_by_project(self, project_id): - session = sql.get_session() - with session.begin(): - query = session.query(ProjectEndpointGroupMembership) - query = query.filter_by(project_id=project_id) - query.delete() +class EndpointFilter(sql.Catalog): + @versionutils.deprecated( + as_of=versionutils.deprecated.MITAKA, + in_favor_of=_NEW, + what=_OLD, + remove_in=2) + def __init__(self, *args, **kwargs): + super(EndpointFilter, self).__init__(*args, **kwargs) diff --git a/keystone/contrib/endpoint_filter/controllers.py b/keystone/contrib/endpoint_filter/controllers.py deleted file mode 100644 index f157b0ebfa..0000000000 --- a/keystone/contrib/endpoint_filter/controllers.py +++ /dev/null @@ -1,299 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# -# 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. - -import six - -from keystone.catalog import controllers as catalog_controllers -from keystone.common import controller -from keystone.common import dependency -from keystone.common import validation -from keystone.contrib.endpoint_filter import schema -from keystone import exception -from keystone import notifications -from keystone import resource - - -@dependency.requires('catalog_api', 'endpoint_filter_api', 'resource_api') -class _ControllerBase(controller.V3Controller): - """Base behaviors for endpoint filter controllers.""" - - 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.endpoint_filter_api.list_endpoint_groups_for_project( - project_id) - endpoint_groups = [self.endpoint_filter_api.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.endpoint_filter_api.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 - - -class EndpointFilterV3Controller(_ControllerBase): - - def __init__(self): - super(EndpointFilterV3Controller, self).__init__() - notifications.register_event_callback( - notifications.ACTIONS.deleted, 'project', - self._on_project_or_endpoint_delete) - notifications.register_event_callback( - notifications.ACTIONS.deleted, 'endpoint', - self._on_project_or_endpoint_delete) - - def _on_project_or_endpoint_delete(self, service, resource_type, operation, - payload): - project_or_endpoint_id = payload['resource_info'] - if resource_type == 'project': - self.endpoint_filter_api.delete_association_by_project( - project_or_endpoint_id) - else: - self.endpoint_filter_api.delete_association_by_endpoint( - project_or_endpoint_id) - - @controller.protected() - def add_endpoint_to_project(self, context, project_id, endpoint_id): - """Establishes an association between an endpoint and a project.""" - # NOTE(gyee): we just need to make sure endpoint and project exist - # first. We don't really care whether if project is disabled. - # The relationship can still be established even with a disabled - # project as there are no security implications. - self.catalog_api.get_endpoint(endpoint_id) - self.resource_api.get_project(project_id) - self.endpoint_filter_api.add_endpoint_to_project(endpoint_id, - project_id) - - @controller.protected() - def check_endpoint_in_project(self, context, project_id, endpoint_id): - """Verifies endpoint is currently associated with given project.""" - self.catalog_api.get_endpoint(endpoint_id) - self.resource_api.get_project(project_id) - self.endpoint_filter_api.check_endpoint_in_project(endpoint_id, - project_id) - - @controller.protected() - 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 - - return catalog_controllers.EndpointV3.wrap_collection( - context, [v for v in six.itervalues(filtered_endpoints)]) - - @controller.protected() - def remove_endpoint_from_project(self, context, project_id, endpoint_id): - """Remove the endpoint from the association with given project.""" - self.endpoint_filter_api.remove_endpoint_from_project(endpoint_id, - project_id) - - @controller.protected() - def list_projects_for_endpoint(self, context, endpoint_id): - """Return a list of projects associated with the endpoint.""" - self.catalog_api.get_endpoint(endpoint_id) - refs = self.endpoint_filter_api.list_projects_for_endpoint(endpoint_id) - - projects = [self.resource_api.get_project( - ref['project_id']) for ref in refs] - return resource.controllers.ProjectV3.wrap_collection(context, - projects) - - -class EndpointGroupV3Controller(_ControllerBase): - collection_name = 'endpoint_groups' - member_name = 'endpoint_group' - - VALID_FILTER_KEYS = ['service_id', 'region_id', 'interface'] - - def __init__(self): - super(EndpointGroupV3Controller, self).__init__() - - @classmethod - def base_url(cls, context, path=None): - """Construct a path and pass it to V3Controller.base_url method.""" - path = '/OS-EP-FILTER/' + cls.collection_name - return super(EndpointGroupV3Controller, cls).base_url(context, - path=path) - - @controller.protected() - @validation.validated(schema.endpoint_group_create, 'endpoint_group') - def create_endpoint_group(self, context, endpoint_group): - """Creates an Endpoint Group with the associated filters.""" - ref = self._assign_unique_id(self._normalize_dict(endpoint_group)) - self._require_attribute(ref, 'filters') - self._require_valid_filter(ref) - ref = self.endpoint_filter_api.create_endpoint_group(ref['id'], ref) - return EndpointGroupV3Controller.wrap_member(context, ref) - - def _require_valid_filter(self, endpoint_group): - filters = endpoint_group.get('filters') - for key in six.iterkeys(filters): - if key not in self.VALID_FILTER_KEYS: - raise exception.ValidationError( - attribute=self._valid_filter_keys(), - target='endpoint_group') - - def _valid_filter_keys(self): - return ' or '.join(self.VALID_FILTER_KEYS) - - @controller.protected() - def get_endpoint_group(self, context, endpoint_group_id): - """Retrieve the endpoint group associated with the id if exists.""" - ref = self.endpoint_filter_api.get_endpoint_group(endpoint_group_id) - return EndpointGroupV3Controller.wrap_member( - context, ref) - - @controller.protected() - @validation.validated(schema.endpoint_group_update, 'endpoint_group') - def update_endpoint_group(self, context, endpoint_group_id, - endpoint_group): - """Update fixed values and/or extend the filters.""" - if 'filters' in endpoint_group: - self._require_valid_filter(endpoint_group) - ref = self.endpoint_filter_api.update_endpoint_group(endpoint_group_id, - endpoint_group) - return EndpointGroupV3Controller.wrap_member( - context, ref) - - @controller.protected() - def delete_endpoint_group(self, context, endpoint_group_id): - """Delete endpoint_group.""" - self.endpoint_filter_api.delete_endpoint_group(endpoint_group_id) - - @controller.protected() - def list_endpoint_groups(self, context): - """List all endpoint groups.""" - refs = self.endpoint_filter_api.list_endpoint_groups() - return EndpointGroupV3Controller.wrap_collection( - context, refs) - - @controller.protected() - def list_endpoint_groups_for_project(self, context, project_id): - """List all endpoint groups associated with a given project.""" - return EndpointGroupV3Controller.wrap_collection( - context, self._get_endpoint_groups_for_project(project_id)) - - @controller.protected() - def list_projects_associated_with_endpoint_group(self, - context, - endpoint_group_id): - """List all projects associated with endpoint group.""" - endpoint_group_refs = (self.endpoint_filter_api. - list_projects_associated_with_endpoint_group( - endpoint_group_id)) - projects = [] - for endpoint_group_ref in endpoint_group_refs: - project = self.resource_api.get_project( - endpoint_group_ref['project_id']) - if project: - projects.append(project) - return resource.controllers.ProjectV3.wrap_collection(context, - projects) - - @controller.protected() - def list_endpoints_associated_with_endpoint_group(self, - context, - endpoint_group_id): - """List all the endpoints filtered by a specific endpoint group.""" - filtered_endpoints = self._get_endpoints_filtered_by_endpoint_group( - endpoint_group_id) - return catalog_controllers.EndpointV3.wrap_collection( - context, filtered_endpoints) - - -class ProjectEndpointGroupV3Controller(_ControllerBase): - collection_name = 'project_endpoint_groups' - member_name = 'project_endpoint_group' - - def __init__(self): - super(ProjectEndpointGroupV3Controller, self).__init__() - notifications.register_event_callback( - notifications.ACTIONS.deleted, 'project', - self._on_project_delete) - - def _on_project_delete(self, service, resource_type, - operation, payload): - project_id = payload['resource_info'] - (self.endpoint_filter_api. - delete_endpoint_group_association_by_project( - project_id)) - - @controller.protected() - def get_endpoint_group_in_project(self, context, endpoint_group_id, - project_id): - """Retrieve the endpoint group associated with the id if exists.""" - self.resource_api.get_project(project_id) - self.endpoint_filter_api.get_endpoint_group(endpoint_group_id) - ref = self.endpoint_filter_api.get_endpoint_group_in_project( - endpoint_group_id, project_id) - return ProjectEndpointGroupV3Controller.wrap_member( - context, ref) - - @controller.protected() - def add_endpoint_group_to_project(self, context, endpoint_group_id, - project_id): - """Creates an association between an endpoint group and project.""" - self.resource_api.get_project(project_id) - self.endpoint_filter_api.get_endpoint_group(endpoint_group_id) - self.endpoint_filter_api.add_endpoint_group_to_project( - endpoint_group_id, project_id) - - @controller.protected() - def remove_endpoint_group_from_project(self, context, endpoint_group_id, - project_id): - """Remove the endpoint group from associated project.""" - self.resource_api.get_project(project_id) - self.endpoint_filter_api.get_endpoint_group(endpoint_group_id) - self.endpoint_filter_api.remove_endpoint_group_from_project( - endpoint_group_id, project_id) - - @classmethod - def _add_self_referential_link(cls, context, ref): - url = ('/OS-EP-FILTER/endpoint_groups/%(endpoint_group_id)s' - '/projects/%(project_id)s' % { - 'endpoint_group_id': ref['endpoint_group_id'], - 'project_id': ref['project_id']}) - ref.setdefault('links', {}) - ref['links']['self'] = url diff --git a/keystone/contrib/endpoint_filter/routers.py b/keystone/contrib/endpoint_filter/routers.py index d5c4387870..f75110f9f4 100644 --- a/keystone/contrib/endpoint_filter/routers.py +++ b/keystone/contrib/endpoint_filter/routers.py @@ -12,152 +12,22 @@ # License for the specific language governing permissions and limitations # under the License. -import functools +from oslo_log import log +from oslo_log import versionutils -from keystone.common import json_home from keystone.common import wsgi -from keystone.contrib.endpoint_filter import controllers +from keystone.i18n import _ -build_resource_relation = functools.partial( - json_home.build_v3_extension_resource_relation, - extension_name='OS-EP-FILTER', extension_version='1.0') - -build_parameter_relation = functools.partial( - json_home.build_v3_extension_parameter_relation, - extension_name='OS-EP-FILTER', extension_version='1.0') - -ENDPOINT_GROUP_PARAMETER_RELATION = build_parameter_relation( - parameter_name='endpoint_group_id') +LOG = log.getLogger(__name__) -class EndpointFilterExtension(wsgi.V3ExtensionRouter): - """API Endpoints for the Endpoint Filter extension. +class EndpointFilterExtension(wsgi.Middleware): - The API looks like:: - - PUT /OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id} - GET /OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id} - HEAD /OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id} - DELETE /OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id} - GET /OS-EP-FILTER/endpoints/{endpoint_id}/projects - GET /OS-EP-FILTER/projects/{project_id}/endpoints - GET /OS-EP-FILTER/projects/{project_id}/endpoint_groups - - GET /OS-EP-FILTER/endpoint_groups - POST /OS-EP-FILTER/endpoint_groups - GET /OS-EP-FILTER/endpoint_groups/{endpoint_group_id} - HEAD /OS-EP-FILTER/endpoint_groups/{endpoint_group_id} - PATCH /OS-EP-FILTER/endpoint_groups/{endpoint_group_id} - DELETE /OS-EP-FILTER/endpoint_groups/{endpoint_group_id} - - GET /OS-EP-FILTER/endpoint_groups/{endpoint_group_id}/projects - GET /OS-EP-FILTER/endpoint_groups/{endpoint_group_id}/endpoints - - PUT /OS-EP-FILTER/endpoint_groups/{endpoint_group}/projects/ - {project_id} - GET /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} - - """ - - PATH_PREFIX = '/OS-EP-FILTER' - PATH_PROJECT_ENDPOINT = '/projects/{project_id}/endpoints/{endpoint_id}' - PATH_ENDPOINT_GROUPS = '/endpoint_groups/{endpoint_group_id}' - PATH_ENDPOINT_GROUP_PROJECTS = PATH_ENDPOINT_GROUPS + ( - '/projects/{project_id}') - - def add_routes(self, mapper): - endpoint_filter_controller = controllers.EndpointFilterV3Controller() - endpoint_group_controller = controllers.EndpointGroupV3Controller() - project_endpoint_group_controller = ( - controllers.ProjectEndpointGroupV3Controller()) - - self._add_resource( - mapper, endpoint_filter_controller, - path=self.PATH_PREFIX + '/endpoints/{endpoint_id}/projects', - get_action='list_projects_for_endpoint', - rel=build_resource_relation(resource_name='endpoint_projects'), - path_vars={ - 'endpoint_id': json_home.Parameters.ENDPOINT_ID, - }) - self._add_resource( - mapper, endpoint_filter_controller, - path=self.PATH_PREFIX + self.PATH_PROJECT_ENDPOINT, - get_head_action='check_endpoint_in_project', - put_action='add_endpoint_to_project', - delete_action='remove_endpoint_from_project', - rel=build_resource_relation(resource_name='project_endpoint'), - path_vars={ - 'endpoint_id': json_home.Parameters.ENDPOINT_ID, - 'project_id': json_home.Parameters.PROJECT_ID, - }) - self._add_resource( - mapper, endpoint_filter_controller, - path=self.PATH_PREFIX + '/projects/{project_id}/endpoints', - get_action='list_endpoints_for_project', - rel=build_resource_relation(resource_name='project_endpoints'), - path_vars={ - 'project_id': json_home.Parameters.PROJECT_ID, - }) - self._add_resource( - mapper, endpoint_group_controller, - path=self.PATH_PREFIX + '/projects/{project_id}/endpoint_groups', - get_action='list_endpoint_groups_for_project', - rel=build_resource_relation( - resource_name='project_endpoint_groups'), - path_vars={ - 'project_id': json_home.Parameters.PROJECT_ID, - }) - self._add_resource( - mapper, endpoint_group_controller, - path=self.PATH_PREFIX + '/endpoint_groups', - get_action='list_endpoint_groups', - post_action='create_endpoint_group', - rel=build_resource_relation(resource_name='endpoint_groups')) - self._add_resource( - mapper, endpoint_group_controller, - path=self.PATH_PREFIX + self.PATH_ENDPOINT_GROUPS, - get_head_action='get_endpoint_group', - patch_action='update_endpoint_group', - delete_action='delete_endpoint_group', - rel=build_resource_relation(resource_name='endpoint_group'), - path_vars={ - 'endpoint_group_id': ENDPOINT_GROUP_PARAMETER_RELATION - }) - self._add_resource( - mapper, project_endpoint_group_controller, - path=self.PATH_PREFIX + self.PATH_ENDPOINT_GROUP_PROJECTS, - get_head_action='get_endpoint_group_in_project', - put_action='add_endpoint_group_to_project', - delete_action='remove_endpoint_group_from_project', - rel=build_resource_relation( - resource_name='endpoint_group_to_project_association'), - path_vars={ - 'project_id': json_home.Parameters.PROJECT_ID, - 'endpoint_group_id': ENDPOINT_GROUP_PARAMETER_RELATION - }) - self._add_resource( - mapper, endpoint_group_controller, - path=self.PATH_PREFIX + self.PATH_ENDPOINT_GROUPS + ( - '/projects'), - get_action='list_projects_associated_with_endpoint_group', - rel=build_resource_relation( - resource_name='projects_associated_with_endpoint_group'), - path_vars={ - 'endpoint_group_id': ENDPOINT_GROUP_PARAMETER_RELATION - }) - self._add_resource( - mapper, endpoint_group_controller, - path=self.PATH_PREFIX + self.PATH_ENDPOINT_GROUPS + ( - '/endpoints'), - get_action='list_endpoints_associated_with_endpoint_group', - rel=build_resource_relation( - resource_name='endpoints_in_endpoint_group'), - path_vars={ - 'endpoint_group_id': ENDPOINT_GROUP_PARAMETER_RELATION - }) + def __init__(self, *args, **kwargs): + super(EndpointFilterExtension, self).__init__(*args, **kwargs) + msg = _("Remove endpoint_filter_extension from the paste pipeline, " + "the endpoint filter extension is now always available. " + "Update the [pipeline:api_v3] section in keystone-paste.ini " + "accordingly as it will be removed in the O release.") + versionutils.report_deprecated_feature(LOG, msg) diff --git a/keystone/contrib/endpoint_filter/schema.py b/keystone/contrib/endpoint_filter/schema.py deleted file mode 100644 index cbe54e3666..0000000000 --- a/keystone/contrib/endpoint_filter/schema.py +++ /dev/null @@ -1,35 +0,0 @@ -# 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 keystone.common import validation -from keystone.common.validation import parameter_types - - -_endpoint_group_properties = { - 'description': validation.nullable(parameter_types.description), - 'filters': { - 'type': 'object' - }, - 'name': parameter_types.name -} - -endpoint_group_create = { - 'type': 'object', - 'properties': _endpoint_group_properties, - 'required': ['name', 'filters'] -} - -endpoint_group_update = { - 'type': 'object', - 'properties': _endpoint_group_properties, - 'minProperties': 1 -} diff --git a/keystone/server/backends.py b/keystone/server/backends.py index d7c8e9291f..1d4349ee4d 100644 --- a/keystone/server/backends.py +++ b/keystone/server/backends.py @@ -14,7 +14,6 @@ from keystone import assignment from keystone import auth from keystone import catalog from keystone.common import cache -from keystone.contrib import endpoint_filter from keystone import credential from keystone import endpoint_policy from keystone import federation @@ -45,7 +44,6 @@ def load_backends(): catalog_api=catalog.Manager(), credential_api=credential.Manager(), domain_config_api=resource.DomainConfigManager(), - endpoint_filter_api=endpoint_filter.Manager(), endpoint_policy_api=endpoint_policy.Manager(), federation_api=federation.Manager(), id_generator_api=identity.generator.Manager(), diff --git a/keystone/tests/unit/test_associate_project_endpoint_extension.py b/keystone/tests/unit/test_associate_project_endpoint_extension.py index 3df8fca1c2..ac74451171 100644 --- a/keystone/tests/unit/test_associate_project_endpoint_extension.py +++ b/keystone/tests/unit/test_associate_project_endpoint_extension.py @@ -15,25 +15,25 @@ import copy import uuid +import mock +from oslo_log import versionutils from six.moves import http_client from testtools import matchers +from keystone.contrib.endpoint_filter import routers from keystone.tests import unit from keystone.tests.unit import test_v3 -class TestExtensionCase(test_v3.RestfulTestCase): - - EXTENSION_NAME = 'endpoint_filter' - EXTENSION_TO_ADD = 'endpoint_filter_extension' +class EndpointFilterTestCase(test_v3.RestfulTestCase): def config_overrides(self): - super(TestExtensionCase, self).config_overrides() + super(EndpointFilterTestCase, self).config_overrides() self.config_fixture.config( group='catalog', driver='endpoint_filter.sql') def setUp(self): - super(TestExtensionCase, self).setUp() + super(EndpointFilterTestCase, self).setUp() self.default_request_url = ( '/OS-EP-FILTER/projects/%(project_id)s' '/endpoints/%(endpoint_id)s' % { @@ -41,7 +41,17 @@ class TestExtensionCase(test_v3.RestfulTestCase): 'endpoint_id': self.endpoint_id}) -class EndpointFilterCRUDTestCase(TestExtensionCase): +class EndpointFilterDeprecateTestCase(test_v3.RestfulTestCase): + + @mock.patch.object(versionutils, 'report_deprecated_feature') + def test_exception_happens(self, mock_deprecator): + routers.EndpointFilterExtension(mock.ANY) + mock_deprecator.assert_called_once_with(mock.ANY, mock.ANY) + args, _kwargs = mock_deprecator.call_args + self.assertIn("Remove endpoint_filter_extension from", args[1]) + + +class EndpointFilterCRUDTestCase(EndpointFilterTestCase): def test_create_endpoint_project_association(self): """PUT /OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id} @@ -269,8 +279,8 @@ class EndpointFilterCRUDTestCase(TestExtensionCase): self.assertEqual(self.endpoint_id, catalog[0]['endpoints'][0]['id']) # add the second endpoint to default project, bypassing - # endpoint_filter_api API manager. - self.endpoint_filter_api.driver.add_endpoint_to_project( + # catalog_api API manager. + self.catalog_api.driver.add_endpoint_to_project( endpoint_id2, self.default_domain_project_id) @@ -283,13 +293,13 @@ class EndpointFilterCRUDTestCase(TestExtensionCase): self.assertEqual(1, len(catalog[0]['endpoints'])) # remove the endpoint2 from the default project, and add it again via - # endpoint_filter_api API manager. - self.endpoint_filter_api.driver.remove_endpoint_from_project( + # catalog_api API manager. + self.catalog_api.driver.remove_endpoint_from_project( endpoint_id2, self.default_domain_project_id) # add second endpoint to default project, this can be done by calling - # the endpoint_filter_api API manager directly but call the REST API + # the catalog_api API manager directly but call the REST API # instead for consistency. self.put('/OS-EP-FILTER/projects/%(project_id)s' '/endpoints/%(endpoint_id)s' % { @@ -340,8 +350,8 @@ class EndpointFilterCRUDTestCase(TestExtensionCase): self.assertListEqual([self.endpoint_id, endpoint_id2], ep_id_list) # remove the endpoint2 from the default project, bypassing - # endpoint_filter_api API manager. - self.endpoint_filter_api.driver.remove_endpoint_from_project( + # catalog_api API manager. + self.catalog_api.driver.remove_endpoint_from_project( endpoint_id2, self.default_domain_project_id) @@ -355,13 +365,13 @@ class EndpointFilterCRUDTestCase(TestExtensionCase): self.assertEqual(2, len(catalog[0]['endpoints'])) # add back the endpoint2 to the default project, and remove it by - # endpoint_filter_api API manage. - self.endpoint_filter_api.driver.add_endpoint_to_project( + # catalog_api API manage. + self.catalog_api.driver.add_endpoint_to_project( endpoint_id2, self.default_domain_project_id) # remove the endpoint2 from the default project, this can be done - # by calling the endpoint_filter_api API manager directly but call + # by calling the catalog_api API manager directly but call # the REST API instead for consistency. self.delete('/OS-EP-FILTER/projects/%(project_id)s' '/endpoints/%(endpoint_id)s' % { @@ -378,7 +388,7 @@ class EndpointFilterCRUDTestCase(TestExtensionCase): self.assertEqual(self.endpoint_id, catalog[0]['endpoints'][0]['id']) -class EndpointFilterTokenRequestTestCase(TestExtensionCase): +class EndpointFilterTokenRequestTestCase(EndpointFilterTestCase): def test_project_scoped_token_using_endpoint_filter(self): """Verify endpoints from project scoped token filtered.""" @@ -595,7 +605,7 @@ class EndpointFilterTokenRequestTestCase(TestExtensionCase): auth_catalog.result['catalog']) -class JsonHomeTests(TestExtensionCase, test_v3.JsonHomeTestMixin): +class JsonHomeTests(EndpointFilterTestCase, test_v3.JsonHomeTestMixin): JSON_HOME_DATA = { 'http://docs.openstack.org/api/openstack-identity/3/ext/OS-EP-FILTER/' '1.0/rel/endpoint_projects': { @@ -666,7 +676,7 @@ class JsonHomeTests(TestExtensionCase, test_v3.JsonHomeTestMixin): } -class EndpointGroupCRUDTestCase(TestExtensionCase): +class EndpointGroupCRUDTestCase(EndpointFilterTestCase): DEFAULT_ENDPOINT_GROUP_BODY = { 'endpoint_group': { diff --git a/keystone/tests/unit/test_validation.py b/keystone/tests/unit/test_validation.py index c478ca1f91..68ddd97dd4 100644 --- a/keystone/tests/unit/test_validation.py +++ b/keystone/tests/unit/test_validation.py @@ -21,7 +21,6 @@ from keystone.catalog import schema as catalog_schema from keystone.common import validation from keystone.common.validation import parameter_types from keystone.common.validation import validators -from keystone.contrib.endpoint_filter import schema as endpoint_filter_schema from keystone.credential import schema as credential_schema from keystone import exception from keystone.federation import schema as federation_schema @@ -1298,8 +1297,8 @@ class EndpointGroupValidationTestCase(unit.BaseTestCase): def setUp(self): super(EndpointGroupValidationTestCase, self).setUp() - create = endpoint_filter_schema.endpoint_group_create - update = endpoint_filter_schema.endpoint_group_update + create = catalog_schema.endpoint_group_create + update = catalog_schema.endpoint_group_update self.create_endpoint_grp_validator = validators.SchemaValidator(create) self.update_endpoint_grp_validator = validators.SchemaValidator(update) diff --git a/setup.cfg b/setup.cfg index ba85c93561..3de816bd43 100644 --- a/setup.cfg +++ b/setup.cfg @@ -156,7 +156,7 @@ keystone.trust = sql = keystone.trust.backends.sql:Trust keystone.endpoint_filter = - sql = keystone.contrib.endpoint_filter.backends.sql:EndpointFilter + sql = keystone.catalog.backends.sql:Catalog keystone.endpoint_policy = sql = keystone.endpoint_policy.backends.sql:EndpointPolicy