* YANG to TOSCA translator
[osm/SO.git] / common / python / rift / mano / yang_translator / rwmano / yang / yang_vnfd.py
1 # Copyright 2016 RIFT.io Inc
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15
16 from copy import deepcopy
17
18 from rift.mano.yang_translator.common.exception import ValidationError
19 from rift.mano.yang_translator.common.utils import _
20 from rift.mano.yang_translator.rwmano.syntax.tosca_resource \
21 import ToscaResource
22 from rift.mano.yang_translator.rwmano.yang.yang_vdu import YangVdu
23
24 TARGET_CLASS_NAME = 'YangVnfd'
25
26
27 class YangVnfd(ToscaResource):
28 '''Class for RIFT.io YANG VNF descriptor translation to TOSCA type.'''
29
30 yangtype = 'vnfd'
31
32 CONFIG_TYPES = ['script', 'netconf', 'rest', 'juju']
33
34 OTHER_KEYS = (MGMT_INTF, HTTP_EP, MON_PARAM) = \
35 ('mgmt_interface', 'http_endpoint', 'monitoring_param')
36 vnf_prefix_type = 'tosca.nodes.nfv.riftio.'
37
38
39 def __init__(self,
40 log,
41 name,
42 type_,
43 yang):
44 super(YangVnfd, self).__init__(log,
45 name,
46 type_,
47 yang)
48 self.props = {}
49 self.vdus = []
50 self.mgmt_intf = {}
51 self.mon_param = []
52 self.http_ep = []
53 self.vnf_configuration = None
54 self.monitor_param = {}
55 self.monitor_param_1 = {}
56 self.vnf_type = None
57 self.tosca = None
58 self.script_files = []
59
60 def handle_yang(self):
61 self.log.debug(_("Process VNFD desc {0}: {1}").format(self.name,
62 self.yang))
63
64 def process_vnf_config(conf):
65 vnf_conf = {}
66 config = {}
67
68 init_primitive_config = {}
69 if 'config_template' in conf:
70 config['config_template'] = conf['config_template']
71 if 'config_attributes' in conf:
72 if 'config_delay' in conf['config_attributes']:
73 config['config_delay'] = conf['config_attributes']['config_delay']
74 if 'config_priority' in conf['config_attributes']:
75 config['config_priority'] = conf['config_attributes']['config_priority']
76 if 'config_type' in conf:
77 config['config_type'] = conf['config_type']
78 if 'script' in conf:
79 config['config_details'] = conf['script']
80 for conf_type in self.CONFIG_TYPES:
81 if conf_type in conf:
82 config['config_type'] = conf_type
83 if len(config) > 0:
84 vnf_conf['config'] = config
85
86 if 'initial_config_primitive' in conf:
87 init_config_prims = []
88 for init_conf_prim in conf['initial_config_primitive']:
89 init_conf = {}
90 if 'name' in init_conf_prim:
91 init_conf['name'] = init_conf_prim['name']
92 if 'seq' in init_conf_prim:
93 init_conf['seq'] = init_conf_prim['seq']
94 if 'user_defined_script' in init_conf_prim:
95 init_conf['user_defined_script'] = init_conf_prim['user_defined_script']
96 self.script_files.append(init_conf_prim['user_defined_script'])
97 if 'parameter' in init_conf_prim:
98 init_conf['parameter'] = []
99 for parameter in init_conf_prim['parameter']:
100 init_conf['parameter'].append({parameter['name']: parameter['value']})
101 init_config_prims.append(init_conf)
102 vnf_conf['initial_config_primitive'] = init_config_prims
103
104 self.vnf_configuration = vnf_conf
105
106 def process_mgmt_intf(intf):
107 if len(self.mgmt_intf) > 0:
108 err_msg(_("{0}, Already processed another mgmt intf {1}, "
109 "got another {2}").
110 format(self, self.msmg_intf, intf))
111 self.log.error(err_msg)
112 raise ValidationError(message=err_msg)
113
114 self.mgmt_intf['protocol'] = 'tcp'
115
116 if self.PORT in intf:
117 self.mgmt_intf[self.PORT] = intf.pop(self.PORT)
118 self.props[self.PORT] = self.mgmt_intf[self.PORT]
119
120 if 'vdu_id' in intf:
121 for vdu in self.vdus:
122 if intf['vdu_id'] == vdu.id:
123 self.mgmt_intf[self.VDU] = vdu.get_name(self.name)
124 intf.pop('vdu_id')
125 break
126
127 if self.DASHBOARD_PARAMS in intf:
128 self.mgmt_intf[self.DASHBOARD_PARAMS] = \
129 intf.pop(self.DASHBOARD_PARAMS)
130
131 if len(intf):
132 self.log.warn(_("{0}, Did not process all in mgmt "
133 "interface {1}").
134 format(self, intf))
135 self.log.debug(_("{0}, Management interface: {1}").
136 format(self, self.mgmt_intf))
137
138 def process_http_ep(eps):
139 self.log.debug("{}, HTTP EP: {}".format(self, eps))
140 for ep in eps:
141 http_ep = {'protocol': 'http'} # Required for TOSCA
142 http_ep[self.PATH] = ep.pop(self.PATH)
143 http_ep[self.PORT] = ep.pop(self.PORT)
144 if self.POLL_INTVL in http_ep:
145 http_ep[self.POLL_INTVL] = ep.pop(self.POLL_INTVL_SECS)
146 if len(ep):
147 self.log.warn(_("{0}, Did not process the following for "
148 "http ep {1}").format(self, ep))
149 self.log.debug(_("{0}, http endpoint: {1}").format(self, http_ep))
150 self.http_ep.append(http_ep)
151
152 def process_mon_param(params):
153 for param in params:
154 monp = {}
155 fields = [self.NAME, self.ID, 'value_type', 'units', 'group_tag',
156 'json_query_method', 'http_endpoint_ref', 'widget_type',
157 self.DESC]
158 mon_param = {}
159 ui_param = {}
160 if 'name' in param:
161 mon_param['name'] = param['name']
162 if 'description' in param:
163 mon_param['description'] = param['description']
164 if 'polling_interval' in param:
165 mon_param['polling_interval'] = param['polling_interval']
166 if 'http_endpoint_ref' in param:
167 mon_param['url_path'] = param['http_endpoint_ref']
168 if 'json_query_method' in param:
169 mon_param['json_query_method'] = param['json_query_method'].lower()
170 if 'group_tag' in param:
171 ui_param['group_tag'] = param['group_tag']
172 if 'widget_type' in param:
173 ui_param['widget_type'] = param['widget_type'].lower()
174 if 'units' in param:
175 ui_param['units'] = param['units']
176 mon_param['ui_data'] = ui_param
177
178 self.mon_param.append(mon_param)
179
180 if len(param):
181 self.log.warn(_("{0}, Did not process the following for "
182 "monitporing-param {1}").
183 format(self, param))
184 self.log.debug(_("{0}, Monitoring param: {1}").format(self, monp))
185 #self.mon_param.append(monp)
186
187 def process_cp(cps):
188 for cp_dic in cps:
189 self.log.debug("{}, CP: {}".format(self, cp_dic))
190 name = cp_dic.pop(self.NAME)
191 for vdu in self.vdus:
192 if vdu.has_cp(name):
193 vdu.set_cp_type(name, cp_dic.pop(self.TYPE_Y))
194 break
195 if len(cp_dic):
196 self.log.warn(_("{0}, Did not process the following for "
197 "connection-point {1}: {2}").
198 format(self, name, cp_dic))
199
200 ENDPOINTS_MAP = {
201 self.MGMT_INTF: process_mgmt_intf,
202 self.HTTP_EP: process_http_ep,
203 self.MON_PARAM: process_mon_param,
204 'connection_point': process_cp
205 }
206 dic = deepcopy(self.yang)
207 try:
208 for key in self.REQUIRED_FIELDS:
209 self.props[key] = dic.pop(key)
210
211 self.id = self.props[self.ID]
212
213 # Process VDUs before CPs so as to update the CP struct in VDU
214 # when we process CP later
215 if self.VDU in dic:
216 for vdu_dic in dic.pop(self.VDU):
217 vdu = YangVdu(self.log, vdu_dic.pop(self.NAME),
218 self.VDU, vdu_dic)
219 vdu.process_vdu()
220 self.vdus.append(vdu)
221 for key in ENDPOINTS_MAP.keys():
222 if key in dic:
223 ENDPOINTS_MAP[key](dic.pop(key))
224 if self.VNF_CONFIG in dic:
225 process_vnf_config(dic.pop(self.VNF_CONFIG))
226
227 self.remove_ignored_fields(dic)
228 if len(dic):
229 self.log.warn(_("{0}, Did not process the following for "
230 "VNFD: {1}").
231 format(self, dic))
232 self.log.debug(_("{0}, VNFD: {1}").format(self, self.props))
233 except Exception as e:
234 err_msg = _("Exception processing VNFD {0} : {1}"). \
235 format(self.name, e)
236 self.log.error(err_msg)
237 raise ValidationError(message=err_msg)
238
239 def update_cp_vld(self, cp_name, vld_name):
240 for vdu in self.vdus:
241 cp = vdu.get_cp(cp_name)
242 if cp:
243 vdu.set_vld(cp_name, vld_name)
244 break
245 def _generate_vnf_type(self, tosca):
246 name = self.name.split('_', 1)[0]
247 self.vnf_type = "{0}{1}{2}".format(self.vnf_prefix_type, name, 'VNF')
248 if self.NODE_TYPES not in tosca and self.vnf_type:
249 tosca[self.NODE_TYPES] = {}
250 tosca[self.NODE_TYPES][self.vnf_type] = {
251 self.DERIVED_FROM : self.T_VNF1
252 }
253
254 def generate_tosca_template(self, tosca):
255 self.tosca = tosca
256 tosca['tosca_definitions_version'] = 'tosca_simple_profile_for_nfv_1_0'
257 tosca[self.IMPORT] = []
258 tosca[self.IMPORT].append("riftiotypes.yaml")
259 tosca[self.DESC] = self.props[self.DESC]
260 tosca[self.METADATA] = {
261 'ID': self.name,
262 self.VENDOR: self.props[self.VENDOR],
263 self.VERSION: self.props[self.VERSION],
264 }
265 if self.name:
266 self._generate_vnf_type(tosca);
267
268
269 tosca[self.TOPOLOGY_TMPL] = {}
270 tosca[self.TOPOLOGY_TMPL][self.NODE_TMPL] = {}
271 tosca[self.TOPOLOGY_TMPL][self.SUBSTITUTION_MAPPING] = {}
272 tosca[self.TOPOLOGY_TMPL][self.SUBSTITUTION_MAPPING]['node_type'] = self.vnf_type
273
274 for vdu in self.vdus:
275 vdu.generate_vdu_template(tosca, self.name)
276 if 'vdu' in self.mgmt_intf and self.mgmt_intf['vdu'] == vdu.get_name(self.name): #TEST
277 mgmt_interface = {}
278 mgmt_interface[self.PROPERTIES] = self.mgmt_intf
279 self.mgmt_intf.pop('vdu')
280 caps = []
281 tosca[self.TOPOLOGY_TMPL][self.NODE_TMPL][vdu.get_name(self.name)][self.CAPABILITIES]['mgmt_interface'] = mgmt_interface #TEST
282 if len(self.mon_param) > 0:
283 mon_param = {}
284 mon_param = {}
285 mon_param['properties'] = self.mon_param[0]
286 tosca[self.TOPOLOGY_TMPL][self.NODE_TMPL][vdu.get_name(self.name)][self.CAPABILITIES]['monitoring_param'] = mon_param #TEST
287 if len(self.mon_param) == 2:
288 mon_param = {}
289 mon_param = {}
290 mon_param['properties'] = self.mon_param[1]
291 tosca[self.TOPOLOGY_TMPL][self.NODE_TMPL][vdu.get_name(self.name)][self.CAPABILITIES]['monitoring_param_1'] = mon_param
292
293 node = {}
294 node[self.TYPE] = self.T_VNF1
295
296 # Remove fields not required in TOSCA
297 self.props.pop(self.DESC)
298
299 # Update index to the member-vnf-index
300
301 # For now I am putting index as 1. This needs to be revisted
302 self.props[self.ID] = 1
303 node[self.PROPERTIES] = self.props
304
305 caps = {}
306 if len(self.mgmt_intf):
307 caps[self.MGMT_INTF] = {
308 self.PROPERTIES: self.mgmt_intf
309 }
310
311 if len(self.http_ep):
312 caps[self.HTTP_EP] = {
313 self.PROPERTIES: self.http_ep[0]
314 }
315 if len(self.http_ep) > 1:
316 self.log.warn(_("{0}: Currently only one HTTP endpoint "
317 "supported: {1}").
318 format(self, self.http_ep))
319
320 if len(self.mon_param):
321 count = 0
322 for monp in self.mon_param:
323 name = "{}_{}".format(self.MON_PARAM, count)
324 caps[name] = {self.PROPERTIES: monp}
325 count += 1
326
327 node[self.CAPABILITIES] = caps
328
329 if len(self.vdus):
330 reqs = []
331 for vdu in self.vdus:
332 reqs.append({'vdus': {self.NODE: vdu.get_name(self.name)}})
333
334 node[self.REQUIREMENTS] = reqs
335 else:
336 self.log.warn(_("{0}, Did not find any VDUS with this VNF").
337 format(self))
338
339 self.log.debug(_("{0}, VNF node: {1}").format(self, node))
340
341 #tosca[self.TOPOLOGY_TMPL][self.NODE_TMPL][self.name] = node
342 self.get_vnf_configuration_policy(tosca)
343
344 return tosca
345
346 def generate_vld_link(self, virtualLink, conn_point):
347 if self.REQUIREMENTS not in self.tosca[self.TOPOLOGY_TMPL][self.SUBSTITUTION_MAPPING]:
348 self.tosca[self.TOPOLOGY_TMPL][self.SUBSTITUTION_MAPPING] = {}
349 self.tosca[self.TOPOLOGY_TMPL][self.SUBSTITUTION_MAPPING]['node_type'] = self.vnf_type
350 #self.tosca[self.TOPOLOGY_TMPL][self.SUBSTITUTION_MAPPING]['node_type'] = []
351 #self.tosca[self.TOPOLOGY_TMPL][self.SUBSTITUTION_MAPPING]['node_type'].\
352 #append(['node_type', self.vnf_type])
353 self.tosca[self.TOPOLOGY_TMPL][self.SUBSTITUTION_MAPPING][self.REQUIREMENTS] = []
354
355 for vdu in self.vdus:
356 if conn_point in vdu.cp_name_to_cp_node:
357 conn_point_node_name = vdu.cp_name_to_cp_node[conn_point]
358 self.tosca[self.TOPOLOGY_TMPL][self.SUBSTITUTION_MAPPING][self.REQUIREMENTS].\
359 append({virtualLink : "[{0}, {1}]".format(conn_point_node_name, "virtualLink")})
360
361 if self.REQUIREMENTS not in self.tosca[self.NODE_TYPES][self.vnf_type]:
362 self.tosca[self.NODE_TYPES][self.vnf_type][self.REQUIREMENTS] = []
363 self.tosca[self.NODE_TYPES][self.vnf_type][self.REQUIREMENTS].append({virtualLink : {
364 "type": "tosca.nodes.nfv.VL"}})
365
366
367 def generate_tosca(self):
368 tosca = {}
369 return tosca
370
371 def get_vnf_configuration_policy(self, tosca):
372 if self.vnf_configuration:
373 if self.POLICIES in tosca:
374 tosca[self.TOPOLOGY_TMPL][self.POLICIES]['configuration'] ={
375 'type' : self.T_VNF_CONFIG,
376 self.PROPERTIES: self.vnf_configuration
377 }
378 else:
379 tosca[self.TOPOLOGY_TMPL][self.POLICIES] = []
380 # This is bad hack. TOSCA Openstack does not return policies without target
381 if len(tosca[self.TOPOLOGY_TMPL][self.NODE_TMPL]) > 0:
382 node_name = list(tosca[self.TOPOLOGY_TMPL][self.NODE_TMPL].keys())[0]
383 tosca[self.TOPOLOGY_TMPL][self.POLICIES].append({'configuration' :{
384 'type' : self.T_VNF_CONFIG,
385 self.PROPERTIES: self.vnf_configuration,
386 self.TARGETS : "[{0}]".format(node_name)
387 }})
388
389 def get_supporting_files(self):
390 files = []
391 for file in self.script_files:
392 files.append({
393 self.TYPE: 'script',
394 self.NAME: file,
395 self.DEST: "{}/{}".format(self.SCRIPT_DIR, file),
396 })
397
398
399 for vdu in self.vdus:
400 vdu_files = vdu.get_supporting_files()
401 for vdu_file in vdu_files:
402 files.append(vdu_file)
403
404 return files