From b8c756ecdd7cced1db4300935484e8c83701c82e Mon Sep 17 00:00:00 2001 From: WuKong Date: Tue, 30 Jun 2015 18:47:29 +0200 Subject: migrate moon code from github to opnfv Change-Id: Ice53e368fd1114d56a75271aa9f2e598e3eba604 Signed-off-by: WuKong --- .../keystone/contrib/endpoint_filter/__init__.py | 15 ++ .../contrib/endpoint_filter/backends/__init__.py | 0 .../endpoint_filter/backends/catalog_sql.py | 76 ++++++ .../contrib/endpoint_filter/backends/sql.py | 224 +++++++++++++++ .../contrib/endpoint_filter/controllers.py | 300 +++++++++++++++++++++ .../keystone/contrib/endpoint_filter/core.py | 289 ++++++++++++++++++++ .../endpoint_filter/migrate_repo/__init__.py | 0 .../endpoint_filter/migrate_repo/migrate.cfg | 25 ++ .../versions/001_add_endpoint_filtering_table.py | 47 ++++ .../versions/002_add_endpoint_groups.py | 51 ++++ .../migrate_repo/versions/__init__.py | 0 .../keystone/contrib/endpoint_filter/routers.py | 149 ++++++++++ .../keystone/contrib/endpoint_filter/schema.py | 35 +++ 13 files changed, 1211 insertions(+) create mode 100644 keystone-moon/keystone/contrib/endpoint_filter/__init__.py create mode 100644 keystone-moon/keystone/contrib/endpoint_filter/backends/__init__.py create mode 100644 keystone-moon/keystone/contrib/endpoint_filter/backends/catalog_sql.py create mode 100644 keystone-moon/keystone/contrib/endpoint_filter/backends/sql.py create mode 100644 keystone-moon/keystone/contrib/endpoint_filter/controllers.py create mode 100644 keystone-moon/keystone/contrib/endpoint_filter/core.py create mode 100644 keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/__init__.py create mode 100644 keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/migrate.cfg create mode 100644 keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/versions/001_add_endpoint_filtering_table.py create mode 100644 keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/versions/002_add_endpoint_groups.py create mode 100644 keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/versions/__init__.py create mode 100644 keystone-moon/keystone/contrib/endpoint_filter/routers.py create mode 100644 keystone-moon/keystone/contrib/endpoint_filter/schema.py (limited to 'keystone-moon/keystone/contrib/endpoint_filter') diff --git a/keystone-moon/keystone/contrib/endpoint_filter/__init__.py b/keystone-moon/keystone/contrib/endpoint_filter/__init__.py new file mode 100644 index 00000000..72508c3e --- /dev/null +++ b/keystone-moon/keystone/contrib/endpoint_filter/__init__.py @@ -0,0 +1,15 @@ +# 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-moon/keystone/contrib/endpoint_filter/backends/__init__.py b/keystone-moon/keystone/contrib/endpoint_filter/backends/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/keystone-moon/keystone/contrib/endpoint_filter/backends/catalog_sql.py b/keystone-moon/keystone/contrib/endpoint_filter/backends/catalog_sql.py new file mode 100644 index 00000000..6ac3c1ca --- /dev/null +++ b/keystone-moon/keystone/contrib/endpoint_filter/backends/catalog_sql.py @@ -0,0 +1,76 @@ +# 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 oslo_config import cfg +import six + +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 + + +@dependency.requires('endpoint_filter_api') +class EndpointFilterCatalog(sql.Catalog): + def get_v3_catalog(self, user_id, project_id): + substitutions = dict(six.iteritems(CONF)) + substitutions.update({'tenant_id': project_id, 'user_id': user_id}) + + services = {} + + refs = self.endpoint_filter_api.list_endpoints_for_project(project_id) + + if (not refs and + CONF.endpoint_filter.return_all_endpoints_if_no_filter): + return super(EndpointFilterCatalog, self).get_v3_catalog( + user_id, project_id) + + for entry in refs: + try: + endpoint = self.get_endpoint(entry['endpoint_id']) + if not endpoint['enabled']: + # Skip disabled endpoints. + continue + service_id = endpoint['service_id'] + services.setdefault( + service_id, + self.get_service(service_id)) + service = services[service_id] + del endpoint['service_id'] + del endpoint['enabled'] + del endpoint['legacy_endpoint_id'] + endpoint['url'] = catalog_core.format_url( + endpoint['url'], substitutions) + # populate filtered endpoints + if 'endpoints' in services[service_id]: + service['endpoints'].append(endpoint) + else: + service['endpoints'] = [endpoint] + except exception.EndpointNotFound: + # remove bad reference from association + self.endpoint_filter_api.remove_endpoint_from_project( + entry['endpoint_id'], project_id) + + # format catalog + catalog = [] + for service_id, service in six.iteritems(services): + formatted_service = {} + formatted_service['id'] = service['id'] + formatted_service['type'] = service['type'] + formatted_service['endpoints'] = service['endpoints'] + catalog.append(formatted_service) + + return catalog diff --git a/keystone-moon/keystone/contrib/endpoint_filter/backends/sql.py b/keystone-moon/keystone/contrib/endpoint_filter/backends/sql.py new file mode 100644 index 00000000..a998423f --- /dev/null +++ b/keystone-moon/keystone/contrib/endpoint_filter/backends/sql.py @@ -0,0 +1,224 @@ +# 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.common import sql +from keystone import exception +from keystone.i18n import _ + + +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(object): + + @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(): + session.delete(endpoint_group_ref) + self._delete_endpoint_group_association_by_endpoint_group( + session, endpoint_group_id) + + 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() diff --git a/keystone-moon/keystone/contrib/endpoint_filter/controllers.py b/keystone-moon/keystone/contrib/endpoint_filter/controllers.py new file mode 100644 index 00000000..dc4ef7a3 --- /dev/null +++ b/keystone-moon/keystone/contrib/endpoint_filter/controllers.py @@ -0,0 +1,300 @@ +# 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 six.iteritems(filters): + 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-moon/keystone/contrib/endpoint_filter/core.py b/keystone-moon/keystone/contrib/endpoint_filter/core.py new file mode 100644 index 00000000..972b65dd --- /dev/null +++ b/keystone-moon/keystone/contrib/endpoint_filter/core.py @@ -0,0 +1,289 @@ +# 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 abc + +from oslo_config import cfg +from oslo_log import log +import six + +from keystone.common import dependency +from keystone.common import extension +from keystone.common import manager +from keystone import exception + + +CONF = cfg.CONF +LOG = log.getLogger(__name__) + +extension_data = { + 'name': 'OpenStack Keystone Endpoint Filter API', + 'namespace': 'http://docs.openstack.org/identity/api/ext/' + 'OS-EP-FILTER/v1.0', + 'alias': 'OS-EP-FILTER', + 'updated': '2013-07-23T12:00:0-00:00', + 'description': 'OpenStack Keystone Endpoint Filter API.', + 'links': [ + { + 'rel': 'describedby', + # TODO(ayoung): needs a description + 'type': 'text/html', + 'href': 'https://github.com/openstack/identity-api/blob/master' + '/openstack-identity-api/v3/src/markdown/' + 'identity-api-v3-os-ep-filter-ext.md', + } + ]} +extension.register_admin_extension(extension_data['alias'], extension_data) + + +@dependency.provider('endpoint_filter_api') +class Manager(manager.Manager): + """Default pivot point for the Endpoint Filter backend. + + See :mod:`keystone.common.manager.Manager` for more details on how this + dynamically calls the backend. + + """ + + def __init__(self): + super(Manager, self).__init__(CONF.endpoint_filter.driver) + + +@six.add_metaclass(abc.ABCMeta) +class Driver(object): + """Interface description for an Endpoint Filter driver.""" + + @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 diff --git a/keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/__init__.py b/keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/migrate.cfg b/keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/migrate.cfg new file mode 100644 index 00000000..c7d34785 --- /dev/null +++ b/keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/migrate.cfg @@ -0,0 +1,25 @@ +[db_settings] +# Used to identify which repository this database is versioned under. +# You can use the name of your project. +repository_id=endpoint_filter + +# The name of the database table used to track the schema version. +# This name shouldn't already be used by your project. +# If this is changed once a database is under version control, you'll need to +# change the table name in each database too. +version_table=migrate_version + +# When committing a change script, Migrate will attempt to generate the +# sql for all supported databases; normally, if one of them fails - probably +# because you don't have that database installed - it is ignored and the +# commit continues, perhaps ending successfully. +# Databases in this list MUST compile successfully during a commit, or the +# entire commit will fail. List the databases your application will actually +# be using to ensure your updates to that database work properly. +# This must be a list; example: ['postgres','sqlite'] +required_dbs=[] + +# When creating new change scripts, Migrate will stamp the new script with +# a version number. By default this is latest_version + 1. You can set this +# to 'true' to tell Migrate to use the UTC timestamp instead. +use_timestamp_numbering=False diff --git a/keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/versions/001_add_endpoint_filtering_table.py b/keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/versions/001_add_endpoint_filtering_table.py new file mode 100644 index 00000000..090e7f47 --- /dev/null +++ b/keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/versions/001_add_endpoint_filtering_table.py @@ -0,0 +1,47 @@ +# 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 sqlalchemy as sql + + +def upgrade(migrate_engine): + # Upgrade operations go here. Don't create your own engine; bind + # migrate_engine to your metadata + meta = sql.MetaData() + meta.bind = migrate_engine + + endpoint_filtering_table = sql.Table( + 'project_endpoint', + meta, + sql.Column( + 'endpoint_id', + sql.String(64), + primary_key=True, + nullable=False), + sql.Column( + 'project_id', + sql.String(64), + primary_key=True, + nullable=False)) + + endpoint_filtering_table.create(migrate_engine, checkfirst=True) + + +def downgrade(migrate_engine): + meta = sql.MetaData() + meta.bind = migrate_engine + # Operations to reverse the above upgrade go here. + for table_name in ['project_endpoint']: + table = sql.Table(table_name, meta, autoload=True) + table.drop() diff --git a/keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/versions/002_add_endpoint_groups.py b/keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/versions/002_add_endpoint_groups.py new file mode 100644 index 00000000..5f80160a --- /dev/null +++ b/keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/versions/002_add_endpoint_groups.py @@ -0,0 +1,51 @@ +# Copyright 2014 Hewlett-Packard Company +# +# 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 sqlalchemy as sql + + +def upgrade(migrate_engine): + # Upgrade operations go here. Don't create your own engine; bind + # migrate_engine to your metadata + meta = sql.MetaData() + meta.bind = migrate_engine + + endpoint_group_table = sql.Table( + 'endpoint_group', + meta, + sql.Column('id', sql.String(64), primary_key=True), + sql.Column('name', sql.String(255), nullable=False), + sql.Column('description', sql.Text, nullable=True), + sql.Column('filters', sql.Text(), nullable=False)) + endpoint_group_table.create(migrate_engine, checkfirst=True) + + project_endpoint_group_table = sql.Table( + 'project_endpoint_group', + meta, + sql.Column('endpoint_group_id', sql.String(64), + sql.ForeignKey('endpoint_group.id'), nullable=False), + sql.Column('project_id', sql.String(64), nullable=False), + sql.PrimaryKeyConstraint('endpoint_group_id', + 'project_id')) + project_endpoint_group_table.create(migrate_engine, checkfirst=True) + + +def downgrade(migrate_engine): + meta = sql.MetaData() + meta.bind = migrate_engine + # Operations to reverse the above upgrade go here. + for table_name in ['project_endpoint_group', + 'endpoint_group']: + table = sql.Table(table_name, meta, autoload=True) + table.drop() diff --git a/keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/versions/__init__.py b/keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/versions/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/keystone-moon/keystone/contrib/endpoint_filter/routers.py b/keystone-moon/keystone/contrib/endpoint_filter/routers.py new file mode 100644 index 00000000..00c8cd72 --- /dev/null +++ b/keystone-moon/keystone/contrib/endpoint_filter/routers.py @@ -0,0 +1,149 @@ +# 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 functools + +from keystone.common import json_home +from keystone.common import wsgi +from keystone.contrib.endpoint_filter import controllers + + +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 EndpointFilterExtension(wsgi.V3ExtensionRouter): + """API Endpoints for the Endpoint Filter extension. + + 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/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 + '/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-moon/keystone/contrib/endpoint_filter/schema.py b/keystone-moon/keystone/contrib/endpoint_filter/schema.py new file mode 100644 index 00000000..cbe54e36 --- /dev/null +++ b/keystone-moon/keystone/contrib/endpoint_filter/schema.py @@ -0,0 +1,35 @@ +# 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 +} -- cgit 1.2.3-korg