Major refactoring of how OS clients are created and managed

* Single universal ClientManager class was dropped in favor of
   of individual in-context methods to create OS clients without
   ClientManager restrictions.
* Environment class was renamed to ExecutionSession to avoid
   common confusion with io.murano.Environment
* execution_session_local module was introduced to simplify
   keep of per-execution session (per-deployment) data. This
   is similar to thread-locals with the difference that there can
   be many threads in single session.
* All OS-clients related code was migrated to keystone client
   sessions and API v3 (except for GLARE and Mistral that doesn't
   support sessions). This increases performance and solves
   authentication problems that could be caused by token expiration
   even with trusts enabled.
* [DEFAULT]/home_region setting was introduced instead of
   [murano]/region_for_services to configure what region
   should be used by the clients by default (where Murano API
   resides). All client factories respect this setting.

Change-Id: If02c7e5d7d39574d0621e0e8dc27d1f501a31984
This commit is contained in:
Stan Lagun 2016-02-04 03:14:37 +03:00
parent 12a8b87302
commit fc76b3b1b4
27 changed files with 608 additions and 591 deletions

View File

@ -21,7 +21,7 @@ from oslo_config import cfg as config
from oslo_log import log as logging from oslo_log import log as logging
import murano.dsl.helpers as helpers from murano.common import auth_utils
CONF = config.CONF CONF = config.CONF
@ -30,9 +30,7 @@ LOG = logging.getLogger(__name__)
class GlanceClient(object): class GlanceClient(object):
def __init__(self, context): def __init__(self, context):
client_manager = helpers.get_environment(context).clients self.client = self.create_glance_client()
self.client = client_manager.get_client("glance", True,
self.create_glance_client)
def list(self): def list(self):
images = self.client.images.list() images = self.client.images.list()
@ -67,14 +65,11 @@ class GlanceClient(object):
def init_plugin(cls): def init_plugin(cls):
cls.CONF = cfg.init_config(CONF) cls.CONF = cfg.init_config(CONF)
def create_glance_client(self, keystone_client, auth_token): def create_glance_client(self):
LOG.debug("Creating a glance client") LOG.debug("Creating a glance client")
glance_endpoint = keystone_client.service_catalog.url_for( params = auth_utils.get_session_client_parameters(
service_type='image', endpoint_type=self.CONF.endpoint_type) service_type='image', conf=self.CONF)
client = glanceclient.Client(self.CONF.api_version, return glanceclient.Client(self.CONF.api_version, **params)
endpoint=glance_endpoint,
token=auth_token)
return client
class AmbiguousNameException(Exception): class AmbiguousNameException(Exception):

View File

