RIFT OSM R1 Initial Submission
[osm/SO.git] / common / python / rift / mano / yang_translator / rwmano / yang / yang_vnfd.py
diff --git a/common/python/rift/mano/yang_translator/rwmano/yang/yang_vnfd.py b/common/python/rift/mano/yang_translator/rwmano/yang/yang_vnfd.py
new file mode 100644 (file)
index 0000000..7449c5a
--- /dev/null
@@ -0,0 +1,393 @@
+# 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
+
+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_vdu import YangVdu
+
+TARGET_CLASS_NAME = 'YangVnfd'
+
+
+class YangVnfd(ToscaResource):
+    '''Class for RIFT.io YANG VNF descriptor translation to TOSCA type.'''
+
+    yangtype = 'vnfd'
+
+    CONFIG_TYPES = ['script', 'netconf', 'rest', 'juju']
+
+    OTHER_KEYS = (MGMT_INTF, HTTP_EP, MON_PARAM) = \
+                 ('mgmt_interface', 'http_endpoint', 'monitoring_param')
+
+
+    def __init__(self,
+                 log,
+                 name,
+                 type_,
+                 yang):
+        super(YangVnfd, self).__init__(log,
+                                       name,
+                                       type_,
+                                       yang)
+        self.props = {}
+        self.vdus = []
+        self.mgmt_intf = {}
+        self.mon_param = []
+        self.http_ep = []
+
+    def handle_yang(self):
+        self.log.debug(_("Process VNFD desc {0}: {1}").format(self.name,
+                                                              self.yang))
+
+        def process_vnf_config(conf):
+            vnf_conf = {}
+            if self.CONFIG_ATTR in conf:
+                for key, value in conf.pop(self.CONFIG_ATTR).items():
+                    vnf_conf[key] = value
+
+            if self.CONFIG_TMPL in conf:
+                vnf_conf[self.CONFIG_TMPL] = conf.pop(self.CONFIG_TMPL)
+
+            def copy_config_details(conf_type, conf_details):
+                vnf_conf[self.CONFIG_TYPE] = conf_type
+                vnf_conf[self.CONFIG_DETAILS] = conf_details
+
+            for key in self.CONFIG_TYPES:
+                if key in conf:
+                    copy_config_details(key, conf.pop(key))
+                    break
+
+            if len(conf):
+                self.log.warn(_("{0}, Did not process all in VNF "
+                                "configuration {1}").
+                              format(self, conf))
+            self.log.debug(_("{0}, vnf config: {1}").format(self, vnf_conf))
+            self.props[self.VNF_CONFIG] = vnf_conf
+
+        def process_mgmt_intf(intf):
+            if len(self.mgmt_intf) > 0:
+                err_msg(_("{0}, Already processed another mgmt intf {1}, "
+                          "got another {2}").
+                        format(self, self.msmg_intf, intf))
+                self.log.error(err_msg)
+                raise ValidationError(message=err_msg)
+
+            self.mgmt_intf['protocol'] = 'tcp'
+
+            if self.PORT in intf:
+                self.mgmt_intf[self.PORT] = intf.pop(self.PORT)
+                self.props[self.PORT] = self.mgmt_intf[self.PORT]
+
+            if 'vdu_id' in intf:
+                for vdu in self.vdus:
+                    if intf['vdu_id'] == vdu.id:
+                        self.mgmt_intf[self.VDU] = vdu.get_name(self.name)
+                        intf.pop('vdu_id')
+                        break
+
+            if self.DASHBOARD_PARAMS in intf:
+                self.mgmt_intf[self.DASHBOARD_PARAMS] = \
+                                            intf.pop(self.DASHBOARD_PARAMS)
+
+            if len(intf):
+                self.log.warn(_("{0}, Did not process all in mgmt "
+                                "interface {1}").
+                              format(self, intf))
+            self.log.debug(_("{0}, Management interface: {1}").
+                           format(self, self.mgmt_intf))
+
+        def process_http_ep(eps):
+            self.log.debug("{}, HTTP EP: {}".format(self, eps))
+            for ep in eps:
+                http_ep = {'protocol': 'http'}  # Required for TOSCA
+                http_ep[self.PATH] = ep.pop(self.PATH)
+                http_ep[self.PORT] = ep.pop(self.PORT)
+                http_ep[self.POLL_INTVL] = ep.pop(self.POLL_INTVL_SECS)
+                if len(ep):
+                    self.log.warn(_("{0}, Did not process the following for "
+                                    "http ep {1}").format(self, ep))
+                    self.log.debug(_("{0}, http endpoint: {1}").format(self, http_ep))
+                self.http_ep.append(http_ep)
+
+        def process_mon_param(params):
+            for param in params:
+                monp = {}
+                fields = [self.NAME, self.ID, 'value_type', 'units', 'group_tag',
+                          'json_query_method', 'http_endpoint_ref', 'widget_type',
+                          self.DESC]
+                for key in fields:
+                    if key in param:
+                        monp[key] = param.pop(key)
+
+                if len(param):
+                    self.log.warn(_("{0}, Did not process the following for "
+                                    "monitporing-param {1}").
+                                  format(self, param))
+                    self.log.debug(_("{0}, Monitoring param: {1}").format(self, monp))
+                self.mon_param.append(monp)
+
+        def process_cp(cps):
+            for cp_dic in cps:
+                self.log.debug("{}, CP: {}".format(self, cp_dic))
+                name = cp_dic.pop(self.NAME)
+                for vdu in self.vdus:
+                    if vdu.has_cp(name):
+                        vdu.set_cp_type(name, cp_dic.pop(self.TYPE_Y))
+                        break
+                if len(cp_dic):
+                    self.log.warn(_("{0}, Did not process the following for "
+                                    "connection-point {1}: {2}").
+                                  format(self, name, cp_dic))
+
+        ENDPOINTS_MAP = {
+            self.MGMT_INTF: process_mgmt_intf,
+            self.HTTP_EP:  process_http_ep,
+            self.MON_PARAM: process_mon_param,
+            'connection_point': process_cp
+        }
+
+        dic = deepcopy(self.yang)
+        try:
+            for key in self.REQUIRED_FIELDS:
+                self.props[key] = dic.pop(key)
+
+            self.id = self.props[self.ID]
+
+            # Process VDUs before CPs so as to update the CP struct in VDU
+            # when we process CP later
+            if self.VDU in dic:
+                for vdu_dic in dic.pop(self.VDU):
+                    vdu = YangVdu(self.log, vdu_dic.pop(self.NAME),
+                                  self.VDU, vdu_dic)
+                    vdu.process_vdu()
+                    self.vdus.append(vdu)
+
+            for key in ENDPOINTS_MAP.keys():
+                if key in dic:
+                    ENDPOINTS_MAP[key](dic.pop(key))
+
+            if self.VNF_CONFIG in dic:
+                process_vnf_config(dic.pop(self.VNF_CONFIG))
+
+            self.remove_ignored_fields(dic)
+            if len(dic):
+                self.log.warn(_("{0}, Did not process the following for "
+                                "VNFD: {1}").
+                              format(self, dic))
+            self.log.debug(_("{0}, VNFD: {1}").format(self, self.props))
+        except Exception as e:
+            err_msg = _("Exception processing VNFD {0} : {1}"). \
+                      format(self.name, e)
+            self.log.error(err_msg)
+            raise ValidationError(message=err_msg)
+
+    def update_cp_vld(self, cp_name, vld_name):
+        for vdu in self.vdus:
+            cp = vdu.get_cp(cp_name)
+            if cp:
+                vdu.set_vld(cp_name, vld_name)
+                break
+
+    def generate_tosca_type(self, tosca):
+        self.log.debug(_("{0} Generate tosa types").
+                       format(self))
+
+        for vdu in self.vdus:
+            tosca = vdu.generate_tosca_type(tosca)
+
+        # Add data_types
+        if self.T_VNF_CONFIG not in tosca[self.DATA_TYPES]:
+            tosca[self.DATA_TYPES][self.T_VNF_CONFIG] = {
+                self.PROPERTIES:
+                {self.CONFIG_TYPE:
+                 {self.TYPE: self.STRING},
+                 'config_delay':
+                 {self.TYPE: self.INTEGER,
+                  self.DEFAULT: 0,
+                  self.REQUIRED: self.NO,
+                  self.CONSTRAINTS:
+                  [{'greater_or_equal': 0}]},
+                 'config_priority':
+                 {self.TYPE: self.INTEGER,
+                  self.CONSTRAINTS:
+                  [{'greater_than': 0}]},
+                 self.CONFIG_DETAILS:
+                 {self.TYPE: self.MAP},
+                 self.CONFIG_TMPL:
+                 {self.TYPE: self.STRING,
+                  self.REQUIRED: self.NO},
+                }
+            }
+
+        # Add capability types
+        if self.CAPABILITY_TYPES not in tosca:
+            tosca[self.CAPABILITY_TYPES] = {}
+        if self.T_HTTP_EP not in tosca[self.CAPABILITY_TYPES]:
+            tosca[self.CAPABILITY_TYPES][self.T_HTTP_EP] = {
+                self.DERIVED_FROM: 'tosca.capabilities.Endpoint',
+                self.PROPERTIES: {
+                    'polling_interval':
+                    {self.TYPE: self.INTEGER},
+                    'path':
+                    {self.TYPE: self.STRING},
+                },
+            }
+
+        if self.T_MGMT_INTF not in tosca[self.CAPABILITY_TYPES]:
+            tosca[self.CAPABILITY_TYPES][self.T_MGMT_INTF] = {
+                self.DERIVED_FROM: 'tosca.capabilities.Endpoint',
+                self.PROPERTIES: {
+                    self.DASHBOARD_PARAMS:
+                    {self.TYPE: self.MAP},
+                    self.VDU:
+                    {self.TYPE: self.STRING},
+                },
+            }
+
+        if self.T_MON_PARAM not in tosca[self.CAPABILITY_TYPES]:
+            tosca[self.CAPABILITY_TYPES][self.T_MON_PARAM] = {
+                self.DERIVED_FROM: 'tosca.capabilities.nfv.Metric',
+                self.PROPERTIES: {
+                    'id':
+                    {self.TYPE: self.INTEGER},
+                    'name':
+                    {self.TYPE: self.STRING},
+                    'value_type':
+                    {self.TYPE: self.STRING,
+                     self.DEFAULT: 'INT'},
+                    'group_tag':
+                    {self.TYPE: self.STRING,
+                     self.DEFAULT: 'Group1'},
+                    'units':
+                    {self.TYPE: self.STRING},
+                    'description':
+                    {self.TYPE: self.STRING},
+                    'json_query_method':
+                    {self.TYPE: self.STRING,
+                     self.DEFAULT: 'NAMEKEY'},
+                    'http_endpoint_ref':
+                    {self.TYPE: self.STRING},
+                    'widget_type':
+                    {self.TYPE: self.STRING,
+                     self.DEFAULT: 'COUNTER'},
+                }
+            }
+
+        # Define the VNF type
+        if self.T_VNF1 not in tosca[self.NODE_TYPES]:
+            tosca[self.NODE_TYPES][self.T_VNF1] = {
+                self.DERIVED_FROM: 'tosca.nodes.nfv.VNF',
+                self.PROPERTIES: {
+                    'vnf_configuration':
+                    {self.TYPE: self.T_VNF_CONFIG},
+                    'port':
+                    {self.TYPE: self.INTEGER,
+                     self.CONSTRAINTS:
+                     [{'in_range': '[1, 65535]'}]},
+                    self.START_BY_DFLT:
+                    {self.TYPE: self.BOOL,
+                     self.DEFAULT: self.TRUE},
+                },
+                self.CAPABILITIES: {
+                    'mgmt_interface':
+                    {self.TYPE: self.T_MGMT_INTF},
+                    'http_endpoint':
+                    {self.TYPE: self.T_HTTP_EP},
+                    'monitoring_param_0':
+                    {self.TYPE: self.T_MON_PARAM},
+                    'monitoring_param_1':
+                    {self.TYPE: self.T_MON_PARAM},
+                },
+                self.REQUIREMENTS: [
+                    {'vdus':
+                     {self.TYPE: 'tosca.capabilities.nfv.VirtualLinkable',
+                      self.RELATIONSHIP:
+                      'tosca.relationships.nfv.VirtualLinksTo',
+                      self.NODE: self.T_VDU1,
+                      self.OCCURENCES: '[1, UNBOUND]'}}
+                ],
+            }
+
+        return tosca
+
+    def generate_vnf_template(self, tosca, index):
+        self.log.debug(_("{0}, Generate tosca template for VNF {1}").
+                       format(self, index, tosca))
+
+        for vdu in self.vdus:
+            tosca = vdu.generate_vdu_template(tosca, self.name)
+
+        node = {}
+        node[self.TYPE] = self.T_VNF1
+
+        # Remove fields not required in TOSCA
+        self.props.pop(self.DESC)
+
+        # Update index to the member-vnf-index
+        self.props[self.ID] = index
+        node[self.PROPERTIES] = self.props
+
+        caps = {}
+        if len(self.mgmt_intf):
+            caps[self.MGMT_INTF] = {
+                self.PROPERTIES: self.mgmt_intf
+            }
+
+        if len(self.http_ep):
+            caps[self.HTTP_EP] = {
+                self.PROPERTIES: self.http_ep[0]
+            }
+            if len(self.http_ep) > 1:
+                self.log.warn(_("{0}: Currently only one HTTP endpoint "
+                                "supported: {1}").
+                              format(self, self.http_ep))
+
+        if len(self.mon_param):
+            count = 0
+            for monp in self.mon_param:
+                name = "{}_{}".format(self.MON_PARAM, count)
+                caps[name] = {self.PROPERTIES: monp}
+                count += 1
+
+        node[self.CAPABILITIES] = caps
+
+        if len(self.vdus):
+            reqs = []
+            for vdu in self.vdus:
+                reqs.append({'vdus': {self.NODE: vdu.get_name(self.name)}})
+
+            node[self.REQUIREMENTS] = reqs
+        else:
+            self.log.warn(_("{0}, Did not find any VDUS with this VNF").
+                          format(self))
+
+        self.log.debug(_("{0}, VNF node: {1}").format(self, node))
+
+        tosca[self.TOPOLOGY_TMPL][self.NODE_TMPL][self.name] = node
+
+        return tosca
+
+    def get_supporting_files(self):
+        files = []
+
+        for vdu in self.vdus:
+            f = vdu.get_supporting_files()
+            if f and len(f):
+                files.extend(f)
+
+        return files