Fixing and refactoring authentication

* Moved pecan configuration to oslo config
* Refactored functional base test
* Got rig of thread local related code in mistral/context.py
* Added new exception ApplicationContextNotFoundException
* Fixed example configuration file
* Made minor cosmetic changes (blank lines, naming)

Change-Id: I1899ce2562a34ebafa20c5735bdf4f0c80dd0175
This commit is contained in:
Renat Akhmerov 2014-01-27 12:11:28 -05:00
parent 05b0989327
commit 44e38c97b7
12 changed files with 136 additions and 121 deletions

View File

@ -15,12 +15,13 @@ default_log_levels = mistral=INFO,mistral.cmd.api=INFO,mistral.api=DEBUG,wsme=DE
#log_config_append = etc/logging.conf
[api]
# Address to bind the API server to
# Host and port to bind the API server to
host = 0.0.0.0
# Port the bind the API server to
port = 8989
[pecan]
auth_enable = True
[database]
#A valid SQLAlchemy connection string
#connection = mysql://root:password@localhost:3306/mistral
@ -34,7 +35,7 @@ rabbit_task_queue = tasks
rabbit_user = guest
rabbit_password = guest
[keystone_authtoken]
[keystone]
auth_uri=http://localhost:5000/v3
auth_host=localhost
auth_port=5000

View File

@ -21,24 +21,13 @@ from oslo.config import cfg
_ENFORCER = None
OPT_GROUP_NAME = 'keystone_authtoken'
auth_token.CONF = cfg.CONF
def register_opts(conf):
"""Register keystoneclient middleware options
"""
conf.register_opts(auth_token.opts,
group=OPT_GROUP_NAME)
auth_token.CONF = conf
register_opts(cfg.CONF)
def install(app, conf):
if conf.app.auth_enable:
return auth_token.AuthProtocol(app,
conf=dict(cfg.CONF.keystone_authtoken))
def setup(app):
if cfg.CONF.pecan.auth_enable:
return auth_token.AuthProtocol(app, conf=dict(cfg.CONF.keystone))
else:
return app

View File

@ -16,17 +16,28 @@
import pecan
from oslo.config import cfg
from mistral import context as ctx
from mistral.api import config as api_config
from mistral.db import api as db_api
from mistral.services import periodic
from mistral.api import access_control as ac
from mistral.api import access_control
def get_pecan_config():
# Set up the pecan configuration
filename = api_config.__file__.replace('.pyc', '.py')
return pecan.configuration.conf_from_file(filename)
# Set up the pecan configuration.
opts = cfg.CONF.pecan
cfg_dict = {
"app": {
"root": opts.root,
"modules": opts.modules,
"debug": opts.debug,
"auth_enable": opts.auth_enable
}
}
return pecan.configuration.conf_from_dict(cfg_dict)
def setup_app(config=None):
@ -46,6 +57,7 @@ def setup_app(config=None):
**app_conf
)
app = ac.install(app, config)
# Set up access control.
app = access_control.setup(app)
return app

View File

