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:
parent
49e9486617
commit
998f5158e1
@ -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'
|
||||
)
|
||||
|
@ -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'),
|
||||
|
@ -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')
|
||||
|
@ -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,39 +63,51 @@ 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)
|
||||
)
|
||||
|
||||
# TODO(rakhmerov): We may have more than one endpoint because of regions
|
||||
# TODO(rakhmerov): and ideally we need a config option for region
|
||||
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]
|
||||
|
||||
|
||||
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():
|
||||
return get_endpoint_for_project('keystone')
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user