Merge "Use Keystone trusts to get fresh token"
This commit is contained in:
commit
af2a623c2b
meta/io.murano/Classes
murano
@ -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
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
|
||||||
|
}
|
||||||
|
}
|
142
murano/engine/client_manager.py
Normal file
142
murano/engine/client_manager.py
Normal 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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user