deb-mistral/mistral/context.py
Angus Salkeld 60d90e5660 Make sure the context is correctly passed through the rpc
This adds a rpc context serializer to pass the context to the engine and
executor.

Change-Id: I6e76c7e28f475bb815f9184041bc8b848249d419
2014-06-23 15:42:16 +10:00

165 lines
4.6 KiB
Python

# -*- coding: utf-8 -*-
#
# Copyright 2013 - Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import eventlet
from oslo import messaging
from pecan import hooks
from mistral import exceptions as exc
from mistral.openstack.common import jsonutils
from mistral.openstack.common import log as logging
from mistral import utils
LOG = logging.getLogger(__name__)
# TODO(rakhmerov): refactor all threadlocal things with mistral.utils
_CTX_THREAD_LOCAL_NAME = "MISTRAL_APP_CTX_THREAD_LOCAL"
class BaseContext(object):
"""Container for context variables."""
_elements = set()
def __init__(self, __mapping=None, **kwargs):
if __mapping is None:
self.__values = dict(**kwargs)
else:
if isinstance(__mapping, BaseContext):
__mapping = __mapping.__values
self.__values = dict(__mapping)
self.__values.update(**kwargs)
bad_keys = set(self.__values) - self._elements
if bad_keys:
raise TypeError("Only %s keys are supported. %s given" %
(tuple(self._elements), tuple(bad_keys)))
def __getattr__(self, name):
try:
return self.__values[name]
except KeyError:
if name in self._elements:
return None
else:
raise AttributeError(name)
def to_dict(self):
return self.__values
class MistralContext(BaseContext):
_elements = set([
"user_id",
"project_id",
"auth_token",
"service_catalog",
"user_name",
"project_name",
"roles",
"is_admin",
])
def has_ctx():
return utils.has_thread_local(_CTX_THREAD_LOCAL_NAME)
def ctx():
if not has_ctx():
raise exc.ApplicationContextNotFoundException()
return utils.get_thread_local(_CTX_THREAD_LOCAL_NAME)
def set_ctx(new_ctx):
utils.set_thread_local(_CTX_THREAD_LOCAL_NAME, new_ctx)
def _wrapper(context, thread_desc, thread_group, func, *args, **kwargs):
try:
set_ctx(context)
func(*args, **kwargs)
except Exception as e:
LOG.exception("Thread '%s' fails with exception: '%s'"
% (thread_desc, e))
if thread_group and not thread_group.exc:
thread_group.exc = e
thread_group.failed_thread = thread_desc
finally:
if thread_group:
thread_group._on_thread_exit()
set_ctx(None)
def spawn(thread_description, func, *args, **kwargs):
eventlet.spawn(_wrapper, ctx().clone(), thread_description,
None, func, *args, **kwargs)
def context_from_headers(headers):
return MistralContext(
user_id=headers.get('X-User-Id'),
project_id=headers.get('X-Project-Id'),
auth_token=headers.get('X-Auth-Token'),
service_catalog=headers.get('X-Service-Catalog'),
user_name=headers.get('X-User-Name'),
project_name=headers.get('X-Project-Name'),
roles=headers.get('X-Roles', "").split(",")
)
class JsonPayloadSerializer(messaging.NoOpSerializer):
@staticmethod
def serialize_entity(context, entity):
return jsonutils.to_primitive(entity, convert_instances=True)
class RpcContextSerializer(messaging.Serializer):
def __init__(self, base=None):
self._base = base or messaging.NoOpSerializer()
def serialize_entity(self, context, entity):
if not self._base:
return entity
return self._base.serialize_entity(context, entity)
def deserialize_entity(self, context, entity):
if not self._base:
return entity
return self._base.deserialize_entity(context, entity)
def serialize_context(self, context):
return context.to_dict()
def deserialize_context(self, context):
ctx = MistralContext(**context)
set_ctx(ctx)
return ctx
class ContextHook(hooks.PecanHook):
def before(self, state):
request_ctx = context_from_headers(state.request.headers)
set_ctx(request_ctx)
def after(self, state):
set_ctx(None)