You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
284 lines
9.9 KiB
284 lines
9.9 KiB
#
|
|
# 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 oslo_log import log as logging
|
|
import six
|
|
|
|
from heat.common import context
|
|
from heat.common import exception
|
|
from heat.common.i18n import _
|
|
from heat.common.i18n import _LE
|
|
from heat.common import template_format
|
|
from heat.engine import attributes
|
|
from heat.engine import environment
|
|
from heat.engine import function
|
|
from heat.engine import properties
|
|
from heat.engine import resource
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class RemoteStack(resource.Resource):
|
|
"""
|
|
A Resource representing a stack which can be created using specified
|
|
context.
|
|
"""
|
|
|
|
PROPERTIES = (
|
|
CONTEXT, TEMPLATE, TIMEOUT, PARAMETERS,
|
|
) = (
|
|
'context', 'template', 'timeout', 'parameters',
|
|
)
|
|
|
|
ATTRIBUTES = (
|
|
NAME_ATTR, OUTPUTS,
|
|
) = (
|
|
'stack_name', 'outputs',
|
|
)
|
|
|
|
_CONTEXT_KEYS = (
|
|
REGION_NAME
|
|
) = (
|
|
'region_name'
|
|
)
|
|
|
|
properties_schema = {
|
|
CONTEXT: properties.Schema(
|
|
properties.Schema.MAP,
|
|
_('Context for this stack.'),
|
|
schema={
|
|
REGION_NAME: properties.Schema(
|
|
properties.Schema.STRING,
|
|
_('Region name in which this stack will be created.'),
|
|
required=True,
|
|
)
|
|
}
|
|
),
|
|
TEMPLATE: properties.Schema(
|
|
properties.Schema.STRING,
|
|
_('Template that specifies the stack to be created as '
|
|
'a resource.'),
|
|
required=True,
|
|
update_allowed=True
|
|
),
|
|
TIMEOUT: properties.Schema(
|
|
properties.Schema.INTEGER,
|
|
_('Number of minutes to wait for this stack creation.'),
|
|
update_allowed=True
|
|
),
|
|
PARAMETERS: properties.Schema(
|
|
properties.Schema.MAP,
|
|
_('Set of parameters passed to this stack.'),
|
|
default={},
|
|
update_allowed=True
|
|
),
|
|
}
|
|
|
|
attributes_schema = {
|
|
NAME_ATTR: attributes.Schema(
|
|
_('Name of the stack.')
|
|
),
|
|
OUTPUTS: attributes.Schema(
|
|
_('A dict of key-value pairs output from the stack.')
|
|
),
|
|
}
|
|
|
|
def __init__(self, name, definition, stack):
|
|
super(RemoteStack, self).__init__(name, definition, stack)
|
|
self._region_name = None
|
|
self._local_context = None
|
|
|
|
def _context(self):
|
|
if self._local_context:
|
|
return self._local_context
|
|
|
|
ctx_props = self.properties.get(self.CONTEXT)
|
|
if ctx_props:
|
|
self._region_name = ctx_props[self.REGION_NAME]
|
|
else:
|
|
self._region_name = self.context.region_name
|
|
|
|
# Build RequestContext from existing one
|
|
dict_ctxt = self.context.to_dict()
|
|
dict_ctxt.update({'region_name': self._region_name})
|
|
self._local_context = context.RequestContext.from_dict(dict_ctxt)
|
|
return self._local_context
|
|
|
|
def heat(self):
|
|
# A convenience method overriding Resource.heat()
|
|
return self._context().clients.heat()
|
|
|
|
def client_plugin(self):
|
|
# A convenience method overriding Resource.client_plugin()
|
|
return self._context().clients.client_plugin('heat')
|
|
|
|
def validate(self):
|
|
super(RemoteStack, self).validate()
|
|
|
|
try:
|
|
self.heat()
|
|
except Exception as ex:
|
|
exc_info = dict(region=self._region_name, exc=six.text_type(ex))
|
|
msg = _('Cannot establish connection to Heat endpoint at region '
|
|
'"%(region)s" due to "%(exc)s"') % exc_info
|
|
raise exception.StackValidationFailed(message=msg)
|
|
|
|
try:
|
|
params = self.properties[self.PARAMETERS]
|
|
env = environment.get_child_environment(self.stack.env, params)
|
|
tmpl = template_format.parse(self.properties[self.TEMPLATE])
|
|
args = {
|
|
'template': tmpl,
|
|
'files': self.stack.t.files,
|
|
'environment': env.user_env_as_dict(),
|
|
}
|
|
self.heat().stacks.validate(**args)
|
|
except Exception as ex:
|
|
exc_info = dict(region=self._region_name, exc=six.text_type(ex))
|
|
LOG.error(_LE('exception: %s'), type(ex))
|
|
msg = _('Failed validating stack template using Heat endpoint at '
|
|
'region "%(region)s" due to "%(exc)s"') % exc_info
|
|
raise exception.StackValidationFailed(message=msg)
|
|
|
|
def handle_create(self):
|
|
params = self.properties[self.PARAMETERS]
|
|
env = environment.get_child_environment(self.stack.env, params)
|
|
tmpl = template_format.parse(self.properties[self.TEMPLATE])
|
|
args = {
|
|
'stack_name': self.physical_resource_name_or_FnGetRefId(),
|
|
'template': tmpl,
|
|
'timeout_mins': self.properties[self.TIMEOUT],
|
|
'disable_rollback': True,
|
|
'parameters': params,
|
|
'files': self.stack.t.files,
|
|
'environment': env.user_env_as_dict(),
|
|
}
|
|
remote_stack_id = self.heat().stacks.create(**args)['stack']['id']
|
|
self.resource_id_set(remote_stack_id)
|
|
|
|
def handle_delete(self):
|
|
if self.resource_id is not None:
|
|
try:
|
|
self.heat().stacks.delete(stack_id=self.resource_id)
|
|
except Exception as e:
|
|
self.client_plugin().ignore_not_found(e)
|
|
|
|
def handle_resume(self):
|
|
if self.resource_id is None:
|
|
raise exception.Error(_('Cannot resume %s, resource not found')
|
|
% self.name)
|
|
self.heat().actions.resume(stack_id=self.resource_id)
|
|
|
|
def handle_suspend(self):
|
|
if self.resource_id is None:
|
|
raise exception.Error(_('Cannot suspend %s, resource not found')
|
|
% self.name)
|
|
self.heat().actions.suspend(stack_id=self.resource_id)
|
|
|
|
def _needs_update(self, after, before, after_props, before_props,
|
|
prev_resource):
|
|
# Always issue an update to the remote stack and let the individual
|
|
# resources in it decide if they need updating.
|
|
return True
|
|
|
|
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
|
|
# Always issue an update to the remote stack and let the individual
|
|
# resources in it decide if they need updating.
|
|
if self.resource_id:
|
|
snippet = json_snippet.get('Properties', {})
|
|
self.properties = properties.Properties(self.properties_schema,
|
|
snippet,
|
|
function.resolve,
|
|
self.name)
|
|
|
|
params = self.properties[self.PARAMETERS]
|
|
env = environment.get_child_environment(self.stack.env, params)
|
|
tmpl = template_format.parse(self.properties[self.TEMPLATE])
|
|
fields = {
|
|
'stack_id': self.resource_id,
|
|
'parameters': params,
|
|
'template': tmpl,
|
|
'timeout_mins': self.properties[self.TIMEOUT],
|
|
'disable_rollback': self.stack.disable_rollback,
|
|
'files': self.stack.t.files,
|
|
'environment': env.user_env_as_dict(),
|
|
}
|
|
self.heat().stacks.update(**fields)
|
|
|
|
def _check_action_complete(self, action):
|
|
stack = self.heat().stacks.get(stack_id=self.resource_id)
|
|
if stack.action != action:
|
|
return False
|
|
|
|
if stack.status == self.IN_PROGRESS:
|
|
return False
|
|
elif stack.status == self.COMPLETE:
|
|
return True
|
|
elif stack.status == self.FAILED:
|
|
raise resource.ResourceInError(
|
|
resource_status=stack.stack_status,
|
|
status_reason=stack.stack_status_reason)
|
|
else:
|
|
# Note: this should never happen, so it really means that
|
|
# the resource/engine is in serious problem if it happens.
|
|
raise resource.ResourceUnknownStatus(
|
|
resource_status=stack.stack_status,
|
|
status_reason=stack.stack_status_reason)
|
|
|
|
def check_create_complete(self, *args):
|
|
return self._check_action_complete(action=self.CREATE)
|
|
|
|
def check_delete_complete(self, *args):
|
|
if self.resource_id is None:
|
|
return True
|
|
|
|
try:
|
|
return self._check_action_complete(action=self.DELETE)
|
|
except Exception as ex:
|
|
self.client_plugin().ignore_not_found(ex)
|
|
return True
|
|
|
|
def check_resume_complete(self, *args):
|
|
return self._check_action_complete(action=self.RESUME)
|
|
|
|
def check_suspend_complete(self, *args):
|
|
return self._check_action_complete(action=self.SUSPEND)
|
|
|
|
def check_update_complete(self, *args):
|
|
return self._check_action_complete(action=self.UPDATE)
|
|
|
|
def _resolve_attribute(self, name):
|
|
try:
|
|
stack = self.heat().stacks.get(stack_id=self.resource_id)
|
|
except Exception as e:
|
|
self.client_plugin().ignore_not_found(e)
|
|
return None
|
|
|
|
if name == self.NAME_ATTR:
|
|
value = getattr(stack, name, None)
|
|
return value or self.physical_resource_name_or_FnGetRefId()
|
|
|
|
if name == self.OUTPUTS:
|
|
outputs = stack.outputs
|
|
return dict((output['output_key'], output['output_value'])
|
|
for output in outputs)
|
|
|
|
def FnGetRefId(self):
|
|
return self.resource_id
|
|
|
|
|
|
def resource_mapping():
|
|
return {
|
|
'OS::Heat::Stack': RemoteStack,
|
|
}
|