@ -33,8 +33,7 @@ from murano.common import engine
from murano.dsl import exceptions from murano.dsl import exceptions
from murano.dsl import executor from murano.dsl import executor
from murano.dsl import helpers from murano.dsl import helpers
from murano.engine import client_manager from murano.engine import execution_session
from murano.engine import environment
from murano.engine import mock_context_manager from murano.engine import mock_context_manager
from murano.engine import package_loader from murano.engine import package_loader
@ -202,19 +201,14 @@ class MuranoTestRunner(object):
ks_opts = self._validate_keystone_opts(self.args) ks_opts = self._validate_keystone_opts(self.args)
client = ks_client.Client(**ks_opts) client = ks_client.Client(**ks_opts)
test_env = environment.Environment() test_session = execution_session.ExecutionSession()
test_env.token = client.auth_token test_session.token = client.auth_token
test_env.tenant_id = client.auth_tenant_id test_session.project_id = client.project_id
test_env.clients = client_manager.ClientManager(test_env)
murano_client_factory = lambda: \
test_env.clients.get_murano_client(test_env)
# Replace location of loading packages with provided from command line. # Replace location of loading packages with provided from command line.
if load_packages_from: if load_packages_from:
cfg.CONF.packages_opts.load_packages_from = load_packages_from cfg.CONF.packages_opts.load_packages_from = load_packages_from
with package_loader.CombinedPackageLoader( with package_loader.CombinedPackageLoader(test_session) as pkg_loader:
murano_client_factory, client.tenant_id) as pkg_loader:
engine.get_plugin_loader().register_in_loader(pkg_loader) engine.get_plugin_loader().register_in_loader(pkg_loader)
package = self._load_package(pkg_loader, provided_pkg_name) package = self._load_package(pkg_loader, provided_pkg_name)
@ -236,13 +230,13 @@ class MuranoTestRunner(object):
dsl_executor = executor.MuranoDslExecutor( dsl_executor = executor.MuranoDslExecutor(
pkg_loader, pkg_loader,
mock_context_manager.MockContextManager(), mock_context_manager.MockContextManager(),
test_env) test_session)
obj = package.find_class(pkg_class, False).new( obj = package.find_class(pkg_class, False).new(
None, dsl_executor.object_store, dsl_executor)(None) None, dsl_executor.object_store, dsl_executor)(None)
self._call_service_method('setUp', dsl_executor, obj) self._call_service_method('setUp', dsl_executor, obj)
obj.type.methods[m].usage = 'Action' obj.type.methods[m].usage = 'Action'
test_env.start() test_session.start()
try: try:
obj.type.invoke(m, dsl_executor, obj, (), {}) obj.type.invoke(m, dsl_executor, obj, (), {})
LOG.debug('\n.....{0}.{1}.....OK'.format(obj.type.name, LOG.debug('\n.....{0}.{1}.....OK'.format(obj.type.name,
@ -254,7 +248,7 @@ class MuranoTestRunner(object):
''.format(obj.type.name, m)) ''.format(obj.type.name, m))
exit_code = 1 exit_code = 1
finally: finally:
test_env.finish() test_session.finish()
return exit_code return exit_code
def get_parser(self): def get_parser(self):

View File

@ -1,4 +1,4 @@
# Copyright (c) 2014 Mirantis, Inc. # Copyright (c) 2016 Mirantis, Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
@ -13,92 +13,151 @@
# under the License. # under the License.
from keystoneclient.auth import identity
from keystoneclient import session as ks_session
from keystoneclient.v3 import client as ks_client from keystoneclient.v3 import client as ks_client
from oslo_config import cfg from oslo_config import cfg
from oslo_utils import importutils from oslo_utils import importutils
from murano.dsl import helpers
def get_client(token, project_id):
settings = _get_keystone_settings()
kwargs = {
'token': token,
'project_id': project_id,
'auth_url': settings['auth_url']
}
kwargs.update(settings['ssl'])
kwargs['region_name'] = settings['region_name']
keystone = ks_client.Client(**kwargs)
keystone.management_url = settings['auth_url']
return keystone
def get_client_for_admin(project_name): @helpers.memoize
return _admin_client(project_name=project_name) def _get_keystone_admin_parameters(scoped):
def _admin_client(trust_id=None, project_name=None):
settings = _get_keystone_settings()
kwargs = {
'project_name': project_name,
'trust_id': trust_id
}
for key in ('username', 'password', 'auth_url'):
kwargs[key] = settings[key]
kwargs.update(settings['ssl'])
kwargs['region_name'] = settings['region_name']
client = ks_client.Client(**kwargs)
# without resetting this attributes keystone client cannot re-authenticate
client.project_id = None
client.project_name = None
client.management_url = settings['auth_url']
return client
def get_client_for_trusts(trust_id):
return _admin_client(trust_id)
def create_trust(token, project_id):
client = get_client(token, project_id)
settings = _get_keystone_settings()
trustee_id = get_client_for_admin(
settings['project_name']).user_id
roles = [t['name'] for t in client.auth_ref['roles']]
trust = client.trusts.create(trustor_user=client.user_id,
trustee_user=trustee_id,
impersonation=True,
role_names=roles,
project=project_id)
return trust.id
def delete_trust(trust_id):
keystone_client = get_client_for_trusts(trust_id)
keystone_client.trusts.delete(trust_id)
def _get_keystone_settings():
importutils.import_module('keystonemiddleware.auth_token') importutils.import_module('keystonemiddleware.auth_token')
return { settings = {
'auth_url': cfg.CONF.keystone_authtoken.auth_uri.replace('v2.0', 'v3'), 'auth_url': cfg.CONF.keystone_authtoken.auth_uri.replace('v2.0', 'v3'),
'username': cfg.CONF.keystone_authtoken.admin_user, 'username': cfg.CONF.keystone_authtoken.admin_user,
'password': cfg.CONF.keystone_authtoken.admin_password, 'password': cfg.CONF.keystone_authtoken.admin_password,
'project_name': cfg.CONF.keystone_authtoken.admin_tenant_name, 'user_domain_name': 'default'
'ssl': {
'cacert': cfg.CONF.keystone.ca_file,
'insecure': cfg.CONF.keystone.insecure,
'cert': cfg.CONF.keystone.cert_file,
'key': cfg.CONF.keystone.key_file,
},
'region_name': cfg.CONF.murano.region_name_for_services
} }
if scoped:
settings.update({
'project_name': cfg.CONF.keystone_authtoken.admin_tenant_name,
'project_domain_name': 'default'
})
return settings
@helpers.memoize
def create_keystone_admin_client(scoped):
kwargs = _get_keystone_admin_parameters(scoped)
password_auth = identity.Password(**kwargs)
session = ks_session.Session(auth=password_auth)
_set_ssl_parameters(cfg.CONF.keystone_authtoken, session)
return ks_client.Client(session=session)
def get_client_session(execution_session=None, conf=None):
if not execution_session:
execution_session = helpers.get_execution_session()
trust_id = execution_session.trust_id
if trust_id is None:
return get_token_client_session(
token=execution_session.token,
project_id=execution_session.project_id)
kwargs = _get_keystone_admin_parameters(False)
kwargs['trust_id'] = trust_id
password_auth = identity.Password(**kwargs)
session = ks_session.Session(auth=password_auth)
_set_ssl_parameters(conf, session)
return session
def get_token_client_session(token=None, project_id=None, conf=None):
auth_url = _get_keystone_admin_parameters(False)['auth_url']
if token is None or project_id is None:
execution_session = helpers.get_execution_session()
token = execution_session.token
project_id = execution_session.project_id
token_auth = identity.Token(auth_url, token=token, project_id=project_id)
session = ks_session.Session(auth=token_auth)
_set_ssl_parameters(conf, session)
return session
def create_keystone_client(token=None, project_id=None, conf=None):
return ks_client.Client(session=get_token_client_session(
token=token, project_id=project_id, conf=conf))
def create_trust(trustee_token=None, trustee_project_id=None):
admin_client = create_keystone_admin_client(True)
user_client = create_keystone_client(
token=trustee_token, project_id=trustee_project_id)
trustee_user = admin_client.session.auth.get_user_id(admin_client.session)
auth_ref = user_client.session.auth.get_access(user_client.session)
trustor_user = auth_ref.user_id
project = auth_ref.project_id
roles = auth_ref.role_names
trust = user_client.trusts.create(
trustor_user=trustor_user,
trustee_user=trustee_user,
impersonation=True,
role_names=roles,
project=project)
return trust.id
def delete_trust(trust):
user_client = create_keystone_admin_client(True)
user_client.trusts.delete(trust)
def _get_config_option(conf_section, option_names, default=None):
if not isinstance(option_names, (list, tuple)):
option_names = (option_names,)
for name in option_names:
if hasattr(conf_section, name):
return getattr(conf_section, name)
return default
def _set_ssl_parameters(conf_section, session):
if not conf_section:
return
insecure = _get_config_option(conf_section, 'insecure', False)
if insecure:
session.verify = False
else:
session.verify = _get_config_option(
conf_section, ('ca_file', 'cafile', 'cacert')) or True
cert_file = _get_config_option(conf_section, ('cert_file', 'certfile'))
key_file = _get_config_option(conf_section, ('key_file', 'keyfile'))
if cert_file and key_file:
session.cert = (cert_file, key_file)
elif cert_file:
session.cert = cert_file
else:
session.cert = None
def get_session_client_parameters(service_type=None,
region='',
interface=None,
service_name=None,
conf=None,
session=None,
execution_session=None):
if region == '':
region = cfg.CONF.home_region
result = {
'session': session or get_client_session(
execution_session=execution_session, conf=conf)
}
url = _get_config_option(conf, 'url')
if url:
result['endpoint_override'] = url
else:
if not interface:
interface = _get_config_option(conf, 'endpoint_type')
result.update({
'service_type': service_type,
'service_name': service_name,
'interface': interface,
'region_name': region
})
return result

View File

@ -58,6 +58,8 @@ rabbit_opts = [
] ]
heat_opts = [ heat_opts = [
cfg.StrOpt('url', help='Optional heat endpoint override'),
cfg.BoolOpt('insecure', default=False, cfg.BoolOpt('insecure', default=False,
help='This option explicitly allows Murano to perform ' help='This option explicitly allows Murano to perform '
'"insecure" SSL connections and transfers with Heat API.'), '"insecure" SSL connections and transfers with Heat API.'),
@ -82,13 +84,26 @@ heat_opts = [
] ]
mistral_opts = [ mistral_opts = [
cfg.StrOpt('url', help='Optional mistral endpoint override'),
cfg.StrOpt('endpoint_type', default='publicURL', cfg.StrOpt('endpoint_type', default='publicURL',
help='Mistral endpoint type.'), help='Mistral endpoint type.'),
cfg.StrOpt('service_type', default='workflowv2', cfg.StrOpt('service_type', default='workflowv2',
help='Mistral service type.') help='Mistral service type.'),
cfg.BoolOpt('insecure', default=False,
help='This option explicitly allows Murano to perform '
'"insecure" SSL connections and transfers with Mistral.'),
cfg.StrOpt('ca_cert',
help='(SSL) Tells Murano to use the specified client '
'certificate file when communicating with Mistral.')
] ]
neutron_opts = [ neutron_opts = [
cfg.StrOpt('url', help='Optional neutron endpoint override'),
cfg.BoolOpt('insecure', default=False, cfg.BoolOpt('insecure', default=False,
help='This option explicitly allows Murano to perform ' help='This option explicitly allows Murano to perform '
'"insecure" SSL connections and transfers with Neutron API.'), '"insecure" SSL connections and transfers with Neutron API.'),
@ -101,24 +116,6 @@ neutron_opts = [
help='Neutron endpoint type.') help='Neutron endpoint type.')
] ]
keystone_opts = [
cfg.BoolOpt('insecure', default=False,
help='This option explicitly allows Murano to perform '
'"insecure" SSL connections and transfers with '
'Keystone API running Kyestone API.'),
cfg.StrOpt('ca_file',
help='(SSL) Tells Murano to use the specified certificate file '
'to verify the peer when communicating with Keystone.'),
cfg.StrOpt('cert_file',
help='(SSL) Tells Murano to use the specified client '
'certificate file when communicating with Keystone.'),
cfg.StrOpt('key_file', help='(SSL/SSH) Private key file name to '
'communicate with Keystone API')
]
murano_opts = [ murano_opts = [
cfg.StrOpt('url', help='Optional murano url in format ' cfg.StrOpt('url', help='Optional murano url in format '
'like http://0.0.0.0:8082 used by Murano engine'), 'like http://0.0.0.0:8082 used by Murano engine'),
@ -148,10 +145,7 @@ murano_opts = [
cfg.ListOpt('enabled_plugins', cfg.ListOpt('enabled_plugins',
help="List of enabled Extension Plugins. " help="List of enabled Extension Plugins. "
"Remove or leave commented to enable all installed " "Remove or leave commented to enable all installed "
"plugins."), "plugins.")
cfg.StrOpt('region_name_for_services',
help="Default region name used to get services endpoints.")
] ]
networking_opts = [ networking_opts = [
@ -271,6 +265,11 @@ file_server = [
help='Set a file server.') help='Set a file server.')
] ]
home_region = cfg.StrOpt(
'home_region', default=None,
help="Default region name used to get services endpoints.")
CONF = cfg.CONF CONF = cfg.CONF
CONF.register_opts(paste_deploy_opts, group='paste_deploy') CONF.register_opts(paste_deploy_opts, group='paste_deploy')
CONF.register_cli_opts(bind_opts) CONF.register_cli_opts(bind_opts)
@ -278,10 +277,10 @@ CONF.register_opts(rabbit_opts, group='rabbitmq')
CONF.register_opts(heat_opts, group='heat') CONF.register_opts(heat_opts, group='heat')
CONF.register_opts(mistral_opts, group='mistral') CONF.register_opts(mistral_opts, group='mistral')
CONF.register_opts(neutron_opts, group='neutron') CONF.register_opts(neutron_opts, group='neutron')
CONF.register_opts(keystone_opts, group='keystone')
CONF.register_opts(murano_opts, group='murano') CONF.register_opts(murano_opts, group='murano')
CONF.register_opts(engine_opts, group='engine') CONF.register_opts(engine_opts, group='engine')
CONF.register_opts(file_server) CONF.register_opts(file_server)
CONF.register_opt(home_region)
CONF.register_cli_opts(metadata_dir) CONF.register_cli_opts(metadata_dir)
CONF.register_opts(packages_opts, group='packages_opts') CONF.register_opts(packages_opts, group='packages_opts')
CONF.register_opts(stats_opts, group='stats') CONF.register_opts(stats_opts, group='stats')

View File

@ -34,7 +34,7 @@ from murano.dsl import dsl_exception
from murano.dsl import executor as dsl_executor from murano.dsl import executor as dsl_executor
from murano.dsl import helpers from murano.dsl import helpers
from murano.dsl import serializer from murano.dsl import serializer
from murano.engine import environment from murano.engine import execution_session
from murano.engine import package_loader from murano.engine import package_loader
from murano.engine.system import status_reporter from murano.engine.system import status_reporter
from murano.engine.system import yaql_functions from murano.engine.system import yaql_functions
@ -125,8 +125,8 @@ class TaskExecutor(object):
return self._action return self._action
@property @property
def environment(self): def session(self):
return self._environment return self._session
@property @property
def model(self): def model(self):
@ -137,14 +137,14 @@ class TaskExecutor(object):
reporter = status_reporter.StatusReporter(task['id']) reporter = status_reporter.StatusReporter(task['id'])
self._action = task.get('action') self._action = task.get('action')
self._model = task['model'] self._model = task['model']
self._environment = environment.Environment() self._session = execution_session.ExecutionSession()
self._environment.token = task['token'] self._session.token = task['token']
self._environment.tenant_id = task['tenant_id'] self._session.project_id = task['tenant_id']
self._environment.system_attributes = self._model.get('SystemData', {}) self._session.system_attributes = self._model.get('SystemData', {})
self._reporter = reporter self._reporter = reporter
self._model_policy_enforcer = enforcer.ModelPolicyEnforcer( self._model_policy_enforcer = enforcer.ModelPolicyEnforcer(
self._environment) self._session)
def execute(self): def execute(self):
try: try:
@ -152,13 +152,9 @@ class TaskExecutor(object):
except Exception as e: except Exception as e:
return self.exception_result(e, None, '<system>') return self.exception_result(e, None, '<system>')
murano_client_factory = \ with package_loader.CombinedPackageLoader(self._session) as pkg_loader:
lambda: self._environment.clients.get_murano_client()
with package_loader.CombinedPackageLoader(
murano_client_factory,
self._environment.tenant_id) as pkg_loader:
result = self._execute(pkg_loader) result = self._execute(pkg_loader)
self._model['SystemData'] = self._environment.system_attributes self._model['SystemData'] = self._session.system_attributes
result['model'] = self._model result['model'] = self._model
if (not self._model.get('Objects') and if (not self._model.get('Objects') and
@ -174,7 +170,7 @@ class TaskExecutor(object):
get_plugin_loader().register_in_loader(pkg_loader) get_plugin_loader().register_in_loader(pkg_loader)
executor = dsl_executor.MuranoDslExecutor( executor = dsl_executor.MuranoDslExecutor(
pkg_loader, ContextManager(), self.environment) pkg_loader, ContextManager(), self.session)
try: try:
obj = executor.load(self.model) obj = executor.load(self.model)
except Exception as e: except Exception as e:
@ -188,20 +184,20 @@ class TaskExecutor(object):
try: try:
LOG.debug('Invoking pre-cleanup hooks') LOG.debug('Invoking pre-cleanup hooks')
self.environment.start() self.session.start()
executor.cleanup(self._model) executor.cleanup(self._model)
except Exception as e: except Exception as e:
return self.exception_result(e, obj, '<GC>') return self.exception_result(e, obj, '<GC>')
finally: finally:
LOG.debug('Invoking post-cleanup hooks') LOG.debug('Invoking post-cleanup hooks')
self.environment.finish() self.session.finish()
self._model['ObjectsCopy'] = copy.deepcopy(self._model.get('Objects')) self._model['ObjectsCopy'] = copy.deepcopy(self._model.get('Objects'))
action_result = None action_result = None
if self.action: if self.action:
try: try:
LOG.debug('Invoking pre-execution hooks') LOG.debug('Invoking pre-execution hooks')
self.environment.start() self.session.start()
try: try:
action_result = self._invoke(executor) action_result = self._invoke(executor)
finally: finally:
@ -213,7 +209,7 @@ class TaskExecutor(object):
return self.exception_result(e, obj, self.action['method']) return self.exception_result(e, obj, self.action['method'])
finally: finally:
LOG.debug('Invoking post-execution hooks') LOG.debug('Invoking post-execution hooks')
self.environment.finish() self.session.finish()
try: try:
action_result = serializer.serialize(action_result) action_result = serializer.serialize(action_result)
@ -266,16 +262,16 @@ class TaskExecutor(object):
def _create_trust(self): def _create_trust(self):
if not CONF.engine.use_trusts: if not CONF.engine.use_trusts:
return return
trust_id = self._environment.system_attributes.get('TrustId') trust_id = self._session.system_attributes.get('TrustId')
if not trust_id: if not trust_id:
trust_id = auth_utils.create_trust(self._environment.token, trust_id = auth_utils.create_trust(
self._environment.tenant_id) self._session.token, self._session.project_id)
self._environment.system_attributes['TrustId'] = trust_id self._session.system_attributes['TrustId'] = trust_id
self._environment.trust_id = trust_id self._session.trust_id = trust_id
def _delete_trust(self): def _delete_trust(self):
trust_id = self._environment.trust_id trust_id = self._session.trust_id
if trust_id: if trust_id:
auth_utils.delete_trust(self._environment.trust_id) auth_utils.delete_trust(self._session.trust_id)
self._environment.system_attributes['TrustId'] = None self._session.system_attributes['TrustId'] = None
self._environment.trust_id = None self._session.trust_id = None

View File

@ -263,9 +263,10 @@ class EnvironmentServices(object):
@staticmethod @staticmethod
def get_network_driver(context): def get_network_driver(context):
ks = auth_utils.get_client(context.auth_token, context.tenant) session = auth_utils.get_token_client_session(
context.auth_token, context.tenant)
try: try:
ks.service_catalog.url_for(service_type='network') session.get_endpoint(service_type='network')
except ks_exceptions.EndpointNotFound: except ks_exceptions.EndpointNotFound:
LOG.debug("Will use NovaNetwork as a network driver") LOG.debug("Will use NovaNetwork as a network driver")
return "nova" return "nova"

View File

@ -25,15 +25,15 @@ CTX_CALLER_CONTEXT = '$?callerContext'
CTX_CURRENT_INSTRUCTION = '$?currentInstruction' CTX_CURRENT_INSTRUCTION = '$?currentInstruction'
CTX_CURRENT_EXCEPTION = '$?currentException' CTX_CURRENT_EXCEPTION = '$?currentException'
CTX_CURRENT_METHOD = '$?currentMethod' CTX_CURRENT_METHOD = '$?currentMethod'
CTX_ENVIRONMENT = '$?environment'
CTX_EXECUTOR = '$?executor' CTX_EXECUTOR = '$?executor'
CTX_EXECUTION_SESSION = '$?executionSession'
CTX_ORIGINAL_CONTEXT = '$?originalContext'
CTX_PACKAGE_LOADER = '$?packageLoader' CTX_PACKAGE_LOADER = '$?packageLoader'
CTX_SKIP_FRAME = '$?skipFrame' CTX_SKIP_FRAME = '$?skipFrame'
CTX_THIS = '$?this' CTX_THIS = '$?this'
CTX_TYPE = '$?type' CTX_TYPE = '$?type'
CTX_VARIABLE_SCOPE = '$?variableScope' CTX_VARIABLE_SCOPE = '$?variableScope'
CTX_YAQL_ENGINE = '$?yaqlEngine' CTX_YAQL_ENGINE = '$?yaqlEngine'
CTX_ORIGINAL_CONTEXT = '$?originalContext'
DM_OBJECTS = 'Objects' DM_OBJECTS = 'Objects'
DM_OBJECTS_COPY = 'ObjectsCopy' DM_OBJECTS_COPY = 'ObjectsCopy'
@ -45,6 +45,10 @@ META_NO_TRACE = '?noTrace'
CORE_LIBRARY = 'io.murano' CORE_LIBRARY = 'io.murano'
CORE_LIBRARY_OBJECT = 'io.murano.Object' CORE_LIBRARY_OBJECT = 'io.murano.Object'
TL_CONTEXT = '__murano_context'
TL_ID = '__thread_id'
TL_SESSION = '__murano_execution_session'
RUNTIME_VERSION_1_0 = semantic_version.Version('1.0.0') RUNTIME_VERSION_1_0 = semantic_version.Version('1.0.0')
RUNTIME_VERSION_1_1 = semantic_version.Version('1.1.0') RUNTIME_VERSION_1_1 = semantic_version.Version('1.1.0')
RUNTIME_VERSION_1_2 = semantic_version.Version('1.2.0') RUNTIME_VERSION_1_2 = semantic_version.Version('1.2.0')

View File

@ -247,8 +247,8 @@ class Interfaces(object):
return MuranoObjectInterface(mpl_object) return MuranoObjectInterface(mpl_object)
@property @property
def environment(self): def execution_session(self):
return helpers.get_environment() return helpers.get_execution_session()
@property @property
def caller(self): def caller(self):

View File

@ -39,10 +39,10 @@ LOG = logging.getLogger(__name__)
class MuranoDslExecutor(object): class MuranoDslExecutor(object):
def __init__(self, package_loader, context_manager, environment=None): def __init__(self, package_loader, context_manager, session=None):
self._package_loader = package_loader self._package_loader = package_loader
self._context_manager = context_manager self._context_manager = context_manager
self._environment = environment self._session = session
self._attribute_store = attribute_store.AttributeStore() self._attribute_store = attribute_store.AttributeStore()
self._object_store = object_store.ObjectStore(self) self._object_store = object_store.ObjectStore(self)
self._locks = {} self._locks = {}
@ -66,6 +66,12 @@ class MuranoDslExecutor(object):
def invoke_method(self, method, this, context, args, kwargs, def invoke_method(self, method, this, context, args, kwargs,
skip_stub=False): skip_stub=False):
with helpers.execution_session(self._session):
return self._invoke_method(
method, this, context, args, kwargs, skip_stub=skip_stub)
def _invoke_method(self, method, this, context, args, kwargs,
skip_stub=False):
if isinstance(this, dsl.MuranoObjectInterface): if isinstance(this, dsl.MuranoObjectInterface):
this = this.object this = this.object
kwargs = utils.filter_parameters_dict(kwargs) kwargs = utils.filter_parameters_dict(kwargs)
@ -190,6 +196,10 @@ class MuranoDslExecutor(object):
return tuple(), parameter_values return tuple(), parameter_values
def load(self, data): def load(self, data):
with helpers.execution_session(self._session):
return self._load(data)
def _load(self, data):
if not isinstance(data, dict): if not isinstance(data, dict):
raise TypeError() raise TypeError()
self._attribute_store.load(data.get(constants.DM_ATTRIBUTES) or []) self._attribute_store.load(data.get(constants.DM_ATTRIBUTES) or [])
@ -199,6 +209,10 @@ class MuranoDslExecutor(object):
return dsl.MuranoObjectInterface(result, executor=self) return dsl.MuranoObjectInterface(result, executor=self)
def cleanup(self, data): def cleanup(self, data):
with helpers.execution_session(self._session):
return self._cleanup(data)
def _cleanup(self, data):
objects_copy = data.get(constants.DM_OBJECTS_COPY) objects_copy = data.get(constants.DM_OBJECTS_COPY)
if not objects_copy: if not objects_copy:
return return
@ -244,7 +258,7 @@ class MuranoDslExecutor(object):
context[constants.CTX_EXECUTOR] = weakref.ref(self) context[constants.CTX_EXECUTOR] = weakref.ref(self)
context[constants.CTX_PACKAGE_LOADER] = weakref.ref( context[constants.CTX_PACKAGE_LOADER] = weakref.ref(
self._package_loader) self._package_loader)
context[constants.CTX_ENVIRONMENT] = self._environment context[constants.CTX_EXECUTION_SESSION] = self._session
context[constants.CTX_ATTRIBUTE_STORE] = weakref.ref( context[constants.CTX_ATTRIBUTE_STORE] = weakref.ref(
self._attribute_store) self._attribute_store)
self._root_context_cache[runtime_version] = context self._root_context_cache[runtime_version] = context

View File

@ -109,9 +109,13 @@ def generate_id():
def parallel_select(collection, func, limit=1000): def parallel_select(collection, func, limit=1000):
# workaround for eventlet issue 232 # workaround for eventlet issue 232
# https://github.com/eventlet/eventlet/issues/232 # https://github.com/eventlet/eventlet/issues/232
context = get_context()
session = get_execution_session()
def wrapper(element): def wrapper(element):
try: try:
with contextual(get_context()): with contextual(context):
with execution_session(session):
return func(element), False, None return func(element), False, None
except Exception as e: except Exception as e:
return e, True, sys.exc_info()[2] return e, True, sys.exc_info()[2]
@ -132,7 +136,7 @@ def enum(**enums):
def get_context(): def get_context():
current_thread = eventlet.greenthread.getcurrent() current_thread = eventlet.greenthread.getcurrent()
return getattr(current_thread, '__murano_context', None) return getattr(current_thread, constants.TL_CONTEXT, None)
def get_executor(context=None): def get_executor(context=None):
@ -146,9 +150,15 @@ def get_type(context=None):
return context[constants.CTX_TYPE] return context[constants.CTX_TYPE]
def get_environment(context=None): def get_execution_session(context=None):
context = context or get_context() context = context or get_context()
return context[constants.CTX_ENVIRONMENT] session = None
if context is not None:
session = context[constants.CTX_EXECUTION_SESSION]
if session is None:
current_thread = eventlet.greenthread.getcurrent()
session = getattr(current_thread, constants.TL_SESSION, None)
return session
def get_object_store(context=None): def get_object_store(context=None):
@ -215,27 +225,37 @@ def get_current_thread_id():
global _threads_sequencer global _threads_sequencer
current_thread = eventlet.greenthread.getcurrent() current_thread = eventlet.greenthread.getcurrent()
thread_id = getattr(current_thread, '__thread_id', None) thread_id = getattr(current_thread, constants.TL_ID, None)
if thread_id is None: if thread_id is None:
thread_id = 'T' + str(_threads_sequencer) thread_id = 'T' + str(_threads_sequencer)
_threads_sequencer += 1 _threads_sequencer += 1
setattr(current_thread, '__thread_id', thread_id) setattr(current_thread, constants.TL_ID, thread_id)
return thread_id return thread_id
@contextlib.contextmanager @contextlib.contextmanager
def contextual(ctx): def thread_local_attribute(name, value):
current_thread = eventlet.greenthread.getcurrent() current_thread = eventlet.greenthread.getcurrent()
current_context = getattr(current_thread, '__murano_context', None) old_value = getattr(current_thread, name, None)
if ctx: if value is not None:
setattr(current_thread, '__murano_context', ctx) setattr(current_thread, name, value)
elif hasattr(current_thread, name):
delattr(current_thread, name)
try: try:
yield yield
finally: finally:
if current_context: if old_value is not None:
setattr(current_thread, '__murano_context', current_context) setattr(current_thread, name, old_value)
elif hasattr(current_thread, '__murano_context'): elif hasattr(current_thread, name):
delattr(current_thread, '__murano_context') delattr(current_thread, name)
def contextual(ctx):
return thread_local_attribute(constants.TL_CONTEXT, ctx)
def execution_session(session):
return thread_local_attribute(constants.TL_SESSION, session)
def parse_version_spec(version_spec): def parse_version_spec(version_spec):
@ -332,7 +352,10 @@ def is_instance_of(obj, class_name, pov_or_version_spec=None):
def memoize(func): def memoize(func):
cache = {} cache = {}
return get_memoize_func(func, cache)
def get_memoize_func(func, cache):
@functools.wraps(func) @functools.wraps(func)
def wrap(*args): def wrap(*args):
if args not in cache: if args not in cache:

View File

@ -0,0 +1,97 @@
# Copyright (c) 2016 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.
# This code is almost a complete copy of eventlet.corolocal with only
# the concept of current thread replaced with current session
import weakref
import six
from murano.dsl import helpers
# the entire purpose of this class is to store off the constructor
# arguments in a local variable without calling __init__ directly
class _localbase(object):
__slots__ = '_local__args', '_local__sessions'
def __new__(cls, *args, **kw):
self = object.__new__(cls)
object.__setattr__(self, '_local__args', (args, kw))
object.__setattr__(
self, '_local__sessions', weakref.WeakKeyDictionary())
if (args or kw) and (cls.__init__ is object.__init__):
raise TypeError('Initialization arguments are not supported')
return self
def _patch(session_local):
sessions_dict = object.__getattribute__(session_local, '_local__sessions')
session = helpers.get_execution_session()
localdict = sessions_dict.get(session)
if localdict is None:
# must be the first time we've seen this session, call __init__
localdict = {}
sessions_dict[session] = localdict
cls = type(session_local)
if cls.__init__ is not object.__init__:
args, kw = object.__getattribute__(session_local, '_local__args')
session_local.__init__(*args, **kw)
object.__setattr__(session_local, '__dict__', localdict)
class _local(_localbase):
def __getattribute__(self, attr):
_patch(self)
return object.__getattribute__(self, attr)
def __setattr__(self, attr, value):
_patch(self)
return object.__setattr__(self, attr, value)
def __delattr__(self, attr):
_patch(self)
return object.__delattr__(self, attr)
def session_local(cls):
return type(cls.__name__, (cls, _local), {})
class SessionLocalDict(six.moves.UserDict, object):
def __init__(self, **kwargs):
self.__session_data = weakref.WeakKeyDictionary()
self.__default = {}
super(SessionLocalDict, self).__init__(**kwargs)
@property
def data(self):
session = helpers.get_execution_session()
if session is None:
return self.__default
return self.__session_data.setdefault(session, {})
@data.setter
def data(self, value):
session = helpers.get_execution_session()
if session is None:
self.__default = value
else:
self.__session_data[session] = value
def execution_session_memoize(func):
cache = SessionLocalDict()
return helpers.get_memoize_func(func, cache)

View File

@ -1,241 +0,0 @@
# Copyright (c) 2014 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 weakref
from eventlet import semaphore
import heatclient.client as hclient
import keystoneclient
import keystoneclient.auth.identity.access as access
import muranoclient.v1.client as muranoclient
import neutronclient.v2_0.client as nclient
from oslo_config import cfg
from murano.common import auth_utils
from muranoclient.glance import client as art_client
try:
# integration with congress is optional
import congressclient.v1.client as congress_client
except ImportError as congress_client_import_error:
congress_client = None
try:
import mistralclient.api.client as mistralclient
except ImportError as mistral_import_error:
mistralclient = None
CONF = cfg.CONF
class ClientManager(object):
def __init__(self, environment):
self._trusts_keystone_client = None
self._token_keystone_client = None
self._cache = {}
self._semaphore = semaphore.BoundedSemaphore()
self._environment = weakref.proxy(environment)
def get_client(self, name, use_trusts, client_factory):
if not CONF.engine.use_trusts:
use_trusts = False
keystone_client = None if name == 'keystone' else \
self.get_keystone_client(use_trusts)
self._semaphore.acquire()
try:
client, used_token = self._cache.get(
(name, use_trusts), (None, None))
fresh_token = None if keystone_client is None \
else keystone_client.auth_token
if use_trusts and used_token != fresh_token:
client = None
if not client:
token = fresh_token
if not use_trusts:
token = self._environment.token
client = client_factory(keystone_client, token)
self._cache[(name, use_trusts)] = (client, token)
return client
finally:
self._semaphore.release()
def get_keystone_client(self, use_trusts=True):
if not CONF.engine.use_trusts:
use_trusts = False
factory = lambda _1, _2: \
auth_utils.get_client_for_trusts(self._environment.trust_id) \
if use_trusts else auth_utils.get_client(
self._environment.token, self._environment.tenant_id)
return self.get_client('keystone', use_trusts, factory)
def get_congress_client(self, use_trusts=True):
"""Client for congress services
:return: initialized congress client
:raise ImportError: in case that python-congressclient
is not present on python path
"""
if not congress_client:
# congress client was not imported
raise congress_client_import_error
if not CONF.engine.use_trusts:
use_trusts = False
def factory(keystone_client, auth_token):
auth = access.AccessInfoPlugin(keystone_client.auth_ref)
session = keystoneclient.session.Session(auth=auth)
return congress_client.Client(session=session,
service_type='policy')
return self.get_client('congress', use_trusts, factory)
def get_heat_client(self, use_trusts=True):
if not CONF.engine.use_trusts:
use_trusts = False
def factory(keystone_client, auth_token):
heat_settings = CONF.heat
heat_url = keystone_client.service_catalog.url_for(
service_type='orchestration',
endpoint_type=heat_settings.endpoint_type)
kwargs = {
'token': auth_token,
'ca_file': heat_settings.ca_file or None,
'cert_file': heat_settings.cert_file or None,
'key_file': heat_settings.key_file or None,
'insecure': heat_settings.insecure
}
if not CONF.engine.use_trusts:
kwargs.update({
'username': 'badusername',
'password': 'badpassword'
})
return hclient.Client('1', heat_url, **kwargs)
return self.get_client('heat', use_trusts, factory)
def get_neutron_client(self, use_trusts=True):
if not CONF.engine.use_trusts:
use_trusts = False
def factory(keystone_client, auth_token):
neutron_settings = CONF.neutron
neutron_url = keystone_client.service_catalog.url_for(
service_type='network',
endpoint_type=neutron_settings.endpoint_type)
return nclient.Client(
endpoint_url=neutron_url,
token=auth_token,
ca_cert=neutron_settings.ca_cert or None,
insecure=neutron_settings.insecure)
return self.get_client('neutron', use_trusts, factory)
def get_murano_client(self, use_trusts=True):
if not CONF.engine.use_trusts:
use_trusts = False
def factory(keystone_client, auth_token):
murano_settings = CONF.murano
murano_url = \
murano_settings.url or keystone_client.service_catalog.url_for(
service_type='application-catalog',
endpoint_type=murano_settings.endpoint_type)
if CONF.packages_opts.packages_service == 'glance':
glance_settings = CONF.glance
glance_url = (glance_settings.url or
keystone_client.service_catalog.url_for(
service_type='image',
endpoint_type=glance_settings.endpoint_type))
arts = art_client.Client(
endpoint=glance_url, token=auth_token,
insecure=glance_settings.insecure,
key_file=glance_settings.key_file or None,
ca_file=glance_settings.ca_file or None,
cert_file=glance_settings.cert_file or None,
type_name='murano',
type_version=1)
else:
arts = None
return muranoclient.Client(
endpoint=murano_url,
key_file=murano_settings.key_file or None,
ca_file=murano_settings.cacert or None,
cert_file=murano_settings.cert_file or None,
insecure=murano_settings.insecure,
auth_url=keystone_client.auth_url,
token=auth_token,
artifacts_client=arts)
return self.get_client('murano', use_trusts, factory)
def get_mistral_client(self, use_trusts=True):
if not mistralclient:
raise mistral_import_error
if not CONF.engine.use_trusts:
use_trusts = False
def factory(keystone_client, auth_token):
mistral_settings = CONF.mistral
endpoint_type = mistral_settings.endpoint_type
service_type = mistral_settings.service_type
mistral_url = keystone_client.service_catalog.url_for(
service_type=service_type,
endpoint_type=endpoint_type)
return mistralclient.client(mistral_url=mistral_url,
project_id=keystone_client.tenant_id,
endpoint_type=endpoint_type,
service_type=service_type,
auth_token=auth_token,
user_id=keystone_client.user_id)
return self.get_client('mistral', use_trusts, factory)
def get_artifacts_client(self, use_trusts=True):
if not CONF.engine.use_trusts:
use_trusts = False
def factory(keystone_client, auth_token):
glance_settings = CONF.glance
glance_url = (glance_settings.url or
keystone_client.service_catalog.url_for(
service_type='image',
endpoint_type=glance_settings.endpoint_type))
return art_client.Client(endpoint=glance_url, token=auth_token,
insecure=glance_settings.insecure,
key_file=glance_settings.key_file or None,
cacert=glance_settings.cacert or None,
cert_file=(glance_settings.cert_file or
None),
type_name='murano',
type_version=1)
return self.get_client('artifacts', use_trusts, factory)

View File

@ -16,18 +16,16 @@
from oslo_log import log as logging from oslo_log import log as logging
from murano.common.i18n import _LE from murano.common.i18n import _LE
from murano.engine import client_manager
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class Environment(object): class ExecutionSession(object):
def __init__(self): def __init__(self):
self.token = None self.token = None
self.tenant_id = None self.project_id = None
self.trust_id = None self.trust_id = None
self.system_attributes = {} self.system_attributes = {}
self.clients = client_manager.ClientManager(self)
self._set_up_list = [] self._set_up_list = []
self._tear_down_list = [] self._tear_down_list = []

View File

@ -24,10 +24,13 @@ import uuid
import eventlet import eventlet
from muranoclient.common import exceptions as muranoclient_exc from muranoclient.common import exceptions as muranoclient_exc
from muranoclient.glance import client as glare_client
import muranoclient.v1.client as muranoclient
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
import six import six
from murano.common import auth_utils
from murano.common.i18n import _LE, _LI, _LW from murano.common.i18n import _LE, _LI, _LW
from murano.dsl import constants from murano.dsl import constants
from murano.dsl import exceptions from murano.dsl import exceptions
@ -48,18 +51,67 @@ usage_mem_locks = collections.defaultdict(m_utils.ReaderWriterLock)
class ApiPackageLoader(package_loader.MuranoPackageLoader): class ApiPackageLoader(package_loader.MuranoPackageLoader):
def __init__(self, murano_client_factory, tenant_id, root_loader=None): def __init__(self, execution_session, root_loader=None):
self._cache_directory = self._get_cache_directory() self._cache_directory = self._get_cache_directory()
self._murano_client_factory = murano_client_factory
self.tenant_id = tenant_id
self._class_cache = {} self._class_cache = {}
self._package_cache = {} self._package_cache = {}
self._root_loader = root_loader or self self._root_loader = root_loader or self
self._execution_session = execution_session
self._last_glare_token = None
self._glare_client = None
self._murano_client = None
self._murano_client_session = None
self._mem_locks = [] self._mem_locks = []
self._ipc_locks = [] self._ipc_locks = []
self._downloaded = [] self._downloaded = []
def _get_glare_client(self):
glance_settings = CONF.glance
session = auth_utils.get_client_session(self._execution_session)
token = session.auth.get_token(session)
if self._last_glare_token != token:
self._last_glare_token = token
self._glare_client = None
if self._glare_client is None:
url = glance_settings.url
if not url:
url = session.get_endpoint(
service_type='image',
interface=glance_settings.endpoint_type,
region_name=CONF.home_region)
self._glare_client = glare_client.Client(
endpoint=url, token=token,
insecure=glance_settings.insecure,
key_file=glance_settings.key_file or None,
ca_file=glance_settings.ca_file or None,
cert_file=glance_settings.cert_file or None,
type_name='murano',
type_version=1)
return self._glare_client
@property
def client(self):
murano_settings = CONF.murano
last_glare_client = self._glare_client
if CONF.packages_opts.packages_service == 'glance':
artifacts_client = self._get_glare_client()
else:
artifacts_client = None
if artifacts_client != last_glare_client:
self._murano_client = None
if not self._murano_client:
parameters = auth_utils.get_session_client_parameters(
service_type='application-catalog',
execution_session=self._execution_session,
conf=murano_settings
)
self._murano_client = muranoclient.Client(
artifacts_client=artifacts_client, **parameters)
return self._murano_client
def load_class_package(self, class_name, version_spec): def load_class_package(self, class_name, version_spec):
packages = self._class_cache.get(class_name) packages = self._class_cache.get(class_name)
if packages: if packages:
@ -97,6 +149,7 @@ class ApiPackageLoader(package_loader.MuranoPackageLoader):
exc_info = sys.exc_info() exc_info = sys.exc_info()
six.reraise(exceptions.NoPackageFound(package_name), six.reraise(exceptions.NoPackageFound(package_name),
None, exc_info[2]) None, exc_info[2])
else:
return self._to_dsl_package( return self._to_dsl_package(
self._get_package_by_definition(package_definition)) self._get_package_by_definition(package_definition))
@ -129,7 +182,7 @@ class ApiPackageLoader(package_loader.MuranoPackageLoader):
def _get_definition(self, filter_opts): def _get_definition(self, filter_opts):
filter_opts['catalog'] = True filter_opts['catalog'] = True
try: try:
packages = list(self._murano_client_factory().packages.filter( packages = list(self.client.packages.filter(
**filter_opts)) **filter_opts))
if len(packages) > 1: if len(packages) > 1:
LOG.debug('Ambiguous package resolution: more then 1 package ' LOG.debug('Ambiguous package resolution: more then 1 package '
@ -180,8 +233,7 @@ class ApiPackageLoader(package_loader.MuranoPackageLoader):
download_ipc_lock = m_utils.ExclusiveInterProcessLock( download_ipc_lock = m_utils.ExclusiveInterProcessLock(
path=download_lock_path, sleep_func=eventlet.sleep) path=download_lock_path, sleep_func=eventlet.sleep)
with download_mem_locks[package_id].write_lock(),\ with download_mem_locks[package_id].write_lock(), download_ipc_lock:
download_ipc_lock:
# NOTE(kzaitsev): # NOTE(kzaitsev):
# in case there were 2 concurrent threads/processes one might have # in case there were 2 concurrent threads/processes one might have
@ -198,8 +250,7 @@ class ApiPackageLoader(package_loader.MuranoPackageLoader):
try: try:
LOG.debug("Attempting to download package {} {}".format( LOG.debug("Attempting to download package {} {}".format(
package_def.fully_qualified_name, package_id)) package_def.fully_qualified_name, package_id))
package_data = self._murano_client_factory().packages.download( package_data = self.client.packages.download(package_id)
package_id)
except muranoclient_exc.HTTPException as e: except muranoclient_exc.HTTPException as e:
msg = 'Error loading package id {0}: {1}'.format( msg = 'Error loading package id {0}: {1}'.format(
package_id, str(e) package_id, str(e)
@ -304,7 +355,7 @@ class ApiPackageLoader(package_loader.MuranoPackageLoader):
public = None public = None
other = [] other = []
for package in packages: for package in packages:
if package.owner_id == self.tenant_id: if package.owner_id == self._execution_session.project_id:
return package return package
elif package.is_public: elif package.is_public:
public = package public = package
@ -451,10 +502,9 @@ class DirectoryPackageLoader(package_loader.MuranoPackageLoader):
class CombinedPackageLoader(package_loader.MuranoPackageLoader): class CombinedPackageLoader(package_loader.MuranoPackageLoader):
def __init__(self, murano_client_factory, tenant_id, root_loader=None): def __init__(self, execution_session, root_loader=None):
root_loader = root_loader or self root_loader = root_loader or self
self.api_loader = ApiPackageLoader( self.api_loader = ApiPackageLoader(execution_session, root_loader)
murano_client_factory, tenant_id, root_loader)
self.directory_loaders = [] self.directory_loaders = []
for folder in CONF.packages_opts.load_packages_from: for folder in CONF.packages_opts.load_packages_from:

View File

@ -66,7 +66,7 @@ class AgentListener(object):
return return
if self._receive_thread is None: if self._receive_thread is None:
helpers.get_environment().on_session_finish( helpers.get_execution_session().on_session_finish(
lambda: self.stop()) lambda: self.stop())
self._receive_thread = eventlet.spawn(self._receive) self._receive_thread = eventlet.spawn(self._receive)

View File

@ -16,15 +16,18 @@
import copy import copy
import eventlet import eventlet
import heatclient.client as hclient
import heatclient.exc as heat_exc import heatclient.exc as heat_exc
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
import six import six
from murano.common import auth_utils
from murano.common.i18n import _LW from murano.common.i18n import _LW
from murano.common import utils from murano.common import utils
from murano.dsl import dsl from murano.dsl import dsl
from murano.dsl import helpers from murano.dsl import helpers
from murano.dsl import session_local_storage
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
CONF = cfg.CONF CONF = cfg.CONF
@ -46,17 +49,35 @@ class HeatStack(object):
self._hot_environment = '' self._hot_environment = ''
self._applied = True self._applied = True
self._description = description self._description = description
self._clients = helpers.get_environment().clients
self._last_stack_timestamps = (None, None) self._last_stack_timestamps = (None, None)
self._tags = '' self._tags = ''
self._client = self._get_client(CONF.home_region)
pass
@staticmethod
def _create_client(session, region_name):
parameters = auth_utils.get_session_client_parameters(
service_type='orchestration', region=region_name,
conf=CONF.heat, session=session)
return hclient.Client('1', **parameters)
@staticmethod
@session_local_storage.execution_session_memoize
def _get_client(region_name):
session = auth_utils.get_client_session(conf=CONF.heat)
return HeatStack._create_client(session, region_name)
@classmethod
def _get_token_client(cls):
ks_session = auth_utils.get_token_client_session(conf=CONF.heat)
return cls._create_client(ks_session, CONF.home_region)
def current(self): def current(self):
client = self._clients.get_heat_client()
if self._template is not None: if self._template is not None:
return self._template return self._template
try: try:
stack_info = client.stacks.get(stack_id=self._name) stack_info = self._client.stacks.get(stack_id=self._name)
template = client.stacks.template( template = self._client.stacks.template(
stack_id='{0}/{1}'.format( stack_id='{0}/{1}'.format(
stack_info.stack_name, stack_info.stack_name,
stack_info.id)) stack_info.id))
@ -126,11 +147,11 @@ class HeatStack(object):
def _wait_state(self, status_func, wait_progress=False): def _wait_state(self, status_func, wait_progress=False):
tries = 4 tries = 4
delay = 1 delay = 1
while tries > 0: while tries > 0:
while True: while True:
client = self._clients.get_heat_client()
try: try:
stack_info = client.stacks.get( stack_info = self._client.stacks.get(
stack_id=self._name) stack_id=self._name)
status = stack_info.stack_status status = stack_info.stack_status
tries = 4 tries = 4
@ -194,7 +215,7 @@ class HeatStack(object):
resources = template.get('Resources') or template.get('resources') resources = template.get('Resources') or template.get('resources')
if current_status == 'NOT_FOUND': if current_status == 'NOT_FOUND':
if resources is not None: if resources is not None:
token_client = self._clients.get_heat_client(use_trusts=False) token_client = self._get_token_client()
token_client.stacks.create( token_client.stacks.create(
stack_name=self._name, stack_name=self._name,
parameters=self._parameters, parameters=self._parameters,
@ -207,9 +228,7 @@ class HeatStack(object):
self._wait_state(lambda status: status == 'CREATE_COMPLETE') self._wait_state(lambda status: status == 'CREATE_COMPLETE')
else: else:
if resources is not None: if resources is not None:
trust_client = self._clients.get_heat_client() self._client.stacks.update(
trust_client.stacks.update(
stack_id=self._name, stack_id=self._name,
parameters=self._parameters, parameters=self._parameters,
files=self._files, files=self._files,
@ -225,11 +244,10 @@ class HeatStack(object):
self._applied = not utils.is_different(self._template, template) self._applied = not utils.is_different(self._template, template)
def delete(self): def delete(self):
client = self._clients.get_heat_client()
try: try:
if not self.current(): if not self.current():
return return
client.stacks.delete(stack_id=self._name) self._client.stacks.delete(stack_id=self._name)
self._wait_state( self._wait_state(
lambda status: status in ('DELETE_COMPLETE', 'NOT_FOUND'), lambda status: status in ('DELETE_COMPLETE', 'NOT_FOUND'),
wait_progress=True) wait_progress=True)

View File

@ -17,9 +17,17 @@
import json import json
import eventlet import eventlet
try:
import mistralclient.api.client as mistralclient
except ImportError as mistral_import_error:
mistralclient = None
from oslo_config import cfg
from murano.common import auth_utils
from murano.dsl import dsl from murano.dsl import dsl
from murano.dsl import helpers from murano.dsl import session_local_storage
CONF = cfg.CONF
class MistralError(Exception): class MistralError(Exception):
@ -28,18 +36,44 @@ class MistralError(Exception):
@dsl.name('io.murano.system.MistralClient') @dsl.name('io.murano.system.MistralClient')
class MistralClient(object): class MistralClient(object):
def __init__(self, context): def __init__(self):
self._clients = helpers.get_environment(context).clients self._client = self._create_client(CONF.home_region)
@staticmethod
@session_local_storage.execution_session_memoize
def _create_client(region):
if not mistralclient:
raise mistral_import_error
mistral_settings = CONF.mistral
endpoint_type = mistral_settings.endpoint_type
service_type = mistral_settings.service_type
session = auth_utils.get_client_session()
mistral_url = mistral_settings.url or session.get_endpoint(
service_type=service_type,
endpoint_type=endpoint_type,
region_name=region)
auth_ref = session.auth.get_access(session)
return mistralclient.client(
mistral_url=mistral_url,
project_id=auth_ref.project_id,
endpoint_type=endpoint_type,
service_type=service_type,
auth_token=auth_ref.auth_token,
user_id=auth_ref.user_id,
insecure=mistral_settings.insecure,
cacert=mistral_settings.ca_cert
)
def upload(self, definition): def upload(self, definition):
mistral_client = self._clients.get_mistral_client() self._client.workflows.create(definition)
mistral_client.workflows.create(definition)
def run(self, name, timeout=600, inputs=None, params=None): def run(self, name, timeout=600, inputs=None, params=None):
mistral_client = self._clients.get_mistral_client() execution = self._client.executions.create(
execution = mistral_client.executions.create(workflow_name=name, workflow_name=name, workflow_input=inputs, params=params)
workflow_input=inputs,
params=params)
# For the fire and forget functionality - when we do not want to wait # For the fire and forget functionality - when we do not want to wait
# for the result of the run. # for the result of the run.
if timeout == 0: if timeout == 0:
@ -51,7 +85,7 @@ class MistralClient(object):
with eventlet.timeout.Timeout(timeout): with eventlet.timeout.Timeout(timeout):
while state not in ('ERROR', 'SUCCESS'): while state not in ('ERROR', 'SUCCESS'):
eventlet.sleep(2) eventlet.sleep(2)
execution = mistral_client.executions.get(execution.id) execution = self._client.executions.get(execution.id)
state = execution.state state = execution.state
except eventlet.timeout.Timeout: except eventlet.timeout.Timeout:
error_message = ( error_message = (

View File

@ -16,15 +16,18 @@ import math
import netaddr import netaddr
from netaddr.strategy import ipv4 from netaddr.strategy import ipv4
import neutronclient.v2_0.client as nclient
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import uuidutils from oslo_utils import uuidutils
import retrying import retrying
from murano.common import auth_utils
from murano.common import exceptions as exc from murano.common import exceptions as exc
from murano.common.i18n import _LI from murano.common.i18n import _LI
from murano.dsl import dsl from murano.dsl import dsl
from murano.dsl import helpers from murano.dsl import helpers
from murano.dsl import session_local_storage
CONF = cfg.CONF CONF = cfg.CONF
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -33,11 +36,19 @@ LOG = logging.getLogger(__name__)
@dsl.name('io.murano.system.NetworkExplorer') @dsl.name('io.murano.system.NetworkExplorer')
class NetworkExplorer(object): class NetworkExplorer(object):
def __init__(self): def __init__(self):
environment = helpers.get_environment() session = helpers.get_execution_session()
self._clients = environment.clients self._project_id = session.project_id
self._tenant_id = environment.tenant_id
self._settings = CONF.networking self._settings = CONF.networking
self._available_cidrs = self._generate_possible_cidrs() self._available_cidrs = self._generate_possible_cidrs()
self._client = self._get_client(CONF.home_region)
@staticmethod
@session_local_storage.execution_session_memoize
def _get_client(region_name):
neutron_settings = CONF.neutron
return nclient.Client(**auth_utils.get_session_client_parameters(
service_type='network', region=region_name, conf=neutron_settings
))
# NOTE(starodubcevna): to avoid simultaneous router requests we use retry # NOTE(starodubcevna): to avoid simultaneous router requests we use retry
# decorator with random delay 1-10 seconds between attempts and maximum # decorator with random delay 1-10 seconds between attempts and maximum
@ -47,11 +58,10 @@ class NetworkExplorer(object):
wait_random_min=1000, wait_random_max=10000, wait_random_min=1000, wait_random_max=10000,
stop_max_delay=30000) stop_max_delay=30000)
def get_default_router(self): def get_default_router(self):
client = self._clients.get_neutron_client()
router_name = self._settings.router_name router_name = self._settings.router_name
routers = client.list_routers( routers = self._client.list_routers(
tenant_id=self._tenant_id, name=router_name).get('routers') tenant_id=self._project_id, name=router_name).get('routers')
if len(routers) == 0: if len(routers) == 0:
LOG.debug('Router {name} not found'.format(name=router_name)) LOG.debug('Router {name} not found'.format(name=router_name))
if self._settings.create_router: if self._settings.create_router:
@ -61,7 +71,7 @@ class NetworkExplorer(object):
kwargs = {'id': external_network} \ kwargs = {'id': external_network} \
if uuidutils.is_uuid_like(external_network) \ if uuidutils.is_uuid_like(external_network) \
else {'name': external_network} else {'name': external_network}
networks = client.list_networks(**kwargs).get('networks') networks = self._client.list_networks(**kwargs).get('networks')
ext_nets = filter(lambda n: n['router:external'], networks) ext_nets = filter(lambda n: n['router:external'], networks)
if len(ext_nets) == 0: if len(ext_nets) == 0:
raise KeyError('Router %s could not be created, ' raise KeyError('Router %s could not be created, '
@ -77,7 +87,8 @@ class NetworkExplorer(object):
'admin_state_up': True, 'admin_state_up': True,
} }
} }
router = client.create_router(body=body_data).get('router') router = self._client.create_router(
body=body_data).get('router')
LOG.info(_LI('Created router: {id}').format(id=router['id'])) LOG.info(_LI('Created router: {id}').format(id=router['id']))
return router['id'] return router['id']
else: else:
@ -112,20 +123,18 @@ class NetworkExplorer(object):
return self._settings.default_dns return self._settings.default_dns
def get_external_network_id_for_router(self, router_id): def get_external_network_id_for_router(self, router_id):
client = self._clients.get_neutron_client() router = self._client.show_router(router_id).get('router')
router = client.show_router(router_id).get('router')
if not router or 'external_gateway_info' not in router: if not router or 'external_gateway_info' not in router:
return None return None
return router['external_gateway_info'].get('network_id') return router['external_gateway_info'].get('network_id')
def get_external_network_id_for_network(self, network_id): def get_external_network_id_for_network(self, network_id):
client = self._clients.get_neutron_client() network = self._client.show_network(network_id).get('network')
network = client.show_network(network_id).get('network')
if network.get('router:external', False): if network.get('router:external', False):
return network_id return network_id
# Get router interfaces of the network # Get router interfaces of the network
router_ports = client.list_ports( router_ports = self._client.list_ports(
**{'device_owner': 'network:router_interface', **{'device_owner': 'network:router_interface',
'network_id': network_id}).get('ports') 'network_id': network_id}).get('ports')
@ -141,14 +150,13 @@ class NetworkExplorer(object):
def _get_cidrs_taken_by_router(self, router_id): def _get_cidrs_taken_by_router(self, router_id):
if not router_id: if not router_id:
return [] return []
client = self._clients.get_neutron_client() ports = self._client.list_ports(device_id=router_id)['ports']
ports = client.list_ports(device_id=router_id)['ports']
subnet_ids = [] subnet_ids = []
for port in ports: for port in ports:
for fixed_ip in port['fixed_ips']: for fixed_ip in port['fixed_ips']:
subnet_ids.append(fixed_ip['subnet_id']) subnet_ids.append(fixed_ip['subnet_id'])
all_subnets = client.list_subnets()['subnets'] all_subnets = self._client.list_subnets()['subnets']
filtered_cidrs = [netaddr.IPNetwork(subnet['cidr']) for subnet in filtered_cidrs = [netaddr.IPNetwork(subnet['cidr']) for subnet in
all_subnets if subnet['id'] in subnet_ids] all_subnets if subnet['id'] in subnet_ids]
@ -169,13 +177,10 @@ class NetworkExplorer(object):
return list(net.subnet(width - bits_for_hosts)) return list(net.subnet(width - bits_for_hosts))
def list_networks(self): def list_networks(self):
client = self._clients.get_neutron_client() return self._client.list_networks()['networks']
return client.list_networks()['networks']
def list_subnetworks(self): def list_subnetworks(self):
client = self._clients.get_neutron_client() return self._client.list_subnets()['subnets']
return client.list_subnets()['subnets']
def list_ports(self): def list_ports(self):
client = self._clients.get_neutron_client() return self._client.list_ports()['ports']
return client.list_ports()['ports']

View File

@ -34,12 +34,12 @@ class TestFixture(object):
return exc.load(model) return exc.load(model)
def finish_env(self): def finish_env(self):
env = helpers.get_environment() session = helpers.get_execution_session()
env.finish() session.finish()
def start_env(self): def start_env(self):
env = helpers.get_environment() session = helpers.get_execution_session()
env.start() session.start()
def assert_equal(self, expected, observed, message=None): def assert_equal(self, expected, observed, message=None):
self._test_case.assertEqual(expected, observed, message) self._test_case.assertEqual(expected, observed, message)

View File

@ -34,8 +34,9 @@ _opt_lists = [
('rabbitmq', murano.common.config.rabbit_opts), ('rabbitmq', murano.common.config.rabbit_opts),
('heat', murano.common.config.heat_opts), ('heat', murano.common.config.heat_opts),
('neutron', murano.common.config.neutron_opts), ('neutron', murano.common.config.neutron_opts),
('keystone', murano.common.config.keystone_opts),
('murano', murano.common.config.murano_opts), ('murano', murano.common.config.murano_opts),
('glance', murano.common.config.glance_opts),
('mistral', murano.common.config.mistral_opts),
('networking', murano.common.config.networking_opts), ('networking', murano.common.config.networking_opts),
('stats', murano.common.config.stats_opts), ('stats', murano.common.config.stats_opts),
('packages_opts', murano.common.config.packages_opts), ('packages_opts', murano.common.config.packages_opts),

View File

@ -15,14 +15,22 @@
import re import re
try:
# integration with congress is optional
import congressclient.v1.client as congress_client
except ImportError as congress_client_import_error:
congress_client = None
from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
from murano.common import auth_utils
from murano.common.i18n import _, _LI from murano.common.i18n import _, _LI
from murano.policy import congress_rules from murano.policy import congress_rules
from murano.policy.modify.actions import action_manager as am from murano.policy.modify.actions import action_manager as am
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class ValidationError(Exception): class ValidationError(Exception):
@ -40,10 +48,25 @@ class ModelPolicyEnforcer(object):
table along with congress data rules to return validation results. table along with congress data rules to return validation results.
""" """
def __init__(self, environment, action_manager=None): def __init__(self, execution_session, action_manager=None):
self._environment = environment self._execution_session = execution_session
self._client_manager = environment.clients
self._action_manager = action_manager or am.ModifyActionManager() self._action_manager = action_manager or am.ModifyActionManager()
self._client = None
def _create_client(self):
if not congress_client:
# congress client was not imported
raise congress_client_import_error
return congress_client.Client(
**auth_utils.get_session_client_parameters(
service_type='policy',
execution_session=self._execution_session))
@property
def client(self):
if self._client is None:
self._client = self._create_client()
return self._client
def modify(self, obj, package_loader=None): def modify(self, obj, package_loader=None):
"""Modifies model using Congress rule engine. """Modifies model using Congress rule engine.
@ -110,7 +133,7 @@ class ModelPolicyEnforcer(object):
def _execute_simulation(self, package_loader, env_id, model, query): def _execute_simulation(self, package_loader, env_id, model, query):
rules = congress_rules.CongressRulesManager().convert( rules = congress_rules.CongressRulesManager().convert(
model, package_loader, self._environment.tenant_id) model, package_loader, self._execution_session.project_id)
rules_str = list(map(str, rules)) rules_str = list(map(str, rules))
# cleanup of data populated by murano driver # cleanup of data populated by murano driver
rules_str.insert(0, 'deleteEnv("{0}")'.format(env_id)) rules_str.insert(0, 'deleteEnv("{0}")'.format(env_id))
@ -118,9 +141,7 @@ class ModelPolicyEnforcer(object):
LOG.debug('Congress rules: \n {rules} ' LOG.debug('Congress rules: \n {rules} '
.format(rules='\n '.join(rules_str))) .format(rules='\n '.join(rules_str)))
client = self._check_client() validation_result = self.client.execute_policy_action(
validation_result = client.execute_policy_action(
"murano_system", "murano_system",
"simulate", "simulate",
False, False,
@ -130,12 +151,6 @@ class ModelPolicyEnforcer(object):
'sequence': rules_line}) 'sequence': rules_line})
return validation_result return validation_result
def _check_client(self):
client = self._client_manager.get_congress_client(self._environment)
if not client:
raise ValueError(_('Congress client is not configured!'))
return client
@staticmethod @staticmethod
def _parse_simulation_result(query, env_id, results): def _parse_simulation_result(query, env_id, results):
"""Transforms list of strings in format """Transforms list of strings in format

View File

@ -25,7 +25,7 @@ from murano.dsl import helpers
from murano.dsl import murano_object from murano.dsl import murano_object
from murano.dsl import serializer from murano.dsl import serializer
from murano.dsl import yaql_integration from murano.dsl import yaql_integration
from murano.engine import environment from murano.engine import execution_session
from murano.engine.system import yaql_functions from murano.engine.system import yaql_functions
from murano.tests.unit.dsl.foundation import object_model from murano.tests.unit.dsl.foundation import object_model
@ -77,7 +77,7 @@ class Runner(object):
self.executor = executor.MuranoDslExecutor( self.executor = executor.MuranoDslExecutor(
package_loader, TestContextManager(functions), package_loader, TestContextManager(functions),
environment.Environment()) execution_session.ExecutionSession())
self._root = self.executor.load(model).object self._root = self.executor.load(model).object
def _execute(self, name, object_id, *args, **kwargs): def _execute(self, name, object_id, *args, **kwargs):

View File

@ -19,7 +19,7 @@ from murano.common import exceptions as exc
from murano.dsl import constants from murano.dsl import constants
from murano.dsl import helpers from murano.dsl import helpers
from murano.dsl import yaql_integration from murano.dsl import yaql_integration
from murano.engine import environment from murano.engine import execution_session
from murano.engine.system import agent from murano.engine.system import agent
from murano.engine.system import agent_listener from murano.engine.system import agent_listener
from murano.tests.unit.dsl.foundation import object_model as om from murano.tests.unit.dsl.foundation import object_model as om
@ -37,7 +37,8 @@ class TestAgentListener(test_case.DslTestCase):
'AgentListenerTests') 'AgentListenerTests')
self.runner = self.new_runner(model) self.runner = self.new_runner(model)
self.context = yaql_integration.create_empty_context() self.context = yaql_integration.create_empty_context()
self.context[constants.CTX_ENVIRONMENT] = environment.Environment() self.context[constants.CTX_EXECUTION_SESSION] = \
execution_session.ExecutionSession()
def test_listener_enabled(self): def test_listener_enabled(self):
self.override_config('disable_murano_agent', False, 'engine') self.override_config('disable_murano_agent', False, 'engine')

View File

@ -18,7 +18,7 @@ from yaql import specs
from murano.dsl import constants from murano.dsl import constants
from murano.dsl import executor from murano.dsl import executor
from murano.dsl import murano_class from murano.dsl import murano_class
from murano.engine import environment from murano.engine import execution_session
from murano.engine import mock_context_manager from murano.engine import mock_context_manager
from murano.engine.system import test_fixture from murano.engine.system import test_fixture
from murano.tests.unit import base from murano.tests.unit import base
@ -58,7 +58,7 @@ class MockRunner(runner.Runner):
model = {'Objects': model} model = {'Objects': model}
self.executor = executor.MuranoDslExecutor( self.executor = executor.MuranoDslExecutor(
package_loader, TestMockContextManager(functions), package_loader, TestMockContextManager(functions),
environment.Environment()) execution_session.ExecutionSession())
self._root = self.executor.load(model).object self._root = self.executor.load(model).object

View File

@ -17,6 +17,7 @@ import tempfile
import mock import mock
from oslo_config import cfg from oslo_config import cfg
import semantic_version import semantic_version
import testtools
from murano.dsl import murano_package as dsl_package from murano.dsl import murano_package as dsl_package
from murano.engine import package_loader from murano.engine import package_loader
@ -37,23 +38,22 @@ class TestPackageCache(base.MuranoTestCase):
CONF.set_override('packages_cache', self.location, 'packages_opts') CONF.set_override('packages_cache', self.location, 'packages_opts')
self.murano_client = mock.MagicMock() self.murano_client = mock.MagicMock()
self.murano_client_factory = mock.MagicMock( package_loader.ApiPackageLoader.client = self.murano_client
return_value=self.murano_client) self.loader = package_loader.ApiPackageLoader(None)
self.loader = package_loader.ApiPackageLoader(
self.murano_client_factory, 'test_tenant_id')
def tearDown(self): def tearDown(self):
CONF.set_override('packages_cache', self.old_location, 'packages_opts') CONF.set_override('packages_cache', self.old_location, 'packages_opts')
shutil.rmtree(self.location, ignore_errors=True) shutil.rmtree(self.location, ignore_errors=True)
super(TestPackageCache, self).tearDown() super(TestPackageCache, self).tearDown()
@testtools.skipIf(os.name == 'nt', "Doesn't work on Windows")
def test_load_package(self): def test_load_package(self):
fqn = 'io.murano.apps.test' fqn = 'io.murano.apps.test'
path, name = utils.compose_package( path, name = utils.compose_package(
'test', 'test',
os.path.join(self.location, 'manifest.yaml'), os.path.join(self.location, 'manifest.yaml'),
self.location, archive_dir=self.location) self.location, archive_dir=self.location)
with open(path) as f: with open(path, 'rb') as f:
package_data = f.read() package_data = f.read()
spec = semantic_version.Spec('*') spec = semantic_version.Spec('*')
@ -154,9 +154,9 @@ class TestCombinedPackageLoader(base.MuranoTestCase):
location = os.path.dirname(__file__) location = os.path.dirname(__file__)
CONF.set_override('load_packages_from', [location], 'packages_opts', CONF.set_override('load_packages_from', [location], 'packages_opts',
enforce_type=True) enforce_type=True)
cls.murano_client_factory = mock.MagicMock() cls.execution_session = mock.MagicMock()
cls.loader = package_loader.CombinedPackageLoader( cls.loader = package_loader.CombinedPackageLoader(
cls.murano_client_factory, 'test_tenant_id') cls.execution_session)
cls.api_loader = mock.MagicMock() cls.api_loader = mock.MagicMock()
cls.loader.api_loader = cls.api_loader cls.loader.api_loader = cls.api_loader

View File

@ -18,7 +18,6 @@ import mock
from oslo_config import cfg from oslo_config import cfg
from murano.common import engine from murano.common import engine
from murano.engine import client_manager
from murano.policy import model_policy_enforcer from murano.policy import model_policy_enforcer
from murano.tests.unit import base from murano.tests.unit import base
@ -45,14 +44,8 @@ class TestModelPolicyEnforcer(base.MuranoTestCase):
self.congress_client_mock = \ self.congress_client_mock = \
mock.Mock(spec=congressclient.v1.client.Client) mock.Mock(spec=congressclient.v1.client.Client)
model_policy_enforcer.ModelPolicyEnforcer._create_client = mock.Mock(
self.client_manager_mock = mock.Mock(spec=client_manager.ClientManager) return_value=self.congress_client_mock)
self.client_manager_mock.get_congress_client.return_value = \
self.congress_client_mock
self.environment = mock.Mock()
self.environment.clients = self.client_manager_mock
def test_enforcer_disabled(self): def test_enforcer_disabled(self):
executor = engine.TaskExecutor(self.task) executor = engine.TaskExecutor(self.task)
@ -74,17 +67,11 @@ class TestModelPolicyEnforcer(base.MuranoTestCase):
.validate.assert_called_once_with(self.model_dict, .validate.assert_called_once_with(self.model_dict,
self.package_loader) self.package_loader)
def test_enforcer_no_client(self):
self.client_manager_mock.get_congress_client.return_value = None
enforcer = model_policy_enforcer.ModelPolicyEnforcer(self.environment)
model = {'?': {'id': '123', 'type': 'class'}}
self.assertRaises(ValueError, enforcer.validate, model)
def test_validation_pass(self): def test_validation_pass(self):
self.congress_client_mock.execute_policy_action.return_value = \ self.congress_client_mock.execute_policy_action.return_value = \
{"result": []} {"result": []}
model = {'?': {'id': '123', 'type': 'class'}} model = {'?': {'id': '123', 'type': 'class'}}
enforcer = model_policy_enforcer.ModelPolicyEnforcer(self.environment) enforcer = model_policy_enforcer.ModelPolicyEnforcer(mock.Mock())
enforcer.validate(model) enforcer.validate(model)
def test_validation_failure(self): def test_validation_failure(self):
@ -92,7 +79,7 @@ class TestModelPolicyEnforcer(base.MuranoTestCase):
{"result": ['predeploy_errors("123","instance1","failure")']} {"result": ['predeploy_errors("123","instance1","failure")']}
model = {'?': {'id': '123', 'type': 'class'}} model = {'?': {'id': '123', 'type': 'class'}}
enforcer = model_policy_enforcer.ModelPolicyEnforcer(self.environment) enforcer = model_policy_enforcer.ModelPolicyEnforcer(mock.Mock())
self.assertRaises(model_policy_enforcer.ValidationError, self.assertRaises(model_policy_enforcer.ValidationError,
enforcer.validate, model) enforcer.validate, model)
@ -107,7 +94,7 @@ class TestModelPolicyEnforcer(base.MuranoTestCase):
action_manager = mock.MagicMock() action_manager = mock.MagicMock()
enforcer = model_policy_enforcer.ModelPolicyEnforcer( enforcer = model_policy_enforcer.ModelPolicyEnforcer(
self.environment, action_manager) mock.Mock(), action_manager)
enforcer.modify(obj) enforcer.modify(obj)
self.assertTrue(action_manager.apply_action.called) self.assertTrue(action_manager.apply_action.called)
@ -120,7 +107,7 @@ class TestModelPolicyEnforcer(base.MuranoTestCase):
'predeploy_errors("env2","instance1","Instance 3 has problem")' 'predeploy_errors("env2","instance1","Instance 3 has problem")'
] ]
enforcer = model_policy_enforcer.ModelPolicyEnforcer(self.environment) enforcer = model_policy_enforcer.ModelPolicyEnforcer(None)
result = enforcer._parse_simulation_result( result = enforcer._parse_simulation_result(
'predeploy_errors', 'env1', congress_response) 'predeploy_errors', 'env1', congress_response)

View File

@ -17,50 +17,34 @@ from heatclient.v1 import stacks
import mock import mock
from oslo_config import cfg from oslo_config import cfg
from murano.dsl import constants
from murano.dsl import helpers
from murano.dsl import murano_class
from murano.dsl import object_store
from murano.engine import client_manager
from murano.engine import environment
from murano.engine.system import heat_stack from murano.engine.system import heat_stack
from murano.tests.unit import base from murano.tests.unit import base
MOD_NAME = 'murano.engine.system.heat_stack' CLS_NAME = 'murano.engine.system.heat_stack.HeatStack'
CONF = cfg.CONF CONF = cfg.CONF
class TestHeatStack(base.MuranoTestCase): class TestHeatStack(base.MuranoTestCase):
def setUp(self): def setUp(self):
super(TestHeatStack, self).setUp() super(TestHeatStack, self).setUp()
self.mock_murano_class = mock.Mock(spec=murano_class.MuranoClass) self.heat_client_mock = mock.Mock()
self.mock_murano_class.name = 'io.murano.system.HeatStack'
self.mock_murano_class.declared_parents = []
self.heat_client_mock = mock.MagicMock()
self.heat_client_mock.stacks = mock.MagicMock(spec=stacks.StackManager) self.heat_client_mock.stacks = mock.MagicMock(spec=stacks.StackManager)
self.mock_object_store = mock.Mock(spec=object_store.ObjectStore)
self.environment_mock = mock.Mock(
spec=environment.Environment)
client_manager_mock = mock.Mock(spec=client_manager.ClientManager)
client_manager_mock.get_heat_client.return_value = \
self.heat_client_mock
self.environment_mock.clients = client_manager_mock
CONF.set_override('stack_tags', ['test-murano'], 'heat', CONF.set_override('stack_tags', ['test-murano'], 'heat',
enforce_type=True) enforce_type=True)
self.mock_tag = ','.join(CONF.heat.stack_tags) self.mock_tag = ','.join(CONF.heat.stack_tags)
heat_stack.HeatStack._get_token_client = mock.Mock(
return_value=self.heat_client_mock)
heat_stack.HeatStack._get_client = mock.Mock(
return_value=self.heat_client_mock)
@mock.patch(MOD_NAME + '.HeatStack._wait_state') @mock.patch(CLS_NAME + '._wait_state')
@mock.patch(MOD_NAME + '.HeatStack._get_status') @mock.patch(CLS_NAME + '._get_status')
def test_push_adds_version(self, status_get, wait_st): def test_push_adds_version(self, status_get, wait_st):
"""Assert that if heat_template_version is omitted, it's added.""" """Assert that if heat_template_version is omitted, it's added."""
status_get.return_value = 'NOT_FOUND' status_get.return_value = 'NOT_FOUND'
wait_st.return_value = {} wait_st.return_value = {}
context = {constants.CTX_ENVIRONMENT: self.environment_mock} hs = heat_stack.HeatStack('test-stack', 'Generated by TestHeatStack')
with helpers.contextual(context):
hs = heat_stack.HeatStack(
'test-stack', 'Generated by TestHeatStack')
hs._template = {'resources': {'test': 1}} hs._template = {'resources': {'test': 1}}
hs._files = {} hs._files = {}
hs._hot_environment = '' hs._hot_environment = ''
@ -68,9 +52,7 @@ class TestHeatStack(base.MuranoTestCase):
hs._applied = False hs._applied = False
hs.push() hs.push()
with helpers.contextual(context): hs = heat_stack.HeatStack('test-stack', 'Generated by TestHeatStack')
hs = heat_stack.HeatStack(
'test-stack', 'Generated by TestHeatStack')
hs._template = {'resources': {'test': 1}} hs._template = {'resources': {'test': 1}}
hs._files = {} hs._files = {}
hs._parameters = {} hs._parameters = {}
@ -93,16 +75,13 @@ class TestHeatStack(base.MuranoTestCase):
) )
self.assertTrue(hs._applied) self.assertTrue(hs._applied)
@mock.patch(MOD_NAME + '.HeatStack._wait_state') @mock.patch(CLS_NAME + '._wait_state')
@mock.patch(MOD_NAME + '.HeatStack._get_status') @mock.patch(CLS_NAME + '._get_status')
def test_description_is_optional(self, status_get, wait_st): def test_description_is_optional(self, status_get, wait_st):
"""Assert that if heat_template_version is omitted, it's added.""" """Assert that if heat_template_version is omitted, it's added."""
status_get.return_value = 'NOT_FOUND' status_get.return_value = 'NOT_FOUND'
wait_st.return_value = {} wait_st.return_value = {}
context = {constants.CTX_ENVIRONMENT: self.environment_mock}
with helpers.contextual(context):
hs = heat_stack.HeatStack('test-stack', None) hs = heat_stack.HeatStack('test-stack', None)
hs._template = {'resources': {'test': 1}} hs._template = {'resources': {'test': 1}}
hs._files = {} hs._files = {}
@ -126,16 +105,13 @@ class TestHeatStack(base.MuranoTestCase):
) )
self.assertTrue(hs._applied) self.assertTrue(hs._applied)
@mock.patch(MOD_NAME + '.HeatStack._wait_state') @mock.patch(CLS_NAME + '._wait_state')
@mock.patch(MOD_NAME + '.HeatStack._get_status') @mock.patch(CLS_NAME + '._get_status')
def test_heat_files_are_sent(self, status_get, wait_st): def test_heat_files_are_sent(self, status_get, wait_st):
"""Assert that if heat_template_version is omitted, it's added.""" """Assert that if heat_template_version is omitted, it's added."""
status_get.return_value = 'NOT_FOUND' status_get.return_value = 'NOT_FOUND'
wait_st.return_value = {} wait_st.return_value = {}
context = {constants.CTX_ENVIRONMENT: self.environment_mock}
with helpers.contextual(context):
hs = heat_stack.HeatStack('test-stack', None) hs = heat_stack.HeatStack('test-stack', None)
hs._description = None hs._description = None
hs._template = {'resources': {'test': 1}} hs._template = {'resources': {'test': 1}}
@ -160,16 +136,13 @@ class TestHeatStack(base.MuranoTestCase):
) )
self.assertTrue(hs._applied) self.assertTrue(hs._applied)
@mock.patch(MOD_NAME + '.HeatStack._wait_state') @mock.patch(CLS_NAME + '._wait_state')
@mock.patch(MOD_NAME + '.HeatStack._get_status') @mock.patch(CLS_NAME + '._get_status')
def test_heat_environments_are_sent(self, status_get, wait_st): def test_heat_environments_are_sent(self, status_get, wait_st):
"""Assert that if heat_template_version is omitted, it's added.""" """Assert that if heat_template_version is omitted, it's added."""
status_get.return_value = 'NOT_FOUND' status_get.return_value = 'NOT_FOUND'
wait_st.return_value = {} wait_st.return_value = {}
context = {constants.CTX_ENVIRONMENT: self.environment_mock}
with helpers.contextual(context):
hs = heat_stack.HeatStack('test-stack', None) hs = heat_stack.HeatStack('test-stack', None)
hs._description = None hs._description = None
hs._template = {'resources': {'test': 1}} hs._template = {'resources': {'test': 1}}
@ -194,14 +167,11 @@ class TestHeatStack(base.MuranoTestCase):
) )
self.assertTrue(hs._applied) self.assertTrue(hs._applied)
@mock.patch(MOD_NAME + '.HeatStack.current') @mock.patch(CLS_NAME + '.current')
def test_update_wrong_template_version(self, current): def test_update_wrong_template_version(self, current):
"""Template version other than expected should cause error.""" """Template version other than expected should cause error."""
context = {constants.CTX_ENVIRONMENT: self.environment_mock} hs = heat_stack.HeatStack('test-stack', 'Generated by TestHeatStack')
with helpers.contextual(context):
hs = heat_stack.HeatStack(
'test-stack', 'Generated by TestHeatStack')
hs._template = {'resources': {'test': 1}} hs._template = {'resources': {'test': 1}}
invalid_template = { invalid_template = {
@ -227,8 +197,8 @@ class TestHeatStack(base.MuranoTestCase):
expected['heat_template_version'] = '2013-05-23' expected['heat_template_version'] = '2013-05-23'
self.assertEqual(expected, hs._template) self.assertEqual(expected, hs._template)
@mock.patch(MOD_NAME + '.HeatStack._wait_state') @mock.patch(CLS_NAME + '._wait_state')
@mock.patch(MOD_NAME + '.HeatStack._get_status') @mock.patch(CLS_NAME + '._get_status')
def test_heat_stack_tags_are_sent(self, status_get, wait_st): def test_heat_stack_tags_are_sent(self, status_get, wait_st):
"""Assert that heat_stack `tags` parameter get push & with """Assert that heat_stack `tags` parameter get push & with
value from config parameter `stack_tags`. value from config parameter `stack_tags`.
@ -238,9 +208,6 @@ class TestHeatStack(base.MuranoTestCase):
wait_st.return_value = {} wait_st.return_value = {}
CONF.set_override('stack_tags', ['test-murano', 'murano-tag'], 'heat', CONF.set_override('stack_tags', ['test-murano', 'murano-tag'], 'heat',
enforce_type=True) enforce_type=True)
context = {constants.CTX_ENVIRONMENT: self.environment_mock}
with helpers.contextual(context):
hs = heat_stack.HeatStack('test-stack', None) hs = heat_stack.HeatStack('test-stack', None)
hs._description = None hs._description = None
hs._template = {'resources': {'test': 1}} hs._template = {'resources': {'test': 1}}