Synchronise the openstack bugs
[parser.git] / tosca2heat / heat-translator / translator / hot / translate_node_templates.py
index 46cdd71..d8e7e48 100644 (file)
@@ -16,6 +16,8 @@ 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 GetProperty
@@ -25,6 +27,7 @@ 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 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,6 +135,8 @@ log = logging.getLogger('heat-translator')
 
 TOSCA_TO_HOT_TYPE = _generate_type_map()
 
+BASE_TYPES = six.string_types + six.integer_types + (dict, OrderedDict)
+
 
 class TranslateNodeTemplates(object):
     '''Translate TOSCA NodeTemplates to Heat Resources.'''
@@ -146,6 +151,9 @@ class TranslateNodeTemplates(object):
         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 = {}
 
     def translate(self):
         return self._translate_nodetemplates()
@@ -219,9 +227,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:
@@ -253,7 +266,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(
+                    node_depend.type_definition)
+                if base_type.type == 'tosca.nodes.Compute' and \
                    node.related[node_depend].type == \
                    node.type_definition.HOSTEDON:
                     self.hot_lookup[node].properties['server'] = \
@@ -266,6 +281,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()
@@ -295,53 +317,202 @@ class TranslateNodeTemplates(object):
             inputs = resource.properties.get('input_values')
             if inputs:
                 for name, value in six.iteritems(inputs):
-                    inputs[name] = self._translate_input(value, resource)
+                    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()
-            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 = self.get_all_artifacts(tosca_target)
+                if artifact_name in artifacts:
+                    artifact = artifacts[artifact_name]
+                    if artifact.get('type', None) == 'tosca.artifacts.File':
+                        return {'get_file': artifact.get('file')}
+        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': self.translate_param_value(
+                    get_input_args, resource)}
+        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
+
+    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 _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:
-                return {'get_param': input_value.args}
+                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 input_value
+        return tosca_target, prop_name, prop_arg
 
     @staticmethod
     def get_all_artifacts(nodetemplate):
@@ -410,23 +581,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