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