859888d62a
This also adds a check to murano/hacking/checks.py that should catch this error in the future. Blueprint murano-python-3-support Change-Id: Ib8b2b91a40577d18739f0c1bf2c37b7cf4060560
242 lines
8.1 KiB
Python
242 lines
8.1 KiB
Python
# Copyright (c) 2013 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.
|
|
|
|
import copy
|
|
|
|
import eventlet
|
|
import heatclient.exc as heat_exc
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
import six
|
|
|
|
from murano.common.i18n import _LW
|
|
from murano.common import utils
|
|
from murano.dsl import dsl
|
|
from murano.dsl import helpers
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
CONF = cfg.CONF
|
|
|
|
HEAT_TEMPLATE_VERSION = '2013-05-23'
|
|
|
|
|
|
class HeatStackError(Exception):
|
|
pass
|
|
|
|
|
|
@dsl.name('io.murano.system.HeatStack')
|
|
class HeatStack(object):
|
|
def __init__(self, name, description=None):
|
|
self._name = name
|
|
self._template = None
|
|
self._parameters = {}
|
|
self._files = {}
|
|
self._hot_environment = ''
|
|
self._applied = True
|
|
self._description = description
|
|
self._clients = helpers.get_environment().clients
|
|
self._last_stack_timestamps = (None, None)
|
|
self._tags = ''
|
|
|
|
def current(self):
|
|
client = self._clients.get_heat_client()
|
|
if self._template is not None:
|
|
return self._template
|
|
try:
|
|
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))
|
|
self._template = template
|
|
self._parameters.update(
|
|
HeatStack._remove_system_params(stack_info.parameters))
|
|
self._applied = True
|
|
return self._template.copy()
|
|
except heat_exc.HTTPNotFound:
|
|
self._applied = True
|
|
self._template = {}
|
|
self._parameters.clear()
|
|
return {}
|
|
|
|
def parameters(self):
|
|
self.current()
|
|
return self._parameters.copy()
|
|
|
|
def reload(self):
|
|
self._template = None
|
|
self._parameters.clear()
|
|
return self.current()
|
|
|
|
def set_template(self, template):
|
|
self._template = template
|
|
self._parameters.clear()
|
|
self._applied = False
|
|
|
|
def set_parameters(self, parameters):
|
|
self._parameters = parameters
|
|
self._applied = False
|
|
|
|
def set_files(self, files):
|
|
self._files = files
|
|
self._applied = False
|
|
|
|
def set_hot_environment(self, hot_environment):
|
|
self._hot_environment = hot_environment
|
|
self._applied = False
|
|
|
|
def update_template(self, 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._template = helpers.merge_dicts(self._template, template)
|
|
self._applied = False
|
|
|
|
@staticmethod
|
|
def _remove_system_params(parameters):
|
|
return dict((k, v) for k, v in six.iteritems(parameters) if
|
|
not k.startswith('OS::'))
|
|
|
|
def _get_status(self):
|
|
status = [None]
|
|
|
|
def status_func(state_value):
|
|
status[0] = state_value
|
|
return True
|
|
|
|
self._wait_state(status_func)
|
|
return status[0]
|
|
|
|
def _wait_state(self, status_func, wait_progress=False):
|
|
tries = 4
|
|
delay = 1
|
|
while tries > 0:
|
|
while True:
|
|
client = self._clients.get_heat_client()
|
|
try:
|
|
stack_info = client.stacks.get(
|
|
stack_id=self._name)
|
|
status = stack_info.stack_status
|
|
tries = 4
|
|
delay = 1
|
|
except heat_exc.HTTPNotFound:
|
|
stack_info = None
|
|
status = 'NOT_FOUND'
|
|
except Exception:
|
|
tries -= 1
|
|
delay *= 2
|
|
if not tries:
|
|
raise
|
|
eventlet.sleep(delay)
|
|
break
|
|
|
|
if 'IN_PROGRESS' in status:
|
|
eventlet.sleep(2)
|
|
continue
|
|
|
|
last_stack_timestamps = self._last_stack_timestamps
|
|
self._last_stack_timestamps = (None, None) if not stack_info \
|
|
else(stack_info.creation_time, stack_info.updated_time)
|
|
|
|
if (wait_progress and last_stack_timestamps ==
|
|
self._last_stack_timestamps and
|
|
last_stack_timestamps != (None, None)):
|
|
eventlet.sleep(2)
|
|
continue
|
|
|
|
if not status_func(status):
|
|
reason = ': {0}'.format(
|
|
stack_info.stack_status_reason) if stack_info else ''
|
|
raise EnvironmentError(
|
|
"Unexpected stack state {0}{1}".format(status, reason))
|
|
|
|
try:
|
|
return dict([(t['output_key'], t['output_value'])
|
|
for t in stack_info.outputs])
|
|
except Exception:
|
|
return {}
|
|
return {}
|
|
|
|
def output(self):
|
|
return self._wait_state(lambda status: True)
|
|
|
|
def push(self):
|
|
if self._applied or self._template is None:
|
|
return
|
|
|
|
self._tags = ','.join(CONF.heat.stack_tags)
|
|
if 'heat_template_version' not in self._template:
|
|
self._template['heat_template_version'] = HEAT_TEMPLATE_VERSION
|
|
|
|
if 'description' not in self._template and self._description:
|
|
self._template['description'] = self._description
|
|
|
|
template = copy.deepcopy(self._template)
|
|
LOG.debug('Pushing: {template}'.format(template=template))
|
|
|
|
current_status = self._get_status()
|
|
resources = template.get('Resources') or template.get('resources')
|
|
if current_status == 'NOT_FOUND':
|
|
if resources is not None:
|
|
token_client = self._clients.get_heat_client(use_trusts=False)
|
|
token_client.stacks.create(
|
|
stack_name=self._name,
|
|
parameters=self._parameters,
|
|
template=template,
|
|
files=self._files,
|
|
environment=self._hot_environment,
|
|
disable_rollback=True,
|
|
tags=self._tags)
|
|
|
|
self._wait_state(lambda status: status == 'CREATE_COMPLETE')
|
|
else:
|
|
if resources is not None:
|
|
trust_client = self._clients.get_heat_client()
|
|
|
|
trust_client.stacks.update(
|
|
stack_id=self._name,
|
|
parameters=self._parameters,
|
|
files=self._files,
|
|
environment=self._hot_environment,
|
|
template=template,
|
|
disable_rollback=True,
|
|
tags=self._tags)
|
|
self._wait_state(
|
|
lambda status: status == 'UPDATE_COMPLETE', True)
|
|
else:
|
|
self.delete()
|
|
|
|
self._applied = not utils.is_different(self._template, template)
|
|
|
|
def delete(self):
|
|
client = self._clients.get_heat_client()
|
|
try:
|
|
if not self.current():
|
|
return
|
|
client.stacks.delete(stack_id=self._name)
|
|
self._wait_state(
|
|
lambda status: status in ('DELETE_COMPLETE', 'NOT_FOUND'),
|
|
wait_progress=True)
|
|
except heat_exc.NotFound:
|
|
LOG.warning(_LW('Stack {stack_name} already deleted?')
|
|
.format(stack_name=self._name))
|
|
|
|
self._template = {}
|
|
self._applied = True
|