Merge "Use Keystone trusts to get fresh token"

This commit is contained in:
Jenkins 2014-12-10 00:06:51 +00:00 committed by Gerrit Code Review
commit af2a623c2b
10 changed files with 403 additions and 199 deletions

@ -59,15 +59,15 @@ Methods:
deploy: deploy:
Usage: Action Usage: Action
Body: Body:
Try: - $minimalStack:
- $.agentListener.start() resources: {}
- If: len($.applications) = 0 - $.stack.updateTemplate($minimalStack)
Then: - $.stack.push()
- $.stack.delete() - Try:
Else: - $.agentListener.start()
- $.applications.pselect($.deploy()) - $.applications.pselect($.deploy())
Finally: Finally:
- $.agentListener.stop() - $.agentListener.stop()
destroy: destroy:
Body: Body:

@ -98,8 +98,6 @@ neutron_opts = [
] ]
keystone_opts = [ keystone_opts = [
cfg.StrOpt('auth_url', help='URL to access OpenStack Identity service.'),
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 ' '"insecure" SSL connections and transfers with '
@ -180,7 +178,10 @@ stats_opts = [
engine_opts = [ engine_opts = [
cfg.BoolOpt('disable_murano_agent', default=False, 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? # TODO(sjmc7): move into engine opts?

@ -26,6 +26,8 @@ from murano.common import rpc
from murano.dsl import dsl_exception from murano.dsl import dsl_exception
from murano.dsl import executor from murano.dsl import executor
from murano.dsl import results_serializer 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 environment
from murano.engine import package_class_loader from murano.engine import package_class_loader
from murano.engine import package_loader from murano.engine import package_loader
@ -103,32 +105,50 @@ class TaskExecutor(object):
self._environment = environment.Environment() self._environment = environment.Environment()
self._environment.token = task['token'] self._environment.token = task['token']
self._environment.tenant_id = task['tenant_id'] self._environment.tenant_id = task['tenant_id']
self._environment.system_attributes = self._model.get('SystemData', {})
self._environment.clients = client_manager.ClientManager()
def execute(self): def execute(self):
token, tenant_id = self.environment.token, self.environment.tenant_id self._create_trust()
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)
exc = executor.MuranoDslExecutor(class_loader, self.environment) try:
obj = exc.load(self.model) # pkg_loader = package_loader.DirectoryPackageLoader('./meta')
# return self._execute(pkg_loader)
try: murano_client_factory = lambda: \
# Skip execution of action in case of no action is provided. self._environment.clients.get_murano_client(self._environment)
# Model will be just loaded, cleaned-up and unloaded. with package_loader.ApiPackageLoader(
# Most of the time this is used for deletion of environments. murano_client_factory) as pkg_loader:
if self.action: return self._execute(pkg_loader)
self._invoke(exc) finally:
except Exception as e: if self._model['Objects'] is None:
if isinstance(e, dsl_exception.MuranoPlException): self._delete_trust()
LOG.error('\n' + e.format(prefix=' '))
else:
LOG.exception(e)
reporter = status_reporter.StatusReporter()
reporter.initialize(obj)
reporter.report_error(obj, str(e))
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): def _invoke(self, mpl_executor):
obj = mpl_executor.object_store.get(self.action['object_id']) obj = mpl_executor.object_store.get(self.action['object_id'])
@ -136,3 +156,19 @@ class TaskExecutor(object):
if obj is not None: if obj is not None:
obj.type.invoke(method_name, mpl_executor, obj, args) 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

101
murano/engine/auth_utils.py Normal file

@ -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
}
}

@ -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)

@ -18,3 +18,6 @@ class Environment(object):
def __init__(self): def __init__(self):
self.token = None self.token = None
self.tenant_id = None self.tenant_id = None
self.trust_id = None
self.system_attributes = {}
self.clients = None

@ -20,9 +20,7 @@ import sys
import tempfile import tempfile
import uuid import uuid
from keystoneclient.v2_0 import client as keystoneclient
from muranoclient.common import exceptions as muranoclient_exc from muranoclient.common import exceptions as muranoclient_exc
from muranoclient.v1 import client as muranoclient
import six import six
from murano.common import config from murano.common import config
@ -46,9 +44,9 @@ class PackageLoader(six.with_metaclass(abc.ABCMeta)):
class ApiPackageLoader(PackageLoader): class ApiPackageLoader(PackageLoader):
def __init__(self, token_id, tenant_id): def __init__(self, murano_client_factory):
self._cache_directory = self._get_cache_directory() 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): def get_package_by_class(self, name):
filter_opts = {'class_name': name, 'limit': 1} 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) LOG.debug('Cache for package loader is located at: %s' % directory)
return 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): def _get_definition(self, filter_opts):
try: try:
packages = self._client.packages.filter(**filter_opts) packages = self._murano_client_factory().packages.filter(
**filter_opts)
try: try:
return packages.next() return packages.next()
except StopIteration: except StopIteration:
@ -145,7 +109,8 @@ class ApiPackageLoader(PackageLoader):
LOG.exception('Unable to load package from cache. Clean-up...') LOG.exception('Unable to load package from cache. Clean-up...')
shutil.rmtree(package_directory, ignore_errors=True) shutil.rmtree(package_directory, ignore_errors=True)
try: 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: 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)

