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