summaryrefslogtreecommitdiffstats
path: root/tosca2heat/heat-translator/translator/hot/translate_node_templates.py
diff options
context:
space:
mode:
Diffstat (limited to 'tosca2heat/heat-translator/translator/hot/translate_node_templates.py')
-rw-r--r--tosca2heat/heat-translator/translator/hot/translate_node_templates.py400
1 files changed, 321 insertions, 79 deletions
diff --git a/tosca2heat/heat-translator/translator/hot/translate_node_templates.py b/tosca2heat/heat-translator/translator/hot/translate_node_templates.py
index 0aefd48..1a1a4d7 100644
--- a/tosca2heat/heat-translator/translator/hot/translate_node_templates.py
+++ b/tosca2heat/heat-translator/translator/hot/translate_node_templates.py
@@ -11,13 +11,17 @@
# License for the specific language governing permissions and limitations
# under the License.
+import copy
import importlib
import logging
import os
import six
+from collections import OrderedDict
+from toscaparser.functions import Concat
from toscaparser.functions import GetAttribute
from toscaparser.functions import GetInput
+from toscaparser.functions import GetOperationOutput
from toscaparser.functions import GetProperty
from toscaparser.properties import Property
from toscaparser.relationship_template import RelationshipTemplate
@@ -25,6 +29,8 @@ from toscaparser.utils.gettextutils import _
from translator.common.exception import ToscaClassAttributeError
from translator.common.exception import ToscaClassImportError
from translator.common.exception import ToscaModImportError
+from translator.common.exception import UnsupportedTypeError
+from translator.common import utils
from translator.conf.config import ConfigProvider as translatorConfig
from translator.hot.syntax.hot_resource import HotResource
from translator.hot.tosca.tosca_block_storage_attachment import (
@@ -132,20 +138,31 @@ log = logging.getLogger('heat-translator')
TOSCA_TO_HOT_TYPE = _generate_type_map()
+BASE_TYPES = six.string_types + six.integer_types + (dict, OrderedDict)
+
+HOT_SCALING_POLICY_TYPE = ["OS::Heat::AutoScalingGroup",
+ "OS::Senlin::Profile"]
+
class TranslateNodeTemplates(object):
'''Translate TOSCA NodeTemplates to Heat Resources.'''
- def __init__(self, tosca, hot_template):
+ def __init__(self, tosca, hot_template, csar_dir=None):
self.tosca = tosca
self.nodetemplates = self.tosca.nodetemplates
self.hot_template = hot_template
+ self.csar_dir = csar_dir
# list of all HOT resources generated
self.hot_resources = []
# mapping between TOSCA nodetemplate and HOT resource
log.debug(_('Mapping between TOSCA nodetemplate and HOT resource.'))
self.hot_lookup = {}
self.policies = self.tosca.topology_template.policies
+ # stores the last deploy of generated behavior for a resource
+ # useful to satisfy underlying dependencies between interfaces
+ self.last_deploy_map = {}
+ self.hot_template_version = None
+ self.processed_policy_res = []
def translate(self):
return self._translate_nodetemplates()
@@ -161,23 +178,56 @@ class TranslateNodeTemplates(object):
if resource.type == "OS::Nova::ServerGroup":
resource.handle_properties(self.hot_resources)
+ elif resource.type in ("OS::Heat::ScalingPolicy",
+ "OS::Senlin::Policy"):
+ if resource.name in self.processed_policy_res:
+ return
+ self.processed_policy_res.append(resource.name)
+ self.hot_resources = \
+ resource.handle_properties(self.hot_resources)
+ extra_hot_resources = []
+ for res in self.hot_resources:
+ if res.type == 'OS::Heat::ScalingPolicy':
+ extra_res = copy.deepcopy(res)
+ scaling_adjustment = res.properties['scaling_adjustment']
+ if scaling_adjustment < 0:
+ res.name = res.name + '_scale_in'
+ extra_res.name = extra_res.name + '_scale_out'
+ extra_res.properties['scaling_adjustment'] = \
+ -1 * scaling_adjustment
+ extra_hot_resources.append(extra_res)
+ self.processed_policy_res.append(res.name)
+ self.processed_policy_res.append(extra_res.name)
+ elif scaling_adjustment > 0:
+ res.name = res.name + '_scale_out'
+ extra_res.name = extra_res.name + '_scale_in'
+ extra_res.properties['scaling_adjustment'] = \
+ -1 * scaling_adjustment
+ extra_hot_resources.append(extra_res)
+ self.processed_policy_res.append(res.name)
+ self.processed_policy_res.append(extra_res.name)
+ else:
+ continue
+ self.hot_resources += extra_hot_resources
else:
resource.handle_properties()
def _translate_nodetemplates(self):
-
log.debug(_('Translating the node templates.'))
suffix = 0
# Copy the TOSCA graph: nodetemplate
for node in self.nodetemplates:
- base_type = HotResource.get_base_type(node.type_definition)
- hot_node = TOSCA_TO_HOT_TYPE[base_type.type](node)
+ base_type = HotResource.get_base_type_str(node.type_definition)
+ if base_type not in TOSCA_TO_HOT_TYPE:
+ raise UnsupportedTypeError(type=_('%s') % base_type)
+ hot_node = TOSCA_TO_HOT_TYPE[base_type](node,
+ csar_dir=self.csar_dir)
self.hot_resources.append(hot_node)
self.hot_lookup[node] = hot_node
# BlockStorage Attachment is a special case,
# which doesn't match to Heat Resources 1 to 1.
- if base_type.type == "tosca.nodes.Compute":
+ if base_type == "tosca.nodes.Compute":
volume_name = None
requirements = node.requirements
if requirements:
@@ -192,7 +242,7 @@ class TranslateNodeTemplates(object):
"tosca.nodes.BlockStorage"):
volume_name = node_name
break
- else: # unreachable code !
+ else:
for n in self.nodetemplates:
if n.name == value and \
n.is_derived_from(
@@ -201,9 +251,8 @@ class TranslateNodeTemplates(object):
break
suffix = suffix + 1
- attachment_node = self._get_attachment_node(node,
- suffix,
- volume_name)
+ attachment_node = self._get_attachment_node(
+ node, suffix, volume_name)
if attachment_node:
self.hot_resources.append(attachment_node)
for i in self.tosca.inputs:
@@ -216,6 +265,15 @@ class TranslateNodeTemplates(object):
for policy in self.policies:
policy_type = policy.type_definition
+ if policy.is_derived_from('tosca.policies.Scaling') and \
+ policy_type.type != 'tosca.policies.Scaling.Cluster':
+ TOSCA_TO_HOT_TYPE[policy_type.type] = \
+ TOSCA_TO_HOT_TYPE['tosca.policies.Scaling']
+ if not policy.is_derived_from('tosca.policies.Scaling') and \
+ policy_type.type not in TOSCA_TO_HOT_TYPE:
+ raise UnsupportedTypeError(type=_('%s') % policy_type.type)
+ elif policy_type.type == 'tosca.policies.Scaling.Cluster':
+ self.hot_template_version = '2016-04-08'
policy_node = TOSCA_TO_HOT_TYPE[policy_type.type](policy)
self.hot_resources.append(policy_node)
@@ -223,9 +281,14 @@ class TranslateNodeTemplates(object):
# into multiple HOT resources and may change their name
lifecycle_resources = []
for resource in self.hot_resources:
- expanded = resource.handle_life_cycle()
- if expanded:
- lifecycle_resources += expanded
+ expanded_resources, deploy_lookup, last_deploy = resource.\
+ handle_life_cycle()
+ if expanded_resources:
+ lifecycle_resources += expanded_resources
+ if deploy_lookup:
+ self.hot_lookup.update(deploy_lookup)
+ if last_deploy:
+ self.last_deploy_map[resource] = last_deploy
self.hot_resources += lifecycle_resources
# Handle configuration from ConnectsTo relationship in the TOSCA node:
@@ -234,7 +297,7 @@ class TranslateNodeTemplates(object):
connectsto_resources = []
for node in self.nodetemplates:
for requirement in node.requirements:
- for endpoint, details in six.iteritems(requirement):
+ for endpoint, details in requirement.items():
relation = None
if isinstance(details, dict):
target = details.get('node')
@@ -257,7 +320,9 @@ class TranslateNodeTemplates(object):
# if the source of dependency is a server and the
# relationship type is 'tosca.relationships.HostedOn',
# add dependency as properties.server
- if node_depend.type == 'tosca.nodes.Compute' and \
+ base_type = HotResource.get_base_type_str(
+ node_depend.type_definition)
+ if base_type == 'tosca.nodes.Compute' and \
node.related[node_depend].type == \
node.type_definition.HOSTEDON:
self.hot_lookup[node].properties['server'] = \
@@ -270,6 +335,13 @@ class TranslateNodeTemplates(object):
self.hot_lookup[node].depends_on_nodes.append(
self.hot_lookup[node_depend].top_of_chain())
+ last_deploy = self.last_deploy_map.get(
+ self.hot_lookup[node_depend])
+ if last_deploy and \
+ last_deploy not in self.hot_lookup[node].depends_on:
+ self.hot_lookup[node].depends_on.append(last_deploy)
+ self.hot_lookup[node].depends_on_nodes.append(last_deploy)
+
# handle hosting relationship
for resource in self.hot_resources:
resource.handle_hosting()
@@ -281,7 +353,8 @@ class TranslateNodeTemplates(object):
# dependent nodes in correct order
self.processed_resources = []
for resource in self.hot_resources:
- self._recursive_handle_properties(resource)
+ if resource.type not in HOT_SCALING_POLICY_TYPE:
+ self._recursive_handle_properties(resource)
# handle resources that need to expand to more than one HOT resource
expansion_resources = []
@@ -298,66 +371,215 @@ class TranslateNodeTemplates(object):
# traverse the reference chain to get the actual value
inputs = resource.properties.get('input_values')
if inputs:
- for name, value in six.iteritems(inputs):
- inputs[name] = self._translate_input(value, resource)
+ for name, value in inputs.items():
+ inputs[name] = self.translate_param_value(value, resource)
+
+ # remove resources without type defined
+ # for example a SoftwareComponent without interfaces
+ # would fall in this case
+ to_remove = []
+ for resource in self.hot_resources:
+ if resource.type is None:
+ to_remove.append(resource)
+
+ for resource in to_remove:
+ self.hot_resources.remove(resource)
return self.hot_resources
- def _translate_input(self, input_value, resource):
+ def translate_param_value(self, param_value, resource):
+ tosca_template = None
+ if resource:
+ tosca_template = resource.nodetemplate
+
get_property_args = None
- if isinstance(input_value, GetProperty):
- get_property_args = input_value.args
+ if isinstance(param_value, GetProperty):
+ get_property_args = param_value.args
# to remove when the parser is fixed to return GetProperty
- if isinstance(input_value, dict) and 'get_property' in input_value:
- get_property_args = input_value['get_property']
+ elif isinstance(param_value, dict) and 'get_property' in param_value:
+ get_property_args = param_value['get_property']
if get_property_args is not None:
- hot_target = self._find_hot_resource_for_tosca(
- get_property_args[0], resource)
- if hot_target:
- props = hot_target.get_tosca_props()
- prop_name = get_property_args[1]
- if prop_name in props:
- return props[prop_name]
- elif isinstance(input_value, GetAttribute):
+ tosca_target, prop_name, prop_arg = \
+ self.decipher_get_operation(get_property_args,
+ tosca_template)
+ if tosca_target:
+ prop_value = tosca_target.get_property_value(prop_name)
+ if prop_value:
+ prop_value = self.translate_param_value(
+ prop_value, resource)
+ return self._unfold_value(prop_value, prop_arg)
+ get_attr_args = None
+ if isinstance(param_value, GetAttribute):
+ get_attr_args = param_value.result().args
+ # to remove when the parser is fixed to return GetAttribute
+ elif isinstance(param_value, dict) and 'get_attribute' in param_value:
+ get_attr_args = param_value['get_attribute']
+ if get_attr_args is not None:
# for the attribute
# get the proper target type to perform the translation
- args = input_value.result().args
- hot_target = self._find_hot_resource_for_tosca(args[0], resource)
-
- return hot_target.get_hot_attribute(args[1], args)
- # most of artifacts logic should move to the parser
- elif isinstance(input_value, dict) and 'get_artifact' in input_value:
- get_artifact_args = input_value['get_artifact']
-
- hot_target = self._find_hot_resource_for_tosca(
- get_artifact_args[0], resource)
- artifacts = TranslateNodeTemplates.get_all_artifacts(
- hot_target.nodetemplate)
-
- if get_artifact_args[1] in artifacts:
- artifact = artifacts[get_artifact_args[1]]
- if artifact.get('type', None) == 'tosca.artifacts.File':
- return {'get_file': artifact.get('file')}
- elif isinstance(input_value, GetInput):
- if isinstance(input_value.args, list) \
- and len(input_value.args) == 1:
- return {'get_param': input_value.args[0]}
+ tosca_target, attr_name, attr_arg = \
+ self.decipher_get_operation(get_attr_args, tosca_template)
+ attr_args = []
+ if attr_arg:
+ attr_args += attr_arg
+ if tosca_target:
+ if tosca_target in self.hot_lookup:
+ attr_value = self.hot_lookup[tosca_target].\
+ get_hot_attribute(attr_name, attr_args)
+ attr_value = self.translate_param_value(
+ attr_value, resource)
+ return self._unfold_value(attr_value, attr_arg)
+ elif isinstance(param_value, dict) and 'get_artifact' in param_value:
+ get_artifact_args = param_value['get_artifact']
+ tosca_target, artifact_name, _ = \
+ self.decipher_get_operation(get_artifact_args,
+ tosca_template)
+
+ if tosca_target:
+ artifacts = HotResource.get_all_artifacts(tosca_target)
+ if artifact_name in artifacts:
+ cwd = os.getcwd()
+ artifact = artifacts[artifact_name]
+ if self.csar_dir:
+ os.chdir(self.csar_dir)
+ get_file = os.path.abspath(artifact.get('file'))
+ else:
+ get_file = artifact.get('file')
+ if artifact.get('type', None) == 'tosca.artifacts.File':
+ return {'get_file': get_file}
+ os.chdir(cwd)
+ get_input_args = None
+ if isinstance(param_value, GetInput):
+ get_input_args = param_value.args
+ elif isinstance(param_value, dict) and 'get_input' in param_value:
+ get_input_args = param_value['get_input']
+ if get_input_args is not None:
+ if isinstance(get_input_args, list) \
+ and len(get_input_args) == 1:
+ return {'get_param': self.translate_param_value(
+ get_input_args[0], resource)}
else:
- return {'get_param': input_value.args}
+ return {'get_param': self.translate_param_value(
+ get_input_args, resource)}
+ elif isinstance(param_value, GetOperationOutput):
+ res = self._translate_get_operation_output_function(
+ param_value.args, tosca_template)
+ if res:
+ return res
+ elif isinstance(param_value, dict) \
+ and 'get_operation_output' in param_value:
+ res = self._translate_get_operation_output_function(
+ param_value['get_operation_output'], tosca_template)
+ if res:
+ return res
+ concat_list = None
+ if isinstance(param_value, Concat):
+ concat_list = param_value.args
+ elif isinstance(param_value, dict) and 'concat' in param_value:
+ concat_list = param_value['concat']
+ if concat_list is not None:
+ res = self._translate_concat_function(concat_list, resource)
+ if res:
+ return res
+
+ if isinstance(param_value, list):
+ translated_list = []
+ for elem in param_value:
+ translated_elem = self.translate_param_value(elem, resource)
+ if translated_elem:
+ translated_list.append(translated_elem)
+ return translated_list
+
+ if isinstance(param_value, BASE_TYPES):
+ return param_value
+
+ return None
- return input_value
+ def _translate_concat_function(self, concat_list, resource):
+ str_replace_template = ''
+ str_replace_params = {}
+ index = 0
+ for elem in concat_list:
+ str_replace_template += '$s' + str(index)
+ str_replace_params['$s' + str(index)] = \
+ self.translate_param_value(elem, resource)
+ index += 1
+
+ return {'str_replace': {
+ 'template': str_replace_template,
+ 'params': str_replace_params
+ }}
+
+ def _translate_get_operation_output_function(self, args, tosca_template):
+ tosca_target = self._find_tosca_node(args[0],
+ tosca_template)
+ if tosca_target and len(args) >= 4:
+ operations = HotResource.get_all_operations(tosca_target)
+ # ignore Standard interface name,
+ # it is the only one supported in the translator anyway
+ op_name = args[2]
+ output_name = args[3]
+ if op_name in operations:
+ operation = operations[op_name]
+ if operation in self.hot_lookup:
+ matching_deploy = self.hot_lookup[operation]
+ matching_config_name = matching_deploy.\
+ properties['config']['get_resource']
+ matching_config = self.find_hot_resource(
+ matching_config_name)
+ if matching_config:
+ outputs = matching_config.properties.get('outputs')
+ if outputs is None:
+ outputs = []
+ outputs.append({'name': output_name})
+ matching_config.properties['outputs'] = outputs
+ return {'get_attr': [
+ matching_deploy.name,
+ output_name
+ ]}
@staticmethod
- def get_all_artifacts(nodetemplate):
- artifacts = nodetemplate.type_definition.get_value('artifacts',
- parent=True)
- if not artifacts:
- artifacts = {}
- tpl_artifacts = nodetemplate.entity_tpl.get('artifacts')
- if tpl_artifacts:
- artifacts.update(tpl_artifacts)
+ def _unfold_value(value, value_arg):
+ if value_arg is not None:
+ if isinstance(value, dict):
+ val = value.get(value_arg)
+ if val is not None:
+ return val
+
+ index = utils.str_to_num(value_arg)
+ if isinstance(value, list) and index is not None:
+ return value[index]
+ return value
+
+ def decipher_get_operation(self, args, current_tosca_node):
+ tosca_target = self._find_tosca_node(args[0],
+ current_tosca_node)
+ new_target = None
+ if tosca_target and len(args) > 2:
+ cap_or_req_name = args[1]
+ cap = tosca_target.get_capability(cap_or_req_name)
+ if cap:
+ new_target = cap
+ else:
+ for req in tosca_target.requirements:
+ if cap_or_req_name in req:
+ new_target = self._find_tosca_node(
+ req[cap_or_req_name])
+ cap = new_target.get_capability(cap_or_req_name)
+ if cap:
+ new_target = cap
+ break
+
+ if new_target:
+ tosca_target = new_target
+
+ prop_name = args[2]
+ prop_arg = args[3] if len(args) >= 4 else None
+ else:
+ prop_name = args[1]
+ prop_arg = args[2] if len(args) >= 3 else None
- return artifacts
+ return tosca_target, prop_name, prop_arg
def _get_attachment_node(self, node, suffix, volume_name):
attach = False
@@ -420,23 +642,29 @@ class TranslateNodeTemplates(object):
if resource.name == name:
return resource
- def _find_tosca_node(self, tosca_name):
- for node in self.nodetemplates:
- if node.name == tosca_name:
- return node
-
- def _find_hot_resource_for_tosca(self, tosca_name,
- current_hot_resource=None):
+ def _find_tosca_node(self, tosca_name, current_tosca_template=None):
+ tosca_node = None
if tosca_name == 'SELF':
- return current_hot_resource
- if tosca_name == 'HOST' and current_hot_resource is not None:
- for req in current_hot_resource.nodetemplate.requirements:
+ tosca_node = current_tosca_template
+ if tosca_name == 'HOST' and current_tosca_template:
+ for req in current_tosca_template.requirements:
if 'host' in req:
- return self._find_hot_resource_for_tosca(req['host'])
+ tosca_node = self._find_tosca_node(req['host'])
- for node in self.nodetemplates:
- if node.name == tosca_name:
- return self.hot_lookup[node]
+ if tosca_node is None:
+ for node in self.nodetemplates:
+ if node.name == tosca_name:
+ tosca_node = node
+ break
+ return tosca_node
+
+ def _find_hot_resource_for_tosca(self, tosca_name,
+ current_hot_resource=None):
+ current_tosca_resource = current_hot_resource.nodetemplate \
+ if current_hot_resource else None
+ tosca_node = self._find_tosca_node(tosca_name, current_tosca_resource)
+ if tosca_node:
+ return self.hot_lookup[tosca_node]
return None
@@ -444,7 +672,7 @@ class TranslateNodeTemplates(object):
connect_interfaces):
connectsto_resources = []
if connect_interfaces:
- for iname, interface in six.iteritems(connect_interfaces):
+ for iname, interface in connect_interfaces.items():
connectsto_resources += \
self._create_connect_config(source_node, target_name,
interface)
@@ -470,16 +698,30 @@ class TranslateNodeTemplates(object):
raise Exception(msg)
config_name = source_node.name + '_' + target_name + '_connect_config'
implement = connect_config.get('implementation')
+ cwd = os.getcwd()
if config_location == 'target':
+ if self.csar_dir:
+ os.chdir(self.csar_dir)
+ get_file = os.path.abspath(implement)
+ else:
+ get_file = implement
hot_config = HotResource(target_node,
config_name,
'OS::Heat::SoftwareConfig',
- {'config': {'get_file': implement}})
+ {'config': {'get_file': get_file}},
+ csar_dir=self.csar_dir)
elif config_location == 'source':
+ if self.csar_dir:
+ os.chdir(self.csar_dir)
+ get_file = os.path.abspath(implement)
+ else:
+ get_file = implement
hot_config = HotResource(source_node,
config_name,
'OS::Heat::SoftwareConfig',
- {'config': {'get_file': implement}})
+ {'config': {'get_file': get_file}},
+ csar_dir=self.csar_dir)
+ os.chdir(cwd)
connectsto_resources.append(hot_config)
hot_target = self._find_hot_resource_for_tosca(target_name)
hot_source = self._find_hot_resource_for_tosca(source_node.name)