@ -16,11 +16,8 @@
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
import keystoneclient.v2_0.client as ksclient
import murano.common.config as config
import murano.common.utils as utils import murano.common.utils as utils
import murano.dsl.helpers as helpers import murano.dsl.helpers as helpers
import murano.dsl.murano_class as murano_class import murano.dsl.murano_class as murano_class
@ -44,45 +41,15 @@ class HeatStack(murano_object.MuranoObject):
self._parameters = {} self._parameters = {}
self._applied = True self._applied = True
self._description = description self._description = description
environment = helpers.get_environment(_context) self._clients = helpers.get_environment(_context).clients
keystone_settings = config.CONF.keystone
heat_settings = config.CONF.heat
client = ksclient.Client( def current(self, _context):
endpoint=keystone_settings.auth_url, client = self._clients.get_heat_client(_context)
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):
if self._template is not None: if self._template is not None:
return self._template return self._template
try: try:
stack_info = self._heat_client.stacks.get(stack_id=self._name) stack_info = client.stacks.get(stack_id=self._name)
template = self._heat_client.stacks.template( template = 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))
@ -98,14 +65,14 @@ class HeatStack(murano_object.MuranoObject):
self._parameters.clear() self._parameters.clear()
return {} return {}
def parameters(self): def parameters(self, _context):
self.current() self.current(_context)
return self._parameters.copy() return self._parameters.copy()
def reload(self): def reload(self, _context):
self._template = None self._template = None
self._parameters.clear() self._parameters.clear()
return self.current() return self.current(_context)
def setTemplate(self, template): def setTemplate(self, template):
self._template = template self._template = template
@ -116,14 +83,14 @@ class HeatStack(murano_object.MuranoObject):
self._parameters = parameters self._parameters = parameters
self._applied = False self._applied = False
def updateTemplate(self, template): def updateTemplate(self, _context, template):
template_version = template.get('heat_template_version', template_version = template.get('heat_template_version',
HEAT_TEMPLATE_VERSION) HEAT_TEMPLATE_VERSION)
if template_version != HEAT_TEMPLATE_VERSION: if template_version != HEAT_TEMPLATE_VERSION:
err_msg = ("Currently only heat_template_version %s " err_msg = ("Currently only heat_template_version %s "
"is supported." % HEAT_TEMPLATE_VERSION) "is supported." % HEAT_TEMPLATE_VERSION)
raise HeatStackError(err_msg) raise HeatStackError(err_msg)
self.current() self.current(_context)
self._template = helpers.merge_dicts(self._template, template) self._template = helpers.merge_dicts(self._template, template)
self._applied = False self._applied = False
@ -132,23 +99,24 @@ class HeatStack(murano_object.MuranoObject):
return dict((k, v) for k, v in parameters.iteritems() if return dict((k, v) for k, v in parameters.iteritems() if
not k.startswith('OS::')) not k.startswith('OS::'))
def _get_status(self): def _get_status(self, context):
status = [None] status = [None]
def status_func(state_value): def status_func(state_value):
status[0] = state_value status[0] = state_value
return True return True
self._wait_state(status_func) self._wait_state(context, status_func)
return status[0] return status[0]
def _wait_state(self, status_func): def _wait_state(self, context, status_func):
tries = 4 tries = 4
delay = 1 delay = 1
while tries > 0: while tries > 0:
while True: while True:
client = self._clients.get_heat_client(context)
try: try:
stack_info = self._heat_client.stacks.get( stack_info = 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
@ -164,7 +132,7 @@ class HeatStack(murano_object.MuranoObject):
eventlet.sleep(delay) eventlet.sleep(delay)
break break
if 'IN_PROGRESS' in status: if 'IN_PROGRESS' in status or status == '_':
eventlet.sleep(2) eventlet.sleep(2)
continue continue
if not status_func(status): if not status_func(status):
@ -180,10 +148,10 @@ class HeatStack(murano_object.MuranoObject):
return {} return {}
return {} return {}
def output(self): def output(self, _context):
return self._wait_state(lambda status: True) return self._wait_state(_context, lambda status: True)
def push(self): def push(self, _context):
if self._applied or self._template is None: if self._applied or self._template is None:
return return
@ -196,41 +164,47 @@ class HeatStack(murano_object.MuranoObject):
template = copy.deepcopy(self._template) template = copy.deepcopy(self._template)
LOG.info('Pushing: {0}'.format(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') resources = template.get('Resources') or template.get('resources')
if current_status == 'NOT_FOUND': if current_status == 'NOT_FOUND':
if resources: if resources is not None:
self._heat_client.stacks.create( token_client = self._clients.get_heat_client(_context, False)
token_client.stacks.create(
stack_name=self._name, stack_name=self._name,
parameters=self._parameters, parameters=self._parameters,
template=template, template=template,
disable_rollback=True) disable_rollback=True)
self._wait_state( self._wait_state(
_context,
lambda status: status == 'CREATE_COMPLETE') lambda status: status == 'CREATE_COMPLETE')
else: else:
if resources: if resources is not None:
self._heat_client.stacks.update( trust_client = self._clients.get_heat_client(_context)
trust_client.stacks.update(
stack_id=self._name, stack_id=self._name,
parameters=self._parameters, parameters=self._parameters,
template=template) template=template)
self._wait_state( self._wait_state(
_context,
lambda status: status == 'UPDATE_COMPLETE') lambda status: status == 'UPDATE_COMPLETE')
else: else:
self.delete() self.delete(_context)
self._applied = not utils.is_different(self._template, template) self._applied = not utils.is_different(self._template, template)
def delete(self): def delete(self, _context):
client = self._clients.get_heat_client(_context)
try: try:
if not self.current(): if not self.current(_context):
return return
self._heat_client.stacks.delete( client.stacks.delete(stack_id=self._name)
stack_id=self._name)
self._wait_state( self._wait_state(
_context,
lambda status: status in ('DELETE_COMPLETE', 'NOT_FOUND')) lambda status: status in ('DELETE_COMPLETE', 'NOT_FOUND'))
except heat_exc.NotFound: 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._template = {}
self._applied = True self._applied = True

@ -14,11 +14,8 @@
# limitations under the License. # limitations under the License.
import math import math
import keystoneclient.apiclient.exceptions as ks_exc
import keystoneclient.v2_0.client as ksclient
import netaddr import netaddr
from netaddr.strategy import ipv4 from netaddr.strategy import ipv4
import neutronclient.v2_0.client as nclient
import murano.common.config as config import murano.common.config as config
import murano.dsl.helpers as helpers import murano.dsl.helpers as helpers
@ -36,43 +33,18 @@ class NetworkExplorer(murano_object.MuranoObject):
# noinspection PyAttributeOutsideInit # noinspection PyAttributeOutsideInit
def initialize(self, _context): def initialize(self, _context):
environment = helpers.get_environment(_context) environment = helpers.get_environment(_context)
self._clients = environment.clients
self._tenant_id = environment.tenant_id self._tenant_id = environment.tenant_id
keystone_settings = config.CONF.keystone
neutron_settings = config.CONF.neutron
self._settings = config.CONF.networking 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() self._available_cidrs = self._generate_possible_cidrs()
# noinspection PyPep8Naming # noinspection PyPep8Naming
def getDefaultRouter(self): def getDefaultRouter(self, _context):
client = self._clients.get_neutron_client(_context)
router_name = self._settings.router_name router_name = self._settings.router_name
routers = self._neutron.\ routers = client.list_routers(
list_routers(tenant_id=self._tenant_id, name=router_name).\ tenant_id=self._tenant_id, name=router_name).get('routers')
get('routers')
if len(routers) == 0: if len(routers) == 0:
LOG.debug('Router {0} not found'.format(router_name)) LOG.debug('Router {0} not found'.format(router_name))
if self._settings.create_router: if self._settings.create_router:
@ -82,8 +54,7 @@ class NetworkExplorer(murano_object.MuranoObject):
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 = self._neutron.list_networks(**kwargs). \ networks = client.list_networks(**kwargs).get('networks')
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, '
@ -99,8 +70,7 @@ class NetworkExplorer(murano_object.MuranoObject):
'admin_state_up': True, 'admin_state_up': True,
} }
} }
router = self._neutron.create_router(body=body_data).\ router = client.create_router(body=body_data).get('router')
get('router')
LOG.debug('Created router: {0}'.format(router)) LOG.debug('Created router: {0}'.format(router))
return router['id'] return router['id']
else: else:
@ -113,13 +83,13 @@ class NetworkExplorer(murano_object.MuranoObject):
return router_id return router_id
# noinspection PyPep8Naming # noinspection PyPep8Naming
def getAvailableCidr(self, routerId, netId): def getAvailableCidr(self, _context, routerId, netId):
"""Uses hash of network IDs to minimize the collisions: """Uses hash of network IDs to minimize the collisions:
different nets will attempt to pick different cidrs out of available different nets will attempt to pick different cidrs out of available
range. range.
If the cidr is taken will pick another one 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) id_hash = hash(netId)
num_fails = 0 num_fails = 0
while num_fails < len(self._available_cidrs): while num_fails < len(self._available_cidrs):
@ -137,20 +107,22 @@ class NetworkExplorer(murano_object.MuranoObject):
return self._settings.default_dns return self._settings.default_dns
# noinspection PyPep8Naming # noinspection PyPep8Naming
def getExternalNetworkIdForRouter(self, routerId): def getExternalNetworkIdForRouter(self, _context, routerId):
router = self._neutron.show_router(routerId).get('router') client = self._clients.get_neutron_client(_context)
router = client.show_router(routerId).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')
# noinspection PyPep8Naming # noinspection PyPep8Naming
def getExternalNetworkIdForNetwork(self, networkId): def getExternalNetworkIdForNetwork(self, _context, networkId):
network = self._neutron.show_network(networkId).get('network') client = self._clients.get_neutron_client(_context)
network = client.show_network(networkId).get('network')
if network.get('router:external', False): if network.get('router:external', False):
return networkId return networkId
# Get router interfaces of the network # Get router interfaces of the network
router_ports = self._neutron.list_ports( router_ports = client.list_ports(
**{'device_owner': 'network:router_interface', **{'device_owner': 'network:router_interface',
'network_id': networkId}).get('ports') 'network_id': networkId}).get('ports')
@ -158,21 +130,23 @@ class NetworkExplorer(murano_object.MuranoObject):
# check if the router has external_gateway set # check if the router has external_gateway set
for router_port in router_ports: for router_port in router_ports:
ext_net_id = self.getExternalNetworkIdForRouter( ext_net_id = self.getExternalNetworkIdForRouter(
_context,
router_port.get('device_id')) router_port.get('device_id'))
if ext_net_id: if ext_net_id:
return ext_net_id return ext_net_id
return None 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: if not router_id:
return [] 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 = [] 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 = self._neutron.list_subnets()['subnets'] all_subnets = 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]

