--- /dev/null
+# 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