1 # -*- coding: utf-8 -*-
4 # Copyright 2019 ADLINK Technology Inc..
5 # This file is part of ETSI OSM
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
12 # http://www.apache.org/licenses/LICENSE-2.0
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
23 Eclipse fog05 connector, implements methods to interact with fog05 using REST Client + REST Proxy
25 Manages LXD containers on x86_64 by default, currently missing EPA and VF/PF
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
34 __author__
="Gabriele Baldoni"
35 __date__
="$13-may-2019 10:35:12$"
40 from osm_ro
import vimconn
43 from functools
import partial
44 from fog05rest
import FIMAPI
45 from fog05rest
import fimerrors
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
={}):
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
65 Returns: Raise an exception is some needed parameter is missing, but it must not do any connectivity
69 vimconn
.vimconnector
.__init
__(self
, uuid
, name
, tenant_id
, tenant_name
, url
, url_admin
, user
, passwd
, log_level
,
70 config
, persistent_info
)
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
)
80 def __get_ip_range(self
, first
, count
):
81 int_first
= struct
.unpack('!L', socket
.inet_aton(first
))[0]
82 int_last
= int_first
+ count
83 last
= socket
.inet_ntoa(struct
.pack('!L', int_last
))
86 def __name_filter(self
, desc
, filter_name
=None):
87 if filter_name
is None:
89 return desc
.get('name') == filter_name
91 def __id_filter(self
, desc
, filter_id
=None):
94 return desc
.get('uuid') == filter_id
96 def __checksum_filter(self
, desc
, filter_checksum
=None):
97 if filter_checksum
is None:
99 return desc
.get('checksum') == filter_checksum
101 def check_vim_connectivity(self
):
102 """Checks VIM can be reached and user credentials are ok.
103 Returns None if success or raised vimconnConnectionException, vimconnAuthException, ...
108 except fimerrors
.FIMAuthExcetpion
as fae
:
109 raise vimconn
.vimconnAuthException("Unable to authenticate to the VIM. Error {}".format(fae
))
110 except Exception as e
:
111 raise vimconn
.vimconnConnectionException("VIM not reachable. Error {}".format(e
))
113 def new_network(self
, net_name
, net_type
, ip_profile
=None, shared
=False, provider_network_profile
=None):
114 """Adds a tenant network to VIM
116 'net_name': name of the network
118 'bridge': overlay isolated network
119 'data': underlay E-LAN network for Passthrough and SRIOV interfaces
120 'ptp': underlay E-LINE network for Passthrough and SRIOV interfaces.
121 'ip_profile': is a dict containing the IP parameters of the network
122 'ip_version': can be "IPv4" or "IPv6" (Currently only IPv4 is implemented)
123 'subnet_address': ip_prefix_schema, that is X.X.X.X/Y
124 'gateway_address': (Optional) ip_schema, that is X.X.X.X
125 'dns_address': (Optional) comma separated list of ip_schema, e.g. X.X.X.X[,X,X,X,X]
126 'dhcp_enabled': True or False
127 'dhcp_start_address': ip_schema, first IP to grant
128 'dhcp_count': number of IPs to grant.
129 'shared': if this network can be seen/use by other tenants/organization
130 Returns the network identifier on success or raises and exception on failure
132 self
.logger
.debug('new_network: {}'.format(locals()))
133 if net_type
in ['data','ptp']:
134 raise vimconn
.vimconnNotImplemented('{} type of network not supported'.format(net_type
))
136 net_uuid
= '{}'.format(uuid
.uuid4())
144 if ip_profile
is not None:
146 if ip_profile
.get('ip_version') == 'IPv4':
148 ip_range
= self
.__get
_ip
_range
(ip_profile
.get('dhcp_start_address'), ip_profile
.get('dhcp_count'))
149 dhcp_range
= '{},{}'.format(ip_range
[0],ip_range
[1])
150 ip
.update({'subnet':ip_profile
.get('subnet_address')})
151 ip
.update({'dns':ip_profile
.get('dns', None)})
152 ip
.update({'dhcp_enable':ip_profile
.get('dhcp_enabled', False)})
153 ip
.update({'dhcp_range': dhcp_range
})
154 ip
.update({'gateway':ip_profile
.get('gateway_address', None)})
155 desc
.update({'ip_configuration':ip_info
})
157 raise vimconn
.vimconnNotImplemented('IPV6 network is not implemented at VIM')
158 desc
.update({'ip_configuration':ip
})
159 self
.logger
.debug('VIM new_network args: {} - Generated Eclipse fog05 Descriptor {}'.format(locals(), desc
))
161 self
.fos_api
.network
.add_network(desc
)
162 except fimerrors
.FIMAResouceExistingException
as free
:
163 raise vimconn
.vimconnConflictException("Network already exists at VIM. Error {}".format(free
))
164 except Exception as e
:
165 raise vimconn
.vimconnException("Unable to create network {}. Error {}".format(net_name
, e
))
166 # No way from the current rest service to get the actual error, most likely it will be an already existing error
169 def get_network_list(self
, filter_dict
={}):
170 """Obtain tenant networks of VIM
172 'filter_dict' (optional) contains entries to return only networks that matches ALL entries:
173 name: string => returns only networks with this name
174 id: string => returns networks with this VIM id, this imply returns one network at most
175 shared: boolean >= returns only networks that are (or are not) shared
176 tenant_id: sting => returns only networks that belong to this tenant/project
177 ,#(not used yet) admin_state_up: boolean => returns only networks that are (or are not) in admin state active
178 #(not used yet) status: 'ACTIVE','ERROR',... => filter networks that are on this status
179 Returns the network list of dictionaries. each dictionary contains:
180 'id': (mandatory) VIM network id
181 'name': (mandatory) VIM network name
182 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER'
183 'network_type': (optional) can be 'vxlan', 'vlan' or 'flat'
184 'segmentation_id': (optional) in case network_type is vlan or vxlan this field contains the segmentation id
185 'error_msg': (optional) text that explains the ERROR status
186 other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param
187 List can be empty if no network map the filter_dict. Raise an exception only upon VIM connectivity,
188 authorization, or some other unspecific error
190 self
.logger
.debug('get_network_list: {}'.format(filter_dict
))
193 nets
= self
.fos_api
.network
.list()
194 except Exception as e
:
195 raise vimconn
.vimconnConnectionException("Cannot get network list from VIM, connection error. Error {}".format(e
))
198 partial(self
.__name
_filter
, filter_name
=filter_dict
.get('name')),
199 partial(self
.__id
_filter
,filter_id
=filter_dict
.get('id'))
207 match
= match
and f(n
)
214 'name':n
.get('name'),
220 def get_network(self
, net_id
):
221 """Obtain network details from the 'net_id' VIM network
222 Return a dict that contains:
223 'id': (mandatory) VIM network id, that is, net_id
224 'name': (mandatory) VIM network name
225 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER'
226 'error_msg': (optional) text that explains the ERROR status
227 other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param
228 Raises an exception upon error or when network is not found
230 self
.logger
.debug('get_network: {}'.format(net_id
))
231 res
= self
.get_network_list(filter_dict
={'id':net_id
})
233 raise vimconn
.vimconnNotFoundException("Network {} not found at VIM".format(net_id
))
236 def delete_network(self
, net_id
, created_items
=None):
237 """Deletes a tenant network from VIM
238 Returns the network identifier or raises an exception upon error or when network is not found
240 self
.logger
.debug('delete_network: {}'.format(net_id
))
242 self
.fos_api
.network
.remove_network(net_id
)
243 except fimerrors
.FIMNotFoundException
as fnfe
:
244 raise vimconn
.vimconnNotFoundException("Network {} not found at VIM (already deleted?). Error {}".format(net_id
, fnfe
))
245 except Exception as e
:
246 raise vimconn
.vimconnException("Cannot delete network {} from VIM. Error {}".format(net_id
, e
))
249 def refresh_nets_status(self
, net_list
):
250 """Get the status of the networks
252 'net_list': a list with the VIM network id to be get the status
253 Returns a dictionary with:
254 'net_id': #VIM id of this network
255 status: #Mandatory. Text with one of:
256 # DELETED (not found at vim)
257 # VIM_ERROR (Cannot connect to VIM, authentication problems, VIM response error, ...)
258 # OTHER (Vim reported other status not understood)
259 # ERROR (VIM indicates an ERROR status)
260 # ACTIVE, INACTIVE, DOWN (admin down),
261 # BUILD (on building process)
262 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
263 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
266 self
.logger
.debug('Refeshing network status with args: {}'.format(locals()))
270 osm_n
= self
.get_network(n
)
272 osm_n
.get('id'):{'status':osm_n
.get('status')}
274 except vimconn
.vimconnNotFoundException
:
276 n
:{'status':'VIM_ERROR'}
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
285 self
.logger
.debug('VIM get_flavor with args: {}'.format(locals()))
287 r
= self
.fos_api
.flavor
.get(flavor_id
)
288 except Exception as e
:
289 raise vimconn
.vimconnConnectionException("VIM not reachable. Error {}".format(e
))
291 raise vimconn
.vimconnNotFoundException("Flavor not found at VIM")
292 return {'id':r
.get('uuid'), 'name':r
.get('name'), 'fos':r
}
294 def get_flavor_id_from_data(self
, flavor_dict
):
295 """Obtain flavor id that match the flavor description
297 'flavor_dict': dictionary that contains:
298 'disk': main hard disk in GB
300 'vcpus': number of virtual cpus
301 #TODO: complete parameters for EPA
302 Returns the flavor_id or raises a vimconnNotFoundException
304 self
.logger
.debug('VIM get_flavor_id_from_data with args : {}'.format(locals()))
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 x
.get('ram_size_mb') == flavor_dict
.get('ram') and x
.get('storage_size_gb') == flavor_dict
.get('disk'))]
312 raise vimconn
.vimconnNotFoundException ( "No flavor found" )
315 def new_flavor(self
, flavor_data
):
316 """Adds a tenant flavor to VIM
317 flavor_data contains a dictionary with information, keys:
319 ram: memory (cloud type) in MBytes
320 vpcus: cpus (cloud type)
321 extended: EPA parameters
322 - numas: #items requested in same NUMA
323 memory: number of 1G huge pages memory
324 paired-threads|cores|threads: number of paired hyperthreads, complete cores OR individual threads
325 interfaces: # passthrough(PT) or SRIOV interfaces attached to this numa
326 - name: interface name
327 dedicated: yes|no|yes:sriov; for PT, SRIOV or only one SRIOV for the physical NIC
328 bandwidth: X Gbps; requested guarantee bandwidth
329 vpci: requested virtual PCI address
333 Returns the flavor identifier"""
334 self
.logger
.debug('VIM new_flavor with args: {}'.format(locals()))
335 flv_id
= '{}'.format(uuid
.uuid4())
338 'name':flavor_data
.get('name'),
339 'cpu_arch': self
.arch
,
340 'cpu_min_count': flavor_data
.get('vcpus'),
342 'ram_size_mb':float(flavor_data
.get('ram')),
343 'storage_size_gb':float(flavor_data
.get('disk'))
346 self
.fos_api
.flavor
.add(desc
)
347 except fimerrors
.FIMAResouceExistingException
as free
:
348 raise vimconn
.vimconnConflictException("Flavor {} already exist at VIM. Error {}".format(flv_id
, free
))
349 except Exception as e
:
350 raise vimconn
.vimconnConnectionException("VIM not reachable. Error {}".format(e
))
354 def delete_flavor(self
, flavor_id
):
355 """Deletes a tenant flavor from VIM identify by its id
356 Returns the used id or raise an exception"""
358 self
.fos_api
.flavor
.remove(flavor_id
)
359 except fimerrors
.FIMNotFoundException
as fnfe
:
360 raise vimconn
.vimconnNotFoundException("Flavor {} not found at VIM (already deleted?). Error {}".format(flavor_id
, fnfe
))
361 except Exception as e
:
362 raise vimconn
.vimconnConnectionException("VIM not reachable. Error {}".format(e
))
365 def new_image(self
, image_dict
):
366 """ Adds a tenant image to VIM. imge_dict is a dictionary with:
368 disk_format: qcow2, vhd, vmdk, raw (by default), ...
369 location: path or URI
370 public: "yes" or "no"
371 metadata: metadata of the image
372 Returns the image id or raises an exception if failed
374 self
.logger
.debug('VIM new_image with args: {}'.format(locals()))
375 img_id
= '{}'.format(uuid
.uuid4())
377 'name':image_dict
.get('name'),
379 'uri':image_dict
.get('location')
382 self
.fos_api
.image
.add(desc
)
383 except fimerrors
.FIMAResouceExistingException
as free
:
384 raise vimconn
.vimconnConflictException("Image {} already exist at VIM. Error {}".format(img_id
, free
))
385 except Exception as e
:
386 raise vimconn
.vimconnConnectionException("VIM not reachable. Error {}".format(e
))
389 def get_image_id_from_path(self
, path
):
391 """Get the image id from image path in the VIM database.
392 Returns the image_id or raises a vimconnNotFoundException
394 self
.logger
.debug('VIM get_image_id_from_path with args: {}'.format(locals()))
396 imgs
= self
.fos_api
.image
.list()
397 except Exception as e
:
398 raise vimconn
.vimconnConnectionException("VIM not reachable. Error {}".format(e
))
399 res
= [x
.get('uuid') for x
in imgs
if x
.get('uri')==path
]
401 raise vimconn
.vimconnNotFoundException("Image with this path was not found")
404 def get_image_list(self
, filter_dict
={}):
405 """Obtain tenant images from VIM
409 checksum: image checksum
411 Returns the image list of dictionaries:
412 [{<the fields at Filter_dict plus some VIM specific>}, ...]
415 self
.logger
.debug('VIM get_image_list args: {}'.format(locals()))
418 fimgs
= self
.fos_api
.image
.list()
419 except Exception as e
:
420 raise vimconn
.vimconnConnectionException("VIM not reachable. Error {}".format(e
))
423 partial(self
.__name
_filter
, filter_name
=filter_dict
.get('name')),
424 partial(self
.__id
_filter
,filter_id
=filter_dict
.get('id')),
425 partial(self
.__checksum
_filter
,filter_checksum
=filter_dict
.get('checksum'))
433 match
= match
and f(i
)
439 'name':i
.get('name'),
441 'checksum':i
.get('checksum'),
442 'location':i
.get('uri'),
447 #raise vimconnNotImplemented( "Should have implemented this" )
449 def new_vminstance(self
, name
, description
, start
, image_id
, flavor_id
, net_list
, cloud_config
=None, disk_list
=None,
450 availability_zone_index
=None, availability_zone_list
=None):
451 """Adds a VM instance to VIM
453 'start': (boolean) indicates if VM must start or created in pause mode.
454 'image_id','flavor_id': image and flavor VIM id to use for the VM
455 'net_list': list of interfaces, each one is a dictionary with:
456 'name': (optional) name for the interface.
457 'net_id': VIM network id where this interface must be connect to. Mandatory for type==virtual
458 'vpci': (optional) virtual vPCI address to assign at the VM. Can be ignored depending on VIM capabilities
459 'model': (optional and only have sense for type==virtual) interface model: virtio, e1000, ...
460 'mac_address': (optional) mac address to assign to this interface
461 'ip_address': (optional) IP address to assign to this interface
462 #TODO: CHECK if an optional 'vlan' parameter is needed for VIMs when type if VF and net_id is not provided,
463 the VLAN tag to be used. In case net_id is provided, the internal network vlan is used for tagging VF
464 'type': (mandatory) can be one of:
465 'virtual', in this case always connected to a network of type 'net_type=bridge'
466 'PCI-PASSTHROUGH' or 'PF' (passthrough): depending on VIM capabilities it can be connected to a data/ptp network ot it
467 can created unconnected
468 'SR-IOV' or 'VF' (SRIOV with VLAN tag): same as PF for network connectivity.
469 'VFnotShared'(SRIOV without VLAN tag) same as PF for network connectivity. VF where no other VFs
470 are allocated on the same physical NIC
471 'bw': (optional) only for PF/VF/VFnotShared. Minimal Bandwidth required for the interface in GBPS
472 'port_security': (optional) If False it must avoid any traffic filtering at this interface. If missing
473 or True, it must apply the default VIM behaviour
474 After execution the method will add the key:
475 'vim_id': must be filled/added by this method with the VIM identifier generated by the VIM for this
476 interface. 'net_list' is modified
477 'cloud_config': (optional) dictionary with:
478 'key-pairs': (optional) list of strings with the public key to be inserted to the default user
479 'users': (optional) list of users to be inserted, each item is a dict with:
480 'name': (mandatory) user name,
481 'key-pairs': (optional) list of strings with the public key to be inserted to the user
482 'user-data': (optional) can be a string with the text script to be passed directly to cloud-init,
483 or a list of strings, each one contains a script to be passed, usually with a MIMEmultipart file
484 'config-files': (optional). List of files to be transferred. Each item is a dict with:
485 'dest': (mandatory) string with the destination absolute path
486 'encoding': (optional, by default text). Can be one of:
487 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
488 'content' (mandatory): string with the content of the file
489 'permissions': (optional) string with file permissions, typically octal notation '0644'
490 'owner': (optional) file owner, string with the format 'owner:group'
491 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk)
492 'disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
493 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
494 'size': (mandatory) string with the size of the disk in GB
495 availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required
496 availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
497 availability_zone_index is None
498 Returns a tuple with the instance identifier and created_items or raises an exception on error
499 created_items can be None or a dictionary where this method can include key-values that will be passed to
500 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
501 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
504 self
.logger
.debug('new_vminstance with rgs: {}'.format(locals()))
505 fdu_uuid
= '{}'.format(uuid
.uuid4())
507 flv
= self
.fos_api
.flavor
.get(flavor_id
)
508 img
= self
.fos_api
.image
.get(image_id
)
511 raise vimconn
.vimconnNotFoundException("Flavor {} not found at VIM".format(flavor_id
))
513 raise vimconn
.vimconnNotFoundException("Image {} not found at VIM".format(image_id
))
518 'connection_points':[]
524 'computation_requirements':flv
,
526 'hypervisor':self
.hv
,
527 'migration_kind':'LIVE',
530 'connection_points':[],
538 cp_id
= '{}'.format(uuid
.uuid4())
539 n
.update({'vim_id':cp_id
})
540 pair_id
= n
.get('net_id')
547 'name':n
.get('name','eth{}'.format(intf_id
)),
549 'if_type':'INTERNAL',
550 'virtual_interface':{
551 'intf_type':n
.get('model','VIRTIO'),
552 'vpci':n
.get('vpci','0:0:0'),
553 'bandwidth':int(n
.get('bw', 100))
556 if n
.get('mac_address', None) is not None:
557 intf_d
['mac_address'] = n
['mac_address']
559 created_items
['connection_points'].append(cp_id
)
560 fdu_desc
['connection_points'].append(cp_d
)
561 fdu_desc
['interfaces'].append(intf_d
)
563 intf_id
= intf_id
+ 1
565 if cloud_config
is not None:
567 'conf_type':'CLOUD_INIT'
569 if cloud_config
.get('user-data') is not None:
570 configuration
.update({'script':cloud_config
.get('user-data')})
571 if cloud_config
.get('key-pairs') is not None:
572 configuration
.update({'ssh_keys':cloud_config
.get('key-pairs')})
574 if 'script' in configuration
:
575 fdu_desc
.update({'configuration':configuration
})
577 ### NODE Selection ###
578 # Infrastructure info
581 # computational capabilities -> cpu, ram, and disk available
582 # hypervisors -> list of available hypervisors (eg. KVM, LXD, BARE)
586 # UPDATING AVAILABLE INFRASTRUCTURE
588 if len(self
.nodes
) == 0:
589 nodes_id
= self
.fos_api
.node
.list()
591 nodes_id
= self
.nodes
594 n_info
= self
.fos_api
.node
.info(n
)
598 for p
in self
.fos_api
.node
.plugins(n
):
599 n_plugs
.append(self
.fos_api
.plugin
.info(n
,p
))
601 n_cpu_number
= len(n_info
.get('cpu'))
602 n_cpu_arch
= n_info
.get('cpu')[0].get('arch')
603 n_cpu_freq
= n_info
.get('cpu')[0].get('frequency')
604 n_ram
= n_info
.get('ram').get('size')
605 n_disk_size
= sorted(list(filter(lambda x
: 'sda' in x
['local_address'], n_info
.get('disks'))), key
= lambda k
: k
['dimension'])[-1].get('dimension')
609 if p
.get('type') == 'runtime':
610 hvs
.append(p
.get('name'))
614 'computational_capabilities':{
615 'cpu_count':n_cpu_number
,
616 'cpu_arch':n_cpu_arch
,
617 'cpu_freq':n_cpu_freq
,
619 'disk_size':n_disk_size
626 compatible_nodes
= []
628 if fdu_desc
.get('hypervisor') in n
.get('hypervisors'):
629 n_comp
= n
.get('computational_capabilities')
630 f_comp
= fdu_desc
.get('computation_requirements')
631 if f_comp
.get('cpu_arch') == n_comp
.get('cpu_arch'):
632 if f_comp
.get('cpu_min_count') <= n_comp
.get('cpu_count') and f_comp
.get('ram_size_mb') <= n_comp
.get('ram_size'):
633 if f_comp
.get('disk_size_gb') <= n_comp
.get('disk_size'):
634 compatible_nodes
.append(n
)
636 if len(compatible_nodes
) == 0:
637 raise vimconn
.vimconnConflictException("No available nodes at VIM")
638 selected_node
= random
.choice(compatible_nodes
)
640 created_items
.update({'fdu_id':fdu_uuid
, 'node_id': selected_node
.get('uuid')})
642 self
.logger
.debug('FOS Node {} FDU Descriptor: {}'.format(selected_node
.get('uuid'), fdu_desc
))
645 self
.fos_api
.fdu
.onboard(fdu_desc
)
646 instanceid
= self
.fos_api
.fdu
.instantiate(fdu_uuid
, selected_node
.get('uuid'))
647 created_items
.update({'instance_id':instanceid
})
649 self
.fdu_node_map
.update({instanceid
: selected_node
.get('uuid')})
650 self
.logger
.debug('new_vminstance return: {}'.format((fdu_uuid
, created_items
)))
651 return (instanceid
, created_items
)
652 except fimerrors
.FIMAResouceExistingException
as free
:
653 raise vimconn
.vimconnConflictException("VM already exists at VIM. Error {}".format(free
))
654 except Exception as e
:
655 raise vimconn
.vimconnException("Error while instantiating VM {}. Error {}".format(name
, e
))
658 def get_vminstance(self
,vm_id
):
659 """Returns the VM instance information from VIM"""
660 self
.logger
.debug('VIM get_vminstance with args: {}'.format(locals()))
663 intsinfo
= self
.fos_api
.fdu
.instance_info(vm_id
)
664 except Exception as e
:
665 raise vimconn
.vimconnConnectionException("VIM not reachable. Error {}".format(e
))
667 raise vimconn
.vimconnNotFoundException('VM with id {} not found!'.format(vm_id
))
671 def delete_vminstance(self
, vm_id
, created_items
=None):
673 Removes a VM instance from VIM and each associate elements
674 :param vm_id: VIM identifier of the VM, provided by method new_vminstance
675 :param created_items: dictionary with extra items to be deleted. provided by method new_vminstance and/or method
677 :return: None or the same vm_id. Raises an exception on fail
679 self
.logger
.debug('FOS delete_vminstance with args: {}'.format(locals()))
680 fduid
= created_items
.get('fdu_id')
682 self
.fos_api
.fdu
.terminate(vm_id
)
683 self
.fos_api
.fdu
.offload(fduid
)
684 except Exception as e
:
685 raise vimconn
.vimconnException("Error on deletting VM with id {}. Error {}".format(vm_id
,e
))
688 #raise vimconnNotImplemented( "Should have implemented this" )
690 def refresh_vms_status(self
, vm_list
):
691 """Get the status of the virtual machines and their interfaces/ports
692 Params: the list of VM identifiers
693 Returns a dictionary with:
694 vm_id: #VIM id of this Virtual Machine
695 status: #Mandatory. Text with one of:
696 # DELETED (not found at vim)
697 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
698 # OTHER (Vim reported other status not understood)
699 # ERROR (VIM indicates an ERROR status)
700 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
701 # BUILD (on building process), ERROR
702 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
704 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
705 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
706 interfaces: list with interface info. Each item a dictionary with:
707 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
708 mac_address: #Text format XX:XX:XX:XX:XX:XX
709 vim_net_id: #network id where this interface is connected, if provided at creation
710 vim_interface_id: #interface/port VIM id
711 ip_address: #null, or text with IPv4, IPv6 address
712 compute_node: #identification of compute node where PF,VF interface is allocated
713 pci: #PCI address of the NIC that hosts the PF,VF
714 vlan: #physical VLAN used for VF
716 self
.logger
.debug('FOS refresh_vms_status with args: {}'.format(locals()))
719 'CONFIGURE':'INACTIVE',
728 self
.logger
.debug('FOS refresh_vms_status for {}'.format(vm
))
731 nid
= self
.fdu_node_map
.get(vm
)
734 'status':'VIM_ERROR',
735 'error_msg':'Not compute node associated for VM'
740 vm_info
= self
.fos_api
.fdu
.instance_info(vm
)
743 'status':'VIM_ERROR',
744 'error_msg':'unable to connect to VIM'
749 r
.update({vm
:{'status':'DELETED'}})
753 desc
= self
.fos_api
.fdu
.info(vm_info
['fdu_uuid'])
754 osm_status
= fos2osm_status
.get(vm_info
.get('status'))
756 self
.logger
.debug('FOS status info {}'.format(vm_info
))
757 self
.logger
.debug('FOS status is {} <-> OSM Status {}'.format(vm_info
.get('status'), osm_status
))
758 info
.update({'status':osm_status
})
759 if vm_info
.get('status') == 'ERROR':
760 info
.update({'error_msg':vm_info
.get('error_code')})
761 info
.update({'vim_info':yaml
.safe_dump(vm_info
)})
764 for intf_name
in vm_info
.get('hypervisor_info').get('network',[]):
765 intf_info
= vm_info
.get('hypervisor_info').get('network').get(intf_name
)
767 face
['compute_node'] = nid
768 face
['vim_info'] = yaml
.safe_dump(intf_info
)
769 face
['mac_address'] = intf_info
.get('hwaddr')
771 for a
in intf_info
.get('addresses'):
772 addrs
.append(a
.get('address'))
774 face
['ip_address'] = ','.join(addrs
)
776 face
['ip_address'] = ''
777 face
['pci'] = '0:0:0.0'
778 # getting net id by CP
780 cp_info
= vm_info
.get('connection_points')[i
]
783 if cp_info
is not None:
784 cp_id
= cp_info
['cp_uuid']
785 cps_d
= desc
['connection_points']
786 matches
= [x
for x
in cps_d
if x
['uuid'] == cp_id
]
789 face
['vim_net_id'] = cpd
.get('pair_id','')
791 face
['vim_net_id'] = ''
792 face
['vim_interface_id'] = cp_id
793 # cp_info.get('uuid')
795 face
['vim_net_id'] = ''
796 face
['vim_interface_id'] = intf_name
802 info
.update({'interfaces':faces
})
804 self
.logger
.debug('FOS refresh_vms_status res for {} is {}'.format(vm
, info
))
805 self
.logger
.debug('FOS refresh_vms_status res is {}'.format(r
))
809 #raise vimconnNotImplemented( "Should have implemented this" )
811 def action_vminstance(self
, vm_id
, action_dict
, created_items
={}):
813 Send and action over a VM instance. Returns created_items if the action was successfully sent to the VIM.
814 created_items is a dictionary with items that
815 :param vm_id: VIM identifier of the VM, provided by method new_vminstance
816 :param action_dict: dictionary with the action to perform
817 :param created_items: provided by method new_vminstance is a dictionary with key-values that will be passed to
818 the method delete_vminstance. Can be used to store created ports, volumes, etc. Format is vimconnector
819 dependent, but do not use nested dictionaries and a value of None should be the same as not present. This
820 method can modify this value
821 :return: None, or a console dict
823 self
.logger
.debug('VIM action_vminstance with args: {}'.format(locals()))
824 nid
= self
.fdu_node_map
.get(vm_id
)
826 raise vimconn
.vimconnNotFoundException('No node for this VM')
828 fdu_info
= self
.fos_api
.fdu
.instance_info(vm_id
)
829 if "start" in action_dict
:
830 if fdu_info
.get('status') == 'CONFIGURE':
831 self
.fos_api
.fdu
.start(vm_id
)
832 elif fdu_info
.get('status') == 'PAUSE':
833 self
.fos_api
.fdu
.resume(vm_id
)
835 raise vimconn
.vimconnConflictException("Cannot start from this state")
836 elif "pause" in action_dict
:
837 if fdu_info
.get('status') == 'RUN':
838 self
.fos_api
.fdu
.pause(vm_id
)
840 raise vimconn
.vimconnConflictException("Cannot pause from this state")
841 elif "resume" in action_dict
:
842 if fdu_info
.get('status') == 'PAUSE':
843 self
.fos_api
.fdu
.resume(vm_id
)
845 raise vimconn
.vimconnConflictException("Cannot resume from this state")
846 elif "shutoff" in action_dict
or "shutdown" or "forceOff" in action_dict
:
847 if fdu_info
.get('status') == 'RUN':
848 self
.fos_api
.fdu
.stop(vm_id
)
850 raise vimconn
.vimconnConflictException("Cannot shutoff from this state")
851 elif "terminate" in action_dict
:
852 if fdu_info
.get('status') == 'RUN':
853 self
.fos_api
.fdu
.stop(vm_id
)
854 self
.fos_api
.fdu
.clean(vm_id
)
855 self
.fos_api
.fdu
.undefine(vm_id
)
856 # self.fos_api.fdu.offload(vm_id)
857 elif fdu_info
.get('status') == 'CONFIGURE':
858 self
.fos_api
.fdu
.clean(vm_id
)
859 self
.fos_api
.fdu
.undefine(vm_id
)
860 # self.fos_api.fdu.offload(vm_id)
861 elif fdu_info
.get('status') == 'PAUSE':
862 self
.fos_api
.fdu
.resume(vm_id
)
863 self
.fos_api
.fdu
.stop(vm_id
)
864 self
.fos_api
.fdu
.clean(vm_id
)
865 self
.fos_api
.fdu
.undefine(vm_id
)
866 # self.fos_api.fdu.offload(vm_id)
868 raise vimconn
.vimconnConflictException("Cannot terminate from this state")
869 elif "rebuild" in action_dict
:
870 raise vimconnNotImplemented("Rebuild not implememnted")
871 elif "reboot" in action_dict
:
872 if fdu_info
.get('status') == 'RUN':
873 self
.fos_api
.fdu
.stop(vm_id
)
874 self
.fos_api
.fdu
.start(vm_id
)
876 raise vimconn
.vimconnConflictException("Cannot reboot from this state")
877 except Exception as e
:
878 raise vimconn
.vimconnConnectionException("VIM not reachable. Error {}".format(e
))