diff --git a/mistral/actions/openstack/actions.py b/mistral/actions/openstack/actions.py index 0aa518a4..55326312 100644 --- a/mistral/actions/openstack/actions.py +++ b/mistral/actions/openstack/actions.py @@ -109,21 +109,24 @@ class KeystoneAction(base.OpenStackAction): LOG.debug("Keystone action security context: %s" % ctx) + # TODO(akovi) cacert is deprecated in favor of session + # TODO(akovi) this piece of code should be refactored + # TODO(akovi) to follow the new guide lines kwargs = { 'token': ctx.auth_token, - 'auth_url': CONF.keystone_authtoken.auth_uri, + 'auth_url': ctx.auth_uri, 'project_id': ctx.project_id, - 'cacert': CONF.keystone_authtoken.cafile, + 'cacert': ctx.auth_cacert, } # In case of trust-scoped token explicitly pass endpoint parameter. if (ctx.is_trust_scoped or keystone_utils.is_token_trust_scoped(ctx.auth_token)): - kwargs['endpoint'] = CONF.keystone_authtoken.auth_uri + kwargs['endpoint'] = ctx.auth_uri client = self._client_class(**kwargs) - client.management_url = CONF.keystone_authtoken.auth_uri + client.management_url = ctx.auth_uri return client @@ -214,7 +217,7 @@ class NeutronAction(base.OpenStackAction): endpoint_url=neutron_endpoint.url, region_name=neutron_endpoint.region, token=ctx.auth_token, - auth_url=CONF.keystone_authtoken.auth_uri + auth_url=ctx.auth_uri ) @@ -595,7 +598,7 @@ class DesignateAction(base.OpenStackAction): client = self._client_class( endpoint=designate_url, tenant_id=ctx.project_id, - auth_url=CONF.keystone_authtoken.auth_uri, + auth_url=ctx.auth_uri, region_name=designate_endpoint.region, service_type='dns' ) diff --git a/mistral/context.py b/mistral/context.py index 80d23641..8c642ee8 100644 --- a/mistral/context.py +++ b/mistral/context.py @@ -68,6 +68,8 @@ class BaseContext(object): class MistralContext(BaseContext): # Use set([...]) since set literals are not supported in Python 2.6. _elements = set([ + "auth_uri", + "auth_cacert", "user_id", "project_id", "auth_token", @@ -120,6 +122,8 @@ def spawn(thread_description, func, *args, **kwargs): def context_from_headers(headers): return MistralContext( + auth_uri=CONF.keystone_authtoken.auth_uri, + auth_cacert=CONF.keystone_authtoken.cafile, user_id=headers.get('X-User-Id'), project_id=headers.get('X-Project-Id'), auth_token=headers.get('X-Auth-Token'), diff --git a/mistral/tests/unit/utils/test_keystone_utils.py b/mistral/tests/unit/utils/test_keystone_utils.py index 28360f58..d34fef8d 100644 --- a/mistral/tests/unit/utils/test_keystone_utils.py +++ b/mistral/tests/unit/utils/test_keystone_utils.py @@ -15,6 +15,57 @@ from mistral.tests.unit import base from mistral.utils.openstack import keystone +SERVICES_CATALOG = [ + { + "type": "compute", + "name": "nova", + "endpoints": [ + { + "interface": "private", + "url": "https://example.com/nova/private", + "region": "RegionOne" + }, + { + "interface": "public", + "url": "https://example.com/nova/public", + "region": "RegionOne" + } + ] + }, + { + "type": "compute", + "name": "nova2", + "endpoints": [ + { + "interface": "public", + "url": "https://example.com/nova2/public/r1", + "region": "RegionOne" + }, + { + "interface": "public", + "url": "https://example.com/nova2/public/r2", + "region": "RegionTwo" + } + ] + }, + { + "type": "orchestration", + "name": "heat", + "endpoints": [ + { + "interface": "private", + "url": "https://example.com/heat/private", + "region": "RegionOne" + }, + { + "interface": "public", + "url": "https://example.com/heat/public", + "region": "RegionOne" + } + ] + } +] + class KeystoneUtilsTest(base.BaseTest): def setUp(self): @@ -41,3 +92,24 @@ class KeystoneUtilsTest(base.BaseTest): expected, keystone.format_url(url_template, self.values) ) + + def test_service_endpoints_select(self): + def find(name, typ=None, catalog=SERVICES_CATALOG): + return keystone.select_service_endpoints(name, typ, catalog) + + endpoints = find('nova', 'compute') + self.assertEqual('https://example.com/nova/public', endpoints[0].url, + message='public interface must be selected') + + endpoints = find('nova2') + self.assertEqual(2, len(endpoints), + message='public endpoints must be selected ' + 'in each region') + + endpoints = find('heat') + self.assertEqual('https://example.com/heat/public', endpoints[0].url, + message='selection should work without type set') + + endpoints = find('nova', None, []) + self.assertEqual([], endpoints, + message='empty catalog should be accepted') diff --git a/mistral/utils/openstack/keystone.py b/mistral/utils/openstack/keystone.py index ec10467f..db29f3d8 100644 --- a/mistral/utils/openstack/keystone.py +++ b/mistral/utils/openstack/keystone.py @@ -14,6 +14,7 @@ # limitations under the License. from keystoneclient.v3 import client as ks_client +from keystoneclient.v3.endpoints import Endpoint from oslo_config import cfg from mistral import context @@ -23,7 +24,7 @@ CONF = cfg.CONF def client(): ctx = context.ctx() - auth_url = CONF.keystone_authtoken.auth_uri + auth_url = ctx.auth_uri cl = ks_client.Client( username=ctx.user_name, @@ -62,37 +63,49 @@ def client_for_trusts(trust_id): def get_endpoint_for_project(service_name=None, service_type=None): - admin_project_name = CONF.keystone_authtoken.admin_tenant_name - keystone_client = _admin_client(project_name=admin_project_name) - service_list = keystone_client.services.list() - - if service_name: - service_ids = [s.id for s in service_list if s.name == service_name] - elif service_type: - service_ids = [s.id for s in service_list if s.type == service_type] - else: + if service_name is None and service_type is None: raise Exception( "Either 'service_name' or 'service_type' must be provided." ) - if not service_ids: - raise Exception("Either service '%s' or service type " - "'%s' doesn't exist!" % (service_name, service_type)) + ctx = context.ctx() - endpoints = keystone_client.endpoints.list( - service=service_ids[0], - interface='public' - ) + token = ctx.auth_token + response = client().tokens.get_token_data(token, include_catalog=True) + + endpoints = select_service_endpoints( + service_name, + service_type, + response["token"]["catalog"]) if not endpoints: raise Exception( "No endpoints found [service_name=%s, service_type=%s]" % (service_name, service_type) ) + else: + # TODO(rakhmerov): We may have more than one endpoint because + # TODO(rakhmerov): of regions and ideally we need a config option + # TODO(rakhmerov): for region + return endpoints[0] - # TODO(rakhmerov): We may have more than one endpoint because of regions - # TODO(rakhmerov): and ideally we need a config option for region - return endpoints[0] + +def select_service_endpoints(service_name, service_type, services): + endpoints = [] + + for catalog in services: + + if service_name and catalog["name"] != service_name: + continue + + if service_type and catalog["type"] != service_type: + continue + + for endpoint in catalog["endpoints"]: + if endpoint["interface"] == 'public': + endpoints.append(Endpoint(None, endpoint, loaded=True)) + + return endpoints def get_keystone_endpoint_v2():