X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=RO-VIM-opennebula%2Fosm_rovim_opennebula%2Fvimconn_opennebula.py;h=ad9ca29808a7f35864c0c680c1643d02bba66dae;hb=6bbe485f69b608dd8bd9b9267be0d78c6c5b0086;hp=440b1cb69720f27ec51026ae9d760635ed93af29;hpb=7d782eff123e5b44d41437377ccca66ad1e8b21b;p=osm%2FRO.git diff --git a/RO-VIM-opennebula/osm_rovim_opennebula/vimconn_opennebula.py b/RO-VIM-opennebula/osm_rovim_opennebula/vimconn_opennebula.py index 440b1cb6..ad9ca298 100644 --- a/RO-VIM-opennebula/osm_rovim_opennebula/vimconn_opennebula.py +++ b/RO-VIM-opennebula/osm_rovim_opennebula/vimconn_opennebula.py @@ -25,22 +25,40 @@ """ vimconnector implements all the methods to interact with OpenNebula using the XML-RPC API. """ -__author__ = "Jose Maria Carmona Perez,Juan Antonio Hernando Labajo, Emilio Abraham Garrido Garcia,Alberto Florez " \ - "Pages, Andres Pozo Munoz, Santiago Perez Marin, Onlife Networks Telefonica I+D Product Innovation " +__author__ = ( + "Jose Maria Carmona Perez,Juan Antonio Hernando Labajo, Emilio Abraham Garrido Garcia,Alberto Florez " + "Pages, Andres Pozo Munoz, Santiago Perez Marin, Onlife Networks Telefonica I+D Product Innovation " +) __date__ = "$13-dec-2017 11:09:29$" -from osm_ro import vimconn -import requests + +from osm_ro_plugin import vimconn import logging +import requests + +# import logging import oca -import untangle + +# import untangle import math import random import pyone -class vimconnector(vimconn.vimconnector): - def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, - log_level="DEBUG", config={}, persistent_info={}): +class vimconnector(vimconn.VimConnector): + def __init__( + self, + uuid, + name, + tenant_id, + tenant_name, + url, + url_admin=None, + user=None, + passwd=None, + log_level="DEBUG", + config={}, + persistent_info={}, + ): """Constructor of VIM Params: 'uuid': id asigned to this VIM @@ -58,30 +76,46 @@ class vimconnector(vimconn.vimconnector): Returns: Raise an exception is some needed parameter is missing, but it must not do any connectivity check against the VIM """ - - vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level, - config) + vimconn.VimConnector.__init__( + self, + uuid, + name, + tenant_id, + tenant_name, + url, + url_admin, + user, + passwd, + log_level, + config, + ) + + self.logger = logging.getLogger("ro.vim.openstack") def _new_one_connection(self): - return pyone.OneServer(self.url, session=self.user + ':' + self.passwd) + return pyone.OneServer(self.url, session=self.user + ":" + self.passwd) def new_tenant(self, tenant_name, tenant_description): - # '''Adds a new tenant to VIM with this name and description, returns the tenant identifier''' + # """Adds a new tenant to VIM with this name and description, returns the tenant identifier""" try: - client = oca.Client(self.user + ':' + self.passwd, self.url) + client = oca.Client(self.user + ":" + self.passwd, self.url) group_list = oca.GroupPool(client) user_list = oca.UserPool(client) group_list.info() user_list.info() create_primarygroup = 1 + # create group-tenant for group in group_list: if str(group.name) == str(tenant_name): create_primarygroup = 0 break + if create_primarygroup == 1: oca.Group.allocate(client, tenant_name) + group_list.info() + # set to primary_group the tenant_group and oneadmin to secondary_group for group in group_list: if str(group.name) == str(tenant_name): @@ -92,30 +126,37 @@ class vimconnector(vimconn.vimconnector): else: self._add_secondarygroup(user.id, group.id) user.chgrp(group.id) + return str(group.id) except Exception as e: self.logger.error("Create new tenant error: " + str(e)) - raise vimconn.vimconnException(e) + + raise vimconn.VimConnException(e) def delete_tenant(self, tenant_id): """Delete a tenant from VIM. Returns the old tenant identifier""" try: - client = oca.Client(self.user + ':' + self.passwd, self.url) + client = oca.Client(self.user + ":" + self.passwd, self.url) group_list = oca.GroupPool(client) user_list = oca.UserPool(client) group_list.info() user_list.info() + for group in group_list: if str(group.id) == str(tenant_id): for user in user_list: if str(user.name) == str(self.user): self._delete_secondarygroup(user.id, group.id) group.delete(client) + return None - raise vimconn.vimconnNotFoundException("Group {} not found".format(tenant_id)) + + raise vimconn.VimConnNotFoundException( + "Group {} not found".format(tenant_id) + ) except Exception as e: self.logger.error("Delete tenant " + str(tenant_id) + " error: " + str(e)) - raise vimconn.vimconnException(e) + raise vimconn.VimConnException(e) def _add_secondarygroup(self, id_user, id_group): # change secondary_group to primary_group @@ -133,7 +174,9 @@ class vimconnector(vimconn.vimconnector): {}\ \ \ - '.format(self.user, self.passwd, (str(id_user)), (str(id_group))) + '.format( + self.user, self.passwd, (str(id_user)), (str(id_group)) + ) requests.post(self.url, params) def _delete_secondarygroup(self, id_user, id_group): @@ -151,10 +194,19 @@ class vimconnector(vimconn.vimconnector): {}\ \ \ - '.format(self.user, self.passwd, (str(id_user)), (str(id_group))) + '.format( + self.user, self.passwd, (str(id_user)), (str(id_group)) + ) requests.post(self.url, params) - def new_network(self, net_name, net_type, ip_profile=None, shared=False, vlan=None): # , **vim_specific): + def new_network( + self, + net_name, + net_type, + ip_profile=None, + shared=False, + provider_network_profile=None, + ): """Adds a tenant network to VIM Params: 'net_name': name of the network @@ -171,73 +223,84 @@ class vimconnector(vimconn.vimconnector): 'dhcp_start_address': ip_schema, first IP to grant 'dhcp_count': number of IPs to grant. 'shared': if this network can be seen/use by other tenants/organization - 'vlan': in case of a data or ptp net_type, the intended vlan tag to be used for the network + 'provider_network_profile': (optional) contains {segmentation-id: vlan, provider-network: vim_netowrk} Returns a tuple with the network identifier and created_items, or raises an exception on error created_items can be None or a dictionary where this method can include key-values that will be passed to the method delete_network. Can be used to store created segments, created l2gw connections, etc. Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same as not present. """ - # oca library method cannot be used in this case (problem with cluster parameters) try: + vlan = None + + if provider_network_profile: + vlan = provider_network_profile.get("segmentation-id") + created_items = {} one = self._new_one_connection() size = "254" + if ip_profile is None: subnet_rand = random.randint(0, 255) ip_start = "192.168.{}.1".format(subnet_rand) else: index = ip_profile["subnet_address"].find("/") ip_start = ip_profile["subnet_address"][:index] + if "dhcp_count" in ip_profile and ip_profile["dhcp_count"] is not None: size = str(ip_profile["dhcp_count"]) - elif "dhcp_count" not in ip_profile and ip_profile["ip_version"] == "IPv4": - prefix = ip_profile["subnet_address"][index + 1:] + elif ( + "dhcp_count" not in ip_profile + and ip_profile["ip_version"] == "IPv4" + ): + prefix = ip_profile["subnet_address"][index + 1 :] size = int(math.pow(2, 32 - prefix)) - if "dhcp_start_address" in ip_profile and ip_profile["dhcp_start_address"] is not None: + + if ( + "dhcp_start_address" in ip_profile + and ip_profile["dhcp_start_address"] is not None + ): ip_start = str(ip_profile["dhcp_start_address"]) - if ip_profile["ip_version"] == "IPv6": - ip_prefix_type = "GLOBAL_PREFIX" + # if ip_profile["ip_version"] == "IPv6": + # ip_prefix_type = "GLOBAL_PREFIX" if vlan is not None: vlan_id = vlan else: vlan_id = str(random.randint(100, 4095)) - #if "internal" in net_name: + + # if "internal" in net_name: # OpenNebula not support two networks with same name random_net_name = str(random.randint(1, 1000000)) net_name = net_name + random_net_name - net_id = one.vn.allocate({ - 'NAME': net_name, - 'VN_MAD': '802.1Q', - 'PHYDEV': self.config["network"]["phydev"], - 'VLAN_ID': vlan_id - }, self.config["cluster"]["id"]) - arpool = {'AR_POOL': { - 'AR': { - 'TYPE': 'IP4', - 'IP': ip_start, - 'SIZE': size - } - } - } + net_id = one.vn.allocate( + { + "NAME": net_name, + "VN_MAD": "802.1Q", + "PHYDEV": self.config["network"]["phydev"], + "VLAN_ID": vlan_id, + }, + self.config["cluster"]["id"], + ) + arpool = {"AR_POOL": {"AR": {"TYPE": "IP4", "IP": ip_start, "SIZE": size}}} one.vn.add_ar(net_id, arpool) + return net_id, created_items except Exception as e: self.logger.error("Create new network error: " + str(e)) - raise vimconn.vimconnException(e) + + raise vimconn.VimConnException(e) def get_network_list(self, filter_dict={}): """Obtain tenant networks of VIM - Params: - 'filter_dict' (optional) contains entries to return only networks that matches ALL entries: - name: string => returns only networks with this name - id: string => returns networks with this VIM id, this imply returns one network at most - shared: boolean >= returns only networks that are (or are not) shared - tenant_id: sting => returns only networks that belong to this tenant/project - ,#(not used yet) admin_state_up: boolean => returns only networks that are (or are not) in admin state active - #(not used yet) status: 'ACTIVE','ERROR',... => filter networks that are on this status + :params filter_dict: (optional) contains entries to return only networks that matches ALL entries: + name: string => returns only networks with this name + id: string => returns networks with this VIM id, this imply returns one network at most + shared: boolean >= returns only networks that are (or are not) shared + tenant_id: sting => returns only networks that belong to this tenant/project + (not used yet) admin_state_up: boolean => returns only networks that are (or are not) in admin state active + (not used yet) status: 'ACTIVE','ERROR',... => filter networks that are on this status Returns the network list of dictionaries. each dictionary contains: 'id': (mandatory) VIM network id 'name': (mandatory) VIM network name @@ -249,27 +312,37 @@ class vimconnector(vimconn.vimconnector): List can be empty if no network map the filter_dict. Raise an exception only upon VIM connectivity, authorization, or some other unspecific error """ - try: one = self._new_one_connection() net_pool = one.vnpool.info(-2, -1, -1).VNET response = [] + if "name" in filter_dict: network_name_filter = filter_dict["name"] else: network_name_filter = None + if "id" in filter_dict: network_id_filter = filter_dict["id"] else: network_id_filter = None + for network in net_pool: - if network.NAME == network_name_filter or str(network.ID) == str(network_id_filter): - net_dict = {"name": network.NAME, "id": str(network.ID), "status": "ACTIVE"} + if network.NAME == network_name_filter or str(network.ID) == str( + network_id_filter + ): + net_dict = { + "name": network.NAME, + "id": str(network.ID), + "status": "ACTIVE", + } response.append(net_dict) + return response except Exception as e: self.logger.error("Get network list error: " + str(e)) - raise vimconn.vimconnException(e) + + raise vimconn.VimConnException(e) def get_network(self, net_id): """Obtain network details from the 'net_id' VIM network @@ -285,19 +358,24 @@ class vimconnector(vimconn.vimconnector): one = self._new_one_connection() net_pool = one.vnpool.info(-2, -1, -1).VNET net = {} + for network in net_pool: if str(network.ID) == str(net_id): - net['id'] = network.ID - net['name'] = network.NAME - net['status'] = "ACTIVE" + net["id"] = network.ID + net["name"] = network.NAME + net["status"] = "ACTIVE" break + if net: return net else: - raise vimconn.vimconnNotFoundException("Network {} not found".format(net_id)) + raise vimconn.VimConnNotFoundException( + "Network {} not found".format(net_id) + ) except Exception as e: self.logger.error("Get network " + str(net_id) + " error): " + str(e)) - raise vimconn.vimconnException(e) + + raise vimconn.VimConnException(e) def delete_network(self, net_id, created_items=None): """ @@ -307,13 +385,16 @@ class vimconnector(vimconn.vimconnector): Returns the network identifier or raises an exception upon error or when network is not found """ try: - one = self._new_one_connection() one.vn.delete(int(net_id)) + return net_id except Exception as e: - self.logger.error("Delete network " + str(net_id) + "error: network not found" + str(e)) - raise vimconn.vimconnException(e) + self.logger.error( + "Delete network " + str(net_id) + "error: network not found" + str(e) + ) + + raise vimconn.VimConnException(e) def refresh_nets_status(self, net_list): """Get the status of the networks @@ -336,25 +417,30 @@ class vimconnector(vimconn.vimconnector): try: for net_id in net_list: net = {} + try: net_vim = self.get_network(net_id) net["status"] = net_vim["status"] net["vim_info"] = None - except vimconn.vimconnNotFoundException as e: + except vimconn.VimConnNotFoundException as e: self.logger.error("Exception getting net status: {}".format(str(e))) - net['status'] = "DELETED" - net['error_msg'] = str(e) - except vimconn.vimconnException as e: + net["status"] = "DELETED" + net["error_msg"] = str(e) + except vimconn.VimConnException as e: self.logger.error(e) net["status"] = "VIM_ERROR" net["error_msg"] = str(e) + net_dict[net_id] = net + return net_dict - except vimconn.vimconnException as e: + except vimconn.VimConnException as e: self.logger.error(e) + for k in net_dict: net_dict[k]["status"] = "VIM_ERROR" net_dict[k]["error_msg"] = str(e) + return net_dict def get_flavor(self, flavor_id): # Esta correcto @@ -363,15 +449,19 @@ class vimconnector(vimconn.vimconnector): Raises an exception upon error or if not found """ try: - one = self._new_one_connection() template = one.template.info(int(flavor_id)) + if template is not None: - return {'id': template.ID, 'name': template.NAME} - raise vimconn.vimconnNotFoundException("Flavor {} not found".format(flavor_id)) + return {"id": template.ID, "name": template.NAME} + + raise vimconn.VimConnNotFoundException( + "Flavor {} not found".format(flavor_id) + ) except Exception as e: self.logger.error("get flavor " + str(flavor_id) + " error: " + str(e)) - raise vimconn.vimconnException(e) + + raise vimconn.VimConnException(e) def new_flavor(self, flavor_data): """Adds a tenant flavor to VIM @@ -381,9 +471,9 @@ class vimconnector(vimconn.vimconnector): vpcus: cpus (cloud type) extended: EPA parameters - numas: #items requested in same NUMA - memory: number of 1G huge pages memory - paired-threads|cores|threads: number of paired hyperthreads, complete cores OR individual threads - interfaces: # passthrough(PT) or SRIOV interfaces attached to this numa + memory: number of 1G huge pages memory + paired-threads|cores|threads: number of paired hyperthreads, complete cores OR individual threads + interfaces: # passthrough(PT) or SRIOV interfaces attached to this numa - name: interface name dedicated: yes|no|yes:sriov; for PT, SRIOV or only one SRIOV for the physical NIC bandwidth: X Gbps; requested guarantee bandwidth @@ -392,48 +482,49 @@ class vimconnector(vimconn.vimconnector): is_public: #TODO to concrete Returns the flavor identifier""" - - disk_size = str(int(flavor_data["disk"])*1024) + disk_size = str(int(flavor_data["disk"]) * 1024) try: one = self._new_one_connection() - template_id = one.template.allocate({ - 'TEMPLATE': { - 'NAME': flavor_data["name"], - 'CPU': flavor_data["vcpus"], - 'VCPU': flavor_data["vcpus"], - 'MEMORY': flavor_data["ram"], - 'DISK': { - 'SIZE': disk_size - }, - 'CONTEXT': { - 'NETWORK': "YES", - 'SSH_PUBLIC_KEY': '$USER[SSH_PUBLIC_KEY]' - }, - 'GRAPHICS': { - 'LISTEN': '0.0.0.0', - 'TYPE': 'VNC' - }, - 'CLUSTER_ID': self.config["cluster"]["id"] + template_id = one.template.allocate( + { + "TEMPLATE": { + "NAME": flavor_data["name"], + "CPU": flavor_data["vcpus"], + "VCPU": flavor_data["vcpus"], + "MEMORY": flavor_data["ram"], + "DISK": {"SIZE": disk_size}, + "CONTEXT": { + "NETWORK": "YES", + "SSH_PUBLIC_KEY": "$USER[SSH_PUBLIC_KEY]", + }, + "GRAPHICS": {"LISTEN": "0.0.0.0", "TYPE": "VNC"}, + "CLUSTER_ID": self.config["cluster"]["id"], + } } - }) - return template_id + ) + return template_id except Exception as e: self.logger.error("Create new flavor error: " + str(e)) - raise vimconn.vimconnException(e) + + raise vimconn.VimConnException(e) def delete_flavor(self, flavor_id): - """ Deletes a tenant flavor from VIM - Returns the old flavor_id + """Deletes a tenant flavor from VIM + Returns the old flavor_id """ try: one = self._new_one_connection() one.template.delete(int(flavor_id), False) + return flavor_id except Exception as e: - self.logger.error("Error deleting flavor " + str(flavor_id) + ". Flavor not found") - raise vimconn.vimconnException(e) + self.logger.error( + "Error deleting flavor " + str(flavor_id) + ". Flavor not found" + ) + + raise vimconn.VimConnException(e) def get_image_list(self, filter_dict={}): """Obtain tenant images from VIM @@ -450,81 +541,107 @@ class vimconnector(vimconn.vimconnector): one = self._new_one_connection() image_pool = one.imagepool.info(-2, -1, -1).IMAGE images = [] + if "name" in filter_dict: image_name_filter = filter_dict["name"] else: image_name_filter = None + if "id" in filter_dict: image_id_filter = filter_dict["id"] else: image_id_filter = None + for image in image_pool: - if str(image_name_filter) == str(image.NAME) or str(image.ID) == str(image_id_filter): + if str(image_name_filter) == str(image.NAME) or str(image.ID) == str( + image_id_filter + ): images_dict = {"name": image.NAME, "id": str(image.ID)} images.append(images_dict) + return images except Exception as e: self.logger.error("Get image list error: " + str(e)) - raise vimconn.vimconnException(e) - - def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None, disk_list=None, - availability_zone_index=None, availability_zone_list=None): - - """Adds a VM instance to VIM - Params: - 'start': (boolean) indicates if VM must start or created in pause mode. - 'image_id','flavor_id': image and flavor VIM id to use for the VM - 'net_list': list of interfaces, each one is a dictionary with: - 'name': (optional) name for the interface. - 'net_id': VIM network id where this interface must be connect to. Mandatory for type==virtual - 'vpci': (optional) virtual vPCI address to assign at the VM. Can be ignored depending on VIM capabilities - 'model': (optional and only have sense for type==virtual) interface model: virtio, e1000, ... - 'mac_address': (optional) mac address to assign to this interface - 'ip_address': (optional) IP address to assign to this interface - #TODO: CHECK if an optional 'vlan' parameter is needed for VIMs when type if VF and net_id is not provided, - the VLAN tag to be used. In case net_id is provided, the internal network vlan is used for tagging VF - 'type': (mandatory) can be one of: - 'virtual', in this case always connected to a network of type 'net_type=bridge' - 'PCI-PASSTHROUGH' or 'PF' (passthrough): depending on VIM capabilities it can be connected to a data/ptp network ot it - can created unconnected - 'SR-IOV' or 'VF' (SRIOV with VLAN tag): same as PF for network connectivity. - 'VFnotShared'(SRIOV without VLAN tag) same as PF for network connectivity. VF where no other VFs - are allocated on the same physical NIC - 'bw': (optional) only for PF/VF/VFnotShared. Minimal Bandwidth required for the interface in GBPS - 'port_security': (optional) If False it must avoid any traffic filtering at this interface. If missing - or True, it must apply the default VIM behaviour - After execution the method will add the key: - 'vim_id': must be filled/added by this method with the VIM identifier generated by the VIM for this - interface. 'net_list' is modified - 'cloud_config': (optional) dictionary with: - 'key-pairs': (optional) list of strings with the public key to be inserted to the default user - 'users': (optional) list of users to be inserted, each item is a dict with: - 'name': (mandatory) user name, - 'key-pairs': (optional) list of strings with the public key to be inserted to the user - 'user-data': (optional) can be a string with the text script to be passed directly to cloud-init, - or a list of strings, each one contains a script to be passed, usually with a MIMEmultipart file - 'config-files': (optional). List of files to be transferred. Each item is a dict with: - 'dest': (mandatory) string with the destination absolute path - 'encoding': (optional, by default text). Can be one of: - 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64' - 'content' (mandatory): string with the content of the file - 'permissions': (optional) string with file permissions, typically octal notation '0644' - 'owner': (optional) file owner, string with the format 'owner:group' - 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk) - 'disk_list': (optional) list with additional disks to the VM. Each item is a dict with: - 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted - 'size': (mandatory) string with the size of the disk in GB - availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required - availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if + raise vimconn.VimConnException(e) + + def new_vminstance( + self, + name, + description, + start, + image_id, + flavor_id, + net_list, + cloud_config=None, + disk_list=None, + availability_zone_index=None, + availability_zone_list=None, + ): + """ + Adds a VM instance to VIM + :param name: + :param description: + :param start: (boolean) indicates if VM must start or created in pause mode. + :param image_id: image VIM id to use for the VM + :param flavor_id: flavor VIM id to use for the VM + :param net_list: list of interfaces, each one is a dictionary with: + 'name': (optional) name for the interface. + 'net_id': VIM network id where this interface must be connect to. Mandatory for type==virtual + 'vpci': (optional) virtual vPCI address to assign at the VM. Can be ignored depending on VIM + capabilities + 'model': (optional and only have sense for type==virtual) interface model: virtio, e1000, ... + 'mac_address': (optional) mac address to assign to this interface + 'ip_address': (optional) IP address to assign to this interface + #TODO: CHECK if an optional 'vlan' parameter is needed for VIMs when type if VF and net_id is not + provided, the VLAN tag to be used. In case net_id is provided, the internal network vlan is + used for tagging VF + 'type': (mandatory) can be one of: + 'virtual', in this case always connected to a network of type 'net_type=bridge' + 'PCI-PASSTHROUGH' or 'PF' (passthrough): depending on VIM capabilities it can be connected to + a data/ptp network ot itcan created unconnected + 'SR-IOV' or 'VF' (SRIOV with VLAN tag): same as PF for network connectivity. + 'VFnotShared'(SRIOV without VLAN tag) same as PF for network connectivity. VF where no other VFs + are allocated on the same physical NIC + 'bw': (optional) only for PF/VF/VFnotShared. Minimal Bandwidth required for the interface in GBPS + 'port_security': (optional) If False it must avoid any traffic filtering at this interface. If missing + or True, it must apply the default VIM behaviour + After execution the method will add the key: + 'vim_id': must be filled/added by this method with the VIM identifier generated by the VIM for this + interface. 'net_list' is modified + :param cloud_config: (optional) dictionary with: + 'key-pairs': (optional) list of strings with the public key to be inserted to the default user + 'users': (optional) list of users to be inserted, each item is a dict with: + 'name': (mandatory) user name, + 'key-pairs': (optional) list of strings with the public key to be inserted to the user + 'user-data': (optional) can be a string with the text script to be passed directly to cloud-init, + or a list of strings, each one contains a script to be passed, usually with a MIMEmultipart file + 'config-files': (optional). List of files to be transferred. Each item is a dict with: + 'dest': (mandatory) string with the destination absolute path + 'encoding': (optional, by default text). Can be one of: + 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64' + 'content' (mandatory): string with the content of the file + 'permissions': (optional) string with file permissions, typically octal notation '0644' + 'owner': (optional) file owner, string with the format 'owner:group' + 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk) + :param disk_list: (optional) list with additional disks to the VM. Each item is a dict with: + 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted + 'size': (mandatory) string with the size of the disk in GB + :param availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV + required + :param availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if availability_zone_index is None - Returns a tuple with the instance identifier and created_items or raises an exception on error - created_items can be None or a dictionary where this method can include key-values that will be passed to - the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc. - Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same - as not present. - """ + :return: a tuple with the instance identifier and created_items or raises an exception on error + created_items can be None or a dictionary where this method can include key-values that will be passed to + the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc. + Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same + as not present. + """ self.logger.debug( - "new_vminstance input: image='{}' flavor='{}' nics='{}'".format(image_id, flavor_id, str(net_list))) + "new_vminstance input: image='{}' flavor='{}' nics='{}'".format( + image_id, flavor_id, str(net_list) + ) + ) + try: one = self._new_one_connection() template_vim = one.template.info(int(flavor_id), True) @@ -532,45 +649,63 @@ class vimconnector(vimconn.vimconnector): one = self._new_one_connection() template_updated = "" + for net in net_list: net_in_vim = one.vn.info(int(net["net_id"])) net["vim_id"] = str(net_in_vim.ID) network = 'NIC = [NETWORK = "{}",NETWORK_UNAME = "{}" ]'.format( - net_in_vim.NAME, net_in_vim.UNAME) + net_in_vim.NAME, net_in_vim.UNAME + ) template_updated += network - template_updated += "DISK = [ IMAGE_ID = {},\n SIZE = {}]".format(image_id, disk_size) + template_updated += "DISK = [ IMAGE_ID = {},\n SIZE = {}]".format( + image_id, disk_size + ) if isinstance(cloud_config, dict): if cloud_config.get("key-pairs"): context = 'CONTEXT = [\n NETWORK = "YES",\n SSH_PUBLIC_KEY = "' + for key in cloud_config["key-pairs"]: - context += key + '\n' + context += key + "\n" + # if False: # context += '"\n USERNAME = ' context += '"]' template_updated += context - vm_instance_id = one.template.instantiate(int(flavor_id), name, False, template_updated) + vm_instance_id = one.template.instantiate( + int(flavor_id), name, False, template_updated + ) self.logger.info( - "Instanciating in OpenNebula a new VM name:{} id:{}".format(name, flavor_id)) + "Instanciating in OpenNebula a new VM name:{} id:{}".format( + name, flavor_id + ) + ) + return str(vm_instance_id), None except pyone.OneNoExistsException as e: self.logger.error("Network with id " + str(e) + " not found: " + str(e)) - raise vimconn.vimconnNotFoundException(e) + + raise vimconn.VimConnNotFoundException(e) except Exception as e: self.logger.error("Create new vm instance error: " + str(e)) - raise vimconn.vimconnException(e) + + raise vimconn.VimConnException(e) def get_vminstance(self, vm_id): """Returns the VM instance information from VIM""" try: one = self._new_one_connection() vm = one.vm.info(int(vm_id)) + return vm except Exception as e: - self.logger.error("Getting vm instance error: " + str(e) + ": VM Instance not found") - raise vimconn.vimconnException(e) + self.logger.error( + "Getting vm instance error: " + str(e) + ": VM Instance not found" + ) + + raise vimconn.VimConnException(e) def delete_vminstance(self, vm_id, created_items=None): """ @@ -584,76 +719,87 @@ class vimconnector(vimconn.vimconnector): one = self._new_one_connection() one.vm.recover(int(vm_id), 3) vm = None + while True: if vm is not None and vm.LCM_STATE == 0: break else: vm = one.vm.info(int(vm_id)) + except pyone.OneNoExistsException: + self.logger.info( + "The vm " + str(vm_id) + " does not exist or is already deleted" + ) - except pyone.OneNoExistsException as e: - self.logger.info("The vm " + str(vm_id) + " does not exist or is already deleted") - raise vimconn.vimconnNotFoundException("The vm {} does not exist or is already deleted".format(vm_id)) + raise vimconn.VimConnNotFoundException( + "The vm {} does not exist or is already deleted".format(vm_id) + ) except Exception as e: self.logger.error("Delete vm instance " + str(vm_id) + " error: " + str(e)) - raise vimconn.vimconnException(e) + raise vimconn.VimConnException(e) def refresh_vms_status(self, vm_list): """Get the status of the virtual machines and their interfaces/ports - Params: the list of VM identifiers - Returns a dictionary with: - vm_id: #VIM id of this Virtual Machine - status: #Mandatory. Text with one of: - # DELETED (not found at vim) - # VIM_ERROR (Cannot connect to VIM, VIM response error, ...) - # OTHER (Vim reported other status not understood) - # ERROR (VIM indicates an ERROR status) - # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running), - # BUILD (on building process), ERROR - # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address - # - error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR - vim_info: #Text with plain information obtained from vim (yaml.safe_dump) - interfaces: list with interface info. Each item a dictionary with: - vim_info: #Text with plain information obtained from vim (yaml.safe_dump) - mac_address: #Text format XX:XX:XX:XX:XX:XX - vim_net_id: #network id where this interface is connected, if provided at creation - vim_interface_id: #interface/port VIM id - ip_address: #null, or text with IPv4, IPv6 address - compute_node: #identification of compute node where PF,VF interface is allocated - pci: #PCI address of the NIC that hosts the PF,VF - vlan: #physical VLAN used for VF + Params: the list of VM identifiers + Returns a dictionary with: + vm_id: #VIM id of this Virtual Machine + status: #Mandatory. Text with one of: + # DELETED (not found at vim) + # VIM_ERROR (Cannot connect to VIM, VIM response error, ...) + # OTHER (Vim reported other status not understood) + # ERROR (VIM indicates an ERROR status) + # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running), + # BUILD (on building process), ERROR + # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address + # + error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR + vim_info: #Text with plain information obtained from vim (yaml.safe_dump) + interfaces: list with interface info. Each item a dictionary with: + vim_info: #Text with plain information obtained from vim (yaml.safe_dump) + mac_address: #Text format XX:XX:XX:XX:XX:XX + vim_net_id: #network id where this interface is connected, if provided at creation + vim_interface_id: #interface/port VIM id + ip_address: #null, or text with IPv4, IPv6 address + compute_node: #identification of compute node where PF,VF interface is allocated + pci: #PCI address of the NIC that hosts the PF,VF + vlan: #physical VLAN used for VF """ vm_dict = {} try: for vm_id in vm_list: vm = {} + if self.get_vminstance(vm_id) is not None: vm_element = self.get_vminstance(vm_id) else: self.logger.info("The vm " + str(vm_id) + " does not exist.") - vm['status'] = "DELETED" - vm['error_msg'] = ("The vm " + str(vm_id) + " does not exist.") + vm["status"] = "DELETED" + vm["error_msg"] = "The vm " + str(vm_id) + " does not exist." continue + vm["vim_info"] = None vm_status = vm_element.LCM_STATE + if vm_status == 3: - vm['status'] = "ACTIVE" + vm["status"] = "ACTIVE" elif vm_status == 36: - vm['status'] = "ERROR" - vm['error_msg'] = "VM failure" + vm["status"] = "ERROR" + vm["error_msg"] = "VM failure" else: - vm['status'] = "BUILD" + vm["status"] = "BUILD" if vm_element is not None: interfaces = self._get_networks_vm(vm_element) vm["interfaces"] = interfaces + vm_dict[vm_id] = vm + return vm_dict except Exception as e: self.logger.error(e) for k in vm_dict: vm_dict[k]["status"] = "VIM_ERROR" vm_dict[k]["error_msg"] = str(e) + return vm_dict def _get_networks_vm(self, vm_element): @@ -661,24 +807,40 @@ class vimconnector(vimconn.vimconnector): try: if isinstance(vm_element.TEMPLATE["NIC"], list): for net in vm_element.TEMPLATE["NIC"]: - interface = {'vim_info': None, "mac_address": str(net["MAC"]), "vim_net_id": str(net["NETWORK_ID"]), - "vim_interface_id": str(net["NETWORK_ID"])} + interface = { + "vim_info": None, + "mac_address": str(net["MAC"]), + "vim_net_id": str(net["NETWORK_ID"]), + "vim_interface_id": str(net["NETWORK_ID"]), + } + # maybe it should be 2 different keys for ip_address if an interface has ipv4 and ipv6 - if u'IP' in net: + if "IP" in net: interface["ip_address"] = str(net["IP"]) - if u'IP6_GLOBAL' in net: + + if "IP6_GLOBAL" in net: interface["ip_address"] = str(net["IP6_GLOBAL"]) + interfaces.append(interface) else: net = vm_element.TEMPLATE["NIC"] - interface = {'vim_info': None, "mac_address": str(net["MAC"]), "vim_net_id": str(net["NETWORK_ID"]), - "vim_interface_id": str(net["NETWORK_ID"])} + interface = { + "vim_info": None, + "mac_address": str(net["MAC"]), + "vim_net_id": str(net["NETWORK_ID"]), + "vim_interface_id": str(net["NETWORK_ID"]), + } + # maybe it should be 2 different keys for ip_address if an interface has ipv4 and ipv6 - if u'IP' in net: + if "IP" in net: interface["ip_address"] = str(net["IP"]) - if u'IP6_GLOBAL' in net: + + if "IP6_GLOBAL" in net: interface["ip_address"] = str(net["IP6_GLOBAL"]) + interfaces.append(interface) return interfaces - except Exception as e: - self.logger.error("Error getting vm interface_information of vm_id: " + str(vm_element.ID)) + except Exception: + self.logger.error( + "Error getting vm interface_information of vm_id: " + str(vm_element.ID) + )