@ -13,11 +13,13 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from heatclient.v1 import stacks
import mock import mock
from murano.tests.unit import base
from murano.dsl import murano_object from murano.dsl import murano_object
from murano.engine import client_manager
from murano.engine.system import heat_stack from murano.engine.system import heat_stack
from murano.tests.unit import base
MOD_NAME = 'murano.engine.system.heat_stack' 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 = mock.Mock(spec=murano_object.MuranoObject)
self.mock_murano_obj.name = 'TestObj' self.mock_murano_obj.name = 'TestObj'
self.mock_murano_obj.parents = [] 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') self.client_manager_mock.get_heat_client.return_value = \
def test_push_adds_version(self, mock_heat_client): self.heat_client_mock
def test_push_adds_version(self):
"""Assert that if heat_template_version is omitted, it's added.""" """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 # 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 # 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, hs = heat_stack.HeatStack(self.mock_murano_obj,
None, None, None) None, None, None)
hs._heat_client = mock_heat_client
hs._name = 'test-stack' hs._name = 'test-stack'
hs._description = 'Generated by TestHeatStack' hs._description = 'Generated by TestHeatStack'
hs._template = {'resources': {'test': 1}} hs._template = {'resources': {'test': 1}}
hs._parameters = {} hs._parameters = {}
hs._applied = False hs._applied = False
hs.push() hs._clients = self.client_manager_mock
hs.push(None)
expected_template = { expected_template = {
'heat_template_version': '2013-05-23', 'heat_template_version': '2013-05-23',
'description': 'Generated by TestHeatStack', 'description': 'Generated by TestHeatStack',
'resources': {'test': 1} 'resources': {'test': 1}
} }
mock_heat_client.stacks.create.assert_called_with( self.heat_client_mock.stacks.create.assert_called_with(
stack_name='test-stack', stack_name='test-stack',
disable_rollback=True, disable_rollback=True,
parameters={}, parameters={},
@ -64,8 +72,7 @@ class TestHeatStack(base.MuranoTestCase):
) )
self.assertTrue(hs._applied) self.assertTrue(hs._applied)
@mock.patch('heatclient.client.Client') def test_description_is_optional(self):
def test_description_is_optional(self, mock_heat_client):
"""Assert that if heat_template_version is omitted, it's added.""" """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 # 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 # 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, hs = heat_stack.HeatStack(self.mock_murano_obj,
None, None, None) None, None, None)
hs._heat_client = mock_heat_client hs._clients = self.client_manager_mock
hs._name = 'test-stack' hs._name = 'test-stack'
hs._description = None hs._description = None
hs._template = {'resources': {'test': 1}} hs._template = {'resources': {'test': 1}}
hs._parameters = {} hs._parameters = {}
hs._applied = False hs._applied = False
hs.push() hs.push(None)
expected_template = { expected_template = {
'heat_template_version': '2013-05-23', 'heat_template_version': '2013-05-23',
'resources': {'test': 1} 'resources': {'test': 1}
} }
mock_heat_client.stacks.create.assert_called_with( self.heat_client_mock.stacks.create.assert_called_with(
stack_name='test-stack', stack_name='test-stack',
disable_rollback=True, disable_rollback=True,
parameters={}, parameters={},
@ -107,7 +114,7 @@ class TestHeatStack(base.MuranoTestCase):
hs._template = {'resources': {'test': 1}} hs._template = {'resources': {'test': 1}}
hs.type.properties = {} hs.type.properties = {}
erroring_template = { invalid_template = {
'heat_template_version': 'something else' 'heat_template_version': 'something else'
} }
@ -116,17 +123,18 @@ class TestHeatStack(base.MuranoTestCase):
e = self.assertRaises(heat_stack.HeatStackError, e = self.assertRaises(heat_stack.HeatStackError,
hs.updateTemplate, hs.updateTemplate,
erroring_template) None,
invalid_template)
err_msg = "Currently only heat_template_version 2013-05-23 "\ err_msg = "Currently only heat_template_version 2013-05-23 "\
"is supported." "is supported."
self.assertEqual(err_msg, str(e)) self.assertEqual(err_msg, str(e))
# Check it's ok without a version # Check it's ok without a version
hs.updateTemplate({}) hs.updateTemplate(None, {})
expected = {'resources': {'test': 1}} expected = {'resources': {'test': 1}}
self.assertEqual(expected, hs._template) self.assertEqual(expected, hs._template)
# .. or with a good version # .. 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' expected['heat_template_version'] = '2013-05-23'
self.assertEqual(expected, hs._template) self.assertEqual(expected, hs._template)