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