+ except Exception as e2:
+ self.logger.error("new_vminstance rollback fail {}".format(e2))
+
+ self._format_exception(e)
+
+ @staticmethod
+ def remove_keep_tag_from_persistent_volumes(created_items: Dict) -> Dict:
+ """Removes the keep flag from persistent volumes. So, those volumes could be removed.
+
+ Args:
+ created_items (dict): All created items belongs to VM
+
+ Returns:
+ updated_created_items (dict): Dict which does not include keep flag for volumes.
+
+ """
+ return {
+ key.replace(":keep", ""): value for (key, value) in created_items.items()
+ }
+
+ def get_vminstance(self, vm_id):
+ """Returns the VM instance information from VIM"""
+ # self.logger.debug("Getting VM from VIM")
+ try:
+ self._reload_connection()
+ server = self.nova.servers.find(id=vm_id)
+ # TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
+
+ return server.to_dict()
+ except (
+ ksExceptions.ClientException,
+ nvExceptions.ClientException,
+ nvExceptions.NotFound,
+ ConnectionError,
+ ) as e:
+ self._format_exception(e)
+
+ def get_vminstance_console(self, vm_id, console_type="vnc"):
+ """
+ Get a console for the virtual machine
+ Params:
+ vm_id: uuid of the VM
+ console_type, can be:
+ "novnc" (by default), "xvpvnc" for VNC types,
+ "rdp-html5" for RDP types, "spice-html5" for SPICE types
+ Returns dict with the console parameters:
+ protocol: ssh, ftp, http, https, ...
+ server: usually ip address
+ port: the http, ssh, ... port
+ suffix: extra text, e.g. the http path and query string
+ """
+ self.logger.debug("Getting VM CONSOLE from VIM")
+
+ try:
+ self._reload_connection()
+ server = self.nova.servers.find(id=vm_id)
+
+ if console_type is None or console_type == "novnc":
+ console_dict = server.get_vnc_console("novnc")
+ elif console_type == "xvpvnc":
+ console_dict = server.get_vnc_console(console_type)
+ elif console_type == "rdp-html5":
+ console_dict = server.get_rdp_console(console_type)
+ elif console_type == "spice-html5":
+ console_dict = server.get_spice_console(console_type)
+ else:
+ raise vimconn.VimConnException(
+ "console type '{}' not allowed".format(console_type),
+ http_code=vimconn.HTTP_Bad_Request,
+ )
+
+ console_dict1 = console_dict.get("console")
+
+ if console_dict1:
+ console_url = console_dict1.get("url")
+
+ if console_url:
+ # parse console_url
+ protocol_index = console_url.find("//")
+ suffix_index = (
+ console_url[protocol_index + 2 :].find("/") + protocol_index + 2
+ )
+ port_index = (
+ console_url[protocol_index + 2 : suffix_index].find(":")
+ + protocol_index
+ + 2
+ )
+
+ if protocol_index < 0 or port_index < 0 or suffix_index < 0:
+ return (
+ -vimconn.HTTP_Internal_Server_Error,
+ "Unexpected response from VIM",
+ )
+
+ console_dict = {
+ "protocol": console_url[0:protocol_index],
+ "server": console_url[protocol_index + 2 : port_index],
+ "port": console_url[port_index:suffix_index],
+ "suffix": console_url[suffix_index + 1 :],
+ }
+ protocol_index += 2
+
+ return console_dict
+ raise vimconn.VimConnUnexpectedResponse("Unexpected response from VIM")
+ except (
+ nvExceptions.NotFound,
+ ksExceptions.ClientException,
+ nvExceptions.ClientException,
+ nvExceptions.BadRequest,
+ ConnectionError,
+ ) as e:
+ self._format_exception(e)
+
+ def _delete_ports_by_id_wth_neutron(self, k_id: str) -> None:
+ """Neutron delete ports by id.
+ Args:
+ k_id (str): Port id in the VIM
+ """
+ try:
+ port_dict = self.neutron.list_ports()
+ existing_ports = [port["id"] for port in port_dict["ports"] if port_dict]
+
+ if k_id in existing_ports:
+ self.neutron.delete_port(k_id)
+
+ except Exception as e:
+ self.logger.error("Error deleting port: {}: {}".format(type(e).__name__, e))
+
+ def _delete_volumes_by_id_wth_cinder(
+ self, k: str, k_id: str, volumes_to_hold: list, created_items: dict
+ ) -> bool:
+ """Cinder delete volume by id.
+ Args:
+ k (str): Full item name in created_items
+ k_id (str): ID of floating ip in VIM
+ volumes_to_hold (list): Volumes not to delete
+ created_items (dict): All created items belongs to VM
+ """
+ try:
+ if k_id in volumes_to_hold:
+ return
+
+ if self.cinder.volumes.get(k_id).status != "available":
+ return True
+
+ else:
+ self.cinder.volumes.delete(k_id)
+ created_items[k] = None
+
+ except Exception as e:
+ self.logger.error(
+ "Error deleting volume: {}: {}".format(type(e).__name__, e)
+ )
+
+ def _delete_floating_ip_by_id(self, k: str, k_id: str, created_items: dict) -> None:
+ """Neutron delete floating ip by id.
+ Args:
+ k (str): Full item name in created_items
+ k_id (str): ID of floating ip in VIM
+ created_items (dict): All created items belongs to VM
+ """
+ try:
+ self.neutron.delete_floatingip(k_id)
+ created_items[k] = None
+
+ except Exception as e:
+ self.logger.error(
+ "Error deleting floating ip: {}: {}".format(type(e).__name__, e)
+ )
+
+ @staticmethod
+ def _get_item_name_id(k: str) -> Tuple[str, str]:
+ k_item, _, k_id = k.partition(":")
+ return k_item, k_id
+
+ def _delete_vm_ports_attached_to_network(self, created_items: dict) -> None:
+ """Delete VM ports attached to the networks before deleting virtual machine.
+ Args:
+ created_items (dict): All created items belongs to VM
+ """
+
+ for k, v in created_items.items():
+ if not v: # skip already deleted
+ continue
+
+ try:
+ k_item, k_id = self._get_item_name_id(k)
+ if k_item == "port":
+ self._delete_ports_by_id_wth_neutron(k_id)
+
+ except Exception as e:
+ self.logger.error(
+ "Error deleting port: {}: {}".format(type(e).__name__, e)
+ )
+
+ def _delete_created_items(
+ self, created_items: dict, volumes_to_hold: list, keep_waiting: bool
+ ) -> bool:
+ """Delete Volumes and floating ip if they exist in created_items."""
+ for k, v in created_items.items():
+ if not v: # skip already deleted
+ continue
+
+ try:
+ k_item, k_id = self._get_item_name_id(k)
+
+ if k_item == "volume":
+ unavailable_vol = self._delete_volumes_by_id_wth_cinder(
+ k, k_id, volumes_to_hold, created_items
+ )
+
+ if unavailable_vol:
+ keep_waiting = True
+
+ elif k_item == "floating_ip":
+ self._delete_floating_ip_by_id(k, k_id, created_items)
+
+ except Exception as e:
+ self.logger.error("Error deleting {}: {}".format(k, e))
+
+ return keep_waiting
+
+ @staticmethod
+ def _extract_items_wth_keep_flag_from_created_items(created_items: dict) -> dict:
+ """Remove the volumes which has key flag from created_items
+
+ Args:
+ created_items (dict): All created items belongs to VM
+
+ Returns:
+ created_items (dict): Persistent volumes eliminated created_items
+ """
+ return {
+ key: value
+ for (key, value) in created_items.items()
+ if len(key.split(":")) == 2
+ }
+
+ def delete_vminstance(
+ self, vm_id: str, created_items: dict = None, volumes_to_hold: list = None
+ ) -> None:
+ """Removes a VM instance from VIM. Returns the old identifier.
+ Args:
+ vm_id (str): Identifier of VM instance
+ created_items (dict): All created items belongs to VM
+ volumes_to_hold (list): Volumes_to_hold
+ """
+ if created_items is None:
+ created_items = {}
+ if volumes_to_hold is None:
+ volumes_to_hold = []
+
+ try:
+ created_items = self._extract_items_wth_keep_flag_from_created_items(
+ created_items
+ )
+
+ self._reload_connection()
+
+ # Delete VM ports attached to the networks before the virtual machine
+ if created_items:
+ self._delete_vm_ports_attached_to_network(created_items)
+
+ if vm_id:
+ self.nova.servers.delete(vm_id)
+
+ # Although having detached, volumes should have in active status before deleting.
+ # We ensure in this loop
+ keep_waiting = True
+ elapsed_time = 0
+
+ while keep_waiting and elapsed_time < volume_timeout:
+ keep_waiting = False
+
+ # Delete volumes and floating IP.
+ keep_waiting = self._delete_created_items(
+ created_items, volumes_to_hold, keep_waiting
+ )
+
+ if keep_waiting:
+ time.sleep(1)
+ elapsed_time += 1
+
+ except (
+ nvExceptions.NotFound,
+ ksExceptions.ClientException,
+ nvExceptions.ClientException,
+ ConnectionError,
+ ) as e:
+ self._format_exception(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),
+ # CREATING (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:
+ - 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
+ 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 = {}
+ self.logger.debug(
+ "refresh_vms status: Getting tenant VM instance information from VIM"
+ )
+
+ for vm_id in vm_list:
+ vm = {}
+
+ try:
+ vm_vim = self.get_vminstance(vm_id)
+
+ if vm_vim["status"] in vmStatus2manoFormat:
+ vm["status"] = vmStatus2manoFormat[vm_vim["status"]]
+ else:
+ vm["status"] = "OTHER"
+ vm["error_msg"] = "VIM status reported " + vm_vim["status"]
+
+ vm_vim.pop("OS-EXT-SRV-ATTR:user_data", None)
+ vm_vim.pop("user_data", None)
+ vm["vim_info"] = self.serialize(vm_vim)
+
+ vm["interfaces"] = []
+ if vm_vim.get("fault"):
+ vm["error_msg"] = str(vm_vim["fault"])
+
+ # get interfaces
+ try:
+ self._reload_connection()
+ port_dict = self.neutron.list_ports(device_id=vm_id)
+
+ for port in port_dict["ports"]:
+ interface = {}
+ interface["vim_info"] = self.serialize(port)
+ interface["mac_address"] = port.get("mac_address")
+ interface["vim_net_id"] = port["network_id"]
+ interface["vim_interface_id"] = port["id"]
+ # check if OS-EXT-SRV-ATTR:host is there,
+ # in case of non-admin credentials, it will be missing
+
+ if vm_vim.get("OS-EXT-SRV-ATTR:host"):
+ interface["compute_node"] = vm_vim["OS-EXT-SRV-ATTR:host"]
+
+ interface["pci"] = None
+
+ # check if binding:profile is there,
+ # in case of non-admin credentials, it will be missing
+ if port.get("binding:profile"):
+ if port["binding:profile"].get("pci_slot"):
+ # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting
+ # the slot to 0x00
+ # TODO: This is just a workaround valid for niantinc. Find a better way to do so
+ # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
+ pci = port["binding:profile"]["pci_slot"]
+ # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
+ interface["pci"] = pci
+
+ interface["vlan"] = None
+
+ if port.get("binding:vif_details"):
+ interface["vlan"] = port["binding:vif_details"].get("vlan")
+
+ # Get vlan from network in case not present in port for those old openstacks and cases where
+ # it is needed vlan at PT
+ if not interface["vlan"]:
+ # if network is of type vlan and port is of type direct (sr-iov) then set vlan id
+ network = self.neutron.show_network(port["network_id"])
+
+ if (
+ network["network"].get("provider:network_type")
+ == "vlan"
+ ):
+ # and port.get("binding:vnic_type") in ("direct", "direct-physical"):
+ interface["vlan"] = network["network"].get(
+ "provider:segmentation_id"
+ )
+
+ ips = []
+ # look for floating ip address
+ try:
+ floating_ip_dict = self.neutron.list_floatingips(
+ port_id=port["id"]
+ )
+
+ if floating_ip_dict.get("floatingips"):
+ ips.append(
+ floating_ip_dict["floatingips"][0].get(
+ "floating_ip_address"
+ )
+ )
+ except Exception:
+ pass
+
+ for subnet in port["fixed_ips"]:
+ ips.append(subnet["ip_address"])
+
+ interface["ip_address"] = ";".join(ips)
+ vm["interfaces"].append(interface)
+ except Exception as e:
+ self.logger.error(
+ "Error getting vm interface information {}: {}".format(
+ type(e).__name__, e
+ ),
+ exc_info=True,
+ )
+ except vimconn.VimConnNotFoundException as e:
+ self.logger.error("Exception getting vm status: %s", str(e))
+ vm["status"] = "DELETED"
+ vm["error_msg"] = str(e)
+ except vimconn.VimConnException as e:
+ self.logger.error("Exception getting vm status: %s", str(e))
+ vm["status"] = "VIM_ERROR"
+ vm["error_msg"] = str(e)
+
+ vm_dict[vm_id] = vm
+
+ return vm_dict
+
+ def action_vminstance(self, vm_id, action_dict, created_items={}):
+ """Send and action over a VM instance from VIM
+ Returns None or the console dict if the action was successfully sent to the VIM
+ """
+ self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
+
+ try:
+ self._reload_connection()
+ server = self.nova.servers.find(id=vm_id)
+
+ if "start" in action_dict:
+ if action_dict["start"] == "rebuild":
+ server.rebuild()
+ else:
+ if server.status == "PAUSED":
+ server.unpause()
+ elif server.status == "SUSPENDED":
+ server.resume()
+ elif server.status == "SHUTOFF":
+ server.start()
+ else:
+ self.logger.debug(
+ "ERROR : Instance is not in SHUTOFF/PAUSE/SUSPEND state"
+ )
+ raise vimconn.VimConnException(
+ "Cannot 'start' instance while it is in active state",
+ http_code=vimconn.HTTP_Bad_Request,
+ )
+
+ elif "pause" in action_dict:
+ server.pause()
+ elif "resume" in action_dict:
+ server.resume()
+ elif "shutoff" in action_dict or "shutdown" in action_dict:
+ self.logger.debug("server status %s", server.status)
+ if server.status == "ACTIVE":
+ server.stop()
+ else:
+ self.logger.debug("ERROR: VM is not in Active state")
+ raise vimconn.VimConnException(
+ "VM is not in active state, stop operation is not allowed",
+ http_code=vimconn.HTTP_Bad_Request,
+ )
+ elif "forceOff" in action_dict:
+ server.stop() # TODO
+ elif "terminate" in action_dict:
+ server.delete()
+ elif "createImage" in action_dict:
+ server.create_image()
+ # "path":path_schema,
+ # "description":description_schema,
+ # "name":name_schema,
+ # "metadata":metadata_schema,
+ # "imageRef": id_schema,
+ # "disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
+ elif "rebuild" in action_dict:
+ server.rebuild(server.image["id"])
+ elif "reboot" in action_dict:
+ server.reboot() # reboot_type="SOFT"
+ elif "console" in action_dict:
+ console_type = action_dict["console"]