summaryrefslogtreecommitdiffstats
path: root/tosca2heat/heat-translator/translator/hot
diff options
context:
space:
mode:
Diffstat (limited to 'tosca2heat/heat-translator/translator/hot')
-rw-r--r--tosca2heat/heat-translator/translator/hot/__init__.py0
-rw-r--r--tosca2heat/heat-translator/translator/hot/syntax/__init__.py0
-rw-r--r--tosca2heat/heat-translator/translator/hot/syntax/hot_output.py25
-rw-r--r--tosca2heat/heat-translator/translator/hot/syntax/hot_parameter.py52
-rw-r--r--tosca2heat/heat-translator/translator/hot/syntax/hot_resource.py362
-rw-r--r--tosca2heat/heat-translator/translator/hot/syntax/hot_template.py83
-rw-r--r--tosca2heat/heat-translator/translator/hot/tests/__init__.py0
-rw-r--r--tosca2heat/heat-translator/translator/hot/tests/test_hot_parameter.py44
-rw-r--r--tosca2heat/heat-translator/translator/hot/tests/test_translate_inputs.py351
-rw-r--r--tosca2heat/heat-translator/translator/hot/tests/test_translate_outputs.py50
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/__init__.py0
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tests/__init__.py0
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_blockstorage.py84
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_compute.py286
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_objectstore.py71
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_policies.py80
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage.py71
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage_attachment.py48
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tosca_compute.py280
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tosca_database.py30
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tosca_dbms.py30
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tosca_network_network.py118
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tosca_network_port.py114
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tosca_object_storage.py57
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tosca_policies.py36
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tosca_software_component.py30
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tosca_web_application.py30
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tosca_webserver.py30
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca_translator.py68
-rw-r--r--tosca2heat/heat-translator/translator/hot/translate_inputs.py167
-rw-r--r--tosca2heat/heat-translator/translator/hot/translate_node_templates.py483
-rw-r--r--tosca2heat/heat-translator/translator/hot/translate_outputs.py48
32 files changed, 3128 insertions, 0 deletions
diff --git a/tosca2heat/heat-translator/translator/hot/__init__.py b/tosca2heat/heat-translator/translator/hot/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/__init__.py
diff --git a/tosca2heat/heat-translator/translator/hot/syntax/__init__.py b/tosca2heat/heat-translator/translator/hot/syntax/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/syntax/__init__.py
diff --git a/tosca2heat/heat-translator/translator/hot/syntax/hot_output.py b/tosca2heat/heat-translator/translator/hot/syntax/hot_output.py
new file mode 100644
index 0000000..ad77fb3
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/syntax/hot_output.py
@@ -0,0 +1,25 @@
+#
+# 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.
+
+
+class HotOutput(object):
+ '''Attributes for HOT output section.'''
+
+ def __init__(self, name, value, description=None):
+ self.name = name
+ self.value = value
+ self.description = description
+
+ def get_dict_output(self):
+ return {self.name: {'value': self.value,
+ 'description': self.description}}
diff --git a/tosca2heat/heat-translator/translator/hot/syntax/hot_parameter.py b/tosca2heat/heat-translator/translator/hot/syntax/hot_parameter.py
new file mode 100644
index 0000000..1ecb2ce
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/syntax/hot_parameter.py
@@ -0,0 +1,52 @@
+#
+# 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 collections import OrderedDict
+import logging
+from toscaparser.utils.gettextutils import _
+
+KEYS = (TYPE, DESCRIPTION, DEFAULT, CONSTRAINTS, HIDDEN, LABEL) = \
+ ('type', 'description', 'default', 'constraints', 'hidden', 'label')
+
+log = logging.getLogger('heat-translator')
+
+
+class HotParameter(object):
+ '''Attributes for HOT parameter section.'''
+
+ def __init__(self, name, type, label=None, description=None, default=None,
+ hidden=None, constraints=None):
+ self.name = name
+ self.type = type
+ self.label = label
+ self.description = description
+ self.default = default
+ self.hidden = hidden
+ self.constraints = constraints
+ log.info(_('Initialized the input parameters.'))
+
+ def get_dict_output(self):
+ param_sections = OrderedDict()
+ param_sections[TYPE] = self.type
+ if self.label:
+ param_sections[LABEL] = self.label
+ if self.description:
+ param_sections[DESCRIPTION] = self.description
+ if self.default:
+ param_sections[DEFAULT] = self.default
+ if self.hidden:
+ param_sections[HIDDEN] = self.hidden
+ if self.constraints:
+ param_sections[CONSTRAINTS] = self.constraints
+
+ return {self.name: param_sections}
diff --git a/tosca2heat/heat-translator/translator/hot/syntax/hot_resource.py b/tosca2heat/heat-translator/translator/hot/syntax/hot_resource.py
new file mode 100644
index 0000000..d7d0100
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/syntax/hot_resource.py
@@ -0,0 +1,362 @@
+#
+# 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 collections import OrderedDict
+import logging
+import six
+
+from toscaparser.elements.interfaces import InterfacesDef
+from toscaparser.functions import GetInput
+from toscaparser.nodetemplate import NodeTemplate
+from toscaparser.utils.gettextutils import _
+
+
+SECTIONS = (TYPE, PROPERTIES, MEDADATA, DEPENDS_ON, UPDATE_POLICY,
+ DELETION_POLICY) = \
+ ('type', 'properties', 'metadata',
+ 'depends_on', 'update_policy', 'deletion_policy')
+log = logging.getLogger('heat-translator')
+
+
+class HotResource(object):
+ '''Base class for TOSCA node type translation to Heat resource type.'''
+
+ def __init__(self, nodetemplate, name=None, type=None, properties=None,
+ metadata=None, depends_on=None,
+ update_policy=None, deletion_policy=None):
+ log.debug(_('Translating TOSCA node type to HOT resource type.'))
+ self.nodetemplate = nodetemplate
+ if name:
+ self.name = name
+ else:
+ self.name = nodetemplate.name
+ self.type = type
+ self.properties = properties or {}
+ # special case for HOT softwareconfig
+ if type == 'OS::Heat::SoftwareConfig':
+ self.properties['group'] = 'script'
+ self.metadata = metadata
+
+ # The difference between depends_on and depends_on_nodes is
+ # that depends_on defines dependency in the context of the
+ # HOT template and it is used during the template output.
+ # Depends_on_nodes defines the direct dependency between the
+ # tosca nodes and is not used during the output of the
+ # HOT template but for internal processing only. When a tosca
+ # node depends on another node it will be always added to
+ # depends_on_nodes but not always to depends_on. For example
+ # if the source of dependency is a server, the dependency will
+ # be added as properties.get_resource and not depends_on
+ if depends_on:
+ self.depends_on = depends_on
+ self.depends_on_nodes = depends_on
+ else:
+ self.depends_on = []
+ self.depends_on_nodes = []
+ self.update_policy = update_policy
+ self.deletion_policy = deletion_policy
+ self.group_dependencies = {}
+ # if hide_resource is set to true, then this resource will not be
+ # generated in the output yaml.
+ self.hide_resource = False
+
+ def handle_properties(self):
+ # the property can hold a value or the intrinsic function get_input
+ # for value, copy it
+ # for get_input, convert to get_param
+ for prop in self.nodetemplate.get_properties_objects():
+ pass
+
+ def handle_life_cycle(self):
+ hot_resources = []
+ deploy_lookup = {}
+ # TODO(anyone): sequence for life cycle needs to cover different
+ # scenarios and cannot be fixed or hard coded here
+ operations_deploy_sequence = ['create', 'configure', 'start']
+
+ operations = HotResource._get_all_operations(self.nodetemplate)
+
+ # create HotResource for each operation used for deployment:
+ # create, start, configure
+ # ignore the other operations
+ # observe the order: create, start, configure
+ # use the current HotResource for the first operation in this order
+
+ # hold the original name since it will be changed during
+ # the transformation
+ node_name = self.name
+ reserve_current = 'NONE'
+
+ for operation in operations_deploy_sequence:
+ if operation in operations.keys():
+ reserve_current = operation
+ break
+
+ # create the set of SoftwareDeployment and SoftwareConfig for
+ # the interface operations
+ hosting_server = None
+ if self.nodetemplate.requirements is not None:
+ hosting_server = self._get_hosting_server()
+ for operation in operations.values():
+ if operation.name in operations_deploy_sequence:
+ config_name = node_name + '_' + operation.name + '_config'
+ deploy_name = node_name + '_' + operation.name + '_deploy'
+ hot_resources.append(
+ HotResource(self.nodetemplate,
+ config_name,
+ 'OS::Heat::SoftwareConfig',
+ {'config':
+ {'get_file': operation.implementation}}))
+
+ # hosting_server is None if requirements is None
+ hosting_on_server = (hosting_server.name if
+ hosting_server else None)
+ if operation.name == reserve_current:
+ deploy_resource = self
+ self.name = deploy_name
+ self.type = 'OS::Heat::SoftwareDeployment'
+ self.properties = {'config': {'get_resource': config_name},
+ 'server': {'get_resource':
+ hosting_on_server}}
+ deploy_lookup[operation.name] = self
+ else:
+ sd_config = {'config': {'get_resource': config_name},
+ 'server': {'get_resource':
+ hosting_on_server}}
+ deploy_resource = \
+ HotResource(self.nodetemplate,
+ deploy_name,
+ 'OS::Heat::SoftwareDeployment',
+ sd_config)
+ hot_resources.append(deploy_resource)
+ deploy_lookup[operation.name] = deploy_resource
+ lifecycle_inputs = self._get_lifecycle_inputs(operation)
+ if lifecycle_inputs:
+ deploy_resource.properties['input_values'] = \
+ lifecycle_inputs
+
+ # Add dependencies for the set of HOT resources in the sequence defined
+ # in operations_deploy_sequence
+ # TODO(anyone): find some better way to encode this implicit sequence
+ group = {}
+ for op, hot in deploy_lookup.items():
+ # position to determine potential preceding nodes
+ op_index = operations_deploy_sequence.index(op)
+ for preceding_op in \
+ reversed(operations_deploy_sequence[:op_index]):
+ preceding_hot = deploy_lookup.get(preceding_op)
+ if preceding_hot:
+ hot.depends_on.append(preceding_hot)
+ hot.depends_on_nodes.append(preceding_hot)
+ group[preceding_hot] = hot
+ break
+
+ # save this dependency chain in the set of HOT resources
+ self.group_dependencies.update(group)
+ for hot in hot_resources:
+ hot.group_dependencies.update(group)
+
+ return hot_resources
+
+ def handle_connectsto(self, tosca_source, tosca_target, hot_source,
+ hot_target, config_location, operation):
+ # The ConnectsTo relationship causes a configuration operation in
+ # the target.
+ # This hot resource is the software config portion in the HOT template
+ # This method adds the matching software deployment with the proper
+ # target server and dependency
+ if config_location == 'target':
+ hosting_server = hot_target._get_hosting_server()
+ hot_depends = hot_target
+ elif config_location == 'source':
+ hosting_server = self._get_hosting_server()
+ hot_depends = hot_source
+ deploy_name = tosca_source.name + '_' + tosca_target.name + \
+ '_connect_deploy'
+ sd_config = {'config': {'get_resource': self.name},
+ 'server': {'get_resource': hosting_server.name}}
+ deploy_resource = \
+ HotResource(self.nodetemplate,
+ deploy_name,
+ 'OS::Heat::SoftwareDeployment',
+ sd_config,
+ depends_on=[hot_depends])
+ connect_inputs = self._get_connect_inputs(config_location, operation)
+ if connect_inputs:
+ deploy_resource.properties['input_values'] = connect_inputs
+
+ return deploy_resource
+
+ def handle_expansion(self):
+ pass
+
+ def handle_hosting(self):
+ # handle hosting server for the OS:HEAT::SoftwareDeployment
+ # from the TOSCA nodetemplate, traverse the relationship chain
+ # down to the server
+ if self.type == 'OS::Heat::SoftwareDeployment':
+ # skip if already have hosting
+ # If type is NodeTemplate, look up corresponding HotResrouce
+ host_server = self.properties.get('server')
+ if host_server is None or not host_server['get_resource']:
+ raise Exception(_("Internal Error: expecting host "
+ "in software deployment"))
+ elif isinstance(host_server['get_resource'], NodeTemplate):
+ self.properties['server']['get_resource'] = \
+ host_server['get_resource'].name
+
+ def top_of_chain(self):
+ dependent = self.group_dependencies.get(self)
+ if dependent is None:
+ return self
+ else:
+ return dependent.top_of_chain()
+
+ def get_dict_output(self):
+ resource_sections = OrderedDict()
+ resource_sections[TYPE] = self.type
+ if self.properties:
+ resource_sections[PROPERTIES] = self.properties
+ if self.metadata:
+ resource_sections[MEDADATA] = self.metadata
+ if self.depends_on:
+ resource_sections[DEPENDS_ON] = []
+ for depend in self.depends_on:
+ resource_sections[DEPENDS_ON].append(depend.name)
+ if self.update_policy:
+ resource_sections[UPDATE_POLICY] = self.update_policy
+ if self.deletion_policy:
+ resource_sections[DELETION_POLICY] = self.deletion_policy
+
+ return {self.name: resource_sections}
+
+ def _get_lifecycle_inputs(self, operation):
+ # check if this lifecycle operation has input values specified
+ # extract and convert to HOT format
+ if isinstance(operation.value, six.string_types):
+ # the operation has a static string
+ return {}
+ else:
+ # the operation is a dict {'implemenation': xxx, 'input': yyy}
+ inputs = operation.value.get('inputs')
+ deploy_inputs = {}
+ if inputs:
+ for name, value in six.iteritems(inputs):
+ deploy_inputs[name] = value
+ return deploy_inputs
+
+ def _get_connect_inputs(self, config_location, operation):
+ if config_location == 'target':
+ inputs = operation.get('pre_configure_target').get('inputs')
+ elif config_location == 'source':
+ inputs = operation.get('pre_configure_source').get('inputs')
+ deploy_inputs = {}
+ if inputs:
+ for name, value in six.iteritems(inputs):
+ deploy_inputs[name] = value
+ return deploy_inputs
+
+ def _get_hosting_server(self, node_template=None):
+ # find the server that hosts this software by checking the
+ # requirements and following the hosting chain
+ this_node_template = self.nodetemplate \
+ if node_template is None else node_template
+ for requirement in this_node_template.requirements:
+ for requirement_name, assignment in six.iteritems(requirement):
+ for check_node in this_node_template.related_nodes:
+ # check if the capability is Container
+ if isinstance(assignment, dict):
+ node_name = assignment.get('node')
+ else:
+ node_name = assignment
+ if node_name and node_name == check_node.name:
+ if self._is_container_type(requirement_name,
+ check_node):
+ return check_node
+ elif check_node.related_nodes:
+ return self._get_hosting_server(check_node)
+ return None
+
+ def _is_container_type(self, requirement_name, node):
+ # capability is a list of dict
+ # For now just check if it's type tosca.nodes.Compute
+ # TODO(anyone): match up requirement and capability
+ base_type = HotResource.get_base_type(node.type_definition)
+ if base_type.type == 'tosca.nodes.Compute':
+ return True
+ else:
+ return False
+
+ def get_hot_attribute(self, attribute, args):
+ # this is a place holder and should be implemented by the subclass
+ # if translation is needed for the particular attribute
+ raise Exception(_("No translation in TOSCA type {0} for attribute "
+ "{1}").format(self.nodetemplate.type, attribute))
+
+ def get_tosca_props(self):
+ tosca_props = {}
+ for prop in self.nodetemplate.get_properties_objects():
+ if isinstance(prop.value, GetInput):
+ tosca_props[prop.name] = {'get_param': prop.value.input_name}
+ else:
+ tosca_props[prop.name] = prop.value
+ return tosca_props
+
+ @staticmethod
+ def _get_all_operations(node):
+ operations = {}
+ for operation in node.interfaces:
+ operations[operation.name] = operation
+
+ node_type = node.type_definition
+ if isinstance(node_type, str) or \
+ node_type.type == "tosca.policies.Placement":
+ return operations
+
+ while True:
+ type_operations = HotResource._get_interface_operations_from_type(
+ node_type, node, 'Standard')
+ type_operations.update(operations)
+ operations = type_operations
+
+ if node_type.parent_type is not None:
+ node_type = node_type.parent_type
+ else:
+ return operations
+
+ @staticmethod
+ def _get_interface_operations_from_type(node_type, node, lifecycle_name):
+ operations = {}
+ if isinstance(node_type, str) or \
+ node_type.type == "tosca.policies.Placement":
+ return operations
+ if node_type.interfaces and lifecycle_name in node_type.interfaces:
+ for name, elems in node_type.interfaces[lifecycle_name].items():
+ # ignore empty operations (only type)
+ # ignore global interface inputs,
+ # concrete inputs are on the operations themselves
+ if name != 'type' and name != 'inputs':
+ operations[name] = InterfacesDef(node_type,
+ lifecycle_name,
+ node, name, elems)
+ return operations
+
+ @staticmethod
+ def get_base_type(node_type):
+ if node_type.parent_type is not None:
+ if node_type.parent_type.type.endswith('.Root'):
+ return node_type
+ else:
+ return HotResource.get_base_type(node_type.parent_type)
+ else:
+ return node_type
diff --git a/tosca2heat/heat-translator/translator/hot/syntax/hot_template.py b/tosca2heat/heat-translator/translator/hot/syntax/hot_template.py
new file mode 100644
index 0000000..4263c4d
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/syntax/hot_template.py
@@ -0,0 +1,83 @@
+#
+# 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 collections import OrderedDict
+import logging
+import textwrap
+from toscaparser.utils.gettextutils import _
+import yaml
+
+log = logging.getLogger('heat-translator')
+
+
+class HotTemplate(object):
+ '''Container for full Heat Orchestration template.'''
+
+ SECTIONS = (VERSION, DESCRIPTION, PARAMETER_GROUPS, PARAMETERS,
+ RESOURCES, OUTPUTS, MAPPINGS) = \
+ ('heat_template_version', 'description', 'parameter_groups',
+ 'parameters', 'resources', 'outputs', '__undefined__')
+
+ VERSIONS = (LATEST,) = ('2013-05-23',)
+
+ def __init__(self):
+ self.resources = []
+ self.outputs = []
+ self.parameters = []
+ self.description = ""
+
+ def represent_ordereddict(self, dumper, data):
+ nodes = []
+ for key, value in data.items():
+ node_key = dumper.represent_data(key)
+ node_value = dumper.represent_data(value)
+ nodes.append((node_key, node_value))
+ return yaml.nodes.MappingNode(u'tag:yaml.org,2002:map', nodes)
+
+ def output_to_yaml(self):
+ log.debug(_('Converting translated output to yaml format.'))
+ dict_output = OrderedDict()
+ # Version
+ version_string = self.VERSION + ": " + self.LATEST + "\n\n"
+
+ # Description
+ desc_str = ""
+ if self.description:
+ # Wrap the text to a new line if the line exceeds 80 characters.
+ wrapped_txt = "\n ".join(textwrap.wrap(self.description, 80))
+ desc_str = self.DESCRIPTION + ": >\n " + wrapped_txt + "\n\n"
+
+ # Parameters
+ all_params = OrderedDict()
+ for parameter in self.parameters:
+ all_params.update(parameter.get_dict_output())
+ dict_output.update({self.PARAMETERS: all_params})
+
+ # Resources
+ all_resources = OrderedDict()
+ for resource in self.resources:
+ if not resource.hide_resource:
+ all_resources.update(resource.get_dict_output())
+ dict_output.update({self.RESOURCES: all_resources})
+
+ # Outputs
+ all_outputs = OrderedDict()
+ for output in self.outputs:
+ all_outputs.update(output.get_dict_output())
+ dict_output.update({self.OUTPUTS: all_outputs})
+
+ yaml.add_representer(OrderedDict, self.represent_ordereddict)
+ yaml_string = yaml.dump(dict_output, default_flow_style=False)
+ # get rid of the '' from yaml.dump around numbers
+ yaml_string = yaml_string.replace('\'', '')
+ return version_string + desc_str + yaml_string
diff --git a/tosca2heat/heat-translator/translator/hot/tests/__init__.py b/tosca2heat/heat-translator/translator/hot/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/tests/__init__.py
diff --git a/tosca2heat/heat-translator/translator/hot/tests/test_hot_parameter.py b/tosca2heat/heat-translator/translator/hot/tests/test_hot_parameter.py
new file mode 100644
index 0000000..8d3f535
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/tests/test_hot_parameter.py
@@ -0,0 +1,44 @@
+# 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 collections import OrderedDict
+
+from toscaparser.tests.base import TestCase
+from translator.hot.syntax.hot_parameter import CONSTRAINTS
+from translator.hot.syntax.hot_parameter import DEFAULT
+from translator.hot.syntax.hot_parameter import DESCRIPTION
+from translator.hot.syntax.hot_parameter import HIDDEN
+from translator.hot.syntax.hot_parameter import HotParameter
+from translator.hot.syntax.hot_parameter import LABEL
+from translator.hot.syntax.hot_parameter import TYPE
+
+TEST_CONSTRAINTS = {'equal': 'allowed_values', 'greater_than': 'range'}
+
+
+class HotParameterTest(TestCase):
+
+ # This test ensures the variables set during the creation of a HotParameter
+ # object are returned in an OrderedDict when calling get_dict_output().
+ def test_dict_output(self):
+ name = 'HotParameterTest'
+ hot_parameter = HotParameter(name, 'Type',
+ label='Label',
+ description='Description',
+ default='Default',
+ hidden=True,
+ constraints=TEST_CONSTRAINTS)
+ expected_dict = OrderedDict([(TYPE, 'Type'), (LABEL, 'Label'),
+ (DESCRIPTION, 'Description'),
+ (DEFAULT, 'Default'), (HIDDEN, True),
+ (CONSTRAINTS, TEST_CONSTRAINTS)])
+
+ self.assertEqual(hot_parameter.get_dict_output()[name], expected_dict)
diff --git a/tosca2heat/heat-translator/translator/hot/tests/test_translate_inputs.py b/tosca2heat/heat-translator/translator/hot/tests/test_translate_inputs.py
new file mode 100644
index 0000000..2b302ab
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/tests/test_translate_inputs.py
@@ -0,0 +1,351 @@
+# 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 collections import OrderedDict
+from toscaparser.parameters import Input
+from toscaparser.tests.base import TestCase
+from toscaparser.utils.gettextutils import _
+import toscaparser.utils.yamlparser
+from translator.common.utils import CompareUtils
+from translator.hot.translate_inputs import TranslateInputs
+
+
+class ToscaTemplateInputValidationTest(TestCase):
+
+ def _translate_input_test(self, tpl_snippet, input_params,
+ expectedmessage=None,
+ expected_hot_params=None):
+ inputs_dict = (toscaparser.utils.yamlparser.
+ simple_parse(tpl_snippet)['inputs'])
+ inputs = []
+ for name, attrs in inputs_dict.items():
+ input = Input(name, attrs)
+ inputs.append(input)
+
+ translateinput = TranslateInputs(inputs, input_params)
+ try:
+ resulted_hot_params = translateinput.translate()
+ if expected_hot_params:
+ self._compare_hot_params(resulted_hot_params,
+ expected_hot_params)
+ except Exception as err:
+ self.assertEqual(expectedmessage, err.__str__())
+
+ def _compare_hot_params(self, resulted_hot_params,
+ expected_hot_params):
+ for expected_param in expected_hot_params:
+ for resulted_param_obj in resulted_hot_params:
+ resulted_param = resulted_param_obj.get_dict_output()
+ result = CompareUtils.compare_dicts(expected_param,
+ resulted_param)
+ if not result:
+ raise Exception(_("hot input and resulted input "
+ "params are not equal."))
+
+ def test_invalid_input_type(self):
+ tpl_snippet = '''
+ inputs:
+ cpus:
+ type: integer
+ description: Number of CPUs for the server.
+ constraints:
+ - valid_values: [ 1, 2, 4, 8 ]
+ '''
+
+ input_params = {'cpus': '0.3'}
+ expectedmessage = _('"0.3" is not an integer.')
+ self._translate_input_test(tpl_snippet, input_params,
+ expectedmessage)
+
+ def test_invalid_input_constraints_for_equal(self):
+ tpl_snippet = '''
+ inputs:
+ num_cpus:
+ type: integer
+ description: Number of CPUs for the server.
+ constraints:
+ - equal: 1
+ '''
+
+ input_params = {'num_cpus': '0'}
+ expectedmessage = _('The value "0" of property "num_cpus" is not '
+ 'equal to "1".')
+ self._translate_input_test(tpl_snippet, input_params, expectedmessage)
+
+ def test_invalid_input_constraints_for_greater_or_equal(self):
+ tpl_snippet = '''
+ inputs:
+ num_cpus:
+ type: integer
+ description: Number of CPUs for the server.
+ constraints:
+ - greater_or_equal: 1
+ '''
+
+ input_params = {'num_cpus': '0'}
+ expectedmessage = _('The value "0" of property "num_cpus" must be '
+ 'greater than or equal to "1".')
+ self._translate_input_test(tpl_snippet, input_params, expectedmessage)
+
+ def test_invalid_input_constraints_for_greater_than(self):
+ tpl_snippet = '''
+ inputs:
+ num_cpus:
+ type: integer
+ description: Number of CPUs for the server.
+ constraints:
+ - greater_than: 1
+ '''
+
+ input_params = {'num_cpus': '0'}
+ expectedmessage = _('The value "0" of property "num_cpus" must be '
+ 'greater than "1".')
+ self._translate_input_test(tpl_snippet, input_params, expectedmessage)
+
+ def test_invalid_input_constraints_for_less_than(self):
+ tpl_snippet = '''
+ inputs:
+ num_cpus:
+ type: integer
+ description: Number of CPUs for the server.
+ constraints:
+ - less_than: 8
+ '''
+
+ input_params = {'num_cpus': '8'}
+ expectedmessage = _('The value "8" of property "num_cpus" must be '
+ 'less than "8".')
+ self._translate_input_test(tpl_snippet, input_params, expectedmessage)
+
+ def test_invalid_input_constraints_for_less_or_equal(self):
+ tpl_snippet = '''
+ inputs:
+ num_cpus:
+ type: integer
+ description: Number of CPUs for the server.
+ constraints:
+ - less_or_equal: 8
+ '''
+
+ input_params = {'num_cpus': '9'}
+ expectedmessage = _('The value "9" of property "num_cpus" must be '
+ 'less than or equal to "8".')
+ self._translate_input_test(tpl_snippet, input_params, expectedmessage)
+
+ def test_invalid_input_constraints_for_valid_values(self):
+ tpl_snippet = '''
+ inputs:
+ num_cpus:
+ type: integer
+ description: Number of CPUs for the server.
+ constraints:
+ - valid_values: [ 1, 2, 4, 8 ]
+ '''
+
+ input_params = {'num_cpus': '3'}
+ expectedmessage = _('The value "3" of property "num_cpus" is not '
+ 'valid. Expected a value from "[1, 2, 4, 8]".')
+ self._translate_input_test(tpl_snippet, input_params, expectedmessage)
+
+ def test_invalid_input_constraints_for_in_range(self):
+ tpl_snippet = '''
+ inputs:
+ num_cpus:
+ type: integer
+ description: Number of CPUs for the server.
+ constraints:
+ - in_range: [ 1, 8 ]
+ '''
+
+ input_params = {'num_cpus': '10'}
+ expectedmessage = _('The value "10" of property "num_cpus" is out of '
+ 'range "(min:1, max:8)".')
+ self._translate_input_test(tpl_snippet, input_params, expectedmessage)
+
+ def test_invalid_input_constraints_for_min_length(self):
+ tpl_snippet = '''
+ inputs:
+ user_name:
+ type: string
+ description: Name of the user.
+ constraints:
+ - min_length: 8
+ '''
+
+ input_params = {'user_name': 'abcd'}
+ expectedmessage = _('Length of value "abcd" of property "user_name" '
+ 'must be at least "8".')
+ self._translate_input_test(tpl_snippet, input_params, expectedmessage)
+
+ def test_invalid_input_constraints_for_max_length(self):
+ tpl_snippet = '''
+ inputs:
+ user_name:
+ type: string
+ description: Name of the user.
+ constraints:
+ - max_length: 6
+ '''
+
+ input_params = {'user_name': 'abcdefg'}
+ expectedmessage = _('Length of value "abcdefg" of property '
+ '"user_name" must be no greater than "6".')
+ self._translate_input_test(tpl_snippet, input_params, expectedmessage)
+
+ def test_invalid_input_constraints_for_pattern(self):
+ tpl_snippet = '''
+ inputs:
+ user_name:
+ type: string
+ description: Name of the user.
+ constraints:
+ - pattern: '^\w+$'
+ '''
+
+ input_params = {'user_name': '1-abc'}
+ expectedmessage = _('The value "1-abc" of property "user_name" does '
+ 'not match pattern "^\\w+$".')
+ self._translate_input_test(tpl_snippet, input_params, expectedmessage)
+
+ def test_valid_input_storage_size(self):
+ tpl_snippet = '''
+ inputs:
+ storage_size:
+ type: scalar-unit.size
+ description: size of the storage volume.
+ '''
+
+ expectedmessage = _('both equal.')
+ input_params = {'storage_size': '2 GB'}
+ expected_hot_params = [{'storage_size':
+ OrderedDict([('type', 'number'),
+ ('description',
+ 'size of the storage volume.'),
+ ('default', 2)])}]
+ self._translate_input_test(tpl_snippet, input_params,
+ expectedmessage, expected_hot_params)
+
+ """ TOSCA 2000 MB => 2 GB HOT conversion"""
+ input_params = {'storage_size': '2000 MB'}
+ expected_hot_params = [{'storage_size':
+ OrderedDict([('type', 'number'),
+ ('description',
+ 'size of the storage volume.'),
+ ('default', 2)])}]
+ self._translate_input_test(tpl_snippet, input_params,
+ expectedmessage, expected_hot_params)
+
+ """ TOSCA 2048 MB => 2 GB HOT conversion"""
+ input_params = {'storage_size': '2048 MB'}
+ expected_hot_params = [{'storage_size':
+ OrderedDict([('type', 'number'),
+ ('description',
+ 'size of the storage volume.'),
+ ('default', 2)])}]
+ self._translate_input_test(tpl_snippet, input_params,
+ expectedmessage, expected_hot_params)
+
+ """ TOSCA 2 MB => 1 GB HOT conversion"""
+ input_params = {'storage_size': '2 MB'}
+ expected_hot_params = [{'storage_size':
+ OrderedDict([('type', 'number'),
+ ('description',
+ 'size of the storage volume.'),
+ ('default', 1)])}]
+ self._translate_input_test(tpl_snippet, input_params,
+ expectedmessage, expected_hot_params)
+
+ """ TOSCA 1024 MB => 1 GB HOT conversion"""
+ input_params = {'storage_size': '1024 MB'}
+ expected_hot_params = [{'storage_size':
+ OrderedDict([('type', 'number'),
+ ('description',
+ 'size of the storage volume.'),
+ ('default', 1)])}]
+ self._translate_input_test(tpl_snippet, input_params,
+ expectedmessage, expected_hot_params)
+
+ """ TOSCA 1024 MiB => 1 GB HOT conversion"""
+ input_params = {'storage_size': '1024 MiB'}
+ expected_hot_params = [{'storage_size':
+ OrderedDict([('type', 'number'),
+ ('description',
+ 'size of the storage volume.'),
+ ('default', 1)])}]
+ self._translate_input_test(tpl_snippet, input_params,
+ expectedmessage, expected_hot_params)
+
+ def test_invalid_input_storage_size(self):
+ tpl_snippet = '''
+ inputs:
+ storage_size:
+ type: scalar-unit.size
+ description: size of the storage volume.
+ '''
+
+ input_params = {'storage_size': '0 MB'}
+ expectedmsg = _("Unit value should be > 0.")
+ self._translate_input_test(tpl_snippet, input_params, expectedmsg)
+
+ input_params = {'storage_size': '-2 MB'}
+ expectedmsg = _('"-2 MB" is not a valid scalar-unit.')
+ self._translate_input_test(tpl_snippet, input_params, expectedmsg)
+
+ def test_invalid_input_type_version(self):
+ tpl_snippet = '''
+ inputs:
+ version:
+ type: version
+ '''
+
+ input_params = {'version': '0.a'}
+ expectedmessage = _('Value of TOSCA version property '
+ '"0.a" is invalid.')
+ self._translate_input_test(tpl_snippet, input_params,
+ expectedmessage)
+
+ input_params = {'version': '0.0.0.abc'}
+ expectedmessage = _('Value of TOSCA version property '
+ '"0.0.0.abc" is invalid.')
+ self._translate_input_test(tpl_snippet, input_params,
+ expectedmessage)
+
+ def test_valid_input_type_version(self):
+ tpl_snippet = '''
+ inputs:
+ version:
+ type: version
+ default: 12
+ '''
+
+ expectedmessage = _('both equal.')
+ input_params = {'version': '18'}
+ expected_hot_params = [{'version':
+ OrderedDict([('type', 'string'),
+ ('default', '18.0')])}]
+ self._translate_input_test(tpl_snippet, input_params, expectedmessage,
+ expected_hot_params)
+
+ input_params = {'version': '18.0'}
+ expected_hot_params = [{'version':
+ OrderedDict([('type', 'string'),
+ ('default', '18.0')])}]
+ self._translate_input_test(tpl_snippet, input_params, expectedmessage,
+ expected_hot_params)
+
+ input_params = {'version': '18.0.1'}
+ expected_hot_params = [{'version':
+ OrderedDict([('type', 'string'),
+ ('default', '18.0.1')])}]
+ self._translate_input_test(tpl_snippet, input_params, expectedmessage,
+ expected_hot_params)
diff --git a/tosca2heat/heat-translator/translator/hot/tests/test_translate_outputs.py b/tosca2heat/heat-translator/translator/hot/tests/test_translate_outputs.py
new file mode 100644
index 0000000..955150e
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/tests/test_translate_outputs.py
@@ -0,0 +1,50 @@
+# 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.
+
+import os
+from toscaparser.tests.base import TestCase
+from toscaparser.tosca_template import ToscaTemplate
+import toscaparser.utils.yamlparser
+from translator.hot.tosca_translator import TOSCATranslator
+
+
+class ToscaTemplateOutputTest(TestCase):
+
+ def test_translate_output(self):
+ tosca_tpl = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)),
+ "../../tests/data/"
+ "tosca_nodejs_mongodb_two_instances.yaml")
+ tosca = ToscaTemplate(tosca_tpl)
+ translate = TOSCATranslator(tosca, [])
+ hot_translation = translate.translate()
+
+ expected_output = {'nodejs_url':
+ {'description': 'URL for the nodejs '
+ 'server, http://<IP>:3000',
+ 'value':
+ {'get_attr':
+ ['app_server', 'networks', 'private', 0]}},
+ 'mongodb_url':
+ {'description': 'URL for the mongodb server.',
+ 'value':
+ {'get_attr':
+ ['mongo_server', 'networks', 'private', 0]}}}
+
+ hot_translation_dict = \
+ toscaparser.utils.yamlparser.simple_parse(hot_translation)
+
+ outputs = hot_translation_dict.get('outputs')
+ for resource_name in outputs:
+ translated_value = outputs.get(resource_name)
+ expected_value = expected_output.get(resource_name)
+ self.assertEqual(translated_value, expected_value)
diff --git a/tosca2heat/heat-translator/translator/hot/tosca/__init__.py b/tosca2heat/heat-translator/translator/hot/tosca/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/tosca/__init__.py
diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tests/__init__.py b/tosca2heat/heat-translator/translator/hot/tosca/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/tosca/tests/__init__.py
diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_blockstorage.py b/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_blockstorage.py
new file mode 100644
index 0000000..d4fffe1
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_blockstorage.py
@@ -0,0 +1,84 @@
+# 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 toscaparser.common.exception import InvalidPropertyValueError
+from toscaparser.nodetemplate import NodeTemplate
+from toscaparser.tests.base import TestCase
+from toscaparser.utils.gettextutils import _
+import toscaparser.utils.yamlparser
+from translator.hot.tosca.tosca_block_storage import ToscaBlockStorage
+
+
+class ToscaBlockStoreTest(TestCase):
+
+ def _tosca_blockstore_test(self, tpl_snippet, expectedprops):
+ nodetemplates = (toscaparser.utils.yamlparser.
+ simple_parse(tpl_snippet)['node_templates'])
+ name = list(nodetemplates.keys())[0]
+ try:
+ nodetemplate = NodeTemplate(name, nodetemplates)
+ tosca_block_store = ToscaBlockStorage(nodetemplate)
+ tosca_block_store.handle_properties()
+ if not self._compare_properties(tosca_block_store.properties,
+ expectedprops):
+ raise Exception(_("Hot Properties are not"
+ " same as expected properties"))
+ except Exception:
+ # for time being rethrowing. Will be handled future based
+ # on new development
+ raise
+
+ def _compare_properties(self, hotprops, expectedprops):
+ return all(item in hotprops.items() for item in expectedprops.items())
+
+ def test_node_blockstorage_with_properties(self):
+ tpl_snippet = '''
+ node_templates:
+ my_storage:
+ type: tosca.nodes.BlockStorage
+ properties:
+ size: 1024 MiB
+ snapshot_id: abc
+ '''
+ expectedprops = {'snapshot_id': 'abc',
+ 'size': 1}
+ self._tosca_blockstore_test(
+ tpl_snippet,
+ expectedprops)
+
+ tpl_snippet = '''
+ node_templates:
+ my_storage:
+ type: tosca.nodes.BlockStorage
+ properties:
+ size: 124 MB
+ snapshot_id: abc
+ '''
+ expectedprops = {'snapshot_id': 'abc',
+ 'size': 1}
+ self._tosca_blockstore_test(
+ tpl_snippet,
+ expectedprops)
+
+ def test_node_blockstorage_with_invalid_size_property(self):
+ tpl_snippet = '''
+ node_templates:
+ my_storage:
+ type: tosca.nodes.BlockStorage
+ properties:
+ size: 0 MB
+ snapshot_id: abc
+ '''
+ expectedprops = {}
+ self.assertRaises(InvalidPropertyValueError,
+ lambda: self._tosca_blockstore_test(tpl_snippet,
+ expectedprops))
diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_compute.py b/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_compute.py
new file mode 100644
index 0000000..e0cdbb6
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_compute.py
@@ -0,0 +1,286 @@
+# 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.
+
+import json
+import mock
+from mock import patch
+
+from toscaparser.nodetemplate import NodeTemplate
+from toscaparser.tests.base import TestCase
+from toscaparser.utils.gettextutils import _
+import toscaparser.utils.yamlparser
+from translator.hot.tosca.tosca_compute import ToscaCompute
+
+
+class ToscaComputeTest(TestCase):
+
+ def _tosca_compute_test(self, tpl_snippet, expectedprops):
+ nodetemplates = (toscaparser.utils.yamlparser.
+ simple_parse(tpl_snippet)['node_templates'])
+ name = list(nodetemplates.keys())[0]
+ try:
+ nodetemplate = NodeTemplate(name, nodetemplates)
+ nodetemplate.validate()
+ toscacompute = ToscaCompute(nodetemplate)
+ toscacompute.handle_properties()
+ if not self._compare_properties(toscacompute.properties,
+ expectedprops):
+ raise Exception(_("Hot Properties are not"
+ " same as expected properties"))
+ except Exception:
+ # for time being rethrowing. Will be handled future based
+ # on new development in Glance and Graffiti
+ raise
+
+ def _compare_properties(self, hotprops, expectedprops):
+ return all(item in hotprops.items() for item in expectedprops.items())
+
+ def test_node_compute_with_host_and_os_capabilities(self):
+ tpl_snippet = '''
+ node_templates:
+ server:
+ type: tosca.nodes.Compute
+ capabilities:
+ host:
+ properties:
+ disk_size: 10 GB
+ num_cpus: 4
+ mem_size: 4 GB
+ os:
+ properties:
+ architecture: x86_64
+ type: Linux
+ distribution: Fedora
+ version: 18.0
+ '''
+ expectedprops = {'flavor': 'm1.large',
+ 'image': 'fedora-amd64-heat-config'}
+ self._tosca_compute_test(
+ tpl_snippet,
+ expectedprops)
+
+ def test_node_compute_without_os_capabilities(self):
+ tpl_snippet = '''
+ node_templates:
+ server:
+ type: tosca.nodes.Compute
+ capabilities:
+ host:
+ properties:
+ disk_size: 10 GB
+ num_cpus: 4
+ mem_size: 4 GB
+ #left intentionally
+ '''
+ expectedprops = {'flavor': 'm1.large',
+ 'image': None}
+ self._tosca_compute_test(
+ tpl_snippet,
+ expectedprops)
+
+ def test_node_compute_without_host_capabilities(self):
+ tpl_snippet = '''
+ node_templates:
+ server:
+ type: tosca.nodes.Compute
+ capabilities:
+ os:
+ properties:
+ architecture: x86_64
+ type: Linux
+ distribution: Fedora
+ version: 18.0
+ '''
+ expectedprops = {'flavor': None,
+ 'image': 'fedora-amd64-heat-config'}
+ self._tosca_compute_test(
+ tpl_snippet,
+ expectedprops)
+
+ def test_node_compute_without_properties_and_os_capabilities(self):
+ tpl_snippet = '''
+ node_templates:
+ server:
+ type: tosca.nodes.Compute
+ properties:
+ #left intentionally
+ capabilities:
+ #left intentionally
+ '''
+ expectedprops = {'flavor': None,
+ 'image': None}
+ self._tosca_compute_test(
+ tpl_snippet,
+ expectedprops)
+
+ def test_node_compute_with_only_type(self):
+ tpl_snippet = '''
+ node_templates:
+ server:
+ type: tosca.nodes.Compute
+ '''
+ expectedprops = {'flavor': None,
+ 'image': None}
+ self._tosca_compute_test(
+ tpl_snippet,
+ expectedprops)
+
+ def test_node_compute_host_capabilities_without_properties(self):
+ tpl_snippet = '''
+ node_templates:
+ server:
+ type: tosca.nodes.Compute
+ capabilities:
+ host:
+ properties:
+ #left intentionally
+ '''
+ expectedprops = {'flavor': 'm1.nano'}
+ self._tosca_compute_test(
+ tpl_snippet,
+ expectedprops)
+
+ def test_node_compute_host_capabilities_without_disk_size(self):
+ tpl_snippet = '''
+ node_templates:
+ server:
+ type: tosca.nodes.Compute
+ capabilities:
+ host:
+ properties:
+ num_cpus: 4
+ mem_size: 4 GB
+ '''
+ expectedprops = {'flavor': 'm1.large'}
+ self._tosca_compute_test(
+ tpl_snippet,
+ expectedprops)
+
+ def test_node_compute_host_capabilities_without_mem_size(self):
+ tpl_snippet = '''
+ node_templates:
+ server:
+ type: tosca.nodes.Compute
+ capabilities:
+ host:
+ properties:
+ num_cpus: 4
+ disk_size: 10 GB
+ '''
+ expectedprops = {'flavor': 'm1.large'}
+ self._tosca_compute_test(
+ tpl_snippet,
+ expectedprops)
+
+ def test_node_compute_host_capabilities_without_mem_size_disk_size(self):
+ tpl_snippet = '''
+ node_templates:
+ server:
+ type: tosca.nodes.Compute
+ capabilities:
+ host:
+ properties:
+ num_cpus: 4
+ '''
+ expectedprops = {'flavor': 'm1.large'}
+ self._tosca_compute_test(
+ tpl_snippet,
+ expectedprops)
+
+ @patch('requests.post')
+ @patch('requests.get')
+ @patch('os.getenv')
+ def test_node_compute_with_nova_flavor(self, mock_os_getenv,
+ mock_get, mock_post):
+ tpl_snippet = '''
+ node_templates:
+ server:
+ type: tosca.nodes.Compute
+ capabilities:
+ host:
+ properties:
+ num_cpus: 1
+ disk_size: 1 GB
+ mem_size: 1 GB
+ '''
+ with patch('translator.common.utils.'
+ 'check_for_env_variables') as mock_check_env:
+ mock_check_env.return_value = True
+ mock_os_getenv.side_effect = ['demo', 'demo',
+ 'demo', 'http://abc.com/5000/',
+ 'demo', 'demo',
+ 'demo', 'http://abc.com/5000/']
+ mock_ks_response = mock.MagicMock()
+ mock_ks_response.status_code = 200
+ mock_ks_content = {
+ 'access': {
+ 'token': {
+ 'id': 'd1dfa603-3662-47e0-b0b6-3ae7914bdf76'
+ },
+ 'serviceCatalog': [{
+ 'type': 'compute',
+ 'endpoints': [{
+ 'publicURL': 'http://abc.com'
+ }]
+ }]
+ }
+ }
+ mock_ks_response.content = json.dumps(mock_ks_content)
+ mock_nova_response = mock.MagicMock()
+ mock_nova_response.status_code = 200
+ mock_flavor_content = {
+ 'flavors': [{
+ 'name': 'm1.mock_flavor',
+ 'ram': 1024,
+ 'disk': 1,
+ 'vcpus': 1
+ }]
+ }
+ mock_nova_response.content = \
+ json.dumps(mock_flavor_content)
+ mock_post.return_value = mock_ks_response
+ mock_get.return_value = mock_nova_response
+ expectedprops = {'flavor': 'm1.mock_flavor'}
+ self._tosca_compute_test(
+ tpl_snippet,
+ expectedprops)
+
+ @patch('requests.post')
+ @patch('requests.get')
+ @patch('os.getenv')
+ def test_node_compute_without_nova_flavor(self, mock_os_getenv,
+ mock_get, mock_post):
+ tpl_snippet = '''
+ node_templates:
+ server:
+ type: tosca.nodes.Compute
+ capabilities:
+ host:
+ properties:
+ num_cpus: 1
+ disk_size: 1 GB
+ mem_size: 1 GB
+ '''
+ with patch('translator.common.utils.'
+ 'check_for_env_variables') as mock_check_env:
+ mock_check_env.return_value = True
+ mock_os_getenv.side_effect = ['demo', 'demo',
+ 'demo', 'http://abc.com/5000/']
+ mock_ks_response = mock.MagicMock()
+ mock_ks_content = {}
+ mock_ks_response.content = json.dumps(mock_ks_content)
+ expectedprops = {'flavor': 'm1.small',
+ 'user_data_format': 'SOFTWARE_CONFIG',
+ 'image': None}
+ self._tosca_compute_test(
+ tpl_snippet,
+ expectedprops)
diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_objectstore.py b/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_objectstore.py
new file mode 100644
index 0000000..4c42794
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_objectstore.py
@@ -0,0 +1,71 @@
+# 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 toscaparser.nodetemplate import NodeTemplate
+from toscaparser.tests.base import TestCase
+from toscaparser.utils.gettextutils import _
+import toscaparser.utils.yamlparser
+from translator.hot.tosca.tosca_object_storage import ToscaObjectStorage
+
+
+class ToscaObjectStoreTest(TestCase):
+
+ def _tosca_objectstore_test(self, tpl_snippet, expectedprops):
+ nodetemplates = (toscaparser.utils.yamlparser.
+ simple_parse(tpl_snippet)['node_templates'])
+ name = list(nodetemplates.keys())[0]
+ try:
+ nodetemplate = NodeTemplate(name, nodetemplates)
+ tosca_object_store = ToscaObjectStorage(nodetemplate)
+ tosca_object_store.handle_properties()
+ if not self._compare_properties(tosca_object_store.properties,
+ expectedprops):
+ raise Exception(_("Hot Properties are not"
+ " same as expected properties"))
+ except Exception:
+ # for time being rethrowing. Will be handled future based
+ # on new development
+ raise
+
+ def _compare_properties(self, hotprops, expectedprops):
+ return all(item in hotprops.items() for item in expectedprops.items())
+
+ def test_node_objectstorage_with_properties(self):
+ tpl_snippet = '''
+ node_templates:
+ server:
+ type: tosca.nodes.ObjectStorage
+ properties:
+ name: test
+ size: 1024 KB
+ maxsize: 1 MB
+ '''
+ expectedprops = {'name': 'test',
+ 'X-Container-Meta': {'Quota-Bytes': 1000000}}
+ self._tosca_objectstore_test(
+ tpl_snippet,
+ expectedprops)
+
+ def test_node_objectstorage_with_few_properties(self):
+ tpl_snippet = '''
+ node_templates:
+ server:
+ type: tosca.nodes.ObjectStorage
+ properties:
+ name: test
+ size: 1024 B
+ '''
+ expectedprops = {'name': 'test',
+ 'X-Container-Meta': {'Quota-Bytes': 1024}}
+ self._tosca_objectstore_test(
+ tpl_snippet,
+ expectedprops)
diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_policies.py b/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_policies.py
new file mode 100644
index 0000000..24368ab
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_policies.py
@@ -0,0 +1,80 @@
+# 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 toscaparser.nodetemplate import NodeTemplate
+from toscaparser.policy import Policy
+from toscaparser.tests.base import TestCase
+import toscaparser.utils.yamlparser
+from translator.hot.tosca.tosca_compute import ToscaCompute
+from translator.hot.tosca.tosca_policies import ToscaPolicies
+
+
+class ToscaPoicyTest(TestCase):
+
+ def _tosca_policy_test(self, tpl_snippet, expectedprops):
+ nodetemplates = (toscaparser.utils.yamlparser.
+ simple_parse(tpl_snippet)['node_templates'])
+ policies = (toscaparser.utils.yamlparser.
+ simple_parse(tpl_snippet)['policies'])
+ name = list(nodetemplates.keys())[0]
+ policy_name = list(policies[0].keys())[0]
+ for policy in policies:
+ tpl = policy[policy_name]
+ targets = tpl["targets"]
+ try:
+ nodetemplate = NodeTemplate(name, nodetemplates)
+ toscacompute = ToscaCompute(nodetemplate)
+ toscacompute.handle_properties()
+
+ policy = Policy(policy_name, tpl, targets,
+ "node_templates")
+ toscapolicy = ToscaPolicies(policy)
+ nodetemplate = [toscacompute]
+ toscapolicy.handle_properties(nodetemplate)
+
+ self.assertEqual(toscacompute.properties, expectedprops)
+ except Exception:
+ raise
+
+ def test_compute_with_policies(self):
+ tpl_snippet = '''
+ node_templates:
+ server:
+ type: tosca.nodes.Compute
+ capabilities:
+ host:
+ properties:
+ disk_size: 10 GB
+ num_cpus: 4
+ mem_size: 4 GB
+ os:
+ properties:
+ architecture: x86_64
+ type: Linux
+ distribution: Fedora
+ version: 18.0
+ policies:
+ - my_compute_placement_policy:
+ type: tosca.policies.Placement
+ description: Apply my placement policy to my application servers
+ targets: [ server ]
+ '''
+ expectedprops = {'flavor': 'm1.large',
+ 'image': 'fedora-amd64-heat-config',
+ 'scheduler_hints': {
+ 'group': {
+ 'get_resource':
+ 'my_compute_placement_policy'}},
+ 'user_data_format': 'SOFTWARE_CONFIG'}
+ self._tosca_policy_test(
+ tpl_snippet,
+ expectedprops)
diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage.py
new file mode 100644
index 0000000..d4b2f44
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage.py
@@ -0,0 +1,71 @@
+#
+# 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.
+
+import logging
+from toscaparser.common.exception import InvalidPropertyValueError
+from toscaparser.elements.scalarunit import ScalarUnit_Size
+from toscaparser.functions import GetInput
+from toscaparser.utils.gettextutils import _
+from translator.hot.syntax.hot_resource import HotResource
+
+log = logging.getLogger('heat-translator')
+
+# Name used to dynamically load appropriate map class.
+TARGET_CLASS_NAME = 'ToscaBlockStorage'
+
+
+class ToscaBlockStorage(HotResource):
+ '''Translate TOSCA node type tosca.nodes.BlockStorage.'''
+
+ toscatype = 'tosca.nodes.BlockStorage'
+
+ def __init__(self, nodetemplate):
+ super(ToscaBlockStorage, self).__init__(nodetemplate,
+ type='OS::Cinder::Volume')
+ pass
+
+ def handle_properties(self):
+ tosca_props = {}
+ for prop in self.nodetemplate.get_properties_objects():
+ if isinstance(prop.value, GetInput):
+ tosca_props[prop.name] = {'get_param': prop.value.input_name}
+ else:
+ if prop.name == "size":
+ size_value = (ScalarUnit_Size(prop.value).
+ get_num_from_scalar_unit('GiB'))
+ if size_value == 0:
+ # OpenStack Heat expects size in GB
+ msg = _('Cinder Volume Size unit should be in GB.')
+ log.error(msg)
+ raise InvalidPropertyValueError(
+ what=msg)
+ elif int(size_value) < size_value:
+ size_value = int(size_value) + 1
+ log.warning(_("Cinder unit value should be in "
+ "multiples of GBs. so corrected "
+ " %(prop_val)s to %(size_value)s GB.")
+ % {'prop_val': prop.value,
+ 'size_value': size_value})
+ tosca_props[prop.name] = int(size_value)
+ else:
+ tosca_props[prop.name] = prop.value
+ self.properties = tosca_props
+
+ def get_hot_attribute(self, attribute, args):
+ attr = {}
+ # Convert from a TOSCA attribute for a nodetemplate to a HOT
+ # 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]
+ return attr
diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage_attachment.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage_attachment.py
new file mode 100644
index 0000000..715d5b3
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage_attachment.py
@@ -0,0 +1,48 @@
+#
+# 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 toscaparser.functions import GetInput
+from translator.hot.syntax.hot_resource import HotResource
+
+# Name used to dynamically load appropriate map class.
+TARGET_CLASS_NAME = 'ToscaBlockStorageAttachment'
+
+
+class ToscaBlockStorageAttachment(HotResource):
+ '''Translate TOSCA relationship AttachesTo for Compute and BlockStorage.'''
+
+ toscatype = 'tosca.nodes.BlockStorageAttachment'
+
+ def __init__(self, template, nodetemplates, instance_uuid, volume_id):
+ super(ToscaBlockStorageAttachment,
+ self).__init__(template, type='OS::Cinder::VolumeAttachment')
+ self.nodetemplates = nodetemplates
+ self.instance_uuid = {'get_resource': instance_uuid}
+ self.volume_id = {'get_resource': volume_id}
+
+ def handle_properties(self):
+ tosca_props = {}
+ for prop in self.nodetemplate.get_properties_objects():
+ if isinstance(prop.value, GetInput):
+ tosca_props[prop.name] = {'get_param': prop.value.input_name}
+ else:
+ tosca_props[prop.name] = prop.value
+ self.properties = tosca_props
+ # instance_uuid and volume_id for Cinder volume attachment
+ self.properties['instance_uuid'] = self.instance_uuid
+ self.properties['volume_id'] = self.volume_id
+ if 'location' in self.properties:
+ self.properties['mountpoint'] = self.properties.pop('location')
+
+ def handle_life_cycle(self):
+ pass
diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_compute.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_compute.py
new file mode 100644
index 0000000..e2ac130
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_compute.py
@@ -0,0 +1,280 @@
+#
+# 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.
+
+import json
+import logging
+import requests
+
+from toscaparser.utils.gettextutils import _
+import translator.common.utils
+from translator.hot.syntax.hot_resource import HotResource
+
+log = logging.getLogger('heat-translator')
+
+
+# Name used to dynamically load appropriate map class.
+TARGET_CLASS_NAME = 'ToscaCompute'
+
+# A design issue to be resolved is how to translate the generic TOSCA server
+# properties to OpenStack flavors and images. At the Atlanta design summit,
+# there was discussion on using Glance to store metadata and Graffiti to
+# describe artifacts. We will follow these projects to see if they can be
+# leveraged for this TOSCA translation.
+# For development purpose at this time, we temporarily hardcode a list of
+# flavors and images here
+FLAVORS = {'m1.xlarge': {'mem_size': 16384, 'disk_size': 160, 'num_cpus': 8},
+ 'm1.large': {'mem_size': 8192, 'disk_size': 80, 'num_cpus': 4},
+ 'm1.medium': {'mem_size': 4096, 'disk_size': 40, 'num_cpus': 2},
+ 'm1.small': {'mem_size': 2048, 'disk_size': 20, 'num_cpus': 1},
+ 'm1.tiny': {'mem_size': 512, 'disk_size': 1, 'num_cpus': 1},
+ 'm1.micro': {'mem_size': 128, 'disk_size': 0, 'num_cpus': 1},
+ 'm1.nano': {'mem_size': 64, 'disk_size': 0, 'num_cpus': 1}}
+
+IMAGES = {'ubuntu-software-config-os-init': {'architecture': 'x86_64',
+ 'type': 'Linux',
+ 'distribution': 'Ubuntu',
+ 'version': '14.04'},
+ 'ubuntu-12.04-software-config-os-init': {'architecture': 'x86_64',
+ 'type': 'Linux',
+ 'distribution': 'Ubuntu',
+ 'version': '12.04'},
+ 'fedora-amd64-heat-config': {'architecture': 'x86_64',
+ 'type': 'Linux',
+ 'distribution': 'Fedora',
+ 'version': '18.0'},
+ 'F18-x86_64-cfntools': {'architecture': 'x86_64',
+ 'type': 'Linux',
+ 'distribution': 'Fedora',
+ 'version': '19'},
+ 'Fedora-x86_64-20-20131211.1-sda': {'architecture': 'x86_64',
+ 'type': 'Linux',
+ 'distribution': 'Fedora',
+ 'version': '20'},
+ 'cirros-0.3.1-x86_64-uec': {'architecture': 'x86_64',
+ 'type': 'Linux',
+ 'distribution': 'CirrOS',
+ 'version': '0.3.1'},
+ 'cirros-0.3.2-x86_64-uec': {'architecture': 'x86_64',
+ 'type': 'Linux',
+ 'distribution': 'CirrOS',
+ 'version': '0.3.2'},
+ 'rhel-6.5-test-image': {'architecture': 'x86_64',
+ 'type': 'Linux',
+ 'distribution': 'RHEL',
+ 'version': '6.5'}}
+
+
+class ToscaCompute(HotResource):
+ '''Translate TOSCA node type tosca.nodes.Compute.'''
+
+ COMPUTE_HOST_PROP = (DISK_SIZE, MEM_SIZE, NUM_CPUS) = \
+ ('disk_size', 'mem_size', 'num_cpus')
+
+ COMPUTE_OS_PROP = (ARCHITECTURE, DISTRIBUTION, TYPE, VERSION) = \
+ ('architecture', 'distribution', 'type', 'version')
+ toscatype = 'tosca.nodes.Compute'
+
+ def __init__(self, nodetemplate):
+ super(ToscaCompute, self).__init__(nodetemplate,
+ type='OS::Nova::Server')
+ # List with associated hot port resources with this server
+ self.assoc_port_resources = []
+ pass
+
+ def handle_properties(self):
+ self.properties = self.translate_compute_flavor_and_image(
+ self.nodetemplate.get_capability('host'),
+ self.nodetemplate.get_capability('os'))
+ self.properties['user_data_format'] = 'SOFTWARE_CONFIG'
+ tosca_props = self.get_tosca_props()
+ for key, value in tosca_props.items():
+ self.properties[key] = value
+
+ # To be reorganized later based on new development in Glance and Graffiti
+ def translate_compute_flavor_and_image(self,
+ host_capability,
+ os_capability):
+ hot_properties = {}
+ host_cap_props = {}
+ os_cap_props = {}
+ image = None
+ flavor = None
+ if host_capability:
+ for prop in host_capability.get_properties_objects():
+ host_cap_props[prop.name] = prop.value
+ flavor = self._best_flavor(host_cap_props)
+ if os_capability:
+ for prop in os_capability.get_properties_objects():
+ os_cap_props[prop.name] = prop.value
+ image = self._best_image(os_cap_props)
+ hot_properties['flavor'] = flavor
+ hot_properties['image'] = image
+ return hot_properties
+
+ def _create_nova_flavor_dict(self):
+ '''Populates and returns the flavors dict using Nova ReST API'''
+ try:
+ access_dict = translator.common.utils.get_ks_access_dict()
+ access_token = translator.common.utils.get_token_id(access_dict)
+ if access_token is None:
+ return None
+ nova_url = translator.common.utils.get_url_for(access_dict,
+ 'compute')
+ if not nova_url:
+ return None
+ nova_response = requests.get(nova_url + '/flavors/detail',
+ headers={'X-Auth-Token':
+ access_token})
+ if nova_response.status_code != 200:
+ return None
+ flavors = json.loads(nova_response.content)['flavors']
+ flavor_dict = dict()
+ for flavor in flavors:
+ flavor_name = str(flavor['name'])
+ flavor_dict[flavor_name] = {
+ 'mem_size': flavor['ram'],
+ 'disk_size': flavor['disk'],
+ 'num_cpus': flavor['vcpus'],
+ }
+ except Exception as e:
+ # Handles any exception coming from openstack
+ log.warn(_('Choosing predefined flavors since received '
+ 'Openstack Exception: %s') % str(e))
+ return None
+ return flavor_dict
+
+ def _best_flavor(self, properties):
+ log.info(_('Choosing the best flavor for given attributes.'))
+ # Check whether user exported all required environment variables.
+ flavors = FLAVORS
+ if translator.common.utils.check_for_env_variables():
+ resp = self._create_nova_flavor_dict()
+ if resp:
+ flavors = resp
+
+ # start with all flavors
+ match_all = flavors.keys()
+
+ # TODO(anyone): Handle the case where the value contains something like
+ # get_input instead of a value.
+ # flavors that fit the CPU count
+ cpu = properties.get(self.NUM_CPUS)
+ if cpu is None:
+ self._log_compute_msg(self.NUM_CPUS, 'flavor')
+ match_cpu = self._match_flavors(match_all, flavors, self.NUM_CPUS, cpu)
+
+ # flavors that fit the mem size
+ mem = properties.get(self.MEM_SIZE)
+ if mem:
+ mem = translator.common.utils.MemoryUnit.convert_unit_size_to_num(
+ mem, 'MB')
+ else:
+ self._log_compute_msg(self.MEM_SIZE, 'flavor')
+ match_cpu_mem = self._match_flavors(match_cpu, flavors,
+ self.MEM_SIZE, mem)
+ # flavors that fit the disk size
+ disk = properties.get(self.DISK_SIZE)
+ if disk:
+ disk = translator.common.utils.MemoryUnit.\
+ convert_unit_size_to_num(disk, 'GB')
+ else:
+ self._log_compute_msg(self.DISK_SIZE, 'flavor')
+ match_cpu_mem_disk = self._match_flavors(match_cpu_mem, flavors,
+ self.DISK_SIZE, disk)
+ # if multiple match, pick the flavor with the least memory
+ # the selection can be based on other heuristic, e.g. pick one with the
+ # least total resource
+ if len(match_cpu_mem_disk) > 1:
+ return self._least_flavor(match_cpu_mem_disk, flavors, 'mem_size')
+ elif len(match_cpu_mem_disk) == 1:
+ return match_cpu_mem_disk[0]
+ else:
+ return None
+
+ def _best_image(self, properties):
+ match_all = IMAGES.keys()
+ architecture = properties.get(self.ARCHITECTURE)
+ if architecture is None:
+ self._log_compute_msg(self.ARCHITECTURE, 'image')
+ match_arch = self._match_images(match_all, IMAGES,
+ self.ARCHITECTURE, architecture)
+ type = properties.get(self.TYPE)
+ if type is None:
+ self._log_compute_msg(self.TYPE, 'image')
+ match_type = self._match_images(match_arch, IMAGES, self.TYPE, type)
+ distribution = properties.get(self.DISTRIBUTION)
+ if distribution is None:
+ self._log_compute_msg(self.DISTRIBUTION, 'image')
+ match_distribution = self._match_images(match_type, IMAGES,
+ self.DISTRIBUTION,
+ distribution)
+ version = properties.get(self.VERSION)
+ if version is None:
+ self._log_compute_msg(self.VERSION, 'image')
+ match_version = self._match_images(match_distribution, IMAGES,
+ self.VERSION, version)
+
+ if len(match_version):
+ return list(match_version)[0]
+
+ def _match_flavors(self, this_list, this_dict, attr, size):
+ '''Return from this list all flavors matching the attribute size.'''
+ if not size:
+ return list(this_list)
+ matching_flavors = []
+ for flavor in this_list:
+ if isinstance(size, int):
+ if this_dict[flavor][attr] >= size:
+ matching_flavors.append(flavor)
+ log.debug(_('Returning list of flavors matching the attribute size.'))
+ return matching_flavors
+
+ def _least_flavor(self, this_list, this_dict, attr):
+ '''Return from this list the flavor with the smallest attr.'''
+ least_flavor = this_list[0]
+ for flavor in this_list:
+ if this_dict[flavor][attr] < this_dict[least_flavor][attr]:
+ least_flavor = flavor
+ return least_flavor
+
+ def _match_images(self, this_list, this_dict, attr, prop):
+ if not prop:
+ return this_list
+ matching_images = []
+ for image in this_list:
+ if this_dict[image][attr].lower() == str(prop).lower():
+ matching_images.append(image)
+ return matching_images
+
+ def get_hot_attribute(self, attribute, args):
+ attr = {}
+ # Convert from a TOSCA attribute for a nodetemplate to a HOT
+ # attribute for the matching resource. Unless there is additional
+ # runtime support, this should be a one to one mapping.
+
+ # Note: We treat private and public IP addresses equally, but
+ # this will change in the future when TOSCA starts to support
+ # multiple private/public IP addresses.
+ log.debug(_('Converting TOSCA attribute for a nodetemplate to a HOT \
+ attriute.'))
+ if attribute == 'private_address' or \
+ attribute == 'public_address':
+ attr['get_attr'] = [self.name, 'networks', 'private', 0]
+
+ return attr
+
+ def _log_compute_msg(self, prop, what):
+ msg = _('No value is provided for Compute capability '
+ 'property "%(prop)s". This may set an undesired "%(what)s" '
+ 'in the template.') % {'prop': prop, 'what': what}
+ log.warn(msg)
diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_database.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_database.py
new file mode 100644
index 0000000..26c9d4d
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_database.py
@@ -0,0 +1,30 @@
+#
+# 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 translator.hot.syntax.hot_resource import HotResource
+
+# Name used to dynamically load appropriate map class.
+TARGET_CLASS_NAME = 'ToscaDatabase'
+
+
+class ToscaDatabase(HotResource):
+ '''Translate TOSCA node type tosca.nodes.Database.'''
+
+ toscatype = 'tosca.nodes.Database'
+
+ def __init__(self, nodetemplate):
+ super(ToscaDatabase, self).__init__(nodetemplate)
+ pass
+
+ def handle_properties(self):
+ pass
diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_dbms.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_dbms.py
new file mode 100644
index 0000000..38c31bd
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_dbms.py
@@ -0,0 +1,30 @@
+#
+# 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 translator.hot.syntax.hot_resource import HotResource
+
+# Name used to dynamically load appropriate map class.
+TARGET_CLASS_NAME = 'ToscaDbms'
+
+
+class ToscaDbms(HotResource):
+ '''Translate TOSCA node type tosca.nodes.DBMS.'''
+
+ toscatype = 'tosca.nodes.DBMS'
+
+ def __init__(self, nodetemplate):
+ super(ToscaDbms, self).__init__(nodetemplate)
+ pass
+
+ def handle_properties(self):
+ pass
diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_network_network.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_network_network.py
new file mode 100644
index 0000000..2b80313
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_network_network.py
@@ -0,0 +1,118 @@
+#
+# 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 toscaparser.common.exception import InvalidPropertyValueError
+from translator.hot.syntax.hot_resource import HotResource
+
+# Name used to dynamically load appropriate map class.
+TARGET_CLASS_NAME = 'ToscaNetwork'
+
+
+class ToscaNetwork(HotResource):
+ '''Translate TOSCA node type tosca.nodes.network.Network.'''
+
+ toscatype = 'tosca.nodes.network.Network'
+ SUBNET_SUFFIX = '_subnet'
+ NETWORK_PROPS = ['network_name', 'network_id', 'segmentation_id']
+ SUBNET_PROPS = ['ip_version', 'cidr', 'start_ip', 'end_ip', 'gateway_ip']
+
+ existing_resource_id = None
+
+ def __init__(self, nodetemplate):
+ super(ToscaNetwork, self).__init__(nodetemplate,
+ type='OS::Neutron::Net')
+ pass
+
+ def handle_properties(self):
+ tosca_props = self.get_tosca_props()
+
+ net_props = {}
+ for key, value in tosca_props.items():
+ if key in self.NETWORK_PROPS:
+ if key == 'network_name':
+ # If CIDR is specified network_name should
+ # be used as the name for the new network.
+ if 'cidr' in tosca_props.keys():
+ net_props['name'] = value
+ # If CIDR is not specified network_name will be used
+ # to lookup existing network. If network_id is specified
+ # together with network_name then network_id should be
+ # used to lookup the network instead
+ elif 'network_id' not in tosca_props.keys():
+ self.hide_resource = True
+ self.existing_resource_id = value
+ break
+ elif key == 'network_id':
+ self.hide_resource = True
+ self.existing_resource_id = value
+ break
+ elif key == 'segmentation_id':
+ net_props['segmentation_id'] = \
+ tosca_props['segmentation_id']
+ # Hardcode to vxlan for now until we add the network type
+ # and physical network to the spec.
+ net_props['value_specs'] = {'provider:segmentation_id':
+ value, 'provider:network_type':
+ 'vxlan'}
+ self.properties = net_props
+
+ def handle_expansion(self):
+ # If the network resource should not be output (they are hidden),
+ # there is no need to generate subnet resource
+ if self.hide_resource:
+ return
+
+ tosca_props = self.get_tosca_props()
+
+ subnet_props = {}
+
+ ip_pool_start = None
+ ip_pool_end = None
+
+ for key, value in tosca_props.items():
+ if key in self.SUBNET_PROPS:
+ if key == 'start_ip':
+ ip_pool_start = value
+ elif key == 'end_ip':
+ ip_pool_end = value
+ elif key == 'dhcp_enabled':
+ subnet_props['enable_dhcp'] = value
+ else:
+ subnet_props[key] = value
+
+ if 'network_id' in tosca_props:
+ subnet_props['network'] = tosca_props['network_id']
+ else:
+ subnet_props['network'] = '{ get_resource: %s }' % (self.name)
+
+ # Handle allocation pools
+ # Do this only if both start_ip and end_ip are provided
+ # If one of them is missing throw an exception.
+ if ip_pool_start and ip_pool_end:
+ allocation_pool = {}
+ allocation_pool['start'] = ip_pool_start
+ allocation_pool['end'] = ip_pool_end
+ allocation_pools = [allocation_pool]
+ subnet_props['allocation_pools'] = allocation_pools
+ elif ip_pool_start:
+ raise InvalidPropertyValueError(what=_('start_ip'))
+ elif ip_pool_end:
+ raise InvalidPropertyValueError(what=_('end_ip'))
+
+ subnet_resource_name = self.name + self.SUBNET_SUFFIX
+
+ hot_resources = [HotResource(self.nodetemplate,
+ type='OS::Neutron::Subnet',
+ name=subnet_resource_name,
+ properties=subnet_props)]
+ return hot_resources
diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_network_port.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_network_port.py
new file mode 100644
index 0000000..4fd2d70
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_network_port.py
@@ -0,0 +1,114 @@
+#
+# 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 translator.hot.syntax.hot_resource import HotResource
+
+# Name used to dynamically load appropriate map class.
+TARGET_CLASS_NAME = 'ToscaNetworkPort'
+TOSCA_LINKS_TO = 'tosca.relationships.network.LinksTo'
+TOSCA_BINDS_TO = 'tosca.relationships.network.BindsTo'
+
+
+class ToscaNetworkPort(HotResource):
+ '''Translate TOSCA node type tosca.nodes.network.Port.'''
+
+ toscatype = 'tosca.nodes.network.Port'
+
+ def __init__(self, nodetemplate):
+ super(ToscaNetworkPort, self).__init__(nodetemplate,
+ type='OS::Neutron::Port')
+ # Default order
+ self.order = 0
+ pass
+
+ def _generate_networks_for_compute(self, port_resources):
+ '''Generate compute networks property list from the port resources.'''
+ networks = []
+ for resource in port_resources:
+ networks.append({'port': '{ get_resource: %s }' % (resource.name)})
+ return networks
+
+ def _insert_sorted_resource(self, resources, resource):
+ '''Insert a resource in the list of resources and keep the order.'''
+ lo = 0
+ hi = len(resources)
+ while lo < hi:
+ mid = (lo + hi) // 2
+ if resource.order < resources[mid].order:
+ hi = mid
+ else:
+ lo = mid + 1
+ resources.insert(lo, resource)
+
+ def handle_properties(self):
+ tosca_props = self.get_tosca_props()
+ port_props = {}
+ for key, value in tosca_props.items():
+ if key == 'ip_address':
+ fixed_ip = {}
+ fixed_ip['ip_address'] = value
+ port_props['fixed_ips'] = [fixed_ip]
+ elif key == 'order':
+ self.order = value
+ # TODO(sdmonov): Need to implement the properties below
+ elif key == 'is_default':
+ pass
+ elif key == 'ip_range_start':
+ pass
+ elif key == 'ip_range_end':
+ pass
+ else:
+ port_props[key] = value
+
+ links_to = None
+ binds_to = None
+ for rel, node in self.nodetemplate.relationships.items():
+ # Check for LinksTo relations. If found add a network property with
+ # the network name into the port
+ if not links_to and rel.is_derived_from(TOSCA_LINKS_TO):
+ links_to = node
+
+ network_resource = None
+ for hot_resource in self.depends_on_nodes:
+ if links_to.name == hot_resource.name:
+ network_resource = hot_resource
+ self.depends_on.remove(hot_resource)
+ break
+
+ if network_resource.existing_resource_id:
+ port_props['network'] =\
+ str(network_resource.existing_resource_id)
+ else:
+ port_props['network'] = '{ get_resource: %s }'\
+ % (links_to.name)
+
+ # Check for BindsTo relationship. If found add network to the
+ # network property of the corresponding compute resource
+ elif not binds_to and rel.is_derived_from(TOSCA_BINDS_TO):
+ binds_to = node
+ compute_resource = None
+ for hot_resource in self.depends_on_nodes:
+ if binds_to.name == hot_resource.name:
+ compute_resource = hot_resource
+ self.depends_on.remove(hot_resource)
+ break
+ if compute_resource:
+ port_rsrcs = compute_resource.assoc_port_resources
+ self._insert_sorted_resource(port_rsrcs, self)
+ # TODO(sdmonov): Using generate networks every time we add
+ # a network is not the fastest way to do the things. We
+ # should do this only once at the end.
+ networks = self._generate_networks_for_compute(port_rsrcs)
+ compute_resource.properties['networks'] = networks
+
+ self.properties = port_props
diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_object_storage.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_object_storage.py
new file mode 100644
index 0000000..177503f
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_object_storage.py
@@ -0,0 +1,57 @@
+#
+# 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 toscaparser.elements.scalarunit import ScalarUnit_Size
+from translator.hot.syntax.hot_resource import HotResource
+
+# Name used to dynamically load appropriate map class.
+TARGET_CLASS_NAME = 'ToscaObjectStorage'
+
+
+class ToscaObjectStorage(HotResource):
+ '''Translate TOSCA node type tosca.nodes.ObjectStorage.'''
+
+ toscatype = 'tosca.nodes.ObjectStorage'
+
+ def __init__(self, nodetemplate):
+ super(ToscaObjectStorage, self).__init__(nodetemplate,
+ type='OS::Swift::Container')
+ pass
+
+ def handle_properties(self):
+ tosca_props = self.get_tosca_props()
+ objectstore_props = {}
+ container_quota = {}
+ skip_check = False
+
+ for key, value in tosca_props.items():
+ if key == "name":
+ objectstore_props["name"] = value
+ elif key == "size" or key == "maxsize":
+ # currently heat is not supporting dynamically increase
+ # the container quota-size.
+ # if both defined in tosca template, consider store_maxsize.
+ if skip_check:
+ continue
+ quota_size = None
+ if "maxsize" in tosca_props.keys():
+ quota_size = tosca_props["maxsize"]
+ else:
+ quota_size = tosca_props["size"]
+ container_quota["Quota-Bytes"] = \
+ ScalarUnit_Size(quota_size).get_num_from_scalar_unit()
+ objectstore_props["X-Container-Meta"] = container_quota
+ skip_check = True
+
+ objectstore_props["X-Container-Read"] = '".r:*"'
+ self.properties = objectstore_props
diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_policies.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_policies.py
new file mode 100644
index 0000000..b32fc1d
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_policies.py
@@ -0,0 +1,36 @@
+#
+# 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 translator.hot.syntax.hot_resource import HotResource
+
+# Name used to dynamically load appropriate map class.
+TARGET_CLASS_NAME = 'ToscaPolicies'
+
+
+class ToscaPolicies(HotResource):
+ '''Translate TOSCA policy type tosca.poicies.Placement.'''
+
+ toscatype = 'tosca.policies.Placement'
+
+ def __init__(self, policy):
+ super(ToscaPolicies, self).__init__(policy,
+ type='OS::Nova::ServerGroup')
+ self.policy = policy
+
+ def handle_properties(self, resources):
+ self.properties["name"] = self.name
+ self.properties["policies"] = ["affinity"]
+ for resource in resources:
+ if resource.name in self.policy.targets:
+ resource.properties["scheduler_hints"] = {
+ "group": {"get_resource": self.name}}
diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_software_component.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_software_component.py
new file mode 100644
index 0000000..044de43
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_software_component.py
@@ -0,0 +1,30 @@
+#
+# 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 translator.hot.syntax.hot_resource import HotResource
+
+# Name used to dynamically load appropriate map class.
+TARGET_CLASS_NAME = 'ToscaSoftwareComponent'
+
+
+class ToscaSoftwareComponent(HotResource):
+ '''Translate TOSCA node type tosca.nodes.SoftwareComponent.'''
+
+ toscatype = 'tosca.nodes.SoftwareComponent'
+
+ def __init__(self, nodetemplate):
+ super(ToscaSoftwareComponent, self).__init__(nodetemplate)
+ pass
+
+ def handle_properties(self):
+ pass
diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_web_application.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_web_application.py
new file mode 100644
index 0000000..d0a9c5d
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_web_application.py
@@ -0,0 +1,30 @@
+#
+# 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 translator.hot.syntax.hot_resource import HotResource
+
+# Name used to dynamically load appropriate map class.
+TARGET_CLASS_NAME = 'ToscaWebApplication'
+
+
+class ToscaWebApplication(HotResource):
+ '''Translate TOSCA node type tosca.nodes.WebApplication.'''
+
+ toscatype = 'tosca.nodes.WebApplication'
+
+ def __init__(self, nodetemplate):
+ super(ToscaWebApplication, self).__init__(nodetemplate)
+ pass
+
+ def handle_properties(self):
+ pass
diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_webserver.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_webserver.py
new file mode 100644
index 0000000..83bda80
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_webserver.py
@@ -0,0 +1,30 @@
+#
+# 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 translator.hot.syntax.hot_resource import HotResource
+
+# Name used to dynamically load appropriate map class.
+TARGET_CLASS_NAME = 'ToscaWebserver'
+
+
+class ToscaWebserver(HotResource):
+ '''Translate TOSCA node type tosca.nodes.WebServer.'''
+
+ toscatype = 'tosca.nodes.WebServer'
+
+ def __init__(self, nodetemplate):
+ super(ToscaWebserver, self).__init__(nodetemplate)
+ pass
+
+ def handle_properties(self):
+ pass
diff --git a/tosca2heat/heat-translator/translator/hot/tosca_translator.py b/tosca2heat/heat-translator/translator/hot/tosca_translator.py
new file mode 100644
index 0000000..14ef8a1
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/tosca_translator.py
@@ -0,0 +1,68 @@
+#
+# 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.
+
+import logging
+from toscaparser.utils.gettextutils import _
+from translator.hot.syntax.hot_template import HotTemplate
+from translator.hot.translate_inputs import TranslateInputs
+from translator.hot.translate_node_templates import TranslateNodeTemplates
+from translator.hot.translate_outputs import TranslateOutputs
+
+log = logging.getLogger('heat-translator')
+
+
+class TOSCATranslator(object):
+ '''Invokes translation methods.'''
+
+ def __init__(self, tosca, parsed_params, deploy=None):
+ super(TOSCATranslator, self).__init__()
+ self.tosca = tosca
+ self.hot_template = HotTemplate()
+ self.parsed_params = parsed_params
+ self.deploy = deploy
+ self.node_translator = None
+ log.info(_('Initialized parmaters for translation.'))
+
+ def translate(self):
+ self._resolve_input()
+ self.hot_template.description = self.tosca.description
+ self.hot_template.parameters = self._translate_inputs()
+ self.node_translator = TranslateNodeTemplates(self.tosca,
+ self.hot_template)
+ self.hot_template.resources = self.node_translator.translate()
+ self.hot_template.outputs = self._translate_outputs()
+ return self.hot_template.output_to_yaml()
+
+ def _translate_inputs(self):
+ translator = TranslateInputs(self.tosca.inputs, self.parsed_params,
+ self.deploy)
+ return translator.translate()
+
+ def _translate_outputs(self):
+ translator = TranslateOutputs(self.tosca.outputs, self.node_translator)
+ return translator.translate()
+
+ # check all properties for all node and ensure they are resolved
+ # to actual value
+ def _resolve_input(self):
+ for n in self.tosca.nodetemplates:
+ for node_prop in n.get_properties_objects():
+ if isinstance(node_prop.value, dict):
+ try:
+ self.parsed_params[node_prop.value['get_input']]
+ except Exception:
+ msg = (_('Must specify all input values in \
+ TOSCA template, missing %s.') %
+ node_prop.value['get_input'])
+ log.error(msg)
+ raise ValueError(msg)
diff --git a/tosca2heat/heat-translator/translator/hot/translate_inputs.py b/tosca2heat/heat-translator/translator/hot/translate_inputs.py
new file mode 100644
index 0000000..6d677d1
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/translate_inputs.py
@@ -0,0 +1,167 @@
+#
+# 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.
+
+import logging
+from toscaparser.dataentity import DataEntity
+from toscaparser.elements.scalarunit import ScalarUnit_Size
+from toscaparser.parameters import Input
+from toscaparser.utils.gettextutils import _
+from toscaparser.utils.validateutils import TOSCAVersionProperty
+from translator.hot.syntax.hot_parameter import HotParameter
+
+
+INPUT_CONSTRAINTS = (CONSTRAINTS, DESCRIPTION, LENGTH, RANGE,
+ MIN, MAX, ALLOWED_VALUES, ALLOWED_PATTERN) = \
+ ('constraints', 'description', 'length', 'range',
+ 'min', 'max', 'allowed_values', 'allowed_pattern')
+
+TOSCA_CONSTRAINT_OPERATORS = (EQUAL, GREATER_THAN, GREATER_OR_EQUAL, LESS_THAN,
+ LESS_OR_EQUAL, IN_RANGE, VALID_VALUES, LENGTH,
+ MIN_LENGTH, MAX_LENGTH, PATTERN) = \
+ ('equal', 'greater_than', 'greater_or_equal',
+ 'less_than', 'less_or_equal', 'in_range',
+ 'valid_values', 'length', 'min_length',
+ 'max_length', 'pattern')
+
+TOSCA_TO_HOT_CONSTRAINTS_ATTRS = {'equal': 'allowed_values',
+ 'greater_than': 'range',
+ 'greater_or_equal': 'range',
+ 'less_than': 'range',
+ 'less_or_equal': 'range',
+ 'in_range': 'range',
+ 'valid_values': 'allowed_values',
+ 'length': 'length',
+ 'min_length': 'length',
+ 'max_length': 'length',
+ 'pattern': 'allowed_pattern'}
+
+TOSCA_TO_HOT_INPUT_TYPES = {'string': 'string',
+ 'integer': 'number',
+ 'float': 'number',
+ 'boolean': 'boolean',
+ 'timestamp': 'string',
+ 'scalar-unit.size': 'number',
+ 'version': 'string',
+ 'null': 'string',
+ 'PortDef': 'number'}
+
+log = logging.getLogger('heat-translator')
+
+
+class TranslateInputs(object):
+
+ '''Translate TOSCA Inputs to Heat Parameters.'''
+
+ def __init__(self, inputs, parsed_params, deploy=None):
+ self.inputs = inputs
+ self.parsed_params = parsed_params
+ self.deploy = deploy
+
+ def translate(self):
+ return self._translate_inputs()
+
+ def _translate_inputs(self):
+ hot_inputs = []
+ if 'key_name' in self.parsed_params and 'key_name' not in self.inputs:
+ name = 'key_name'
+ type = 'string'
+ default = self.parsed_params[name]
+ schema_dict = {'type': type, 'default': default}
+ input = Input(name, schema_dict)
+ self.inputs.append(input)
+
+ log.info(_('Translating TOSCA input type to HOT input type.'))
+ for input in self.inputs:
+ hot_default = None
+ hot_input_type = TOSCA_TO_HOT_INPUT_TYPES[input.type]
+
+ if input.name in self.parsed_params:
+ hot_default = DataEntity.validate_datatype(
+ input.type, self.parsed_params[input.name])
+ elif input.default is not None:
+ hot_default = DataEntity.validate_datatype(input.type,
+ input.default)
+ else:
+ if self.deploy:
+ msg = _("Need to specify a value "
+ "for input {0}.").format(input.name)
+ log.error(msg)
+ raise Exception(msg)
+ if input.type == "scalar-unit.size":
+ # Assumption here is to use this scalar-unit.size for size of
+ # cinder volume in heat templates and will be in GB.
+ # should add logic to support other types if needed.
+ input_value = hot_default
+ hot_default = (ScalarUnit_Size(hot_default).
+ get_num_from_scalar_unit('GiB'))
+ if hot_default == 0:
+ msg = _('Unit value should be > 0.')
+ log.error(msg)
+ raise Exception(msg)
+ elif int(hot_default) < hot_default:
+ hot_default = int(hot_default) + 1
+ log.warning(_("Cinder unit value should be in multiples"
+ " of GBs. So corrected %(input_value)s "
+ "to %(hot_default)s GB.")
+ % {'input_value': input_value,
+ 'hot_default': hot_default})
+ if input.type == 'version':
+ hot_default = TOSCAVersionProperty(hot_default).get_version()
+
+ hot_constraints = []
+ if input.constraints:
+ for constraint in input.constraints:
+ if hot_default:
+ constraint.validate(hot_default)
+ hc, hvalue = self._translate_constraints(
+ constraint.constraint_key, constraint.constraint_value)
+ hot_constraints.append({hc: hvalue})
+
+ hot_inputs.append(HotParameter(name=input.name,
+ type=hot_input_type,
+ description=input.description,
+ default=hot_default,
+ constraints=hot_constraints))
+ return hot_inputs
+
+ def _translate_constraints(self, name, value):
+ hot_constraint = TOSCA_TO_HOT_CONSTRAINTS_ATTRS[name]
+
+ # Offset used to support less_than and greater_than.
+ # TODO(anyone): when parser supports float, verify this works
+ offset = 1
+
+ if name == EQUAL:
+ hot_value = [value]
+ elif name == GREATER_THAN:
+ hot_value = {"min": value + offset}
+ elif name == GREATER_OR_EQUAL:
+ hot_value = {"min": value}
+ elif name == LESS_THAN:
+ hot_value = {"max": value - offset}
+ elif name == LESS_OR_EQUAL:
+ hot_value = {"max": value}
+ elif name == IN_RANGE:
+ # value is list type here
+ min_value = min(value)
+ max_value = max(value)
+ hot_value = {"min": min_value, "max": max_value}
+ elif name == LENGTH:
+ hot_value = {"min": value, "max": value}
+ elif name == MIN_LENGTH:
+ hot_value = {"min": value}
+ elif name == MAX_LENGTH:
+ hot_value = {"max": value}
+ else:
+ hot_value = value
+ return hot_constraint, hot_value
diff --git a/tosca2heat/heat-translator/translator/hot/translate_node_templates.py b/tosca2heat/heat-translator/translator/hot/translate_node_templates.py
new file mode 100644
index 0000000..46cdd71
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/translate_node_templates.py
@@ -0,0 +1,483 @@
+#
+# 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.
+
+import importlib
+import logging
+import os
+import six
+
+from toscaparser.functions import GetAttribute
+from toscaparser.functions import GetInput
+from toscaparser.functions import GetProperty
+from toscaparser.properties import Property
+from toscaparser.relationship_template import RelationshipTemplate
+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.conf.config import ConfigProvider as translatorConfig
+from translator.hot.syntax.hot_resource import HotResource
+from translator.hot.tosca.tosca_block_storage_attachment import (
+ ToscaBlockStorageAttachment
+ )
+
+###########################
+# Module utility Functions
+# for dynamic class loading
+###########################
+
+
+def _generate_type_map():
+ '''Generate TOSCA translation types map.
+
+ Load user defined classes from location path specified in conf file.
+ Base classes are located within the tosca directory.
+
+ '''
+
+ # Base types directory
+ BASE_PATH = 'translator/hot/tosca'
+
+ # Custom types directory defined in conf file
+ custom_path = translatorConfig.get_value('DEFAULT',
+ 'custom_types_location')
+
+ # First need to load the parent module, for example 'contrib.hot',
+ # for all of the dynamically loaded classes.
+ classes = []
+ _load_classes((BASE_PATH, custom_path), classes)
+ try:
+ types_map = {clazz.toscatype: clazz for clazz in classes}
+ except AttributeError as e:
+ raise ToscaClassAttributeError(message=e.message)
+
+ return types_map
+
+
+def _load_classes(locations, classes):
+ '''Dynamically load all the classes from the given locations.'''
+
+ for cls_path in locations:
+ # Use the absolute path of the class path
+ abs_path = os.path.dirname(os.path.abspath(__file__))
+ abs_path = abs_path.replace('translator/hot', cls_path)
+
+ # Grab all the tosca type module files in the given path
+ mod_files = [f for f in os.listdir(abs_path) if f.endswith('.py')
+ and not f.startswith('__init__')
+ and f.startswith('tosca_')]
+
+ # For each module, pick out the target translation class
+ for f in mod_files:
+ # NOTE: For some reason the existing code does not use the map to
+ # instantiate ToscaBlockStorageAttachment. Don't add it to the map
+ # here until the dependent code is fixed to use the map.
+ if f == 'tosca_block_storage_attachment.py':
+ continue
+
+ mod_name = cls_path + '/' + f.strip('.py')
+ mod_name = mod_name.replace('/', '.')
+ try:
+ mod = importlib.import_module(mod_name)
+ target_name = getattr(mod, 'TARGET_CLASS_NAME')
+ clazz = getattr(mod, target_name)
+ classes.append(clazz)
+ except ImportError:
+ raise ToscaModImportError(mod_name=mod_name)
+ except AttributeError:
+ if target_name:
+ raise ToscaClassImportError(name=target_name,
+ mod_name=mod_name)
+ else:
+ # TARGET_CLASS_NAME is not defined in module.
+ # Re-raise the exception
+ raise
+
+##################
+# Module constants
+##################
+
+SECTIONS = (TYPE, PROPERTIES, REQUIREMENTS, INTERFACES, LIFECYCLE, INPUT) = \
+ ('type', 'properties', 'requirements',
+ 'interfaces', 'lifecycle', 'input')
+
+# TODO(anyone): the following requirement names should not be hard-coded
+# in the translator. Since they are basically arbitrary names, we have to get
+# them from TOSCA type definitions.
+# To be fixed with the blueprint:
+# https://blueprints.launchpad.net/heat-translator/+spec/tosca-custom-types
+REQUIRES = (CONTAINER, DEPENDENCY, DATABASE_ENDPOINT, CONNECTION, HOST) = \
+ ('container', 'dependency', 'database_endpoint',
+ 'connection', 'host')
+
+INTERFACES_STATE = (CREATE, START, CONFIGURE, START, DELETE) = \
+ ('create', 'stop', 'configure', 'start', 'delete')
+
+
+TOSCA_TO_HOT_REQUIRES = {'container': 'server', 'host': 'server',
+ 'dependency': 'depends_on', "connects": 'depends_on'}
+
+TOSCA_TO_HOT_PROPERTIES = {'properties': 'input'}
+log = logging.getLogger('heat-translator')
+
+TOSCA_TO_HOT_TYPE = _generate_type_map()
+
+
+class TranslateNodeTemplates(object):
+ '''Translate TOSCA NodeTemplates to Heat Resources.'''
+
+ def __init__(self, tosca, hot_template):
+ self.tosca = tosca
+ self.nodetemplates = self.tosca.nodetemplates
+ self.hot_template = hot_template
+ # 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
+
+ def translate(self):
+ return self._translate_nodetemplates()
+
+ def _recursive_handle_properties(self, resource):
+ '''Recursively handle the properties of the depends_on_nodes nodes.'''
+ # Use of hashtable (dict) here should be faster?
+ if resource in self.processed_resources:
+ return
+ self.processed_resources.append(resource)
+ for depend_on in resource.depends_on_nodes:
+ self._recursive_handle_properties(depend_on)
+
+ if resource.type == "OS::Nova::ServerGroup":
+ resource.handle_properties(self.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)
+ 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":
+ volume_name = None
+ requirements = node.requirements
+ if requirements:
+ # Find the name of associated BlockStorage node
+ for requires in requirements:
+ for value in requires.values():
+ if isinstance(value, dict):
+ for node_name in value.values():
+ for n in self.nodetemplates:
+ if n.name == node_name:
+ volume_name = node_name
+ break
+ else: # unreachable code !
+ for n in self.nodetemplates:
+ if n.name == node_name:
+ volume_name = node_name
+ break
+
+ suffix = suffix + 1
+ 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:
+ if (i.name == 'key_name' and
+ node.get_property_value('key_name') is None):
+ schema = {'type': i.type, 'default': i.default}
+ value = {"get_param": "key_name"}
+ prop = Property(i.name, value, schema)
+ node._properties.append(prop)
+
+ for policy in self.policies:
+ policy_type = policy.type_definition
+ policy_node = TOSCA_TO_HOT_TYPE[policy_type.type](policy)
+ self.hot_resources.append(policy_node)
+
+ # Handle life cycle operations: this may expand each node
+ # 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
+ self.hot_resources += lifecycle_resources
+
+ # Handle configuration from ConnectsTo relationship in the TOSCA node:
+ # this will generate multiple HOT resources, set of 2 for each
+ # configuration
+ connectsto_resources = []
+ for node in self.nodetemplates:
+ for requirement in node.requirements:
+ for endpoint, details in six.iteritems(requirement):
+ relation = None
+ if isinstance(details, dict):
+ target = details.get('node')
+ relation = details.get('relationship')
+ else:
+ target = details
+ if (target and relation and
+ not isinstance(relation, six.string_types)):
+ interfaces = relation.get('interfaces')
+ connectsto_resources += \
+ self._create_connect_configs(node,
+ target,
+ interfaces)
+ self.hot_resources += connectsto_resources
+
+ # Copy the initial dependencies based on the relationship in
+ # the TOSCA template
+ for node in self.nodetemplates:
+ for node_depend in node.related_nodes:
+ # 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 \
+ node.related[node_depend].type == \
+ node.type_definition.HOSTEDON:
+ self.hot_lookup[node].properties['server'] = \
+ {'get_resource': self.hot_lookup[node_depend].name}
+ # for all others, add dependency as depends_on
+ else:
+ self.hot_lookup[node].depends_on.append(
+ self.hot_lookup[node_depend].top_of_chain())
+
+ self.hot_lookup[node].depends_on_nodes.append(
+ self.hot_lookup[node_depend].top_of_chain())
+
+ # handle hosting relationship
+ for resource in self.hot_resources:
+ resource.handle_hosting()
+
+ # handle built-in properties of HOT resources
+ # if a resource depends on other resources,
+ # their properties need to be handled first.
+ # Use recursion to handle the properties of the
+ # dependent nodes in correct order
+ self.processed_resources = []
+ for resource in self.hot_resources:
+ self._recursive_handle_properties(resource)
+
+ # handle resources that need to expand to more than one HOT resource
+ expansion_resources = []
+ for resource in self.hot_resources:
+ expanded = resource.handle_expansion()
+ if expanded:
+ expansion_resources += expanded
+ self.hot_resources += expansion_resources
+
+ # Resolve function calls: GetProperty, GetAttribute, GetInput
+ # at this point, all the HOT resources should have been created
+ # in the graph.
+ for resource in self.hot_resources:
+ # 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)
+
+ return self.hot_resources
+
+ def _translate_input(self, input_value, resource):
+ get_property_args = None
+ if isinstance(input_value, GetProperty):
+ get_property_args = input_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 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):
+ # 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]}
+ else:
+ return {'get_param': input_value.args}
+
+ return input_value
+
+ @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)
+
+ return artifacts
+
+ def _get_attachment_node(self, node, suffix, volume_name):
+ attach = False
+ ntpl = self.nodetemplates
+ for key, value in node.relationships.items():
+ if key.is_derived_from('tosca.relationships.AttachesTo'):
+ if value.is_derived_from('tosca.nodes.BlockStorage'):
+ attach = True
+ if attach:
+ relationship_tpl = None
+ for req in node.requirements:
+ for key, val in req.items():
+ attach = val
+ relship = val.get('relationship')
+ for rkey, rval in val.items():
+ if relship and isinstance(relship, dict):
+ for rkey, rval in relship.items():
+ if rkey == 'type':
+ relationship_tpl = val
+ attach = rval
+ elif rkey == 'template':
+ rel_tpl_list = \
+ (self.tosca.topology_template.
+ _tpl_relationship_templates())
+ relationship_tpl = rel_tpl_list[rval]
+ attach = rval
+ else:
+ continue
+ elif isinstance(relship, str):
+ attach = relship
+ relationship_tpl = val
+ relationship_templates = \
+ self.tosca._tpl_relationship_templates()
+ if 'relationship' in relationship_tpl and \
+ attach not in \
+ self.tosca._tpl_relationship_types() and \
+ attach in relationship_templates:
+ relationship_tpl['relationship'] = \
+ relationship_templates[attach]
+ break
+ if relationship_tpl:
+ rval_new = attach + "_" + str(suffix)
+ att = RelationshipTemplate(
+ relationship_tpl, rval_new,
+ self.tosca._tpl_relationship_types())
+ hot_node = ToscaBlockStorageAttachment(att, ntpl,
+ node.name,
+ volume_name
+ )
+ return hot_node
+
+ def find_hot_resource(self, name):
+ for resource in self.hot_resources:
+ 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):
+ 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]
+
+ return None
+
+ def _create_connect_configs(self, source_node, target_name,
+ connect_interfaces):
+ connectsto_resources = []
+ if connect_interfaces:
+ for iname, interface in six.iteritems(connect_interfaces):
+ connectsto_resources += \
+ self._create_connect_config(source_node, target_name,
+ interface)
+ return connectsto_resources
+
+ def _create_connect_config(self, source_node, target_name,
+ connect_interface):
+ connectsto_resources = []
+ target_node = self._find_tosca_node(target_name)
+ # the configuration can occur on the source or the target
+ connect_config = connect_interface.get('pre_configure_target')
+ if connect_config is not None:
+ config_location = 'target'
+ else:
+ connect_config = connect_interface.get('pre_configure_source')
+ if connect_config is not None:
+ config_location = 'source'
+ else:
+ msg = _("Template error: "
+ "no configuration found for ConnectsTo "
+ "in {1}").format(self.nodetemplate.name)
+ log.error(msg)
+ raise Exception(msg)
+ config_name = source_node.name + '_' + target_name + '_connect_config'
+ implement = connect_config.get('implementation')
+ if config_location == 'target':
+ hot_config = HotResource(target_node,
+ config_name,
+ 'OS::Heat::SoftwareConfig',
+ {'config': {'get_file': implement}})
+ elif config_location == 'source':
+ hot_config = HotResource(source_node,
+ config_name,
+ 'OS::Heat::SoftwareConfig',
+ {'config': {'get_file': implement}})
+ 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)
+ connectsto_resources.append(hot_config.
+ handle_connectsto(source_node,
+ target_node,
+ hot_source,
+ hot_target,
+ config_location,
+ connect_interface))
+ return connectsto_resources
diff --git a/tosca2heat/heat-translator/translator/hot/translate_outputs.py b/tosca2heat/heat-translator/translator/hot/translate_outputs.py
new file mode 100644
index 0000000..4197cdd
--- /dev/null
+++ b/tosca2heat/heat-translator/translator/hot/translate_outputs.py
@@ -0,0 +1,48 @@
+#
+# 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.
+
+import logging
+
+from toscaparser.utils.gettextutils import _
+from translator.hot.syntax.hot_output import HotOutput
+
+log = logging.getLogger('heat-translator')
+
+
+class TranslateOutputs(object):
+ '''Translate TOSCA Outputs to Heat Outputs.'''
+
+ def __init__(self, outputs, node_translator):
+ log.debug(_('Translating TOSCA outputs to HOT outputs.'))
+ self.outputs = outputs
+ self.nodes = node_translator
+
+ def translate(self):
+ return self._translate_outputs()
+
+ 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))
+ return hot_outputs