* YANG to TOSCA VNFFG Support
[osm/SO.git] / common / python / rift / mano / yang_translator / rwmano / yang / yang_vdu.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 import os
17 import shutil
18 import tempfile
19
20 from copy import deepcopy
21
22 from rift.mano.yang_translator.common.exception import ValidationError
23 from rift.mano.yang_translator.common.utils import _
24 from rift.mano.yang_translator.rwmano.syntax.tosca_resource \
25 import ToscaResource
26
27 import rift.package.image
28
29 TARGET_CLASS_NAME = 'YangVdu'
30
31
32 class YangVdu(ToscaResource):
33 '''Class for RIFT.io YANG VDU descriptor translation to TOSCA type.'''
34
35 yangtype = 'vdu'
36
37 OTHER_KEYS = (VM_FLAVOR, CLOUD_INIT, IMAGE, IMAGE_CHKSUM,
38 VNFD_CP_REF, CP_TYPE, CLOUD_INIT_FILE,) = \
39 ('vm_flavor', 'cloud_init', 'image', 'image_checksum',
40 'vnfd_connection_point_ref', 'cp_type', 'cloud_init_file',)
41
42 TOSCA_MISC_KEYS = (VIRT_LINK, VIRT_BIND, VDU_INTF_NAME,
43 VDU_INTF_TYPE) = \
44 ('virtualLink', 'virtualBinding', 'vdu_intf_name',
45 'vdu_intf_type')
46
47 VM_FLAVOR_MAP = {
48 'vcpu_count': 'num_cpus',
49 'memory_mb': 'mem_size',
50 'storage_gb': 'disk_size',
51 }
52
53 VM_SIZE_UNITS_MAP = {
54 'vcpu_count': '',
55 'memory_mb': ' MB',
56 'storage_gb': ' GB',
57 }
58
59 TOSCA_MEM_SIZE = {
60 'LARGE': 'huge',
61 'SMALL': 'normal',
62 'SIZE_2MB': 'size_2MB',
63 'SIZE_1GB': 'size_1GB',
64 'PREFER_LARGE': 'prefer_huge'
65
66 }
67
68 def __init__(self,
69 log,
70 name,
71 type_,
72 yang):
73 super(YangVdu, self).__init__(log,
74 name,
75 type_,
76 yang)
77 self.yang = yang
78 self.props = {}
79 self.ext_cp = []
80 self.int_cp = []
81 self.image = None
82 self.cloud_init_file = None
83 self.host_epa = None
84 self.vswitch_epa = None
85 self.hypervisor_epa = None
86 self.guest_epa = None
87 self.cp_name_to_cp_node = {}
88 self.pinning_epa_prop = {}
89 self.mem_page_guest_epa = None
90 self.conn_point_to_conection_node = {}
91
92 def process_vdu(self):
93 self.log.debug(_("Process VDU desc {0}: {1}").format(self.name,
94 self.yang))
95
96 vdu_dic = deepcopy(self.yang)
97 vdu = {}
98
99 fields = [self.ID, self.COUNT, self.CLOUD_INIT,
100 self.IMAGE, self.IMAGE_CHKSUM, self.CLOUD_INIT_FILE,]
101 for key in fields:
102 if key in vdu_dic:
103 vdu[key] = vdu_dic.pop(key)
104
105 self.id = vdu[self.ID]
106
107 if self.VM_FLAVOR in vdu_dic:
108 vdu[self.NFV_COMPUTE] = {}
109 for key, value in vdu_dic.pop(self.VM_FLAVOR).items():
110 vdu[self.NFV_COMPUTE][self.VM_FLAVOR_MAP[key]] = "{}{}". \
111 format(value, self.VM_SIZE_UNITS_MAP[key])
112
113 if self.EXT_INTF in vdu_dic:
114 for ext_intf in vdu_dic.pop(self.EXT_INTF):
115 cp = {}
116 cp[self.NAME] = ext_intf.pop(self.VNFD_CP_REF)
117 cp[self.VDU_INTF_NAME] = ext_intf.pop(self.NAME)
118 cp[self.VDU_INTF_TYPE] = ext_intf[self.VIRT_INTF][self.TYPE_Y]
119 self.log.debug(_("{0}, External interface {1}: {2}").
120 format(self, cp, ext_intf))
121 self.ext_cp.append(cp)
122
123 if self.HOST_EPA in vdu_dic:
124 host_epa = vdu_dic.pop(self.HOST_EPA)
125 host_epa_prop = {}
126 self.host_epa = host_epa
127 '''
128 if 'cpu_model' in host_epa:
129 host_epa_prop['cpu_model'] = host_epa['cpu_model'].lower()
130 if 'cpu_arch' in host_epa:
131 host_epa_prop['cpu_arch'] = host_epa['cpu_arch'].lower()
132 if 'cpu_vendor' in host_epa:
133 host_epa_prop['cpu_vendor'] = host_epa['cpu_vendor'].lower()
134 if 'cpu_socket_count' in host_epa:
135 host_epa_prop['cpu_socket_count'] = host_epa['cpu_socket_count']
136 if 'cpu_core_count' in host_epa:
137 host_epa_prop['cpu_core_count'] = host_epa['cpu_core_count']
138 if 'cpu_core_thread_count' in host_epa:
139 host_epa_prop['cpu_core_thread_count'] = host_epa['cpu_core_thread_count']
140 if 'om_cpu_model_string' in host_epa:
141 host_epa_prop['om_cpu_model_string'] = host_epa['om_cpu_model_string']
142 if 'cpu_feature' in host_epa:
143 host_epa_prop['cpu_feature'] = []
144 for cpu_feature in host_epa['cpu_feature']:
145 cpu_feature_prop = {}
146 cpu_feature_prop['feature'] = cpu_feature['feature'].lower()
147 host_epa_prop['cpu_feature'] .append(cpu_feature_prop)
148
149 if 'om_cpu_feature' in host_epa:
150 host_epa_prop['om_cpu_feature'] = []
151 for cpu_feature in host_epa['om_cpu_feature']:
152 om_cpu_feature_prop = {}
153 om_cpu_feature_prop['feature'] = cpu_feature
154 host_epa_prop['om_cpu_feature'].append(om_cpu_feature_prop)
155 self.host_epa = host_epa
156 '''
157 # We might have to re write this piece of code, there are mismatch in
158 # enum names. Its all capital in RIFT yang and TOSCA
159 if self.VSWITCH_EPA in vdu_dic:
160 vswitch_epa = vdu_dic.pop(self.VSWITCH_EPA)
161 self.vswitch_epa = vswitch_epa
162 if self.HYPERVISOR_EPA in vdu_dic:
163 hypervisor_epa = vdu_dic.pop(self.HYPERVISOR_EPA)
164 hypervisor_epa_prop = {}
165
166 if 'type_yang' in hypervisor_epa:
167 hypervisor_epa_prop['type'] = hypervisor_epa['type_yang']
168 if 'version' in hypervisor_epa:
169 hypervisor_epa_prop['version'] = str(hypervisor_epa['version'])
170 else:
171 hypervisor_epa_prop['version'] = '1'
172 self.hypervisor_epa = hypervisor_epa_prop
173
174 if self.GUEST_EPA in vdu_dic:
175 guest_epa = vdu_dic[self.GUEST_EPA]
176 guest_epa_prop = {}
177
178 # This is a hack. I have to rewrite this. I have got this quick to working
179 # 'ANY' check should be added in riftio common file. Its not working for some reason. Will fix.
180
181 if 'cpu_pinning_policy' in guest_epa and guest_epa['cpu_pinning_policy'] != 'ANY':
182 self.pinning_epa_prop['cpu_affinity'] = guest_epa['cpu_pinning_policy'].lower()
183 if 'cpu_thread_pinning_policy' in guest_epa:
184 self.pinning_epa_prop['thread_allocation'] = guest_epa['cpu_thread_pinning_policy'].lower()
185 if 'mempage_size' in guest_epa:
186 self.mem_page_guest_epa = self.TOSCA_MEM_SIZE[guest_epa['mempage_size']]
187
188 if 'numa_node_policy' in guest_epa:
189 num_node_policy = guest_epa['numa_node_policy']
190 if 'node_cnt' in num_node_policy:
191 guest_epa_prop['node_cnt'] = num_node_policy['node_cnt']
192 if 'mem_policy' in num_node_policy:
193 guest_epa_prop['mem_policy'] = num_node_policy['mem_policy']
194 if 'node' in num_node_policy:
195 nodes = []
196 for node in num_node_policy['node']:
197 node_prop = {}
198 if 'id' in node:
199 node_prop['id'] = node['id']
200 if 'vcpu' in node:
201 vc =[]
202 for vcp in node['vcpu']:
203 vc.append(vcp['id'])
204
205 node_prop['vcpus'] = vc
206 if 'memory_mb' in node:
207 node_prop['mem_size'] = "{} MB".format(node['memory_mb'])
208 # om_numa_type generation
209
210 if 'num_cores' in node:
211 node_prop['om_numa_type'] = 'num_cores'
212 node_prop['num_cores'] = node['num_cores']
213 elif 'paired_threads' in node:
214 node_prop['om_numa_type'] = 'paired-threads'
215 node_prop['paired_threads'] = node['paired_threads']
216 elif 'threads]' in node:
217 node_prop['om_numa_type'] = 'threads]'
218 node_prop['num_thread]'] = node['threads]']
219
220 nodes.append(node_prop)
221 guest_epa_prop['node'] = nodes
222
223 self.guest_epa = guest_epa_prop
224
225 self.remove_ignored_fields(vdu_dic)
226
227 for cp in self.ext_cp:
228 cp_name = cp[self.NAME].replace('/', '_')
229 self.conn_point_to_conection_node[cp[self.NAME]] = cp_name
230
231
232 if len(vdu_dic):
233 self.log.warn(_("{0}, Did not process the following in "
234 "VDU: {1}").
235 format(self, vdu_dic))
236
237 self.log.debug(_("{0} VDU: {1}").format(self, vdu))
238 self.props = vdu
239
240 def get_cp(self, name):
241 for cp in self.ext_cp:
242 if cp[self.NAME] == name:
243 return cp
244 return None
245
246 def has_cp(self, name):
247 if self.get_cp(name):
248 return True
249 return False
250
251 def set_cp_type(self, name, cp_type):
252 for idx, cp in enumerate(self.ext_cp):
253 if cp[self.NAME] == name:
254 cp[self.CP_TYPE] = cp_type
255 self.ext_cp[idx] = cp
256 self.log.debug(_("{0}, Updated CP: {1}").
257 format(self, self.ext_cp[idx]))
258 return
259
260 err_msg = (_("{0}, Did not find connection point {1}").
261 format(self, name))
262 self.log.error(err_msg)
263 raise ValidationError(message=err_msg)
264
265 def set_vld(self, name, vld_name):
266 cp = self.get_cp(name)
267 if cp:
268 cp[self.VLD] = vld_name
269 else:
270 err_msg = (_("{0}, Did not find connection point {1}").
271 format(self, name))
272 self.log.error(err_msg)
273 raise ValidationError(message=err_msg)
274
275 def get_name(self, vnf_name):
276 # Create a unique name incase multiple VNFs use same
277 # name for the vdu
278 return "{}_{}".format(vnf_name, self.name)
279 #return self.name
280
281 def generate_tosca_type(self, tosca):
282 self.log.debug(_("{0} Generate tosa types").
283 format(self, tosca))
284
285 # Add custom artifact type
286 if self.ARTIFACT_TYPES not in tosca:
287 tosca[self.ARTIFACT_TYPES] = {}
288 if self.T_ARTF_QCOW2 not in tosca[self.ARTIFACT_TYPES]:
289 tosca[self.ARTIFACT_TYPES][self.T_ARTF_QCOW2] = {
290 self.DERIVED_FROM: 'tosca.artifacts.Deployment.Image.VM.QCOW2',
291 self.IMAGE_CHKSUM:
292 {self.TYPE: self.STRING,
293 self.REQUIRED: self.NO},
294 }
295
296 if self.T_VDU1 not in tosca[self.NODE_TYPES]:
297 tosca[self.NODE_TYPES][self.T_VDU1] = {
298 self.DERIVED_FROM: 'tosca.nodes.nfv.VDU',
299 self.PROPERTIES: {
300 self.COUNT:
301 {self.TYPE: self.INTEGER,
302 self.DEFAULT: 1},
303 self.CLOUD_INIT:
304 {self.TYPE: self.STRING,
305 self.REQUIRED: self.NO,},
306 self.CLOUD_INIT_FILE:
307 {self.TYPE: self.STRING,
308 self.REQUIRED: self.NO,},
309 },
310 self.CAPABILITIES: {
311 self.VIRT_LINK: {
312 self.TYPE: 'tosca.capabilities.nfv.VirtualLinkable'
313 },
314 },
315 }
316
317 # Add CP type
318 if self.T_CP1 not in tosca[self.NODE_TYPES]:
319 tosca[self.NODE_TYPES][self.T_CP1] = {
320 self.DERIVED_FROM: 'tosca.nodes.nfv.CP',
321 self.PROPERTIES: {
322 self.NAME:
323 {self.TYPE: self.STRING,
324 self.DESC: 'Name of the connection point'},
325 self.CP_TYPE:
326 {self.TYPE: self.STRING,
327 self.DESC: 'Type of the connection point'},
328 self.VDU_INTF_NAME:
329 {self.TYPE: self.STRING,
330 self.DESC: 'Name of the interface on VDU'},
331 self.VDU_INTF_TYPE:
332 {self.TYPE: self.STRING,
333 self.DESC: 'Type of the interface on VDU'},
334 },
335 }
336
337 return tosca
338
339 def generate_vdu_template(self, tosca, vnf_name):
340 self.log.debug(_("{0} Generate tosca template for {2}").
341 format(self, tosca, vnf_name))
342
343 name = self.get_name(vnf_name)
344
345 node = {}
346 node[self.TYPE] = self.T_VDU1
347 node[self.CAPABILITIES] = {}
348
349 if self.NFV_COMPUTE in self.props:
350 node[self.CAPABILITIES][self.NFV_COMPUTE] = {self.PROPERTIES: self.props.pop(self.NFV_COMPUTE)}
351 else:
352 self.log.warn(_("{0}, Does not have host requirements defined").
353 format(self))
354 if self.host_epa:
355 node[self.CAPABILITIES][self.HOST_EPA] = {
356 self.PROPERTIES: self.host_epa
357 }
358 if self.vswitch_epa:
359 node[self.CAPABILITIES][self.VSWITCH_EPA] = {
360 self.PROPERTIES: self.vswitch_epa
361 }
362 if self.hypervisor_epa:
363 node[self.CAPABILITIES][self.HYPERVISOR_EPA] = {
364 self.PROPERTIES: self.hypervisor_epa
365 }
366 if self.guest_epa:
367 node[self.CAPABILITIES]['numa_extension'] = {
368 self.PROPERTIES: self.guest_epa
369 }
370 if len(self.pinning_epa_prop) > 0:
371 if node[self.CAPABILITIES][self.NFV_COMPUTE] and node[self.CAPABILITIES][self.NFV_COMPUTE][self.PROPERTIES]:
372 node[self.CAPABILITIES][self.NFV_COMPUTE][self.PROPERTIES]['cpu_allocation'] = self.pinning_epa_prop
373 if self.mem_page_guest_epa:
374 if node[self.CAPABILITIES][self.NFV_COMPUTE] and node[self.CAPABILITIES][self.NFV_COMPUTE][self.PROPERTIES]:
375 node[self.CAPABILITIES][self.NFV_COMPUTE][self.PROPERTIES]['mem_page_size'] = self.mem_page_guest_epa
376
377 if self.IMAGE in self.props:
378 img_name = "{}_{}_vm_image".format(vnf_name, self.name)
379 image = "../{}/{}".format(self.IMAGE_DIR, self.props.pop(self.IMAGE))
380 self.image = image
381 node[self.ARTIFACTS] = {img_name: {
382 self.FILE: image,
383 self.TYPE: self.T_ARTF_QCOW2,
384 }}
385 if self.IMAGE_CHKSUM in self.props:
386 node[self.ARTIFACTS][img_name][self.IMAGE_CHKSUM] = \
387 self.props.pop(self.IMAGE_CHKSUM)
388 node[self.INTERFACES] = {'Standard': {
389 'create': img_name
390 }}
391 # Add cloud init script if available
392 if self.CLOUD_INIT_FILE in self.props:
393 cloud_name = "{}_{}_cloud_init".format(vnf_name, self.name)
394 self.cloud_init_file = self.props[self.CLOUD_INIT_FILE]
395 cloud_init_file = "../{}/{}".format(self.CLOUD_INIT_DIR, self.props.pop(self.CLOUD_INIT_FILE))
396 if self.ARTIFACTS in node:
397 node[self.ARTIFACTS][cloud_name] = {
398 self.FILE: cloud_init_file,
399 self.TYPE: self.T_ARTF_CLOUD_INIT,
400 }
401 else:
402 node[self.ARTIFACTS] = {
403 cloud_name: {
404 self.FILE: cloud_init_file,
405 self.TYPE: self.T_ARTF_CLOUD_INIT,
406 }}
407
408 # Remove
409 self.props.pop(self.ID)
410 node[self.PROPERTIES] = self.props
411
412 self.log.debug(_("{0}, VDU node: {1}").format(self, node))
413 tosca[self.TOPOLOGY_TMPL][self.NODE_TMPL][name] = node
414
415 # Generate the connection point templates
416 for cp in self.ext_cp:
417 cpt = {self.TYPE: self.T_CP1}
418
419 cpt[self.REQUIREMENTS] = []
420 cpt[self.REQUIREMENTS].append({self.VIRT_BIND: {
421 self.NODE: self.get_name(vnf_name)
422 }})
423 if self.VLD in cp:
424 vld = cp.pop(self.VLD)
425 cpt[self.REQUIREMENTS].append({self.VIRT_LINK: {
426 self.NODE: vld
427 }})
428
429 cpt[self.PROPERTIES] = cp
430 cp_name = cp[self.NAME].replace('/', '_')
431 self.cp_name_to_cp_node[cp[self.NAME]] = cp_name
432
433 self.log.debug(_("{0}, CP node {1}: {2}").
434 format(self, cp_name, cpt))
435 tosca[self.TOPOLOGY_TMPL][self.NODE_TMPL][cp_name] = cpt
436
437 return tosca
438
439 def get_supporting_files(self):
440 files = []
441
442 if self.image is not None:
443 image_name = os.path.basename(self.image)
444
445 files.append({
446 self.TYPE: 'image',
447 self.NAME: image_name,
448 self.DEST: "{}/{}".format(self.IMAGE_DIR, image_name),
449 })
450
451 if self.cloud_init_file is not None:
452 files.append({
453 self.TYPE: 'cloud_init',
454 self.NAME: self.cloud_init_file,
455 self.DEST: "{}/{}".format(self.CLOUD_INIT, self.cloud_init_file)
456 })
457
458 self.log.debug(_("Supporting files for {} : {}").format(self, files))
459
460 return files