Use client credentials to retrieve service list

Credentials received in the request are used to retrieve endpoint
list from keystone. This avoids the usage of the admin creds
and opens the gate towards connecting to any clouds without
previous configuration.

Implements: blueprint mistral-multi-vim-support
Change-Id: Ib5ae5911f2535f4f340af8f4bcb4817818747029
This commit is contained in:
Andras Kovi 2016-06-08 17:26:19 +02:00
parent 49e9486617
commit 998f5158e1
4 changed files with 118 additions and 26 deletions

View File

@ -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'
)

View File

@ -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'),

View File

@ -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')

View File

@ -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():