@ -19,6 +19,7 @@ Configuration options registration and useful routines.
"""
from oslo.config import cfg
from keystoneclient.middleware import auth_token
from mistral.openstack.common import log
from mistral import version
@ -29,6 +30,14 @@ api_opts = [
cfg.IntOpt('port', default=8989, help='Mistral API server port')
]
pecan_opts = [
cfg.StrOpt('root', default='mistral.api.controllers.root.RootController',
help='Pecan root controller'),
cfg.ListOpt('modules', default=["mistral.api"]),
cfg.BoolOpt('debug', default=False),
cfg.BoolOpt('auth_enable', default=True)
]
db_opts = [
# TODO: add DB properties.
]
@ -48,6 +57,8 @@ rabbit_opts = [
CONF = cfg.CONF
CONF.register_opts(api_opts, group='api')
CONF.register_opts(pecan_opts, group='pecan')
CONF.register_opts(auth_token.opts, group='keystone')
CONF.register_opts(db_opts, group='database')
CONF.register_opts(rabbit_opts, group='rabbit')

View File

@ -15,15 +15,19 @@
# limitations under the License.
from pecan.hooks import PecanHook
import threading
import eventlet
from eventlet import corolocal
from mistral.openstack.common import log as logging
from mistral import utils
from mistral import exceptions as exc
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"""
@ -38,7 +42,9 @@ class BaseContext(object):
__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)))
@ -69,34 +75,19 @@ class MistralContext(BaseContext):
])
_CTXS = threading.local()
_CTXS._curr_ctxs = {}
def has_ctx():
ident = corolocal.get_ident()
return ident in _CTXS._curr_ctxs and _CTXS._curr_ctxs[ident]
return utils.has_thread_local(_CTX_THREAD_LOCAL_NAME)
def ctx():
if not has_ctx():
# TODO(akuznetsov): replace with specific error
raise RuntimeError("Context isn't available here")
return _CTXS._curr_ctxs[corolocal.get_ident()]
raise exc.ApplicationContextNotFoundException()
def current():
return ctx()
return utils.get_thread_local(_CTX_THREAD_LOCAL_NAME)
def set_ctx(new_ctx):
ident = corolocal.get_ident()
if not new_ctx and ident in _CTXS._curr_ctxs:
del _CTXS._curr_ctxs[ident]
if new_ctx:
_CTXS._curr_ctxs[ident] = new_ctx
utils.set_thread_local(_CTX_THREAD_LOCAL_NAME, new_ctx)
def _wrapper(ctx, thread_description, thread_group, func, *args, **kwargs):
@ -117,7 +108,7 @@ def _wrapper(ctx, thread_description, thread_group, func, *args, **kwargs):
def spawn(thread_description, func, *args, **kwargs):
eventlet.spawn(_wrapper, current().clone(), thread_description,
eventlet.spawn(_wrapper, ctx().clone(), thread_description,
None, func, *args, **kwargs)

View File

@ -62,3 +62,13 @@ class EngineException(MistralException):
super(MistralException, self).__init__(message)
if message:
self.message = message
class ApplicationContextNotFoundException(MistralException):
message = "Application context not found"
code = "APP_CTX_NOT_FOUND_ERROR"
def __init__(self, message=None):
super(MistralException, self).__init__(message)
if message:
self.message = message

View File

@ -22,8 +22,8 @@ from mistral.openstack.common import periodic_task
from mistral.openstack.common import threadgroup
from mistral import context
from mistral import dsl
from mistral.services import scheduler as s
from mistral.services import trusts as t
from mistral.services import scheduler as sched
from mistral.services import trusts
LOG = log.getLogger(__name__)
@ -32,20 +32,24 @@ class MistralPeriodicTasks(periodic_task.PeriodicTasks):
@periodic_task.periodic_task(spacing=1, run_immediately=True)
def scheduler_events(self, ctx):
LOG.debug('Processing next Scheduler events.')
for event in s.get_next_events():
workbook = db_api.workbook_get(event['workbook_name'])
ctx = t.create_context(workbook)
context.set_ctx(ctx)
target_task = dsl.Parser(
workbook['definition']).get_event_task_name(event['name'])
engine.start_workflow_execution(workbook['name'], target_task)
s.set_next_execution_time(event)
context.set_ctx(None)
for event in sched.get_next_events():
wb = db_api.workbook_get(event['workbook_name'])
context.set_ctx(trusts.create_context(wb))
try:
target_task = dsl.Parser(
wb['definition']).get_event_task_name(event['name'])
engine.start_workflow_execution(wb['name'], target_task)
finally:
sched.set_next_execution_time(event)
context.set_ctx(None)
def setup():
tg = threadgroup.ThreadGroup()
pt = MistralPeriodicTasks()
tg.add_dynamic_timer(
pt.run_periodic_tasks,
initial_delay=None,

View File

@ -29,9 +29,9 @@ def create_trust(workbook):
ctx = context.current()
admin_user = CONF.keystone_authtoken.admin_user
admin_password = CONF.keystone_authtoken.admin_password
admin_tenant_name = CONF.keystone_authtoken.admin_tenant_name
admin_user = CONF.keystone.admin_user
admin_password = CONF.keystone.admin_password
admin_tenant_name = CONF.keystone.admin_tenant_name
trustee_id = keystone.client_for_trusts(
admin_user,
@ -50,31 +50,38 @@ def create_trust(workbook):
def create_context(workbook):
if not workbook.trust_id:
if 'trust_id' not in workbook:
return
admin_user = CONF.keystone_authtoken.admin_user
admin_password = CONF.keystone_authtoken.admin_password
admin_user = CONF.keystone.admin_user
admin_password = CONF.keystone.admin_password
client = keystone.client_for_trusts(
admin_user,
admin_password,
trust_id=workbook['trust_id'],
project_id=workbook['project_id'])
if CONF.pecan.auth_enable:
client = keystone.client_for_trusts(
admin_user,
admin_password,
trust_id=workbook['trust_id'],
project_id=workbook['project_id'])
return context.MistralContext(
user_id=client.user_id,
project_id=workbook['project_id'],
auth_token=client.auth_token
)
return context.MistralContext(
user_id=client.user_id,
project_id=workbook['project_id'],
auth_token=client.auth_token
)
else:
return context.MistralContext(
user_id=None,
project_id=None,
auth_token=None
)
def delete_trust(workbook):
if workbook.trust_id:
if 'trust_id' not in workbook:
return
admin_user = CONF.keystone_authtoken.admin_user
admin_password = CONF.keystone_authtoken.admin_password
admin_user = CONF.keystone.admin_user
admin_password = CONF.keystone.admin_password
keystone_client = keystone.client_for_trusts(
admin_user,

View File

@ -14,13 +14,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo.config import cfg
from mistral.db import api as db_api
from mistral.services import trusts
def create_workbook(values):
workbook = db_api.workbook_create(values)
workbook = trusts.create_trust(workbook)
if cfg.CONF.pecan.auth_enable:
workbook = trusts.create_trust(workbook)
##TODO(akuznetsov) filter fields
##TODO(akuznetsov) create events

View File

@ -17,8 +17,19 @@
import pecan
import pecan.testing
from oslo.config import cfg
from mistral.openstack.common import importutils
from mistral.tests.unit import base as test_base
# We need to make sure that all configuration properties are registered.
importutils.import_module("mistral.config")
# Disable authentication for functional tests.
cfg.CONF.unregister_opt(cfg.BoolOpt('auth_enable'), 'pecan')
cfg.CONF.register_opt(cfg.BoolOpt('auth_enable', default=False), group='pecan')
__all__ = ['FunctionalTest']
@ -30,11 +41,13 @@ class FunctionalTest(test_base.DbTestCase):
def setUp(self):
super(FunctionalTest, self).setUp()
pecan_opts = cfg.CONF.pecan
self.app = pecan.testing.load_test_app({
'app': {
'root': 'mistral.api.controllers.root.RootController',
'modules': ['mistral.api'],
'debug': False,
'root': pecan_opts.root,
'modules': pecan_opts.modules,
'debug': pecan_opts.debug,
'auth_enable': False
}
})

View File

@ -1,27 +0,0 @@
# -*- 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.
from mistral.api import app as api_app
from mistral.api import config as api_config
from mistral.tests.api.base import FunctionalTest
class TestAppConfig(FunctionalTest):
def test_get_pecan_config(self):
config = api_app.get_pecan_config()
self.assertEqual(dict(config.app), api_config.app)

View File

@ -22,7 +22,7 @@ CONF = cfg.CONF
def client():
ctx = context.current()
ctx = context.ctx()
auth_url = CONF.keystone_authtoken.auth_uri
keystone = keystone_client.Client(username=ctx['user_name'],
@ -30,21 +30,20 @@ def client():
tenant_id=ctx['project_id'],
auth_url=auth_url)
keystone.management_url = auth_url
return keystone
def client_for_trusts(username, password,
project_name=None,
trust_id=None,
def client_for_trusts(username, password, project_name=None, trust_id=None,
project_id=None):
auth_url = CONF.keystone_authtoken.auth_uri
keystone = keystone_client.Client(username=username,
password=password,
tenant_name=project_name,
tenant_id=project_id,
auth_url=auth_url,
trust_id=trust_id)
client = keystone_client.Client(username=username,
password=password,
tenant_name=project_name,
tenant_id=project_id,
auth_url=auth_url,
trust_id=trust_id)
client.management_url = auth_url
keystone.management_url = auth_url
return keystone
return client