4e421d1f39a088d14821de5bee73123d977e1ba7
[osm/SO.git] / common / python / rift / mano / yang_translator / rwmano / yang / yang_nsd.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 import os
18
19 from rift.mano.yang_translator.common.exception import ValidationError
20 from rift.mano.yang_translator.common.utils import _
21 from rift.mano.yang_translator.rwmano.syntax.tosca_resource \
22 import ToscaResource
23 from rift.mano.yang_translator.rwmano.yang.yang_vld import YangVld
24
25 TARGET_CLASS_NAME = 'YangNsd'
26
27
28 class YangNsd(ToscaResource):
29 '''Class for RIFT.io YANG NS descriptor translation to TOSCA type.'''
30
31 yangtype = 'nsd'
32
33 OTHER_FIELDS = (SCALE_GRP, CONF_PRIM,
34 USER_DEF_SCRIPT, SCALE_ACT,
35 TRIGGER, NS_CONF_PRIM_REF,
36 CONST_VNFD, VNFD_MEMBERS,
37 MIN_INST_COUNT, MAX_INST_COUNT,
38 INPUT_PARAM_XPATH, CONFIG_ACTIONS,
39 INITIAL_CFG,) = \
40 ('scaling_group_descriptor', 'service_primitive',
41 'user_defined_script', 'scaling_config_action',
42 'trigger', 'ns_config_primitive_name_ref',
43 'constituent_vnfd', 'vnfd_member',
44 'min_instance_count', 'max_instance_count',
45 'input_parameter_xpath', 'config_actions',
46 'initial_config_primitive',)
47
48 def __init__(self,
49 log,
50 name,
51 type_,
52 yang,
53 vnfd_files):
54 super(YangNsd, self).__init__(log,
55 name,
56 type_,
57 yang)
58 self.props = {}
59 self.inputs = []
60 self.vnfds = {}
61 self.vlds = {}
62 self.conf_prims = []
63 self.scale_grps = []
64 self.initial_cfg = []
65 self.placement_groups = []
66 self.vnf_id_to_vnf_map = {}
67 self.vnfd_files = vnfd_files
68 self.vld_to_vnf_map = {}
69 self.vnf_to_vld_map = {}
70 self._vnf_vld_conn_point_map = {}
71
72 def handle_yang(self, vnfds):
73 self.log.debug(_("Process NSD desc {0}: {1}").
74 format(self.name, self.yang))
75
76 def process_input_param(param):
77 if self.XPATH in param:
78 val = param.pop(self.XPATH)
79 # Strip namesapce, catalog and nsd part
80 self.inputs.append({
81 self.NAME:
82 self.map_yang_name_to_tosca(
83 val.replace('/nsd:nsd-catalog/nsd:nsd/nsd:', ''))})
84 if len(param):
85 self.log.warn(_("{0}, Did not process the following for "
86 "input param {1}: {2}").
87 format(self, self.inputs, param))
88 self.log.debug(_("{0}, inputs: {1}").format(self, self.inputs[-1]))
89
90 def process_const_vnfd(cvnfd):
91 # Get the matching VNFD
92 vnfd_id = cvnfd.pop(self.VNFD_ID_REF)
93 for vnfd in vnfds:
94 if vnfd.type == self.VNFD and vnfd.id == vnfd_id:
95 self.vnf_id_to_vnf_map[vnfd_id] = vnfd.name
96 self.vnfds[cvnfd.pop(self.MEM_VNF_INDEX)] = vnfd
97 if self.START_BY_DFLT in cvnfd:
98 vnfd.props[self.START_BY_DFLT] = \
99 cvnfd.pop(self.START_BY_DFLT)
100 break
101
102 if len(cvnfd):
103 self.log.warn(_("{0}, Did not process the following for "
104 "constituent vnfd {1}: {2}").
105 format(self, vnfd_id, cvnfd))
106 self.log.debug(_("{0}, VNFD: {1}").format(self, self.vnfds))
107
108 def process_scale_grp(dic):
109 sg = {}
110 self.log.debug(_("{0}, scale group: {1}").format(self, dic))
111 fields = [self.NAME, self.MIN_INST_COUNT, self.MAX_INST_COUNT]
112 for key in fields:
113 if key in dic:
114 sg[key] = dic.pop(key)
115
116 membs = {}
117 for vnfd_memb in dic.pop(self.VNFD_MEMBERS):
118 vnfd_idx = vnfd_memb[self.MEM_VNF_INDEX_REF]
119 if vnfd_idx in self.vnfds:
120 membs[self.vnfds[vnfd_idx].name] = \
121 vnfd_memb[self.COUNT]
122 sg['vnfd_members'] = membs
123
124 trigs = {}
125 if self.SCALE_ACT in dic:
126 for sg_act in dic.pop(self.SCALE_ACT):
127 # Validate the primitive
128 prim = sg_act.pop(self.NS_CONF_PRIM_REF)
129 for cprim in self.conf_prims:
130 if cprim[self.NAME] == prim:
131 trigs[sg_act.pop(self.TRIGGER)] = prim
132 break
133 if len(sg_act):
134 err_msg = (_("{0}, Did not find config-primitive {1}").
135 format(self, prim))
136 self.log.error(err_msg)
137 raise ValidationError(message=err_msg)
138 sg[self.CONFIG_ACTIONS] = trigs
139
140 if len(dic):
141 self.log.warn(_("{0}, Did not process all fields for {1}").
142 format(self, dic))
143 self.log.debug(_("{0}, Scale group {1}").format(self, sg))
144 self.scale_grps.append(sg)
145
146 def process_initial_config(dic):
147 icp = {}
148 self.log.debug(_("{0}, initial config: {1}").format(self, dic))
149 for key in [self.NAME, self.SEQ, self.USER_DEF_SCRIPT]:
150 if key in dic:
151 icp[key] = dic.pop(key)
152
153 params = {}
154 if self.PARAM in dic:
155 for p in dic.pop(self.PARAM):
156 if (self.NAME in p and
157 self.VALUE in p):
158 params[p[self.NAME]] = p[self.VALUE]
159 else:
160 # TODO (pjoseph): Need to add support to read the
161 # config file and get the value from that
162 self.log.warn(_("{0}, Got parameter without value: {1}").
163 format(self, p))
164 if len(params):
165 icp[self.PARAM] = params
166
167 if len(dic):
168 self.log.warn(_("{0}, Did not process all fields for {1}").
169 format(self, dic))
170 self.log.debug(_("{0}, Initial config {1}").format(self, icp))
171 self.initial_cfg.append({self.PROPERTIES : icp})
172
173 def process_vld(vld, dic):
174 vld_conf = {}
175 vld_prop = {}
176 ip_profile_vld = None
177 vld_name = None
178 if 'ip_profile_ref' in vld:
179 ip_profile_name = vld['ip_profile_ref']
180 if 'ip_profiles' in dic:
181 for ip_prof in dic['ip_profiles']:
182 if ip_profile_name == ip_prof['name']:
183 ip_profile_vld = ip_prof
184 if 'name' in vld:
185 vld_name = vld['name']
186 if 'description' in vld:
187 vld_conf['description'] = vld['description']
188 if 'vendor' in vld:
189 vld_conf['vendor'] = vld['vendor']
190 if ip_profile_vld:
191 if 'ip_profile_params' in ip_profile_vld:
192 ip_param = ip_profile_vld['ip_profile_params']
193 if 'gateway_address' in ip_param:
194 vld_conf['gateway_ip'] = ip_param['gateway_address']
195 if 'subnet_address' in ip_param:
196 vld_conf['cidr'] = ip_param['subnet_address']
197 if 'ip_version' in ip_param:
198 vld_conf['ip_version'] = ip_param['ip_version'].replace('ipv','')
199
200 if vld_name:
201 vld_prop = {vld_name :
202 {
203 'type': self.T_ELAN,
204 self.PROPERTIES : vld_conf
205 }}
206 self.vlds[vld_name] = { 'type': self.T_ELAN,
207 self.PROPERTIES : vld_conf
208 }
209
210 self.vld_to_vnf_map[vld_name] = []
211 if 'vnfd_connection_point_ref' in vld:
212 for vnfd_ref in vld['vnfd_connection_point_ref']:
213 vnf_name = self.vnf_id_to_vnf_map[vnfd_ref['vnfd_id_ref']]
214 if vnf_name in self.vnf_to_vld_map:
215 self.vnf_to_vld_map[vnf_name].append(vld_name)
216 self._vnf_vld_conn_point_map[vnf_name].\
217 append((vld_name ,vnfd_ref['vnfd_connection_point_ref']))
218 else:
219 self.vnf_to_vld_map[vnf_name] = []
220 self._vnf_vld_conn_point_map[vnf_name] = []
221 self.vnf_to_vld_map[vnf_name].append(vld_name)
222 self._vnf_vld_conn_point_map[vnf_name].\
223 append((vld_name ,vnfd_ref['vnfd_connection_point_ref']))
224
225 def process_placement_group(placement_groups):
226 for i in range(0, len(placement_groups)):
227 placement_group = placement_groups[i]
228 pg_name = "placement_{0}".format(i)
229 pg_config = {}
230 targets = []
231 if 'name' in placement_group:
232 pg_config['name'] = placement_group['name']
233 if 'requirement' in placement_group:
234 pg_config['requirement'] = placement_group['requirement']
235 if 'strategy' in placement_group:
236 pg_config['strategy'] = placement_group['strategy']
237 if 'member_vnfd' in placement_group:
238 for member_vnfd in placement_group['member_vnfd']:
239 targets.append(self.vnf_id_to_vnf_map[member_vnfd['vnfd_id_ref']])
240 placement = { pg_name : {
241 'type': self.T_PLACEMENT,
242 self.PROPERTIES: pg_config,
243 self.TARGETS : str(targets)
244 }
245 }
246 self.placement_groups.append(placement)
247
248 dic = deepcopy(self.yang)
249 try:
250 for key in self.REQUIRED_FIELDS:
251 self.props[key] = dic.pop(key)
252
253 self.id = self.props[self.ID]
254
255 # Process constituent VNFDs
256 if self.CONST_VNFD in dic:
257 for cvnfd in dic.pop(self.CONST_VNFD):
258 process_const_vnfd(cvnfd)
259
260 # Process VLDs
261 if self.VLD in dic:
262 for vld_dic in dic.pop(self.VLD):
263 process_vld(vld_dic, dic)
264 #self.vlds.append(vld)
265
266 # Process config primitives
267 if self.CONF_PRIM in dic:
268 for cprim in dic.pop(self.CONF_PRIM):
269 conf_prim = {self.NAME: cprim.pop(self.NAME)}
270 if self.USER_DEF_SCRIPT in cprim:
271 conf_prim[self.USER_DEF_SCRIPT] = \
272 cprim.pop(self.USER_DEF_SCRIPT)
273 self.conf_prims.append(conf_prim)
274 else:
275 err_msg = (_("{0}, Only user defined script supported "
276 "in config-primitive for now {}: {}").
277 format(self, conf_prim, cprim))
278 self.log.error(err_msg)
279 raise ValidationError(message=err_msg)
280
281 # Process scaling group
282 if self.SCALE_GRP in dic:
283 for sg_dic in dic.pop(self.SCALE_GRP):
284 process_scale_grp(sg_dic)
285
286 # Process initial config primitives
287 if self.INITIAL_CFG in dic:
288 for icp_dic in dic.pop(self.INITIAL_CFG):
289 process_initial_config(icp_dic)
290
291 # Process the input params
292 if self.INPUT_PARAM_XPATH in dic:
293 for param in dic.pop(self.INPUT_PARAM_XPATH):
294 process_input_param(param)
295
296 if 'placement_groups' in dic:
297 process_placement_group(dic['placement_groups'])
298
299
300 self.remove_ignored_fields(dic)
301 if len(dic):
302 self.log.warn(_("{0}, Did not process the following for "
303 "NSD {1}: {2}").
304 format(self, self.props, dic))
305 self.log.debug(_("{0}, NSD: {1}").format(self, self.props))
306 except Exception as e:
307 err_msg = _("Exception processing NSD {0} : {1}"). \
308 format(self.name, e)
309 self.log.error(err_msg)
310 self.log.exception(e)
311 raise ValidationError(message=err_msg)
312
313 def generate_tosca_type(self):
314
315 self.log.debug(_("{0} Generate tosa types").
316 format(self))
317
318 tosca = {}
319 #tosca[self.DATA_TYPES] = {}
320 #tosca[self.NODE_TYPES] = {}
321 return tosca
322 for idx, vnfd in self.vnfds.items():
323 tosca = vnfd.generate_tosca_type(tosca)
324
325 for vld in self.vlds:
326 tosca = vld.generate_tosca_type(tosca)
327
328 # Generate type for config primitives
329 if self.GROUP_TYPES not in tosca:
330 tosca[self.GROUP_TYPES] = {}
331 if self.T_CONF_PRIM not in tosca[self.GROUP_TYPES]:
332 tosca[self.GROUP_TYPES][self.T_CONF_PRIM] = {
333 self.DERIVED_FROM: 'tosca.policies.Root',
334 self.PROPERTIES: {
335 'primitive': self.MAP
336 }}
337
338 # Generate type for scaling group
339 if self.POLICY_TYPES not in tosca:
340 tosca[self.POLICY_TYPES] = {}
341 if self.T_SCALE_GRP not in tosca[self.POLICY_TYPES]:
342 tosca[self.POLICY_TYPES][self.T_SCALE_GRP] = {
343 self.DERIVED_FROM: 'tosca.policies.Root',
344 self.PROPERTIES: {
345 self.NAME:
346 {self.TYPE: self.STRING},
347 self.MAX_INST_COUNT:
348 {self.TYPE: self.INTEGER},
349 self.MIN_INST_COUNT:
350 {self.TYPE: self.INTEGER},
351 'vnfd_members':
352 {self.TYPE: self.MAP},
353 self.CONFIG_ACTIONS:
354 {self.TYPE: self.MAP}
355 }}
356
357 if self.T_INITIAL_CFG not in tosca[self.POLICY_TYPES]:
358 tosca[self.POLICY_TYPES][self.T_INITIAL_CFG] = {
359 self.DERIVED_FROM: 'tosca.policies.Root',
360 self.PROPERTIES: {
361 self.NAME:
362 {self.TYPE: self.STRING},
363 self.SEQ:
364 {self.TYPE: self.INTEGER},
365 self.USER_DEF_SCRIPT:
366 {self.TYPE: self.STRING},
367 self.PARAM:
368 {self.TYPE: self.MAP},
369 }}
370
371 return tosca
372
373 def generate_tosca_template(self, tosca):
374 self.log.debug(_("{0}, Generate tosca template").
375 format(self, tosca))
376 # Add the standard entries
377 tosca['tosca_definitions_version'] = \
378 'tosca_simple_profile_for_nfv_1_0'
379 tosca[self.DESC] = self.props[self.DESC]
380 tosca[self.METADATA] = {
381 'ID': self.name,
382 self.VENDOR: self.props[self.VENDOR],
383 self.VERSION: self.props[self.VERSION],
384 }
385 if len(self.vnfd_files) > 0:
386 tosca[self.IMPORT] = []
387 imports = []
388 for vnfd_file in self.vnfd_files:
389 tosca[self.IMPORT].append('"{0}.yaml"'.format(vnfd_file))
390
391 tosca[self.TOPOLOGY_TMPL] = {}
392
393 # Add input params
394 '''
395 if len(self.inputs):
396 if self.INPUTS not in tosca[self.TOPOLOGY_TMPL]:
397 tosca[self.TOPOLOGY_TMPL][self.INPUTS] = {}
398 for inp in self.inputs:
399 entry = {inp[self.NAME]: {self.TYPE: self.STRING,
400 self.DESC:
401 'Translated from YANG'}}
402 tosca[self.TOPOLOGY_TMPL][self.INPUTS] = entry
403 '''
404 tosca[self.TOPOLOGY_TMPL][self.NODE_TMPL] = {}
405
406 # Add the VNFDs and VLDs
407 for idx, vnfd in self.vnfds.items():
408 #vnfd.generate_vnf_template(tosca, idx)
409 node = {
410 'type' : vnfd.vnf_type,
411 self.PROPERTIES : {
412 self.ID : idx,
413 self.VENDOR : self.props[self.VENDOR],
414 self.VERSION : self.props[self.VERSION]
415 }
416 }
417 if vnfd.name in self.vnf_to_vld_map:
418 vld_list = self.vnf_to_vld_map[vnfd.name]
419 node[self.REQUIREMENTS] = []
420 for vld_idx in range(0, len(vld_list)):
421 vld_link_name = "{0}{1}".format("virtualLink", vld_idx + 1)
422 vld_prop = {}
423 vld_prop[vld_link_name] = vld_list[vld_idx]
424 node[self.REQUIREMENTS].append(vld_prop)
425 if vnfd.name in self._vnf_vld_conn_point_map:
426 vnf_vld_list = self._vnf_vld_conn_point_map[vnfd.name]
427 for vnf_vld in vnf_vld_list:
428 vnfd.generate_vld_link(vld_link_name, vnf_vld[1])
429
430
431 tosca[self.TOPOLOGY_TMPL][self.NODE_TMPL][vnfd.name] = node
432
433 for vld_node_name in self.vlds:
434 tosca[self.TOPOLOGY_TMPL][self.NODE_TMPL][vld_node_name] = self.vlds[vld_node_name]
435
436 # add the config primitives
437 if len(self.conf_prims):
438 if self.GROUPS not in tosca[self.TOPOLOGY_TMPL]:
439 tosca[self.TOPOLOGY_TMPL][self.GROUPS] = {}
440
441 conf_prims = {
442 self.TYPE: self.T_CONF_PRIM
443 }
444 conf_prims[self.MEMBERS] = [vnfd.name for vnfd in
445 self.vnfds.values()]
446 prims = {}
447 for confp in self.conf_prims:
448 prims[confp[self.NAME]] = {
449 self.USER_DEF_SCRIPT: confp[self.USER_DEF_SCRIPT]
450 }
451 conf_prims[self.PROPERTIES] = {
452 self.PRIMITIVES: prims
453 }
454
455 tosca[self.TOPOLOGY_TMPL][self.GROUPS][self.CONF_PRIM] = conf_prims
456
457
458 # Add the scale group
459 if len(self.scale_grps):
460 if self.POLICIES not in tosca[self.TOPOLOGY_TMPL]:
461 tosca[self.TOPOLOGY_TMPL][self.POLICIES] = []
462
463 for sg in self.scale_grps:
464 sgt = {
465 self.TYPE: self.T_SCALE_GRP,
466 }
467 sgt.update(sg)
468 tosca[self.TOPOLOGY_TMPL][self.POLICIES].append({
469 self.SCALE_GRP: sgt
470 })
471
472 # Add initial configs
473 if len(self.initial_cfg):
474 if self.POLICIES not in tosca[self.TOPOLOGY_TMPL]:
475 tosca[self.TOPOLOGY_TMPL][self.POLICIES] = []
476
477 for icp in self.initial_cfg:
478 if len(tosca[self.TOPOLOGY_TMPL][self.NODE_TMPL]) > 0:
479 node_name = list(tosca[self.TOPOLOGY_TMPL][self.NODE_TMPL].keys())[0]
480 icpt = {
481 self.TYPE: self.T_INITIAL_CFG,
482 self.TARGETS : "[{0}]".format(node_name)
483 }
484 icpt.update(icp)
485 tosca[self.TOPOLOGY_TMPL][self.POLICIES].append({
486 self.INITIAL_CFG: icpt
487 })
488
489 if len(self.placement_groups) > 0:
490 if self.POLICIES not in tosca[self.TOPOLOGY_TMPL]:
491 tosca[self.TOPOLOGY_TMPL][self.POLICIES] = []
492
493 for placment_group in self.placement_groups:
494 tosca[self.TOPOLOGY_TMPL][self.POLICIES].append(placment_group)
495
496 return tosca
497
498 def get_supporting_files(self):
499 files = []
500 # Get the config files for initial config
501 for icp in self.initial_cfg:
502 if 'properties' in icp:
503 if 'user_defined_script' in icp['properties']:
504 script = os.path.basename(icp['properties']['user_defined_script'])
505 files.append({
506 self.TYPE: 'script',
507 self.NAME: script,
508 self.DEST: "{}/{}".format(self.SCRIPT_DIR, script),
509 })
510
511 # TODO (pjoseph): Add support for config scripts,
512 # charms, etc
513
514 self.log.debug(_("{0}, supporting files: {1}").format(self, files))
515 return files