Modified Docker-file include azure, corrected import azure plugin
[osm/RO.git] / RO-VIM-fos / osm_rovim_fos / vimconn_fos.py
1 # -*- coding: utf-8 -*-
2
3 ##
4 # Copyright 2019 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 fog05 using REST Client + REST Proxy
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__ ="$13-may-2019 10:35:12$"
36
37 import uuid
38 import socket
39 import struct
40 from osm_ro import vimconn
41 import random
42 import yaml
43 from functools import partial
44 from fog05rest import FIMAPI
45 from fog05rest import fimerrors
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
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))
84 return (first, last)
85
86 def __name_filter(self, desc, filter_name=None):
87 if filter_name is None:
88 return True
89 return desc.get('name') == filter_name
90
91 def __id_filter(self, desc, filter_id=None):
92 if filter_id is None:
93 return True
94 return desc.get('uuid') == filter_id
95
96 def __checksum_filter(self, desc, filter_checksum=None):
97 if filter_checksum is None:
98 return True
99 return desc.get('checksum') == filter_checksum
100
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, ...
104 """
105 try:
106 self.fos_api.check()
107 return None
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))
112
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
115 Params:
116 'net_name': name of the network
117 'net_type': one of:
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
131 """
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))
135
136 net_uuid = '{}'.format(uuid.uuid4())
137 desc = {
138 'uuid':net_uuid,
139 'name':net_name,
140 'net_type':'ELAN',
141 'is_mgmt':False
142 }
143
144 if ip_profile is not None:
145 ip = {}
146 if ip_profile.get('ip_version') == 'IPv4':
147 ip_info = {}
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})
156 else:
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))
160 try:
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
167 return net_uuid,{}
168
169 def get_network_list(self, filter_dict={}):
170 """Obtain tenant networks of VIM
171 Params:
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
189 """
190 self.logger.debug('get_network_list: {}'.format(filter_dict))
191 res = []
192 try:
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))
196
197 filters = [
198 partial(self.__name_filter, filter_name=filter_dict.get('name')),
199 partial(self.__id_filter,filter_id=filter_dict.get('id'))
200 ]
201
202 r1 = []
203
204 for n in nets:
205 match = True
206 for f in filters:
207 match = match and f(n)
208 if match:
209 r1.append(n)
210
211 for n in r1:
212 osm_net = {
213 'id':n.get('uuid'),
214 'name':n.get('name'),
215 'status':'ACTIVE'
216 }
217 res.append(osm_net)
218 return res
219
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
229 """
230 self.logger.debug('get_network: {}'.format(net_id))
231 res = self.get_network_list(filter_dict={'id':net_id})
232 if len(res) == 0:
233 raise vimconn.vimconnNotFoundException("Network {} not found at VIM".format(net_id))
234 return res[0]
235
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
239 """
240 self.logger.debug('delete_network: {}'.format(net_id))
241 try:
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))
247 return net_id
248
249 def refresh_nets_status(self, net_list):
250 """Get the status of the networks
251 Params:
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)
264 'net_id2': ...
265 """
266 self.logger.debug('Refeshing network status with args: {}'.format(locals()))
267 r = {}
268 for n in net_list:
269 try:
270 osm_n = self.get_network(n)
271 r.update({
272 osm_n.get('id'):{'status':osm_n.get('status')}
273 })
274 except vimconn.vimconnNotFoundException:
275 r.update({
276 n:{'status':'VIM_ERROR'}
277 })
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 x.get('ram_size_mb') == flavor_dict.get('ram') and x.get('storage_size_gb') == flavor_dict.get('disk'))]
311 if len(r) == 0:
312 raise vimconn.vimconnNotFoundException ( "No flavor found" )
313 return r[0]
314
315 def new_flavor(self, flavor_data):
316 """Adds a tenant flavor to VIM
317 flavor_data contains a dictionary with information, keys:
318 name: flavor name
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
330 disk: disk size
331 is_public:
332 #TODO to concrete
333 Returns the flavor identifier"""
334 self.logger.debug('VIM new_flavor with args: {}'.format(locals()))
335 flv_id = '{}'.format(uuid.uuid4())
336 desc = {
337 'uuid':flv_id,
338 'name':flavor_data.get('name'),
339 'cpu_arch': self.arch,
340 'cpu_min_count': flavor_data.get('vcpus'),
341 'cpu_min_freq': 0.0,
342 'ram_size_mb':float(flavor_data.get('ram')),
343 'storage_size_gb':float(flavor_data.get('disk'))
344 }
345 try:
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))
351 return flv_id
352
353
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"""
357 try:
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))
363 return flavor_id
364
365 def new_image(self, image_dict):
366 """ Adds a tenant image to VIM. imge_dict is a dictionary with:
367 name: name
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
373 """
374 self.logger.debug('VIM new_image with args: {}'.format(locals()))
375 img_id = '{}'.format(uuid.uuid4())
376 desc = {
377 'name':image_dict.get('name'),
378 'uuid':img_id,
379 'uri':image_dict.get('location')
380 }
381 try:
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))
387 return img_id
388
389 def get_image_id_from_path(self, path):
390
391 """Get the image id from image path in the VIM database.
392 Returns the image_id or raises a vimconnNotFoundException
393 """
394 self.logger.debug('VIM get_image_id_from_path with args: {}'.format(locals()))
395 try:
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]
400 if len(res) == 0:
401 raise vimconn.vimconnNotFoundException("Image with this path was not found")
402 return res[0]
403
404 def get_image_list(self, filter_dict={}):
405 """Obtain tenant images from VIM
406 Filter_dict can be:
407 name: image name
408 id: image uuid
409 checksum: image checksum
410 location: image path
411 Returns the image list of dictionaries:
412 [{<the fields at Filter_dict plus some VIM specific>}, ...]
413 List can be empty
414 """
415 self.logger.debug('VIM get_image_list args: {}'.format(locals()))
416 r = []
417 try:
418 fimgs = self.fos_api.image.list()
419 except Exception as e:
420 raise vimconn.vimconnConnectionException("VIM not reachable. Error {}".format(e))
421
422 filters = [
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'))
426 ]
427
428 r1 = []
429
430 for i in fimgs:
431 match = True
432 for f in filters:
433 match = match and f(i)
434 if match:
435 r1.append(i)
436
437 for i in r1:
438 img_info = {
439 'name':i.get('name'),
440 'id':i.get('uuid'),
441 'checksum':i.get('checksum'),
442 'location':i.get('uri'),
443 'fos':i
444 }
445 r.append(img_info)
446 return r
447 #raise vimconnNotImplemented( "Should have implemented this" )
448
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
452 Params:
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
502 as not present.
503 """
504 self.logger.debug('new_vminstance with rgs: {}'.format(locals()))
505 fdu_uuid = '{}'.format(uuid.uuid4())
506
507 flv = self.fos_api.flavor.get(flavor_id)
508 img = self.fos_api.image.get(image_id)
509
510 if flv is None:
511 raise vimconn.vimconnNotFoundException("Flavor {} not found at VIM".format(flavor_id))
512 if img is None:
513 raise vimconn.vimconnNotFoundException("Image {} not found at VIM".format(image_id))
514
515 created_items = {
516 'fdu_id':'',
517 'node_id':'',
518 'connection_points':[]
519 }
520
521 fdu_desc = {
522 'name':name,
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 }
533
534 nets = []
535 cps = []
536 intf_id = 0
537 for n in net_list:
538 cp_id = '{}'.format(uuid.uuid4())
539 n.update({'vim_id':cp_id})
540 pair_id = n.get('net_id')
541
542 cp_d = {
543 'uuid':cp_id,
544 'pair_id':pair_id
545 }
546 intf_d = {
547 'name':n.get('name','eth{}'.format(intf_id)),
548 'is_mgmt':False,
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))
554 }
555 }
556 if n.get('mac_address', None) is not None:
557 intf_d['mac_address'] = n['mac_address']
558
559 created_items['connection_points'].append(cp_id)
560 fdu_desc['connection_points'].append(cp_d)
561 fdu_desc['interfaces'].append(intf_d)
562
563 intf_id = intf_id + 1
564
565 if cloud_config is not None:
566 configuration = {
567 'conf_type':'CLOUD_INIT'
568 }
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')})
573
574 if 'script' in configuration:
575 fdu_desc.update({'configuration':configuration})
576
577 ### NODE Selection ###
578 # Infrastructure info
579 # nodes dict with
580 # uuid -> node uuid
581 # computational capabilities -> cpu, ram, and disk available
582 # hypervisors -> list of available hypervisors (eg. KVM, LXD, BARE)
583 #
584 #
585
586 # UPDATING AVAILABLE INFRASTRUCTURE
587
588 if len(self.nodes) == 0:
589 nodes_id = self.fos_api.node.list()
590 else:
591 nodes_id = self.nodes
592 nodes = []
593 for n in nodes_id:
594 n_info = self.fos_api.node.info(n)
595 if n_info is None:
596 continue
597 n_plugs = []
598 for p in self.fos_api.node.plugins(n):
599 n_plugs.append(self.fos_api.plugin.info(n,p))
600
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')
606
607 hvs = []
608 for p in n_plugs:
609 if p.get('type') == 'runtime':
610 hvs.append(p.get('name'))
611
612 ni = {
613 'uuid':n,
614 'computational_capabilities':{
615 'cpu_count':n_cpu_number,
616 'cpu_arch':n_cpu_arch,
617 'cpu_freq':n_cpu_freq,
618 'ram_size':n_ram,
619 'disk_size':n_disk_size
620 },
621 'hypervisors':hvs
622 }
623 nodes.append(ni)
624
625 # NODE SELECTION
626 compatible_nodes = []
627 for n in 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)
635
636 if len(compatible_nodes) == 0:
637 raise vimconn.vimconnConflictException("No available nodes at VIM")
638 selected_node = random.choice(compatible_nodes)
639
640 created_items.update({'fdu_id':fdu_uuid, 'node_id': selected_node.get('uuid')})
641
642 self.logger.debug('FOS Node {} FDU Descriptor: {}'.format(selected_node.get('uuid'), fdu_desc))
643
644 try:
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})
648
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))
656
657
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()))
661
662 try:
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))
666 if intsinfo is None:
667 raise vimconn.vimconnNotFoundException('VM with id {} not found!'.format(vm_id))
668 return intsinfo
669
670
671 def delete_vminstance(self, vm_id, created_items=None):
672 """
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
676 action_vminstance
677 :return: None or the same vm_id. Raises an exception on fail
678 """
679 self.logger.debug('FOS delete_vminstance with args: {}'.format(locals()))
680 fduid = created_items.get('fdu_id')
681 try:
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))
686 return vm_id
687
688 #raise vimconnNotImplemented( "Should have implemented this" )
689
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
703 #
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
715 """
716 self.logger.debug('FOS refresh_vms_status with args: {}'.format(locals()))
717 fos2osm_status = {
718 'DEFINE':'OTHER',
719 'CONFIGURE':'INACTIVE',
720 'RUN':'ACTIVE',
721 'PAUSE':'PAUSED',
722 'ERROR':'ERROR'
723 }
724
725 r = {}
726
727 for vm in vm_list:
728 self.logger.debug('FOS refresh_vms_status for {}'.format(vm))
729
730 info = {}
731 nid = self.fdu_node_map.get(vm)
732 if nid is None:
733 r.update({vm:{
734 'status':'VIM_ERROR',
735 'error_msg':'Not compute node associated for VM'
736 }})
737 continue
738
739 try:
740 vm_info = self.fos_api.fdu.instance_info(vm)
741 except:
742 r.update({vm:{
743 'status':'VIM_ERROR',
744 'error_msg':'unable to connect to VIM'
745 }})
746 continue
747
748 if vm_info is None:
749 r.update({vm:{'status':'DELETED'}})
750 continue
751
752
753 desc = self.fos_api.fdu.info(vm_info['fdu_uuid'])
754 osm_status = fos2osm_status.get(vm_info.get('status'))
755
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)})
762 faces = []
763 i = 0
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)
766 face = {}
767 face['compute_node'] = nid
768 face['vim_info'] = yaml.safe_dump(intf_info)
769 face['mac_address'] = intf_info.get('hwaddr')
770 addrs = []
771 for a in intf_info.get('addresses'):
772 addrs.append(a.get('address'))
773 if len(addrs) >= 0:
774 face['ip_address'] = ','.join(addrs)
775 else:
776 face['ip_address'] = ''
777 face['pci'] = '0:0:0.0'
778 # getting net id by CP
779 try:
780 cp_info = vm_info.get('connection_points')[i]
781 except IndexError:
782 cp_info = None
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]
787 if len(matches) > 0:
788 cpd = matches[0]
789 face['vim_net_id'] = cpd.get('pair_id','')
790 else:
791 face['vim_net_id'] = ''
792 face['vim_interface_id'] = cp_id
793 # cp_info.get('uuid')
794 else:
795 face['vim_net_id'] = ''
796 face['vim_interface_id'] = intf_name
797 faces.append(face)
798 i += 1
799
800
801
802 info.update({'interfaces':faces})
803 r.update({vm:info})
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))
806 return r
807
808
809 #raise vimconnNotImplemented( "Should have implemented this" )
810
811 def action_vminstance(self, vm_id, action_dict, created_items={}):
812 """
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
822 """
823 self.logger.debug('VIM action_vminstance with args: {}'.format(locals()))
824 nid = self.fdu_node_map.get(vm_id)
825 if nid is None:
826 raise vimconn.vimconnNotFoundException('No node for this VM')
827 try:
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)
834 else:
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)
839 else:
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)
844 else:
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)
849 else:
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)
867 else:
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)
875 else:
876 raise vimconn.vimconnConflictException("Cannot reboot from this state")
877 except Exception as e:
878 raise vimconn.vimconnConnectionException("VIM not reachable. Error {}".format(e))