summaryrefslogtreecommitdiffstats
path: root/keystone-moon/keystone/contrib/endpoint_policy
diff options
context:
space:
mode:
authorWuKong <rebirthmonkey@gmail.com>2015-06-30 18:47:29 +0200
committerWuKong <rebirthmonkey@gmail.com>2015-06-30 18:47:29 +0200
commitb8c756ecdd7cced1db4300935484e8c83701c82e (patch)
tree87e51107d82b217ede145de9d9d59e2100725bd7 /keystone-moon/keystone/contrib/endpoint_policy
parentc304c773bae68fb854ed9eab8fb35c4ef17cf136 (diff)
migrate moon code from github to opnfv
Change-Id: Ice53e368fd1114d56a75271aa9f2e598e3eba604 Signed-off-by: WuKong <rebirthmonkey@gmail.com>
Diffstat (limited to 'keystone-moon/keystone/contrib/endpoint_policy')
-rw-r--r--keystone-moon/keystone/contrib/endpoint_policy/__init__.py15
-rw-r--r--keystone-moon/keystone/contrib/endpoint_policy/backends/__init__.py0
-rw-r--r--keystone-moon/keystone/contrib/endpoint_policy/backends/sql.py140
-rw-r--r--keystone-moon/keystone/contrib/endpoint_policy/controllers.py166
-rw-r--r--keystone-moon/keystone/contrib/endpoint_policy/core.py430
-rw-r--r--keystone-moon/keystone/contrib/endpoint_policy/migrate_repo/__init__.py0
-rw-r--r--keystone-moon/keystone/contrib/endpoint_policy/migrate_repo/migrate.cfg25
-rw-r--r--keystone-moon/keystone/contrib/endpoint_policy/migrate_repo/versions/001_add_endpoint_policy_table.py48
-rw-r--r--keystone-moon/keystone/contrib/endpoint_policy/migrate_repo/versions/__init__.py0
-rw-r--r--keystone-moon/keystone/contrib/endpoint_policy/routers.py85
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,
+ })