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

View File

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

View File

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

View File

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

101
murano/engine/auth_utils.py Normal file
View 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
}
}

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

View File

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

View File

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

View File

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

View File

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

View File

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