From 4bc8eb99dbadadf5013de93fcb424038149e1576 Mon Sep 17 00:00:00 2001 From: Gulsum Atici Date: Mon, 21 Nov 2022 14:11:02 +0300 Subject: [PATCH] Refactor NG_RO/ns.py _process_vdu_params method Refactoring NG_RO/ns.py _process_vdu_params and vimconn_openstack.py delete_vminstance methods together with adding unit tests. Change-Id: I66b5e4dac296fff7bdac77add997818653ee0801 Signed-off-by: Gulsum Atici --- NG-RO/osm_ng_ro/ns.py | 635 ++-- NG-RO/osm_ng_ro/tests/test_ns.py | 2611 +++++++++++++++-- .../tests/test_vimconn_openstack.py | 1004 ++++++- .../osm_rovim_openstack/vimconn_openstack.py | 182 +- ...g_process_vdu_params-45301da2e7d933de.yaml | 24 + 5 files changed, 3944 insertions(+), 512 deletions(-) create mode 100644 releasenotes/notes/refactoring_process_vdu_params-45301da2e7d933de.yaml diff --git a/NG-RO/osm_ng_ro/ns.py b/NG-RO/osm_ng_ro/ns.py index 77b391cc..c6f918b2 100644 --- a/NG-RO/osm_ng_ro/ns.py +++ b/NG-RO/osm_ng_ro/ns.py @@ -23,7 +23,7 @@ from random import choice as random_choice from threading import Lock from time import time from traceback import format_exc as traceback_format_exc -from typing import Any, Dict, Tuple, Type +from typing import Any, Dict, List, Optional, Tuple, Type from uuid import uuid4 from cryptography.hazmat.backends import default_backend as crypto_default_backend @@ -976,24 +976,25 @@ class Ns(object): @staticmethod def find_persistent_root_volumes( vnfd: dict, - target_vdu: str, + target_vdu: dict, vdu_instantiation_volumes_list: list, disk_list: list, - ) -> (list, dict): + ) -> Dict[str, any]: """Find the persistent root volumes and add them to the disk_list - by parsing the instantiation parameters + by parsing the instantiation parameters. Args: - vnfd: VNFD - target_vdu: processed VDU - vdu_instantiation_volumes_list: instantiation parameters for the each VDU as a list - disk_list: to be filled up + vnfd (dict): VNF descriptor + target_vdu (dict): processed VDU + vdu_instantiation_volumes_list (list): instantiation parameters for the each VDU as a list + disk_list (list): to be filled up Returns: - disk_list: filled VDU list which is used for VDU creation + persistent_root_disk (dict): Details of persistent root disk """ persistent_root_disk = {} + # There can be only one root disk, when we find it, it will return the result for vdu, vsd in product( vnfd.get("vdu", ()), vnfd.get("virtual-storage-desc", ()) @@ -1021,8 +1022,7 @@ class Ns(object): disk_list.append(persistent_root_disk[vsd["id"]]) - # There can be only one root disk, when we find it, it will return the result - return disk_list, persistent_root_disk + return persistent_root_disk else: @@ -1033,19 +1033,18 @@ class Ns(object): } disk_list.append(persistent_root_disk[vsd["id"]]) - return disk_list, persistent_root_disk - return disk_list, persistent_root_disk + return persistent_root_disk @staticmethod def find_persistent_volumes( persistent_root_disk: dict, - target_vdu: str, + target_vdu: dict, vdu_instantiation_volumes_list: list, disk_list: list, - ) -> list: + ) -> None: """Find the ordinary persistent volumes and add them to the disk_list - by parsing the instantiation parameters + by parsing the instantiation parameters. Args: persistent_root_disk: persistent root disk dictionary @@ -1053,9 +1052,6 @@ class Ns(object): vdu_instantiation_volumes_list: instantiation parameters for the each VDU as a list disk_list: to be filled up - Returns: - disk_list: filled VDU list which is used for VDU creation - """ # Find the ordinary volumes which are not added to the persistent_root_disk persistent_disk = {} @@ -1080,150 +1076,254 @@ class Ns(object): } disk_list.append(persistent_disk[disk["id"]]) - return disk_list - @staticmethod - def _process_vdu_params( - target_vdu: Dict[str, Any], - indata: Dict[str, Any], - vim_info: Dict[str, Any], - target_record_id: str, - **kwargs: Dict[str, Any], - ) -> Dict[str, Any]: - """Function to process VDU parameters. + def _sort_vdu_interfaces(target_vdu: dict) -> None: + """Sort the interfaces according to position number. Args: - target_vdu (Dict[str, Any]): [description] - indata (Dict[str, Any]): [description] - vim_info (Dict[str, Any]): [description] - target_record_id (str): [description] + target_vdu (dict): Details of VDU to be created - Returns: - Dict[str, Any]: [description] """ - vnfr_id = kwargs.get("vnfr_id") - nsr_id = kwargs.get("nsr_id") - vnfr = kwargs.get("vnfr") - vdu2cloud_init = kwargs.get("vdu2cloud_init") - tasks_by_target_record_id = kwargs.get("tasks_by_target_record_id") - logger = kwargs.get("logger") - db = kwargs.get("db") - fs = kwargs.get("fs") - ro_nsr_public_key = kwargs.get("ro_nsr_public_key") - - vnf_preffix = "vnfrs:{}".format(vnfr_id) - ns_preffix = "nsrs:{}".format(nsr_id) - image_text = ns_preffix + ":image." + target_vdu["ns-image-id"] - flavor_text = ns_preffix + ":flavor." + target_vdu["ns-flavor-id"] - extra_dict = {"depends_on": [image_text, flavor_text]} - net_list = [] - # If the position info is provided for all the interfaces, it will be sorted # according to position number ascendingly. - if all( + sorted_interfaces = sorted( + target_vdu["interfaces"], + key=lambda x: (x.get("position") is None, x.get("position")), + ) + target_vdu["interfaces"] = sorted_interfaces + + @staticmethod + def _partially_locate_vdu_interfaces(target_vdu: dict) -> None: + """Only place the interfaces which has specific position. + + Args: + target_vdu (dict): Details of VDU to be created + + """ + # If the position info is provided for some interfaces but not all of them, the interfaces + # which has specific position numbers will be placed and others' positions will not be taken care. + if any( i.get("position") + 1 for i in target_vdu["interfaces"] if i.get("position") is not None ): - sorted_interfaces = sorted( - target_vdu["interfaces"], - key=lambda x: (x.get("position") is None, x.get("position")), - ) + n = len(target_vdu["interfaces"]) + sorted_interfaces = [-1] * n + k, m = 0, 0 + + while k < n: + if target_vdu["interfaces"][k].get("position") is not None: + if any(i.get("position") == 0 for i in target_vdu["interfaces"]): + idx = target_vdu["interfaces"][k]["position"] + 1 + else: + idx = target_vdu["interfaces"][k]["position"] + sorted_interfaces[idx - 1] = target_vdu["interfaces"][k] + k += 1 + + while m < n: + if target_vdu["interfaces"][m].get("position") is None: + idy = sorted_interfaces.index(-1) + sorted_interfaces[idy] = target_vdu["interfaces"][m] + m += 1 + target_vdu["interfaces"] = sorted_interfaces - # If the position info is provided for some interfaces but not all of them, the interfaces - # which has specific position numbers will be placed and others' positions will not be taken care. - else: - if any( - i.get("position") + 1 - for i in target_vdu["interfaces"] - if i.get("position") is not None + @staticmethod + def _prepare_vdu_cloud_init( + target_vdu: dict, vdu2cloud_init: dict, db: object, fs: object + ) -> Dict: + """Fill cloud_config dict with cloud init details. + + Args: + target_vdu (dict): Details of VDU to be created + vdu2cloud_init (dict): Cloud init dict + db (object): DB object + fs (object): FS object + + Returns: + cloud_config (dict): Cloud config details of VDU + + """ + # cloud config + cloud_config = {} + + if target_vdu.get("cloud-init"): + if target_vdu["cloud-init"] not in vdu2cloud_init: + vdu2cloud_init[target_vdu["cloud-init"]] = Ns._get_cloud_init( + db=db, + fs=fs, + location=target_vdu["cloud-init"], + ) + + cloud_content_ = vdu2cloud_init[target_vdu["cloud-init"]] + cloud_config["user-data"] = Ns._parse_jinja2( + cloud_init_content=cloud_content_, + params=target_vdu.get("additionalParams"), + context=target_vdu["cloud-init"], + ) + + if target_vdu.get("boot-data-drive"): + cloud_config["boot-data-drive"] = target_vdu.get("boot-data-drive") + + return cloud_config + + @staticmethod + def _check_vld_information_of_interfaces( + interface: dict, ns_preffix: str, vnf_preffix: str + ) -> Optional[str]: + """Prepare the net_text by the virtual link information for vnf and ns level. + Args: + interface (dict): Interface details + ns_preffix (str): Prefix of NS + vnf_preffix (str): Prefix of VNF + + Returns: + net_text (str): information of net + + """ + net_text = "" + if interface.get("ns-vld-id"): + net_text = ns_preffix + ":vld." + interface["ns-vld-id"] + elif interface.get("vnf-vld-id"): + net_text = vnf_preffix + ":vld." + interface["vnf-vld-id"] + + return net_text + + @staticmethod + def _prepare_interface_port_security(interface: dict) -> None: + """ + + Args: + interface (dict): Interface details + + """ + if "port-security-enabled" in interface: + interface["port_security"] = interface.pop("port-security-enabled") + + if "port-security-disable-strategy" in interface: + interface["port_security_disable_strategy"] = interface.pop( + "port-security-disable-strategy" + ) + + @staticmethod + def _create_net_item_of_interface(interface: dict, net_text: str) -> dict: + """Prepare net item including name, port security, floating ip etc. + + Args: + interface (dict): Interface details + net_text (str): information of net + + Returns: + net_item (dict): Dict including net details + + """ + + net_item = { + x: v + for x, v in interface.items() + if x + in ( + "name", + "vpci", + "port_security", + "port_security_disable_strategy", + "floating_ip", + ) + } + net_item["net_id"] = "TASK-" + net_text + net_item["type"] = "virtual" + + return net_item + + @staticmethod + def _prepare_type_of_interface( + interface: dict, tasks_by_target_record_id: dict, net_text: str, net_item: dict + ) -> None: + """Fill the net item type by interface type such as SR-IOV, OM-MGMT, bridge etc. + + Args: + interface (dict): Interface details + tasks_by_target_record_id (dict): Task details + net_text (str): information of net + net_item (dict): Dict including net details + + """ + # TODO mac_address: used for SR-IOV ifaces #TODO for other types + # TODO floating_ip: True/False (or it can be None) + + if interface.get("type") in ("SR-IOV", "PCI-PASSTHROUGH"): + # Mark the net create task as type data + if deep_get( + tasks_by_target_record_id, + net_text, + "extra_dict", + "params", + "net_type", ): - n = len(target_vdu["interfaces"]) - sorted_interfaces = [-1] * n - k, m = 0, 0 - while k < n: - if target_vdu["interfaces"][k].get("position"): - idx = target_vdu["interfaces"][k]["position"] - sorted_interfaces[idx - 1] = target_vdu["interfaces"][k] - k += 1 - while m < n: - if not target_vdu["interfaces"][m].get("position"): - idy = sorted_interfaces.index(-1) - sorted_interfaces[idy] = target_vdu["interfaces"][m] - m += 1 + tasks_by_target_record_id[net_text]["extra_dict"]["params"][ + "net_type" + ] = "data" + + net_item["use"] = "data" + net_item["model"] = interface["type"] + net_item["type"] = interface["type"] + + elif ( + interface.get("type") == "OM-MGMT" + or interface.get("mgmt-interface") + or interface.get("mgmt-vnf") + ): + net_item["use"] = "mgmt" + + else: + # If interface.get("type") in ("VIRTIO", "E1000", "PARAVIRT"): + net_item["use"] = "bridge" + net_item["model"] = interface.get("type") - target_vdu["interfaces"] = sorted_interfaces + @staticmethod + def _prepare_vdu_interfaces( + target_vdu: dict, + extra_dict: dict, + ns_preffix: str, + vnf_preffix: str, + logger: object, + tasks_by_target_record_id: dict, + net_list: list, + ) -> None: + """Prepare the net_item and add net_list, add mgmt interface to extra_dict. - # If the position info is not provided for the interfaces, interfaces will be attached - # according to the order in the VNFD. + Args: + target_vdu (dict): VDU to be created + extra_dict (dict): Dictionary to be filled + ns_preffix (str): NS prefix as string + vnf_preffix (str): VNF prefix as string + logger (object): Logger Object + tasks_by_target_record_id (dict): Task details + net_list (list): Net list of VDU + """ for iface_index, interface in enumerate(target_vdu["interfaces"]): - if interface.get("ns-vld-id"): - net_text = ns_preffix + ":vld." + interface["ns-vld-id"] - elif interface.get("vnf-vld-id"): - net_text = vnf_preffix + ":vld." + interface["vnf-vld-id"] - else: + + net_text = Ns._check_vld_information_of_interfaces( + interface, ns_preffix, vnf_preffix + ) + if not net_text: + # Interface not connected to any vld logger.error( "Interface {} from vdu {} not connected to any vld".format( iface_index, target_vdu["vdu-name"] ) ) - - continue # interface not connected to any vld + continue extra_dict["depends_on"].append(net_text) - if "port-security-enabled" in interface: - interface["port_security"] = interface.pop("port-security-enabled") - - if "port-security-disable-strategy" in interface: - interface["port_security_disable_strategy"] = interface.pop( - "port-security-disable-strategy" - ) - - net_item = { - x: v - for x, v in interface.items() - if x - in ( - "name", - "vpci", - "port_security", - "port_security_disable_strategy", - "floating_ip", - ) - } - net_item["net_id"] = "TASK-" + net_text - net_item["type"] = "virtual" + Ns._prepare_interface_port_security(interface) - # TODO mac_address: used for SR-IOV ifaces #TODO for other types - # TODO floating_ip: True/False (or it can be None) - if interface.get("type") in ("SR-IOV", "PCI-PASSTHROUGH"): - # mark the net create task as type data - if deep_get( - tasks_by_target_record_id, - net_text, - "extra_dict", - "params", - "net_type", - ): - tasks_by_target_record_id[net_text]["extra_dict"]["params"][ - "net_type" - ] = "data" + net_item = Ns._create_net_item_of_interface(interface, net_text) - net_item["use"] = "data" - net_item["model"] = interface["type"] - net_item["type"] = interface["type"] - elif ( - interface.get("type") == "OM-MGMT" - or interface.get("mgmt-interface") - or interface.get("mgmt-vnf") - ): - net_item["use"] = "mgmt" - else: - # if interface.get("type") in ("VIRTIO", "E1000", "PARAVIRT"): - net_item["use"] = "bridge" - net_item["model"] = interface.get("type") + Ns._prepare_type_of_interface( + interface, tasks_by_target_record_id, net_text, net_item + ) if interface.get("ip-address"): net_item["ip_address"] = interface["ip-address"] @@ -1238,27 +1338,18 @@ class Ns(object): elif interface.get("mgmt-interface"): extra_dict["mgmt_vdu_interface"] = iface_index - # cloud config - cloud_config = {} - - if target_vdu.get("cloud-init"): - if target_vdu["cloud-init"] not in vdu2cloud_init: - vdu2cloud_init[target_vdu["cloud-init"]] = Ns._get_cloud_init( - db=db, - fs=fs, - location=target_vdu["cloud-init"], - ) - - cloud_content_ = vdu2cloud_init[target_vdu["cloud-init"]] - cloud_config["user-data"] = Ns._parse_jinja2( - cloud_init_content=cloud_content_, - params=target_vdu.get("additionalParams"), - context=target_vdu["cloud-init"], - ) + @staticmethod + def _prepare_vdu_ssh_keys( + target_vdu: dict, ro_nsr_public_key: dict, cloud_config: dict + ) -> None: + """Add ssh keys to cloud config. - if target_vdu.get("boot-data-drive"): - cloud_config["boot-data-drive"] = target_vdu.get("boot-data-drive") + Args: + target_vdu (dict): Details of VDU to be created + ro_nsr_public_key (dict): RO NSR public Key + cloud_config (dict): Cloud config details + """ ssh_keys = [] if target_vdu.get("ssh-keys"): @@ -1270,12 +1361,192 @@ class Ns(object): if ssh_keys: cloud_config["key-pairs"] = ssh_keys + @staticmethod + def _select_persistent_root_disk(vsd: dict, vdu: dict) -> dict: + """Selects the persistent root disk if exists. + Args: + vsd (dict): Virtual storage descriptors in VNFD + vdu (dict): VNF descriptor + + Returns: + root_disk (dict): Selected persistent root disk + """ + if vsd.get("id") == vdu.get("virtual-storage-desc", [[]])[0]: + root_disk = vsd + if root_disk.get( + "type-of-storage" + ) == "persistent-storage:persistent-storage" and root_disk.get( + "size-of-storage" + ): + return root_disk + + @staticmethod + def _add_persistent_root_disk_to_disk_list( + vnfd: dict, target_vdu: dict, persistent_root_disk: dict, disk_list: list + ) -> None: + """Find the persistent root disk and add to disk list. + + Args: + vnfd (dict): VNF descriptor + target_vdu (dict): Details of VDU to be created + persistent_root_disk (dict): Details of persistent root disk + disk_list (list): Disks of VDU + + """ + for vdu in vnfd.get("vdu", ()): + if vdu["name"] == target_vdu["vdu-name"]: + for vsd in vnfd.get("virtual-storage-desc", ()): + root_disk = Ns._select_persistent_root_disk(vsd, vdu) + + if not root_disk: + continue + + persistent_root_disk[vsd["id"]] = { + "image_id": vdu.get("sw-image-desc"), + "size": root_disk["size-of-storage"], + } + + disk_list.append(persistent_root_disk[vsd["id"]]) + break + + @staticmethod + def _add_persistent_ordinary_disks_to_disk_list( + target_vdu: dict, + persistent_root_disk: dict, + persistent_ordinary_disk: dict, + disk_list: list, + ) -> None: + """Fill the disk list by adding persistent ordinary disks. + + Args: + target_vdu (dict): Details of VDU to be created + persistent_root_disk (dict): Details of persistent root disk + persistent_ordinary_disk (dict): Details of persistent ordinary disk + disk_list (list): Disks of VDU + + """ + if target_vdu.get("virtual-storages"): + for disk in target_vdu["virtual-storages"]: + if ( + disk.get("type-of-storage") + == "persistent-storage:persistent-storage" + and disk["id"] not in persistent_root_disk.keys() + ): + persistent_ordinary_disk[disk["id"]] = { + "size": disk["size-of-storage"], + } + disk_list.append(persistent_ordinary_disk[disk["id"]]) + + @staticmethod + def _prepare_vdu_affinity_group_list( + target_vdu: dict, extra_dict: dict, ns_preffix: str + ) -> List[Dict[str, any]]: + """Process affinity group details to prepare affinity group list. + + Args: + target_vdu (dict): Details of VDU to be created + extra_dict (dict): Dictionary to be filled + ns_preffix (str): Prefix as string + + Returns: + + affinity_group_list (list): Affinity group details + + """ + affinity_group_list = [] + + if target_vdu.get("affinity-or-anti-affinity-group-id"): + for affinity_group_id in target_vdu["affinity-or-anti-affinity-group-id"]: + affinity_group = {} + affinity_group_text = ( + ns_preffix + ":affinity-or-anti-affinity-group." + affinity_group_id + ) + + if not isinstance(extra_dict.get("depends_on"), list): + raise NsException("Invalid extra_dict format.") + + extra_dict["depends_on"].append(affinity_group_text) + affinity_group["affinity_group_id"] = "TASK-" + affinity_group_text + affinity_group_list.append(affinity_group) + + return affinity_group_list + + @staticmethod + def _process_vdu_params( + target_vdu: Dict[str, Any], + indata: Dict[str, Any], + vim_info: Dict[str, Any], + target_record_id: str, + **kwargs: Dict[str, Any], + ) -> Dict[str, Any]: + """Function to process VDU parameters. + + Args: + target_vdu (Dict[str, Any]): [description] + indata (Dict[str, Any]): [description] + vim_info (Dict[str, Any]): [description] + target_record_id (str): [description] + + Returns: + Dict[str, Any]: [description] + """ + vnfr_id = kwargs.get("vnfr_id") + nsr_id = kwargs.get("nsr_id") + vnfr = kwargs.get("vnfr") + vdu2cloud_init = kwargs.get("vdu2cloud_init") + tasks_by_target_record_id = kwargs.get("tasks_by_target_record_id") + logger = kwargs.get("logger") + db = kwargs.get("db") + fs = kwargs.get("fs") + ro_nsr_public_key = kwargs.get("ro_nsr_public_key") + + vnf_preffix = "vnfrs:{}".format(vnfr_id) + ns_preffix = "nsrs:{}".format(nsr_id) + image_text = ns_preffix + ":image." + target_vdu["ns-image-id"] + flavor_text = ns_preffix + ":flavor." + target_vdu["ns-flavor-id"] + extra_dict = {"depends_on": [image_text, flavor_text]} + net_list = [] + persistent_root_disk = {} + persistent_ordinary_disk = {} vdu_instantiation_volumes_list = [] disk_list = [] vnfd_id = vnfr["vnfd-id"] vnfd = db.get_one("vnfds", {"_id": vnfd_id}) + # If the position info is provided for all the interfaces, it will be sorted + # according to position number ascendingly. + if all( + True if i.get("position") is not None else False + for i in target_vdu["interfaces"] + ): + + Ns._sort_vdu_interfaces(target_vdu) + + # If the position info is provided for some interfaces but not all of them, the interfaces + # which has specific position numbers will be placed and others' positions will not be taken care. + else: + + Ns._partially_locate_vdu_interfaces(target_vdu) + + # If the position info is not provided for the interfaces, interfaces will be attached + # according to the order in the VNFD. + Ns._prepare_vdu_interfaces( + target_vdu, + extra_dict, + ns_preffix, + vnf_preffix, + logger, + tasks_by_target_record_id, + net_list, + ) + + # cloud config + cloud_config = Ns._prepare_vdu_cloud_init(target_vdu, vdu2cloud_init, db, fs) + + # Prepare VDU ssh keys + Ns._prepare_vdu_ssh_keys(target_vdu, ro_nsr_public_key, cloud_config) + if target_vdu.get("additionalParams"): vdu_instantiation_volumes_list = ( target_vdu.get("additionalParams").get("OSM").get("vdu_volumes") @@ -1284,13 +1555,13 @@ class Ns(object): if vdu_instantiation_volumes_list: # Find the root volumes and add to the disk_list - (disk_list, persistent_root_disk,) = Ns.find_persistent_root_volumes( + persistent_root_disk = Ns.find_persistent_root_volumes( vnfd, target_vdu, vdu_instantiation_volumes_list, disk_list ) # Find the ordinary volumes which are not added to the persistent_root_disk # and put them to the disk list - disk_list = Ns.find_persistent_volumes( + Ns.find_persistent_volumes( persistent_root_disk, target_vdu, vdu_instantiation_volumes_list, @@ -1298,45 +1569,19 @@ class Ns(object): ) else: + # Vdu_instantiation_volumes_list is empty + # First get add the persistent root disks to disk_list + Ns._add_persistent_root_disk_to_disk_list( + vnfd, target_vdu, persistent_root_disk, disk_list + ) + # Add the persistent non-root disks to disk_list + Ns._add_persistent_ordinary_disks_to_disk_list( + target_vdu, persistent_root_disk, persistent_ordinary_disk, disk_list + ) - # vdu_instantiation_volumes_list is empty - for vdu in vnfd.get("vdu", ()): - if vdu["name"] == target_vdu["vdu-name"]: - for vsd in vnfd.get("virtual-storage-desc", ()): - if vsd.get("id") == vdu.get("virtual-storage-desc", [[]])[0]: - root_disk = vsd - if root_disk.get( - "type-of-storage" - ) == "persistent-storage:persistent-storage" and root_disk.get( - "size-of-storage" - ): - persistent_root_disk[vsd["id"]] = { - "image_id": vdu.get("sw-image-desc"), - "size": root_disk["size-of-storage"], - } - disk_list.append(persistent_root_disk[vsd["id"]]) - - if target_vdu.get("virtual-storages"): - for disk in target_vdu["virtual-storages"]: - if ( - disk.get("type-of-storage") - == "persistent-storage:persistent-storage" - and disk["id"] not in persistent_root_disk.keys() - ): - disk_list.append({"size": disk["size-of-storage"]}) - - affinity_group_list = [] - - if target_vdu.get("affinity-or-anti-affinity-group-id"): - affinity_group = {} - for affinity_group_id in target_vdu["affinity-or-anti-affinity-group-id"]: - affinity_group_text = ( - ns_preffix + ":affinity-or-anti-affinity-group." + affinity_group_id - ) - - extra_dict["depends_on"].append(affinity_group_text) - affinity_group["affinity_group_id"] = "TASK-" + affinity_group_text - affinity_group_list.append(affinity_group) + affinity_group_list = Ns._prepare_vdu_affinity_group_list( + target_vdu, extra_dict, ns_preffix + ) extra_dict["params"] = { "name": "{}-{}-{}-{}".format( diff --git a/NG-RO/osm_ng_ro/tests/test_ns.py b/NG-RO/osm_ng_ro/tests/test_ns.py index af448508..f4a1c87a 100644 --- a/NG-RO/osm_ng_ro/tests/test_ns.py +++ b/NG-RO/osm_ng_ro/tests/test_ns.py @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. ####################################################################################### - +from copy import deepcopy import unittest from unittest.mock import MagicMock, Mock, patch @@ -33,6 +33,283 @@ __author__ = "Eduardo Sousa" __date__ = "$19-NOV-2021 00:00:00$" +# Variables used in Tests +vnfd_wth_persistent_storage = { + "_id": "ad6356e3-698c-43bf-9901-3aae9e9b9d18", + "df": [ + { + "id": "default-df", + "vdu-profile": [{"id": "several_volumes-VM", "min-number-of-instances": 1}], + } + ], + "id": "several_volumes-vnf", + "product-name": "several_volumes-vnf", + "vdu": [ + { + "id": "several_volumes-VM", + "name": "several_volumes-VM", + "sw-image-desc": "ubuntu20.04", + "alternative-sw-image-desc": [ + "ubuntu20.04-aws", + "ubuntu20.04-azure", + ], + "virtual-storage-desc": [ + "persistent-root-volume", + "persistent-volume2", + "ephemeral-volume", + ], + } + ], + "version": "1.0", + "virtual-storage-desc": [ + { + "id": "persistent-volume2", + "type-of-storage": "persistent-storage:persistent-storage", + "size-of-storage": "10", + }, + { + "id": "persistent-root-volume", + "type-of-storage": "persistent-storage:persistent-storage", + "size-of-storage": "10", + }, + { + "id": "ephemeral-volume", + "type-of-storage": "etsi-nfv-descriptors:ephemeral-storage", + "size-of-storage": "1", + }, + ], + "_admin": { + "storage": { + "fs": "mongo", + "path": "/app/storage/", + }, + "type": "vnfd", + }, +} +vim_volume_id = "ru937f49-3870-4169-b758-9732e1ff40f3" +task_by_target_record_id = { + "nsrs:th47f48-9870-4169-b758-9732e1ff40f3": { + "extra_dict": {"params": {"net_type": "SR-IOV"}} + } +} +interfaces_wthout_positions = [ + { + "name": "vdu-eth1", + "ns-vld-id": "net1", + }, + { + "name": "vdu-eth2", + "ns-vld-id": "net2", + }, + { + "name": "vdu-eth3", + "ns-vld-id": "mgmtnet", + }, +] +interfaces_wth_all_positions = [ + { + "name": "vdu-eth1", + "ns-vld-id": "net1", + "position": 2, + }, + { + "name": "vdu-eth2", + "ns-vld-id": "net2", + "position": 0, + }, + { + "name": "vdu-eth3", + "ns-vld-id": "mgmtnet", + "position": 1, + }, +] +target_vdu_wth_persistent_storage = { + "_id": "09a0baa7-b7cb-4924-bd63-9f04a1c23960", + "ns-flavor-id": "0", + "ns-image-id": "0", + "vdu-name": "several_volumes-VM", + "interfaces": [ + { + "name": "vdu-eth0", + "ns-vld-id": "mgmtnet", + } + ], + "virtual-storages": [ + { + "id": "persistent-volume2", + "size-of-storage": "10", + "type-of-storage": "persistent-storage:persistent-storage", + }, + { + "id": "persistent-root-volume", + "size-of-storage": "10", + "type-of-storage": "persistent-storage:persistent-storage", + }, + { + "id": "ephemeral-volume", + "size-of-storage": "1", + "type-of-storage": "etsi-nfv-descriptors:ephemeral-storage", + }, + ], +} +db = MagicMock(name="database mock") +fs = MagicMock(name="database mock") +ns_preffix = "nsrs:th47f48-9870-4169-b758-9732e1ff40f3" +vnf_preffix = "vnfrs:wh47f48-y870-4169-b758-5732e1ff40f5" +vnfr_id = "wh47f48-y870-4169-b758-5732e1ff40f5" +nsr_id = "th47f48-9870-4169-b758-9732e1ff40f3" +indata = { + "name": "sample_name", +} +expected_extra_dict = { + "depends_on": [ + f"{ns_preffix}:image.0", + f"{ns_preffix}:flavor.0", + ], + "params": { + "affinity_group_list": [], + "availability_zone_index": None, + "availability_zone_list": None, + "cloud_config": None, + "description": "several_volumes-VM", + "disk_list": [], + "flavor_id": f"TASK-{ns_preffix}:flavor.0", + "image_id": f"TASK-{ns_preffix}:image.0", + "name": "sample_name-vnf-several-volu-several_volumes-VM-0", + "net_list": [], + "start": True, + }, +} + +expected_extra_dict2 = { + "depends_on": [ + f"{ns_preffix}:image.0", + f"{ns_preffix}:flavor.0", + ], + "params": { + "affinity_group_list": [], + "availability_zone_index": None, + "availability_zone_list": None, + "cloud_config": None, + "description": "without_volumes-VM", + "disk_list": [], + "flavor_id": f"TASK-{ns_preffix}:flavor.0", + "image_id": f"TASK-{ns_preffix}:image.0", + "name": "sample_name-vnf-several-volu-without_volumes-VM-0", + "net_list": [], + "start": True, + }, +} +tasks_by_target_record_id = { + "nsrs:th47f48-9870-4169-b758-9732e1ff40f3": { + "extra_dict": { + "params": { + "net_type": "SR-IOV", + } + } + } +} +kwargs = { + "db": MagicMock(), + "vdu2cloud_init": {}, + "vnfr": { + "vnfd-id": "ad6356e3-698c-43bf-9901-3aae9e9b9d18", + "member-vnf-index-ref": "vnf-several-volumes", + }, +} +vnfd_wthout_persistent_storage = { + "_id": "ad6356e3-698c-43bf-9901-3aae9e9b9d18", + "df": [ + { + "id": "default-df", + "vdu-profile": [{"id": "without_volumes-VM", "min-number-of-instances": 1}], + } + ], + "id": "without_volumes-vnf", + "product-name": "without_volumes-vnf", + "vdu": [ + { + "id": "without_volumes-VM", + "name": "without_volumes-VM", + "sw-image-desc": "ubuntu20.04", + "alternative-sw-image-desc": [ + "ubuntu20.04-aws", + "ubuntu20.04-azure", + ], + "virtual-storage-desc": ["root-volume", "ephemeral-volume"], + } + ], + "version": "1.0", + "virtual-storage-desc": [ + {"id": "root-volume", "size-of-storage": "10"}, + { + "id": "ephemeral-volume", + "type-of-storage": "etsi-nfv-descriptors:ephemeral-storage", + "size-of-storage": "1", + }, + ], + "_admin": { + "storage": { + "fs": "mongo", + "path": "/app/storage/", + }, + "type": "vnfd", + }, +} + +target_vdu_wthout_persistent_storage = { + "_id": "09a0baa7-b7cb-4924-bd63-9f04a1c23960", + "ns-flavor-id": "0", + "ns-image-id": "0", + "vdu-name": "without_volumes-VM", + "interfaces": [ + { + "name": "vdu-eth0", + "ns-vld-id": "mgmtnet", + } + ], + "virtual-storages": [ + { + "id": "root-volume", + "size-of-storage": "10", + }, + { + "id": "ephemeral-volume", + "size-of-storage": "1", + "type-of-storage": "etsi-nfv-descriptors:ephemeral-storage", + }, + ], +} +cloud_init_content = """ +disk_setup: + ephemeral0: + table_type: {{type}} + layout: True + overwrite: {{is_override}} +runcmd: + - [ ls, -l, / ] + - [ sh, -xc, "echo $(date) '{{command}}'" ] +""" + +user_data = """ +disk_setup: + ephemeral0: + table_type: mbr + layout: True + overwrite: False +runcmd: + - [ ls, -l, / ] + - [ sh, -xc, "echo $(date) '& rm -rf /'" ] +""" + + +class CopyingMock(MagicMock): + def __call__(self, *args, **kwargs): + args = deepcopy(args) + kwargs = deepcopy(kwargs) + return super(CopyingMock, self).__call__(*args, **kwargs) + + class TestNs(unittest.TestCase): def setUp(self): pass @@ -1636,7 +1913,6 @@ class TestNs(unittest.TestCase): self, epa_params, ): - db = MagicMock(name="database mock") kwargs = { "db": db, } @@ -1812,7 +2088,6 @@ class TestNs(unittest.TestCase): self, epa_params, ): - db = MagicMock(name="database mock") kwargs = { "db": db, @@ -1989,7 +2264,6 @@ class TestNs(unittest.TestCase): self, epa_params, ): - db = MagicMock(name="database mock") kwargs = { "db": db, @@ -2821,269 +3095,6 @@ class TestNs(unittest.TestCase): ) self.assertEqual(result, expected_result) - def test__process_vdu_params_empty_kargs(self): - pass - - def test__process_vdu_params_interface_ns_vld_id(self): - pass - - def test__process_vdu_params_interface_vnf_vld_id(self): - pass - - def test__process_vdu_params_interface_unknown(self): - pass - - def test__process_vdu_params_interface_port_security_enabled(self): - pass - - def test__process_vdu_params_interface_port_security_disable_strategy(self): - pass - - def test__process_vdu_params_interface_sriov(self): - pass - - def test__process_vdu_params_interface_pci_passthrough(self): - pass - - def test__process_vdu_params_interface_om_mgmt(self): - pass - - def test__process_vdu_params_interface_mgmt_interface(self): - pass - - def test__process_vdu_params_interface_mgmt_vnf(self): - pass - - def test__process_vdu_params_interface_bridge(self): - pass - - def test__process_vdu_params_interface_ip_address(self): - pass - - def test__process_vdu_params_interface_mac_address(self): - pass - - def test__process_vdu_params_vdu_cloud_init_missing(self): - pass - - def test__process_vdu_params_vdu_cloud_init_present(self): - pass - - def test__process_vdu_params_vdu_boot_data_drive(self): - pass - - def test__process_vdu_params_vdu_ssh_keys(self): - pass - - def test__process_vdu_params_vdu_ssh_access_required(self): - pass - - @patch("osm_ng_ro.ns.Ns._get_cloud_init") - @patch("osm_ng_ro.ns.Ns._parse_jinja2") - def test__process_vdu_params_vdu_persistent_root_volume( - self, get_cloud_init, parse_jinja2 - ): - db = MagicMock(name="database mock") - kwargs = { - "db": db, - "vdu2cloud_init": {}, - "vnfr": { - "vnfd-id": "ad6356e3-698c-43bf-9901-3aae9e9b9d18", - "member-vnf-index-ref": "vnf-several-volumes", - }, - } - get_cloud_init.return_value = {} - parse_jinja2.return_value = {} - db.get_one.return_value = { - "_id": "ad6356e3-698c-43bf-9901-3aae9e9b9d18", - "df": [ - { - "id": "default-df", - "vdu-profile": [ - {"id": "several_volumes-VM", "min-number-of-instances": 1} - ], - } - ], - "id": "several_volumes-vnf", - "product-name": "several_volumes-vnf", - "vdu": [ - { - "id": "several_volumes-VM", - "name": "several_volumes-VM", - "sw-image-desc": "ubuntu20.04", - "alternative-sw-image-desc": [ - "ubuntu20.04-aws", - "ubuntu20.04-azure", - ], - "virtual-storage-desc": [ - "persistent-root-volume", - "persistent-volume2", - "ephemeral-volume", - ], - } - ], - "version": "1.0", - "virtual-storage-desc": [ - { - "id": "persistent-volume2", - "type-of-storage": "persistent-storage:persistent-storage", - "size-of-storage": "10", - }, - { - "id": "persistent-root-volume", - "type-of-storage": "persistent-storage:persistent-storage", - "size-of-storage": "10", - }, - { - "id": "ephemeral-volume", - "type-of-storage": "etsi-nfv-descriptors:ephemeral-storage", - "size-of-storage": "1", - }, - ], - "_admin": { - "storage": { - "fs": "mongo", - "path": "/app/storage/", - }, - "type": "vnfd", - }, - } - - target_vdu = { - "_id": "09a0baa7-b7cb-4924-bd63-9f04a1c23960", - "ns-flavor-id": "0", - "ns-image-id": "0", - "vdu-name": "several_volumes-VM", - "interfaces": [ - { - "name": "vdu-eth0", - "ns-vld-id": "mgmtnet", - } - ], - "virtual-storages": [ - { - "id": "persistent-volume2", - "size-of-storage": "10", - "type-of-storage": "persistent-storage:persistent-storage", - }, - { - "id": "persistent-root-volume", - "size-of-storage": "10", - "type-of-storage": "persistent-storage:persistent-storage", - }, - { - "id": "ephemeral-volume", - "size-of-storage": "1", - "type-of-storage": "etsi-nfv-descriptors:ephemeral-storage", - }, - ], - } - indata = { - "name": "sample_name", - } - expected_result = [{"image_id": "ubuntu20.04", "size": "10"}, {"size": "10"}] - result = Ns._process_vdu_params( - target_vdu, indata, vim_info=None, target_record_id=None, **kwargs - ) - self.assertEqual( - expected_result, result["params"]["disk_list"], "Wrong Disk List" - ) - - @patch("osm_ng_ro.ns.Ns._get_cloud_init") - @patch("osm_ng_ro.ns.Ns._parse_jinja2") - def test__process_vdu_params_vdu_without_persistent_storage( - self, get_cloud_init, parse_jinja2 - ): - db = MagicMock(name="database mock") - kwargs = { - "db": db, - "vdu2cloud_init": {}, - "vnfr": { - "vnfd-id": "ad6356e3-698c-43bf-9901-3aae9e9b9d18", - "member-vnf-index-ref": "vnf-several-volumes", - }, - } - get_cloud_init.return_value = {} - parse_jinja2.return_value = {} - db.get_one.return_value = { - "_id": "ad6356e3-698c-43bf-9901-3aae9e9b9d18", - "df": [ - { - "id": "default-df", - "vdu-profile": [ - {"id": "without_volumes-VM", "min-number-of-instances": 1} - ], - } - ], - "id": "without_volumes-vnf", - "product-name": "without_volumes-vnf", - "vdu": [ - { - "id": "without_volumes-VM", - "name": "without_volumes-VM", - "sw-image-desc": "ubuntu20.04", - "alternative-sw-image-desc": [ - "ubuntu20.04-aws", - "ubuntu20.04-azure", - ], - "virtual-storage-desc": ["root-volume", "ephemeral-volume"], - } - ], - "version": "1.0", - "virtual-storage-desc": [ - {"id": "root-volume", "size-of-storage": "10"}, - { - "id": "ephemeral-volume", - "type-of-storage": "etsi-nfv-descriptors:ephemeral-storage", - "size-of-storage": "1", - }, - ], - "_admin": { - "storage": { - "fs": "mongo", - "path": "/app/storage/", - }, - "type": "vnfd", - }, - } - - target_vdu = { - "_id": "09a0baa7-b7cb-4924-bd63-9f04a1c23960", - "ns-flavor-id": "0", - "ns-image-id": "0", - "vdu-name": "without_volumes-VM", - "interfaces": [ - { - "name": "vdu-eth0", - "ns-vld-id": "mgmtnet", - } - ], - "virtual-storages": [ - { - "id": "root-volume", - "size-of-storage": "10", - }, - { - "id": "ephemeral-volume", - "size-of-storage": "1", - "type-of-storage": "etsi-nfv-descriptors:ephemeral-storage", - }, - ], - } - indata = { - "name": "sample_name", - } - expected_result = [] - result = Ns._process_vdu_params( - target_vdu, indata, vim_info=None, target_record_id=None, **kwargs - ) - self.assertEqual( - expected_result, result["params"]["disk_list"], "Wrong Disk List" - ) - - def test__process_vdu_params(self): - pass - @patch("osm_ng_ro.ns.Ns._assign_vim") def test__rebuild_start_stop_task(self, assign_vim): self.ns = Ns() @@ -3221,3 +3232,2069 @@ class TestNs(unittest.TestCase): ) self.assertDictEqual(task, expected_result) + + +class TestProcessVduParams(unittest.TestCase): + def setUp(self): + self.ns = Ns() + self.logger = CopyingMock(autospec=True) + + def test_find_persistent_root_volumes_empty_instantiation_vol_list(self): + """Find persistent root volume, instantiation_vol_list is empty.""" + vnfd = deepcopy(vnfd_wth_persistent_storage) + target_vdu = target_vdu_wth_persistent_storage + vdu_instantiation_volumes_list = [] + disk_list = [] + expected_persist_root_disk = { + "persistent-root-volume": { + "image_id": "ubuntu20.04", + "size": "10", + } + } + expected_disk_list = [ + { + "image_id": "ubuntu20.04", + "size": "10", + }, + ] + persist_root_disk = self.ns.find_persistent_root_volumes( + vnfd, target_vdu, vdu_instantiation_volumes_list, disk_list + ) + self.assertEqual(persist_root_disk, expected_persist_root_disk) + self.assertEqual(disk_list, expected_disk_list) + self.assertEqual(len(disk_list), 1) + + def test_find_persistent_root_volumes_always_selects_first_vsd_as_root(self): + """Find persistent root volume, always selects the first vsd as root volume.""" + vnfd = deepcopy(vnfd_wth_persistent_storage) + vnfd["vdu"][0]["virtual-storage-desc"] = [ + "persistent-volume2", + "persistent-root-volume", + "ephemeral-volume", + ] + target_vdu = target_vdu_wth_persistent_storage + vdu_instantiation_volumes_list = [] + disk_list = [] + expected_persist_root_disk = { + "persistent-volume2": { + "image_id": "ubuntu20.04", + "size": "10", + } + } + expected_disk_list = [ + { + "image_id": "ubuntu20.04", + "size": "10", + }, + ] + persist_root_disk = self.ns.find_persistent_root_volumes( + vnfd, target_vdu, vdu_instantiation_volumes_list, disk_list + ) + self.assertEqual(persist_root_disk, expected_persist_root_disk) + self.assertEqual(disk_list, expected_disk_list) + self.assertEqual(len(disk_list), 1) + + def test_find_persistent_root_volumes_empty_size_of_storage(self): + """Find persistent root volume, size of storage is empty.""" + vnfd = deepcopy(vnfd_wth_persistent_storage) + vnfd["virtual-storage-desc"][0]["size-of-storage"] = "" + vnfd["vdu"][0]["virtual-storage-desc"] = [ + "persistent-volume2", + "persistent-root-volume", + "ephemeral-volume", + ] + target_vdu = target_vdu_wth_persistent_storage + vdu_instantiation_volumes_list = [] + disk_list = [] + persist_root_disk = self.ns.find_persistent_root_volumes( + vnfd, target_vdu, vdu_instantiation_volumes_list, disk_list + ) + self.assertEqual(persist_root_disk, None) + self.assertEqual(disk_list, []) + + def test_find_persistent_root_empty_disk_list(self): + """Find persistent root volume, empty disk list.""" + vnfd = deepcopy(vnfd_wth_persistent_storage) + target_vdu = target_vdu_wth_persistent_storage + vdu_instantiation_volumes_list = [] + disk_list = [] + expected_persist_root_disk = { + "persistent-root-volume": { + "image_id": "ubuntu20.04", + "size": "10", + } + } + expected_disk_list = [ + { + "image_id": "ubuntu20.04", + "size": "10", + }, + ] + persist_root_disk = self.ns.find_persistent_root_volumes( + vnfd, target_vdu, vdu_instantiation_volumes_list, disk_list + ) + self.assertEqual(persist_root_disk, expected_persist_root_disk) + self.assertEqual(disk_list, expected_disk_list) + self.assertEqual(len(disk_list), 1) + + def test_find_persistent_root_volumes_target_vdu_mismatch(self): + """Find persistent root volume, target vdu name is not matching.""" + vnfd = deepcopy(vnfd_wth_persistent_storage) + vnfd["vdu"][0]["name"] = "Several_Volumes-VM" + target_vdu = target_vdu_wth_persistent_storage + vdu_instantiation_volumes_list = [] + disk_list = [] + result = self.ns.find_persistent_root_volumes( + vnfd, target_vdu, vdu_instantiation_volumes_list, disk_list + ) + self.assertEqual(result, None) + self.assertEqual(disk_list, []) + self.assertEqual(len(disk_list), 0) + + def test_find_persistent_root_volumes_with_instantiation_vol_list(self): + """Find persistent root volume, existing volume needs to be used.""" + vnfd = deepcopy(vnfd_wth_persistent_storage) + target_vdu = target_vdu_wth_persistent_storage + vdu_instantiation_volumes_list = [ + { + "vim-volume-id": vim_volume_id, + "name": "persistent-root-volume", + } + ] + disk_list = [] + expected_persist_root_disk = { + "persistent-root-volume": { + "vim_volume_id": vim_volume_id, + "image_id": "ubuntu20.04", + }, + } + expected_disk_list = [ + { + "vim_volume_id": vim_volume_id, + "image_id": "ubuntu20.04", + }, + ] + persist_root_disk = self.ns.find_persistent_root_volumes( + vnfd, target_vdu, vdu_instantiation_volumes_list, disk_list + ) + self.assertEqual(persist_root_disk, expected_persist_root_disk) + self.assertEqual(disk_list, expected_disk_list) + self.assertEqual(len(disk_list), 1) + + def test_find_persistent_root_volumes_invalid_instantiation_params(self): + """Find persistent root volume, existing volume id keyword is invalid.""" + vnfd = deepcopy(vnfd_wth_persistent_storage) + target_vdu = target_vdu_wth_persistent_storage + vdu_instantiation_volumes_list = [ + { + "volume-id": vim_volume_id, + "name": "persistent-root-volume", + } + ] + disk_list = [] + with self.assertRaises(KeyError): + self.ns.find_persistent_root_volumes( + vnfd, target_vdu, vdu_instantiation_volumes_list, disk_list + ) + + self.assertEqual(disk_list, []) + self.assertEqual(len(disk_list), 0) + + def test_find_persistent_volumes_vdu_wth_persistent_root_disk_wthout_inst_vol_list( + self, + ): + """Find persistent ordinary volume, there is persistent root disk and instatiation volume list is empty.""" + persistent_root_disk = { + "persistent-root-volume": { + "image_id": "ubuntu20.04", + "size": "10", + } + } + + target_vdu = target_vdu_wth_persistent_storage + vdu_instantiation_volumes_list = [] + disk_list = [ + { + "image_id": "ubuntu20.04", + "size": "10", + }, + ] + + expected_disk_list = [ + { + "image_id": "ubuntu20.04", + "size": "10", + }, + { + "size": "10", + }, + ] + self.ns.find_persistent_volumes( + persistent_root_disk, target_vdu, vdu_instantiation_volumes_list, disk_list + ) + self.assertEqual(disk_list, expected_disk_list) + + def test_find_persistent_volumes_vdu_wth_inst_vol_list(self): + """Find persistent ordinary volume, vim-volume-id is given as instantiation parameter.""" + persistent_root_disk = { + "persistent-root-volume": { + "image_id": "ubuntu20.04", + "size": "10", + } + } + vdu_instantiation_volumes_list = [ + { + "vim-volume-id": vim_volume_id, + "name": "persistent-volume2", + } + ] + target_vdu = target_vdu_wth_persistent_storage + disk_list = [ + { + "image_id": "ubuntu20.04", + "size": "10", + }, + ] + expected_disk_list = [ + { + "image_id": "ubuntu20.04", + "size": "10", + }, + { + "vim_volume_id": vim_volume_id, + }, + ] + self.ns.find_persistent_volumes( + persistent_root_disk, target_vdu, vdu_instantiation_volumes_list, disk_list + ) + self.assertEqual(disk_list, expected_disk_list) + + def test_find_persistent_volumes_vdu_wthout_persistent_storage(self): + """Find persistent ordinary volume, there is not any persistent disk.""" + persistent_root_disk = {} + vdu_instantiation_volumes_list = [] + target_vdu = target_vdu_wthout_persistent_storage + disk_list = [] + self.ns.find_persistent_volumes( + persistent_root_disk, target_vdu, vdu_instantiation_volumes_list, disk_list + ) + self.assertEqual(disk_list, disk_list) + + def test_find_persistent_volumes_vdu_wth_persistent_root_disk_wthout_ordinary_disk( + self, + ): + """There is persistent root disk, but there is not ordinary persistent disk.""" + persistent_root_disk = { + "persistent-root-volume": { + "image_id": "ubuntu20.04", + "size": "10", + } + } + vdu_instantiation_volumes_list = [] + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + target_vdu["virtual-storages"] = [ + { + "id": "persistent-root-volume", + "size-of-storage": "10", + "type-of-storage": "persistent-storage:persistent-storage", + }, + { + "id": "ephemeral-volume", + "size-of-storage": "1", + "type-of-storage": "etsi-nfv-descriptors:ephemeral-storage", + }, + ] + disk_list = [ + { + "image_id": "ubuntu20.04", + "size": "10", + }, + ] + self.ns.find_persistent_volumes( + persistent_root_disk, target_vdu, vdu_instantiation_volumes_list, disk_list + ) + self.assertEqual(disk_list, disk_list) + + def test_find_persistent_volumes_wth_inst_vol_list_disk_id_mismatch(self): + """Find persistent ordinary volume, volume id is not persistent_root_disk dict, + vim-volume-id is given as instantiation parameter but disk id is not matching.""" + vdu_instantiation_volumes_list = [ + { + "vim-volume-id": vim_volume_id, + "name": "persistent-volume3", + } + ] + persistent_root_disk = { + "persistent-root-volume": { + "image_id": "ubuntu20.04", + "size": "10", + } + } + disk_list = [ + { + "image_id": "ubuntu20.04", + "size": "10", + }, + ] + expected_disk_list = [ + { + "image_id": "ubuntu20.04", + "size": "10", + }, + { + "size": "10", + }, + ] + + target_vdu = target_vdu_wth_persistent_storage + self.ns.find_persistent_volumes( + persistent_root_disk, target_vdu, vdu_instantiation_volumes_list, disk_list + ) + self.assertEqual(disk_list, expected_disk_list) + + def test_sort_vdu_interfaces_position_all_wth_positions(self): + """Interfaces are sorted according to position, all have positions.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + target_vdu["interfaces"] = [ + { + "name": "vdu-eth1", + "ns-vld-id": "datanet", + "position": 2, + }, + { + "name": "vdu-eth0", + "ns-vld-id": "mgmtnet", + "position": 1, + }, + ] + sorted_interfaces = [ + { + "name": "vdu-eth0", + "ns-vld-id": "mgmtnet", + "position": 1, + }, + { + "name": "vdu-eth1", + "ns-vld-id": "datanet", + "position": 2, + }, + ] + self.ns._sort_vdu_interfaces(target_vdu) + self.assertEqual(target_vdu["interfaces"], sorted_interfaces) + + def test_sort_vdu_interfaces_position_some_wth_position(self): + """Interfaces are sorted according to position, some of them have positions.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + target_vdu["interfaces"] = [ + { + "name": "vdu-eth0", + "ns-vld-id": "mgmtnet", + }, + { + "name": "vdu-eth1", + "ns-vld-id": "datanet", + "position": 1, + }, + ] + sorted_interfaces = [ + { + "name": "vdu-eth1", + "ns-vld-id": "datanet", + "position": 1, + }, + { + "name": "vdu-eth0", + "ns-vld-id": "mgmtnet", + }, + ] + self.ns._sort_vdu_interfaces(target_vdu) + self.assertEqual(target_vdu["interfaces"], sorted_interfaces) + + def test_sort_vdu_interfaces_position_empty_interface_list(self): + """Interface list is empty.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + target_vdu["interfaces"] = [] + sorted_interfaces = [] + self.ns._sort_vdu_interfaces(target_vdu) + self.assertEqual(target_vdu["interfaces"], sorted_interfaces) + + def test_partially_locate_vdu_interfaces(self): + """Some interfaces have positions.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + target_vdu["interfaces"] = [ + { + "name": "vdu-eth1", + "ns-vld-id": "net1", + }, + {"name": "vdu-eth2", "ns-vld-id": "net2", "position": 3}, + { + "name": "vdu-eth3", + "ns-vld-id": "mgmtnet", + }, + { + "name": "vdu-eth1", + "ns-vld-id": "datanet", + "position": 1, + }, + ] + self.ns._partially_locate_vdu_interfaces(target_vdu) + self.assertDictEqual( + target_vdu["interfaces"][0], + { + "name": "vdu-eth1", + "ns-vld-id": "datanet", + "position": 1, + }, + ) + self.assertDictEqual( + target_vdu["interfaces"][2], + {"name": "vdu-eth2", "ns-vld-id": "net2", "position": 3}, + ) + + def test_partially_locate_vdu_interfaces_position_start_from_0(self): + """Some interfaces have positions, position start from 0.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + target_vdu["interfaces"] = [ + { + "name": "vdu-eth1", + "ns-vld-id": "net1", + }, + {"name": "vdu-eth2", "ns-vld-id": "net2", "position": 3}, + { + "name": "vdu-eth3", + "ns-vld-id": "mgmtnet", + }, + { + "name": "vdu-eth1", + "ns-vld-id": "datanet", + "position": 0, + }, + ] + self.ns._partially_locate_vdu_interfaces(target_vdu) + self.assertDictEqual( + target_vdu["interfaces"][0], + { + "name": "vdu-eth1", + "ns-vld-id": "datanet", + "position": 0, + }, + ) + self.assertDictEqual( + target_vdu["interfaces"][3], + {"name": "vdu-eth2", "ns-vld-id": "net2", "position": 3}, + ) + + def test_partially_locate_vdu_interfaces_wthout_position(self): + """Interfaces do not have positions.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + target_vdu["interfaces"] = interfaces_wthout_positions + expected_result = deepcopy(target_vdu["interfaces"]) + self.ns._partially_locate_vdu_interfaces(target_vdu) + self.assertEqual(target_vdu["interfaces"], expected_result) + + def test_partially_locate_vdu_interfaces_all_has_position(self): + """All interfaces have position.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + target_vdu["interfaces"] = interfaces_wth_all_positions + expected_interfaces = [ + { + "name": "vdu-eth2", + "ns-vld-id": "net2", + "position": 0, + }, + { + "name": "vdu-eth3", + "ns-vld-id": "mgmtnet", + "position": 1, + }, + { + "name": "vdu-eth1", + "ns-vld-id": "net1", + "position": 2, + }, + ] + self.ns._partially_locate_vdu_interfaces(target_vdu) + self.assertEqual(target_vdu["interfaces"], expected_interfaces) + + @patch("osm_ng_ro.ns.Ns._get_cloud_init") + @patch("osm_ng_ro.ns.Ns._parse_jinja2") + def test_prepare_vdu_cloud_init(self, mock_parse_jinja2, mock_get_cloud_init): + """Target_vdu has cloud-init and boot-data-drive.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + target_vdu["cloud-init"] = "sample-cloud-init-path" + target_vdu["boot-data-drive"] = "vda" + vdu2cloud_init = {} + mock_get_cloud_init.return_value = cloud_init_content + mock_parse_jinja2.return_value = user_data + expected_result = { + "user-data": user_data, + "boot-data-drive": "vda", + } + result = self.ns._prepare_vdu_cloud_init(target_vdu, vdu2cloud_init, db, fs) + self.assertDictEqual(result, expected_result) + mock_get_cloud_init.assert_called_once_with( + db=db, fs=fs, location="sample-cloud-init-path" + ) + mock_parse_jinja2.assert_called_once_with( + cloud_init_content=cloud_init_content, + params=None, + context="sample-cloud-init-path", + ) + + @patch("osm_ng_ro.ns.Ns._get_cloud_init") + @patch("osm_ng_ro.ns.Ns._parse_jinja2") + def test_prepare_vdu_cloud_init_get_cloud_init_raise_exception( + self, mock_parse_jinja2, mock_get_cloud_init + ): + """Target_vdu has cloud-init and boot-data-drive, get_cloud_init method raises exception.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + target_vdu["cloud-init"] = "sample-cloud-init-path" + target_vdu["boot-data-drive"] = "vda" + vdu2cloud_init = {} + mock_get_cloud_init.side_effect = NsException( + "Mismatch descriptor for cloud init." + ) + + with self.assertRaises(NsException) as err: + self.ns._prepare_vdu_cloud_init(target_vdu, vdu2cloud_init, db, fs) + self.assertEqual(str(err.exception), "Mismatch descriptor for cloud init.") + + mock_get_cloud_init.assert_called_once_with( + db=db, fs=fs, location="sample-cloud-init-path" + ) + mock_parse_jinja2.assert_not_called() + + @patch("osm_ng_ro.ns.Ns._get_cloud_init") + @patch("osm_ng_ro.ns.Ns._parse_jinja2") + def test_prepare_vdu_cloud_init_parse_jinja2_raise_exception( + self, mock_parse_jinja2, mock_get_cloud_init + ): + """Target_vdu has cloud-init and boot-data-drive, parse_jinja2 method raises exception.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + target_vdu["cloud-init"] = "sample-cloud-init-path" + target_vdu["boot-data-drive"] = "vda" + vdu2cloud_init = {} + mock_get_cloud_init.return_value = cloud_init_content + mock_parse_jinja2.side_effect = NsException("Error parsing cloud-init content.") + + with self.assertRaises(NsException) as err: + self.ns._prepare_vdu_cloud_init(target_vdu, vdu2cloud_init, db, fs) + self.assertEqual(str(err.exception), "Error parsing cloud-init content.") + mock_get_cloud_init.assert_called_once_with( + db=db, fs=fs, location="sample-cloud-init-path" + ) + mock_parse_jinja2.assert_called_once_with( + cloud_init_content=cloud_init_content, + params=None, + context="sample-cloud-init-path", + ) + + @patch("osm_ng_ro.ns.Ns._get_cloud_init") + @patch("osm_ng_ro.ns.Ns._parse_jinja2") + def test_prepare_vdu_cloud_init_vdu_wthout_boot_data_drive( + self, mock_parse_jinja2, mock_get_cloud_init + ): + """Target_vdu has cloud-init but do not have boot-data-drive.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + target_vdu["cloud-init"] = "sample-cloud-init-path" + vdu2cloud_init = {} + mock_get_cloud_init.return_value = cloud_init_content + mock_parse_jinja2.return_value = user_data + expected_result = { + "user-data": user_data, + } + result = self.ns._prepare_vdu_cloud_init(target_vdu, vdu2cloud_init, db, fs) + self.assertDictEqual(result, expected_result) + mock_get_cloud_init.assert_called_once_with( + db=db, fs=fs, location="sample-cloud-init-path" + ) + mock_parse_jinja2.assert_called_once_with( + cloud_init_content=cloud_init_content, + params=None, + context="sample-cloud-init-path", + ) + + @patch("osm_ng_ro.ns.Ns._get_cloud_init") + @patch("osm_ng_ro.ns.Ns._parse_jinja2") + def test_prepare_vdu_cloud_init_exists_in_vdu2cloud_init( + self, mock_parse_jinja2, mock_get_cloud_init + ): + """Target_vdu has cloud-init, vdu2cloud_init dict has cloud-init_content.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + target_vdu["cloud-init"] = "sample-cloud-init-path" + target_vdu["boot-data-drive"] = "vda" + vdu2cloud_init = {"sample-cloud-init-path": cloud_init_content} + mock_parse_jinja2.return_value = user_data + expected_result = { + "user-data": user_data, + "boot-data-drive": "vda", + } + result = self.ns._prepare_vdu_cloud_init(target_vdu, vdu2cloud_init, db, fs) + self.assertDictEqual(result, expected_result) + mock_get_cloud_init.assert_not_called() + mock_parse_jinja2.assert_called_once_with( + cloud_init_content=cloud_init_content, + params=None, + context="sample-cloud-init-path", + ) + + @patch("osm_ng_ro.ns.Ns._get_cloud_init") + @patch("osm_ng_ro.ns.Ns._parse_jinja2") + def test_prepare_vdu_cloud_init_no_cloud_init( + self, mock_parse_jinja2, mock_get_cloud_init + ): + """Target_vdu do not have cloud-init.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + target_vdu["boot-data-drive"] = "vda" + vdu2cloud_init = {} + expected_result = { + "boot-data-drive": "vda", + } + result = self.ns._prepare_vdu_cloud_init(target_vdu, vdu2cloud_init, db, fs) + self.assertDictEqual(result, expected_result) + mock_get_cloud_init.assert_not_called() + mock_parse_jinja2.assert_not_called() + + def test_check_vld_information_of_interfaces_ns_vld_vnf_vld_both_exist(self): + """ns_vld and vnf_vld both exist.""" + interface = { + "name": "vdu-eth0", + "ns-vld-id": "mgmtnet", + "vnf-vld-id": "mgmt_cp_int", + } + expected_result = f"{ns_preffix}:vld.mgmtnet" + result = self.ns._check_vld_information_of_interfaces( + interface, ns_preffix, vnf_preffix + ) + self.assertEqual(result, expected_result) + + def test_check_vld_information_of_interfaces_empty_interfaces(self): + """Interface dict is empty.""" + interface = {} + result = self.ns._check_vld_information_of_interfaces( + interface, ns_preffix, vnf_preffix + ) + self.assertEqual(result, "") + + def test_check_vld_information_of_interfaces_has_only_vnf_vld(self): + """Interface dict has only vnf_vld.""" + interface = { + "name": "vdu-eth0", + "vnf-vld-id": "mgmt_cp_int", + } + expected_result = f"{vnf_preffix}:vld.mgmt_cp_int" + result = self.ns._check_vld_information_of_interfaces( + interface, ns_preffix, vnf_preffix + ) + self.assertEqual(result, expected_result) + + def test_check_vld_information_of_interfaces_has_vnf_vld_wthout_vnf_prefix( + self, + ): + """Interface dict has only vnf_vld but vnf_preffix does not exist.""" + interface = { + "name": "vdu-eth0", + "vnf-vld-id": "mgmt_cp_int", + } + vnf_preffix = None + with self.assertRaises(Exception) as err: + self.ns._check_vld_information_of_interfaces( + interface, ns_preffix, vnf_preffix + ) + self.assertEqual(type(err), TypeError) + + def test_prepare_interface_port_security_has_security_details(self): + """Interface dict has port security details.""" + interface = { + "name": "vdu-eth0", + "ns-vld-id": "mgmtnet", + "vnf-vld-id": "mgmt_cp_int", + "port-security-enabled": True, + "port-security-disable-strategy": "allow-address-pairs", + } + expected_interface = { + "name": "vdu-eth0", + "ns-vld-id": "mgmtnet", + "vnf-vld-id": "mgmt_cp_int", + "port_security": True, + "port_security_disable_strategy": "allow-address-pairs", + } + self.ns._prepare_interface_port_security(interface) + self.assertDictEqual(interface, expected_interface) + + def test_prepare_interface_port_security_empty_interfaces(self): + """Interface dict is empty.""" + interface = {} + expected_interface = {} + self.ns._prepare_interface_port_security(interface) + self.assertDictEqual(interface, expected_interface) + + def test_prepare_interface_port_security_wthout_port_security(self): + """Interface dict does not have port security details.""" + interface = { + "name": "vdu-eth0", + "ns-vld-id": "mgmtnet", + "vnf-vld-id": "mgmt_cp_int", + } + expected_interface = { + "name": "vdu-eth0", + "ns-vld-id": "mgmtnet", + "vnf-vld-id": "mgmt_cp_int", + } + self.ns._prepare_interface_port_security(interface) + self.assertDictEqual(interface, expected_interface) + + def test_create_net_item_of_interface_floating_ip_port_security(self): + """Interface dict has floating ip, port-security details.""" + interface = { + "name": "vdu-eth0", + "vcpi": "sample_vcpi", + "port_security": True, + "port_security_disable_strategy": "allow-address-pairs", + "floating_ip": "10.1.1.12", + "ns-vld-id": "mgmtnet", + "vnf-vld-id": "mgmt_cp_int", + } + net_text = f"{ns_preffix}" + expected_net_item = { + "name": "vdu-eth0", + "port_security": True, + "port_security_disable_strategy": "allow-address-pairs", + "floating_ip": "10.1.1.12", + "net_id": f"TASK-{ns_preffix}", + "type": "virtual", + } + result = self.ns._create_net_item_of_interface(interface, net_text) + self.assertDictEqual(result, expected_net_item) + + def test_create_net_item_of_interface_invalid_net_text(self): + """net-text is invalid.""" + interface = { + "name": "vdu-eth0", + "vcpi": "sample_vcpi", + "port_security": True, + "port_security_disable_strategy": "allow-address-pairs", + "floating_ip": "10.1.1.12", + "ns-vld-id": "mgmtnet", + "vnf-vld-id": "mgmt_cp_int", + } + net_text = None + with self.assertRaises(TypeError): + self.ns._create_net_item_of_interface(interface, net_text) + + def test_create_net_item_of_interface_empty_interface(self): + """Interface dict is empty.""" + interface = {} + net_text = ns_preffix + expected_net_item = { + "net_id": f"TASK-{ns_preffix}", + "type": "virtual", + } + result = self.ns._create_net_item_of_interface(interface, net_text) + self.assertDictEqual(result, expected_net_item) + + @patch("osm_ng_ro.ns.deep_get") + def test_prepare_type_of_interface_type_sriov(self, mock_deep_get): + """Interface type is SR-IOV.""" + interface = { + "name": "vdu-eth0", + "vcpi": "sample_vcpi", + "port_security": True, + "port_security_disable_strategy": "allow-address-pairs", + "floating_ip": "10.1.1.12", + "ns-vld-id": "mgmtnet", + "vnf-vld-id": "mgmt_cp_int", + "type": "SR-IOV", + } + mock_deep_get.return_value = "SR-IOV" + net_text = ns_preffix + net_item = {} + expected_net_item = { + "use": "data", + "model": "SR-IOV", + "type": "SR-IOV", + } + self.ns._prepare_type_of_interface( + interface, tasks_by_target_record_id, net_text, net_item + ) + self.assertDictEqual(net_item, expected_net_item) + self.assertEqual( + "data", + tasks_by_target_record_id[net_text]["extra_dict"]["params"]["net_type"], + ) + mock_deep_get.assert_called_once_with( + tasks_by_target_record_id, net_text, "extra_dict", "params", "net_type" + ) + + @patch("osm_ng_ro.ns.deep_get") + def test_prepare_type_of_interface_type_pic_passthrough_deep_get_return_empty_dict( + self, mock_deep_get + ): + """Interface type is PCI-PASSTHROUGH, deep_get method return empty dict.""" + interface = { + "name": "vdu-eth0", + "vcpi": "sample_vcpi", + "port_security": True, + "port_security_disable_strategy": "allow-address-pairs", + "floating_ip": "10.1.1.12", + "ns-vld-id": "mgmtnet", + "vnf-vld-id": "mgmt_cp_int", + "type": "PCI-PASSTHROUGH", + } + mock_deep_get.return_value = {} + tasks_by_target_record_id = {} + net_text = ns_preffix + net_item = {} + expected_net_item = { + "use": "data", + "model": "PCI-PASSTHROUGH", + "type": "PCI-PASSTHROUGH", + } + self.ns._prepare_type_of_interface( + interface, tasks_by_target_record_id, net_text, net_item + ) + self.assertDictEqual(net_item, expected_net_item) + mock_deep_get.assert_called_once_with( + tasks_by_target_record_id, net_text, "extra_dict", "params", "net_type" + ) + + @patch("osm_ng_ro.ns.deep_get") + def test_prepare_type_of_interface_type_mgmt(self, mock_deep_get): + """Interface type is mgmt.""" + interface = { + "name": "vdu-eth0", + "vcpi": "sample_vcpi", + "port_security": True, + "port_security_disable_strategy": "allow-address-pairs", + "floating_ip": "10.1.1.12", + "ns-vld-id": "mgmtnet", + "vnf-vld-id": "mgmt_cp_int", + "type": "OM-MGMT", + } + tasks_by_target_record_id = {} + net_text = ns_preffix + net_item = {} + expected_net_item = { + "use": "mgmt", + } + self.ns._prepare_type_of_interface( + interface, tasks_by_target_record_id, net_text, net_item + ) + self.assertDictEqual(net_item, expected_net_item) + mock_deep_get.assert_not_called() + + @patch("osm_ng_ro.ns.deep_get") + def test_prepare_type_of_interface_type_bridge(self, mock_deep_get): + """Interface type is bridge.""" + interface = { + "name": "vdu-eth0", + "vcpi": "sample_vcpi", + "port_security": True, + "port_security_disable_strategy": "allow-address-pairs", + "floating_ip": "10.1.1.12", + "ns-vld-id": "mgmtnet", + "vnf-vld-id": "mgmt_cp_int", + } + tasks_by_target_record_id = {} + net_text = ns_preffix + net_item = {} + expected_net_item = { + "use": "bridge", + "model": None, + } + self.ns._prepare_type_of_interface( + interface, tasks_by_target_record_id, net_text, net_item + ) + self.assertDictEqual(net_item, expected_net_item) + mock_deep_get.assert_not_called() + + @patch("osm_ng_ro.ns.Ns._check_vld_information_of_interfaces") + @patch("osm_ng_ro.ns.Ns._prepare_interface_port_security") + @patch("osm_ng_ro.ns.Ns._create_net_item_of_interface") + @patch("osm_ng_ro.ns.Ns._prepare_type_of_interface") + def test_prepare_vdu_interfaces( + self, + mock_type_of_interface, + mock_item_of_interface, + mock_port_security, + mock_vld_information_of_interface, + ): + """Prepare vdu interfaces successfully.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + interface_1 = { + "name": "vdu-eth1", + "ns-vld-id": "net1", + "ip-address": "13.2.12.31", + "mgmt-interface": True, + } + interface_2 = { + "name": "vdu-eth2", + "vnf-vld-id": "net2", + "mac-address": "d0:94:66:ed:fc:e2", + } + interface_3 = { + "name": "vdu-eth3", + "ns-vld-id": "mgmtnet", + } + target_vdu["interfaces"] = [interface_1, interface_2, interface_3] + extra_dict = { + "params": "test_params", + "find_params": "test_find_params", + "depends_on": [], + } + + net_text_1 = f"{ns_preffix}:net1" + net_text_2 = f"{vnf_preffix}:net2" + net_text_3 = f"{ns_preffix}:mgmtnet" + net_item_1 = { + "name": "vdu-eth1", + "net_id": f"TASK-{ns_preffix}", + "type": "virtual", + } + net_item_2 = { + "name": "vdu-eth2", + "net_id": f"TASK-{ns_preffix}", + "type": "virtual", + } + net_item_3 = { + "name": "vdu-eth3", + "net_id": f"TASK-{ns_preffix}", + "type": "virtual", + } + mock_item_of_interface.side_effect = [net_item_1, net_item_2, net_item_3] + mock_vld_information_of_interface.side_effect = [ + net_text_1, + net_text_2, + net_text_3, + ] + net_list = [] + expected_extra_dict = { + "params": "test_params", + "find_params": "test_find_params", + "depends_on": [net_text_1, net_text_2, net_text_3], + "mgmt_vdu_interface": 0, + } + updated_net_item1 = deepcopy(net_item_1) + updated_net_item1.update({"ip_address": "13.2.12.31"}) + updated_net_item2 = deepcopy(net_item_2) + updated_net_item2.update({"mac_address": "d0:94:66:ed:fc:e2"}) + expected_net_list = [updated_net_item1, updated_net_item2, net_item_3] + self.ns._prepare_vdu_interfaces( + target_vdu, + extra_dict, + ns_preffix, + vnf_preffix, + self.logger, + tasks_by_target_record_id, + net_list, + ) + _call_mock_vld_information_of_interface = ( + mock_vld_information_of_interface.call_args_list + ) + self.assertEqual( + _call_mock_vld_information_of_interface[0][0], + (interface_1, ns_preffix, vnf_preffix), + ) + self.assertEqual( + _call_mock_vld_information_of_interface[1][0], + (interface_2, ns_preffix, vnf_preffix), + ) + self.assertEqual( + _call_mock_vld_information_of_interface[2][0], + (interface_3, ns_preffix, vnf_preffix), + ) + + _call_mock_port_security = mock_port_security.call_args_list + self.assertEqual(_call_mock_port_security[0].args[0], interface_1) + self.assertEqual(_call_mock_port_security[1].args[0], interface_2) + self.assertEqual(_call_mock_port_security[2].args[0], interface_3) + + _call_mock_item_of_interface = mock_item_of_interface.call_args_list + self.assertEqual(_call_mock_item_of_interface[0][0], (interface_1, net_text_1)) + self.assertEqual(_call_mock_item_of_interface[1][0], (interface_2, net_text_2)) + self.assertEqual(_call_mock_item_of_interface[2][0], (interface_3, net_text_3)) + + _call_mock_type_of_interface = mock_type_of_interface.call_args_list + self.assertEqual( + _call_mock_type_of_interface[0][0], + (interface_1, tasks_by_target_record_id, net_text_1, net_item_1), + ) + self.assertEqual( + _call_mock_type_of_interface[1][0], + (interface_2, tasks_by_target_record_id, net_text_2, net_item_2), + ) + self.assertEqual( + _call_mock_type_of_interface[2][0], + (interface_3, tasks_by_target_record_id, net_text_3, net_item_3), + ) + self.assertEqual(net_list, expected_net_list) + self.assertEqual(extra_dict, expected_extra_dict) + self.logger.error.assert_not_called() + + @patch("osm_ng_ro.ns.Ns._check_vld_information_of_interfaces") + @patch("osm_ng_ro.ns.Ns._prepare_interface_port_security") + @patch("osm_ng_ro.ns.Ns._create_net_item_of_interface") + @patch("osm_ng_ro.ns.Ns._prepare_type_of_interface") + def test_prepare_vdu_interfaces_create_net_item_raise_exception( + self, + mock_type_of_interface, + mock_item_of_interface, + mock_port_security, + mock_vld_information_of_interface, + ): + """Prepare vdu interfaces, create_net_item_of_interface method raise exception.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + interface_1 = { + "name": "vdu-eth1", + "ns-vld-id": "net1", + "ip-address": "13.2.12.31", + "mgmt-interface": True, + } + interface_2 = { + "name": "vdu-eth2", + "vnf-vld-id": "net2", + "mac-address": "d0:94:66:ed:fc:e2", + } + interface_3 = { + "name": "vdu-eth3", + "ns-vld-id": "mgmtnet", + } + target_vdu["interfaces"] = [interface_1, interface_2, interface_3] + extra_dict = { + "params": "test_params", + "find_params": "test_find_params", + "depends_on": [], + } + net_text_1 = f"{ns_preffix}:net1" + mock_item_of_interface.side_effect = [TypeError, TypeError, TypeError] + + mock_vld_information_of_interface.side_effect = [net_text_1] + net_list = [] + expected_extra_dict = { + "params": "test_params", + "find_params": "test_find_params", + "depends_on": [net_text_1], + } + with self.assertRaises(TypeError): + self.ns._prepare_vdu_interfaces( + target_vdu, + extra_dict, + ns_preffix, + vnf_preffix, + self.logger, + tasks_by_target_record_id, + net_list, + ) + + _call_mock_vld_information_of_interface = ( + mock_vld_information_of_interface.call_args_list + ) + self.assertEqual( + _call_mock_vld_information_of_interface[0][0], + (interface_1, ns_preffix, vnf_preffix), + ) + + _call_mock_port_security = mock_port_security.call_args_list + self.assertEqual(_call_mock_port_security[0].args[0], interface_1) + + _call_mock_item_of_interface = mock_item_of_interface.call_args_list + self.assertEqual(_call_mock_item_of_interface[0][0], (interface_1, net_text_1)) + + mock_type_of_interface.assert_not_called() + self.logger.error.assert_not_called() + self.assertEqual(net_list, []) + self.assertEqual(extra_dict, expected_extra_dict) + + @patch("osm_ng_ro.ns.Ns._check_vld_information_of_interfaces") + @patch("osm_ng_ro.ns.Ns._prepare_interface_port_security") + @patch("osm_ng_ro.ns.Ns._create_net_item_of_interface") + @patch("osm_ng_ro.ns.Ns._prepare_type_of_interface") + def test_prepare_vdu_interfaces_vld_information_is_empty( + self, + mock_type_of_interface, + mock_item_of_interface, + mock_port_security, + mock_vld_information_of_interface, + ): + """Prepare vdu interfaces, check_vld_information_of_interface method returns empty result.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + interface_1 = { + "name": "vdu-eth1", + "ns-vld-id": "net1", + "ip-address": "13.2.12.31", + "mgmt-interface": True, + } + interface_2 = { + "name": "vdu-eth2", + "vnf-vld-id": "net2", + "mac-address": "d0:94:66:ed:fc:e2", + } + interface_3 = { + "name": "vdu-eth3", + "ns-vld-id": "mgmtnet", + } + target_vdu["interfaces"] = [interface_1, interface_2, interface_3] + extra_dict = { + "params": "test_params", + "find_params": "test_find_params", + "depends_on": [], + } + mock_vld_information_of_interface.side_effect = ["", "", ""] + net_list = [] + self.ns._prepare_vdu_interfaces( + target_vdu, + extra_dict, + ns_preffix, + vnf_preffix, + self.logger, + tasks_by_target_record_id, + net_list, + ) + + _call_mock_vld_information_of_interface = ( + mock_vld_information_of_interface.call_args_list + ) + self.assertEqual( + _call_mock_vld_information_of_interface[0][0], + (interface_1, ns_preffix, vnf_preffix), + ) + self.assertEqual( + _call_mock_vld_information_of_interface[1][0], + (interface_2, ns_preffix, vnf_preffix), + ) + self.assertEqual( + _call_mock_vld_information_of_interface[2][0], + (interface_3, ns_preffix, vnf_preffix), + ) + + _call_logger = self.logger.error.call_args_list + self.assertEqual( + _call_logger[0][0], + ("Interface 0 from vdu several_volumes-VM not connected to any vld",), + ) + self.assertEqual( + _call_logger[1][0], + ("Interface 1 from vdu several_volumes-VM not connected to any vld",), + ) + self.assertEqual( + _call_logger[2][0], + ("Interface 2 from vdu several_volumes-VM not connected to any vld",), + ) + self.assertEqual(net_list, []) + self.assertEqual( + extra_dict, + { + "params": "test_params", + "find_params": "test_find_params", + "depends_on": [], + }, + ) + + mock_item_of_interface.assert_not_called() + mock_port_security.assert_not_called() + mock_type_of_interface.assert_not_called() + + @patch("osm_ng_ro.ns.Ns._check_vld_information_of_interfaces") + @patch("osm_ng_ro.ns.Ns._prepare_interface_port_security") + @patch("osm_ng_ro.ns.Ns._create_net_item_of_interface") + @patch("osm_ng_ro.ns.Ns._prepare_type_of_interface") + def test_prepare_vdu_interfaces_empty_interface_list( + self, + mock_type_of_interface, + mock_item_of_interface, + mock_port_security, + mock_vld_information_of_interface, + ): + """Prepare vdu interfaces, interface list is empty.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + target_vdu["interfaces"] = [] + extra_dict = {} + net_list = [] + self.ns._prepare_vdu_interfaces( + target_vdu, + extra_dict, + ns_preffix, + vnf_preffix, + self.logger, + tasks_by_target_record_id, + net_list, + ) + mock_type_of_interface.assert_not_called() + mock_vld_information_of_interface.assert_not_called() + mock_item_of_interface.assert_not_called() + mock_port_security.assert_not_called() + + def test_prepare_vdu_ssh_keys(self): + """Target_vdu has ssh-keys and ro_nsr_public_key exists.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + target_vdu["ssh-keys"] = ["sample-ssh-key"] + ro_nsr_public_key = {"public_key": "path_of_public_key"} + target_vdu["ssh-access-required"] = True + cloud_config = {} + expected_cloud_config = { + "key-pairs": ["sample-ssh-key", {"public_key": "path_of_public_key"}] + } + self.ns._prepare_vdu_ssh_keys(target_vdu, ro_nsr_public_key, cloud_config) + self.assertDictEqual(cloud_config, expected_cloud_config) + + def test_prepare_vdu_ssh_keys_target_vdu_wthout_ssh_keys(self): + """Target_vdu does not have ssh-keys.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + ro_nsr_public_key = {"public_key": "path_of_public_key"} + target_vdu["ssh-access-required"] = True + cloud_config = {} + expected_cloud_config = {"key-pairs": [{"public_key": "path_of_public_key"}]} + self.ns._prepare_vdu_ssh_keys(target_vdu, ro_nsr_public_key, cloud_config) + self.assertDictEqual(cloud_config, expected_cloud_config) + + def test_prepare_vdu_ssh_keys_ssh_access_is_not_required(self): + """Target_vdu has ssh-keys, ssh-access is not required.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + target_vdu["ssh-keys"] = ["sample-ssh-key"] + ro_nsr_public_key = {"public_key": "path_of_public_key"} + target_vdu["ssh-access-required"] = False + cloud_config = {} + expected_cloud_config = {"key-pairs": ["sample-ssh-key"]} + self.ns._prepare_vdu_ssh_keys(target_vdu, ro_nsr_public_key, cloud_config) + self.assertDictEqual(cloud_config, expected_cloud_config) + + @patch("osm_ng_ro.ns.Ns._select_persistent_root_disk") + def test_add_persistent_root_disk_to_disk_list( + self, mock_select_persistent_root_disk + ): + """Add persistent root disk to disk_list""" + root_disk = { + "id": "persistent-root-volume", + "type-of-storage": "persistent-storage:persistent-storage", + "size-of-storage": "10", + } + mock_select_persistent_root_disk.return_value = root_disk + vnfd = deepcopy(vnfd_wth_persistent_storage) + vnfd["virtual-storage-desc"][1] = root_disk + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + persistent_root_disk = {} + disk_list = [] + expected_disk_list = [ + { + "image_id": "ubuntu20.04", + "size": "10", + } + ] + self.ns._add_persistent_root_disk_to_disk_list( + vnfd, target_vdu, persistent_root_disk, disk_list + ) + self.assertEqual(disk_list, expected_disk_list) + mock_select_persistent_root_disk.assert_called_once() + + @patch("osm_ng_ro.ns.Ns._select_persistent_root_disk") + def test_add_persistent_root_disk_to_disk_list_select_persistent_root_disk_raises( + self, mock_select_persistent_root_disk + ): + """Add persistent root disk to disk_list""" + root_disk = { + "id": "persistent-root-volume", + "type-of-storage": "persistent-storage:persistent-storage", + "size-of-storage": "10", + } + mock_select_persistent_root_disk.side_effect = AttributeError + vnfd = deepcopy(vnfd_wth_persistent_storage) + vnfd["virtual-storage-desc"][1] = root_disk + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + persistent_root_disk = {} + disk_list = [] + with self.assertRaises(AttributeError): + self.ns._add_persistent_root_disk_to_disk_list( + vnfd, target_vdu, persistent_root_disk, disk_list + ) + self.assertEqual(disk_list, []) + mock_select_persistent_root_disk.assert_called_once() + + def test_add_persistent_ordinary_disk_to_disk_list(self): + """Add persistent ordinary disk to disk_list""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + persistent_root_disk = { + "persistent-root-volume": { + "image_id": "ubuntu20.04", + "size": "10", + } + } + persistent_ordinary_disk = {} + disk_list = [] + expected_disk_list = [ + { + "size": "10", + } + ] + self.ns._add_persistent_ordinary_disks_to_disk_list( + target_vdu, persistent_root_disk, persistent_ordinary_disk, disk_list + ) + self.assertEqual(disk_list, expected_disk_list) + + def test_add_persistent_ordinary_disk_to_disk_list_vsd_id_in_root_disk_dict(self): + """Add persistent ordinary disk, vsd id is in root_disk dict.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + persistent_root_disk = { + "persistent-root-volume": { + "image_id": "ubuntu20.04", + "size": "10", + }, + "persistent-volume2": { + "size": "10", + }, + } + persistent_ordinary_disk = {} + disk_list = [] + + self.ns._add_persistent_ordinary_disks_to_disk_list( + target_vdu, persistent_root_disk, persistent_ordinary_disk, disk_list + ) + self.assertEqual(disk_list, []) + + @patch("osm_ng_ro.ns.Ns._select_persistent_root_disk") + def test_add_persistent_root_disk_to_disk_list_vnfd_wthout_persistent_storage( + self, mock_select_persistent_root_disk + ): + """VNFD does not have persistent storage.""" + vnfd = deepcopy(vnfd_wthout_persistent_storage) + target_vdu = deepcopy(target_vdu_wthout_persistent_storage) + mock_select_persistent_root_disk.return_value = None + persistent_root_disk = {} + disk_list = [] + self.ns._add_persistent_root_disk_to_disk_list( + vnfd, target_vdu, persistent_root_disk, disk_list + ) + self.assertEqual(disk_list, []) + self.assertEqual(mock_select_persistent_root_disk.call_count, 2) + + @patch("osm_ng_ro.ns.Ns._select_persistent_root_disk") + def test_add_persistent_root_disk_to_disk_list_wthout_persistent_root_disk( + self, mock_select_persistent_root_disk + ): + """Persistent_root_disk dict is empty.""" + vnfd = deepcopy(vnfd_wthout_persistent_storage) + target_vdu = deepcopy(target_vdu_wthout_persistent_storage) + mock_select_persistent_root_disk.return_value = None + persistent_root_disk = {} + disk_list = [] + self.ns._add_persistent_root_disk_to_disk_list( + vnfd, target_vdu, persistent_root_disk, disk_list + ) + self.assertEqual(disk_list, []) + self.assertEqual(mock_select_persistent_root_disk.call_count, 2) + + def test_prepare_vdu_affinity_group_list_invalid_extra_dict(self): + """Invalid extra dict.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + target_vdu["affinity-or-anti-affinity-group-id"] = "sample_affinity-group-id" + extra_dict = {} + ns_preffix = "nsrs:th47f48-9870-4169-b758-9732e1ff40f3" + with self.assertRaises(NsException) as err: + self.ns._prepare_vdu_affinity_group_list(target_vdu, extra_dict, ns_preffix) + self.assertEqual(str(err.exception), "Invalid extra_dict format.") + + def test_prepare_vdu_affinity_group_list_one_affinity_group(self): + """There is one affinity-group.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + target_vdu["affinity-or-anti-affinity-group-id"] = ["sample_affinity-group-id"] + extra_dict = {"depends_on": []} + ns_preffix = "nsrs:th47f48-9870-4169-b758-9732e1ff40f3" + affinity_group_txt = "nsrs:th47f48-9870-4169-b758-9732e1ff40f3:affinity-or-anti-affinity-group.sample_affinity-group-id" + expected_result = [{"affinity_group_id": "TASK-" + affinity_group_txt}] + expected_extra_dict = {"depends_on": [affinity_group_txt]} + result = self.ns._prepare_vdu_affinity_group_list( + target_vdu, extra_dict, ns_preffix + ) + self.assertDictEqual(extra_dict, expected_extra_dict) + self.assertEqual(result, expected_result) + + def test_prepare_vdu_affinity_group_list_several_affinity_groups(self): + """There are two affinity-groups.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + target_vdu["affinity-or-anti-affinity-group-id"] = [ + "affinity-group-id1", + "affinity-group-id2", + ] + extra_dict = {"depends_on": []} + ns_preffix = "nsrs:th47f48-9870-4169-b758-9732e1ff40f3" + affinity_group_txt1 = "nsrs:th47f48-9870-4169-b758-9732e1ff40f3:affinity-or-anti-affinity-group.affinity-group-id1" + affinity_group_txt2 = "nsrs:th47f48-9870-4169-b758-9732e1ff40f3:affinity-or-anti-affinity-group.affinity-group-id2" + expected_result = [ + {"affinity_group_id": "TASK-" + affinity_group_txt1}, + {"affinity_group_id": "TASK-" + affinity_group_txt2}, + ] + expected_extra_dict = {"depends_on": [affinity_group_txt1, affinity_group_txt2]} + result = self.ns._prepare_vdu_affinity_group_list( + target_vdu, extra_dict, ns_preffix + ) + self.assertDictEqual(extra_dict, expected_extra_dict) + self.assertEqual(result, expected_result) + + def test_prepare_vdu_affinity_group_list_no_affinity_group(self): + """There is not any affinity-group.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + extra_dict = {"depends_on": []} + ns_preffix = "nsrs:th47f48-9870-4169-b758-9732e1ff40f3" + result = self.ns._prepare_vdu_affinity_group_list( + target_vdu, extra_dict, ns_preffix + ) + self.assertDictEqual(extra_dict, {"depends_on": []}) + self.assertEqual(result, []) + + @patch("osm_ng_ro.ns.Ns._sort_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._partially_locate_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_cloud_init") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_ssh_keys") + @patch("osm_ng_ro.ns.Ns.find_persistent_root_volumes") + @patch("osm_ng_ro.ns.Ns.find_persistent_volumes") + @patch("osm_ng_ro.ns.Ns._add_persistent_root_disk_to_disk_list") + @patch("osm_ng_ro.ns.Ns._add_persistent_ordinary_disks_to_disk_list") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_affinity_group_list") + def test_process_vdu_params_with_inst_vol_list( + self, + mock_prepare_vdu_affinity_group_list, + mock_add_persistent_ordinary_disks_to_disk_list, + mock_add_persistent_root_disk_to_disk_list, + mock_find_persistent_volumes, + mock_find_persistent_root_volumes, + mock_prepare_vdu_ssh_keys, + mock_prepare_vdu_cloud_init, + mock_prepare_vdu_interfaces, + mock_locate_vdu_interfaces, + mock_sort_vdu_interfaces, + ): + """Instantiation volume list is empty.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + + target_vdu["interfaces"] = interfaces_wth_all_positions + + vdu_instantiation_vol_list = [ + { + "vim-volume-id": vim_volume_id, + "name": "persistent-volume2", + } + ] + target_vdu["additionalParams"] = { + "OSM": {"vdu_volumes": vdu_instantiation_vol_list} + } + mock_prepare_vdu_cloud_init.return_value = {} + mock_prepare_vdu_affinity_group_list.return_value = [] + persistent_root_disk = { + "persistent-root-volume": { + "image_id": "ubuntu20.04", + "size": "10", + } + } + mock_find_persistent_root_volumes.return_value = persistent_root_disk + + new_kwargs = deepcopy(kwargs) + new_kwargs.update( + { + "vnfr_id": vnfr_id, + "nsr_id": nsr_id, + "tasks_by_target_record_id": {}, + "logger": "logger", + } + ) + expected_extra_dict_copy = deepcopy(expected_extra_dict) + vnfd = deepcopy(vnfd_wth_persistent_storage) + db.get_one.return_value = vnfd + result = Ns._process_vdu_params( + target_vdu, indata, vim_info=None, target_record_id=None, **new_kwargs + ) + mock_sort_vdu_interfaces.assert_called_once_with(target_vdu) + mock_locate_vdu_interfaces.assert_not_called() + mock_prepare_vdu_cloud_init.assert_called_once() + mock_add_persistent_root_disk_to_disk_list.assert_not_called() + mock_add_persistent_ordinary_disks_to_disk_list.assert_not_called() + mock_prepare_vdu_interfaces.assert_called_once_with( + target_vdu, + expected_extra_dict_copy, + ns_preffix, + vnf_preffix, + "logger", + {}, + [], + ) + self.assertDictEqual(result, expected_extra_dict_copy) + mock_prepare_vdu_ssh_keys.assert_called_once_with(target_vdu, None, {}) + mock_prepare_vdu_affinity_group_list.assert_called_once() + mock_find_persistent_volumes.assert_called_once_with( + persistent_root_disk, target_vdu, vdu_instantiation_vol_list, [] + ) + + @patch("osm_ng_ro.ns.Ns._sort_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._partially_locate_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_cloud_init") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_ssh_keys") + @patch("osm_ng_ro.ns.Ns.find_persistent_root_volumes") + @patch("osm_ng_ro.ns.Ns.find_persistent_volumes") + @patch("osm_ng_ro.ns.Ns._add_persistent_root_disk_to_disk_list") + @patch("osm_ng_ro.ns.Ns._add_persistent_ordinary_disks_to_disk_list") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_affinity_group_list") + def test_process_vdu_params_wth_affinity_groups( + self, + mock_prepare_vdu_affinity_group_list, + mock_add_persistent_ordinary_disks_to_disk_list, + mock_add_persistent_root_disk_to_disk_list, + mock_find_persistent_volumes, + mock_find_persistent_root_volumes, + mock_prepare_vdu_ssh_keys, + mock_prepare_vdu_cloud_init, + mock_prepare_vdu_interfaces, + mock_locate_vdu_interfaces, + mock_sort_vdu_interfaces, + ): + """There is cloud-config.""" + target_vdu = deepcopy(target_vdu_wthout_persistent_storage) + + self.maxDiff = None + target_vdu["interfaces"] = interfaces_wth_all_positions + mock_prepare_vdu_cloud_init.return_value = {} + mock_prepare_vdu_affinity_group_list.return_value = [ + "affinity_group_1", + "affinity_group_2", + ] + + new_kwargs = deepcopy(kwargs) + new_kwargs.update( + { + "vnfr_id": vnfr_id, + "nsr_id": nsr_id, + "tasks_by_target_record_id": {}, + "logger": "logger", + } + ) + expected_extra_dict3 = deepcopy(expected_extra_dict2) + expected_extra_dict3["params"]["affinity_group_list"] = [ + "affinity_group_1", + "affinity_group_2", + ] + vnfd = deepcopy(vnfd_wth_persistent_storage) + db.get_one.return_value = vnfd + result = Ns._process_vdu_params( + target_vdu, indata, vim_info=None, target_record_id=None, **new_kwargs + ) + self.assertDictEqual(result, expected_extra_dict3) + mock_sort_vdu_interfaces.assert_called_once_with(target_vdu) + mock_locate_vdu_interfaces.assert_not_called() + mock_prepare_vdu_cloud_init.assert_called_once() + mock_add_persistent_root_disk_to_disk_list.assert_called_once() + mock_add_persistent_ordinary_disks_to_disk_list.assert_called_once() + mock_prepare_vdu_interfaces.assert_called_once_with( + target_vdu, + expected_extra_dict3, + ns_preffix, + vnf_preffix, + "logger", + {}, + [], + ) + + mock_prepare_vdu_ssh_keys.assert_called_once_with(target_vdu, None, {}) + mock_prepare_vdu_affinity_group_list.assert_called_once() + mock_find_persistent_volumes.assert_not_called() + + @patch("osm_ng_ro.ns.Ns._sort_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._partially_locate_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_cloud_init") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_ssh_keys") + @patch("osm_ng_ro.ns.Ns.find_persistent_root_volumes") + @patch("osm_ng_ro.ns.Ns.find_persistent_volumes") + @patch("osm_ng_ro.ns.Ns._add_persistent_root_disk_to_disk_list") + @patch("osm_ng_ro.ns.Ns._add_persistent_ordinary_disks_to_disk_list") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_affinity_group_list") + def test_process_vdu_params_wth_cloud_config( + self, + mock_prepare_vdu_affinity_group_list, + mock_add_persistent_ordinary_disks_to_disk_list, + mock_add_persistent_root_disk_to_disk_list, + mock_find_persistent_volumes, + mock_find_persistent_root_volumes, + mock_prepare_vdu_ssh_keys, + mock_prepare_vdu_cloud_init, + mock_prepare_vdu_interfaces, + mock_locate_vdu_interfaces, + mock_sort_vdu_interfaces, + ): + """There is cloud-config.""" + target_vdu = deepcopy(target_vdu_wthout_persistent_storage) + + self.maxDiff = None + target_vdu["interfaces"] = interfaces_wth_all_positions + mock_prepare_vdu_cloud_init.return_value = { + "user-data": user_data, + "boot-data-drive": "vda", + } + mock_prepare_vdu_affinity_group_list.return_value = [] + + new_kwargs = deepcopy(kwargs) + new_kwargs.update( + { + "vnfr_id": vnfr_id, + "nsr_id": nsr_id, + "tasks_by_target_record_id": {}, + "logger": "logger", + } + ) + expected_extra_dict3 = deepcopy(expected_extra_dict2) + expected_extra_dict3["params"]["cloud_config"] = { + "user-data": user_data, + "boot-data-drive": "vda", + } + vnfd = deepcopy(vnfd_wth_persistent_storage) + db.get_one.return_value = vnfd + result = Ns._process_vdu_params( + target_vdu, indata, vim_info=None, target_record_id=None, **new_kwargs + ) + mock_sort_vdu_interfaces.assert_called_once_with(target_vdu) + mock_locate_vdu_interfaces.assert_not_called() + mock_prepare_vdu_cloud_init.assert_called_once() + mock_add_persistent_root_disk_to_disk_list.assert_called_once() + mock_add_persistent_ordinary_disks_to_disk_list.assert_called_once() + mock_prepare_vdu_interfaces.assert_called_once_with( + target_vdu, + expected_extra_dict3, + ns_preffix, + vnf_preffix, + "logger", + {}, + [], + ) + self.assertDictEqual(result, expected_extra_dict3) + mock_prepare_vdu_ssh_keys.assert_called_once_with( + target_vdu, None, {"user-data": user_data, "boot-data-drive": "vda"} + ) + mock_prepare_vdu_affinity_group_list.assert_called_once() + mock_find_persistent_volumes.assert_not_called() + + @patch("osm_ng_ro.ns.Ns._sort_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._partially_locate_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_cloud_init") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_ssh_keys") + @patch("osm_ng_ro.ns.Ns.find_persistent_root_volumes") + @patch("osm_ng_ro.ns.Ns.find_persistent_volumes") + @patch("osm_ng_ro.ns.Ns._add_persistent_root_disk_to_disk_list") + @patch("osm_ng_ro.ns.Ns._add_persistent_ordinary_disks_to_disk_list") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_affinity_group_list") + def test_process_vdu_params_wthout_persistent_storage( + self, + mock_prepare_vdu_affinity_group_list, + mock_add_persistent_ordinary_disks_to_disk_list, + mock_add_persistent_root_disk_to_disk_list, + mock_find_persistent_volumes, + mock_find_persistent_root_volumes, + mock_prepare_vdu_ssh_keys, + mock_prepare_vdu_cloud_init, + mock_prepare_vdu_interfaces, + mock_locate_vdu_interfaces, + mock_sort_vdu_interfaces, + ): + """There is not any persistent storage.""" + target_vdu = deepcopy(target_vdu_wthout_persistent_storage) + + self.maxDiff = None + target_vdu["interfaces"] = interfaces_wth_all_positions + mock_prepare_vdu_cloud_init.return_value = {} + mock_prepare_vdu_affinity_group_list.return_value = [] + + new_kwargs = deepcopy(kwargs) + new_kwargs.update( + { + "vnfr_id": vnfr_id, + "nsr_id": nsr_id, + "tasks_by_target_record_id": {}, + "logger": "logger", + } + ) + expected_extra_dict_copy = deepcopy(expected_extra_dict2) + vnfd = deepcopy(vnfd_wthout_persistent_storage) + db.get_one.return_value = vnfd + result = Ns._process_vdu_params( + target_vdu, indata, vim_info=None, target_record_id=None, **new_kwargs + ) + mock_sort_vdu_interfaces.assert_called_once_with(target_vdu) + mock_locate_vdu_interfaces.assert_not_called() + mock_prepare_vdu_cloud_init.assert_called_once() + mock_add_persistent_root_disk_to_disk_list.assert_called_once() + mock_add_persistent_ordinary_disks_to_disk_list.assert_called_once() + mock_prepare_vdu_interfaces.assert_called_once_with( + target_vdu, + expected_extra_dict_copy, + ns_preffix, + vnf_preffix, + "logger", + {}, + [], + ) + self.assertDictEqual(result, expected_extra_dict_copy) + mock_prepare_vdu_ssh_keys.assert_called_once_with(target_vdu, None, {}) + mock_prepare_vdu_affinity_group_list.assert_called_once() + mock_find_persistent_volumes.assert_not_called() + + @patch("osm_ng_ro.ns.Ns._sort_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._partially_locate_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_cloud_init") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_ssh_keys") + @patch("osm_ng_ro.ns.Ns.find_persistent_root_volumes") + @patch("osm_ng_ro.ns.Ns.find_persistent_volumes") + @patch("osm_ng_ro.ns.Ns._add_persistent_root_disk_to_disk_list") + @patch("osm_ng_ro.ns.Ns._add_persistent_ordinary_disks_to_disk_list") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_affinity_group_list") + def test_process_vdu_params_interfaces_partially_located( + self, + mock_prepare_vdu_affinity_group_list, + mock_add_persistent_ordinary_disks_to_disk_list, + mock_add_persistent_root_disk_to_disk_list, + mock_find_persistent_volumes, + mock_find_persistent_root_volumes, + mock_prepare_vdu_ssh_keys, + mock_prepare_vdu_cloud_init, + mock_prepare_vdu_interfaces, + mock_locate_vdu_interfaces, + mock_sort_vdu_interfaces, + ): + """Some interfaces have position.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + + self.maxDiff = None + target_vdu["interfaces"] = [ + { + "name": "vdu-eth1", + "ns-vld-id": "net1", + }, + {"name": "vdu-eth2", "ns-vld-id": "net2", "position": 2}, + { + "name": "vdu-eth3", + "ns-vld-id": "mgmtnet", + }, + ] + mock_prepare_vdu_cloud_init.return_value = {} + mock_prepare_vdu_affinity_group_list.return_value = [] + persistent_root_disk = { + "persistent-root-volume": { + "image_id": "ubuntu20.04", + "size": "10", + } + } + mock_find_persistent_root_volumes.return_value = persistent_root_disk + + new_kwargs = deepcopy(kwargs) + new_kwargs.update( + { + "vnfr_id": vnfr_id, + "nsr_id": nsr_id, + "tasks_by_target_record_id": {}, + "logger": "logger", + } + ) + + vnfd = deepcopy(vnfd_wth_persistent_storage) + db.get_one.return_value = vnfd + result = Ns._process_vdu_params( + target_vdu, indata, vim_info=None, target_record_id=None, **new_kwargs + ) + expected_extra_dict_copy = deepcopy(expected_extra_dict) + mock_sort_vdu_interfaces.assert_not_called() + mock_locate_vdu_interfaces.assert_called_once_with(target_vdu) + mock_prepare_vdu_cloud_init.assert_called_once() + mock_add_persistent_root_disk_to_disk_list.assert_called_once() + mock_add_persistent_ordinary_disks_to_disk_list.assert_called_once() + mock_prepare_vdu_interfaces.assert_called_once_with( + target_vdu, + expected_extra_dict_copy, + ns_preffix, + vnf_preffix, + "logger", + {}, + [], + ) + self.assertDictEqual(result, expected_extra_dict_copy) + mock_prepare_vdu_ssh_keys.assert_called_once_with(target_vdu, None, {}) + mock_prepare_vdu_affinity_group_list.assert_called_once() + mock_find_persistent_volumes.assert_not_called() + mock_find_persistent_root_volumes.assert_not_called() + + @patch("osm_ng_ro.ns.Ns._sort_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._partially_locate_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_cloud_init") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_ssh_keys") + @patch("osm_ng_ro.ns.Ns.find_persistent_root_volumes") + @patch("osm_ng_ro.ns.Ns.find_persistent_volumes") + @patch("osm_ng_ro.ns.Ns._add_persistent_root_disk_to_disk_list") + @patch("osm_ng_ro.ns.Ns._add_persistent_ordinary_disks_to_disk_list") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_affinity_group_list") + def test_process_vdu_params_no_interface_position( + self, + mock_prepare_vdu_affinity_group_list, + mock_add_persistent_ordinary_disks_to_disk_list, + mock_add_persistent_root_disk_to_disk_list, + mock_find_persistent_volumes, + mock_find_persistent_root_volumes, + mock_prepare_vdu_ssh_keys, + mock_prepare_vdu_cloud_init, + mock_prepare_vdu_interfaces, + mock_locate_vdu_interfaces, + mock_sort_vdu_interfaces, + ): + """Interfaces do not have position.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + + self.maxDiff = None + target_vdu["interfaces"] = interfaces_wthout_positions + mock_prepare_vdu_cloud_init.return_value = {} + mock_prepare_vdu_affinity_group_list.return_value = [] + persistent_root_disk = { + "persistent-root-volume": { + "image_id": "ubuntu20.04", + "size": "10", + } + } + mock_find_persistent_root_volumes.return_value = persistent_root_disk + new_kwargs = deepcopy(kwargs) + new_kwargs.update( + { + "vnfr_id": vnfr_id, + "nsr_id": nsr_id, + "tasks_by_target_record_id": {}, + "logger": "logger", + } + ) + + vnfd = deepcopy(vnfd_wth_persistent_storage) + db.get_one.return_value = vnfd + result = Ns._process_vdu_params( + target_vdu, indata, vim_info=None, target_record_id=None, **new_kwargs + ) + expected_extra_dict_copy = deepcopy(expected_extra_dict) + mock_sort_vdu_interfaces.assert_not_called() + mock_locate_vdu_interfaces.assert_called_once_with(target_vdu) + mock_prepare_vdu_cloud_init.assert_called_once() + mock_add_persistent_root_disk_to_disk_list.assert_called_once() + mock_add_persistent_ordinary_disks_to_disk_list.assert_called_once() + mock_prepare_vdu_interfaces.assert_called_once_with( + target_vdu, + expected_extra_dict_copy, + ns_preffix, + vnf_preffix, + "logger", + {}, + [], + ) + self.assertDictEqual(result, expected_extra_dict_copy) + mock_prepare_vdu_ssh_keys.assert_called_once_with(target_vdu, None, {}) + mock_prepare_vdu_affinity_group_list.assert_called_once() + mock_find_persistent_volumes.assert_not_called() + mock_find_persistent_root_volumes.assert_not_called() + + @patch("osm_ng_ro.ns.Ns._sort_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._partially_locate_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_cloud_init") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_ssh_keys") + @patch("osm_ng_ro.ns.Ns.find_persistent_root_volumes") + @patch("osm_ng_ro.ns.Ns.find_persistent_volumes") + @patch("osm_ng_ro.ns.Ns._add_persistent_root_disk_to_disk_list") + @patch("osm_ng_ro.ns.Ns._add_persistent_ordinary_disks_to_disk_list") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_affinity_group_list") + def test_process_vdu_params_prepare_vdu_interfaces_raises_exception( + self, + mock_prepare_vdu_affinity_group_list, + mock_add_persistent_ordinary_disks_to_disk_list, + mock_add_persistent_root_disk_to_disk_list, + mock_find_persistent_volumes, + mock_find_persistent_root_volumes, + mock_prepare_vdu_ssh_keys, + mock_prepare_vdu_cloud_init, + mock_prepare_vdu_interfaces, + mock_locate_vdu_interfaces, + mock_sort_vdu_interfaces, + ): + """Prepare vdu interfaces method raises exception.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + + self.maxDiff = None + target_vdu["interfaces"] = interfaces_wthout_positions + mock_prepare_vdu_cloud_init.return_value = {} + mock_prepare_vdu_affinity_group_list.return_value = [] + persistent_root_disk = { + "persistent-root-volume": { + "image_id": "ubuntu20.04", + "size": "10", + } + } + mock_find_persistent_root_volumes.return_value = persistent_root_disk + new_kwargs = deepcopy(kwargs) + new_kwargs.update( + { + "vnfr_id": vnfr_id, + "nsr_id": nsr_id, + "tasks_by_target_record_id": {}, + "logger": "logger", + } + ) + mock_prepare_vdu_interfaces.side_effect = TypeError + + vnfd = deepcopy(vnfd_wth_persistent_storage) + db.get_one.return_value = vnfd + with self.assertRaises(Exception) as err: + Ns._process_vdu_params( + target_vdu, indata, vim_info=None, target_record_id=None, **new_kwargs + ) + self.assertEqual(type(err), TypeError) + mock_sort_vdu_interfaces.assert_not_called() + mock_locate_vdu_interfaces.assert_called_once_with(target_vdu) + mock_prepare_vdu_cloud_init.assert_not_called() + mock_add_persistent_root_disk_to_disk_list.assert_not_called() + mock_add_persistent_ordinary_disks_to_disk_list.assert_not_called() + mock_prepare_vdu_interfaces.assert_called_once() + mock_prepare_vdu_ssh_keys.assert_not_called() + mock_prepare_vdu_affinity_group_list.assert_not_called() + mock_find_persistent_volumes.assert_not_called() + mock_find_persistent_root_volumes.assert_not_called() + + @patch("osm_ng_ro.ns.Ns._sort_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._partially_locate_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_interfaces") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_cloud_init") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_ssh_keys") + @patch("osm_ng_ro.ns.Ns.find_persistent_root_volumes") + @patch("osm_ng_ro.ns.Ns.find_persistent_volumes") + @patch("osm_ng_ro.ns.Ns._add_persistent_root_disk_to_disk_list") + @patch("osm_ng_ro.ns.Ns._add_persistent_ordinary_disks_to_disk_list") + @patch("osm_ng_ro.ns.Ns._prepare_vdu_affinity_group_list") + def test_process_vdu_params_add_persistent_root_disk_raises_exception( + self, + mock_prepare_vdu_affinity_group_list, + mock_add_persistent_ordinary_disks_to_disk_list, + mock_add_persistent_root_disk_to_disk_list, + mock_find_persistent_volumes, + mock_find_persistent_root_volumes, + mock_prepare_vdu_ssh_keys, + mock_prepare_vdu_cloud_init, + mock_prepare_vdu_interfaces, + mock_locate_vdu_interfaces, + mock_sort_vdu_interfaces, + ): + """Add persistent root disk method raises exception.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + + self.maxDiff = None + target_vdu["interfaces"] = interfaces_wthout_positions + mock_prepare_vdu_cloud_init.return_value = {} + mock_prepare_vdu_affinity_group_list.return_value = [] + mock_add_persistent_root_disk_to_disk_list.side_effect = KeyError + new_kwargs = deepcopy(kwargs) + new_kwargs.update( + { + "vnfr_id": vnfr_id, + "nsr_id": nsr_id, + "tasks_by_target_record_id": {}, + "logger": "logger", + } + ) + + vnfd = deepcopy(vnfd_wth_persistent_storage) + db.get_one.return_value = vnfd + with self.assertRaises(Exception) as err: + Ns._process_vdu_params( + target_vdu, indata, vim_info=None, target_record_id=None, **new_kwargs + ) + self.assertEqual(type(err), KeyError) + mock_sort_vdu_interfaces.assert_not_called() + mock_locate_vdu_interfaces.assert_called_once_with(target_vdu) + mock_prepare_vdu_cloud_init.assert_called_once() + mock_add_persistent_root_disk_to_disk_list.assert_called_once() + mock_add_persistent_ordinary_disks_to_disk_list.assert_not_called() + mock_prepare_vdu_interfaces.assert_called_once_with( + target_vdu, + { + "depends_on": [ + f"{ns_preffix}:image.0", + f"{ns_preffix}:flavor.0", + ] + }, + ns_preffix, + vnf_preffix, + "logger", + {}, + [], + ) + + mock_prepare_vdu_ssh_keys.assert_called_once_with(target_vdu, None, {}) + mock_prepare_vdu_affinity_group_list.assert_not_called() + mock_find_persistent_volumes.assert_not_called() + mock_find_persistent_root_volumes.assert_not_called() + + def test_select_persistent_root_disk(self): + vdu = deepcopy(target_vdu_wth_persistent_storage) + vdu["virtual-storage-desc"] = [ + "persistent-root-volume", + "persistent-volume2", + "ephemeral-volume", + ] + vsd = deepcopy(vnfd_wth_persistent_storage)["virtual-storage-desc"][1] + expected_result = vsd + result = Ns._select_persistent_root_disk(vsd, vdu) + self.assertEqual(result, expected_result) + + def test_select_persistent_root_disk_first_vsd_is_different(self): + """VDU first virtual-storage-desc is different than vsd id.""" + vdu = deepcopy(target_vdu_wth_persistent_storage) + vdu["virtual-storage-desc"] = [ + "persistent-volume2", + "persistent-root-volume", + "ephemeral-volume", + ] + vsd = deepcopy(vnfd_wth_persistent_storage)["virtual-storage-desc"][1] + expected_result = None + result = Ns._select_persistent_root_disk(vsd, vdu) + self.assertEqual(result, expected_result) + + def test_select_persistent_root_disk_vsd_is_not_persistent(self): + """vsd type is not persistent.""" + vdu = deepcopy(target_vdu_wth_persistent_storage) + vdu["virtual-storage-desc"] = [ + "persistent-volume2", + "persistent-root-volume", + "ephemeral-volume", + ] + vsd = deepcopy(vnfd_wth_persistent_storage)["virtual-storage-desc"][1] + vsd["type-of-storage"] = "etsi-nfv-descriptors:ephemeral-storage" + expected_result = None + result = Ns._select_persistent_root_disk(vsd, vdu) + self.assertEqual(result, expected_result) + + def test_select_persistent_root_disk_vsd_does_not_have_size(self): + """vsd size is None.""" + vdu = deepcopy(target_vdu_wth_persistent_storage) + vdu["virtual-storage-desc"] = [ + "persistent-volume2", + "persistent-root-volume", + "ephemeral-volume", + ] + vsd = deepcopy(vnfd_wth_persistent_storage)["virtual-storage-desc"][1] + vsd["size-of-storage"] = None + expected_result = None + result = Ns._select_persistent_root_disk(vsd, vdu) + self.assertEqual(result, expected_result) + + def test_select_persistent_root_disk_vdu_wthout_vsd(self): + """VDU does not have virtual-storage-desc.""" + vdu = deepcopy(target_vdu_wth_persistent_storage) + vsd = deepcopy(vnfd_wth_persistent_storage)["virtual-storage-desc"][1] + expected_result = None + result = Ns._select_persistent_root_disk(vsd, vdu) + self.assertEqual(result, expected_result) + + def test_select_persistent_root_disk_invalid_vsd_type(self): + """vsd is list, expected to be a dict.""" + vdu = deepcopy(target_vdu_wth_persistent_storage) + vsd = deepcopy(vnfd_wth_persistent_storage)["virtual-storage-desc"] + with self.assertRaises(AttributeError): + Ns._select_persistent_root_disk(vsd, vdu) diff --git a/RO-VIM-openstack/osm_rovim_openstack/tests/test_vimconn_openstack.py b/RO-VIM-openstack/osm_rovim_openstack/tests/test_vimconn_openstack.py index f7a0f1d8..bdd6bed9 100644 --- a/RO-VIM-openstack/osm_rovim_openstack/tests/test_vimconn_openstack.py +++ b/RO-VIM-openstack/osm_rovim_openstack/tests/test_vimconn_openstack.py @@ -31,8 +31,13 @@ import unittest import mock from mock import MagicMock, patch from neutronclient.v2_0.client import Client +from novaclient import exceptions as nvExceptions from osm_ro_plugin import vimconn -from osm_ro_plugin.vimconn import VimConnConnectionException, VimConnException +from osm_ro_plugin.vimconn import ( + VimConnConnectionException, + VimConnException, + VimConnNotFoundException, +) from osm_rovim_openstack.vimconn_openstack import vimconnector __author__ = "Igor D.C." @@ -66,6 +71,12 @@ ip_addr1 = "20.3.4.5" volume_id = "ac408b73-b9cc-4a6a-a270-82cc4811bd4a" volume_id2 = "o4e0e83-b9uu-4akk-a234-89cc4811bd4a" volume_id3 = "44e0e83-t9uu-4akk-a234-p9cc4811bd4a" +virtual_mac_id = "64e0e83-t9uu-4akk-a234-p9cc4811bd4a" +created_items_all_true = { + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id}": True, + f"port:{port_id}": True, +} class TestSfcOperations(unittest.TestCase): @@ -1131,6 +1142,7 @@ class TestNewVmInstance(unittest.TestCase): self.vimconn.config["keypair"] = "my_keypair" self.vimconn.security_groups_id = "12345" self.vimconn.nova.api_version.get_string.return_value = "2.32" + self.vimconn.logger = CopyingMock() @patch.object(vimconnector, "_get_ids_from_name") def test_prepare_port_dict_security_security_groups_exists_in_config( @@ -4479,6 +4491,996 @@ class TestNewVmInstance(unittest.TestCase): mock_prepare_external_network.assert_not_called() mock_delete_vm_instance.assert_called_once_with(None, {}) + @patch.object(vimconnector, "_delete_ports_by_id_wth_neutron") + def test_delete_vm_ports_attached_to_network_empty_created_items( + self, mock_delete_ports_by_id_wth_neutron + ): + """Created_items is emtpty.""" + created_items = {} + self.vimconn._delete_vm_ports_attached_to_network(created_items) + self.vimconn.neutron.list_ports.assert_not_called() + self.vimconn.neutron.delete_port.assert_not_called() + mock_delete_ports_by_id_wth_neutron.assert_not_called() + + @patch.object(vimconnector, "_delete_ports_by_id_wth_neutron") + def test_delete_vm_ports_attached_to_network( + self, mock_delete_ports_by_id_wth_neutron + ): + created_items = { + "floating_ip:308b73-t9cc-1a6a-a270-12cc4811bd4a": True, + f"volume:{volume_id2}": True, + f"volume:{volume_id}": True, + f"port:{port_id}": True, + } + self.vimconn._delete_vm_ports_attached_to_network(created_items) + mock_delete_ports_by_id_wth_neutron.assert_called_once_with(f"{port_id}") + self.vimconn.logger.error.assert_not_called() + + @patch.object(vimconnector, "_delete_ports_by_id_wth_neutron") + def test_delete_vm_ports_attached_to_network_wthout_port( + self, mock_delete_ports_by_id_wth_neutron + ): + """Created_items does not have port.""" + created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id2}": True, + f"volume:{volume_id}": True, + } + self.vimconn._delete_vm_ports_attached_to_network(created_items) + mock_delete_ports_by_id_wth_neutron.assert_not_called() + self.vimconn.logger.error.assert_not_called() + + @patch.object(vimconnector, "_delete_ports_by_id_wth_neutron") + def test_delete_vm_ports_attached_to_network_delete_port_raise_vimconnexception( + self, mock_delete_ports_by_id_wth_neutron + ): + """_delete_ports_by_id_wth_neutron raises vimconnexception.""" + created_items = deepcopy(created_items_all_true) + mock_delete_ports_by_id_wth_neutron.side_effect = VimConnException( + "Can not delete port" + ) + self.vimconn._delete_vm_ports_attached_to_network(created_items) + mock_delete_ports_by_id_wth_neutron.assert_called_once_with(f"{port_id}") + self.vimconn.logger.error.assert_called_once_with( + "Error deleting port: VimConnException: Can not delete port" + ) + + @patch.object(vimconnector, "_delete_ports_by_id_wth_neutron") + def test_delete_vm_ports_attached_to_network_delete_port_raise_nvexception( + self, mock_delete_ports_by_id_wth_neutron + ): + """_delete_ports_by_id_wth_neutron raises nvExceptions.ClientException.""" + created_items = deepcopy(created_items_all_true) + mock_delete_ports_by_id_wth_neutron.side_effect = nvExceptions.ClientException( + "Connection aborted." + ) + self.vimconn._delete_vm_ports_attached_to_network(created_items) + mock_delete_ports_by_id_wth_neutron.assert_called_once_with(f"{port_id}") + self.vimconn.logger.error.assert_called_once_with( + "Error deleting port: ClientException: Unknown Error (HTTP Connection aborted.)" + ) + + @patch.object(vimconnector, "_delete_ports_by_id_wth_neutron") + def test_delete_vm_ports_attached_to_network_delete_port_invalid_port_item( + self, mock_delete_ports_by_id_wth_neutron + ): + """port item is invalid.""" + created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id2}": True, + f"volume:{volume_id}": True, + f"port:{port_id}:": True, + } + mock_delete_ports_by_id_wth_neutron.side_effect = VimConnException( + "Port is not valid." + ) + self.vimconn._delete_vm_ports_attached_to_network(created_items) + mock_delete_ports_by_id_wth_neutron.assert_called_once_with(f"{port_id}:") + self.vimconn.logger.error.assert_called_once_with( + "Error deleting port: VimConnException: Port is not valid." + ) + + @patch.object(vimconnector, "_delete_ports_by_id_wth_neutron") + def test_delete_vm_ports_attached_to_network_delete_port_already_deleted( + self, mock_delete_ports_by_id_wth_neutron + ): + """port is already deleted.""" + created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id2}": True, + f"volume:{volume_id}": None, + f"port:{port_id}": None, + } + self.vimconn._delete_vm_ports_attached_to_network(created_items) + mock_delete_ports_by_id_wth_neutron.assert_not_called() + self.vimconn.logger.error.assert_not_called() + + def test_delete_floating_ip_by_id(self): + created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"port:{port_id}": True, + } + expected_created_items = { + f"floating_ip:{floating_network_vim_id}": None, + f"port:{port_id}": True, + } + k_id = floating_network_vim_id + k = f"floating_ip:{floating_network_vim_id}" + self.vimconn._delete_floating_ip_by_id(k, k_id, created_items) + self.vimconn.neutron.delete_floatingip.assert_called_once_with(k_id) + self.assertEqual(created_items, expected_created_items) + + def test_delete_floating_ip_by_id_floating_ip_already_deleted(self): + """floating ip is already deleted.""" + created_items = { + f"floating_ip:{floating_network_vim_id}": None, + f"port:{port_id}": True, + } + k_id = floating_network_vim_id + k = f"floating_ip:{floating_network_vim_id}" + self.vimconn._delete_floating_ip_by_id(k, k_id, created_items) + self.vimconn.neutron.delete_floatingip.assert_called_once_with(k_id) + self.assertEqual( + created_items, + { + f"floating_ip:{floating_network_vim_id}": None, + f"port:{port_id}": True, + }, + ) + + def test_delete_floating_ip_by_id_floating_ip_raises_nvexception(self): + """netron delete floating ip raises nvExceptions.ClientException.""" + created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"port:{port_id}": True, + } + k_id = floating_network_vim_id + k = f"floating_ip:{floating_network_vim_id}" + self.vimconn.neutron.delete_floatingip.side_effect = ( + nvExceptions.ClientException("Client exception occured.") + ) + self.vimconn._delete_floating_ip_by_id(k, k_id, created_items) + self.vimconn.neutron.delete_floatingip.assert_called_once_with(k_id) + self.assertEqual( + created_items, + { + f"floating_ip:{floating_network_vim_id}": True, + f"port:{port_id}": True, + }, + ) + self.vimconn.logger.error.assert_called_once_with( + "Error deleting floating ip: ClientException: Unknown Error (HTTP Client exception occured.)" + ) + + def test_delete_floating_ip_by_id_floating_ip_raises_vimconnexception(self): + """netron delete floating ip raises VimConnNotFoundException.""" + created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"port:{port_id}": True, + } + k_id = floating_network_vim_id + k = f"floating_ip:{floating_network_vim_id}" + self.vimconn.neutron.delete_floatingip.side_effect = VimConnNotFoundException( + "Port id could not found." + ) + self.vimconn._delete_floating_ip_by_id(k, k_id, created_items) + self.vimconn.neutron.delete_floatingip.assert_called_once_with(k_id) + self.assertEqual( + created_items, + { + f"floating_ip:{floating_network_vim_id}": True, + f"port:{port_id}": True, + }, + ) + self.vimconn.logger.error.assert_called_once_with( + "Error deleting floating ip: VimConnNotFoundException: Port id could not found." + ) + + def test_delete_floating_ip_by_id_floating_ip_invalid_k_item(self): + """invalid floating ip item.""" + created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"port:{port_id}": True, + } + expected_created_items = { + f"floating_ip:{floating_network_vim_id}::": None, + f"floating_ip:{floating_network_vim_id}": True, + f"port:{port_id}": True, + } + k_id = floating_network_vim_id + k = f"floating_ip:{floating_network_vim_id}::" + self.vimconn._delete_floating_ip_by_id(k, k_id, created_items) + self.vimconn.neutron.delete_floatingip.assert_called_once_with(k_id) + self.assertEqual(created_items, expected_created_items) + + def test_delete_volumes_by_id_with_cinder_volume_status_available(self): + """volume status is available.""" + created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id2}": True, + f"volume:{volume_id}": True, + f"port:{port_id}": None, + } + expected_created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id2}": True, + f"volume:{volume_id}": None, + f"port:{port_id}": None, + } + volumes_to_hold = [] + k = f"volume:{volume_id}" + k_id = volume_id + self.vimconn.cinder.volumes.get.return_value.status = "available" + result = self.vimconn._delete_volumes_by_id_wth_cinder( + k, k_id, volumes_to_hold, created_items + ) + self.assertEqual(result, None) + self.vimconn.cinder.volumes.get.assert_called_once_with(k_id) + self.vimconn.cinder.volumes.delete.assert_called_once_with(k_id) + self.vimconn.logger.error.assert_not_called() + self.assertEqual(created_items, expected_created_items) + + def test_delete_volumes_by_id_with_cinder_volume_already_deleted(self): + """volume is already deleted.""" + created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id2}": True, + f"volume:{volume_id}": None, + f"port:{port_id}": None, + } + expected_created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id2}": True, + f"volume:{volume_id}": None, + f"port:{port_id}": None, + } + volumes_to_hold = [] + k = f"volume:{volume_id}" + k_id = volume_id + self.vimconn.cinder.volumes.get.return_value.status = "available" + result = self.vimconn._delete_volumes_by_id_wth_cinder( + k, k_id, volumes_to_hold, created_items + ) + self.assertEqual(result, None) + self.vimconn.cinder.volumes.get.assert_called_once_with(k_id) + self.vimconn.cinder.volumes.delete.assert_called_once_with(k_id) + self.vimconn.logger.error.assert_not_called() + self.assertEqual(created_items, expected_created_items) + + def test_delete_volumes_by_id_with_cinder_get_volume_raise_exception(self): + """cinder get volume raises exception.""" + created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id2}": True, + f"volume:{volume_id}": True, + f"port:{port_id}": None, + } + expected_created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id2}": True, + f"volume:{volume_id}": True, + f"port:{port_id}": None, + } + volumes_to_hold = [] + k = f"volume:{volume_id}" + k_id = volume_id + self.vimconn.cinder.volumes.get.side_effect = Exception( + "Can not get volume status." + ) + result = self.vimconn._delete_volumes_by_id_wth_cinder( + k, k_id, volumes_to_hold, created_items + ) + self.assertEqual(result, None) + self.vimconn.cinder.volumes.get.assert_called_once_with(k_id) + self.vimconn.cinder.volumes.delete.assert_not_called() + self.vimconn.logger.error.assert_called_once_with( + "Error deleting volume: Exception: Can not get volume status." + ) + self.assertEqual(created_items, expected_created_items) + + def test_delete_volumes_by_id_with_cinder_delete_volume_raise_exception(self): + """cinder delete volume raises exception.""" + created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id2}": True, + f"volume:{volume_id}": True, + f"port:{port_id}": None, + } + expected_created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id2}": True, + f"volume:{volume_id}": True, + f"port:{port_id}": None, + } + volumes_to_hold = [] + k = f"volume:{volume_id}" + k_id = volume_id + self.vimconn.cinder.volumes.get.return_value.status = "available" + self.vimconn.cinder.volumes.delete.side_effect = nvExceptions.ClientException( + "Connection aborted." + ) + result = self.vimconn._delete_volumes_by_id_wth_cinder( + k, k_id, volumes_to_hold, created_items + ) + self.assertEqual(result, None) + self.vimconn.cinder.volumes.get.assert_called_once_with(k_id) + self.vimconn.cinder.volumes.delete.assert_called_once_with(k_id) + self.vimconn.logger.error.assert_called_once_with( + "Error deleting volume: ClientException: Unknown Error (HTTP Connection aborted.)" + ) + self.assertEqual(created_items, expected_created_items) + + def test_delete_volumes_by_id_with_cinder_volume_to_be_hold(self): + """volume_to_hold has item.""" + created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id2}": True, + f"volume:{volume_id}": True, + f"port:{port_id}": None, + } + expected_created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id2}": True, + f"volume:{volume_id}": True, + f"port:{port_id}": None, + } + volumes_to_hold = [volume_id] + k = f"volume:{volume_id}" + k_id = volume_id + result = self.vimconn._delete_volumes_by_id_wth_cinder( + k, k_id, volumes_to_hold, created_items + ) + self.assertEqual(result, None) + self.vimconn.cinder.volumes.get.assert_not_called() + self.vimconn.cinder.volumes.delete.assert_not_called() + self.vimconn.logger.error.assert_not_called() + self.assertEqual(created_items, expected_created_items) + + def test_delete_volumes_by_id_with_cinder_volume_status_not_available(self): + """volume status is not available.""" + created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id2}": True, + f"volume:{volume_id}": True, + f"port:{port_id}": None, + } + expected_created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id2}": True, + f"volume:{volume_id}": True, + f"port:{port_id}": None, + } + volumes_to_hold = [] + k = f"volume:{volume_id}" + k_id = volume_id + self.vimconn.cinder.volumes.get.return_value.status = "unavailable" + result = self.vimconn._delete_volumes_by_id_wth_cinder( + k, k_id, volumes_to_hold, created_items + ) + self.assertEqual(result, True) + self.vimconn.cinder.volumes.get.assert_called_once_with(k_id) + self.vimconn.cinder.volumes.delete.assert_not_called() + self.vimconn.logger.error.assert_not_called() + self.assertEqual(created_items, expected_created_items) + + def test_delete_ports_by_id_by_neutron(self): + """neutron delete ports.""" + k_id = port_id + self.vimconn.neutron.list_ports.return_value = { + "ports": [{"id": port_id}, {"id": port2_id}] + } + + self.vimconn._delete_ports_by_id_wth_neutron(k_id) + self.vimconn.neutron.list_ports.assert_called_once() + self.vimconn.neutron.delete_port.assert_called_once_with(k_id) + self.vimconn.logger.error.assert_not_called() + + def test_delete_ports_by_id_by_neutron_id_not_in_port_list(self): + """port id not in the port list.""" + k_id = volume_id + self.vimconn.neutron.list_ports.return_value = { + "ports": [{"id": port_id}, {"id": port2_id}] + } + + self.vimconn._delete_ports_by_id_wth_neutron(k_id) + self.vimconn.neutron.list_ports.assert_called_once() + self.vimconn.neutron.delete_port.assert_not_called() + self.vimconn.logger.error.assert_not_called() + + def test_delete_ports_by_id_by_neutron_list_port_raise_exception(self): + """neutron list port raises exception.""" + k_id = port_id + self.vimconn.neutron.list_ports.side_effect = nvExceptions.ClientException( + "Connection aborted." + ) + self.vimconn._delete_ports_by_id_wth_neutron(k_id) + self.vimconn.neutron.list_ports.assert_called_once() + self.vimconn.neutron.delete_port.assert_not_called() + self.vimconn.logger.error.assert_called_once_with( + "Error deleting port: ClientException: Unknown Error (HTTP Connection aborted.)" + ) + + def test_delete_ports_by_id_by_neutron_delete_port_raise_exception(self): + """neutron delete port raises exception.""" + k_id = port_id + self.vimconn.neutron.list_ports.return_value = { + "ports": [{"id": port_id}, {"id": port2_id}] + } + self.vimconn.neutron.delete_port.side_effect = nvExceptions.ClientException( + "Connection aborted." + ) + self.vimconn._delete_ports_by_id_wth_neutron(k_id) + self.vimconn.neutron.list_ports.assert_called_once() + self.vimconn.neutron.delete_port.assert_called_once_with(k_id) + self.vimconn.logger.error.assert_called_once_with( + "Error deleting port: ClientException: Unknown Error (HTTP Connection aborted.)" + ) + + def test_get_item_name_id(self): + """Get name and id successfully.""" + k = f"some:{port_id}" + result = self.vimconn._get_item_name_id(k) + self.assertEqual(result, ("some", f"{port_id}")) + + def test_get_item_name_id_wthout_semicolon(self): + """Does not have seperator.""" + k = f"some{port_id}" + result = self.vimconn._get_item_name_id(k) + self.assertEqual(result, (f"some{port_id}", "")) + + def test_get_item_name_id_empty_string(self): + """Empty string.""" + k = "" + result = self.vimconn._get_item_name_id(k) + self.assertEqual(result, ("", "")) + + def test_get_item_name_id_k_is_none(self): + """item is None.""" + k = None + with self.assertRaises(AttributeError): + self.vimconn._get_item_name_id(k) + + @patch.object(vimconnector, "_get_item_name_id") + @patch.object(vimconnector, "_delete_volumes_by_id_wth_cinder") + @patch.object(vimconnector, "_delete_floating_ip_by_id") + def test_delete_created_items( + self, + mock_delete_floating_ip_by_id, + mock_delete_volumes_by_id_wth_cinder, + mock_get_item_name_id, + ): + """Created items has floating ip and volume.""" + created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id}": True, + f"port:{port_id}": None, + } + mock_get_item_name_id.side_effect = [ + ("floating_ip", f"{floating_network_vim_id}"), + ("volume", f"{volume_id}"), + ] + mock_delete_volumes_by_id_wth_cinder.return_value = True + volumes_to_hold = [] + keep_waiting = False + result = self.vimconn._delete_created_items( + created_items, volumes_to_hold, keep_waiting + ) + self.assertEqual(result, True) + self.assertEqual(mock_get_item_name_id.call_count, 2) + mock_delete_volumes_by_id_wth_cinder.assert_called_once_with( + f"volume:{volume_id}", f"{volume_id}", [], created_items + ) + mock_delete_floating_ip_by_id.assert_called_once_with( + f"floating_ip:{floating_network_vim_id}", + f"{floating_network_vim_id}", + created_items, + ) + self.vimconn.logger.error.assert_not_called() + + @patch.object(vimconnector, "_get_item_name_id") + @patch.object(vimconnector, "_delete_volumes_by_id_wth_cinder") + @patch.object(vimconnector, "_delete_floating_ip_by_id") + def test_delete_created_items_wth_volumes_to_hold( + self, + mock_delete_floating_ip_by_id, + mock_delete_volumes_by_id_wth_cinder, + mock_get_item_name_id, + ): + """Created items has floating ip and volume and volumes_to_hold has items.""" + created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id}": True, + f"port:{port_id}": None, + } + mock_get_item_name_id.side_effect = [ + ("floating_ip", f"{floating_network_vim_id}"), + ("volume", f"{volume_id}"), + ] + mock_delete_volumes_by_id_wth_cinder.return_value = True + volumes_to_hold = [f"{volume_id}", f"{volume_id2}"] + keep_waiting = False + result = self.vimconn._delete_created_items( + created_items, volumes_to_hold, keep_waiting + ) + self.assertEqual(result, True) + self.assertEqual(mock_get_item_name_id.call_count, 2) + mock_delete_volumes_by_id_wth_cinder.assert_called_once_with( + f"volume:{volume_id}", f"{volume_id}", volumes_to_hold, created_items + ) + mock_delete_floating_ip_by_id.assert_called_once_with( + f"floating_ip:{floating_network_vim_id}", + f"{floating_network_vim_id}", + created_items, + ) + self.vimconn.logger.error.assert_not_called() + + @patch.object(vimconnector, "_get_item_name_id") + @patch.object(vimconnector, "_delete_volumes_by_id_wth_cinder") + @patch.object(vimconnector, "_delete_floating_ip_by_id") + def test_delete_created_items_wth_keep_waiting_true( + self, + mock_delete_floating_ip_by_id, + mock_delete_volumes_by_id_wth_cinder, + mock_get_item_name_id, + ): + """Keep waiting initial value is True.""" + created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id}": True, + f"port:{port_id}": None, + } + mock_get_item_name_id.side_effect = [ + ("floating_ip", f"{floating_network_vim_id}"), + ("volume", f"{volume_id}"), + ] + mock_delete_volumes_by_id_wth_cinder.return_value = False + volumes_to_hold = [f"{volume_id}", f"{volume_id2}"] + keep_waiting = True + result = self.vimconn._delete_created_items( + created_items, volumes_to_hold, keep_waiting + ) + self.assertEqual(result, True) + self.assertEqual(mock_get_item_name_id.call_count, 2) + mock_delete_volumes_by_id_wth_cinder.assert_called_once_with( + f"volume:{volume_id}", f"{volume_id}", volumes_to_hold, created_items + ) + mock_delete_floating_ip_by_id.assert_called_once_with( + f"floating_ip:{floating_network_vim_id}", + f"{floating_network_vim_id}", + created_items, + ) + self.vimconn.logger.error.assert_not_called() + + @patch.object(vimconnector, "_get_item_name_id") + @patch.object(vimconnector, "_delete_volumes_by_id_wth_cinder") + @patch.object(vimconnector, "_delete_floating_ip_by_id") + def test_delete_created_items_delete_vol_raises( + self, + mock_delete_floating_ip_by_id, + mock_delete_volumes_by_id_wth_cinder, + mock_get_item_name_id, + ): + """Delete volume raises exception.""" + created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id}": True, + f"port:{port_id}": None, + } + mock_get_item_name_id.side_effect = [ + ("floating_ip", f"{floating_network_vim_id}"), + ("volume", f"{volume_id}"), + ] + mock_delete_volumes_by_id_wth_cinder.side_effect = ConnectionError( + "Connection failed." + ) + volumes_to_hold = [] + keep_waiting = False + result = self.vimconn._delete_created_items( + created_items, volumes_to_hold, keep_waiting + ) + self.assertEqual(result, False) + self.assertEqual(mock_get_item_name_id.call_count, 2) + mock_delete_volumes_by_id_wth_cinder.assert_called_once_with( + f"volume:{volume_id}", f"{volume_id}", [], created_items + ) + mock_delete_floating_ip_by_id.assert_called_once_with( + f"floating_ip:{floating_network_vim_id}", + f"{floating_network_vim_id}", + created_items, + ) + self.vimconn.logger.error.assert_called_once_with( + "Error deleting volume:ac408b73-b9cc-4a6a-a270-82cc4811bd4a: Connection failed." + ) + + @patch.object(vimconnector, "_get_item_name_id") + @patch.object(vimconnector, "_delete_volumes_by_id_wth_cinder") + @patch.object(vimconnector, "_delete_floating_ip_by_id") + def test_delete_created_items_delete_fip_raises( + self, + mock_delete_floating_ip_by_id, + mock_delete_volumes_by_id_wth_cinder, + mock_get_item_name_id, + ): + """Delete floating ip raises exception.""" + created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id}": True, + f"port:{port_id}": None, + } + mock_get_item_name_id.side_effect = [ + ("floating_ip", f"{floating_network_vim_id}"), + ("volume", f"{volume_id}"), + ] + mock_delete_volumes_by_id_wth_cinder.return_value = False + mock_delete_floating_ip_by_id.side_effect = ConnectionError( + "Connection failed." + ) + volumes_to_hold = [] + keep_waiting = True + result = self.vimconn._delete_created_items( + created_items, volumes_to_hold, keep_waiting + ) + self.assertEqual(result, True) + self.assertEqual(mock_get_item_name_id.call_count, 2) + mock_delete_volumes_by_id_wth_cinder.assert_called_once_with( + f"volume:{volume_id}", f"{volume_id}", [], created_items + ) + mock_delete_floating_ip_by_id.assert_called_once_with( + f"floating_ip:{floating_network_vim_id}", + f"{floating_network_vim_id}", + created_items, + ) + self.vimconn.logger.error.assert_called_once_with( + "Error deleting floating_ip:108b73-e9cc-5a6a-t270-82cc4811bd4a: Connection failed." + ) + + @patch.object(vimconnector, "_get_item_name_id") + @patch.object(vimconnector, "_delete_volumes_by_id_wth_cinder") + @patch.object(vimconnector, "_delete_floating_ip_by_id") + def test_delete_created_items_get_item_name_raises( + self, + mock_delete_floating_ip_by_id, + mock_delete_volumes_by_id_wth_cinder, + mock_get_item_name_id, + ): + """Get item, name raises exception.""" + created_items = { + 3: True, + f"volume{volume_id}": True, + f"port:{port_id}": None, + } + mock_get_item_name_id.side_effect = [ + TypeError("Invalid Type"), + AttributeError("Invalid attribute"), + ] + volumes_to_hold = [] + keep_waiting = False + result = self.vimconn._delete_created_items( + created_items, volumes_to_hold, keep_waiting + ) + self.assertEqual(result, False) + self.assertEqual(mock_get_item_name_id.call_count, 2) + mock_delete_volumes_by_id_wth_cinder.assert_not_called() + mock_delete_floating_ip_by_id.assert_not_called() + _call_logger = self.vimconn.logger.error.call_args_list + self.assertEqual(_call_logger[0][0], ("Error deleting 3: Invalid Type",)) + self.assertEqual( + _call_logger[1][0], + (f"Error deleting volume{volume_id}: Invalid attribute",), + ) + + @patch.object(vimconnector, "_get_item_name_id") + @patch.object(vimconnector, "_delete_volumes_by_id_wth_cinder") + @patch.object(vimconnector, "_delete_floating_ip_by_id") + def test_delete_created_items_no_fip_wth_port( + self, + mock_delete_floating_ip_by_id, + mock_delete_volumes_by_id_wth_cinder, + mock_get_item_name_id, + ): + """Created items has port, does not have floating ip.""" + created_items = { + f"volume:{volume_id}": True, + f"port:{port_id}": True, + } + mock_get_item_name_id.side_effect = [ + ("volume", f"{volume_id}"), + ("port", f"{port_id}"), + ] + mock_delete_volumes_by_id_wth_cinder.return_value = False + volumes_to_hold = [] + keep_waiting = False + result = self.vimconn._delete_created_items( + created_items, volumes_to_hold, keep_waiting + ) + self.assertEqual(result, False) + self.assertEqual(mock_get_item_name_id.call_count, 2) + mock_delete_volumes_by_id_wth_cinder.assert_called_once_with( + f"volume:{volume_id}", f"{volume_id}", [], created_items + ) + mock_delete_floating_ip_by_id.assert_not_called() + self.vimconn.logger.error.assert_not_called() + + @patch.object(vimconnector, "_get_item_name_id") + @patch.object(vimconnector, "_delete_volumes_by_id_wth_cinder") + @patch.object(vimconnector, "_delete_floating_ip_by_id") + def test_delete_created_items_no_volume( + self, + mock_delete_floating_ip_by_id, + mock_delete_volumes_by_id_wth_cinder, + mock_get_item_name_id, + ): + """Created items does not have volume.""" + created_items = { + f"floating_ip:{floating_network_vim_id}": True, + f"port:{port_id}": None, + } + mock_get_item_name_id.side_effect = [ + ("floating_ip", f"{floating_network_vim_id}") + ] + volumes_to_hold = [] + keep_waiting = False + result = self.vimconn._delete_created_items( + created_items, volumes_to_hold, keep_waiting + ) + self.assertEqual(result, False) + self.assertEqual(mock_get_item_name_id.call_count, 1) + mock_delete_volumes_by_id_wth_cinder.assert_not_called() + mock_delete_floating_ip_by_id.assert_called_once_with( + f"floating_ip:{floating_network_vim_id}", + f"{floating_network_vim_id}", + created_items, + ) + self.vimconn.logger.error.assert_not_called() + + @patch.object(vimconnector, "_get_item_name_id") + @patch.object(vimconnector, "_delete_volumes_by_id_wth_cinder") + @patch.object(vimconnector, "_delete_floating_ip_by_id") + def test_delete_created_items_already_deleted( + self, + mock_delete_floating_ip_by_id, + mock_delete_volumes_by_id_wth_cinder, + mock_get_item_name_id, + ): + """All created items are alerady deleted.""" + created_items = { + f"floating_ip:{floating_network_vim_id}": None, + f"volume:{volume_id}": None, + f"port:{port_id}": None, + } + volumes_to_hold = [] + keep_waiting = False + result = self.vimconn._delete_created_items( + created_items, volumes_to_hold, keep_waiting + ) + self.assertEqual(result, False) + mock_get_item_name_id.assert_not_called() + mock_delete_volumes_by_id_wth_cinder.assert_not_called() + mock_delete_floating_ip_by_id.assert_not_called() + self.vimconn.logger.error.assert_not_called() + + @patch("time.sleep") + @patch.object(vimconnector, "_format_exception") + @patch.object(vimconnector, "_reload_connection") + @patch.object(vimconnector, "_delete_vm_ports_attached_to_network") + @patch.object(vimconnector, "_delete_created_items") + def test_delete_vminstance_successfully( + self, + mock_delete_created_items, + mock_delete_vm_ports_attached_to_network, + mock_reload_connection, + mock_format_exception, + mock_sleep, + ): + vm_id = f"{virtual_mac_id}" + created_items = deepcopy(created_items_all_true) + volumes_to_hold = [f"{volume_id}", f"{volume_id2}"] + mock_delete_created_items.return_value = False + self.vimconn.delete_vminstance(vm_id, created_items, volumes_to_hold) + mock_reload_connection.assert_called_once() + mock_delete_vm_ports_attached_to_network.assert_called_once_with(created_items) + self.vimconn.nova.servers.delete.assert_called_once_with(vm_id) + mock_delete_created_items.assert_called_once_with( + created_items, volumes_to_hold, False + ) + mock_sleep.assert_not_called() + mock_format_exception.assert_not_called() + + @patch("time.sleep") + @patch.object(vimconnector, "_format_exception") + @patch.object(vimconnector, "_reload_connection") + @patch.object(vimconnector, "_delete_vm_ports_attached_to_network") + @patch.object(vimconnector, "_delete_created_items") + def test_delete_vminstance_delete_created_items_raises( + self, + mock_delete_created_items, + mock_delete_vm_ports_attached_to_network, + mock_reload_connection, + mock_format_exception, + mock_sleep, + ): + """Delete creted items raises exception.""" + vm_id = f"{virtual_mac_id}" + created_items = deepcopy(created_items_all_true) + mock_sleep = MagicMock() + volumes_to_hold = [] + err = ConnectionError("ClientException occured.") + mock_delete_created_items.side_effect = err + with self.assertRaises(ConnectionError) as err: + self.vimconn.delete_vminstance(vm_id, created_items, volumes_to_hold) + self.assertEqual(str(err), "ClientException occured.") + mock_reload_connection.assert_called_once() + mock_delete_vm_ports_attached_to_network.assert_called_once_with(created_items) + self.vimconn.nova.servers.delete.assert_called_once_with(vm_id) + mock_delete_created_items.assert_called_once() + mock_sleep.assert_not_called() + + @patch("time.sleep") + @patch.object(vimconnector, "_format_exception") + @patch.object(vimconnector, "_reload_connection") + @patch.object(vimconnector, "_delete_vm_ports_attached_to_network") + @patch.object(vimconnector, "_delete_created_items") + def test_delete_vminstance_delete_vm_ports_raises( + self, + mock_delete_created_items, + mock_delete_vm_ports_attached_to_network, + mock_reload_connection, + mock_format_exception, + mock_sleep, + ): + """Delete vm ports raises exception.""" + vm_id = f"{virtual_mac_id}" + created_items = deepcopy(created_items_all_true) + volumes_to_hold = [f"{volume_id}", f"{volume_id2}"] + err = ConnectionError("ClientException occured.") + mock_delete_vm_ports_attached_to_network.side_effect = err + mock_delete_created_items.side_effect = err + with self.assertRaises(ConnectionError) as err: + self.vimconn.delete_vminstance(vm_id, created_items, volumes_to_hold) + self.assertEqual(str(err), "ClientException occured.") + mock_reload_connection.assert_called_once() + mock_delete_vm_ports_attached_to_network.assert_called_once_with(created_items) + self.vimconn.nova.servers.delete.assert_not_called() + mock_delete_created_items.assert_not_called() + mock_sleep.assert_not_called() + + @patch("time.sleep") + @patch.object(vimconnector, "_format_exception") + @patch.object(vimconnector, "_reload_connection") + @patch.object(vimconnector, "_delete_vm_ports_attached_to_network") + @patch.object(vimconnector, "_delete_created_items") + def test_delete_vminstance_nova_server_delete_raises( + self, + mock_delete_created_items, + mock_delete_vm_ports_attached_to_network, + mock_reload_connection, + mock_format_exception, + mock_sleep, + ): + """Nova server delete raises exception.""" + vm_id = f"{virtual_mac_id}" + created_items = deepcopy(created_items_all_true) + volumes_to_hold = [f"{volume_id}", f"{volume_id2}"] + err = VimConnConnectionException("ClientException occured.") + self.vimconn.nova.servers.delete.side_effect = err + mock_delete_created_items.side_effect = err + with self.assertRaises(VimConnConnectionException) as err: + self.vimconn.delete_vminstance(vm_id, created_items, volumes_to_hold) + self.assertEqual(str(err), "ClientException occured.") + mock_reload_connection.assert_called_once() + mock_delete_vm_ports_attached_to_network.assert_called_once_with(created_items) + self.vimconn.nova.servers.delete.assert_called_once_with(vm_id) + mock_delete_created_items.assert_not_called() + mock_sleep.assert_not_called() + + @patch("time.sleep") + @patch.object(vimconnector, "_format_exception") + @patch.object(vimconnector, "_reload_connection") + @patch.object(vimconnector, "_delete_vm_ports_attached_to_network") + @patch.object(vimconnector, "_delete_created_items") + def test_delete_vminstance_reload_connection_raises( + self, + mock_delete_created_items, + mock_delete_vm_ports_attached_to_network, + mock_reload_connection, + mock_format_exception, + mock_sleep, + ): + """Reload connection raises exception.""" + vm_id = f"{virtual_mac_id}" + created_items = deepcopy(created_items_all_true) + mock_sleep = MagicMock() + volumes_to_hold = [f"{volume_id}", f"{volume_id2}"] + err = ConnectionError("ClientException occured.") + mock_delete_created_items.return_value = False + mock_reload_connection.side_effect = err + with self.assertRaises(ConnectionError) as err: + self.vimconn.delete_vminstance(vm_id, created_items, volumes_to_hold) + self.assertEqual(str(err), "ClientException occured.") + mock_reload_connection.assert_called_once() + mock_delete_vm_ports_attached_to_network.assert_not_called() + self.vimconn.nova.servers.delete.assert_not_called() + mock_delete_created_items.assert_not_called() + mock_sleep.assert_not_called() + + @patch("time.sleep") + @patch.object(vimconnector, "_format_exception") + @patch.object(vimconnector, "_reload_connection") + @patch.object(vimconnector, "_delete_vm_ports_attached_to_network") + @patch.object(vimconnector, "_delete_created_items") + def test_delete_vminstance_created_item_vol_to_hold_are_none( + self, + mock_delete_created_items, + mock_delete_vm_ports_attached_to_network, + mock_reload_connection, + mock_format_exception, + mock_sleep, + ): + """created_items and volumes_to_hold are None.""" + vm_id = f"{virtual_mac_id}" + created_items = None + volumes_to_hold = None + mock_delete_created_items.return_value = False + self.vimconn.delete_vminstance(vm_id, created_items, volumes_to_hold) + mock_reload_connection.assert_called_once() + mock_delete_vm_ports_attached_to_network.assert_not_called() + self.vimconn.nova.servers.delete.assert_called_once_with(vm_id) + mock_delete_created_items.assert_called_once_with({}, [], False) + mock_sleep.assert_not_called() + mock_format_exception.assert_not_called() + + @patch("time.sleep") + @patch.object(vimconnector, "_format_exception") + @patch.object(vimconnector, "_reload_connection") + @patch.object(vimconnector, "_delete_vm_ports_attached_to_network") + @patch.object(vimconnector, "_delete_created_items") + def test_delete_vminstance_vm_id_is_none( + self, + mock_delete_created_items, + mock_delete_vm_ports_attached_to_network, + mock_reload_connection, + mock_format_exception, + mock_sleep, + ): + """vm_id is None.""" + vm_id = None + created_items = deepcopy(created_items_all_true) + volumes_to_hold = [f"{volume_id}", f"{volume_id2}"] + mock_delete_created_items.side_effect = [True, True, False] + self.vimconn.delete_vminstance(vm_id, created_items, volumes_to_hold) + mock_reload_connection.assert_called_once() + mock_delete_vm_ports_attached_to_network.assert_called_once_with(created_items) + self.vimconn.nova.servers.delete.assert_not_called() + self.assertEqual(mock_delete_created_items.call_count, 3) + self.assertEqual(mock_sleep.call_count, 2) + mock_format_exception.assert_not_called() + + @patch("time.sleep") + @patch.object(vimconnector, "_format_exception") + @patch.object(vimconnector, "_reload_connection") + @patch.object(vimconnector, "_delete_vm_ports_attached_to_network") + @patch.object(vimconnector, "_delete_created_items") + def test_delete_vminstance_delete_created_items_return_true( + self, + mock_delete_created_items, + mock_delete_vm_ports_attached_to_network, + mock_reload_connection, + mock_format_exception, + mock_sleep, + ): + """Delete created items always return True.""" + vm_id = None + created_items = deepcopy(created_items_all_true) + volumes_to_hold = [f"{volume_id}", f"{volume_id2}"] + mock_delete_created_items.side_effect = [True] * 1800 + self.vimconn.delete_vminstance(vm_id, created_items, volumes_to_hold) + mock_reload_connection.assert_called_once() + mock_delete_vm_ports_attached_to_network.assert_called_once_with(created_items) + self.vimconn.nova.servers.delete.assert_not_called() + self.assertEqual(mock_delete_created_items.call_count, 1800) + self.assertEqual(mock_sleep.call_count, 1800) + mock_format_exception.assert_not_called() + if __name__ == "__main__": unittest.main() diff --git a/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py b/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py index 993dccdf..b18cf783 100644 --- a/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py +++ b/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py @@ -2689,76 +2689,160 @@ class vimconnector(vimconn.VimConnector): ) as e: self._format_exception(e) - def delete_vminstance(self, vm_id, created_items=None, volumes_to_hold=None): - """Removes a VM instance from VIM. Returns the old identifier""" - # print "osconnector: Getting VM from VIM" + 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 + + 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: self._reload_connection() - # delete VM ports attached to this networks before the virtual machine - for k, v in created_items.items(): - if not v: # skip already deleted - continue - - try: - k_item, _, k_id = k.partition(":") - if k_item == "port": - 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) - ) - # #commented because detaching the volumes makes the servers.delete not work properly ?!? - # #dettach volumes attached - # server = self.nova.servers.get(vm_id) - # volumes_attached_dict = server._info["os-extended-volumes:volumes_attached"] #volume["id"] - # #for volume in volumes_attached_dict: - # # self.cinder.volumes.detach(volume["id"]) + # 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) - # delete volumes. Although having detached, they should have in active status before deleting - # we ensure in this loop + # 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 - for k, v in created_items.items(): - if not v: # skip already deleted - continue - - try: - k_item, _, k_id = k.partition(":") - if k_item == "volume": - if self.cinder.volumes.get(k_id).status != "available": - keep_waiting = True - else: - if k_id not in volumes_to_hold: - self.cinder.volumes.delete(k_id) - created_items[k] = None - elif k_item == "floating_ip": # floating ip - self.neutron.delete_floatingip(k_id) - created_items[k] = None - - except Exception as e: - self.logger.error("Error deleting {}: {}".format(k, e)) + # 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 - return None except ( nvExceptions.NotFound, ksExceptions.ClientException, diff --git a/releasenotes/notes/refactoring_process_vdu_params-45301da2e7d933de.yaml b/releasenotes/notes/refactoring_process_vdu_params-45301da2e7d933de.yaml new file mode 100644 index 00000000..6f5bead6 --- /dev/null +++ b/releasenotes/notes/refactoring_process_vdu_params-45301da2e7d933de.yaml @@ -0,0 +1,24 @@ +####################################################################################### +# Copyright ETSI Contributors and Others. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +####################################################################################### +--- +other: + - | + Refactoring NG_RO/ns.py _process_vdu_params + and vimconn_openstack.py delete_vminstance methods together with adding unit tests. + + + -- 2.17.1