From f7b240c6893a48d71da29974e54cb560b6575eb3 Mon Sep 17 00:00:00 2001 From: shangxdy Date: Sun, 26 Feb 2017 16:22:30 +0800 Subject: Sync heat-translator code Sync heat-translator code from upstream JIRA:PARSER-119 Change-Id: I2287b78ad38bc54f7740fd1ee5f08989d5e680bf Signed-off-by: shangxdy --- .../translator/hot/translate_node_templates.py | 400 +++++++++++++++++---- 1 file changed, 321 insertions(+), 79 deletions(-) (limited to 'tosca2heat/heat-translator/translator/hot/translate_node_templates.py') 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) -- cgit 1.2.3-korg