bb2d0e5a84
Now all OpenStack resource classes inherit CloudResource that provides getRegion method and regionName property. This allows to assign resources to different regions. getRegion() returns CloudRegion instance that resource or it parent belong to. CloudRegion has the similar interface to Environment class and is the correct way to get HeatStack instance associated with the regoin, default network configuration, security group manager and agent listener instances. Environment acts as the default region so backward compatibility is not broken. However new applications should not use environment to set security group rules but rather a region(s) of their instance(s) in order to work correctly when their instances were configured to use region other then the default. Change-Id: I4dbf40c65042e9a354f3bfadfcd63a63e6e3e418
281 lines
9.7 KiB
Python
281 lines
9.7 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 json
|
|
|
|
import eventlet
|
|
import heatclient.client as hclient
|
|
import heatclient.exc as heat_exc
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
import six
|
|
|
|
from murano.common import auth_utils
|
|
from murano.common.i18n import _LW
|
|
from murano.dsl import dsl
|
|
from murano.dsl import helpers
|
|
from murano.dsl import session_local_storage
|
|
|
|
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, this, name, description=None):
|
|
self._name = name
|
|
self._template = None
|
|
self._parameters = {}
|
|
self._files = {}
|
|
self._hot_environment = ''
|
|
self._applied = True
|
|
self._description = description
|
|
self._last_stack_timestamps = (None, None)
|
|
self._tags = ''
|
|
self._owner = this.find_owner('io.murano.CloudRegion')
|
|
|
|
@staticmethod
|
|
def _create_client(session, region_name):
|
|
parameters = auth_utils.get_session_client_parameters(
|
|
service_type='orchestration', region=region_name,
|
|
conf=CONF.heat, session=session)
|
|
return hclient.Client('1', **parameters)
|
|
|
|
@property
|
|
def _client(self):
|
|
region = None if self._owner is None else self._owner['name']
|
|
return self._get_client(region)
|
|
|
|
@staticmethod
|
|
@session_local_storage.execution_session_memoize
|
|
def _get_client(region_name):
|
|
session = auth_utils.get_client_session(conf=CONF.heat)
|
|
return HeatStack._create_client(session, region_name)
|
|
|
|
def _get_token_client(self):
|
|
ks_session = auth_utils.get_token_client_session(conf=CONF.heat)
|
|
region = None if self._owner is None else self._owner['name']
|
|
return self._create_client(ks_session, region)
|
|
|
|
def current(self):
|
|
if self._template is not None:
|
|
return self._template
|
|
try:
|
|
stack_info = self._client.stacks.get(stack_id=self._name)
|
|
template = self._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)
|
|
current = self.current()
|
|
self._template = helpers.merge_dicts(self._template, template)
|
|
self._applied = self._template == current and self._applied
|
|
|
|
@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:
|
|
try:
|
|
stack_info = self._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=json.dumps(template)))
|
|
|
|
while True:
|
|
try:
|
|
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._get_token_client()
|
|
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:
|
|
self._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()
|
|
except heat_exc.HTTPConflict as e:
|
|
LOG.warning(_LW('Conflicting operation: {msg}').format(msg=e))
|
|
eventlet.sleep(3)
|
|
else:
|
|
break
|
|
|
|
self._applied = self._template == template
|
|
|
|
def delete(self):
|
|
while True:
|
|
try:
|
|
if not self.current():
|
|
return
|
|
self._wait_state(lambda s: True)
|
|
self._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))
|
|
break
|
|
except heat_exc.HTTPConflict as e:
|
|
LOG.warning(_LW('Conflicting operation: {msg}').format(msg=e))
|
|
eventlet.sleep(3)
|
|
else:
|
|
break
|
|
|
|
self._template = {}
|
|
self._applied = True
|