Use service catalog from authentication response

When using Mistral in multi openstack deployments, user can pass
'X-Target-Auth-Uri' in the header to let Mistral run openstack service
actions in different openstack deployment. 'X-Target-Service-Catalog'
can also be provided but it's optional.

This patch adds 'is_target' attribute to Mistral context, if it's true,
Mistral will talk to another openstack deployment, 'service_catalog'
in the context can be empty or contain target service catalog provided
by user, Mistral will get service catalog dynamically if it's empty;
if it's false, the 'service_catalog' in context can also be empty(when
auth_enable=False) or the content that get from keystone authentication
response.

This patch also fix the tempest failure introduced by:
https://review.openstack.org/#/c/387883/

Related-Bug: #1634090
Change-Id: Iec3ed0333cd08831f0a15f77e3880f07dd89e1e8
This commit is contained in:
Lingxian Kong 2016-10-25 22:52:42 +13:00
parent dc65464f82
commit bffb2476e7
3 changed files with 54 additions and 30 deletions

View File

@ -76,7 +76,6 @@ class MistralContext(BaseContext):
"project_id",
"auth_token",
"service_catalog",
"target_service_catalog",
"user_name",
"project_name",
"roles",
@ -85,6 +84,7 @@ class MistralContext(BaseContext):
"redelivered",
"expires_at",
"trust_id",
"is_target",
])
def __repr__(self):
@ -108,18 +108,19 @@ def set_ctx(new_ctx):
def context_from_headers_and_env(headers, env):
params = _extract_auth_params_from_headers(headers)
auth_cacert = params['auth_cacert']
auth_token = params['auth_token']
auth_uri = params['auth_uri']
project_id = params['project_id']
user_id = params['user_id']
user_name = params['user_name']
target_service_catalog = params['target_service_catalog']
is_target = params['is_target']
token_info = env.get('keystone.token_info')
token_info = env.get('keystone.token_info', {})
if token_info and target_service_catalog is None:
target_service_catalog = token_info['token']
service_catalog = (params['service_catalog'] if is_target
else token_info.get('token', {}))
return MistralContext(
auth_uri=auth_uri,
@ -127,7 +128,8 @@ def context_from_headers_and_env(headers, env):
user_id=user_id,
project_id=project_id,
auth_token=auth_token,
target_service_catalog=target_service_catalog,
is_target=is_target,
service_catalog=service_catalog,
user_name=user_name,
project_name=headers.get('X-Project-Name'),
roles=headers.get('X-Roles', "").split(","),
@ -137,7 +139,7 @@ def context_from_headers_and_env(headers, env):
def _extract_auth_params_from_headers(headers):
target_service_catalog = None
service_catalog = None
if headers.get("X-Target-Auth-Uri"):
params = {
@ -148,13 +150,17 @@ def _extract_auth_params_from_headers(headers):
'project_id': headers.get('X-Target-Project-Id'),
'user_id': headers.get('X-Target-User-Id'),
'user_name': headers.get('X-Target-User-Name'),
'is_target': True
}
if not params['auth_token']:
raise (exc.MistralException(
'Target auth URI (X-Target-Auth-Uri) target auth token '
'(X-Target-Auth-Token) must be present'))
target_service_catalog = _extract_service_catalog_from_headers(
# It's possible that target service catalog is not provided, in this
# case, Mistral needs to get target service catalog dynamically when
# talking to target openstack deployment later on.
service_catalog = _extract_service_catalog_from_headers(
headers
)
else:
@ -165,9 +171,10 @@ def _extract_auth_params_from_headers(headers):
'project_id': headers.get('X-Project-Id'),
'user_id': headers.get('X-User-Id'),
'user_name': headers.get('X-User-Name'),
'is_target': False
}
params['target_service_catalog'] = target_service_catalog
params['service_catalog'] = service_catalog
return params

View File

@ -12,7 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from mistral import config
from mistral import context as auth_context
from mistral import exceptions
from mistral.tests.unit import base
from mistral.utils.openstack import keystone
@ -23,10 +24,6 @@ class KeystoneUtilsTest(base.BaseTest):
self.values = {'id': 'my_id'}
def override_config(self, name, override, group=None):
config.CONF.set_override(name, override, group)
self.addCleanup(config.CONF.clear_override, name, group)
def test_format_url_dollar_sign(self):
url_template = "http://host:port/v1/$(id)s"
@ -46,3 +43,14 @@ class KeystoneUtilsTest(base.BaseTest):
expected,
keystone.format_url(url_template, self.values)
)
def test_get_endpoint_for_project_noauth(self):
# service_catalog is not set by default.
auth_context.set_ctx(base.get_context())
self.addCleanup(auth_context.set_ctx, None)
self.assertRaises(
exceptions.UnauthorizedException,
keystone.get_endpoint_for_project,
'keystone'
)

View File

@ -20,8 +20,10 @@ from keystoneclient.v3 import client as ks_client
from keystoneclient.v3 import endpoints as ks_endpoints
from oslo_config import cfg
from oslo_utils import timeutils
import six
from mistral import context
from mistral import exceptions
CONF = cfg.CONF
@ -76,30 +78,29 @@ def get_endpoint_for_project(service_name=None, service_type=None):
service_catalog = obtain_service_catalog(ctx)
catalog = service_catalog.get_endpoints(
service_endpoints = service_catalog.get_endpoints(
service_name=service_name,
service_type=service_type
)
endpoint = None
for service_type in catalog:
service = catalog.get(service_type)
for interface in service:
for endpoints in six.itervalues(service_endpoints):
for ep in endpoints:
# is V3 interface?
if 'interface' in interface:
interface_type = interface['interface']
if 'interface' in ep:
interface_type = ep['interface']
if CONF.os_actions_endpoint_type in interface_type:
endpoint = ks_endpoints.Endpoint(
None,
interface,
ep,
loaded=True
)
break
# is V2 interface?
if 'publicURL' in interface:
if 'publicURL' in ep:
endpoint_data = {
'url': interface['publicURL'],
'region': interface['region']
'url': ep['publicURL'],
'region': ep['region']
}
endpoint = ks_endpoints.Endpoint(
None,
@ -122,6 +123,7 @@ def get_endpoint_for_project(service_name=None, service_type=None):
def obtain_service_catalog(ctx):
token = ctx.auth_token
if ctx.is_trust_scoped and is_token_trust_scoped(token):
if ctx.trust_id is None:
raise Exception(
@ -134,22 +136,29 @@ def obtain_service_catalog(ctx):
include_catalog=True
)['token']
else:
if not ctx.target_service_catalog:
response = ctx.service_catalog
# Target service catalog may not be passed via API.
if not response and ctx.is_target:
response = client().tokens.get_token_data(
token,
include_catalog=True)['token']
else:
response = ctx.target_service_catalog
include_catalog=True
)['token']
if not response:
raise exceptions.UnauthorizedException()
service_catalog = ks_service_catalog.ServiceCatalog.factory(response)
return service_catalog
def get_keystone_endpoint_v2():
return get_endpoint_for_project('keystone')
return get_endpoint_for_project('keystone', service_type='identity')
def get_keystone_url_v2():
return get_endpoint_for_project('keystone').url
return get_endpoint_for_project('keystone', service_type='identity').url
def format_url(url_template, values):