Fix Bug 2304: Flavor Not getting Updated in Mongodb nsrs after Vertical scaling
[osm/RO.git] / NG-RO / osm_ng_ro / ns.py
index c6f918b..3a223bc 100644 (file)
@@ -117,6 +117,7 @@ class Ns(object):
             "flavor": Ns._process_flavor_params,
             "vdu": Ns._process_vdu_params,
             "affinity-or-anti-affinity-group": Ns._process_affinity_group_params,
+            "shared-volumes": Ns._process_shared_volumes_params,
         }
         self.db_path_map = {
             "net": "vld",
@@ -124,6 +125,7 @@ class Ns(object):
             "flavor": "flavor",
             "vdu": "vdur",
             "affinity-or-anti-affinity-group": "affinity-or-anti-affinity-group",
+            "shared-volumes": "shared-volumes",
         }
 
     def init_db(self, target_version):
@@ -722,9 +724,12 @@ class Ns(object):
             guest_epa_quota.get("cpu-pinning-policy") == "DEDICATED"
             and not epa_vcpu_set
         ):
+            # Pinning policy "REQUIRE" uses threads as host should support SMT architecture
+            # Pinning policy "ISOLATE" uses cores as host should not support SMT architecture
+            # Pinning policy "PREFER" uses threads in case host supports SMT architecture
             numa[
                 "cores"
-                if guest_epa_quota.get("cpu-thread-pinning-policy") != "PREFER"
+                if guest_epa_quota.get("cpu-thread-pinning-policy") == "ISOLATE"
                 else "threads"
             ] = max(vcpu_count, 1)
             local_epa_vcpu_set = True
@@ -813,17 +818,23 @@ class Ns(object):
         db = kwargs.get("db")
         target_vdur = {}
 
+        for vnf in indata.get("vnf", []):
+            for vdur in vnf.get("vdur", []):
+                if vdur.get("ns-flavor-id") == target_flavor.get("id"):
+                    target_vdur = vdur
+
+        vim_flavor_id = (
+            target_vdur.get("additionalParams", {}).get("OSM", {}).get("vim_flavor_id")
+        )
+        if vim_flavor_id:  # vim-flavor-id was passed so flavor won't be created
+            return {"find_params": {"vim_flavor_id": vim_flavor_id}}
+
         flavor_data = {
             "disk": int(target_flavor["storage-gb"]),
             "ram": int(target_flavor["memory-mb"]),
             "vcpus": int(target_flavor["vcpu-count"]),
         }
 
-        for vnf in indata.get("vnf", []):
-            for vdur in vnf.get("vdur", []):
-                if vdur.get("ns-flavor-id") == target_flavor.get("id"):
-                    target_vdur = vdur
-
         if db and isinstance(indata.get("vnf"), list):
             vnfd_id = indata.get("vnf")[0].get("vnfd-id")
             vnfd = db.get_one("vnfds", {"_id": vnfd_id})
@@ -856,47 +867,8 @@ class Ns(object):
         flavor_data_name = flavor_data.copy()
         flavor_data_name["name"] = target_flavor["name"]
         extra_dict["params"] = {"flavor_data": flavor_data_name}
-
         return extra_dict
 
-    @staticmethod
-    def _ip_profile_to_ro(
-        ip_profile: Dict[str, Any],
-    ) -> Dict[str, Any]:
-        """[summary]
-
-        Args:
-            ip_profile (Dict[str, Any]): [description]
-
-        Returns:
-            Dict[str, Any]: [description]
-        """
-        if not ip_profile:
-            return None
-
-        ro_ip_profile = {
-            "ip_version": "IPv4"
-            if "v4" in ip_profile.get("ip-version", "ipv4")
-            else "IPv6",
-            "subnet_address": ip_profile.get("subnet-address"),
-            "gateway_address": ip_profile.get("gateway-address"),
-            "dhcp_enabled": ip_profile.get("dhcp-params", {}).get("enabled", False),
-            "dhcp_start_address": ip_profile.get("dhcp-params", {}).get(
-                "start-address", None
-            ),
-            "dhcp_count": ip_profile.get("dhcp-params", {}).get("count", None),
-        }
-
-        if ip_profile.get("dns-server"):
-            ro_ip_profile["dns_address"] = ";".join(
-                [v["address"] for v in ip_profile["dns-server"] if v.get("address")]
-            )
-
-        if ip_profile.get("security-group"):
-            ro_ip_profile["security_group"] = ip_profile["security-group"]
-
-        return ro_ip_profile
-
     @staticmethod
     def _process_net_params(
         target_vld: Dict[str, Any],
@@ -949,7 +921,7 @@ class Ns(object):
                     "id": vim_info.get("vim_network_id"),
                 },
             }
