* YANG to TOSCA translator
[osm/SO.git] / common / python / rift / mano / tosca_translator / rwmano / tosca / tosca_compute.py
1 #
2 # Licensed under the Apache License, Version 2.0 (the "License"); you may
3 # not use this file except in compliance with the License. You may obtain
4 # a copy of the License at
5 #
6 # http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11 # License for the specific language governing permissions and limitations
12 # under the License.
13 #
14 # Copyright 2016 RIFT.io Inc
15
16
17 import os
18
19 from rift.mano.tosca_translator.common.utils import _
20 from rift.mano.tosca_translator.common.utils import ChecksumUtils
21 from rift.mano.tosca_translator.common.utils import convert_keys_to_python
22 from rift.mano.tosca_translator.rwmano.syntax.mano_resource import ManoResource
23
24 from toscaparser.common.exception import ValidationError
25 from toscaparser.elements.scalarunit import ScalarUnit_Size
26
27 # Name used to dynamically load appropriate map class.
28 TARGET_CLASS_NAME = 'ToscaCompute'
29
30
31 class ToscaCompute(ManoResource):
32 '''Translate TOSCA node type RIFT.io VDUs.'''
33
34 REQUIRED_PROPS = ['name', 'id', 'image', 'count', 'vm-flavor']
35 OPTIONAL_PROPS = [
36 'external-interface',
37 'image-checksum',
38 'cloud-init',
39 'cloud-init-file',]
40 IGNORE_PROPS = []
41
42 toscatype = 'tosca.nodes.nfv.VDU'
43
44 VALUE_TYPE_CONVERSION_MAP = {
45 'integer': 'INT',
46 'string':'STRING',
47 'float':'DECIMAL'
48 }
49
50
51 TOSCA_MEM_SIZE = {
52 'huge': 'LARGE',
53 'normal': 'SMALL',
54 'size_2MB': 'SIZE_2MB',
55 'size_1GB': 'SIZE_1GB',
56 'prefer_huge': 'PREFER_LARGE'
57
58 }
59
60 def __init__(self, log, nodetemplate, metadata=None):
61 super(ToscaCompute, self).__init__(log,
62 nodetemplate,
63 type_='vdu',
64 metadata=metadata)
65 # List with associated port resources with this server
66 self.assoc_port_resources = []
67 self._image = None # Image to bring up the VDU
68 self._image_cksum = None
69 self._cloud_init = None # Cloud init file
70 self._vnf = None
71 self._yang = None
72 self._id = self.name
73 self._monitor_param = []
74 self._mgmt_interface = {}
75 self._http_endpoint = None
76
77 @property
78 def image(self):
79 return self._image
80
81 @property
82 def cloud_init(self):
83 return self._cloud_init
84
85 @property
86 def vnf(self):
87 return self._vnf
88
89 @vnf.setter
90 def vnf(self, vnf):
91 if self._vnf:
92 err_msg = (_('VDU {0} already has a VNF {1} associated').
93 format(self, self._vnf))
94 self.log.error(err_msg)
95 raise ValidationError(message=err_msg)
96 self._vnf = vnf
97
98 def handle_properties(self):
99 tosca_props = self.get_tosca_props()
100 self.log.debug(_("VDU {0} tosca properties: {1}").
101 format(self.name, tosca_props))
102 vdu_props = {}
103 for key, value in tosca_props.items():
104 if key == 'cloud_init':
105 vdu_props['cloud-init'] = value
106 elif key == 'cloud-init-file':
107 self._cloud_init = "../cloud_init/{}".format(value)
108 else:
109 vdu_props[key] = value
110
111 if 'name' not in vdu_props:
112 vdu_props['name'] = self.name
113
114 if 'id' not in vdu_props:
115 vdu_props['id'] = self.id
116
117 if 'count' not in vdu_props:
118 vdu_props['count'] = 1
119
120 self.log.debug(_("VDU {0} properties: {1}").
121 format(self.name, vdu_props))
122 self.properties = vdu_props
123
124 def handle_capabilities(self):
125
126 def get_mgmt_interface(specs):
127 mgmt_intfce = {}
128 mgmt_intfce['vdu-id'] = self.id
129 if 'dashboard_params' in specs:
130 mgmt_intfce['dashboard-params'] = {'path':specs['dashboard_params']['path'], 'port':specs['dashboard_params']['port']}
131 if 'port' in specs:
132 mgmt_intfce['port'] = specs['port']
133 return mgmt_intfce;
134
135 def get_monitor_param(specs, monitor_id):
136 monitor_param = {}
137 monitor_param['id'] = monitor_id
138 if 'name' in specs:
139 monitor_param['name'] = specs['name']
140 if 'json_query_method' in specs:
141 monitor_param['json_query_method'] = specs['json_query_method'].upper()
142 if 'description' in specs:
143 monitor_param['description'] = specs['description']
144 if 'url_path' in specs:
145 monitor_param['http-endpoint-ref'] = specs['url_path']
146 if 'ui_data' in specs:
147 if 'widget_type' in specs['ui_data']:
148 monitor_param['widget-type'] = specs['ui_data']['widget_type'].upper()
149 if 'units' in specs['ui_data']:
150 monitor_param['units'] = specs['ui_data']['units']
151 if 'group_tag' in specs['ui_data']:
152 monitor_param['group_tag'] = specs['ui_data']['group_tag']
153 if 'constraints' in specs:
154 if 'value_type' in specs['constraints']:
155 monitor_param['value-type'] = ToscaCompute.VALUE_TYPE_CONVERSION_MAP[specs['constraints']['value_type']]
156
157 return monitor_param
158
159 def get_vm_flavor(specs):
160 vm_flavor = {}
161 if 'num_cpus' in specs:
162 vm_flavor['vcpu-count'] = specs['num_cpus']
163 else:
164 vm_flavor['vcpu-count'] = 1
165
166 if 'mem_size' in specs:
167 vm_flavor['memory-mb'] = (ScalarUnit_Size(specs['mem_size']).
168 get_num_from_scalar_unit('MB'))
169 else:
170 vm_flavor['memory-mb'] = 512
171
172 if 'disk_size' in specs:
173 vm_flavor['storage-gb'] = (ScalarUnit_Size(specs['disk_size']).
174 get_num_from_scalar_unit('GB'))
175 else:
176 vm_flavor['storage-gb'] = 4
177
178 return vm_flavor
179
180 def get_host_epa(specs):
181 host_epa = {}
182 if 'cpu_model' in specs:
183 host_epa["cpu-model"] = specs['cpu_model'].upper()
184 if 'cpu_arch' in specs:
185 host_epa["cpu-arch"] = specs['cpu_arch'].upper()
186 if 'cpu_vendor' in specs:
187 host_epa["cpu-vendor"] = specs['cpu_vendor'].upper()
188 if 'cpu_socket_count' in specs:
189 host_epa["cpu-socket-count"] = specs['cpu_socket_count']
190 if 'cpu_core_count' in specs:
191 host_epa["cpu-core-count"] = specs['cpu_core_count']
192 if 'cpu_core_thread_count' in specs:
193 host_epa["cpu-core-thread-count"] = specs['cpu_core_thread_count']
194 if 'om_cpu_model_string' in specs:
195 host_epa["om-cpu-model-string"] = specs['om_cpu_model_string']
196 if 'cpu_feature' in specs:
197 cpu_feature_prop = []
198 for spec in specs['cpu_feature']:
199 cpu_feature_prop.append({'feature':spec.upper()})
200 host_epa['cpu-feature'] = cpu_feature_prop
201 if 'om_cpu_feature' in specs:
202 cpu_feature_prop = []
203 for spec in specs['om_cpu_feature']:
204 cpu_feature_prop.append({'feature':spec})
205 host_epa['om-cpu-feature'] = cpu_feature_prop
206 return host_epa;
207
208 def get_vswitch_epa(specs):
209 vswitch_epa = {}
210 if 'ovs_acceleration' in specs:
211 vswitch_epa['ovs-acceleration'] = specs['ovs_acceleration'].upper()
212 if 'ovs_offload' in specs:
213 vswitch_epa['ovs-offload'] = specs['ovs_offload'].upper()
214 return vswitch_epa
215
216 def get_hypervisor_epa(specs):
217 hypervisor_epa = {}
218 if 'type' in specs:
219 hypervisor_epa['type'] = specs['type'].upper()
220 if 'version' in specs:
221 hypervisor_epa['version'] = str(specs['version'])
222
223 return hypervisor_epa
224
225 def get_guest_epa(specs, nfv_comput_specs):
226 guest_epa = {}
227 guest_epa['numa-node-policy'] = {}
228 guest_epa['numa-node-policy']['node'] = []
229 if 'mem_policy' in specs:
230 guest_epa['numa-node-policy']['mem-policy'] = specs['mem_policy'].upper()
231 if 'node_cnt' in specs:
232 guest_epa['numa-node-policy']['node-cnt'] = specs['node_cnt']
233 if 'node' in specs:
234 for node in specs['node']:
235 node_prop = {}
236 if 'id' in node:
237 node_prop['id'] = node['id']
238 if 'mem_size' in node:
239 if 'MiB' in node['mem_size'] or 'MB' in node['mem_size']:
240 node_prop['memory-mb'] = int(node['mem_size'].replace('MB',''))
241 else:
242 err_msg = "Specify mem_size of NUMA extension should be in MB"
243 raise ValidationError(message=err_msg)
244 if 'vcpus' in node:
245 vcpu_lis =[]
246 for vcpu in node['vcpus']:
247 vcpu_lis.append({'id': vcpu})
248 node_prop['vcpu'] = vcpu_lis
249 if 'om_numa_type' in node:
250 numa_type = node['om_numa_type']
251 if 'paired-threads' == numa_type:
252 node_prop['paired_threads'] = {}
253 node_prop['paired_threads']['num_paired_threads'] = node['paired_threads']['num_paired_threads']
254 elif 'threads' == numa_type:
255 if 'num_threads' in node:
256 node_prop['num_threads'] = node['num_threads']
257 elif 'cores' == numa_type:
258 if 'num_cores' in node:
259 node_prop['num_cores'] = node['num_cores']
260 else:
261 err_msg = "om_numa_type should be among cores, paired-threads or threads"
262 raise ValidationError(message=err_msg)
263 guest_epa['numa-node-policy']['node'].append(node_prop)
264
265 if 'mem_page_size' in nfv_comput_specs:
266 guest_epa['mempage-size'] = self.TOSCA_MEM_SIZE[nfv_comput_specs['mem_page_size']]
267 if 'cpu_allocation' in nfv_comput_specs:
268 if 'cpu_affinity' in nfv_comput_specs['cpu_allocation']:
269 guest_epa['cpu-pinning-policy'] = nfv_comput_specs['cpu_allocation']['cpu_affinity'].upper()
270 guest_epa['trusted-execution'] = False
271 if 'thread_allocation' in nfv_comput_specs['cpu_allocation']:
272 guest_epa['cpu-thread-pinning-policy'] = nfv_comput_specs['cpu_allocation']['thread_allocation'].upper()
273
274 return guest_epa
275
276 tosca_caps = self.get_tosca_caps()
277 self.log.debug(_("VDU {0} tosca capabilites: {1}").
278 format(self.name, tosca_caps))
279 if 'nfv_compute' in tosca_caps:
280 self.properties['vm-flavor'] = get_vm_flavor(tosca_caps['nfv_compute'])
281 self.log.debug(_("VDU {0} properties: {1}").
282 format(self.name, self.properties))
283 if 'host_epa' in tosca_caps:
284 self.properties['host-epa'] = get_host_epa(tosca_caps['host_epa'])
285 if 'hypervisor_epa' in tosca_caps:
286 self.properties['hypervisor-epa'] = get_hypervisor_epa(tosca_caps['hypervisor_epa'])
287 if 'vswitch_epa' in tosca_caps:
288 self.properties['vswitch-epa'] = get_vswitch_epa(tosca_caps['vswitch_epa'])
289 if 'numa_extension' in tosca_caps:
290 self.properties['guest-epa'] = get_guest_epa(tosca_caps['numa_extension'], tosca_caps['nfv_compute'])
291 if 'monitoring_param' in tosca_caps:
292 self._monitor_param.append(get_monitor_param(tosca_caps['monitoring_param'], '1'))
293 if 'monitoring_param_1' in tosca_caps:
294 self._monitor_param.append(get_monitor_param(tosca_caps['monitoring_param_1'], '2'))
295 if 'mgmt_interface' in tosca_caps:
296 self._mgmt_interface = get_mgmt_interface(tosca_caps['mgmt_interface'])
297 if len(self._mgmt_interface) > 0:
298 prop = {}
299 if 'dashboard-params' in self._mgmt_interface:
300 if 'path' in self._mgmt_interface['dashboard-params']:
301 prop['path'] = self._mgmt_interface['dashboard-params']['path']
302 if 'port' in self._mgmt_interface['dashboard-params']:
303 prop['port'] = self._mgmt_interface['dashboard-params']['port']
304 self._http_endpoint = prop
305
306
307
308 def handle_artifacts(self):
309 if self.artifacts is None:
310 return
311 self.log.debug(_("VDU {0} tosca artifacts: {1}").
312 format(self.name, self.artifacts))
313 arts = {}
314 for key in self.artifacts:
315 props = self.artifacts[key]
316 if isinstance(props, dict):
317 details = {}
318 for name, value in props.items():
319 if name == 'type' and value == 'tosca.artifacts.Deployment.Image.riftio.QCOW2':
320 prefix, type_ = value.rsplit('.', 1)
321 if type_ == 'QCOW2':
322 details['type'] = 'qcow2'
323 self._image = props['file']
324 self.properties['image'] = os.path.basename(props['file'])
325 elif name == 'type' and value == 'tosca.artifacts.Deployment.riftio.cloud_init_file':
326 details['cloud_init_file'] = os.path.basename(props['file'])
327 self._cloud_init = props['file']
328 self.properties['cloud_init_file'] = os.path.basename(props['file'])
329 elif name == 'file':
330 details['file'] = value
331 elif name == 'image_checksum':
332 self.properties['image_checksum'] = value
333 else:
334 self.log.warn(_("VDU {0}, unsuported attribute {1}").
335 format(self.name, name))
336 if len(details):
337 arts[key] = details
338 else:
339 arts[key] = self.artifacts[key]
340
341 self.log.debug(_("VDU {0} artifacts: {1}").
342 format(self.name, arts))
343 self.artifacts = arts
344
345 def handle_interfaces(self):
346 # Currently, we support only create operation
347 operations_deploy_sequence = ['create']
348
349 operations = ManoResource._get_all_operations(self.nodetemplate)
350
351 # use the current ManoResource for the first operation in this order
352 # Currently we only support image in create operation
353 for operation in operations.values():
354 if operation.name in operations_deploy_sequence:
355 self.operations[operation.name] = None
356 try:
357 self.operations[operation.name] = operation.implementation
358 for name, details in self.artifacts.items():
359 if name == operation.implementation:
360 self._image = details['file']
361 except KeyError as e:
362 self.log.exception(e)
363 return None
364
365 def update_image_checksum(self, in_file):
366
367 # Create image checksum
368 # in_file is the TOSCA yaml file location
369 if self._image is None:
370 return
371 self.log.debug("Update image: {}".format(in_file))
372 if os.path.exists(in_file):
373 in_dir = os.path.dirname(in_file)
374 img_dir = os.path.dirname(self._image)
375 abs_dir = os.path.normpath(
376 os.path.join(in_dir, img_dir))
377 self.log.debug("Abs path: {}".format(abs_dir))
378 if os.path.isdir(abs_dir):
379 img_path = os.path.join(abs_dir,
380 os.path.basename(self._image))
381 self.log.debug(_("Image path: {0}").
382 format(img_path))
383 if os.path.exists(img_path):
384 # TODO (pjoseph): To be fixed when we can retrieve
385 # the VNF image in Launchpad.
386 # Check if the file is not size 0
387 # else it is a dummy file and to be ignored
388 if os.path.getsize(img_path) != 0:
389 self._image_cksum = ChecksumUtils.get_md5(img_path,
390 log=self.log)
391
392 def get_mano_attribute(self, attribute, args):
393 attr = {}
394 # Convert from a TOSCA attribute for a nodetemplate to a MANO
395 # attribute for the matching resource. Unless there is additional
396 # runtime support, this should be a one to one mapping.
397
398 # Note: We treat private and public IP addresses equally, but
399 # this will change in the future when TOSCA starts to support
400 # multiple private/public IP addresses.
401 self.log.debug(_('Converting TOSCA attribute for a nodetemplate to a MANO \
402 attriute.'))
403 if attribute == 'private_address' or \
404 attribute == 'public_address':
405 attr['get_attr'] = [self.name, 'networks', 'private', 0]
406
407 return attr
408
409 def _update_properties_for_model(self):
410 if self._image:
411 self.properties['image'] = os.path.basename(self._image)
412 if self._image_cksum:
413 self.properties['image-checksum'] = self._image_cksum
414
415 for key in ToscaCompute.IGNORE_PROPS:
416 if key in self.properties:
417 self.properties.pop(key)
418
419 def generate_yang_submodel_gi(self, vnfd):
420 if vnfd is None:
421 return None
422 self._update_properties_for_model()
423 props = convert_keys_to_python(self.properties)
424
425 for monitor_param in self._monitor_param:
426 monitor_props = convert_keys_to_python(monitor_param)
427 vnfd.monitoring_param.add().from_dict(monitor_props)
428 try:
429 if len(self._mgmt_interface) > 0:
430 vnfd.mgmt_interface.from_dict(convert_keys_to_python(self._mgmt_interface))
431 if self._http_endpoint:
432 vnfd.http_endpoint.add().from_dict(convert_keys_to_python(self._http_endpoint))
433 vnfd.vdu.add().from_dict(props)
434 except Exception as e:
435 err_msg = _("{0} Exception vdu from dict {1}: {2}"). \
436 format(self, props, e)
437 self.log.error(err_msg)
438 raise e
439
440 def generate_yang_submodel(self):
441 """Generate yang model for the VDU"""
442 self.log.debug(_("Generate YANG model for {0}").
443 format(self))
444
445 self._update_properties_for_model()
446
447 vdu = self.properties
448
449 return vdu