RIFT OSM R1 Initial Submission
[osm/SO.git] / common / python / rift / mano / tosca_translator / rwmano / translate_node_templates.py
diff --git a/common/python/rift/mano/tosca_translator/rwmano/translate_node_templates.py b/common/python/rift/mano/tosca_translator/rwmano/translate_node_templates.py
new file mode 100644 (file)
index 0000000..dbfaa62
--- /dev/null
@@ -0,0 +1,328 @@
+#
+# 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.
+
+# Copyright 2016 RIFT.io Inc
+
+
+import importlib
+import os
+
+from rift.mano.tosca_translator.common.utils import _
+from rift.mano.tosca_translator.common.exception import ToscaClassAttributeError
+from rift.mano.tosca_translator.common.exception import ToscaClassImportError
+from rift.mano.tosca_translator.common.exception import ToscaModImportError
+from rift.mano.tosca_translator.conf.config import ConfigProvider as translatorConfig
+from rift.mano.tosca_translator.rwmano.syntax.mano_resource import ManoResource
+
+
+class TranslateNodeTemplates(object):
+    '''Translate TOSCA NodeTemplates to RIFT.io MANO Resources.'''
+
+    ##################
+    # Module constants
+    ##################
+
+    TOSCA_TO_MANO_REQUIRES = {'container': 'server',
+                              'host': 'server',
+                              'dependency': 'depends_on',
+                              'connects': 'depends_on'}
+
+    TOSCA_TO_MANO_PROPERTIES = {'properties': 'input'}
+
+    TOSCA_TO_MANO_TYPE = None
+
+    ###########################
+    # Module utility Functions
+    # for dynamic class loading
+    ###########################
+
+    def _load_classes(log, 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('rift/mano/tosca_translator/rwmano', cls_path)
+            log.debug(_("Loading classes from %s") % abs_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.rstrip('.py')
+                # Above have an issue if the mod name ends with p or y
+                f_name, ext = f.rsplit('.', 1)
+                mod_name = cls_path + '/' + f_name
+                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
+
+    def _generate_type_map(log):
+        '''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 = 'rift/mano/tosca_translator/rwmano/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.mano',
+        # for all of the dynamically loaded classes.
+        classes = []
+        TranslateNodeTemplates._load_classes(log,
+                                             (BASE_PATH, custom_path),
+                                             classes)
+        try:
+            types_map = {clazz.toscatype: clazz for clazz in classes}
+            log.debug(_("Type maps loaded: {}").format(types_map.keys()))
+        except AttributeError as e:
+            raise ToscaClassAttributeError(message=e.message)
+
+        return types_map
+
+    def __init__(self, log, tosca, mano_template):
+        self.log = log
+        self.tosca = tosca
+        self.nodetemplates = self.tosca.nodetemplates
+        self.mano_template = mano_template
+        # list of all MANO resources generated
+        self.mano_resources = []
+        self.mano_policies = []
+        self.mano_groups = []
+        # mapping between TOSCA nodetemplate and MANO resource
+        log.debug(_('Mapping between TOSCA nodetemplate and MANO resource.'))
+        self.mano_lookup = {}
+        self.policies = self.tosca.topology_template.policies
+        self.groups = self.tosca.topology_template.groups
+        self.metadata = {}
+
+    def translate(self):
+        if TranslateNodeTemplates.TOSCA_TO_MANO_TYPE is None:
+            TranslateNodeTemplates.TOSCA_TO_MANO_TYPE = \
+                TranslateNodeTemplates._generate_type_map(self.log)
+        # Translate metadata
+        self.translate_metadata()
+        return self._translate_nodetemplates()
+
+    def translate_metadata(self):
+        """Translate and store the metadata in instance"""
+        FIELDS_MAP = {
+            'ID': 'name',
+            'vendor': 'vendor',
+            'version': 'version',
+        }
+        metadata = {}
+        # Initialize to default values
+        metadata['name'] = 'tosca_to_mano'
+        metadata['vendor'] = 'RIFT.io'
+        metadata['version'] = '1.0'
+        if 'metadata' in self.tosca.tpl:
+            tosca_meta = self.tosca.tpl['metadata']
+            for key in FIELDS_MAP:
+                if key in tosca_meta.keys():
+                    metadata[FIELDS_MAP[key]] = str(tosca_meta[key])
+        self.log.debug(_("Metadata {0}").format(metadata))
+        self.metadata = metadata
+
+    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.mano_resources)
+        else:
+            resource.handle_properties()
+
+    def _get_policy_type(self, policy):
+        if isinstance(policy, dict):
+            for key, details in policy.items():
+                if 'type' in details:
+                    return details['type']
+
+    def _translate_nodetemplates(self):
+
+        self.log.debug(_('Translating the node templates.'))
+        # Copy the TOSCA graph: nodetemplate
+        tpl = self.tosca.tpl['topology_template']['node_templates']
+        for node in self.nodetemplates:
+            base_type = ManoResource.get_base_type(node.type_definition)
+            self.log.debug(_("Translate node %(name)s of type %(type)s with "
+                             "base %(base)s") %
+                           {'name': node.name,
+                            'type': node.type,
+                            'base': base_type.type})
+            mano_node = TranslateNodeTemplates. \
+                        TOSCA_TO_MANO_TYPE[base_type.type](
+                            self.log,
+                            node,
+                            metadata=self.metadata)
+            # Currently tosca-parser does not add the artifacts
+            # to the node
+            if mano_node.name in tpl:
+                tpl_node = tpl[mano_node.name]
+                self.log.debug("Check artifacts for {}".format(tpl_node))
+                if 'artifacts' in tpl_node:
+                    mano_node.artifacts = tpl_node['artifacts']
+            self.mano_resources.append(mano_node)
+            self.mano_lookup[node] = mano_node
+
+        # The parser currently do not generate the objects for groups
+        if 'groups' in self.tosca.tpl['topology_template']:
+            tpl = self.tosca.tpl['topology_template']['groups']
+            self.log.debug("Groups: {}".format(tpl))
+            for group, details in tpl.items():
+                self.log.debug(_("Translate group {}: {}").
+                               format(group, details))
+                group_type = details['type']
+                if group_type:
+                    group_node = TranslateNodeTemplates. \
+                                 TOSCA_TO_MANO_TYPE[group_type](
+                                     self.log,
+                                     group,
+                                     details,
+                                     metadata=self.metadata)
+                    self.mano_groups.append(group_node)
+
+        # The parser currently do not generate the objects for policies
+        if 'policies' in self.tosca.tpl['topology_template']:
+            tpl = self.tosca.tpl['topology_template']['policies']
+            # for policy in self.policies:
+            for policy in tpl:
+                self.log.debug(_("Translate policy {}").
+                               format(policy))
+                policy_type = self._get_policy_type(policy)
+                if policy_type:
+                    policy_node = TranslateNodeTemplates. \
+                                  TOSCA_TO_MANO_TYPE[policy_type](
+                                      self.log,
+                                      policy,
+                                      metadata=self.metadata)
+                    self.mano_policies.append(policy_node)
+
+        for node in self.mano_resources:
+            self.log.debug(_("Handle properties for {0} of type {1}").
+                           format(node.name, node.type_))
+            node.handle_properties()
+
+            self.log.debug(_("Handle capabilites for {0} of type {1}").
+                           format(node.name, node.type_))
+            node.handle_capabilities()
+
+            self.log.debug(_("Handle aritfacts for {0} of type {1}").
+                           format(node.name, node.type_))
+            node.handle_artifacts()
+
+            self.log.debug(_("Handle interfaces for {0} of type {1}").
+                           format(node.name, node.type_))
+            node.handle_interfaces()
+
+            self.log.debug(_("Update image checksum for {0} of type {1}").
+                           format(node.name, node.type_))
+            node.update_image_checksum(self.tosca.path)
+
+        for node in self.mano_resources:
+            # Handle vnf and vdu dependencies first
+            if node.type == "vnfd":
+                try:
+                    self.log.debug(_("Handle requirements for {0} of "
+                                     "type {1}").
+                                   format(node.name, node.type_))
+                    node.handle_requirements(self.mano_resources)
+                except Exception as e:
+                    self.log.error(_("Exception for {0} in requirements {1}").
+                                   format(node.name, node.type_))
+                    self.log.exception(e)
+
+        for node in self.mano_resources:
+            # Now handle other dependencies
+            if node.type != "vnfd":
+                try:
+                    self.log.debug(_("Handle requirements for {0} of type {1}").
+                                   format(node.name, node.type_))
+                    node.handle_requirements(self.mano_resources)
+                except Exception as e:
+                    self.log.error(_("Exception for {0} in requirements {1}").
+                                   format(node.name, node.type_))
+                    self.log.exception(e)
+
+        return self.mano_resources
+
+    def translate_groups(self):
+        for group in self.mano_groups:
+            group.handle_properties(self.mano_resources)
+        return self.mano_groups
+
+    def translate_policies(self):
+        for policy in self.mano_policies:
+            policy.handle_properties(self.mano_resources, self.mano_groups)
+        return self.mano_policies
+
+    def find_mano_resource(self, name):
+        for resource in self.mano_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_mano_resource_for_tosca(self, tosca_name,
+                                      current_mano_resource=None):
+        if tosca_name == 'SELF':
+            return current_mano_resource
+        if tosca_name == 'HOST' and current_mano_resource is not None:
+            for req in current_mano_resource.nodetemplate.requirements:
+                if 'host' in req:
+                    return self._find_mano_resource_for_tosca(req['host'])
+
+        for node in self.nodetemplates:
+            if node.name == tosca_name:
+                return self.mano_lookup[node]
+
+        return None