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)
|
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 = {
|
kwargs = {
|
||||||
'token': ctx.auth_token,
|
'token': ctx.auth_token,
|
||||||
'auth_url': CONF.keystone_authtoken.auth_uri,
|
'auth_url': ctx.auth_uri,
|
||||||
'project_id': ctx.project_id,
|
'project_id': ctx.project_id,
|
||||||
'cacert': CONF.keystone_authtoken.cafile,
|
'cacert': ctx.auth_cacert,
|
||||||
}
|
}
|
||||||
|
|
||||||
# In case of trust-scoped token explicitly pass endpoint parameter.
|
# In case of trust-scoped token explicitly pass endpoint parameter.
|
||||||
if (ctx.is_trust_scoped
|
if (ctx.is_trust_scoped
|
||||||
or keystone_utils.is_token_trust_scoped(ctx.auth_token)):
|
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 = self._client_class(**kwargs)
|
||||||
|
|
||||||
client.management_url = CONF.keystone_authtoken.auth_uri
|
client.management_url = ctx.auth_uri
|
||||||
|
|
||||||
return client
|
return client
|
||||||
|
|
||||||
@ -214,7 +217,7 @@ class NeutronAction(base.OpenStackAction):
|
|||||||
endpoint_url=neutron_endpoint.url,
|
endpoint_url=neutron_endpoint.url,
|
||||||
region_name=neutron_endpoint.region,
|
region_name=neutron_endpoint.region,
|
||||||
token=ctx.auth_token,
|
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(
|
client = self._client_class(
|
||||||
endpoint=designate_url,
|
endpoint=designate_url,
|
||||||
tenant_id=ctx.project_id,
|
tenant_id=ctx.project_id,
|
||||||
auth_url=CONF.keystone_authtoken.auth_uri,
|
auth_url=ctx.auth_uri,
|
||||||
region_name=designate_endpoint.region,
|
region_name=designate_endpoint.region,
|
||||||
service_type='dns'
|
service_type='dns'
|
||||||
)
|
)
|
||||||
|
@ -68,6 +68,8 @@ class BaseContext(object):
|
|||||||
class MistralContext(BaseContext):
|
class MistralContext(BaseContext):
|
||||||
# Use set([...]) since set literals are not supported in Python 2.6.
|
# Use set([...]) since set literals are not supported in Python 2.6.
|
||||||
_elements = set([
|
_elements = set([
|
||||||
|
"auth_uri",
|
||||||
|
"auth_cacert",
|
||||||
"user_id",
|
"user_id",
|
||||||
"project_id",
|
"project_id",
|
||||||
"auth_token",
|
"auth_token",
|
||||||
@ -120,6 +122,8 @@ def spawn(thread_description, func, *args, **kwargs):
|
|||||||
|
|
||||||
def context_from_headers(headers):
|
def context_from_headers(headers):
|
||||||
return MistralContext(
|
return MistralContext(
|
||||||
|
auth_uri=CONF.keystone_authtoken.auth_uri,
|
||||||
|
auth_cacert=CONF.keystone_authtoken.cafile,
|
||||||
user_id=headers.get('X-User-Id'),
|
user_id=headers.get('X-User-Id'),
|
||||||
project_id=headers.get('X-Project-Id'),
|
project_id=headers.get('X-Project-Id'),
|
||||||
auth_token=headers.get('X-Auth-Token'),
|
auth_token=headers.get('X-Auth-Token'),
|
||||||
|
@ -15,6 +15,57 @@
|
|||||||
from mistral.tests.unit import base
|
from mistral.tests.unit import base
|
||||||
from mistral.utils.openstack import keystone
|
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):
|
class KeystoneUtilsTest(base.BaseTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -41,3 +92,24 @@ class KeystoneUtilsTest(base.BaseTest):
|
|||||||
expected,
|
expected,
|
||||||
keystone.format_url(url_template, self.values)
|
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.
|
# limitations under the License.
|
||||||
|
|
||||||
from keystoneclient.v3 import client as ks_client
|
from keystoneclient.v3 import client as ks_client
|
||||||
|
from keystoneclient.v3.endpoints import Endpoint
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
from mistral import context
|
from mistral import context
|
||||||
@ -23,7 +24,7 @@ CONF = cfg.CONF
|
|||||||
|
|
||||||
def client():
|
def client():
|
||||||
ctx = context.ctx()
|
ctx = context.ctx()
|
||||||
auth_url = CONF.keystone_authtoken.auth_uri
|
auth_url = ctx.auth_uri
|
||||||
|
|
||||||
cl = ks_client.Client(
|
cl = ks_client.Client(
|
||||||
username=ctx.user_name,
|
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):
|
def get_endpoint_for_project(service_name=None, service_type=None):
|
||||||
admin_project_name = CONF.keystone_authtoken.admin_tenant_name
|
if service_name is None and service_type is None:
|
||||||
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:
|
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"Either 'service_name' or 'service_type' must be provided."
|
"Either 'service_name' or 'service_type' must be provided."
|
||||||
)
|
)
|
||||||
|
|
||||||
if not service_ids:
|
ctx = context.ctx()
|
||||||
raise Exception("Either service '%s' or service type "
|
|
||||||
"'%s' doesn't exist!" % (service_name, service_type))
|
|
||||||
|
|
||||||
endpoints = keystone_client.endpoints.list(
|
token = ctx.auth_token
|
||||||
service=service_ids[0],
|
response = client().tokens.get_token_data(token, include_catalog=True)
|
||||||
interface='public'
|
|
||||||
)
|
endpoints = select_service_endpoints(
|
||||||
|
service_name,
|
||||||
|
service_type,
|
||||||
|
response["token"]["catalog"])
|
||||||
|
|
||||||
if not endpoints:
|
if not endpoints:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"No endpoints found [service_name=%s, service_type=%s]"
|
"No endpoints found [service_name=%s, service_type=%s]"
|
||||||
% (service_name, service_type)
|
% (service_name, service_type)
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
# TODO(rakhmerov): We may have more than one endpoint because of regions
|
# TODO(rakhmerov): We may have more than one endpoint because
|
||||||
# TODO(rakhmerov): and ideally we need a config option for region
|
# TODO(rakhmerov): of regions and ideally we need a config option
|
||||||
|
# TODO(rakhmerov): for region
|
||||||
return endpoints[0]
|
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():
|
def get_keystone_endpoint_v2():
|
||||||
return get_endpoint_for_project('keystone')
|
return get_endpoint_for_project('keystone')
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user