update from RIFT as of 696b75d2fe9fb046261b08c616f1bcf6c0b54a9b second try
[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 vdu_props[key] = value
105
106 if 'name' not in vdu_props:
107 vdu_props['name'] = self.name
108
109 if 'id' not in vdu_props:
110 vdu_props['id'] = self.id
111
112 if 'count' not in vdu_props:
113 vdu_props['count'] = 1
114
115 self.log.debug(_("VDU {0} properties: {1}").
116 format(self.name, vdu_props))
117 self.properties = vdu_props
118
119 def handle_capabilities(self):
120
121 def get_mgmt_interface(specs):
122 mgmt_intfce = {}
123 mgmt_intfce['vdu-id'] = self.id
124 if 'dashboard_params' in specs:
125 mgmt_intfce['dashboard-params'] = {'path':specs['dashboard_params']['path'], 'port':specs['dashboard_params']['port']}
126 if 'port' in specs:
127 mgmt_intfce['port'] = specs['port']
128 return mgmt_intfce;
129
130 def get_monitor_param(specs, monitor_id):
131 monitor_param = {}
132 monitor_param['id'] = monitor_id
133 if 'name' in specs:
134 monitor_param['name'] = specs['name']
135 if 'json_query_method' in specs:
136 monitor_param['json_query_method'] = specs['json_query_method'].upper()
137 if 'description' in specs:
138 monitor_param['description'] = specs['description']
139 if 'url_path' in specs:
140 monitor_param['http-endpoint-ref'] = specs['url_path']
141 if 'ui_data' in specs:
142 if 'widget_type' in specs['ui_data']:
143 monitor_param['widget-type'] = specs['ui_data']['widget_type'].upper()
144 if 'units' in specs['ui_data']:
145 monitor_param['units'] = specs['ui_data']['units']
146 if 'group_tag' in specs['ui_data']:
147 monitor_param['group_tag'] = specs['ui_data']['group_tag']
148 if 'constraints' in specs:
149 if 'value_type' in specs['constraints']:
150 monitor_param['value-type'] = ToscaCompute.VALUE_TYPE_CONVERSION_MAP[specs['constraints']['value_type']]
151
152 return monitor_param
153
154 def get_vm_flavor(specs):
155 vm_flavor = {}
156 if 'num_cpus' in specs:
157 vm_flavor['vcpu-count'] = specs['num_cpus']
158 else:
159 vm_flavor['vcpu-count'] = 1
160
161 if 'mem_size' in specs:
162 vm_flavor['memory-mb'] = (ScalarUnit_Size(specs['mem_size']).
163 get_num_from_scalar_unit('MB'))
164 else:
165 vm_flavor['memory-mb'] = 512
166
167 if 'disk_size' in specs:
168 vm_flavor['storage-gb'] = (ScalarUnit_Size(specs['disk_size']).
169 get_num_from_scalar_unit('GB'))
170 else:
171 vm_flavor['storage-gb'] = 4
172
173 return vm_flavor
174
175 def get_host_epa(specs):
176 host_epa = {}
177 if 'cpu_model' in specs:
178 host_epa["cpu-model"] = specs['cpu_model'].upper()
179 if 'cpu_arch' in specs:
180 host_epa["cpu-arch"] = specs['cpu_arch'].upper()
181 if 'cpu_vendor' in specs:
182 host_epa["cpu-vendor"] = specs['cpu_vendor'].upper()
183 if 'cpu_socket_count' in specs:
184 host_epa["cpu-socket-count"] = specs['cpu_socket_count']
185 if 'cpu_core_count' in specs:
186 host_epa["cpu-core-count"] = specs['cpu_core_count']
187 if 'cpu_core_thread_count' in specs:
188 host_epa["cpu-core-thread-count"] = specs['cpu_core_thread_count']
189 if 'om_cpu_model_string' in specs:
190 host_epa["om-cpu-model-string"] = specs['om_cpu_model_string']
191 if 'cpu_feature' in specs:
192 cpu_feature_prop = []
193 for spec in specs['cpu_feature']:
194 cpu_feature_prop.append({'feature':spec.upper()})
195 host_epa['cpu-feature'] = cpu_feature_prop
196 if 'om_cpu_feature' in specs:
197 cpu_feature_prop = []
198 for spec in specs['om_cpu_feature']:
199 cpu_feature_prop.append({'feature':spec})
200 host_epa['om-cpu-feature'] = cpu_feature_prop
201 return host_epa;
202
203 def get_vswitch_epa(specs):
204 vswitch_epa = {}
205 if 'ovs_acceleration' in specs:
206 vswitch_epa['ovs-acceleration'] = specs['ovs_acceleration'].upper()
207 if 'ovs_offload' in specs:
208 vswitch_epa['ovs-offload'] = specs['ovs_offload'].upper()
209 return vswitch_epa
210
211 def get_hypervisor_epa(specs):
212 hypervisor_epa = {}
213 if 'type' in specs:
214 hypervisor_epa['type'] = specs['type'].upper()
215 if 'version' in specs:
216 hypervisor_epa['version'] = str(specs['version'])
217
218 return hypervisor_epa
219
220 def get_guest_epa(specs, nfv_comput_specs):
221 guest_epa = {}
222 guest_epa['numa-node-policy'] = {}
223 guest_epa['numa-node-policy']['node'] = []
224 if 'mem_policy' in specs:
225 guest_epa['numa-node-policy']['mem-policy'] = specs['mem_policy'].upper()
226 if 'node_cnt' in specs:
227 guest_epa['numa-node-policy']['node-cnt'] = specs['node_cnt']
228 if 'node' in specs:
229 for node in specs['node']:
230 node_prop = {}
231 if 'id' in node:
232 node_prop['id'] = node['id']
233 if 'mem_size' in node:
234 if 'MiB' in node['mem_size'] or 'MB' in node['mem_size']:
235 node_prop['memory-mb'] = int(node['mem_size'].replace('MB',''))
236 else:
237 err_msg = "Specify mem_size of NUMA extension should be in MB"
238 raise ValidationError(message=err_msg)
239 if 'vcpus' in node:
240 vcpu_lis =[]
241 for vcpu in node['vcpus']:
242 vcpu_lis.append({'id': vcpu})
243 node_prop['vcpu'] = vcpu_lis
244 if 'om_numa_type' in node:
245 numa_type = node['om_numa_type']
246 if 'paired-threads' == numa_type:
247 node_prop['paired_threads'] = {}
248 node_prop['paired_threads']['num_paired_threads'] = node['paired_threads']['num_paired_threads']
249 elif 'threads' == numa_type:
250 if 'num_threads' in node:
251 node_prop['num_threads'] = node['num_threads']
252 elif 'cores' == numa_type:
253 if 'num_cores' in node:
254 node_prop['num_cores'] = node['num_cores']
255 else:
256 err_msg = "om_numa_type should be among cores, paired-threads or threads"
257 raise ValidationError(message=err_msg)
258 guest_epa['numa-node-policy']['node'].append(node_prop)
259
260 if 'mem_page_size' in nfv_comput_specs:
261 guest_epa['mempage-size'] = self.TOSCA_MEM_SIZE[nfv_comput_specs['mem_page_size']]
262 if 'cpu_allocation' in nfv_comput_specs:
263 if 'cpu_affinity' in nfv_comput_specs['cpu_allocation']:
264 guest_epa['cpu-pinning-policy'] = nfv_comput_specs['cpu_allocation']['cpu_affinity'].upper()
265 guest_epa['trusted-execution'] = False
266 if 'thread_allocation' in nfv_comput_specs['cpu_allocation']:
267 guest_epa['cpu-thread-pinning-policy'] = nfv_comput_specs['cpu_allocation']['thread_allocation'].upper()
268
269 return guest_epa
270
271 tosca_caps = self.get_tosca_caps()
272 self.log.debug(_("VDU {0} tosca capabilites: {1}").
273 format(self.name, tosca_caps))
274 if 'nfv_compute' in tosca_caps:
275 self.properties['vm-flavor'] = get_vm_flavor(tosca_caps['nfv_compute'])
276 self.log.debug(_("VDU {0} properties: {1}").
277 format(self.name, self.properties))
278 if 'host_epa' in tosca_caps:
279 self.properties['host-epa'] = get_host_epa(tosca_caps['host_epa'])
280 if 'hypervisor_epa' in tosca_caps:
281 self.properties['hypervisor-epa'] = get_hypervisor_epa(tosca_caps['hypervisor_epa'])
282 if 'vswitch_epa' in tosca_caps:
283 self.properties['vswitch-epa'] = get_vswitch_epa(tosca_caps['vswitch_epa'])
284 if 'numa_extension' in tosca_caps:
285 self.properties['guest-epa'] = get_guest_epa(tosca_caps['numa_extension'], tosca_caps['nfv_compute'])
286 if 'monitoring_param' in tosca_caps:
287 self._monitor_param.append(get_monitor_param(tosca_caps['monitoring_param'], '1'))
288 if 'mgmt_interface' in tosca_caps:
289 self._mgmt_interface = get_mgmt_interface(tosca_caps['mgmt_interface'])
290 if len(self._mgmt_interface) > 0:
291 prop = {}
292 if 'dashboard-params' in self._mgmt_interface:
293 if 'path' in self._mgmt_interface['dashboard-params']:
294 prop['path'] = self._mgmt_interface['dashboard-params']['path']
295 if 'port' in self._mgmt_interface['dashboard-params']:
296 prop['port'] = self._mgmt_interface['dashboard-params']['port']
297 self._http_endpoint = prop
298
299 mon_idx = 2
300 monitoring_param_name = 'monitoring_param_1'
301 while True:
302 if monitoring_param_name in tosca_caps:
303 self._monitor_param.append(get_monitor_param(tosca_caps[monitoring_param_name], str(mon_idx)))
304 mon_idx += 1
305 monitoring_param_name = 'monitoring_param_{}'.format(mon_idx)
306 else:
307 break
308
309 # THis is a quick hack to remove monitor params without name
310 for mon_param in list(self._monitor_param):
311 if 'name' not in mon_param:
312 self._monitor_param.remove(mon_param)
313
314 def handle_artifacts(self):
315 if self.artifacts is None:
316 return
317 self.log.debug(_("VDU {0} tosca artifacts: {1}").
318 format(self.name, self.artifacts))
319 arts = {}
320 for key in self.artifacts:
321 props = self.artifacts[key]
322 if isinstance(props, dict):
323 details = {}
324 for name, value in props.items():
325 if name == 'type' and value == 'tosca.artifacts.Deployment.Image.riftio.QCOW2':
326 prefix, type_ = value.rsplit('.', 1)
327 if type_ == 'QCOW2':
328 details['type'] = 'qcow2'
329 self._image = props['file']
330 self.properties['image'] = os.path.basename(props['file'])
331 elif name == 'type' and value == 'tosca.artifacts.Deployment.riftio.cloud_init_file':
332 details['cloud_init_file'] = os.path.basename(props['file'])
333 self._cloud_init = props['file']
334 self.properties['cloud_init_file'] = os.path.basename(props['file'])
335 elif name == 'file':
336 details['file'] = value
337 elif name == 'image_checksum':
338 self.properties['image_checksum'] = value
339 else:
340 self.log.warn(_("VDU {0}, unsuported attribute {1}").
341 format(self.name, name))
342 if len(details):
343 arts[key] = details
344 else:
345 arts[key] = self.artifacts[key]
346
347 self.log.debug(_("VDU {0} artifacts: {1}").
348 format(self.name, arts))
349 self.artifacts = arts
350
351 def handle_interfaces(self):
352 # Currently, we support the following:
353 operations_deploy_sequence = ['create', 'configure']
354
355 operations = ManoResource._get_all_operations(self.nodetemplate)
356
357 # use the current ManoResource for the first operation in this order
358 # Currently we only support image in create operation
359 for operation in operations.values():
360 if operation.name in operations_deploy_sequence:
361 self.operations[operation.name] = None
362 try:
363 self.operations[operation.name] = operation.implementation
364 for name, details in self.artifacts.items():
365 if name == operation.implementation:
366 if operation.name == 'create':
367 self._image = details['file']
368 elif operation.name == 'configure':
369 self._cloud_init = details['file']
370 break
371 except KeyError as e:
372 self.log.exception(e)
373 return None
374
375 def update_image_checksum(self, in_file):
376
377 # Create image checksum
378 # in_file is the TOSCA yaml file location
379 if self._image is None:
380 return
381 self.log.debug("Update image: {}".format(in_file))
382 if os.path.exists(in_file):
383 in_dir = os.path.dirname(in_file)
384 img_dir = os.path.dirname(self._image)
385 abs_dir = os.path.normpath(
386 os.path.join(in_dir, img_dir))
387 self.log.debug("Abs path: {}".format(abs_dir))
388 if os.path.isdir(abs_dir):
389 img_path = os.path.join(abs_dir,
390 os.path.basename(self._image))
391 self.log.debug(_("Image path: {0}").
392 format(img_path))
393 if os.path.exists(img_path):
394 # TODO (pjoseph): To be fixed when we can retrieve
395 # the VNF image in Launchpad.
396 # Check if the file is not size 0
397 # else it is a dummy file and to be ignored
398 if os.path.getsize(img_path) != 0:
399 self._image_cksum = ChecksumUtils.get_md5(img_path,
400 log=self.log)
401
402 def get_mano_attribute(self, attribute, args):
403 attr = {}
404 # Convert from a TOSCA attribute for a nodetemplate to a MANO
405 # attribute for the matching resource. Unless there is additional
406 # runtime support, this should be a one to one mapping.
407
408 # Note: We treat private and public IP addresses equally, but
409 # this will change in the future when TOSCA starts to support
410 # multiple private/public IP addresses.
411 self.log.debug(_('Converting TOSCA attribute for a nodetemplate to a MANO \
412 attriute.'))
413 if attribute == 'private_address' or \
414 attribute == 'public_address':
415 attr['get_attr'] = [self.name, 'networks', 'private', 0]
416
417 return attr
418
419 def _update_properties_for_model(self):
420 if self._image:
421 self.properties['image'] = os.path.basename(self._image)
422 if self._image_cksum:
423 self.properties['image-checksum'] = self._image_cksum
424
425 if self._cloud_init:
426 self.properties['cloud-init-file'] = os.path.basename(self._cloud_init)
427
428 for key in ToscaCompute.IGNORE_PROPS:
429 if key in self.properties:
430 self.properties.pop(key)
431
432 def generate_yang_submodel_gi(self, vnfd):
433 if vnfd is None:
434 return None
435 self._update_properties_for_model()
436 props = convert_keys_to_python(self.properties)
437
438 for monitor_param in self._monitor_param:
439 monitor_props = convert_keys_to_python(monitor_param)
440 vnfd.monitoring_param.add().from_dict(monitor_props)
441 try:
442 if len(self._mgmt_interface) > 0:
443 vnfd.mgmt_interface.from_dict(convert_keys_to_python(self._mgmt_interface))
444 if self._http_endpoint:
445 vnfd.http_endpoint.add().from_dict(convert_keys_to_python(self._http_endpoint))
446 vnfd.vdu.add().from_dict(props)
447 except Exception as e:
448 err_msg = _("{0} Exception vdu from dict {1}: {2}"). \
449 format(self, props, e)
450 self.log.error(err_msg)
451 raise e
452
453 def generate_yang_submodel(self):
454 """Generate yang model for the VDU"""
455 self.log.debug(_("Generate YANG model for {0}").
456 format(self))
457
458 self._update_properties_for_model()
459
460 vdu = self.properties
461
462 return vdu