e851fd335f1f4bf8bbd23520440ef57b3ecac322
[osm/RO.git] / RO-VIM-fos / osm_rovim_fos / vimconn_fos.py
1 # -*- coding: utf-8 -*-
2
3 ##
4 # Copyright 2020 ADLINK Technology Inc.
5 # This file is part of ETSI OSM
6 # All Rights Reserved.
7 #
8 # Licensed under the Apache License, Version 2.0 (the "License"); you may
9 # not use this file except in compliance with the License. You may obtain
10 # a copy of the License at
11 #
12 # http://www.apache.org/licenses/LICENSE-2.0
13 #
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17 # License for the specific language governing permissions and limitations
18 # under the License.
19 #
20 #
21
22 """
23 Eclipse fog05 connector, implements methods to interact with Eclipse fog05
24
25 Manages LXD containers on x86_64 by default, currently missing EPA and VF/PF
26 Support config dict:
27 - arch : cpu architecture for the VIM
28 - hypervisor: virtualization technology supported by the VIM, can
29 can be one of: LXD, KVM, BARE, XEN, DOCKER, MCU
30 the selected VIM need to have at least a node with support
31 for the selected hypervisor
32
33 """
34
35 import logging
36 import uuid
37 import socket
38 import struct
39 from osm_ro_plugin import vimconn
40 # import json
41 from functools import partial
42 from fog05 import FIMAPI
43 from fog05 import fimapi
44 from fog05_sdk.interfaces.FDU import FDU
45
46 __author__ = "Gabriele Baldoni"
47 __date__ = "$2-june-2020 10:35:12$"
48
49
50 class vimconnector(vimconn.VimConnector):
51 def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, log_level=None,
52 config={}, persistent_info={}):
53 """Constructor of VIM
54 Params:
55 'uuid': id asigned to this VIM
56 'name': name assigned to this VIM, can be used for logging
57 'tenant_id', 'tenant_name': (only one of them is mandatory) VIM tenant to be used
58 'url_admin': (optional), url used for administrative tasks
59 'user', 'passwd': credentials of the VIM user
60 'log_level': provider if it should use a different log_level than the general one
61 'config': dictionary with extra VIM information. This contains a consolidate version of general VIM config
62 at creation and particular VIM config at teh attachment
63 'persistent_info': dict where the class can store information that will be available among class
64 destroy/creation cycles. This info is unique per VIM/credential. At first call it will contain an
65 empty dict. Useful to store login/tokens information for speed up communication
66
67 Returns: Raise an exception is some needed parameter is missing, but it must not do any connectivity
68 check against the VIM
69 """
70
71 vimconn.VimConnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level,
72 config, persistent_info)
73
74 self.logger = logging.getLogger('ro.vim.fos')
75 self.logger.debug('vimconn_fos init with config: {}'.format(config))
76 self.arch = config.get('arch', 'x86_64')
77 self.hv = config.get('hypervisor', 'LXD')
78 self.nodes = config.get('nodes', [])
79 self.fdu_node_map = {}
80 self.fos_api = FIMAPI(locator=self.url)
81
82 def __get_ip_range(self, first, count):
83 int_first = struct.unpack('!L', socket.inet_aton(first))[0]
84 int_last = int_first + count
85 last = socket.inet_ntoa(struct.pack('!L', int_last))
86 return (first, last)
87
88 def __name_filter(self, desc, filter_name=None):
89 if filter_name is None:
90 return True
91 return desc.get('name') == filter_name
92
93 def __id_filter(self, desc, filter_id=None):
94 if filter_id is None:
95 return True
96 return desc.get('uuid') == filter_id
97
98 def __checksum_filter(self, desc, filter_checksum=None):
99 if filter_checksum is None:
100 return True
101 return desc.get('checksum') == filter_checksum
102
103 def check_vim_connectivity(self):
104 """Checks VIM can be reached and user credentials are ok.
105 Returns None if success or raised VimConnConnectionException, VimConnAuthException, ...
106 """
107 try:
108 self.fos_api.node.list()
109 return None
110 except fimapi.FIMAuthExcetpion as fae:
111 raise vimconn.VimConnAuthException("Unable to authenticate to the VIM. Error {}".format(fae))
112 except Exception as e:
113 raise vimconn.VimConnConnectionException("VIM not reachable. Error {}".format(e))
114
115 def new_network(self, net_name, net_type, ip_profile=None, shared=False, provider_network_profile=None):
116 """Adds a tenant network to VIM
117 Params:
118 'net_name': name of the network
119 'net_type': one of:
120 'bridge': overlay isolated network
121 'data': underlay E-LAN network for Passthrough and SRIOV interfaces
122 'ptp': underlay E-LINE network for Passthrough and SRIOV interfaces.
123 'ip_profile': is a dict containing the IP parameters of the network
124 'ip_version': can be "IPv4" or "IPv6" (Currently only IPv4 is implemented)
125 'subnet_address': ip_prefix_schema, that is X.X.X.X/Y
126 'gateway_address': (Optional) ip_schema, that is X.X.X.X
127 'dns_address': (Optional) comma separated list of ip_schema, e.g. X.X.X.X[,X,X,X,X]
128 'dhcp_enabled': True or False
129 'dhcp_start_address': ip_schema, first IP to grant
130 'dhcp_count': number of IPs to grant.
131 'shared': if this network can be seen/use by other tenants/organization
132 Returns the network identifier on success or raises and exception on failure
133 """
134 self.logger.debug('new_network: {}'.format(locals()))
135 if net_type in ['data', 'ptp']:
136 raise vimconn.VimConnNotImplemented('{} type of network not supported'.format(net_type))
137
138 net_uuid = '{}'.format(uuid.uuid4())
139 desc = {
140 'uuid': net_uuid,
141 'name': net_name,
142 'net_type': 'ELAN',
143 'is_mgmt': False
144 }
145
146 if ip_profile is not None:
147 ip = {}
148 if ip_profile.get('ip_version') == 'IPv4':
149 ip_info = {}
150 ip_range = self.__get_ip_range(ip_profile.get('dhcp_start_address'), ip_profile.get('dhcp_count'))
151 dhcp_range = '{},{}'.format(ip_range[0], ip_range[1])
152 ip['subnet'] = ip_profile.get('subnet_address')
153 ip['dns'] = ip_profile.get('dns', None)
154 ip['dhcp_enable'] = ip_profile.get('dhcp_enabled', False)
155 ip['dhcp_range'] = dhcp_range
156 ip['gateway'] = ip_profile.get('gateway_address', None)
157 desc['ip_configuration'] = ip_info
158 else:
159 raise vimconn.VimConnNotImplemented('IPV6 network is not implemented at VIM')
160 desc['ip_configuration'] = ip
161 self.logger.debug('VIM new_network args: {} - Generated Eclipse fog05 Descriptor {}'.format(locals(), desc))
162 try:
163 self.fos_api.network.add_network(desc)
164 except fimapi.FIMAResouceExistingException as free:
165 raise vimconn.VimConnConflictException("Network already exists at VIM. Error {}".format(free))
166 except Exception as e:
167 raise vimconn.VimConnException("Unable to create network {}. Error {}".format(net_name, e))
168 # No way from the current rest service to get the actual error, most likely it will be an already
169 # existing error
170 return net_uuid, {}
171
172 def get_network_list(self, filter_dict={}):
173 """Obtain tenant networks of VIM
174 :param filter_dict: (optional) contains entries to return only networks that matches ALL entries:
175 name: string => returns only networks with this name
176 id: string => returns networks with this VIM id, this imply returns one network at most
177 shared: boolean >= returns only networks that are (or are not) shared
178 tenant_id: sting => returns only networks that belong to this tenant/project
179 (not used yet) admin_state_up: boolean => returns only networks that are (or are not) in admin state active
180 (not used yet) status: 'ACTIVE','ERROR',... => filter networks that are on this status
181 Returns the network list of dictionaries. each dictionary contains:
182 'id': (mandatory) VIM network id
183 'name': (mandatory) VIM network name
184 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER'
185 'network_type': (optional) can be 'vxlan', 'vlan' or 'flat'
186 'segmentation_id': (optional) in case network_type is vlan or vxlan this field contains the segmentation id
187 'error_msg': (optional) text that explains the ERROR status
188 other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param
189 List can be empty if no network map the filter_dict. Raise an exception only upon VIM connectivity,
190 authorization, or some other unspecific error
191 """
192 self.logger.debug('get_network_list: {}'.format(filter_dict))
193 res = []
194 try:
195 nets = self.fos_api.network.list()
196 except Exception as e:
197 raise vimconn.VimConnConnectionException(
198 "Cannot get network list from VIM, connection error. Error {}".format(e))
199
200 filters = [
201 partial(self.__name_filter, filter_name=filter_dict.get('name')),
202 partial(self.__id_filter, filter_id=filter_dict.get('id'))
203 ]
204
205 r1 = []
206
207 for n in nets:
208 match = True
209 for f in filters:
210 match = match and f(n)
211 if match:
212 r1.append(n)
213
214 for n in r1:
215 osm_net = {
216 'id': n.get('uuid'),
217 'name': n.get('name'),
218 'status': 'ACTIVE'
219 }
220 res.append(osm_net)
221 return res
222
223 def get_network(self, net_id):
224 """Obtain network details from the 'net_id' VIM network
225 Return a dict that contains:
226 'id': (mandatory) VIM network id, that is, net_id
227 'name': (mandatory) VIM network name
228 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER'
229 'error_msg': (optional) text that explains the ERROR status
230 other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param
231 Raises an exception upon error or when network is not found
232 """
233 self.logger.debug('get_network: {}'.format(net_id))
234 res = self.get_network_list(filter_dict={'id': net_id})
235 if len(res) == 0:
236 raise vimconn.VimConnNotFoundException("Network {} not found at VIM".format(net_id))
237 return res[0]
238
239 def delete_network(self, net_id, created_items=None):
240 """Deletes a tenant network from VIM
241 Returns the network identifier or raises an exception upon error or when network is not found
242 """
243 self.logger.debug('delete_network: {}'.format(net_id))
244 try:
245 self.fos_api.network.remove_network(net_id)
246 except fimapi.FIMNotFoundException as fnfe:
247 raise vimconn.VimConnNotFoundException(
248 "Network {} not found at VIM (already deleted?). Error {}".format(net_id, fnfe))
249 except Exception as e:
250 raise vimconn.VimConnException("Cannot delete network {} from VIM. Error {}".format(net_id, e))
251 return net_id
252
253 def refresh_nets_status(self, net_list):
254 """Get the status of the networks
255 Params:
256 'net_list': a list with the VIM network id to be get the status
257 Returns a dictionary with:
258 'net_id': #VIM id of this network
259 status: #Mandatory. Text with one of:
260 # DELETED (not found at vim)
261 # VIM_ERROR (Cannot connect to VIM, authentication problems, VIM response error, ...)
262 # OTHER (Vim reported other status not understood)
263 # ERROR (VIM indicates an ERROR status)
264 # ACTIVE, INACTIVE, DOWN (admin down),
265 # BUILD (on building process)
266 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
267 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
268 'net_id2': ...
269 """
270 self.logger.debug('Refeshing network status with args: {}'.format(locals()))
271 r = {}
272 for n in net_list:
273 try:
274 osm_n = self.get_network(n)
275 r[osm_n.get('id')] = {'status': osm_n.get('status')}
276 except vimconn.VimConnNotFoundException:
277 r[n] = {'status': 'VIM_ERROR'}
278 return r
279
280 def get_flavor(self, flavor_id):
281 """Obtain flavor details from the VIM
282 Returns the flavor dict details {'id':<>, 'name':<>, other vim specific }
283 Raises an exception upon error or if not found
284 """
285 self.logger.debug('VIM get_flavor with args: {}'.format(locals()))
286 try:
287 r = self.fos_api.flavor.get(flavor_id)
288 except Exception as e:
289 raise vimconn.VimConnConnectionException("VIM not reachable. Error {}".format(e))
290 if r is None:
291 raise vimconn.VimConnNotFoundException("Flavor not found at VIM")
292 return {'id': r.get('uuid'), 'name': r.get('name'), 'fos': r}
293
294 def get_flavor_id_from_data(self, flavor_dict):
295 """Obtain flavor id that match the flavor description
296 Params:
297 'flavor_dict': dictionary that contains:
298 'disk': main hard disk in GB
299 'ram': meomry in MB
300 'vcpus': number of virtual cpus
301 #TODO: complete parameters for EPA
302 Returns the flavor_id or raises a vimconnNotFoundException
303 """
304 self.logger.debug('VIM get_flavor_id_from_data with args : {}'.format(locals()))
305
306 try:
307 flvs = self.fos_api.flavor.list()
308 except Exception as e:
309 raise vimconn.VimConnConnectionException("VIM not reachable. Error {}".format(e))
310 r = [x.get('uuid') for x in flvs if (x.get('cpu_min_count') == flavor_dict.get('vcpus') and
311 x.get('ram_size_mb') == flavor_dict.get('ram') and
312 x.get('storage_size_gb') == flavor_dict.get('disk'))]
313 if len(r) == 0:
314 raise vimconn.VimConnNotFoundException("No flavor found")
315 return r[0]
316
317 def new_flavor(self, flavor_data):
318 """Adds a tenant flavor to VIM
319 flavor_data contains a dictionary with information, keys:
320 name: flavor name
321 ram: memory (cloud type) in MBytes
322 vpcus: cpus (cloud type)
323 extended: EPA parameters
324 - numas: #items requested in same NUMA
325 memory: number of 1G huge pages memory
326 paired-threads|cores|threads: number of paired hyperthreads, complete cores OR individual threads
327 interfaces: # passthrough(PT) or SRIOV interfaces attached to this numa
328 - name: interface name
329 dedicated: yes|no|yes:sriov; for PT, SRIOV or only one SRIOV for the physical NIC
330 bandwidth: X Gbps; requested guarantee bandwidth
331 vpci: requested virtual PCI address
332 disk: disk size
333 is_public:
334 #TODO to concrete
335 Returns the flavor identifier"""
336 self.logger.debug('VIM new_flavor with args: {}'.format(locals()))
337 flv_id = '{}'.format(uuid.uuid4())
338 desc = {
339 'uuid': flv_id,
340 'name': flavor_data.get('name'),
341 'cpu_arch': self.arch,
342 'cpu_min_count': flavor_data.get('vcpus'),
343 'cpu_min_freq': 0,
344 'ram_size_mb': float(flavor_data.get('ram')),
345 'storage_size_gb': float(flavor_data.get('disk'))
346 }
347 try:
348 self.fos_api.flavor.add(desc)
349 except fimapi.FIMAResouceExistingException as free:
350 raise vimconn.VimConnConflictException("Flavor {} already exist at VIM. Error {}".format(flv_id, free))
351 except Exception as e:
352 raise vimconn.VimConnConnectionException("VIM not reachable. Error {}".format(e))
353 return flv_id
354
355 def delete_flavor(self, flavor_id):
356 """Deletes a tenant flavor from VIM identify by its id
357 Returns the used id or raise an exception"""
358 try:
359 self.fos_api.flavor.remove(flavor_id)
360 except fimapi.FIMNotFoundException as fnfe:
361 raise vimconn.VimConnNotFoundException(
362 "Flavor {} not found at VIM (already deleted?). Error {}".format(flavor_id, fnfe))
363 except Exception as e:
364 raise vimconn.VimConnConnectionException("VIM not reachable. Error {}".format(e))
365 return flavor_id
366
367 def new_image(self, image_dict):
368 """ Adds a tenant image to VIM. imge_dict is a dictionary with:
369 name: name
370 disk_format: qcow2, vhd, vmdk, raw (by default), ...
371 location: path or URI
372 public: "yes" or "no"
373 metadata: metadata of the image
374 Returns the image id or raises an exception if failed
375 """
376 self.logger.debug('VIM new_image with args: {}'.format(locals()))
377 img_id = '{}'.format(uuid.uuid4())
378 desc = {
379 'name': image_dict.get('name'),
380 'uuid': img_id,
381 'uri': image_dict.get('location'),
382 'format': image_dict.get('disk_format')
383 }
384 try:
385 self.fos_api.image.add(desc)
386 except fimapi.FIMAResouceExistingException as free:
387 raise vimconn.VimConnConflictException("Image {} already exist at VIM. Error {}".format(img_id, free))
388 except Exception as e:
389 raise vimconn.VimConnConnectionException("VIM not reachable. Error {}".format(e))
390 return img_id
391
392 def get_image_id_from_path(self, path):
393
394 """Get the image id from image path in the VIM database.
395 Returns the image_id or raises a vimconnNotFoundException
396 """
397 self.logger.debug('VIM get_image_id_from_path with args: {}'.format(locals()))
398 try:
399 imgs = self.fos_api.image.list()
400 except Exception as e:
401 raise vimconn.VimConnConnectionException("VIM not reachable. Error {}".format(e))
402 res = [x.get('uuid') for x in imgs if x.get('uri') == path]
403 if len(res) == 0:
404 raise vimconn.VimConnNotFoundException("Image with this path was not found")
405 return res[0]
406
407 def get_image_list(self, filter_dict={}):
408 """Obtain tenant images from VIM
409 Filter_dict can be:
410 name: image name
411 id: image uuid
412 checksum: image checksum
413 location: image path
414 Returns the image list of dictionaries:
415 [{<the fields at Filter_dict plus some VIM specific>}, ...]
416 List can be empty
417 """
418 self.logger.debug('VIM get_image_list args: {}'.format(locals()))
419 r = []
420 try:
421 fimgs = self.fos_api.image.list()
422 except Exception as e:
423 raise vimconn.VimConnConnectionException("VIM not reachable. Error {}".format(e))
424
425 filters = [
426 partial(self.__name_filter, filter_name=filter_dict.get('name')),
427 partial(self.__id_filter, filter_id=filter_dict.get('id')),
428 partial(self.__checksum_filter, filter_checksum=filter_dict.get('checksum'))
429 ]
430
431 r1 = []
432
433 for i in fimgs:
434 match = True
435 for f in filters:
436 match = match and f(i)
437 if match:
438 r1.append(i)
439
440 for i in r1:
441 img_info = {
442 'name': i.get('name'),
443 'id': i.get('uuid'),
444 'checksum': i.get('checksum'),
445 'location': i.get('uri'),
446 'fos': i
447 }
448 r.append(img_info)
449 return r
450 # raise VimConnNotImplemented( "Should have implemented this" )
451
452 def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None, disk_list=None,
453 availability_zone_index=None, availability_zone_list=None):
454 """Adds a VM instance to VIM
455 :param start: (boolean) indicates if VM must start or created in pause mode.
456 :param image_id: :param flavor_id: image and flavor VIM id to use for the VM
457 :param net_list: list of interfaces, each one is a dictionary with:
458 'name': (optional) name for the interface.
459 'net_id': VIM network id where this interface must be connect to. Mandatory for type==virtual
460 'vpci': (optional) virtual vPCI address to assign at the VM. Can be ignored depending on VIM capabilities
461 'model': (optional and only have sense for type==virtual) interface model: virtio, e1000, ...
462 'mac_address': (optional) mac address to assign to this interface
463 'ip_address': (optional) IP address to assign to this interface
464 #TODO: CHECK if an optional 'vlan' parameter is needed for VIMs when type if VF and net_id is not provided,
465 the VLAN tag to be used. In case net_id is provided, the internal network vlan is used for tagging VF
466 'type': (mandatory) can be one of:
467 'virtual', in this case always connected to a network of type 'net_type=bridge'
468 'PCI-PASSTHROUGH' or 'PF' (passthrough): depending on VIM capabilities it can be connected to a
469 data/ptp network ot it can created unconnected
470 'SR-IOV' or 'VF' (SRIOV with VLAN tag): same as PF for network connectivity.
471 'VFnotShared'(SRIOV without VLAN tag) same as PF for network connectivity. VF where no other VFs
472 are allocated on the same physical NIC
473 'bw': (optional) only for PF/VF/VFnotShared. Minimal Bandwidth required for the interface in GBPS
474 'port_security': (optional) If False it must avoid any traffic filtering at this interface. If missing
475 or True, it must apply the default VIM behaviour
476 After execution the method will add the key:
477 'vim_id': must be filled/added by this method with the VIM identifier generated by the VIM for this
478 interface. 'net_list' is modified
479 :param cloud_config: (optional) dictionary with:
480 'key-pairs': (optional) list of strings with the public key to be inserted to the default user
481 'users': (optional) list of users to be inserted, each item is a dict with:
482 'name': (mandatory) user name,
483 'key-pairs': (optional) list of strings with the public key to be inserted to the user
484 'user-data': (optional) can be a string with the text script to be passed directly to cloud-init,
485 or a list of strings, each one contains a script to be passed, usually with a MIMEmultipart file
486 'config-files': (optional). List of files to be transferred. Each item is a dict with:
487 'dest': (mandatory) string with the destination absolute path
488 'encoding': (optional, by default text). Can be one of:
489 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
490 'content' (mandatory): string with the content of the file
491 'permissions': (optional) string with file permissions, typically octal notation '0644'
492 'owner': (optional) file owner, string with the format 'owner:group'
493 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk)
494 :param disk_list: (optional) list with additional disks to the VM. Each item is a dict with:
495 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
496 'size': (mandatory) string with the size of the disk in GB
497 :param availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required
498 :param availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
499 availability_zone_index is None
500 Returns a tuple with the instance identifier and created_items or raises an exception on error
501 created_items can be None or a dictionary where this method can include key-values that will be passed to
502 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
503 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
504 as not present.
505 """
506 self.logger.debug('new_vminstance with args: {}'.format(locals()))
507 fdu_uuid = '{}'.format(uuid.uuid4())
508
509 flv = self.fos_api.flavor.get(flavor_id)
510 img = self.fos_api.image.get(image_id)
511
512 if flv is None:
513 raise vimconn.VimConnNotFoundException("Flavor {} not found at VIM".format(flavor_id))
514 if img is None:
515 raise vimconn.VimConnNotFoundException("Image {} not found at VIM".format(image_id))
516
517 created_items = {
518 'fdu_id': '',
519 'node_id': '',
520 'connection_points': []
521 }
522
523 fdu_desc = {
524 'name': name,
525 'id': fdu_uuid,
526 'uuid': fdu_uuid,
527 'computation_requirements': flv,
528 'image': img,
529 'hypervisor': self.hv,
530 'migration_kind': 'LIVE',
531 'interfaces': [],
532 'io_ports': [],
533 'connection_points': [],
534 'depends_on': [],
535 'storage': []
536 }
537
538 nets = []
539 cps = []
540 intf_id = 0
541 for n in net_list:
542 cp_id = '{}'.format(uuid.uuid4())
543 n['vim_id'] = cp_id
544 pair_id = n.get('net_id')
545
546 cp_d = {
547 'id': cp_id,
548 'name': cp_id,
549 'vld_ref': pair_id
550 }
551 intf_d = {
552 'name': n.get('name', 'eth{}'.format(intf_id)),
553 'is_mgmt': False,
554 'if_type': 'INTERNAL',
555 'virtual_interface': {
556 'intf_type': n.get('model', 'VIRTIO'),
557 'vpci': n.get('vpci', '0:0:0'),
558 'bandwidth': int(n.get('bw', 100))
559 },
560 'cp_id': cp_id
561 }
562 if n.get('mac_address', None) is not None:
563 intf_d['mac_address'] = n['mac_address']
564
565 created_items['connection_points'].append(cp_id)
566 fdu_desc['connection_points'].append(cp_d)
567 fdu_desc['interfaces'].append(intf_d)
568
569 intf_id = intf_id + 1
570
571 if cloud_config is not None:
572 configuration = {'conf_type': 'CLOUD_INIT'}
573 if cloud_config.get('user-data') is not None:
574 configuration['script'] = cloud_config.get('user-data')
575 if cloud_config.get('key-pairs') is not None:
576 configuration['ssh_keys'] = cloud_config.get('key-pairs')
577
578 if 'script' in configuration:
579 fdu_desc['configuration'] = configuration
580
581 self.logger.debug('Eclipse fog05 FDU Descriptor: {}'.format(fdu_desc))
582
583 fdu = FDU(fdu_desc)
584
585 try:
586 self.fos_api.fdu.onboard(fdu)
587 instance = self.fos_api.fdu.define(fdu_uuid)
588 instance_list = self.fos_api.fdu.instance_list(fdu_uuid)
589 selected_node = ''
590 for n in instance_list:
591 instances = instance_list[n]
592 if instance.uuid in instances:
593 selected_node = n
594 if selected_node == '':
595 raise ValueError("Unable to find node for network creation")
596
597 self.logger.debug('Selected node by VIM: {}'.format(selected_node))
598 created_items['fdu_id'] = fdu_uuid
599 created_items['node_id'] = selected_node
600
601 for cp in fdu_desc['connection_points']:
602 nets = self.fos_api.network.list()
603 for net in nets:
604 if net.get('uuid') == cp['vld_ref']:
605 self.fos_api.network.add_network_to_node(net, selected_node)
606
607 self.fos_api.fdu.configure(instance.uuid)
608 self.fos_api.fdu.start(instance.uuid)
609
610 self.logger.debug('Eclipse fog05 FDU Started {}'.format(instance.uuid))
611
612 created_items['instance_id'] = str(instance.uuid)
613
614 self.fdu_node_map[instance.uuid] = selected_node
615 self.logger.debug('new_vminstance returns: {} {}'.format(instance.uuid, created_items))
616 return str(instance.uuid), created_items
617 except fimapi.FIMAResouceExistingException as free:
618 raise vimconn.VimConnConflictException("VM already exists at VIM. Error {}".format(free))
619 except Exception as e:
620 raise vimconn.VimConnException("Error while instantiating VM {}. Error {}".format(name, e))
621
622 def get_vminstance(self, vm_id):
623 """Returns the VM instance information from VIM"""
624 self.logger.debug('VIM get_vminstance with args: {}'.format(locals()))
625
626 try:
627 instance = self.fos_api.fdu.instance_info(vm_id)
628 except Exception as e:
629 raise vimconn.VimConnConnectionException("VIM not reachable. Error {}".format(e))
630 if instance is None:
631 raise vimconn.VimConnNotFoundException('VM with id {} not found!'.format(vm_id))
632 return instance.to_json()
633
634 def delete_vminstance(self, vm_id, created_items=None):
635 """
636 Removes a VM instance from VIM and each associate elements
637 :param vm_id: VIM identifier of the VM, provided by method new_vminstance
638 :param created_items: dictionary with extra items to be deleted. provided by method new_vminstance and/or method
639 action_vminstance
640 :return: None or the same vm_id. Raises an exception on fail
641 """
642 self.logger.debug('FOS delete_vminstance with args: {}'.format(locals()))
643 fduid = created_items.get('fdu_id')
644 try:
645 instance = self.fos_api.fdu.instance_info(vm_id)
646 instance_list = self.fos_api.fdu.instance_list(instance.fdu_id)
647 selected_node = ''
648 for n in instance_list:
649 instances = instance_list[n]
650 if instance.uuid in instances:
651 selected_node = n
652 if selected_node == '':
653 raise ValueError("Unable to find node for the given Instance")
654
655 self.fos_api.fdu.stop(vm_id)
656
657 for cp in instance.to_json()['connection_points']:
658 nets = self.fos_api.network.list()
659 for net in nets:
660 if net.get('uuid') == cp['vld_ref']:
661 self.fos_api.network.remove_network_from_node(net.get('uuid'), selected_node)
662
663 self.fos_api.fdu.clean(vm_id)
664 self.fos_api.fdu.undefine(vm_id)
665
666 self.fos_api.fdu.offload(fduid)
667 except Exception as e:
668 raise vimconn.VimConnException("Error on deleting VM with id {}. Error {}".format(vm_id, e))
669 return vm_id
670
671 # raise VimConnNotImplemented( "Should have implemented this" )
672
673 def refresh_vms_status(self, vm_list):
674 """Get the status of the virtual machines and their interfaces/ports
675 Params: the list of VM identifiers
676 Returns a dictionary with:
677 vm_id: #VIM id of this Virtual Machine
678 status: #Mandatory. Text with one of:
679 # DELETED (not found at vim)
680 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
681 # OTHER (Vim reported other status not understood)
682 # ERROR (VIM indicates an ERROR status)
683 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
684 # BUILD (on building process), ERROR
685 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
686 #
687 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
688 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
689 interfaces: list with interface info. Each item a dictionary with:
690 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
691 mac_address: #Text format XX:XX:XX:XX:XX:XX
692 vim_net_id: #network id where this interface is connected, if provided at creation
693 vim_interface_id: #interface/port VIM id
694 ip_address: #null, or text with IPv4, IPv6 address
695 compute_node: #identification of compute node where PF,VF interface is allocated
696 pci: #PCI address of the NIC that hosts the PF,VF
697 vlan: #physical VLAN used for VF
698 """
699 self.logger.debug('FOS refresh_vms_status with args: {}'.format(locals()))
700 fos2osm_status = {
701 'DEFINE': 'OTHER',
702 'CONFIGURE': 'INACTIVE',
703 'RUN': 'ACTIVE',
704 'PAUSE': 'PAUSED',
705 'ERROR': 'ERROR'
706 }
707
708 r = {}
709
710 for vm in vm_list:
711 self.logger.debug('FOS refresh_vms_status for {}'.format(vm))
712
713 info = {}
714 nid = self.fdu_node_map.get(vm)
715 if nid is None:
716 r[vm] = {
717 'status': 'VIM_ERROR',
718 'error_msg': 'Not compute node associated for VM'
719 }
720 continue
721
722 try:
723 vm_info = self.fos_api.fdu.instance_info(vm)
724 except Exception:
725 r[vm] = {
726 'status': 'VIM_ERROR',
727 'error_msg': 'unable to connect to VIM'
728 }
729 continue
730
731 if vm_info is None:
732 r[vm:] = {'status': 'DELETED'}
733 continue
734
735 desc = self.fos_api.fdu.info(str(vm_info.fdu_id))
736
737 vm_info = vm_info.to_json()
738 desc = desc.to_json()
739
740 osm_status = fos2osm_status.get(vm_info.get('status'))
741
742 self.logger.debug('FOS status info {}'.format(vm_info))
743 self.logger.debug('FOS status is {} <-> OSM Status {}'.format(vm_info.get('status'), osm_status))
744 info['status'] = osm_status
745 if vm_info.get('status') == 'ERROR':
746 info['error_msg'] = vm_info.get('error_code')
747 # yaml.safe_dump(json.loads(json.dumps(vm_info)))
748 # info['vim_info'] = ''
749 faces = []
750 i = 0
751 for intf_name in vm_info.get('hypervisor_info').get('network', []):
752 intf_info = vm_info.get('hypervisor_info').get('network').get(intf_name)
753 face = {}
754 face['compute_node'] = nid
755 # face['vim_info'] = '' #yaml.safe_dump(json.loads(json.dumps(intf_info)))
756 face['mac_address'] = intf_info.get('hwaddr')
757 addrs = []
758 for a in intf_info.get('addresses'):
759 addrs.append(a.get('address'))
760 if len(addrs) >= 0:
761 face['ip_address'] = ','.join(addrs)
762 else:
763 face['ip_address'] = ''
764 face['pci'] = '0:0:0.0'
765
766 try:
767 cp_info = vm_info.get('connection_points')[i]
768 except IndexError:
769 cp_info = None
770 if cp_info is not None:
771 cp_id = cp_info['cp_id']
772 cps_d = desc['connection_points']
773 matches = [x for x in cps_d if x['id'] == cp_id]
774 if len(matches) > 0:
775 cpd = matches[0]
776 face['vim_net_id'] = cpd.get('vld_ref', '')
777 else:
778 face['vim_net_id'] = ''
779 face['vim_interface_id'] = cp_id
780 # cp_info.get('uuid')
781 else:
782 face['vim_net_id'] = ''
783 face['vim_interface_id'] = intf_name
784 faces.append(face)
785 i += 1
786
787 info['interfaces'] = faces
788 r[vm] = info
789 self.logger.debug('FOS refresh_vms_status res for {} is {}'.format(vm, info))
790 self.logger.debug('FOS refresh_vms_status res is {}'.format(r))
791 return r
792
793 def action_vminstance(self, vm_id, action_dict, created_items={}):
794 """
795 Send and action over a VM instance. Returns created_items if the action was successfully sent to the VIM.
796 created_items is a dictionary with items that
797 :param vm_id: VIM identifier of the VM, provided by method new_vminstance
798 :param action_dict: dictionary with the action to perform
799 :param created_items: provided by method new_vminstance is a dictionary with key-values that will be passed to
800 the method delete_vminstance. Can be used to store created ports, volumes, etc. Format is vimconnector
801 dependent, but do not use nested dictionaries and a value of None should be the same as not present. This
802 method can modify this value
803 :return: None, or a console dict
804 """
805 self.logger.debug('VIM action_vminstance with args: {}'.format(locals()))
806 nid = self.fdu_node_map.get(vm_id)
807 if nid is None:
808 raise vimconn.VimConnNotFoundException('No node for this VM')
809 try:
810 instance = self.fos_api.fdu.instance_info(vm_id)
811 if "start" in action_dict:
812 if instance.get('status') == 'CONFIGURE':
813 self.fos_api.fdu.start(vm_id)
814 elif instance.get('status') == 'PAUSE':
815 self.fos_api.fdu.resume(vm_id)
816 else:
817 raise vimconn.VimConnConflictException('Cannot start from current state: {}'.format(
818 instance.get('status')))
819 elif "pause" in action_dict:
820 if instance.get('status') == 'RUN':
821 self.fos_api.fdu.pause(vm_id)
822 else:
823 raise vimconn.VimConnConflictException('Cannot pause from current state: {}'.format(
824 instance.get('status')))
825 elif "resume" in action_dict:
826 if instance.get('status') == 'PAUSE':
827 self.fos_api.fdu.resume(vm_id)
828 else:
829 raise vimconn.VimConnConflictException('Cannot resume from current state: {}'.format(
830 instance.get('status')))
831 elif "shutoff" in action_dict or "shutdown" or "forceOff" in action_dict:
832 if instance.get('status') == 'RUN':
833 self.fos_api.fdu.stop(vm_id)
834 else:
835 raise vimconn.VimConnConflictException('Cannot shutoff from current state: {}'.format(
836 instance.get('status')))
837 elif "terminate" in action_dict:
838 if instance.get('status') == 'RUN':
839 self.fos_api.fdu.stop(vm_id)
840 self.fos_api.fdu.clean(vm_id)
841 self.fos_api.fdu.undefine(vm_id)
842 # self.fos_api.fdu.offload(vm_id)
843 elif instance.get('status') == 'CONFIGURE':
844 self.fos_api.fdu.clean(vm_id)
845 self.fos_api.fdu.undefine(vm_id)
846 # self.fos_api.fdu.offload(vm_id)
847 elif instance.get('status') == 'PAUSE':
848 self.fos_api.fdu.resume(vm_id)
849 self.fos_api.fdu.stop(vm_id)
850 self.fos_api.fdu.clean(vm_id)
851 self.fos_api.fdu.undefine(vm_id)
852 # self.fos_api.fdu.offload(vm_id)
853 else:
854 raise vimconn.VimConnConflictException('Cannot terminate from current state: {}'.format(
855 instance.get('status')))
856 elif "rebuild" in action_dict:
857 raise vimconn.VimConnNotImplemented("Rebuild not implemented")
858 elif "reboot" in action_dict:
859 if instance.get('status') == 'RUN':
860 self.fos_api.fdu.stop(vm_id)
861 self.fos_api.fdu.start(vm_id)
862 else:
863 raise vimconn.VimConnConflictException('Cannot reboot from current state: {}'.format(
864 instance.get('status')))
865 except Exception as e:
866 raise vimconn.VimConnConnectionException("VIM not reachable. Error {}".format(e))