7bc0bf3b12060a00368de7040705e4f31207b196
[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 def __init__(self, log, nodetemplate, metadata=None):
51 super(ToscaCompute, self).__init__(log,
52 nodetemplate,
53 type_='vdu',
54 metadata=metadata)
55 # List with associated port resources with this server
56 self.assoc_port_resources = []
57 self._image = None # Image to bring up the VDU
58 self._image_cksum = None
59 self._cloud_init = None # Cloud init file
60 self._vnf = None
61 self._yang = None
62 self._id = self.name
63 self._monitor_param = []
64 self._mgmt_interface = {}
65
66 @property
67 def image(self):
68 return self._image
69
70 @property
71 def cloud_init(self):
72 return self._cloud_init
73
74 @property
75 def vnf(self):
76 return self._vnf
77
78 @vnf.setter
79 def vnf(self, vnf):
80 if self._vnf:
81 err_msg = (_('VDU {0} already has a VNF {1} associated').
82 format(self, self._vnf))
83 self.log.error(err_msg)
84 raise ValidationError(message=err_msg)
85 self._vnf = vnf
86
87 def handle_properties(self):
88 tosca_props = self.get_tosca_props()
89 self.log.debug(_("VDU {0} tosca properties: {1}").
90 format(self.name, tosca_props))
91 vdu_props = {}
92 for key, value in tosca_props.items():
93 if key == 'cloud_init':
94 vdu_props['cloud-init'] = value
95 elif key == 'cloud-init-file':
96 self._cloud_init = "../cloud_init/{}".format(value)
97 else:
98 vdu_props[key] = value
99
100 if 'name' not in vdu_props:
101 vdu_props['name'] = self.name
102
103 if 'id' not in vdu_props:
104 vdu_props['id'] = self.id
105
106 if 'count' not in vdu_props:
107 vdu_props['count'] = 1
108
109 self.log.debug(_("VDU {0} properties: {1}").
110 format(self.name, vdu_props))
111 self.properties = vdu_props
112
113 def handle_capabilities(self):
114
115 def get_mgmt_interface(specs):
116 mgmt_intfce = {}
117 mgmt_intfce['vdu-id'] = self.id
118 if 'dashboard_params' in specs:
119 mgmt_intfce['dashboard-params'] = {'path':specs['dashboard_params']['path'], 'port':specs['dashboard_params']['port']}
120 if 'port' in specs:
121 mgmt_intfce['port'] = specs['port']
122 return mgmt_intfce;
123
124 def get_monitor_param(specs, monitor_id):
125 monitor_param = {}
126 monitor_param['id'] = monitor_id
127 if 'name' in specs:
128 monitor_param['name'] = specs['name']
129 if 'json_query_method' in specs:
130 monitor_param['json_query_method'] = specs['json_query_method'].upper()
131 if 'description' in specs:
132 monitor_param['description'] = specs['description']
133 if 'url_path' in specs:
134 monitor_param['http-endpoint-ref'] = specs['url_path']
135 if 'ui_data' in specs:
136 if 'widget_type' in specs['ui_data']:
137 monitor_param['widget-type'] = specs['ui_data']['widget_type'].upper()
138 if 'units' in specs['ui_data']:
139 monitor_param['units'] = specs['ui_data']['units']
140 if 'group_tag' in specs['ui_data']:
141 monitor_param['group_tag'] = specs['ui_data']['group_tag']
142 if 'constraints' in specs:
143 if 'value_type' in specs['constraints']:
144 monitor_param['value-type'] = ToscaCompute.VALUE_TYPE_CONVERSION_MAP[specs['constraints']['value_type']]
145
146 return monitor_param
147
148 def get_vm_flavor(specs):
149 vm_flavor = {}
150 if 'num_cpus' in specs:
151 vm_flavor['vcpu-count'] = specs['num_cpus']
152 else:
153 vm_flavor['vcpu-count'] = 1
154
155 if 'mem_size' in specs:
156 vm_flavor['memory-mb'] = (ScalarUnit_Size(specs['mem_size']).
157 get_num_from_scalar_unit('MB'))
158 else:
159 vm_flavor['memory-mb'] = 512
160
161 if 'disk_size' in specs:
162 vm_flavor['storage-gb'] = (ScalarUnit_Size(specs['disk_size']).
163 get_num_from_scalar_unit('GB'))
164 else:
165 vm_flavor['storage-gb'] = 4
166
167 return vm_flavor
168
169 def get_host_epa(specs):
170 host_epa = {}
171 if 'cpu_model' in specs:
172 host_epa["cpu-model"] = specs['cpu_model'].upper()
173 if 'cpu_arch' in specs:
174 host_epa["cpu-arch"] = specs['cpu_arch'].upper()
175 if 'cpu_vendor' in specs:
176 host_epa["cpu-vendor"] = specs['cpu_vendor'].upper()
177 if 'cpu_socket_count' in specs:
178 host_epa["cpu-socket-count"] = specs['cpu_socket_count']
179 if 'cpu_core_count' in specs:
180 host_epa["cpu-core-count"] = specs['cpu_core_count']
181 if 'cpu_core_thread_count' in specs:
182 host_epa["cpu-core-thread-count"] = specs['cpu_core_thread_count']
183 if 'om_cpu_model_string' in specs:
184 host_epa["om-cpu-model-string"] = specs['om_cpu_model_string']
185 if 'cpu_feature' in specs:
186 cpu_feature_prop = []
187 for spec in specs['cpu_feature']:
188 cpu_feature_prop.append({'feature':spec.upper()})
189 host_epa['cpu-feature'] = cpu_feature_prop
190 if 'om_cpu_feature' in specs:
191 cpu_feature_prop = []
192 for spec in specs['om_cpu_feature']:
193 cpu_feature_prop.append({'feature':spec})
194 host_epa['om-cpu-feature'] = cpu_feature_prop
195 return host_epa;
196
197 def get_vswitch_epa(specs):
198 vswitch_epa = {}
199 if 'ovs_acceleration' in specs:
200 vswitch_epa['ovs-acceleration'] = specs['ovs_acceleration'].upper()
201 if 'ovs_offload' in specs:
202 vswitch_epa['ovs-offload'] = specs['ovs_offload'].upper()
203 return vswitch_epa
204
205 def get_hypervisor_epa(specs):
206 hypervisor_epa = {}
207 if 'type' in specs:
208 hypervisor_epa['type'] = specs['type'].upper()
209 if 'version' in specs:
210 hypervisor_epa['version'] = specs['version']
211
212 return hypervisor_epa
213
214 def get_guest_epa(specs):
215 guest_epa = {}
216 guest_epa['numa-node-policy'] = {}
217 guest_epa['numa-node-policy']['node'] = []
218 if 'mem_policy' in specs:
219 guest_epa['numa-node-policy']['mem-policy'] = specs['mem_policy'].upper()
220 if 'node_cnt' in specs:
221 guest_epa['numa-node-policy']['node-cnt'] = specs['node_cnt']
222 if 'node' in specs:
223 for node in specs['node']:
224 node_prop = {}
225 if 'id' in node:
226 node_prop['id'] = node['id']
227 if 'mem_size' in node:
228 if 'MiB' in node['mem_size'] or 'MB' in node['mem_size']:
229 node_prop['memory-mb'] = int(node['mem_size'].replace('MB',''))
230 else:
231 err_msg = "Specify mem_size of NUMA extension should be in MB"
232 raise ValidationError(message=err_msg)
233 if 'om_numa_type' in node:
234 numa_type = node['om_numa_type']
235 if 'paired-threads' == numa_type:
236 node_prop['paired_threads'] = {}
237 node_prop['paired_threads']['num_paired_threads'] = node['paired_threads']['num_paired_threads']
238 elif 'threads' == numa_type:
239 if 'num_threads' in node:
240 node_prop['num_threads'] = node['num_threads']
241 elif 'cores' == numa_type:
242 if 'num_cores' in node:
243 node_prop['num_cores'] = node['num_cores']
244 else:
245 err_msg = "om_numa_type should be among cores, paired-threads or threads"
246 raise ValidationError(message=err_msg)
247 guest_epa['numa-node-policy']['node'].append(node_prop)
248 return guest_epa
249
250 tosca_caps = self.get_tosca_caps()
251 self.log.debug(_("VDU {0} tosca capabilites: {1}").
252 format(self.name, tosca_caps))
253 if 'nfv_compute' in tosca_caps:
254 self.properties['vm-flavor'] = get_vm_flavor(tosca_caps['nfv_compute'])
255 self.log.debug(_("VDU {0} properties: {1}").
256 format(self.name, self.properties))
257 if 'host_epa' in tosca_caps:
258 self.properties['host-epa'] = get_host_epa(tosca_caps['host_epa'])
259 if 'hypervisor_epa' in tosca_caps:
260 self.properties['hypervisor-epa'] = get_hypervisor_epa(tosca_caps['hypervisor_epa'])
261 if 'vswitch_epa' in tosca_caps:
262 self.properties['vswitch-epa'] = get_vswitch_epa(tosca_caps['vswitch_epa'])
263 if 'numa_extension' in tosca_caps:
264 self.properties['guest-epa'] = get_guest_epa(tosca_caps['numa_extension'])
265 if 'monitoring_param' in tosca_caps:
266 self._monitor_param.append(get_monitor_param(tosca_caps['monitoring_param'], '1'))
267 if 'monitoring_param_1' in tosca_caps:
268 self._monitor_param.append(get_monitor_param(tosca_caps['monitoring_param_1'], '2'))
269 if 'mgmt_interface' in tosca_caps:
270 self._mgmt_interface = get_mgmt_interface(tosca_caps['mgmt_interface'])
271
272 def handle_artifacts(self):
273 if self.artifacts is None:
274 return
275 self.log.debug(_("VDU {0} tosca artifacts: {1}").
276 format(self.name, self.artifacts))
277 arts = {}
278 for key in self.artifacts:
279 props = self.artifacts[key]
280 if isinstance(props, dict):
281 details = {}
282 for name, value in props.items():
283 if name == 'type' and value == 'tosca.artifacts.Deployment.Image.riftio.QCOW2':
284 prefix, type_ = value.rsplit('.', 1)
285 if type_ == 'QCOW2':
286 details['type'] = 'qcow2'
287 self.properties['image'] = os.path.basename(props['file'])
288 elif name == 'type' and value == 'tosca.artifacts.Deployment.riftio.cloud_init_file':
289 details['cloud_init_file'] = os.path.basename(props['file'])
290 self.properties['cloud_init_file'] = os.path.basename(props['file'])
291 elif name == 'file':
292 details['file'] = value
293 elif name == 'image_checksum':
294 self.properties['image_checksum'] = value
295 else:
296 self.log.warn(_("VDU {0}, unsuported attribute {1}").
297 format(self.name, name))
298 if len(details):
299 arts[key] = details
300 else:
301 arts[key] = self.artifacts[key]
302
303 self.log.debug(_("VDU {0} artifacts: {1}").
304 format(self.name, arts))
305 self.artifacts = arts
306
307 def handle_interfaces(self):
308 # Currently, we support only create operation
309 operations_deploy_sequence = ['create']
310
311 operations = ManoResource._get_all_operations(self.nodetemplate)
312
313 # use the current ManoResource for the first operation in this order
314 # Currently we only support image in create operation
315 for operation in operations.values():
316 if operation.name in operations_deploy_sequence:
317 self.operations[operation.name] = None
318 try:
319 self.operations[operation.name] = operation.implementation
320 for name, details in self.artifacts.items():
321 if name == operation.implementation:
322 self._image = details['file']
323 except KeyError as e:
324 self.log.exception(e)
325 return None
326
327 def update_image_checksum(self, in_file):
328 # Create image checksum
329 # in_file is the TOSCA yaml file location
330 if self._image is None:
331 return
332 self.log.debug("Update image: {}".format(in_file))
333 if os.path.exists(in_file):
334 in_dir = os.path.dirname(in_file)
335 img_dir = os.path.dirname(self._image)
336 abs_dir = os.path.normpath(
337 os.path.join(in_dir, img_dir))
338 self.log.debug("Abs path: {}".format(abs_dir))
339 if os.path.isdir(abs_dir):
340 img_path = os.path.join(abs_dir,
341 os.path.basename(self._image))
342 self.log.debug(_("Image path: {0}").
343 format(img_path))
344 if os.path.exists(img_path):
345 # TODO (pjoseph): To be fixed when we can retrieve
346 # the VNF image in Launchpad.
347 # Check if the file is not size 0
348 # else it is a dummy file and to be ignored
349 if os.path.getsize(img_path) != 0:
350 self._image_cksum = ChecksumUtils.get_md5(img_path,
351 log=self.log)
352
353 def get_mano_attribute(self, attribute, args):
354 attr = {}
355 # Convert from a TOSCA attribute for a nodetemplate to a MANO
356 # attribute for the matching resource. Unless there is additional
357 # runtime support, this should be a one to one mapping.
358
359 # Note: We treat private and public IP addresses equally, but
360 # this will change in the future when TOSCA starts to support
361 # multiple private/public IP addresses.
362 self.log.debug(_('Converting TOSCA attribute for a nodetemplate to a MANO \
363 attriute.'))
364 if attribute == 'private_address' or \
365 attribute == 'public_address':
366 attr['get_attr'] = [self.name, 'networks', 'private', 0]
367
368 return attr
369
370 def _update_properties_for_model(self):
371 if self._image:
372 self.properties['image'] = os.path.basename(self._image)
373 if self._image_cksum:
374 self.properties['image-checksum'] = self._image_cksum
375
376 for key in ToscaCompute.IGNORE_PROPS:
377 if key in self.properties:
378 self.properties.pop(key)
379
380 def generate_yang_submodel_gi(self, vnfd):
381 if vnfd is None:
382 return None
383 self._update_properties_for_model()
384 props = convert_keys_to_python(self.properties)
385
386 for monitor_param in self._monitor_param:
387 monitor_props = convert_keys_to_python(monitor_param)
388 vnfd.monitoring_param.add().from_dict(monitor_props)
389 try:
390 if len(self._mgmt_interface) > 0:
391 vnfd.mgmt_interface.from_dict(convert_keys_to_python(self._mgmt_interface))
392 vnfd.vdu.add().from_dict(props)
393 except Exception as e:
394 err_msg = _("{0} Exception vdu from dict {1}: {2}"). \
395 format(self, props, e)
396 self.log.error(err_msg)
397 raise e
398
399 def generate_yang_submodel(self):
400 """Generate yang model for the VDU"""
401 self.log.debug(_("Generate YANG model for {0}").
402 format(self))
403
404 self._update_properties_for_model()
405
406 vdu = self.properties
407
408 return vdu