diff --git a/translator/common/utils.py b/translator/common/utils.py index 459b5ee8..8e4b690d 100644 --- a/translator/common/utils.py +++ b/translator/common/utils.py @@ -18,6 +18,7 @@ import numbers import os import re import requests +import six from six.moves.urllib.parse import urlparse import yaml @@ -262,12 +263,17 @@ class UrlUtils(object): def str_to_num(value): """Convert a string representation of a number into a numeric type.""" - if isinstance(value, numbers.Number): + if isinstance(value, numbers.Number) \ + or isinstance(value, six.integer_types) \ + or isinstance(value, float): return value try: return int(value) except ValueError: - return float(value) + try: + return float(value) + except ValueError: + return None def check_for_env_variables(): diff --git a/translator/hot/syntax/hot_resource.py b/translator/hot/syntax/hot_resource.py index 19bcd7a1..739d2ebd 100644 --- a/translator/hot/syntax/hot_resource.py +++ b/translator/hot/syntax/hot_resource.py @@ -104,7 +104,7 @@ class HotResource(object): # scenarios and cannot be fixed or hard coded here operations_deploy_sequence = ['create', 'configure', 'start'] - operations = HotResource._get_all_operations(self.nodetemplate) + operations = HotResource.get_all_operations(self.nodetemplate) # create HotResource for each operation used for deployment: # create, start, configure @@ -351,7 +351,7 @@ class HotResource(object): return tosca_props @staticmethod - def _get_all_operations(node): + def get_all_operations(node): operations = {} for operation in node.interfaces: operations[operation.name] = operation diff --git a/translator/hot/tosca/tosca_block_storage.py b/translator/hot/tosca/tosca_block_storage.py index d4b2f44c..924ff9dc 100644 --- a/translator/hot/tosca/tosca_block_storage.py +++ b/translator/hot/tosca/tosca_block_storage.py @@ -67,5 +67,5 @@ class ToscaBlockStorage(HotResource): # attribute for the matching resource. Unless there is additional # runtime support, this should be a one to one mapping. if attribute == 'volume_id': - attr['get_resource'] = args[0] + attr['get_resource'] = self.name return attr diff --git a/translator/hot/translate_node_templates.py b/translator/hot/translate_node_templates.py index 3b11fd91..e603a14d 100644 --- a/translator/hot/translate_node_templates.py +++ b/translator/hot/translate_node_templates.py @@ -25,6 +25,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 ( @@ -312,7 +313,7 @@ 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 @@ -327,49 +328,118 @@ class TranslateNodeTemplates(object): 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'] + if 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 + if 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) + 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) - 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]} + 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 + if 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)} - return input_value + return param_value + + @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: + 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 tosca_target, prop_name, prop_arg @staticmethod def get_all_artifacts(nodetemplate): @@ -438,23 +508,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_tosca_node(self, tosca_name, current_tosca_template=None): + tosca_node = None + if tosca_name == 'SELF': + tosca_node = current_tosca_template + if tosca_name == 'HOST' and current_tosca_template: + for req in current_tosca_template.requirements: + if 'host' in req: + tosca_node = self._find_tosca_node(req['host']) + + 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): - 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: - if 'host' in req: - return self._find_hot_resource_for_tosca(req['host']) - - for node in self.nodetemplates: - if node.name == tosca_name: - return self.hot_lookup[node] + 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 diff --git a/translator/hot/translate_outputs.py b/translator/hot/translate_outputs.py index 4197cddb..e955f81f 100644 --- a/translator/hot/translate_outputs.py +++ b/translator/hot/translate_outputs.py @@ -33,16 +33,7 @@ class TranslateOutputs(object): def _translate_outputs(self): hot_outputs = [] for output in self.outputs: - if output.value.name == 'get_attribute': - get_parameters = output.value.args - hot_target = self.nodes.find_hot_resource(get_parameters[0]) - hot_value = hot_target.get_hot_attribute(get_parameters[1], - get_parameters) - hot_outputs.append(HotOutput(output.name, - hot_value, - output.description)) - else: - hot_outputs.append(HotOutput(output.name, - output.value, - output.description)) + hot_value = self.nodes.translate_param_value(output.value, None) + hot_outputs.append(HotOutput(output.name, hot_value, + output.description)) return hot_outputs diff --git a/translator/tests/data/hot_output/hot_get_functions_semantic.yaml b/translator/tests/data/hot_output/hot_get_functions_semantic.yaml new file mode 100644 index 00000000..0fe34005 --- /dev/null +++ b/translator/tests/data/hot_output/hot_get_functions_semantic.yaml @@ -0,0 +1,40 @@ +heat_template_version: 2013-05-23 + +description: > + TOSCA template to test get_* functions semantic + +parameters: + map_val: + type: string +resources: + myapp_configure_deploy: + type: OS::Heat::SoftwareDeployment + properties: + config: + get_resource: myapp_configure_config + input_values: + the_list_val: list_val_0 + server: + get_resource: server + depends_on: + - mysql_database + server: + type: OS::Nova::Server + properties: + flavor: m1.small + image: ubuntu-12.04-software-config-os-init + user_data_format: SOFTWARE_CONFIG + myapp_configure_config: + type: OS::Heat::SoftwareConfig + properties: + config: + get_file: myapp_configure.sh + group: script +outputs: + map_val: + description: null + value: + get_input: map_val + static_map_val: + description: null + value: static_value \ No newline at end of file diff --git a/translator/tests/data/test_tosca_get_functions_semantic.yaml b/translator/tests/data/test_tosca_get_functions_semantic.yaml new file mode 100644 index 00000000..2cfa823f --- /dev/null +++ b/translator/tests/data/test_tosca_get_functions_semantic.yaml @@ -0,0 +1,77 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: TOSCA template to test get_* functions semantic + +node_types: + tosca.capabilities.MyFeature: + derived_from: tosca.capabilities.Root + properties: + my_list: + type: list + my_map: + type: map + + tosca.nodes.WebApplication.MyApp: + derived_from: tosca.nodes.WebApplication + requirements: + - myfeature: + capability: tosca.capabilities.MyFeature + node: tosca.nodes.MyDatabase + relationship: tosca.relationships.ConnectsTo + + tosca.nodes.MyDatabase: + derived_from: tosca.nodes.Database + capabilities: + myfeature: + type: tosca.capabilities.MyFeature + +topology_template: + inputs: + map_val: + type: string + + node_templates: + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + num_cpus: 1 + mem_size: 1 GB + os: + properties: + type: Linux + distribution: Ubuntu + version: 12.04 + architecture: x86_64 + + mysql_database: + type: tosca.nodes.MyDatabase + requirements: + - host: server + capabilities: + myfeature: + properties: + my_list: [list_val_0] + my_map: + test_key: {get_input: map_val} + test_key_static: static_value + + myapp: + type: tosca.nodes.WebApplication.MyApp + requirements: + - myfeature: mysql_database + - host: server + interfaces: + Standard: + configure: + implementation: myapp_configure.sh + inputs: + the_list_val: { get_property: [ SELF, myfeature, my_list, 0 ] } + + outputs: + map_val: + value: { get_property: [ myapp, myfeature, my_map, test_key ] } + + static_map_val: + value: { get_property: [ myapp, myfeature, my_map, test_key_static ] } \ No newline at end of file diff --git a/translator/tests/test_tosca_hot_translation.py b/translator/tests/test_tosca_hot_translation.py index 3723b99d..a746153b 100644 --- a/translator/tests/test_tosca_hot_translation.py +++ b/translator/tests/test_tosca_hot_translation.py @@ -477,3 +477,9 @@ class ToscaHotTranslationTest(TestCase): hot_file = '../tests/data/hot_output/hot_interface_on_compute.yaml' params = {} self._test_successful_translation(tosca_file, hot_file, params) + + def test_hot_get_functions_semantic(self): + tosca_file = '../tests/data/test_tosca_get_functions_semantic.yaml' + hot_file = '../tests/data/hot_output/hot_get_functions_semantic.yaml' + params = {} + self._test_successful_translation(tosca_file, hot_file, params)