summaryrefslogtreecommitdiffstats
path: root/tosca2heat/heat-translator/translator/hot/syntax
diff options
context:
space:
mode:
Diffstat (limited to 'tosca2heat/heat-translator/translator/hot/syntax')
-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
5 files changed, 522 insertions, 0 deletions
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