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:
parent
05b0989327
commit
44e38c97b7
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
})
|
||||
|
@ -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)
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user