diff options
Diffstat (limited to 'keystone-moon/keystone/contrib/endpoint_policy')
10 files changed, 909 insertions, 0 deletions
diff --git a/keystone-moon/keystone/contrib/endpoint_policy/__init__.py b/keystone-moon/keystone/contrib/endpoint_policy/__init__.py new file mode 100644 index 00000000..12722dc5 --- /dev/null +++ b/keystone-moon/keystone/contrib/endpoint_policy/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2014 IBM Corp. +# +# 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_policy.core import * # noqa diff --git a/keystone-moon/keystone/contrib/endpoint_policy/backends/__init__.py b/keystone-moon/keystone/contrib/endpoint_policy/backends/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/keystone-moon/keystone/contrib/endpoint_policy/backends/__init__.py diff --git a/keystone-moon/keystone/contrib/endpoint_policy/backends/sql.py b/keystone-moon/keystone/contrib/endpoint_policy/backends/sql.py new file mode 100644 index 00000000..484444f1 --- /dev/null +++ b/keystone-moon/keystone/contrib/endpoint_policy/backends/sql.py @@ -0,0 +1,140 @@ +# Copyright 2014 IBM Corp. +# +# 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 uuid + +import sqlalchemy + +from keystone.common import sql +from keystone import exception + + +class PolicyAssociation(sql.ModelBase, sql.ModelDictMixin): + __tablename__ = 'policy_association' + attributes = ['policy_id', 'endpoint_id', 'region_id', 'service_id'] + # The id column is never exposed outside this module. It only exists to + # provide a primary key, given that the real columns we would like to use + # (endpoint_id, service_id, region_id) can be null + id = sql.Column(sql.String(64), primary_key=True) + policy_id = sql.Column(sql.String(64), nullable=False) + endpoint_id = sql.Column(sql.String(64), nullable=True) + service_id = sql.Column(sql.String(64), nullable=True) + region_id = sql.Column(sql.String(64), nullable=True) + __table_args__ = (sql.UniqueConstraint('endpoint_id', 'service_id', + 'region_id'), {}) + + def to_dict(self): + """Returns the model's attributes as a dictionary. + + We override the standard method in order to hide the id column, + since this only exists to provide the table with a primary key. + + """ + d = {} + for attr in self.__class__.attributes: + d[attr] = getattr(self, attr) + return d + + +class EndpointPolicy(object): + + def create_policy_association(self, policy_id, endpoint_id=None, + service_id=None, region_id=None): + with sql.transaction() as session: + try: + # See if there is already a row for this association, and if + # so, update it with the new policy_id + query = session.query(PolicyAssociation) + query = query.filter_by(endpoint_id=endpoint_id) + query = query.filter_by(service_id=service_id) + query = query.filter_by(region_id=region_id) + association = query.one() + association.policy_id = policy_id + except sql.NotFound: + association = PolicyAssociation(id=uuid.uuid4().hex, + policy_id=policy_id, + endpoint_id=endpoint_id, + service_id=service_id, + region_id=region_id) + session.add(association) + + def check_policy_association(self, policy_id, endpoint_id=None, + service_id=None, region_id=None): + sql_constraints = sqlalchemy.and_( + PolicyAssociation.policy_id == policy_id, + PolicyAssociation.endpoint_id == endpoint_id, + PolicyAssociation.service_id == service_id, + PolicyAssociation.region_id == region_id) + + # NOTE(henry-nash): Getting a single value to save object + # management overhead. + with sql.transaction() as session: + if session.query(PolicyAssociation.id).filter( + sql_constraints).distinct().count() == 0: + raise exception.PolicyAssociationNotFound() + + def delete_policy_association(self, policy_id, endpoint_id=None, + service_id=None, region_id=None): + with sql.transaction() as session: + query = session.query(PolicyAssociation) + query = query.filter_by(policy_id=policy_id) + query = query.filter_by(endpoint_id=endpoint_id) + query = query.filter_by(service_id=service_id) + query = query.filter_by(region_id=region_id) + query.delete() + + def get_policy_association(self, endpoint_id=None, + service_id=None, region_id=None): + sql_constraints = sqlalchemy.and_( + PolicyAssociation.endpoint_id == endpoint_id, + PolicyAssociation.service_id == service_id, + PolicyAssociation.region_id == region_id) + + try: + with sql.transaction() as session: + policy_id = session.query(PolicyAssociation.policy_id).filter( + sql_constraints).distinct().one() + return {'policy_id': policy_id} + except sql.NotFound: + raise exception.PolicyAssociationNotFound() + + def list_associations_for_policy(self, policy_id): + with sql.transaction() as session: + query = session.query(PolicyAssociation) + query = query.filter_by(policy_id=policy_id) + return [ref.to_dict() for ref in query.all()] + + def delete_association_by_endpoint(self, endpoint_id): + with sql.transaction() as session: + query = session.query(PolicyAssociation) + query = query.filter_by(endpoint_id=endpoint_id) + query.delete() + + def delete_association_by_service(self, service_id): + with sql.transaction() as session: + query = session.query(PolicyAssociation) + query = query.filter_by(service_id=service_id) + query.delete() + + def delete_association_by_region(self, region_id): + with sql.transaction() as session: + query = session.query(PolicyAssociation) + query = query.filter_by(region_id=region_id) + query.delete() + + def delete_association_by_policy(self, policy_id): + with sql.transaction() as session: + query = session.query(PolicyAssociation) + query = query.filter_by(policy_id=policy_id) + query.delete() diff --git a/keystone-moon/keystone/contrib/endpoint_policy/controllers.py b/keystone-moon/keystone/contrib/endpoint_policy/controllers.py new file mode 100644 index 00000000..b96834dc --- /dev/null +++ b/keystone-moon/keystone/contrib/endpoint_policy/controllers.py @@ -0,0 +1,166 @@ +# Copyright 2014 IBM Corp. +# +# 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 controller +from keystone.common import dependency +from keystone import notifications + + +@dependency.requires('policy_api', 'catalog_api', 'endpoint_policy_api') +class EndpointPolicyV3Controller(controller.V3Controller): + collection_name = 'endpoints' + member_name = 'endpoint' + + def __init__(self): + super(EndpointPolicyV3Controller, self).__init__() + notifications.register_event_callback( + 'deleted', 'endpoint', self._on_endpoint_delete) + notifications.register_event_callback( + 'deleted', 'service', self._on_service_delete) + notifications.register_event_callback( + 'deleted', 'region', self._on_region_delete) + notifications.register_event_callback( + 'deleted', 'policy', self._on_policy_delete) + + def _on_endpoint_delete(self, service, resource_type, operation, payload): + self.endpoint_policy_api.delete_association_by_endpoint( + payload['resource_info']) + + def _on_service_delete(self, service, resource_type, operation, payload): + self.endpoint_policy_api.delete_association_by_service( + payload['resource_info']) + + def _on_region_delete(self, service, resource_type, operation, payload): + self.endpoint_policy_api.delete_association_by_region( + payload['resource_info']) + + def _on_policy_delete(self, service, resource_type, operation, payload): + self.endpoint_policy_api.delete_association_by_policy( + payload['resource_info']) + + @controller.protected() + def create_policy_association_for_endpoint(self, context, + policy_id, endpoint_id): + """Create an association between a policy and an endpoint.""" + self.policy_api.get_policy(policy_id) + self.catalog_api.get_endpoint(endpoint_id) + self.endpoint_policy_api.create_policy_association( + policy_id, endpoint_id=endpoint_id) + + @controller.protected() + def check_policy_association_for_endpoint(self, context, + policy_id, endpoint_id): + """Check an association between a policy and an endpoint.""" + self.policy_api.get_policy(policy_id) + self.catalog_api.get_endpoint(endpoint_id) + self.endpoint_policy_api.check_policy_association( + policy_id, endpoint_id=endpoint_id) + + @controller.protected() + def delete_policy_association_for_endpoint(self, context, + policy_id, endpoint_id): + """Delete an association between a policy and an endpoint.""" + self.policy_api.get_policy(policy_id) + self.catalog_api.get_endpoint(endpoint_id) + self.endpoint_policy_api.delete_policy_association( + policy_id, endpoint_id=endpoint_id) + + @controller.protected() + def create_policy_association_for_service(self, context, + policy_id, service_id): + """Create an association between a policy and a service.""" + self.policy_api.get_policy(policy_id) + self.catalog_api.get_service(service_id) + self.endpoint_policy_api.create_policy_association( + policy_id, service_id=service_id) + + @controller.protected() + def check_policy_association_for_service(self, context, + policy_id, service_id): + """Check an association between a policy and a service.""" + self.policy_api.get_policy(policy_id) + self.catalog_api.get_service(service_id) + self.endpoint_policy_api.check_policy_association( + policy_id, service_id=service_id) + + @controller.protected() + def delete_policy_association_for_service(self, context, + policy_id, service_id): + """Delete an association between a policy and a service.""" + self.policy_api.get_policy(policy_id) + self.catalog_api.get_service(service_id) + self.endpoint_policy_api.delete_policy_association( + policy_id, service_id=service_id) + + @controller.protected() + def create_policy_association_for_region_and_service( + self, context, policy_id, service_id, region_id): + """Create an association between a policy and region+service.""" + self.policy_api.get_policy(policy_id) + self.catalog_api.get_service(service_id) + self.catalog_api.get_region(region_id) + self.endpoint_policy_api.create_policy_association( + policy_id, service_id=service_id, region_id=region_id) + + @controller.protected() + def check_policy_association_for_region_and_service( + self, context, policy_id, service_id, region_id): + """Check an association between a policy and region+service.""" + self.policy_api.get_policy(policy_id) + self.catalog_api.get_service(service_id) + self.catalog_api.get_region(region_id) + self.endpoint_policy_api.check_policy_association( + policy_id, service_id=service_id, region_id=region_id) + + @controller.protected() + def delete_policy_association_for_region_and_service( + self, context, policy_id, service_id, region_id): + """Delete an association between a policy and region+service.""" + self.policy_api.get_policy(policy_id) + self.catalog_api.get_service(service_id) + self.catalog_api.get_region(region_id) + self.endpoint_policy_api.delete_policy_association( + policy_id, service_id=service_id, region_id=region_id) + + @controller.protected() + def get_policy_for_endpoint(self, context, endpoint_id): + """Get the effective policy for an endpoint.""" + self.catalog_api.get_endpoint(endpoint_id) + ref = self.endpoint_policy_api.get_policy_for_endpoint(endpoint_id) + # NOTE(henry-nash): since the collection and member for this class is + # set to endpoints, we have to handle wrapping this policy entity + # ourselves. + self._add_self_referential_link(context, ref) + return {'policy': ref} + + # NOTE(henry-nash): As in the catalog controller, we must ensure that the + # legacy_endpoint_id does not escape. + + @classmethod + def filter_endpoint(cls, ref): + if 'legacy_endpoint_id' in ref: + ref.pop('legacy_endpoint_id') + return ref + + @classmethod + def wrap_member(cls, context, ref): + ref = cls.filter_endpoint(ref) + return super(EndpointPolicyV3Controller, cls).wrap_member(context, ref) + + @controller.protected() + def list_endpoints_for_policy(self, context, policy_id): + """List endpoints with the effective association to a policy.""" + self.policy_api.get_policy(policy_id) + refs = self.endpoint_policy_api.list_endpoints_for_policy(policy_id) + return EndpointPolicyV3Controller.wrap_collection(context, refs) diff --git a/keystone-moon/keystone/contrib/endpoint_policy/core.py b/keystone-moon/keystone/contrib/endpoint_policy/core.py new file mode 100644 index 00000000..1aa03267 --- /dev/null +++ b/keystone-moon/keystone/contrib/endpoint_policy/core.py @@ -0,0 +1,430 @@ +# Copyright 2014 IBM Corp. +# +# 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 manager +from keystone import exception +from keystone.i18n import _, _LE, _LW + +CONF = cfg.CONF +LOG = log.getLogger(__name__) + + +@dependency.provider('endpoint_policy_api') +@dependency.requires('catalog_api', 'policy_api') +class Manager(manager.Manager): + """Default pivot point for the Endpoint Policy 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_policy.driver) + + def _assert_valid_association(self, endpoint_id, service_id, region_id): + """Assert that the association is supported. + + There are three types of association supported: + + - Endpoint (in which case service and region must be None) + - Service and region (in which endpoint must be None) + - Service (in which case endpoint and region must be None) + + """ + if (endpoint_id is not None and + service_id is None and region_id is None): + return + if (service_id is not None and region_id is not None and + endpoint_id is None): + return + if (service_id is not None and + endpoint_id is None and region_id is None): + return + + raise exception.InvalidPolicyAssociation(endpoint_id=endpoint_id, + service_id=service_id, + region_id=region_id) + + def create_policy_association(self, policy_id, endpoint_id=None, + service_id=None, region_id=None): + self._assert_valid_association(endpoint_id, service_id, region_id) + self.driver.create_policy_association(policy_id, endpoint_id, + service_id, region_id) + + def check_policy_association(self, policy_id, endpoint_id=None, + service_id=None, region_id=None): + self._assert_valid_association(endpoint_id, service_id, region_id) + self.driver.check_policy_association(policy_id, endpoint_id, + service_id, region_id) + + def delete_policy_association(self, policy_id, endpoint_id=None, + service_id=None, region_id=None): + self._assert_valid_association(endpoint_id, service_id, region_id) + self.driver.delete_policy_association(policy_id, endpoint_id, + service_id, region_id) + + def list_endpoints_for_policy(self, policy_id): + + def _get_endpoint(endpoint_id, policy_id): + try: + return self.catalog_api.get_endpoint(endpoint_id) + except exception.EndpointNotFound: + msg = _LW('Endpoint %(endpoint_id)s referenced in ' + 'association for policy %(policy_id)s not found.') + LOG.warning(msg, {'policy_id': policy_id, + 'endpoint_id': endpoint_id}) + raise + + def _get_endpoints_for_service(service_id, endpoints): + # TODO(henry-nash): Consider optimizing this in the future by + # adding an explicit list_endpoints_for_service to the catalog API. + return [ep for ep in endpoints if ep['service_id'] == service_id] + + def _get_endpoints_for_service_and_region( + service_id, region_id, endpoints, regions): + # TODO(henry-nash): Consider optimizing this in the future. + # The lack of a two-way pointer in the region tree structure + # makes this somewhat inefficient. + + def _recursively_get_endpoints_for_region( + region_id, service_id, endpoint_list, region_list, + endpoints_found, regions_examined): + """Recursively search down a region tree for endpoints. + + :param region_id: the point in the tree to examine + :param service_id: the service we are interested in + :param endpoint_list: list of all endpoints + :param region_list: list of all regions + :param endpoints_found: list of matching endpoints found so + far - which will be updated if more are + found in this iteration + :param regions_examined: list of regions we have already looked + at - used to spot illegal circular + references in the tree to avoid never + completing search + :returns: list of endpoints that match + + """ + + if region_id in regions_examined: + msg = _LE('Circular reference or a repeated entry found ' + 'in region tree - %(region_id)s.') + LOG.error(msg, {'region_id': ref.region_id}) + return + + regions_examined.append(region_id) + endpoints_found += ( + [ep for ep in endpoint_list if + ep['service_id'] == service_id and + ep['region_id'] == region_id]) + + for region in region_list: + if region['parent_region_id'] == region_id: + _recursively_get_endpoints_for_region( + region['id'], service_id, endpoints, regions, + endpoints_found, regions_examined) + + endpoints_found = [] + regions_examined = [] + + # Now walk down the region tree + _recursively_get_endpoints_for_region( + region_id, service_id, endpoints, regions, + endpoints_found, regions_examined) + + return endpoints_found + + matching_endpoints = [] + endpoints = self.catalog_api.list_endpoints() + regions = self.catalog_api.list_regions() + for ref in self.driver.list_associations_for_policy(policy_id): + if ref.get('endpoint_id') is not None: + matching_endpoints.append( + _get_endpoint(ref['endpoint_id'], policy_id)) + continue + + if (ref.get('service_id') is not None and + ref.get('region_id') is None): + matching_endpoints += _get_endpoints_for_service( + ref['service_id'], endpoints) + continue + + if (ref.get('service_id') is not None and + ref.get('region_id') is not None): + matching_endpoints += ( + _get_endpoints_for_service_and_region( + ref['service_id'], ref['region_id'], + endpoints, regions)) + continue + + msg = _LW('Unsupported policy association found - ' + 'Policy %(policy_id)s, Endpoint %(endpoint_id)s, ' + 'Service %(service_id)s, Region %(region_id)s, ') + LOG.warning(msg, {'policy_id': policy_id, + 'endpoint_id': ref['endpoint_id'], + 'service_id': ref['service_id'], + 'region_id': ref['region_id']}) + + return matching_endpoints + + def get_policy_for_endpoint(self, endpoint_id): + + def _get_policy(policy_id, endpoint_id): + try: + return self.policy_api.get_policy(policy_id) + except exception.PolicyNotFound: + msg = _LW('Policy %(policy_id)s referenced in association ' + 'for endpoint %(endpoint_id)s not found.') + LOG.warning(msg, {'policy_id': policy_id, + 'endpoint_id': endpoint_id}) + raise + + def _look_for_policy_for_region_and_service(endpoint): + """Look in the region and its parents for a policy. + + Examine the region of the endpoint for a policy appropriate for + the service of the endpoint. If there isn't a match, then chase up + the region tree to find one. + + """ + region_id = endpoint['region_id'] + regions_examined = [] + while region_id is not None: + try: + ref = self.driver.get_policy_association( + service_id=endpoint['service_id'], + region_id=region_id) + return ref['policy_id'] + except exception.PolicyAssociationNotFound: + pass + + # There wasn't one for that region & service, let's + # chase up the region tree + regions_examined.append(region_id) + region = self.catalog_api.get_region(region_id) + region_id = None + if region.get('parent_region_id') is not None: + region_id = region['parent_region_id'] + if region_id in regions_examined: + msg = _LE('Circular reference or a repeated entry ' + 'found in region tree - %(region_id)s.') + LOG.error(msg, {'region_id': region_id}) + break + + # First let's see if there is a policy explicitly defined for + # this endpoint. + + try: + ref = self.driver.get_policy_association(endpoint_id=endpoint_id) + return _get_policy(ref['policy_id'], endpoint_id) + except exception.PolicyAssociationNotFound: + pass + + # There wasn't a policy explicitly defined for this endpoint, so + # now let's see if there is one for the Region & Service. + + endpoint = self.catalog_api.get_endpoint(endpoint_id) + policy_id = _look_for_policy_for_region_and_service(endpoint) + if policy_id is not None: + return _get_policy(policy_id, endpoint_id) + + # Finally, just check if there is one for the service. + try: + ref = self.driver.get_policy_association( + service_id=endpoint['service_id']) + return _get_policy(ref['policy_id'], endpoint_id) + except exception.PolicyAssociationNotFound: + pass + + msg = _('No policy is associated with endpoint ' + '%(endpoint_id)s.') % {'endpoint_id': endpoint_id} + raise exception.NotFound(msg) + + +@six.add_metaclass(abc.ABCMeta) +class Driver(object): + """Interface description for an Endpoint Policy driver.""" + + @abc.abstractmethod + def create_policy_association(self, policy_id, endpoint_id=None, + service_id=None, region_id=None): + """Creates a policy association. + + :param policy_id: identity of policy that is being associated + :type policy_id: string + :param endpoint_id: identity of endpoint to associate + :type endpoint_id: string + :param service_id: identity of the service to associate + :type service_id: string + :param region_id: identity of the region to associate + :type region_id: string + :returns: None + + There are three types of association permitted: + + - Endpoint (in which case service and region must be None) + - Service and region (in which endpoint must be None) + - Service (in which case endpoint and region must be None) + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def check_policy_association(self, policy_id, endpoint_id=None, + service_id=None, region_id=None): + """Checks existence a policy association. + + :param policy_id: identity of policy that is being associated + :type policy_id: string + :param endpoint_id: identity of endpoint to associate + :type endpoint_id: string + :param service_id: identity of the service to associate + :type service_id: string + :param region_id: identity of the region to associate + :type region_id: string + :raises: keystone.exception.PolicyAssociationNotFound if there is no + match for the specified association + :returns: None + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def delete_policy_association(self, policy_id, endpoint_id=None, + service_id=None, region_id=None): + """Deletes a policy association. + + :param policy_id: identity of policy that is being associated + :type policy_id: string + :param endpoint_id: identity of endpoint to associate + :type endpoint_id: string + :param service_id: identity of the service to associate + :type service_id: string + :param region_id: identity of the region to associate + :type region_id: string + :returns: None + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def get_policy_association(self, endpoint_id=None, + service_id=None, region_id=None): + """Gets the policy for an explicit association. + + This method is not exposed as a public API, but is used by + get_policy_for_endpoint(). + + :param endpoint_id: identity of endpoint + :type endpoint_id: string + :param service_id: identity of the service + :type service_id: string + :param region_id: identity of the region + :type region_id: string + :raises: keystone.exception.PolicyAssociationNotFound if there is no + match for the specified association + :returns: dict containing policy_id + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def list_associations_for_policy(self, policy_id): + """List the associations for a policy. + + This method is not exposed as a public API, but is used by + list_endpoints_for_policy(). + + :param policy_id: identity of policy + :type policy_id: string + :returns: List of association dicts + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def list_endpoints_for_policy(self, policy_id): + """List all the endpoints using a given policy. + + :param policy_id: identity of policy that is being associated + :type policy_id: string + :returns: list of endpoints that have an effective association with + that policy + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def get_policy_for_endpoint(self, endpoint_id): + """Get the appropriate policy for a given endpoint. + + :param endpoint_id: identity of endpoint + :type endpoint_id: string + :returns: Policy entity for the endpoint + + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def delete_association_by_endpoint(self, endpoint_id): + """Removes all the policy associations with the specific endpoint. + + :param endpoint_id: identity of endpoint to check + :type endpoint_id: string + :returns: None + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def delete_association_by_service(self, service_id): + """Removes all the policy associations with the specific service. + + :param service_id: identity of endpoint to check + :type service_id: string + :returns: None + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def delete_association_by_region(self, region_id): + """Removes all the policy associations with the specific region. + + :param region_id: identity of endpoint to check + :type region_id: string + :returns: None + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def delete_association_by_policy(self, policy_id): + """Removes all the policy associations with the specific policy. + + :param policy_id: identity of endpoint to check + :type policy_id: string + :returns: None + + """ + raise exception.NotImplemented() # pragma: no cover diff --git a/keystone-moon/keystone/contrib/endpoint_policy/migrate_repo/__init__.py b/keystone-moon/keystone/contrib/endpoint_policy/migrate_repo/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/keystone-moon/keystone/contrib/endpoint_policy/migrate_repo/__init__.py diff --git a/keystone-moon/keystone/contrib/endpoint_policy/migrate_repo/migrate.cfg b/keystone-moon/keystone/contrib/endpoint_policy/migrate_repo/migrate.cfg new file mode 100644 index 00000000..62895d6f --- /dev/null +++ b/keystone-moon/keystone/contrib/endpoint_policy/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_policy + +# 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_policy/migrate_repo/versions/001_add_endpoint_policy_table.py b/keystone-moon/keystone/contrib/endpoint_policy/migrate_repo/versions/001_add_endpoint_policy_table.py new file mode 100644 index 00000000..c77e4380 --- /dev/null +++ b/keystone-moon/keystone/contrib/endpoint_policy/migrate_repo/versions/001_add_endpoint_policy_table.py @@ -0,0 +1,48 @@ +# Copyright 2014 IBM Corp. +# +# 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_policy_table = sql.Table( + 'policy_association', + meta, + sql.Column('id', sql.String(64), primary_key=True), + sql.Column('policy_id', sql.String(64), + nullable=False), + sql.Column('endpoint_id', sql.String(64), + nullable=True), + sql.Column('service_id', sql.String(64), + nullable=True), + sql.Column('region_id', sql.String(64), + nullable=True), + sql.UniqueConstraint('endpoint_id', 'service_id', 'region_id'), + mysql_engine='InnoDB', + mysql_charset='utf8') + + endpoint_policy_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. + table = sql.Table('policy_association', meta, autoload=True) + table.drop() diff --git a/keystone-moon/keystone/contrib/endpoint_policy/migrate_repo/versions/__init__.py b/keystone-moon/keystone/contrib/endpoint_policy/migrate_repo/versions/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/keystone-moon/keystone/contrib/endpoint_policy/migrate_repo/versions/__init__.py diff --git a/keystone-moon/keystone/contrib/endpoint_policy/routers.py b/keystone-moon/keystone/contrib/endpoint_policy/routers.py new file mode 100644 index 00000000..999d1eed --- /dev/null +++ b/keystone-moon/keystone/contrib/endpoint_policy/routers.py @@ -0,0 +1,85 @@ +# Copyright 2014 IBM Corp. +# +# 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_policy import controllers + + +build_resource_relation = functools.partial( + json_home.build_v3_extension_resource_relation, + extension_name='OS-ENDPOINT-POLICY', extension_version='1.0') + + +class EndpointPolicyExtension(wsgi.V3ExtensionRouter): + + PATH_PREFIX = '/OS-ENDPOINT-POLICY' + + def add_routes(self, mapper): + endpoint_policy_controller = controllers.EndpointPolicyV3Controller() + + self._add_resource( + mapper, endpoint_policy_controller, + path='/endpoints/{endpoint_id}' + self.PATH_PREFIX + '/policy', + get_head_action='get_policy_for_endpoint', + rel=build_resource_relation(resource_name='endpoint_policy'), + path_vars={'endpoint_id': json_home.Parameters.ENDPOINT_ID}) + self._add_resource( + mapper, endpoint_policy_controller, + path='/policies/{policy_id}' + self.PATH_PREFIX + '/endpoints', + get_action='list_endpoints_for_policy', + rel=build_resource_relation(resource_name='policy_endpoints'), + path_vars={'policy_id': json_home.Parameters.POLICY_ID}) + self._add_resource( + mapper, endpoint_policy_controller, + path=('/policies/{policy_id}' + self.PATH_PREFIX + + '/endpoints/{endpoint_id}'), + get_head_action='check_policy_association_for_endpoint', + put_action='create_policy_association_for_endpoint', + delete_action='delete_policy_association_for_endpoint', + rel=build_resource_relation( + resource_name='endpoint_policy_association'), + path_vars={ + 'policy_id': json_home.Parameters.POLICY_ID, + 'endpoint_id': json_home.Parameters.ENDPOINT_ID, + }) + self._add_resource( + mapper, endpoint_policy_controller, + path=('/policies/{policy_id}' + self.PATH_PREFIX + + '/services/{service_id}'), + get_head_action='check_policy_association_for_service', + put_action='create_policy_association_for_service', + delete_action='delete_policy_association_for_service', + rel=build_resource_relation( + resource_name='service_policy_association'), + path_vars={ + 'policy_id': json_home.Parameters.POLICY_ID, + 'service_id': json_home.Parameters.SERVICE_ID, + }) + self._add_resource( + mapper, endpoint_policy_controller, + path=('/policies/{policy_id}' + self.PATH_PREFIX + + '/services/{service_id}/regions/{region_id}'), + get_head_action='check_policy_association_for_region_and_service', + put_action='create_policy_association_for_region_and_service', + delete_action='delete_policy_association_for_region_and_service', + rel=build_resource_relation( + resource_name='region_and_service_policy_association'), + path_vars={ + 'policy_id': json_home.Parameters.POLICY_ID, + 'service_id': json_home.Parameters.SERVICE_ID, + 'region_id': json_home.Parameters.REGION_ID, + }) |