-        elif target_vld.get("mgmt-network"):
+        elif target_vld.get("mgmt-network") and not vim_info.get("provider_network"):
             extra_dict["find_params"] = {
                 "mgmt": True,
                 "name": target_vld["id"],
@@ -960,7 +932,7 @@ class Ns(object):
                 "net_name": (
                     f"{indata.get('name')[:16]}-{target_vld.get('name', target_vld.get('id'))[:16]}"
                 ),
-                "ip_profile": Ns._ip_profile_to_ro(vim_info.get("ip_profile")),
+                "ip_profile": vim_info.get("ip_profile"),
                 "provider_network_profile": vim_info.get("provider_network"),
             }
 
@@ -1009,12 +981,10 @@ class Ns(object):
                     == "persistent-storage:persistent-storage"
                 ):
                     for vdu_volume in vdu_instantiation_volumes_list:
-
                         if (
                             vdu_volume["vim-volume-id"]
                             and root_disk["id"] == vdu_volume["name"]
                         ):
-
                             persistent_root_disk[vsd["id"]] = {
                                 "vim_volume_id": vdu_volume["vim-volume-id"],
                                 "image_id": vdu.get("sw-image-desc"),
@@ -1025,16 +995,17 @@ class Ns(object):
                             return persistent_root_disk
 
                     else:
-
                         if root_disk.get("size-of-storage"):
                             persistent_root_disk[vsd["id"]] = {
                                 "image_id": vdu.get("sw-image-desc"),
                                 "size": root_disk.get("size-of-storage"),
+                                "keep": Ns.is_volume_keeping_required(root_disk),
                             }
 
                             disk_list.append(persistent_root_disk[vsd["id"]])
 
                             return persistent_root_disk
+                return persistent_root_disk
 
     @staticmethod
     def find_persistent_volumes(
@@ -1061,9 +1032,7 @@ class Ns(object):
                 and disk["id"] not in persistent_root_disk.keys()
             ):
                 for vdu_volume in vdu_instantiation_volumes_list:
-
                     if vdu_volume["vim-volume-id"] and disk["id"] == vdu_volume["name"]:
-
                         persistent_disk[disk["id"]] = {
                             "vim_volume_id": vdu_volume["vim-volume-id"],
                         }
@@ -1073,9 +1042,56 @@ class Ns(object):
                     if disk["id"] not in persistent_disk.keys():
                         persistent_disk[disk["id"]] = {
                             "size": disk.get("size-of-storage"),
+                            "keep": Ns.is_volume_keeping_required(disk),
                         }
                         disk_list.append(persistent_disk[disk["id"]])
 
+    @staticmethod
+    def is_volume_keeping_required(virtual_storage_desc: Dict[str, Any]) -> bool:
+        """Function to decide keeping persistent volume
+        upon VDU deletion.
+
+        Args:
+            virtual_storage_desc (Dict[str, Any]): virtual storage description dictionary
+
+        Returns:
+            bool (True/False)
+        """
+
+        if not virtual_storage_desc.get("vdu-storage-requirements"):
+            return False
+        for item in virtual_storage_desc.get("vdu-storage-requirements", {}):
+            if item.get("key") == "keep-volume" and item.get("value").lower() == "true":
+                return True
+        return False
+
+    @staticmethod
+    def is_shared_volume(
+        virtual_storage_desc: Dict[str, Any], vnfd_id: str
+    ) -> (str, bool):
+        """Function to decide if the volume type is multi attached or not .
+
+        Args:
+            virtual_storage_desc (Dict[str, Any]): virtual storage description dictionary
+            vnfd_id (str): vnfd id
+
+        Returns:
+            bool (True/False)
+            name (str) New name if it is a multiattach disk
+        """
+
+        if vdu_storage_requirements := virtual_storage_desc.get(
+            "vdu-storage-requirements", {}
+        ):
+            for item in vdu_storage_requirements:
+                if (
+                    item.get("key") == "multiattach"
+                    and item.get("value").lower() == "true"
+                ):
+                    name = f"shared-{virtual_storage_desc['id']}-{vnfd_id}"
+                    return name, True
+        return virtual_storage_desc["id"], False
+
     @staticmethod
     def _sort_vdu_interfaces(target_vdu: dict) -> None:
         """Sort the interfaces according to position number.
@@ -1302,7 +1318,6 @@ class Ns(object):
             net_list    (list):                             Net list of VDU
         """
         for iface_index, interface in enumerate(target_vdu["interfaces"]):
-
             net_text = Ns._check_vld_information_of_interfaces(
                 interface, ns_preffix, vnf_preffix
             )
@@ -1397,15 +1412,14 @@ class Ns(object):
             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"],
+                        "keep": Ns.is_volume_keeping_required(root_disk),
                     }
