diff options
author | 2015-12-22 15:04:53 +0800 | |
---|---|---|
committer | 2015-12-22 15:04:53 +0800 | |
commit | b678c5bfea0f1b142c2a4a5bdee1e3309a021887 (patch) | |
tree | 493ec289254370057daaaded00fd1b4761783c86 /tosca2heat/heat-translator-0.3.0/translator/hot/translate_node_templates.py | |
parent | 828f613ea0c3e334eacf3f7857252246708d1c88 (diff) |
JIRA:PARSER-15 Provide Standalone Heat-Translator Liberty Pypi Packages
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.py | 408 |
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 |