summaryrefslogtreecommitdiffstats
path: root/tosca2heat/heat-translator-0.3.0/translator/hot/translate_node_templates.py
diff options
context:
space:
mode:
Diffstat (limited to 'tosca2heat/heat-translator-0.3.0/translator/hot/translate_node_templates.py')
-rw-r--r--tosca2heat/heat-translator-0.3.0/translator/hot/translate_node_templates.py408
1 files changed, 408 insertions, 0 deletions
diff --git a/tosca2heat/heat-translator-0.3.0/translator/hot/translate_node_templates.py b/tosca2heat/heat-translator-0.3.0/translator/hot/translate_node_templates.py
new file mode 100644
index 0000000..152603d
--- /dev/null
+++ b/tosca2heat/heat-translator-0.3.0/translator/hot/translate_node_templates.py
@@ -0,0 +1,408 @@
+#
+# 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.relationship_template import RelationshipTemplate
+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
+ self.hot_lookup = {}
+
+ 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)
+
+ resource.handle_properties()
+
+ def _translate_nodetemplates(self):
+
+ suffix = 0
+ # Copy the TOSCA graph: nodetemplate
+ for node in self.nodetemplates:
+ hot_node = TOSCA_TO_HOT_TYPE[node.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 node.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)
+
+ # 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):
+ if isinstance(value, GetAttribute):
+ # for the attribute
+ # get the proper target type to perform the translation
+ args = value.result()
+ target = args[0]
+ hot_target = self.find_hot_resource(target)
+
+ inputs[name] = hot_target.get_hot_attribute(args[1],
+ args)
+ else:
+ if isinstance(value, GetProperty) or \
+ isinstance(value, GetInput):
+ inputs[name] = value.result()
+
+ return self.hot_resources
+
+ 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):
+ for node in self.nodetemplates:
+ if node.name == tosca_name:
+ return self.hot_lookup[node]
+
+ 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.warning(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