-
                     disk_list.append(persistent_root_disk[vsd["id"]])
                     break
 
@@ -1415,6 +1429,9 @@ class Ns(object):
         persistent_root_disk: dict,
         persistent_ordinary_disk: dict,
         disk_list: list,
+        extra_dict: dict,
+        vnf_id: str = None,
+        nsr_id: str = None,
     ) -> None:
         """Fill the disk list by adding persistent ordinary disks.
 
@@ -1432,10 +1449,18 @@ class Ns(object):
                     == "persistent-storage:persistent-storage"
                     and disk["id"] not in persistent_root_disk.keys()
                 ):
+                    name, multiattach = Ns.is_shared_volume(disk, vnf_id)
                     persistent_ordinary_disk[disk["id"]] = {
+                        "name": name,
                         "size": disk["size-of-storage"],
+                        "keep": Ns.is_volume_keeping_required(disk),
+                        "multiattach": multiattach,
                     }
                     disk_list.append(persistent_ordinary_disk[disk["id"]])
+                    if multiattach:  # VDU creation has to wait for shared volumes
+                        extra_dict["depends_on"].append(
+                            f"nsrs:{nsr_id}:shared-volumes.{name}"
+                        )
 
     @staticmethod
     def _prepare_vdu_affinity_group_list(
@@ -1506,27 +1531,23 @@ class Ns(object):
         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
@@ -1549,11 +1570,10 @@ class Ns(object):
 
         if target_vdu.get("additionalParams"):
             vdu_instantiation_volumes_list = (
-                target_vdu.get("additionalParams").get("OSM").get("vdu_volumes")
+                target_vdu.get("additionalParams").get("OSM", {}).get("vdu_volumes")
             )
 
         if vdu_instantiation_volumes_list:
-
             # Find the root volumes and add to the disk_list
             persistent_root_disk = Ns.find_persistent_root_volumes(
                 vnfd, target_vdu, vdu_instantiation_volumes_list, disk_list
@@ -1576,7 +1596,13 @@ class Ns(object):
             )
             # 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
+                target_vdu,
+                persistent_root_disk,
+                persistent_ordinary_disk,
+                disk_list,
+                extra_dict,
+                vnfd["id"],
+                nsr_id,
             )
 
         affinity_group_list = Ns._prepare_vdu_affinity_group_list(
@@ -1601,7 +1627,24 @@ class Ns(object):
             "availability_zone_index": None,  # TODO
             "availability_zone_list": None,  # TODO
         }
+        return extra_dict
 
+    @staticmethod
+    def _process_shared_volumes_params(
+        target_shared_volume: Dict[str, Any],
+        indata: Dict[str, Any],
+        vim_info: Dict[str, Any],
+        target_record_id: str,
+        **kwargs: Dict[str, Any],
+    ) -> Dict[str, Any]:
+        extra_dict = {}
+        shared_volume_data = {
+            "size": target_shared_volume["size-of-storage"],
+            "name": target_shared_volume["id"],
+            "type": target_shared_volume["type-of-storage"],
+            "keep": Ns.is_volume_keeping_required(target_shared_volume),
+        }
+        extra_dict["params"] = shared_volume_data
         return extra_dict
 
     @staticmethod
@@ -1639,7 +1682,6 @@ class Ns(object):
         extra_dict["params"] = {
             "affinity_group_data": affinity_group_data,
         }
-
         return extra_dict
 
     @staticmethod
@@ -1675,11 +1717,11 @@ class Ns(object):
 
         vim_details = {}
         vim_details_text = existing_vdu["vim_info"][target_id].get("vim_details", None)
+
         if vim_details_text:
             vim_details = yaml.safe_load(f"{vim_details_text}")
 
         for iface_index, interface in enumerate(existing_vdu["interfaces"]):
-
             if "port-security-enabled" in interface:
                 interface["port_security"] = interface.pop("port-security-enabled")
 
@@ -1869,7 +1911,6 @@ class Ns(object):
         process_params = None
         vdu2cloud_init = indata.get("cloud_init_content") or {}
         ro_nsr_public_key = db_ro_nsr["public_key"]
-
         # According to the type of item, the path, the target_list,
         # the existing_list and the method to process params are set
         db_path = self.db_path_map[item]
@@ -1889,27 +1930,29 @@ class Ns(object):
                 )
                 target_list = target_vnf.get(db_path, []) if target_vnf else []
                 existing_list = vnfr.get(db_path, [])
-        elif item in ("image", "flavor", "affinity-or-anti-affinity-group"):
+        elif item in (
+            "image",
+            "flavor",
+            "affinity-or-anti-affinity-group",
+            "shared-volumes",
+        ):
             db_record = "nsrs:{}:{}".format(nsr_id, db_path)
             target_list = indata.get(item, [])
             existing_list = db_nsr.get(item, [])
         else:
             raise NsException("Item not supported: {}", item)
-
         # ensure all the target_list elements has an "id". If not assign the index as id
         if target_list is None:
             target_list = []
         for target_index, tl in enumerate(target_list):
             if tl and not tl.get("id"):
                 tl["id"] = str(target_index)
-
         # step 1 items (networks,vdus,...) to be deleted/updated
         for item_index, existing_item in enumerate(existing_list):
             target_item = next(
                 (t for t in target_list if t["id"] == existing_item["id"]),
                 None,
             )
-
             for target_vim, existing_viminfo in existing_item.get(
                 "vim_info", {}
             ).items():
@@ -1953,7 +1996,6 @@ class Ns(object):
         # step 2 items (networks,vdus,...) to be created
         for target_item in target_list:
             item_index = -1
-
             for item_index, existing_item in enumerate(existing_list):
                 if existing_item["id"] == target_item["id"]:
                     break
@@ -2010,7 +2052,6 @@ class Ns(object):
                         }
                     )
                     self.logger.debug("calculate_diff_items kwargs={}".format(kwargs))
-
                 extra_dict = process_params(
                     target_item,
                     indata,
@@ -2080,7 +2121,12 @@ class Ns(object):
         changes_list = []
 
         # NS vld, image and flavor
-        for item in ["net", "image", "flavor", "affinity-or-anti-affinity-group"]:
+        for item in [
+            "net",
+            "image",
+            "flavor",
+            "affinity-or-anti-affinity-group",
+        ]:
             self.logger.debug("process NS={} {}".format(nsr_id, item))
             diff_items, task_index = self.calculate_diff_items(
                 indata=indata,
@@ -2099,7 +2145,7 @@ class Ns(object):
         # VNF vlds and vdus
         for vnfr_id, vnfr in db_vnfrs.items():
             # vnfr_id need to be set as global variable for among others nested method _process_vdu_params
-            for item in ["net", "vdu"]:
+            for item in ["net", "vdu", "shared-volumes"]:
                 self.logger.debug("process VNF={} {}".format(vnfr_id, item))
                 diff_items, task_index = self.calculate_diff_items(
                     indata=indata,
@@ -2326,30 +2372,39 @@ class Ns(object):
 
         # Check each VNF of the target
         for target_vnf in target_list:
-            # Find this VNF in the list from DB
-            vnfr_id = target_vnf.get("vnfInstanceId", None)
-            if vnfr_id:
-                existing_vnf = db_vnfrs.get(vnfr_id)
-                db_record = "vnfrs:{}:{}".format(vnfr_id, db_path)
-                # vim_account_id = existing_vnf.get("vim-account-id", "")
+            # Find this VNF in the list from DB, raise exception if vnfInstanceId is not found
+            vnfr_id = target_vnf["vnfInstanceId"]
+            existing_vnf = db_vnfrs.get(vnfr_id, {})
+            db_record = "vnfrs:{}:{}".format(vnfr_id, db_path)
+            # vim_account_id = existing_vnf.get("vim-account-id", "")
 
+            target_vdus = target_vnf.get("additionalParams", {}).get("vdu", [])
             # Check each VDU of this VNF
-            for target_vdu in target_vnf["additionalParams"].get("vdu", None):
+            if not target_vdus:
+                # Create target_vdu_list from DB, if VDUs are not specified
+                target_vdus = []
+                for existing_vdu in existing_vnf.get("vdur"):
+                    vdu_name = existing_vdu.get("vdu-name", None)
+                    vdu_index = existing_vdu.get("count-index", 0)
+                    vdu_to_be_healed = {"vdu-id": vdu_name, "count-index": vdu_index}
+                    target_vdus.append(vdu_to_be_healed)
+            for target_vdu in target_vdus:
                 vdu_name = target_vdu.get("vdu-id", None)
                 # For multi instance VDU count-index is mandatory
                 # For single session VDU count-indes is 0
                 count_index = target_vdu.get("count-index", 0)
                 item_index = 0
-                existing_instance = None
-                for instance in existing_vnf.get("vdur", None):
-                    if (
-                        instance["vdu-name"] == vdu_name
-                        and instance["count-index"] == count_index
-                    ):
-                        existing_instance = instance
-                        break
-                    else:
-                        item_index += 1
+                existing_instance = {}
+                if existing_vnf:
+                    for instance in existing_vnf.get("vdur", {}):
+                        if (
+                            instance["vdu-name"] == vdu_name
+                            and instance["count-index"] == count_index
+                        ):
+                            existing_instance = instance
+                            break
+                        else:
+                            item_index += 1
 
                 target_record_id = "{}.{}".format(db_record, existing_instance["id"])
 
@@ -2838,7 +2893,9 @@ class Ns(object):
         extra_dict,
     ):
         self._assign_vim(target_vim)
-        target_record = "vnfrs:{}:vdur.{}".format(vnf_id, vdu_index)
+        target_record = "vnfrs:{}:vdur.{}.vim_info.{}".format(
+            vnf_id, vdu_index, target_vim
+        )
         target_record_id = "vnfrs:{}:vdur.{}".format(vnf_id, vdu_id)
         deployment_info = {
             "action_id": action_id,
@@ -2944,7 +3001,9 @@ class Ns(object):
     ):
         target_vim, vim_info = next(k_v for k_v in vdu["vim_info"].items())
         self._assign_vim(target_vim)
-        target_record = "vnfrs:{}:vdur.{}".format(vnf["_id"], vdu_index)
+        target_record = "vnfrs:{}:vdur.{}.vim_info.{}".format(
+            vnf["_id"], vdu_index, target_vim
+        )
         target_record_id = "vnfrs:{}:vdur.{}".format(vnf["_id"], vdu["id"])
         deployment_info = {
             "action_id": action_id,
@@ -3008,7 +3067,6 @@ class Ns(object):
                             task_index += 1
                             break
                 else:
-
                     for vdu_index, vdu in enumerate(db_vnfr["vdur"]):
                         extra_dict["params"] = {
                             "vim_vm_id": vdu["vim-id"],
@@ -3059,7 +3117,13 @@ class Ns(object):
     ):
         target_vim, vim_info = next(k_v for k_v in vdu["vim_info"].items())
         self._assign_vim(target_vim)
-        target_record = "vnfrs:{}:vdur.{}".format(vnf["_id"], vdu_index)
+        ns_preffix = "nsrs:{}".format(nsr_id)
+        flavor_text = ns_preffix + ":flavor." + vdu["ns-flavor-id"]
+        extra_dict["depends_on"] = [flavor_text]
+        extra_dict["params"].update({"flavor_id": "TASK-" + flavor_text})
+        target_record = "vnfrs:{}:vdur.{}.vim_info.{}".format(
+            vnf["_id"], vdu_index, target_vim
+        )
         target_record_id = "vnfrs:{}:vdur.{}".format(vnf["_id"], vdu["id"])
         deployment_info = {
             "action_id": action_id,
@@ -3078,9 +3142,36 @@ class Ns(object):
         )
         return task
 
+    def verticalscale_flavor_task(
+        self, vdu, vnf, vdu_index, action_id, nsr_id, task_index, extra_dict
+    ):
+        target_vim, vim_info = next(k_v for k_v in vdu["vim_info"].items())
+        self._assign_vim(target_vim)
+        db_nsr = self.db.get_one("nsrs", {"_id": nsr_id})
+        target_record = "nsrs:{}:flavor.{}.vim_info.{}".format(
+            nsr_id, len(db_nsr["flavor"]) - 1, target_vim
+        )
+        target_record_id = "nsrs:{}:flavor.{}".format(nsr_id, len(db_nsr["flavor"]) - 1)
+        deployment_info = {
+            "action_id": action_id,
+            "nsr_id": nsr_id,
+            "task_index": task_index,
+        }
+        task = Ns._create_task(
+            deployment_info=deployment_info,
+            target_id=target_vim,
+            item="flavor",
+            action="CREATE",
+            target_record=target_record,
+            target_record_id=target_record_id,
+            extra_dict=extra_dict,
+        )
+        return task
+
     def verticalscale(self, session, indata, version, nsr_id, *args, **kwargs):
         task_index = 0
         extra_dict = {}
+        flavor_extra_dict = {}
         now = time()
         action_id = indata.get("action_id", str(uuid4()))
         step = ""
@@ -3102,6 +3193,13 @@ class Ns(object):
                 "vcpus": numVirtualCpu,
                 "disk": sizeOfStorage,
             }
+            flavor_data = {
+                "ram": virtualMemory,
+                "vcpus": numVirtualCpu,
+                "disk": sizeOfStorage,
+            }
+            flavor_extra_dict["find_params"] = {"flavor_data": flavor_data}
+            flavor_extra_dict["params"] = {"flavor_data": flavor_dict}
             db_new_tasks = []
             step = "Creating Tasks for vertical scaling"
             with self.write_lock:
@@ -3113,7 +3211,21 @@ class Ns(object):
                         extra_dict["params"] = {
                             "vim_vm_id": vdu["vim-id"],
                             "flavor_dict": flavor_dict,
+                            "vdu-id-ref": vdu["vdu-id-ref"],
+                            "count-index": vdu["count-index"],
+                            "vnf_instance_id": vnf_instance_id,
                         }
+                        task = self.verticalscale_flavor_task(
+                            vdu,
+                            db_vnfr,
+                            vdu_index,
+                            action_id,
+                            nsr_id,
+                            task_index,
+                            flavor_extra_dict,
+                        )
+                        db_new_tasks.append(task)
+                        task_index += 1
                         task = self.verticalscale_task(
                             vdu,
                             db_vnfr,