diff --git a/meta/io.murano/Classes/Environment.yaml b/meta/io.murano/Classes/Environment.yaml index c8711eb56..acac1fdd4 100644 --- a/meta/io.murano/Classes/Environment.yaml +++ b/meta/io.murano/Classes/Environment.yaml @@ -59,15 +59,15 @@ Methods: deploy: Usage: Action Body: - Try: - - $.agentListener.start() - - If: len($.applications) = 0 - Then: - - $.stack.delete() - Else: - - $.applications.pselect($.deploy()) - Finally: - - $.agentListener.stop() + - $minimalStack: + resources: {} + - $.stack.updateTemplate($minimalStack) + - $.stack.push() + - Try: + - $.agentListener.start() + - $.applications.pselect($.deploy()) + Finally: + - $.agentListener.stop() destroy: Body: diff --git a/murano/common/config.py b/murano/common/config.py index d01daa90c..aec55300d 100644 --- a/murano/common/config.py +++ b/murano/common/config.py @@ -98,8 +98,6 @@ neutron_opts = [ ] keystone_opts = [ - cfg.StrOpt('auth_url', help='URL to access OpenStack Identity service.'), - cfg.BoolOpt('insecure', default=False, help='This option explicitly allows Murano to perform ' '"insecure" SSL connections and transfers with ' @@ -180,7 +178,10 @@ stats_opts = [ engine_opts = [ cfg.BoolOpt('disable_murano_agent', default=False, - help=_('Disallow the use of murano-agent')) + help=_('Disallow the use of murano-agent')), + cfg.BoolOpt('use_trusts', default=False, + help=_("Create resources using trust token rather " + "than user's token")) ] # TODO(sjmc7): move into engine opts? diff --git a/murano/common/engine.py b/murano/common/engine.py index b6bd7b584..c6714768d 100644 --- a/murano/common/engine.py +++ b/murano/common/engine.py @@ -26,6 +26,8 @@ from murano.common import rpc from murano.dsl import dsl_exception from murano.dsl import executor from murano.dsl import results_serializer +from murano.engine import auth_utils +from murano.engine import client_manager from murano.engine import environment from murano.engine import package_class_loader from murano.engine import package_loader @@ -103,32 +105,50 @@ class TaskExecutor(object): self._environment = environment.Environment() self._environment.token = task['token'] self._environment.tenant_id = task['tenant_id'] + self._environment.system_attributes = self._model.get('SystemData', {}) + self._environment.clients = client_manager.ClientManager() def execute(self): - token, tenant_id = self.environment.token, self.environment.tenant_id - with package_loader.ApiPackageLoader(token, tenant_id) as pkg_loader: - class_loader = package_class_loader.PackageClassLoader(pkg_loader) - system_objects.register(class_loader, pkg_loader) + self._create_trust() - exc = executor.MuranoDslExecutor(class_loader, self.environment) - obj = exc.load(self.model) + try: + # pkg_loader = package_loader.DirectoryPackageLoader('./meta') + # return self._execute(pkg_loader) - try: - # Skip execution of action in case of no action is provided. - # Model will be just loaded, cleaned-up and unloaded. - # Most of the time this is used for deletion of environments. - if self.action: - self._invoke(exc) - except Exception as e: - if isinstance(e, dsl_exception.MuranoPlException): - LOG.error('\n' + e.format(prefix=' ')) - else: - LOG.exception(e) - reporter = status_reporter.StatusReporter() - reporter.initialize(obj) - reporter.report_error(obj, str(e)) + murano_client_factory = lambda: \ + self._environment.clients.get_murano_client(self._environment) + with package_loader.ApiPackageLoader( + murano_client_factory) as pkg_loader: + return self._execute(pkg_loader) + finally: + if self._model['Objects'] is None: + self._delete_trust() - return results_serializer.serialize(obj, exc) + def _execute(self, pkg_loader): + class_loader = package_class_loader.PackageClassLoader(pkg_loader) + system_objects.register(class_loader, pkg_loader) + + exc = executor.MuranoDslExecutor(class_loader, self.environment) + obj = exc.load(self.model) + + try: + # Skip execution of action in case of no action is provided. + # Model will be just loaded, cleaned-up and unloaded. + # Most of the time this is used for deletion of environments. + if self.action: + self._invoke(exc) + except Exception as e: + if isinstance(e, dsl_exception.MuranoPlException): + LOG.error('\n' + e.format(prefix=' ')) + else: + LOG.exception(e) + reporter = status_reporter.StatusReporter() + reporter.initialize(obj) + reporter.report_error(obj, str(e)) + + result = results_serializer.serialize(obj, exc) + result['SystemData'] = self._environment.system_attributes + return result def _invoke(self, mpl_executor): obj = mpl_executor.object_store.get(self.action['object_id']) @@ -136,3 +156,19 @@ class TaskExecutor(object): if obj is not None: obj.type.invoke(method_name, mpl_executor, obj, args) + + def _create_trust(self): + if not config.CONF.engine.use_trusts: + return + trust_id = self._environment.system_attributes.get('TrustId') + if not trust_id: + trust_id = auth_utils.create_trust(self._environment) + self._environment.system_attributes['TrustId'] = trust_id + self._environment.trust_id = trust_id + + def _delete_trust(self): + trust_id = self._environment.trust_id + if trust_id: + auth_utils.delete_trust(self._environment) + self._environment.system_attributes['TrustId'] = None + self._environment.trust_id = None diff --git a/murano/engine/auth_utils.py b/murano/engine/auth_utils.py new file mode 100644 index 000000000..9f4935151 --- /dev/null +++ b/murano/engine/auth_utils.py @@ -0,0 +1,101 @@ +# 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. + + +from keystoneclient.v3 import client as ks_client +from oslo.config import cfg + +from murano.openstack.common import importutils + + +def get_client(environment): + settings = _get_keystone_settings() + kwargs = { + 'token': environment.token, + 'tenant_id': environment.tenant_id, + 'auth_url': settings['auth_url'] + } + kwargs.update(settings['ssl']) + keystone = ks_client.Client(**kwargs) + keystone.management_url = settings['auth_url'] + + return keystone + + +def get_client_for_admin(project_name): + return _admin_client(project_name=project_name) + + +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']) + + 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(environment): + return _admin_client(environment.trust_id) + + +def create_trust(environment): + client = get_client(environment) + + 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=environment.tenant_id) + + return trust.id + + +def delete_trust(environment): + keystone_client = get_client_for_trusts(environment) + keystone_client.trusts.delete(environment.trust_id) + + +def _get_keystone_settings(): + importutils.import_module('keystonemiddleware.auth_token') + return { + 'auth_url': cfg.CONF.keystone_authtoken.auth_uri.replace('v2.0', 'v3'), + 'username': cfg.CONF.keystone_authtoken.admin_user, + 'password': cfg.CONF.keystone_authtoken.admin_password, + 'project_name': cfg.CONF.keystone_authtoken.admin_tenant_name, + 'ssl': { + 'cacert': cfg.CONF.keystone.ca_file, + 'insecure': cfg.CONF.keystone.insecure, + 'cert': cfg.CONF.keystone.cert_file, + 'key': cfg.CONF.keystone.key_file + } + } diff --git a/murano/engine/client_manager.py b/murano/engine/client_manager.py new file mode 100644 index 000000000..7e5806e93 --- /dev/null +++ b/murano/engine/client_manager.py @@ -0,0 +1,142 @@ +# 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. + + +from eventlet import semaphore +import heatclient.client as hclient +import muranoclient.v1.client as muranoclient +import neutronclient.v2_0.client as nclient + +from murano.common import config +from murano.dsl import helpers +from murano.engine import auth_utils +from murano.engine import environment + + +class ClientManager(object): + def __init__(self): + self._trusts_keystone_client = None + self._token_keystone_client = None + self._cache = {} + self._semaphore = semaphore.BoundedSemaphore() + + def _get_environment(self, context): + if isinstance(context, environment.Environment): + return context + return helpers.get_environment(context) + + def _get_client(self, context, name, use_trusts, client_factory): + if not config.CONF.engine.use_trusts: + use_trusts = False + + keystone_client = None if name == 'keystone' else \ + self.get_keystone_client(context, 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: + env = self._get_environment(context) + token = env.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, context, use_trusts=True): + if not config.CONF.engine.use_trusts: + use_trusts = False + env = self._get_environment(context) + factory = lambda _1, _2: auth_utils.get_client_for_trusts(env) \ + if use_trusts else auth_utils.get_client(env) + + return self._get_client(context, 'keystone', use_trusts, factory) + + def get_heat_client(self, context, use_trusts=True): + if not config.CONF.engine.use_trusts: + use_trusts = False + + def factory(keystone_client, auth_token): + heat_settings = config.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 config.CONF.engine.use_trusts: + kwargs.update({ + 'username': 'badusername', + 'password': 'badpassword' + }) + return hclient.Client('1', heat_url, **kwargs) + + return self._get_client(context, 'heat', use_trusts, factory) + + def get_neutron_client(self, context, use_trusts=True): + if not config.CONF.engine.use_trusts: + use_trusts = False + + def factory(keystone_client, auth_token): + neutron_settings = config.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(context, 'neutron', use_trusts, factory) + + def get_murano_client(self, context, use_trusts=True): + if not config.CONF.engine.use_trusts: + use_trusts = False + + def factory(keystone_client, auth_token): + murano_settings = config.CONF.murano + + murano_url = \ + murano_settings.url or keystone_client.service_catalog.url_for( + service_type='application_catalog', + endpoint_type=murano_settings.endpoint_type) + + return muranoclient.Client( + endpoint=murano_url, + key_file=murano_settings.key_file or None, + cacert=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) + + return self._get_client(context, 'murano', use_trusts, factory) diff --git a/murano/engine/environment.py b/murano/engine/environment.py index e696a4677..df9ed561a 100644 --- a/murano/engine/environment.py +++ b/murano/engine/environment.py @@ -18,3 +18,6 @@ class Environment(object): def __init__(self): self.token = None self.tenant_id = None + self.trust_id = None + self.system_attributes = {} + self.clients = None diff --git a/murano/engine/package_loader.py b/murano/engine/package_loader.py index 0008ab37a..6af9cb544 100644 --- a/murano/engine/package_loader.py +++ b/murano/engine/package_loader.py @@ -20,9 +20,7 @@ import sys import tempfile import uuid -from keystoneclient.v2_0 import client as keystoneclient from muranoclient.common import exceptions as muranoclient_exc -from muranoclient.v1 import client as muranoclient import six from murano.common import config @@ -46,9 +44,9 @@ class PackageLoader(six.with_metaclass(abc.ABCMeta)): class ApiPackageLoader(PackageLoader): - def __init__(self, token_id, tenant_id): + def __init__(self, murano_client_factory): self._cache_directory = self._get_cache_directory() - self._client = self._get_murano_client(token_id, tenant_id) + self._murano_client_factory = murano_client_factory def get_package_by_class(self, name): filter_opts = {'class_name': name, 'limit': 1} @@ -81,44 +79,10 @@ class ApiPackageLoader(PackageLoader): LOG.debug('Cache for package loader is located at: %s' % directory) return directory - @staticmethod - def _get_murano_client(token_id, tenant_id): - murano_settings = config.CONF.murano - - endpoint_url = murano_settings.url - if endpoint_url is None: - keystone_settings = config.CONF.keystone - keystone_client = keystoneclient.Client( - endpoint=keystone_settings.auth_url, - cacert=keystone_settings.ca_file or None, - cert=keystone_settings.cert_file or None, - key=keystone_settings.key_file or None, - insecure=keystone_settings.insecure - ) - - if not keystone_client.authenticate( - auth_url=keystone_settings.auth_url, - tenant_id=tenant_id, - token=token_id): - raise muranoclient_exc.HTTPUnauthorized() - - endpoint_url = keystone_client.service_catalog.url_for( - service_type='application_catalog', - endpoint_type=murano_settings.endpoint_type - ) - - return muranoclient.Client( - endpoint=endpoint_url, - key_file=murano_settings.key_file or None, - cacert=murano_settings.cacert or None, - cert_file=murano_settings.cert_file or None, - insecure=murano_settings.insecure, - token=token_id - ) - def _get_definition(self, filter_opts): try: - packages = self._client.packages.filter(**filter_opts) + packages = self._murano_client_factory().packages.filter( + **filter_opts) try: return packages.next() except StopIteration: @@ -145,7 +109,8 @@ class ApiPackageLoader(PackageLoader): LOG.exception('Unable to load package from cache. Clean-up...') shutil.rmtree(package_directory, ignore_errors=True) try: - package_data = self._client.packages.download(package_id) + package_data = self._murano_client_factory().packages.download( + package_id) except muranoclient_exc.HTTPException as e: msg = 'Error loading package id {0}: {1}'.format( package_id, str(e) diff --git a/murano/engine/system/heat_stack.py b/murano/engine/system/heat_stack.py index 717fb3709..d0c4020f8 100644 --- a/murano/engine/system/heat_stack.py +++ b/murano/engine/system/heat_stack.py @@ -16,11 +16,8 @@ import copy import eventlet -import heatclient.client as hclient import heatclient.exc as heat_exc -import keystoneclient.v2_0.client as ksclient -import murano.common.config as config import murano.common.utils as utils import murano.dsl.helpers as helpers import murano.dsl.murano_class as murano_class @@ -44,45 +41,15 @@ class HeatStack(murano_object.MuranoObject): self._parameters = {} self._applied = True self._description = description - environment = helpers.get_environment(_context) - keystone_settings = config.CONF.keystone - heat_settings = config.CONF.heat + self._clients = helpers.get_environment(_context).clients - client = ksclient.Client( - endpoint=keystone_settings.auth_url, - cacert=keystone_settings.ca_file or None, - cert=keystone_settings.cert_file or None, - key=keystone_settings.key_file or None, - insecure=keystone_settings.insecure) - - if not client.authenticate( - auth_url=keystone_settings.auth_url, - tenant_id=environment.tenant_id, - token=environment.token): - raise heat_exc.HTTPUnauthorized() - - heat_url = client.service_catalog.url_for( - service_type='orchestration', - endpoint_type=heat_settings.endpoint_type) - - self._heat_client = hclient.Client( - '1', - heat_url, - username='badusername', - password='badpassword', - token_only=True, - token=client.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) - - def current(self): + def current(self, _context): + client = self._clients.get_heat_client(_context) if self._template is not None: return self._template try: - stack_info = self._heat_client.stacks.get(stack_id=self._name) - template = self._heat_client.stacks.template( + stack_info = client.stacks.get(stack_id=self._name) + template = client.stacks.template( stack_id='{0}/{1}'.format( stack_info.stack_name, stack_info.id)) @@ -98,14 +65,14 @@ class HeatStack(murano_object.MuranoObject): self._parameters.clear() return {} - def parameters(self): - self.current() + def parameters(self, _context): + self.current(_context) return self._parameters.copy() - def reload(self): + def reload(self, _context): self._template = None self._parameters.clear() - return self.current() + return self.current(_context) def setTemplate(self, template): self._template = template @@ -116,14 +83,14 @@ class HeatStack(murano_object.MuranoObject): self._parameters = parameters self._applied = False - def updateTemplate(self, template): + def updateTemplate(self, _context, template): template_version = template.get('heat_template_version', HEAT_TEMPLATE_VERSION) if template_version != HEAT_TEMPLATE_VERSION: err_msg = ("Currently only heat_template_version %s " "is supported." % HEAT_TEMPLATE_VERSION) raise HeatStackError(err_msg) - self.current() + self.current(_context) self._template = helpers.merge_dicts(self._template, template) self._applied = False @@ -132,23 +99,24 @@ class HeatStack(murano_object.MuranoObject): return dict((k, v) for k, v in parameters.iteritems() if not k.startswith('OS::')) - def _get_status(self): + def _get_status(self, context): status = [None] def status_func(state_value): status[0] = state_value return True - self._wait_state(status_func) + self._wait_state(context, status_func) return status[0] - def _wait_state(self, status_func): + def _wait_state(self, context, status_func): tries = 4 delay = 1 while tries > 0: while True: + client = self._clients.get_heat_client(context) try: - stack_info = self._heat_client.stacks.get( + stack_info = client.stacks.get( stack_id=self._name) status = stack_info.stack_status tries = 4 @@ -164,7 +132,7 @@ class HeatStack(murano_object.MuranoObject): eventlet.sleep(delay) break - if 'IN_PROGRESS' in status: + if 'IN_PROGRESS' in status or status == '_': eventlet.sleep(2) continue if not status_func(status): @@ -180,10 +148,10 @@ class HeatStack(murano_object.MuranoObject): return {} return {} - def output(self): - return self._wait_state(lambda status: True) + def output(self, _context): + return self._wait_state(_context, lambda status: True) - def push(self): + def push(self, _context): if self._applied or self._template is None: return @@ -196,41 +164,47 @@ class HeatStack(murano_object.MuranoObject): template = copy.deepcopy(self._template) LOG.info('Pushing: {0}'.format(template)) - current_status = self._get_status() + current_status = self._get_status(_context) resources = template.get('Resources') or template.get('resources') if current_status == 'NOT_FOUND': - if resources: - self._heat_client.stacks.create( + if resources is not None: + token_client = self._clients.get_heat_client(_context, False) + token_client.stacks.create( stack_name=self._name, parameters=self._parameters, template=template, disable_rollback=True) self._wait_state( + _context, lambda status: status == 'CREATE_COMPLETE') else: - if resources: - self._heat_client.stacks.update( + if resources is not None: + trust_client = self._clients.get_heat_client(_context) + + trust_client.stacks.update( stack_id=self._name, parameters=self._parameters, template=template) self._wait_state( + _context, lambda status: status == 'UPDATE_COMPLETE') else: - self.delete() + self.delete(_context) self._applied = not utils.is_different(self._template, template) - def delete(self): + def delete(self, _context): + client = self._clients.get_heat_client(_context) try: - if not self.current(): + if not self.current(_context): return - self._heat_client.stacks.delete( - stack_id=self._name) + client.stacks.delete(stack_id=self._name) self._wait_state( + _context, lambda status: status in ('DELETE_COMPLETE', 'NOT_FOUND')) except heat_exc.NotFound: - LOG.warn("Stack {0} already deleted?".format(self._name)) + LOG.warn('Stack {0} already deleted?'.format(self._name)) self._template = {} self._applied = True diff --git a/murano/engine/system/net_explorer.py b/murano/engine/system/net_explorer.py index f1aa9b711..a0366df5a 100644 --- a/murano/engine/system/net_explorer.py +++ b/murano/engine/system/net_explorer.py @@ -14,11 +14,8 @@ # limitations under the License. import math -import keystoneclient.apiclient.exceptions as ks_exc -import keystoneclient.v2_0.client as ksclient import netaddr from netaddr.strategy import ipv4 -import neutronclient.v2_0.client as nclient import murano.common.config as config import murano.dsl.helpers as helpers @@ -36,43 +33,18 @@ class NetworkExplorer(murano_object.MuranoObject): # noinspection PyAttributeOutsideInit def initialize(self, _context): environment = helpers.get_environment(_context) + self._clients = environment.clients self._tenant_id = environment.tenant_id - keystone_settings = config.CONF.keystone - neutron_settings = config.CONF.neutron self._settings = config.CONF.networking - - keystone_client = ksclient.Client( - endpoint=keystone_settings.auth_url, - cacert=keystone_settings.ca_file or None, - cert=keystone_settings.cert_file or None, - key=keystone_settings.key_file or None, - insecure=keystone_settings.insecure) - - if not keystone_client.authenticate( - auth_url=keystone_settings.auth_url, - tenant_id=environment.tenant_id, - token=environment.token): - raise ks_exc.AuthorizationFailure() - - neutron_url = keystone_client.service_catalog.url_for( - service_type='network', - endpoint_type=neutron_settings.endpoint_type) - - self._neutron = \ - nclient.Client(endpoint_url=neutron_url, - token=environment.token, - ca_cert=neutron_settings.ca_cert or None, - insecure=neutron_settings.insecure) - self._available_cidrs = self._generate_possible_cidrs() # noinspection PyPep8Naming - def getDefaultRouter(self): + def getDefaultRouter(self, _context): + client = self._clients.get_neutron_client(_context) router_name = self._settings.router_name - routers = self._neutron.\ - list_routers(tenant_id=self._tenant_id, name=router_name).\ - get('routers') + routers = client.list_routers( + tenant_id=self._tenant_id, name=router_name).get('routers') if len(routers) == 0: LOG.debug('Router {0} not found'.format(router_name)) if self._settings.create_router: @@ -82,8 +54,7 @@ class NetworkExplorer(murano_object.MuranoObject): kwargs = {'id': external_network} \ if uuidutils.is_uuid_like(external_network) \ else {'name': external_network} - networks = self._neutron.list_networks(**kwargs). \ - get('networks') + networks = client.list_networks(**kwargs).get('networks') ext_nets = filter(lambda n: n['router:external'], networks) if len(ext_nets) == 0: raise KeyError('Router %s could not be created, ' @@ -99,8 +70,7 @@ class NetworkExplorer(murano_object.MuranoObject): 'admin_state_up': True, } } - router = self._neutron.create_router(body=body_data).\ - get('router') + router = client.create_router(body=body_data).get('router') LOG.debug('Created router: {0}'.format(router)) return router['id'] else: @@ -113,13 +83,13 @@ class NetworkExplorer(murano_object.MuranoObject): return router_id # noinspection PyPep8Naming - def getAvailableCidr(self, routerId, netId): + def getAvailableCidr(self, _context, routerId, netId): """Uses hash of network IDs to minimize the collisions: different nets will attempt to pick different cidrs out of available range. If the cidr is taken will pick another one """ - taken_cidrs = self._get_cidrs_taken_by_router(routerId) + taken_cidrs = self._get_cidrs_taken_by_router(_context, routerId) id_hash = hash(netId) num_fails = 0 while num_fails < len(self._available_cidrs): @@ -137,20 +107,22 @@ class NetworkExplorer(murano_object.MuranoObject): return self._settings.default_dns # noinspection PyPep8Naming - def getExternalNetworkIdForRouter(self, routerId): - router = self._neutron.show_router(routerId).get('router') + def getExternalNetworkIdForRouter(self, _context, routerId): + client = self._clients.get_neutron_client(_context) + router = client.show_router(routerId).get('router') if not router or 'external_gateway_info' not in router: return None return router['external_gateway_info'].get('network_id') # noinspection PyPep8Naming - def getExternalNetworkIdForNetwork(self, networkId): - network = self._neutron.show_network(networkId).get('network') + def getExternalNetworkIdForNetwork(self, _context, networkId): + client = self._clients.get_neutron_client(_context) + network = client.show_network(networkId).get('network') if network.get('router:external', False): return networkId # Get router interfaces of the network - router_ports = self._neutron.list_ports( + router_ports = client.list_ports( **{'device_owner': 'network:router_interface', 'network_id': networkId}).get('ports') @@ -158,21 +130,23 @@ class NetworkExplorer(murano_object.MuranoObject): # check if the router has external_gateway set for router_port in router_ports: ext_net_id = self.getExternalNetworkIdForRouter( + _context, router_port.get('device_id')) if ext_net_id: return ext_net_id return None - def _get_cidrs_taken_by_router(self, router_id): + def _get_cidrs_taken_by_router(self, _context, router_id): if not router_id: return [] - ports = self._neutron.list_ports(device_id=router_id)['ports'] + client = self._clients.get_neutron_client(_context) + ports = client.list_ports(device_id=router_id)['ports'] subnet_ids = [] for port in ports: for fixed_ip in port['fixed_ips']: subnet_ids.append(fixed_ip['subnet_id']) - all_subnets = self._neutron.list_subnets()['subnets'] + all_subnets = client.list_subnets()['subnets'] filtered_cidrs = [netaddr.IPNetwork(subnet['cidr']) for subnet in all_subnets if subnet['id'] in subnet_ids] diff --git a/murano/tests/unit/test_heat_stack.py b/murano/tests/unit/test_heat_stack.py index 04db94ba4..b46b383fa 100644 --- a/murano/tests/unit/test_heat_stack.py +++ b/murano/tests/unit/test_heat_stack.py @@ -13,11 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +from heatclient.v1 import stacks import mock -from murano.tests.unit import base from murano.dsl import murano_object +from murano.engine import client_manager from murano.engine.system import heat_stack +from murano.tests.unit import base MOD_NAME = 'murano.engine.system.heat_stack' @@ -29,9 +31,15 @@ class TestHeatStack(base.MuranoTestCase): self.mock_murano_obj = mock.Mock(spec=murano_object.MuranoObject) self.mock_murano_obj.name = 'TestObj' self.mock_murano_obj.parents = [] + self.heat_client_mock = mock.MagicMock() + self.heat_client_mock.stacks = mock.MagicMock(spec=stacks.StackManager) + self.client_manager_mock = mock.Mock( + spec=client_manager.ClientManager) - @mock.patch('heatclient.client.Client') - def test_push_adds_version(self, mock_heat_client): + self.client_manager_mock.get_heat_client.return_value = \ + self.heat_client_mock + + def test_push_adds_version(self): """Assert that if heat_template_version is omitted, it's added.""" # Note that the 'with x as y, a as b:' syntax was introduced in # python 2.7, and contextlib.nested was deprecated in py2.7 @@ -43,20 +51,20 @@ class TestHeatStack(base.MuranoTestCase): hs = heat_stack.HeatStack(self.mock_murano_obj, None, None, None) - hs._heat_client = mock_heat_client hs._name = 'test-stack' hs._description = 'Generated by TestHeatStack' hs._template = {'resources': {'test': 1}} hs._parameters = {} hs._applied = False - hs.push() + hs._clients = self.client_manager_mock + hs.push(None) expected_template = { 'heat_template_version': '2013-05-23', 'description': 'Generated by TestHeatStack', 'resources': {'test': 1} } - mock_heat_client.stacks.create.assert_called_with( + self.heat_client_mock.stacks.create.assert_called_with( stack_name='test-stack', disable_rollback=True, parameters={}, @@ -64,8 +72,7 @@ class TestHeatStack(base.MuranoTestCase): ) self.assertTrue(hs._applied) - @mock.patch('heatclient.client.Client') - def test_description_is_optional(self, mock_heat_client): + def test_description_is_optional(self): """Assert that if heat_template_version is omitted, it's added.""" # Note that the 'with x as y, a as b:' syntax was introduced in # python 2.7, and contextlib.nested was deprecated in py2.7 @@ -77,19 +84,19 @@ class TestHeatStack(base.MuranoTestCase): hs = heat_stack.HeatStack(self.mock_murano_obj, None, None, None) - hs._heat_client = mock_heat_client + hs._clients = self.client_manager_mock hs._name = 'test-stack' hs._description = None hs._template = {'resources': {'test': 1}} hs._parameters = {} hs._applied = False - hs.push() + hs.push(None) expected_template = { 'heat_template_version': '2013-05-23', 'resources': {'test': 1} } - mock_heat_client.stacks.create.assert_called_with( + self.heat_client_mock.stacks.create.assert_called_with( stack_name='test-stack', disable_rollback=True, parameters={}, @@ -107,7 +114,7 @@ class TestHeatStack(base.MuranoTestCase): hs._template = {'resources': {'test': 1}} hs.type.properties = {} - erroring_template = { + invalid_template = { 'heat_template_version': 'something else' } @@ -116,17 +123,18 @@ class TestHeatStack(base.MuranoTestCase): e = self.assertRaises(heat_stack.HeatStackError, hs.updateTemplate, - erroring_template) + None, + invalid_template) err_msg = "Currently only heat_template_version 2013-05-23 "\ "is supported." self.assertEqual(err_msg, str(e)) # Check it's ok without a version - hs.updateTemplate({}) + hs.updateTemplate(None, {}) expected = {'resources': {'test': 1}} self.assertEqual(expected, hs._template) # .. or with a good version - hs.updateTemplate({'heat_template_version': '2013-05-23'}) + hs.updateTemplate(None, {'heat_template_version': '2013-05-23'}) expected['heat_template_version'] = '2013-05-23' self.assertEqual(expected, hs._template)