491bd86db4ee23b52ca16473cd3f94609fdd44f5
[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 super(YangNsd, self).__init__(log,
54 name,
55 type_,
56 yang)
57 self.props = {}
58 self.inputs = []
59 self.vnfds = {}
60 self.vlds = []
61 self.conf_prims = []
62 self.scale_grps = []
63 self.initial_cfg = []
64
65 def handle_yang(self, vnfds):
66 self.log.debug(_("Process NSD desc {0}: {1}").
67 format(self.name, self.yang))
68
69 def process_input_param(param):
70 if self.XPATH in param:
71 val = param.pop(self.XPATH)
72 # Strip namesapce, catalog and nsd part
73 self.inputs.append({
74 self.NAME:
75 self.map_yang_name_to_tosca(
76 val.replace('/nsd:nsd-catalog/nsd:nsd/nsd:', ''))})
77 if len(param):
78 self.log.warn(_("{0}, Did not process the following for "
79 "input param {1}: {2}").
80 format(self, self.inputs, param))
81 self.log.debug(_("{0}, inputs: {1}").format(self, self.inputs[-1]))
82
83 def process_const_vnfd(cvnfd):
84 # Get the matching VNFD
85 vnfd_id = cvnfd.pop(self.VNFD_ID_REF)
86 for vnfd in vnfds:
87 if vnfd.type == self.VNFD and vnfd.id == vnfd_id:
88 self.vnfds[cvnfd.pop(self.MEM_VNF_INDEX)] = vnfd
89 if self.START_BY_DFLT in cvnfd:
90 vnfd.props[self.START_BY_DFLT] = \
91 cvnfd.pop(self.START_BY_DFLT)
92 break
93
94 if len(cvnfd):
95 self.log.warn(_("{0}, Did not process the following for "
96 "constituent vnfd {1}: {2}").
97 format(self, vnfd_id, cvnfd))
98 self.log.debug(_("{0}, VNFD: {1}").format(self, self.vnfds))
99
100 def process_scale_grp(dic):
101 sg = {}
102 self.log.debug(_("{0}, scale group: {1}").format(self, dic))
103 fields = [self.NAME, self.MIN_INST_COUNT, self.MAX_INST_COUNT]
104 for key in fields:
105 if key in dic:
106 sg[key] = dic.pop(key)
107
108 membs = {}
109 for vnfd_memb in dic.pop(self.VNFD_MEMBERS):
110 vnfd_idx = vnfd_memb[self.MEM_VNF_INDEX_REF]
111 if vnfd_idx in self.vnfds:
112 membs[self.vnfds[vnfd_idx].name] = \
113 vnfd_memb[self.COUNT]
114 sg['vnfd_members'] = membs
115
116 trigs = {}
117 if self.SCALE_ACT in dic:
118 for sg_act in dic.pop(self.SCALE_ACT):
119 # Validate the primitive
120 prim = sg_act.pop(self.NS_CONF_PRIM_REF)
121 for cprim in self.conf_prims:
122 if cprim[self.NAME] == prim:
123 trigs[sg_act.pop(self.TRIGGER)] = prim
124 break
125 if len(sg_act):
126 err_msg = (_("{0}, Did not find config-primitive {1}").
127 format(self, prim))
128 self.log.error(err_msg)
129 raise ValidationError(message=err_msg)
130 sg[self.CONFIG_ACTIONS] = trigs
131
132 if len(dic):
133 self.log.warn(_("{0}, Did not process all fields for {1}").
134 format(self, dic))
135 self.log.debug(_("{0}, Scale group {1}").format(self, sg))
136 self.scale_grps.append(sg)
137
138 def process_initial_config(dic):
139 icp = {}
140 self.log.debug(_("{0}, initial config: {1}").format(self, dic))
141 for key in [self.NAME, self.SEQ, self.USER_DEF_SCRIPT]:
142 if key in dic:
143 icp[key] = dic.pop(key)
144
145 params = {}
146 if self.PARAM in dic:
147 for p in dic.pop(self.PARAM):
148 if (self.NAME in p and
149 self.VALUE in p):
150 params[p[self.NAME]] = p[self.VALUE]
151 else:
152 # TODO (pjoseph): Need to add support to read the
153 # config file and get the value from that
154 self.log.warn(_("{0}, Got parameter without value: {1}").
155 format(self, p))
156 if len(params):
157 icp[self.PARAM] = params
158
159 if len(dic):
160 self.log.warn(_("{0}, Did not process all fields for {1}").
161 format(self, dic))
162 self.log.debug(_("{0}, Initial config {1}").format(self, icp))
163 self.initial_cfg.append(icp)
164
165 dic = deepcopy(self.yang)
166 try:
167 for key in self.REQUIRED_FIELDS:
168 self.props[key] = dic.pop(key)
169
170 self.id = self.props[self.ID]
171
172 # Process constituent VNFDs
173 if self.CONST_VNFD in dic:
174 for cvnfd in dic.pop(self.CONST_VNFD):
175 process_const_vnfd(cvnfd)
176
177 # Process VLDs
178 if self.VLD in dic:
179 for vld_dic in dic.pop(self.VLD):
180 vld = YangVld(self.log, vld_dic.pop(self.NAME),
181 self.VLD, vld_dic)
182 vld.process_vld(self.vnfds)
183 self.vlds.append(vld)
184
185 # Process config primitives
186 if self.CONF_PRIM in dic:
187 for cprim in dic.pop(self.CONF_PRIM):
188 conf_prim = {self.NAME: cprim.pop(self.NAME)}
189 if self.USER_DEF_SCRIPT in cprim:
190 conf_prim[self.USER_DEF_SCRIPT] = \
191 cprim.pop(self.USER_DEF_SCRIPT)
192 self.conf_prims.append(conf_prim)
193 else:
194 err_msg = (_("{0}, Only user defined script supported "
195 "in config-primitive for now {}: {}").
196 format(self, conf_prim, cprim))
197 self.log.error(err_msg)
198 raise ValidationError(message=err_msg)
199
200 # Process scaling group
201 if self.SCALE_GRP in dic:
202 for sg_dic in dic.pop(self.SCALE_GRP):
203 process_scale_grp(sg_dic)
204
205 # Process initial config primitives
206 if self.INITIAL_CFG in dic:
207 for icp_dic in dic.pop(self.INITIAL_CFG):
208 process_initial_config(icp_dic)
209
210 # Process the input params
211 if self.INPUT_PARAM_XPATH in dic:
212 for param in dic.pop(self.INPUT_PARAM_XPATH):
213 process_input_param(param)
214
215 self.remove_ignored_fields(dic)
216 if len(dic):
217 self.log.warn(_("{0}, Did not process the following for "
218 "NSD {1}: {2}").
219 format(self, self.props, dic))
220 self.log.debug(_("{0}, NSD: {1}").format(self, self.props))
221 except Exception as e:
222 err_msg = _("Exception processing NSD {0} : {1}"). \
223 format(self.name, e)
224 self.log.error(err_msg)
225 self.log.exception(e)
226 raise ValidationError(message=err_msg)
227
228 def generate_tosca_type(self):
229 self.log.debug(_("{0} Generate tosa types").
230 format(self))
231
232 tosca = {}
233 tosca[self.DATA_TYPES] = {}
234 tosca[self.NODE_TYPES] = {}
235
236 for idx, vnfd in self.vnfds.items():
237 tosca = vnfd.generate_tosca_type(tosca)
238
239 for vld in self.vlds:
240 tosca = vld.generate_tosca_type(tosca)
241
242 # Generate type for config primitives
243 if self.GROUP_TYPES not in tosca:
244 tosca[self.GROUP_TYPES] = {}
245 if self.T_CONF_PRIM not in tosca[self.GROUP_TYPES]:
246 tosca[self.GROUP_TYPES][self.T_CONF_PRIM] = {
247 self.DERIVED_FROM: 'tosca.policies.Root',
248 self.PROPERTIES: {
249 'primitive': self.MAP
250 }}
251
252 # Generate type for scaling group
253 if self.POLICY_TYPES not in tosca:
254 tosca[self.POLICY_TYPES] = {}
255 if self.T_SCALE_GRP not in tosca[self.POLICY_TYPES]:
256 tosca[self.POLICY_TYPES][self.T_SCALE_GRP] = {
257 self.DERIVED_FROM: 'tosca.policies.Root',
258 self.PROPERTIES: {
259 self.NAME:
260 {self.TYPE: self.STRING},
261 self.MAX_INST_COUNT:
262 {self.TYPE: self.INTEGER},
263 self.MIN_INST_COUNT:
264 {self.TYPE: self.INTEGER},
265 'vnfd_members':
266 {self.TYPE: self.MAP},
267 self.CONFIG_ACTIONS:
268 {self.TYPE: self.MAP}
269 }}
270
271 if self.T_INITIAL_CFG not in tosca[self.POLICY_TYPES]:
272 tosca[self.POLICY_TYPES][self.T_INITIAL_CFG] = {
273 self.DERIVED_FROM: 'tosca.policies.Root',
274 self.PROPERTIES: {
275 self.NAME:
276 {self.TYPE: self.STRING},
277 self.SEQ:
278 {self.TYPE: self.INTEGER},
279 self.USER_DEF_SCRIPT:
280 {self.TYPE: self.STRING},
281 self.PARAM:
282 {self.TYPE: self.MAP},
283 }}
284
285 return tosca
286
287 def generate_tosca_template(self, tosca):
288 self.log.debug(_("{0}, Generate tosca template").
289 format(self, tosca))
290
291 # Add the standard entries
292 tosca['tosca_definitions_version'] = \
293 'tosca_simple_profile_for_nfv_1_0_0'
294 tosca[self.DESC] = self.props[self.DESC]
295 tosca[self.METADATA] = {
296 'ID': self.name,
297 self.VENDOR: self.props[self.VENDOR],
298 self.VERSION: self.props[self.VERSION],
299 }
300
301 tosca[self.TOPOLOGY_TMPL] = {}
302
303 # Add input params
304 if len(self.inputs):
305 if self.INPUTS not in tosca[self.TOPOLOGY_TMPL]:
306 tosca[self.TOPOLOGY_TMPL][self.INPUTS] = {}
307 for inp in self.inputs:
308 entry = {inp[self.NAME]: {self.TYPE: self.STRING,
309 self.DESC:
310 'Translated from YANG'}}
311 tosca[self.TOPOLOGY_TMPL][self.INPUTS] = entry
312
313 tosca[self.TOPOLOGY_TMPL][self.NODE_TMPL] = {}
314
315 # Add the VNFDs and VLDs
316 for idx, vnfd in self.vnfds.items():
317 vnfd.generate_vnf_template(tosca, idx)
318
319 for vld in self.vlds:
320 vld.generate_tosca_template(tosca)
321
322 # add the config primitives
323 if len(self.conf_prims):
324 if self.GROUPS not in tosca[self.TOPOLOGY_TMPL]:
325 tosca[self.TOPOLOGY_TMPL][self.GROUPS] = {}
326
327 conf_prims = {
328 self.TYPE: self.T_CONF_PRIM
329 }
330 conf_prims[self.MEMBERS] = [vnfd.name for vnfd in
331 self.vnfds.values()]
332 prims = {}
333 for confp in self.conf_prims:
334 prims[confp[self.NAME]] = {
335 self.USER_DEF_SCRIPT: confp[self.USER_DEF_SCRIPT]
336 }
337 conf_prims[self.PROPERTIES] = {
338 self.PRIMITIVES: prims
339 }
340
341 tosca[self.TOPOLOGY_TMPL][self.GROUPS][self.CONF_PRIM] = conf_prims
342
343
344 # Add the scale group
345 if len(self.scale_grps):
346 if self.POLICIES not in tosca[self.TOPOLOGY_TMPL]:
347 tosca[self.TOPOLOGY_TMPL][self.POLICIES] = []
348
349 for sg in self.scale_grps:
350 sgt = {
351 self.TYPE: self.T_SCALE_GRP,
352 }
353 sgt.update(sg)
354 tosca[self.TOPOLOGY_TMPL][self.POLICIES].append({
355 self.SCALE_GRP: sgt
356 })
357
358 # Add initial configs
359 if len(self.initial_cfg):
360 if self.POLICIES not in tosca[self.TOPOLOGY_TMPL]:
361 tosca[self.TOPOLOGY_TMPL][self.POLICIES] = []
362
363 for icp in self.initial_cfg:
364 icpt = {
365 self.TYPE: self.T_INITIAL_CFG,
366 }
367 icpt.update(icp)
368 tosca[self.TOPOLOGY_TMPL][self.POLICIES].append({
369 self.INITIAL_CFG: icpt
370 })
371
372 return tosca
373
374 def get_supporting_files(self):
375 files = []
376
377 for vnfd in self.vnfds.values():
378 f = vnfd.get_supporting_files()
379 if f and len(f):
380 files.extend(f)
381
382 # Get the config files for initial config
383 for icp in self.initial_cfg:
384 if self.USER_DEF_SCRIPT in icp:
385 script = os.path.basename(icp[self.USER_DEF_SCRIPT])
386 files.append({
387 self.TYPE: 'script',
388 self.NAME: script,
389 self.DEST: "{}/{}".format(self.SCRIPT_DIR, script),
390 })
391
392 # TODO (pjoseph): Add support for config scripts,
393 # charms, etc
394
395 self.log.debug(_("{0}, supporting files: {1}").format(self, files))
396 return files