Pip standerdization and tox replacement
[osm/RO.git] / RO-VIM-opennebula / osm_rovim_opennebula / vimconn_opennebula.py
1 # -*- coding: utf-8 -*-
2
3 ##
4 # Copyright 2017 Telefonica Digital Spain S.L.U.
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
18 # License for the specific language governing permissions and limitations
19 # under the License.
20 #
21 # For those usages not covered by the Apache License, Version 2.0 please
22 # contact with: patent-office@telefonica.com
23 ##
24
25 """
26 vimconnector implements all the methods to interact with OpenNebula using the XML-RPC API.
27 """
28 __author__ = (
29 "Jose Maria Carmona Perez,Juan Antonio Hernando Labajo, Emilio Abraham Garrido Garcia,Alberto Florez "
30 "Pages, Andres Pozo Munoz, Santiago Perez Marin, Onlife Networks Telefonica I+D Product Innovation "
31 )
32 __date__ = "$13-dec-2017 11:09:29$"
33
34 from osm_ro_plugin import vimconn
35 import logging
36 import requests
37
38 # import logging
39 import oca
40
41 # import untangle
42 import math
43 import random
44 import pyone
45
46
47 class vimconnector(vimconn.VimConnector):
48 def __init__(
49 self,
50 uuid,
51 name,
52 tenant_id,
53 tenant_name,
54 url,
55 url_admin=None,
56 user=None,
57 passwd=None,
58 log_level="DEBUG",
59 config={},
60 persistent_info={},
61 ):
62 """Constructor of VIM
63 Params:
64 'uuid': id asigned to this VIM
65 'name': name assigned to this VIM, can be used for logging
66 'tenant_id', 'tenant_name': (only one of them is mandatory) VIM tenant to be used
67 'url_admin': (optional), url used for administrative tasks
68 'user', 'passwd': credentials of the VIM user
69 'log_level': provider if it should use a different log_level than the general one
70 'config': dictionary with extra VIM information. This contains a consolidate version of general VIM config
71 at creation and particular VIM config at teh attachment
72 'persistent_info': dict where the class can store information that will be available among class
73 destroy/creation cycles. This info is unique per VIM/credential. At first call it will contain an
74 empty dict. Useful to store login/tokens information for speed up communication
75
76 Returns: Raise an exception is some needed parameter is missing, but it must not do any connectivity
77 check against the VIM
78 """
79 vimconn.VimConnector.__init__(
80 self,
81 uuid,
82 name,
83 tenant_id,
84 tenant_name,
85 url,
86 url_admin,
87 user,
88 passwd,
89 log_level,
90 config,
91 )
92
93 self.logger = logging.getLogger("ro.vim.openstack")
94
95 def _new_one_connection(self):
96 return pyone.OneServer(self.url, session=self.user + ":" + self.passwd)
97
98 def new_tenant(self, tenant_name, tenant_description):
99 # """Adds a new tenant to VIM with this name and description, returns the tenant identifier"""
100 try:
101 client = oca.Client(self.user + ":" + self.passwd, self.url)
102 group_list = oca.GroupPool(client)
103 user_list = oca.UserPool(client)
104 group_list.info()
105 user_list.info()
106 create_primarygroup = 1
107
108 # create group-tenant
109 for group in group_list:
110 if str(group.name) == str(tenant_name):
111 create_primarygroup = 0
112 break
113
114 if create_primarygroup == 1:
115 oca.Group.allocate(client, tenant_name)
116
117 group_list.info()
118
119 # set to primary_group the tenant_group and oneadmin to secondary_group
120 for group in group_list:
121 if str(group.name) == str(tenant_name):
122 for user in user_list:
123 if str(user.name) == str(self.user):
124 if user.name == "oneadmin":
125 return str(0)
126 else:
127 self._add_secondarygroup(user.id, group.id)
128 user.chgrp(group.id)
129
130 return str(group.id)
131 except Exception as e:
132 self.logger.error("Create new tenant error: " + str(e))
133
134 raise vimconn.VimConnException(e)
135
136 def delete_tenant(self, tenant_id):
137 """Delete a tenant from VIM. Returns the old tenant identifier"""
138 try:
139 client = oca.Client(self.user + ":" + self.passwd, self.url)
140 group_list = oca.GroupPool(client)
141 user_list = oca.UserPool(client)
142 group_list.info()
143 user_list.info()
144
145 for group in group_list:
146 if str(group.id) == str(tenant_id):
147 for user in user_list:
148 if str(user.name) == str(self.user):
149 self._delete_secondarygroup(user.id, group.id)
150 group.delete(client)
151
152 return None
153
154 raise vimconn.VimConnNotFoundException(
155 "Group {} not found".format(tenant_id)
156 )
157 except Exception as e:
158 self.logger.error("Delete tenant " + str(tenant_id) + " error: " + str(e))
159 raise vimconn.VimConnException(e)
160
161 def _add_secondarygroup(self, id_user, id_group):
162 # change secondary_group to primary_group
163 params = '<?xml version="1.0"?> \
164 <methodCall>\
165 <methodName>one.user.addgroup</methodName>\
166 <params>\
167 <param>\
168 <value><string>{}:{}</string></value>\
169 </param>\
170 <param>\
171 <value><int>{}</int></value>\
172 </param>\
173 <param>\
174 <value><int>{}</int></value>\
175 </param>\
176 </params>\
177 </methodCall>'.format(
178 self.user, self.passwd, (str(id_user)), (str(id_group))
179 )
180 requests.post(self.url, params)
181
182 def _delete_secondarygroup(self, id_user, id_group):
183 params = '<?xml version="1.0"?> \
184 <methodCall>\
185 <methodName>one.user.delgroup</methodName>\
186 <params>\
187 <param>\
188 <value><string>{}:{}</string></value>\
189 </param>\
190 <param>\
191 <value><int>{}</int></value>\
192 </param>\
193 <param>\
194 <value><int>{}</int></value>\
195 </param>\
196 </params>\
197 </methodCall>'.format(
198 self.user, self.passwd, (str(id_user)), (str(id_group))
199 )
200 requests.post(self.url, params)
201
202 def new_network(
203 self,
204 net_name,
205 net_type,
206 ip_profile=None,
207 shared=False,
208 provider_network_profile=None,
209 ):
210 """Adds a tenant network to VIM
211 Params:
212 'net_name': name of the network
213 'net_type': one of:
214 'bridge': overlay isolated network
215 'data': underlay E-LAN network for Passthrough and SRIOV interfaces
216 'ptp': underlay E-LINE network for Passthrough and SRIOV interfaces.
217 'ip_profile': is a dict containing the IP parameters of the network
218 'ip_version': can be "IPv4" or "IPv6" (Currently only IPv4 is implemented)
219 'subnet_address': ip_prefix_schema, that is X.X.X.X/Y
220 'gateway_address': (Optional) ip_schema, that is X.X.X.X
221 'dns_address': (Optional) comma separated list of ip_schema, e.g. X.X.X.X[,X,X,X,X]
222 'dhcp_enabled': True or False
223 'dhcp_start_address': ip_schema, first IP to grant
224 'dhcp_count': number of IPs to grant.
225 'shared': if this network can be seen/use by other tenants/organization
226 'provider_network_profile': (optional) contains {segmentation-id: vlan, provider-network: vim_netowrk}
227 Returns a tuple with the network identifier and created_items, or raises an exception on error
228 created_items can be None or a dictionary where this method can include key-values that will be passed to
229 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
230 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
231 as not present.
232 """
233 # oca library method cannot be used in this case (problem with cluster parameters)
234 try:
235 vlan = None
236
237 if provider_network_profile:
238 vlan = provider_network_profile.get("segmentation-id")
239
240 created_items = {}
241 one = self._new_one_connection()
242 size = "254"
243
244 if ip_profile is None:
245 subnet_rand = random.randint(0, 255)
246 ip_start = "192.168.{}.1".format(subnet_rand)
247 else:
248 index = ip_profile["subnet_address"].find("/")
249 ip_start = ip_profile["subnet_address"][:index]
250
251 if "dhcp_count" in ip_profile and ip_profile["dhcp_count"] is not None:
252 size = str(ip_profile["dhcp_count"])
253 elif (
254 "dhcp_count" not in ip_profile
255 and ip_profile["ip_version"] == "IPv4"
256 ):
257 prefix = ip_profile["subnet_address"][index + 1 :]
258 size = int(math.pow(2, 32 - prefix))
259
260 if (
261 "dhcp_start_address" in ip_profile
262 and ip_profile["dhcp_start_address"] is not None
263 ):
264 ip_start = str(ip_profile["dhcp_start_address"])
265 # if ip_profile["ip_version"] == "IPv6":
266 # ip_prefix_type = "GLOBAL_PREFIX"
267
268 if vlan is not None:
269 vlan_id = vlan
270 else:
271 vlan_id = str(random.randint(100, 4095))
272
273 # if "internal" in net_name:
274 # OpenNebula not support two networks with same name
275 random_net_name = str(random.randint(1, 1000000))
276 net_name = net_name + random_net_name
277 net_id = one.vn.allocate(
278 {
279 "NAME": net_name,
280 "VN_MAD": "802.1Q",
281 "PHYDEV": self.config["network"]["phydev"],
282 "VLAN_ID": vlan_id,
283 },
284 self.config["cluster"]["id"],
285 )
286 arpool = {"AR_POOL": {"AR": {"TYPE": "IP4", "IP": ip_start, "SIZE": size}}}
287 one.vn.add_ar(net_id, arpool)
288
289 return net_id, created_items
290 except Exception as e:
291 self.logger.error("Create new network error: " + str(e))
292
293 raise vimconn.VimConnException(e)
294
295 def get_network_list(self, filter_dict={}):
296 """Obtain tenant networks of VIM
297 :params filter_dict: (optional) contains entries to return only networks that matches ALL entries:
298 name: string => returns only networks with this name
299 id: string => returns networks with this VIM id, this imply returns one network at most
300 shared: boolean >= returns only networks that are (or are not) shared
301 tenant_id: sting => returns only networks that belong to this tenant/project
302 (not used yet) admin_state_up: boolean => returns only networks that are (or are not) in admin state active
303 (not used yet) status: 'ACTIVE','ERROR',... => filter networks that are on this status
304 Returns the network list of dictionaries. each dictionary contains:
305 'id': (mandatory) VIM network id
306 'name': (mandatory) VIM network name
307 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER'
308 'network_type': (optional) can be 'vxlan', 'vlan' or 'flat'
309 'segmentation_id': (optional) in case network_type is vlan or vxlan this field contains the segmentation id
310 'error_msg': (optional) text that explains the ERROR status
311 other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param
312 List can be empty if no network map the filter_dict. Raise an exception only upon VIM connectivity,
313 authorization, or some other unspecific error
314 """
315 try:
316 one = self._new_one_connection()
317 net_pool = one.vnpool.info(-2, -1, -1).VNET
318 response = []
319
320 if "name" in filter_dict:
321 network_name_filter = filter_dict["name"]
322 else:
323 network_name_filter = None
324
325 if "id" in filter_dict:
326 network_id_filter = filter_dict["id"]
327 else:
328 network_id_filter = None
329
330 for network in net_pool:
331 if network.NAME == network_name_filter or str(network.ID) == str(
332 network_id_filter
333 ):
334 net_dict = {
335 "name": network.NAME,
336 "id": str(network.ID),
337 "status": "ACTIVE",
338 }
339 response.append(net_dict)
340
341 return response
342 except Exception as e:
343 self.logger.error("Get network list error: " + str(e))
344
345 raise vimconn.VimConnException(e)
346
347 def get_network(self, net_id):
348 """Obtain network details from the 'net_id' VIM network
349 Return a dict that contains:
350 'id': (mandatory) VIM network id, that is, net_id
351 'name': (mandatory) VIM network name
352 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER'
353 'error_msg': (optional) text that explains the ERROR status
354 other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param
355 Raises an exception upon error or when network is not found
356 """
357 try:
358 one = self._new_one_connection()
359 net_pool = one.vnpool.info(-2, -1, -1).VNET
360 net = {}
361
362 for network in net_pool:
363 if str(network.ID) == str(net_id):
364 net["id"] = network.ID
365 net["name"] = network.NAME
366 net["status"] = "ACTIVE"
367 break
368
369 if net:
370 return net
371 else:
372 raise vimconn.VimConnNotFoundException(
373 "Network {} not found".format(net_id)
374 )
375 except Exception as e:
376 self.logger.error("Get network " + str(net_id) + " error): " + str(e))
377
378 raise vimconn.VimConnException(e)
379
380 def delete_network(self, net_id, created_items=None):
381 """
382 Removes a tenant network from VIM and its associated elements
383 :param net_id: VIM identifier of the network, provided by method new_network
384 :param created_items: dictionary with extra items to be deleted. provided by method new_network
385 Returns the network identifier or raises an exception upon error or when network is not found
386 """
387 try:
388 one = self._new_one_connection()
389 one.vn.delete(int(net_id))
390
391 return net_id
392 except Exception as e:
393 self.logger.error(
394 "Delete network " + str(net_id) + "error: network not found" + str(e)
395 )
396
397 raise vimconn.VimConnException(e)
398
399 def refresh_nets_status(self, net_list):
400 """Get the status of the networks
401 Params:
402 'net_list': a list with the VIM network id to be get the status
403 Returns a dictionary with:
404 'net_id': #VIM id of this network
405 status: #Mandatory. Text with one of:
406 # DELETED (not found at vim)
407 # VIM_ERROR (Cannot connect to VIM, authentication problems, VIM response error, ...)
408 # OTHER (Vim reported other status not understood)
409 # ERROR (VIM indicates an ERROR status)
410 # ACTIVE, INACTIVE, DOWN (admin down),
411 # BUILD (on building process)
412 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
413 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
414 'net_id2': ...
415 """
416 net_dict = {}
417 try:
418 for net_id in net_list:
419 net = {}
420
421 try:
422 net_vim = self.get_network(net_id)
423 net["status"] = net_vim["status"]
424 net["vim_info"] = None
425 except vimconn.VimConnNotFoundException as e:
426 self.logger.error("Exception getting net status: {}".format(str(e)))
427 net["status"] = "DELETED"
428 net["error_msg"] = str(e)
429 except vimconn.VimConnException as e:
430 self.logger.error(e)
431 net["status"] = "VIM_ERROR"
432 net["error_msg"] = str(e)
433
434 net_dict[net_id] = net
435
436 return net_dict
437 except vimconn.VimConnException as e:
438 self.logger.error(e)
439
440 for k in net_dict:
441 net_dict[k]["status"] = "VIM_ERROR"
442 net_dict[k]["error_msg"] = str(e)
443
444 return net_dict
445
446 def get_flavor(self, flavor_id): # Esta correcto
447 """Obtain flavor details from the VIM
448 Returns the flavor dict details {'id':<>, 'name':<>, other vim specific }
449 Raises an exception upon error or if not found
450 """
451 try:
452 one = self._new_one_connection()
453 template = one.template.info(int(flavor_id))
454
455 if template is not None:
456 return {"id": template.ID, "name": template.NAME}
457
458 raise vimconn.VimConnNotFoundException(
459 "Flavor {} not found".format(flavor_id)
460 )
461 except Exception as e:
462 self.logger.error("get flavor " + str(flavor_id) + " error: " + str(e))
463
464 raise vimconn.VimConnException(e)
465
466 def new_flavor(self, flavor_data):
467 """Adds a tenant flavor to VIM
468 flavor_data contains a dictionary with information, keys:
469 name: flavor name
470 ram: memory (cloud type) in MBytes
471 vpcus: cpus (cloud type)
472 extended: EPA parameters
473 - numas: #items requested in same NUMA
474 memory: number of 1G huge pages memory
475 paired-threads|cores|threads: number of paired hyperthreads, complete cores OR individual threads
476 interfaces: # passthrough(PT) or SRIOV interfaces attached to this numa
477 - name: interface name
478 dedicated: yes|no|yes:sriov; for PT, SRIOV or only one SRIOV for the physical NIC
479 bandwidth: X Gbps; requested guarantee bandwidth
480 vpci: requested virtual PCI address
481 disk: disk size
482 is_public:
483 #TODO to concrete
484 Returns the flavor identifier"""
485 disk_size = str(int(flavor_data["disk"]) * 1024)
486
487 try:
488 one = self._new_one_connection()
489 template_id = one.template.allocate(
490 {
491 "TEMPLATE": {
492 "NAME": flavor_data["name"],
493 "CPU": flavor_data["vcpus"],
494 "VCPU": flavor_data["vcpus"],
495 "MEMORY": flavor_data["ram"],
496 "DISK": {"SIZE": disk_size},
497 "CONTEXT": {
498 "NETWORK": "YES",
499 "SSH_PUBLIC_KEY": "$USER[SSH_PUBLIC_KEY]",
500 },
501 "GRAPHICS": {"LISTEN": "0.0.0.0", "TYPE": "VNC"},
502 "CLUSTER_ID": self.config["cluster"]["id"],
503 }
504 }
505 )
506
507 return template_id
508 except Exception as e:
509 self.logger.error("Create new flavor error: " + str(e))
510
511 raise vimconn.VimConnException(e)
512
513 def delete_flavor(self, flavor_id):
514 """Deletes a tenant flavor from VIM
515 Returns the old flavor_id
516 """
517 try:
518 one = self._new_one_connection()
519 one.template.delete(int(flavor_id), False)
520
521 return flavor_id
522 except Exception as e:
523 self.logger.error(
524 "Error deleting flavor " + str(flavor_id) + ". Flavor not found"
525 )
526
527 raise vimconn.VimConnException(e)
528
529 def get_image_list(self, filter_dict={}):
530 """Obtain tenant images from VIM
531 Filter_dict can be:
532 name: image name
533 id: image uuid
534 checksum: image checksum
535 location: image path
536 Returns the image list of dictionaries:
537 [{<the fields at Filter_dict plus some VIM specific>}, ...]
538 List can be empty
539 """
540 try:
541 one = self._new_one_connection()
542 image_pool = one.imagepool.info(-2, -1, -1).IMAGE
543 images = []
544
545 if "name" in filter_dict:
546 image_name_filter = filter_dict["name"]
547 else:
548 image_name_filter = None
549
550 if "id" in filter_dict:
551 image_id_filter = filter_dict["id"]
552 else:
553 image_id_filter = None
554
555 for image in image_pool:
556 if str(image_name_filter) == str(image.NAME) or str(image.ID) == str(
557 image_id_filter
558 ):
559 images_dict = {"name": image.NAME, "id": str(image.ID)}
560 images.append(images_dict)
561
562 return images
563 except Exception as e:
564 self.logger.error("Get image list error: " + str(e))
565 raise vimconn.VimConnException(e)
566
567 def new_vminstance(
568 self,
569 name,
570 description,
571 start,
572 image_id,
573 flavor_id,
574 net_list,
575 cloud_config=None,
576 disk_list=None,
577 availability_zone_index=None,
578 availability_zone_list=None,
579 ):
580 """
581 Adds a VM instance to VIM
582 :param name:
583 :param description:
584 :param start: (boolean) indicates if VM must start or created in pause mode.
585 :param image_id: image VIM id to use for the VM
586 :param flavor_id: flavor VIM id to use for the VM
587 :param net_list: list of interfaces, each one is a dictionary with:
588 'name': (optional) name for the interface.
589 'net_id': VIM network id where this interface must be connect to. Mandatory for type==virtual
590 'vpci': (optional) virtual vPCI address to assign at the VM. Can be ignored depending on VIM
591 capabilities
592 'model': (optional and only have sense for type==virtual) interface model: virtio, e1000, ...
593 'mac_address': (optional) mac address to assign to this interface
594 'ip_address': (optional) IP address to assign to this interface
595 #TODO: CHECK if an optional 'vlan' parameter is needed for VIMs when type if VF and net_id is not
596 provided, the VLAN tag to be used. In case net_id is provided, the internal network vlan is
597 used for tagging VF
598 'type': (mandatory) can be one of:
599 'virtual', in this case always connected to a network of type 'net_type=bridge'
600 'PCI-PASSTHROUGH' or 'PF' (passthrough): depending on VIM capabilities it can be connected to
601 a data/ptp network ot itcan created unconnected
602 'SR-IOV' or 'VF' (SRIOV with VLAN tag): same as PF for network connectivity.
603 'VFnotShared'(SRIOV without VLAN tag) same as PF for network connectivity. VF where no other VFs
604 are allocated on the same physical NIC
605 'bw': (optional) only for PF/VF/VFnotShared. Minimal Bandwidth required for the interface in GBPS
606 'port_security': (optional) If False it must avoid any traffic filtering at this interface. If missing
607 or True, it must apply the default VIM behaviour
608 After execution the method will add the key:
609 'vim_id': must be filled/added by this method with the VIM identifier generated by the VIM for this
610 interface. 'net_list' is modified
611 :param cloud_config: (optional) dictionary with:
612 'key-pairs': (optional) list of strings with the public key to be inserted to the default user
613 'users': (optional) list of users to be inserted, each item is a dict with:
614 'name': (mandatory) user name,
615 'key-pairs': (optional) list of strings with the public key to be inserted to the user
616 'user-data': (optional) can be a string with the text script to be passed directly to cloud-init,
617 or a list of strings, each one contains a script to be passed, usually with a MIMEmultipart file
618 'config-files': (optional). List of files to be transferred. Each item is a dict with:
619 'dest': (mandatory) string with the destination absolute path
620 'encoding': (optional, by default text). Can be one of:
621 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
622 'content' (mandatory): string with the content of the file
623 'permissions': (optional) string with file permissions, typically octal notation '0644'
624 'owner': (optional) file owner, string with the format 'owner:group'
625 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk)
626 :param disk_list: (optional) list with additional disks to the VM. Each item is a dict with:
627 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
628 'size': (mandatory) string with the size of the disk in GB
629 :param availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV
630 required
631 :param availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
632 availability_zone_index is None
633 :return: a tuple with the instance identifier and created_items or raises an exception on error
634 created_items can be None or a dictionary where this method can include key-values that will be passed to
635 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
636 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
637 as not present.
638 """
639 self.logger.debug(
640 "new_vminstance input: image='{}' flavor='{}' nics='{}'".format(
641 image_id, flavor_id, str(net_list)
642 )
643 )
644
645 try:
646 one = self._new_one_connection()
647 template_vim = one.template.info(int(flavor_id), True)
648 disk_size = str(template_vim.TEMPLATE["DISK"]["SIZE"])
649
650 one = self._new_one_connection()
651 template_updated = ""
652
653 for net in net_list:
654 net_in_vim = one.vn.info(int(net["net_id"]))
655 net["vim_id"] = str(net_in_vim.ID)
656 network = 'NIC = [NETWORK = "{}",NETWORK_UNAME = "{}" ]'.format(
657 net_in_vim.NAME, net_in_vim.UNAME
658 )
659 template_updated += network
660
661 template_updated += "DISK = [ IMAGE_ID = {},\n SIZE = {}]".format(
662 image_id, disk_size
663 )
664
665 if isinstance(cloud_config, dict):
666 if cloud_config.get("key-pairs"):
667 context = 'CONTEXT = [\n NETWORK = "YES",\n SSH_PUBLIC_KEY = "'
668
669 for key in cloud_config["key-pairs"]:
670 context += key + "\n"
671
672 # if False:
673 # context += '"\n USERNAME = '
674 context += '"]'
675 template_updated += context
676
677 vm_instance_id = one.template.instantiate(
678 int(flavor_id), name, False, template_updated
679 )
680 self.logger.info(
681 "Instanciating in OpenNebula a new VM name:{} id:{}".format(
682 name, flavor_id
683 )
684 )
685
686 return str(vm_instance_id), None
687 except pyone.OneNoExistsException as e:
688 self.logger.error("Network with id " + str(e) + " not found: " + str(e))
689
690 raise vimconn.VimConnNotFoundException(e)
691 except Exception as e:
692 self.logger.error("Create new vm instance error: " + str(e))
693
694 raise vimconn.VimConnException(e)
695
696 def get_vminstance(self, vm_id):
697 """Returns the VM instance information from VIM"""
698 try:
699 one = self._new_one_connection()
700 vm = one.vm.info(int(vm_id))
701
702 return vm
703 except Exception as e:
704 self.logger.error(
705 "Getting vm instance error: " + str(e) + ": VM Instance not found"
706 )
707
708 raise vimconn.VimConnException(e)
709
710 def delete_vminstance(self, vm_id, created_items=None):
711 """
712 Removes a VM instance from VIM and its associated elements
713 :param vm_id: VIM identifier of the VM, provided by method new_vminstance
714 :param created_items: dictionary with extra items to be deleted. provided by method new_vminstance and/or method
715 action_vminstance
716 :return: None or the same vm_id. Raises an exception on fail
717 """
718 try:
719 one = self._new_one_connection()
720 one.vm.recover(int(vm_id), 3)
721 vm = None
722
723 while True:
724 if vm is not None and vm.LCM_STATE == 0:
725 break
726 else:
727 vm = one.vm.info(int(vm_id))
728 except pyone.OneNoExistsException:
729 self.logger.info(
730 "The vm " + str(vm_id) + " does not exist or is already deleted"
731 )
732
733 raise vimconn.VimConnNotFoundException(
734 "The vm {} does not exist or is already deleted".format(vm_id)
735 )
736 except Exception as e:
737 self.logger.error("Delete vm instance " + str(vm_id) + " error: " + str(e))
738 raise vimconn.VimConnException(e)
739
740 def refresh_vms_status(self, vm_list):
741 """Get the status of the virtual machines and their interfaces/ports
742 Params: the list of VM identifiers
743 Returns a dictionary with:
744 vm_id: #VIM id of this Virtual Machine
745 status: #Mandatory. Text with one of:
746 # DELETED (not found at vim)
747 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
748 # OTHER (Vim reported other status not understood)
749 # ERROR (VIM indicates an ERROR status)
750 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
751 # BUILD (on building process), ERROR
752 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
753 #
754 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
755 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
756 interfaces: list with interface info. Each item a dictionary with:
757 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
758 mac_address: #Text format XX:XX:XX:XX:XX:XX
759 vim_net_id: #network id where this interface is connected, if provided at creation
760 vim_interface_id: #interface/port VIM id
761 ip_address: #null, or text with IPv4, IPv6 address
762 compute_node: #identification of compute node where PF,VF interface is allocated
763 pci: #PCI address of the NIC that hosts the PF,VF
764 vlan: #physical VLAN used for VF
765 """
766 vm_dict = {}
767 try:
768 for vm_id in vm_list:
769 vm = {}
770
771 if self.get_vminstance(vm_id) is not None:
772 vm_element = self.get_vminstance(vm_id)
773 else:
774 self.logger.info("The vm " + str(vm_id) + " does not exist.")
775 vm["status"] = "DELETED"
776 vm["error_msg"] = "The vm " + str(vm_id) + " does not exist."
777 continue
778
779 vm["vim_info"] = None
780 vm_status = vm_element.LCM_STATE
781
782 if vm_status == 3:
783 vm["status"] = "ACTIVE"
784 elif vm_status == 36:
785 vm["status"] = "ERROR"
786 vm["error_msg"] = "VM failure"
787 else:
788 vm["status"] = "BUILD"
789
790 if vm_element is not None:
791 interfaces = self._get_networks_vm(vm_element)
792 vm["interfaces"] = interfaces
793
794 vm_dict[vm_id] = vm
795
796 return vm_dict
797 except Exception as e:
798 self.logger.error(e)
799 for k in vm_dict:
800 vm_dict[k]["status"] = "VIM_ERROR"
801 vm_dict[k]["error_msg"] = str(e)
802
803 return vm_dict
804
805 def _get_networks_vm(self, vm_element):
806 interfaces = []
807 try:
808 if isinstance(vm_element.TEMPLATE["NIC"], list):
809 for net in vm_element.TEMPLATE["NIC"]:
810 interface = {
811 "vim_info": None,
812 "mac_address": str(net["MAC"]),
813 "vim_net_id": str(net["NETWORK_ID"]),
814 "vim_interface_id": str(net["NETWORK_ID"]),
815 }
816
817 # maybe it should be 2 different keys for ip_address if an interface has ipv4 and ipv6
818 if "IP" in net:
819 interface["ip_address"] = str(net["IP"])
820
821 if "IP6_GLOBAL" in net:
822 interface["ip_address"] = str(net["IP6_GLOBAL"])
823
824 interfaces.append(interface)
825 else:
826 net = vm_element.TEMPLATE["NIC"]
827 interface = {
828 "vim_info": None,
829 "mac_address": str(net["MAC"]),
830 "vim_net_id": str(net["NETWORK_ID"]),
831 "vim_interface_id": str(net["NETWORK_ID"]),
832 }
833
834 # maybe it should be 2 different keys for ip_address if an interface has ipv4 and ipv6
835 if "IP" in net:
836 interface["ip_address"] = str(net["IP"])
837
838 if "IP6_GLOBAL" in net:
839 interface["ip_address"] = str(net["IP6_GLOBAL"])
840
841 interfaces.append(interface)
842 return interfaces
843 except Exception:
844 self.logger.error(
845 "Error getting vm interface_information of vm_id: " + str(vm_element.ID)
846 )