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