RIFT OSM R1 Initial Submission
[osm/SO.git] / common / python / rift / mano / yang_translator / rwmano / yang / yang_nsd.py
diff --git a/common/python/rift/mano/yang_translator/rwmano/yang/yang_nsd.py b/common/python/rift/mano/yang_translator/rwmano/yang/yang_nsd.py
new file mode 100644 (file)
index 0000000..491bd86
--- /dev/null
@@ -0,0 +1,396 @@
+# Copyright 2016 RIFT.io Inc
+#
+# 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 copy import deepcopy
+import os
+
+from rift.mano.yang_translator.common.exception import ValidationError
+from rift.mano.yang_translator.common.utils import _
+from rift.mano.yang_translator.rwmano.syntax.tosca_resource \
+    import ToscaResource
+from rift.mano.yang_translator.rwmano.yang.yang_vld import YangVld
+
+TARGET_CLASS_NAME = 'YangNsd'
+
+
+class YangNsd(ToscaResource):
+    '''Class for RIFT.io YANG NS descriptor translation to TOSCA type.'''
+
+    yangtype = 'nsd'
+
+    OTHER_FIELDS = (SCALE_GRP, CONF_PRIM,
+                    USER_DEF_SCRIPT, SCALE_ACT,
+                    TRIGGER, NS_CONF_PRIM_REF,
+                    CONST_VNFD, VNFD_MEMBERS,
+                    MIN_INST_COUNT, MAX_INST_COUNT,
+                    INPUT_PARAM_XPATH, CONFIG_ACTIONS,
+                    INITIAL_CFG,) = \
+                   ('scaling_group_descriptor', 'service_primitive',
+                    'user_defined_script', 'scaling_config_action',
+                    'trigger', 'ns_config_primitive_name_ref',
+                    'constituent_vnfd', 'vnfd_member',
+                    'min_instance_count', 'max_instance_count',
+                    'input_parameter_xpath', 'config_actions',
+                    'initial_config_primitive',)
+
+    def __init__(self,
+                 log,
+                 name,
+                 type_,
+                 yang):
+        super(YangNsd, self).__init__(log,
+                                      name,
+                                      type_,
+                                      yang)
+        self.props = {}
+        self.inputs = []
+        self.vnfds = {}
+        self.vlds = []
+        self.conf_prims = []
+        self.scale_grps = []
+        self.initial_cfg = []
+
+    def handle_yang(self, vnfds):
+        self.log.debug(_("Process NSD desc {0}: {1}").
+                       format(self.name, self.yang))
+
+        def process_input_param(param):
+            if self.XPATH in param:
+                val = param.pop(self.XPATH)
+                # Strip namesapce, catalog and nsd part
+                self.inputs.append({
+                    self.NAME:
+                    self.map_yang_name_to_tosca(
+                        val.replace('/nsd:nsd-catalog/nsd:nsd/nsd:', ''))})
+            if len(param):
+                self.log.warn(_("{0}, Did not process the following for "
+                                "input param {1}: {2}").
+                              format(self, self.inputs, param))
+            self.log.debug(_("{0}, inputs: {1}").format(self, self.inputs[-1]))
+
+        def process_const_vnfd(cvnfd):
+            # Get the matching VNFD
+            vnfd_id = cvnfd.pop(self.VNFD_ID_REF)
+            for vnfd in vnfds:
+                if vnfd.type == self.VNFD and vnfd.id == vnfd_id:
+                    self.vnfds[cvnfd.pop(self.MEM_VNF_INDEX)] = vnfd
+                    if self.START_BY_DFLT in cvnfd:
+                        vnfd.props[self.START_BY_DFLT] = \
+                                            cvnfd.pop(self.START_BY_DFLT)
+                    break
+
+            if len(cvnfd):
+                self.log.warn(_("{0}, Did not process the following for "
+                                "constituent vnfd {1}: {2}").
+                              format(self, vnfd_id, cvnfd))
+            self.log.debug(_("{0}, VNFD: {1}").format(self, self.vnfds))
+
+        def process_scale_grp(dic):
+            sg = {}
+            self.log.debug(_("{0}, scale group: {1}").format(self, dic))
+            fields = [self.NAME, self.MIN_INST_COUNT, self.MAX_INST_COUNT]
+            for key in fields:
+                if key in dic:
+                    sg[key] = dic.pop(key)
+
+            membs = {}
+            for vnfd_memb in dic.pop(self.VNFD_MEMBERS):
+                vnfd_idx = vnfd_memb[self.MEM_VNF_INDEX_REF]
+                if vnfd_idx in self.vnfds:
+                        membs[self.vnfds[vnfd_idx].name] = \
+                                                    vnfd_memb[self.COUNT]
+            sg['vnfd_members'] = membs
+
+            trigs = {}
+            if self.SCALE_ACT in dic:
+                for sg_act in dic.pop(self.SCALE_ACT):
+                    # Validate the primitive
+                    prim = sg_act.pop(self.NS_CONF_PRIM_REF)
+                    for cprim in self.conf_prims:
+                        if cprim[self.NAME] == prim:
+                            trigs[sg_act.pop(self.TRIGGER)] = prim
+                            break
+                    if len(sg_act):
+                        err_msg = (_("{0}, Did not find config-primitive {1}").
+                                   format(self, prim))
+                        self.log.error(err_msg)
+                        raise ValidationError(message=err_msg)
+            sg[self.CONFIG_ACTIONS] = trigs
+
+            if len(dic):
+                self.log.warn(_("{0}, Did not process all fields for {1}").
+                              format(self, dic))
+            self.log.debug(_("{0}, Scale group {1}").format(self, sg))
+            self.scale_grps.append(sg)
+
+        def process_initial_config(dic):
+            icp = {}
+            self.log.debug(_("{0}, initial config: {1}").format(self, dic))
+            for key in [self.NAME, self.SEQ, self.USER_DEF_SCRIPT]:
+                if key in dic:
+                    icp[key] = dic.pop(key)
+
+            params = {}
+            if self.PARAM in dic:
+                for p in dic.pop(self.PARAM):
+                    if (self.NAME in p and
+                        self.VALUE in p):
+                        params[p[self.NAME]] = p[self.VALUE]
+                    else:
+                        # TODO (pjoseph): Need to add support to read the
+                        # config file and get the value from that
+                        self.log.warn(_("{0}, Got parameter without value: {1}").
+                                      format(self, p))
+                if len(params):
+                    icp[self.PARAM] = params
+
+            if len(dic):
+                self.log.warn(_("{0}, Did not process all fields for {1}").
+                              format(self, dic))
+            self.log.debug(_("{0}, Initial config {1}").format(self, icp))
+            self.initial_cfg.append(icp)
+
+        dic = deepcopy(self.yang)
+        try:
+            for key in self.REQUIRED_FIELDS:
+                self.props[key] = dic.pop(key)
+
+            self.id = self.props[self.ID]
+
+            # Process constituent VNFDs
+            if self.CONST_VNFD in dic:
+                for cvnfd in dic.pop(self.CONST_VNFD):
+                    process_const_vnfd(cvnfd)
+
+            # Process VLDs
+            if self.VLD in dic:
+                for vld_dic in dic.pop(self.VLD):
+                    vld = YangVld(self.log, vld_dic.pop(self.NAME),
+                                  self.VLD, vld_dic)
+                    vld.process_vld(self.vnfds)
+                    self.vlds.append(vld)
+
+            # Process config primitives
+            if self.CONF_PRIM in dic:
+                for cprim in dic.pop(self.CONF_PRIM):
+                    conf_prim = {self.NAME: cprim.pop(self.NAME)}
+                    if self.USER_DEF_SCRIPT in cprim:
+                        conf_prim[self.USER_DEF_SCRIPT] = \
+                                        cprim.pop(self.USER_DEF_SCRIPT)
+                        self.conf_prims.append(conf_prim)
+                    else:
+                        err_msg = (_("{0}, Only user defined script supported "
+                                     "in config-primitive for now {}: {}").
+                                   format(self, conf_prim, cprim))
+                        self.log.error(err_msg)
+                        raise ValidationError(message=err_msg)
+
+            # Process scaling group
+            if self.SCALE_GRP in dic:
+                for sg_dic in dic.pop(self.SCALE_GRP):
+                    process_scale_grp(sg_dic)
+
+            # Process initial config primitives
+            if self.INITIAL_CFG in dic:
+                for icp_dic in dic.pop(self.INITIAL_CFG):
+                    process_initial_config(icp_dic)
+
+            # Process the input params
+            if self.INPUT_PARAM_XPATH in dic:
+                for param in dic.pop(self.INPUT_PARAM_XPATH):
+                    process_input_param(param)
+
+            self.remove_ignored_fields(dic)
+            if len(dic):
+                self.log.warn(_("{0}, Did not process the following for "
+                                "NSD {1}: {2}").
+                              format(self, self.props, dic))
+            self.log.debug(_("{0}, NSD: {1}").format(self, self.props))
+        except Exception as e:
+            err_msg = _("Exception processing NSD {0} : {1}"). \
+                      format(self.name, e)
+            self.log.error(err_msg)
+            self.log.exception(e)
+            raise ValidationError(message=err_msg)
+
+    def generate_tosca_type(self):
+        self.log.debug(_("{0} Generate tosa types").
+                       format(self))
+
+        tosca = {}
+        tosca[self.DATA_TYPES] = {}
+        tosca[self.NODE_TYPES] = {}
+
+        for idx, vnfd in self.vnfds.items():
+            tosca = vnfd.generate_tosca_type(tosca)
+
+        for vld in self.vlds:
+            tosca = vld.generate_tosca_type(tosca)
+
+        # Generate type for config primitives
+        if self.GROUP_TYPES not in tosca:
+            tosca[self.GROUP_TYPES] = {}
+        if self.T_CONF_PRIM not in tosca[self.GROUP_TYPES]:
+            tosca[self.GROUP_TYPES][self.T_CONF_PRIM] = {
+                self.DERIVED_FROM: 'tosca.policies.Root',
+                self.PROPERTIES: {
+                    'primitive': self.MAP
+                }}
+
+        # Generate type for scaling group
+        if self.POLICY_TYPES not in tosca:
+            tosca[self.POLICY_TYPES] = {}
+        if self.T_SCALE_GRP not in tosca[self.POLICY_TYPES]:
+            tosca[self.POLICY_TYPES][self.T_SCALE_GRP] = {
+                self.DERIVED_FROM: 'tosca.policies.Root',
+                self.PROPERTIES: {
+                    self.NAME:
+                    {self.TYPE: self.STRING},
+                    self.MAX_INST_COUNT:
+                    {self.TYPE: self.INTEGER},
+                    self.MIN_INST_COUNT:
+                    {self.TYPE: self.INTEGER},
+                    'vnfd_members':
+                    {self.TYPE: self.MAP},
+                    self.CONFIG_ACTIONS:
+                    {self.TYPE: self.MAP}
+                }}
+
+        if self.T_INITIAL_CFG not in tosca[self.POLICY_TYPES]:
+            tosca[self.POLICY_TYPES][self.T_INITIAL_CFG] = {
+                self.DERIVED_FROM: 'tosca.policies.Root',
+                self.PROPERTIES: {
+                    self.NAME:
+                    {self.TYPE: self.STRING},
+                    self.SEQ:
+                    {self.TYPE: self.INTEGER},
+                    self.USER_DEF_SCRIPT:
+                    {self.TYPE: self.STRING},
+                    self.PARAM:
+                    {self.TYPE: self.MAP},
+                }}
+
+        return tosca
+
+    def generate_tosca_template(self, tosca):
+        self.log.debug(_("{0}, Generate tosca template").
+                       format(self, tosca))
+
+        # Add the standard entries
+        tosca['tosca_definitions_version'] = \
+                                    'tosca_simple_profile_for_nfv_1_0_0'
+        tosca[self.DESC] = self.props[self.DESC]
+        tosca[self.METADATA] = {
+            'ID': self.name,
+            self.VENDOR: self.props[self.VENDOR],
+            self.VERSION: self.props[self.VERSION],
+        }
+
+        tosca[self.TOPOLOGY_TMPL] = {}
+
+        # Add input params
+        if len(self.inputs):
+            if self.INPUTS not in tosca[self.TOPOLOGY_TMPL]:
+                tosca[self.TOPOLOGY_TMPL][self.INPUTS] = {}
+            for inp in self.inputs:
+                entry = {inp[self.NAME]: {self.TYPE: self.STRING,
+                                          self.DESC:
+                                          'Translated from YANG'}}
+                tosca[self.TOPOLOGY_TMPL][self.INPUTS] = entry
+
+        tosca[self.TOPOLOGY_TMPL][self.NODE_TMPL] = {}
+
+        # Add the VNFDs and VLDs
+        for idx, vnfd in self.vnfds.items():
+            vnfd.generate_vnf_template(tosca, idx)
+
+        for vld in self.vlds:
+            vld.generate_tosca_template(tosca)
+
+        # add the config primitives
+        if len(self.conf_prims):
+            if self.GROUPS not in tosca[self.TOPOLOGY_TMPL]:
+                tosca[self.TOPOLOGY_TMPL][self.GROUPS] = {}
+
+            conf_prims = {
+                self.TYPE: self.T_CONF_PRIM
+            }
+            conf_prims[self.MEMBERS] = [vnfd.name for vnfd in
+                                        self.vnfds.values()]
+            prims = {}
+            for confp in self.conf_prims:
+                prims[confp[self.NAME]] = {
+                    self.USER_DEF_SCRIPT: confp[self.USER_DEF_SCRIPT]
+                }
+            conf_prims[self.PROPERTIES] = {
+                self.PRIMITIVES: prims
+            }
+
+            tosca[self.TOPOLOGY_TMPL][self.GROUPS][self.CONF_PRIM] = conf_prims
+
+
+        # Add the scale group
+        if len(self.scale_grps):
+            if self.POLICIES not in tosca[self.TOPOLOGY_TMPL]:
+                tosca[self.TOPOLOGY_TMPL][self.POLICIES] = []
+
+            for sg in self.scale_grps:
+                sgt = {
+                    self.TYPE: self.T_SCALE_GRP,
+                }
+                sgt.update(sg)
+                tosca[self.TOPOLOGY_TMPL][self.POLICIES].append({
+                    self.SCALE_GRP: sgt
+                })
+
+        # Add initial configs
+        if len(self.initial_cfg):
+            if self.POLICIES not in tosca[self.TOPOLOGY_TMPL]:
+                tosca[self.TOPOLOGY_TMPL][self.POLICIES] = []
+
+            for icp in self.initial_cfg:
+                icpt = {
+                    self.TYPE: self.T_INITIAL_CFG,
+                }
+                icpt.update(icp)
+                tosca[self.TOPOLOGY_TMPL][self.POLICIES].append({
+                    self.INITIAL_CFG: icpt
+                })
+
+        return tosca
+
+    def get_supporting_files(self):
+        files = []
+
+        for vnfd in self.vnfds.values():
+            f = vnfd.get_supporting_files()
+            if f and len(f):
+                files.extend(f)
+
+        # Get the config files for initial config
+        for icp in self.initial_cfg:
+            if self.USER_DEF_SCRIPT in icp:
+                script = os.path.basename(icp[self.USER_DEF_SCRIPT])
+                files.append({
+                    self.TYPE: 'script',
+                    self.NAME: script,
+                    self.DEST: "{}/{}".format(self.SCRIPT_DIR, script),
+                })
+
+        # TODO (pjoseph): Add support for config scripts,
+        # charms, etc
+
+        self.log.debug(_("{0}, supporting files: {1}").format(self, files))
+        return files