Merge "Use Keystone trusts to get fresh token"
This commit is contained in:
commit
af2a623c2b
@ -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:
|
||||
|
@ -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?
|
||||
|
@ -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
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):
|
||||
self.token = None
|
||||
self.tenant_id = None
|
||||
self.trust_id = None
|
||||
self.system_attributes = {}
|
||||
self.clients = None
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user