The Stevedore library is used for loading backend drivers rather than using importutils. This provides a level of indirection for deployers/packagers. The importutils method of loading drivers is still supported, but it's deprecated. bp stevedore Change-Id: Id77ebf7056987ff1d3b9f62fab411845e63c86c3
		
			
				
	
	
		
			434 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			434 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# 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.
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    driver_namespace = 'keystone.endpoint_policy'
 | 
						|
 | 
						|
    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
 |