+ net_list.append(net_item)
+
+ if interface.get("mgmt-vnf"):
+ extra_dict["mgmt_vnf_interface"] = iface_index
+ 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"],
+ )
+
+ if target_vdu.get("boot-data-drive"):
+ cloud_config["boot-data-drive"] = target_vdu.get("boot-data-drive")
+
+ ssh_keys = []
+
+ if target_vdu.get("ssh-keys"):
+ ssh_keys += target_vdu.get("ssh-keys")
+
+ if target_vdu.get("ssh-access-required"):
+ ssh_keys.append(ro_nsr_public_key)
+
+ if ssh_keys:
+ cloud_config["key-pairs"] = ssh_keys
+
+ persistent_root_disk = {}
+ disk_list = []
+ vnfd_id = vnfr["vnfd-id"]
+ vnfd = db.get_one("vnfds", {"_id": vnfd_id})
+ 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)
+
+ extra_dict["params"] = {
+ "name": "{}-{}-{}-{}".format(
+ indata["name"][:16],
+ vnfr["member-vnf-index-ref"][:16],
+ target_vdu["vdu-name"][:32],
+ target_vdu.get("count-index") or 0,
+ ),
+ "description": target_vdu["vdu-name"],
+ "start": True,
+ "image_id": "TASK-" + image_text,
+ "flavor_id": "TASK-" + flavor_text,
+ "affinity_group_list": affinity_group_list,
+ "net_list": net_list,
+ "cloud_config": cloud_config or None,
+ "disk_list": disk_list,
+ "availability_zone_index": None, # TODO
+ "availability_zone_list": None, # TODO
+ }
+
+ return extra_dict
+
+ @staticmethod
+ def _process_affinity_group_params(
+ target_affinity_group: Dict[str, Any],
+ indata: Dict[str, Any],
+ vim_info: Dict[str, Any],
+ target_record_id: str,
+ **kwargs: Dict[str, Any],
+ ) -> Dict[str, Any]:
+ """Get affinity or anti-affinity group parameters.
+
+ Args:
+ target_affinity_group (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]
+ """
+
+ extra_dict = {}
+ affinity_group_data = {
+ "name": target_affinity_group["name"],
+ "type": target_affinity_group["type"],
+ "scope": target_affinity_group["scope"],
+ }
+
+ if target_affinity_group.get("vim-affinity-group-id"):
+ affinity_group_data["vim-affinity-group-id"] = target_affinity_group[
+ "vim-affinity-group-id"
+ ]
+
+ extra_dict["params"] = {
+ "affinity_group_data": affinity_group_data,
+ }
+
+ return extra_dict
+
+ @staticmethod
+ def _process_recreate_vdu_params(
+ existing_vdu: Dict[str, Any],
+ db_nsr: Dict[str, Any],
+ vim_info: Dict[str, Any],
+ target_record_id: str,
+ target_id: str,
+ **kwargs: Dict[str, Any],
+ ) -> Dict[str, Any]:
+ """Function to process VDU parameters to recreate.
+
+ Args:
+ existing_vdu (Dict[str, Any]): [description]
+ db_nsr (Dict[str, Any]): [description]
+ vim_info (Dict[str, Any]): [description]
+ target_record_id (str): [description]
+ target_id (str): [description]
+
+ Returns:
+ Dict[str, Any]: [description]
+ """
+ vnfr = kwargs.get("vnfr")
+ vdu2cloud_init = kwargs.get("vdu2cloud_init")
+ # logger = kwargs.get("logger")
+ db = kwargs.get("db")
+ fs = kwargs.get("fs")
+ ro_nsr_public_key = kwargs.get("ro_nsr_public_key")
+
+ extra_dict = {}
+ net_list = []
+
+ 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")
+
+ 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",
+ )
+ }
+ existing_ifaces = existing_vdu["vim_info"][target_id].get(
+ "interfaces_backup", []
+ )
+ net_id = next(
+ (
+ i["vim_net_id"]
+ for i in existing_ifaces
+ if i["ip_address"] == interface["ip-address"]
+ ),
+ None,
+ )
+
+ net_item["net_id"] = net_id
+ net_item["type"] = "virtual"
+
+ # 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"):
+ 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")
+
+ if interface.get("ip-address"):
+ net_item["ip_address"] = interface["ip-address"]
+
+ if interface.get("mac-address"):
+ net_item["mac_address"] = interface["mac-address"]
+
+ net_list.append(net_item)
+
+ if interface.get("mgmt-vnf"):
+ extra_dict["mgmt_vnf_interface"] = iface_index
+ elif interface.get("mgmt-interface"):
+ extra_dict["mgmt_vdu_interface"] = iface_index
+
+ # cloud config
+ cloud_config = {}
+
+ if existing_vdu.get("cloud-init"):
+ if existing_vdu["cloud-init"] not in vdu2cloud_init:
+ vdu2cloud_init[existing_vdu["cloud-init"]] = Ns._get_cloud_init(
+ db=db,
+ fs=fs,
+ location=existing_vdu["cloud-init"],
+ )
+
+ cloud_content_ = vdu2cloud_init[existing_vdu["cloud-init"]]
+ cloud_config["user-data"] = Ns._parse_jinja2(
+ cloud_init_content=cloud_content_,
+ params=existing_vdu.get("additionalParams"),
+ context=existing_vdu["cloud-init"],
+ )
+
+ if existing_vdu.get("boot-data-drive"):
+ cloud_config["boot-data-drive"] = existing_vdu.get("boot-data-drive")
+
+ ssh_keys = []
+
+ if existing_vdu.get("ssh-keys"):
+ ssh_keys += existing_vdu.get("ssh-keys")
+
+ if existing_vdu.get("ssh-access-required"):
+ ssh_keys.append(ro_nsr_public_key)
+
+ if ssh_keys:
+ cloud_config["key-pairs"] = ssh_keys
+
+ disk_list = []
+ for vol_id in vim_details.get("os-extended-volumes:volumes_attached", []):
+ disk_list.append({"vim_id": vol_id["id"]})
+
+ affinity_group_list = []
+
+ if existing_vdu.get("affinity-or-anti-affinity-group-id"):
+ affinity_group = {}
+ for affinity_group_id in existing_vdu["affinity-or-anti-affinity-group-id"]:
+ for group in db_nsr.get("affinity-or-anti-affinity-group"):
+ if (
+ group["id"] == affinity_group_id
+ and group["vim_info"][target_id].get("vim_id", None) is not None
+ ):
+ affinity_group["affinity_group_id"] = group["vim_info"][
+ target_id
+ ].get("vim_id", None)
+ affinity_group_list.append(affinity_group)
+
+ extra_dict["params"] = {
+ "name": "{}-{}-{}-{}".format(
+ db_nsr["name"][:16],
+ vnfr["member-vnf-index-ref"][:16],
+ existing_vdu["vdu-name"][:32],
+ existing_vdu.get("count-index") or 0,
+ ),
+ "description": existing_vdu["vdu-name"],
+ "start": True,
+ "image_id": vim_details["image"]["id"],
+ "flavor_id": vim_details["flavor"]["id"],
+ "affinity_group_list": affinity_group_list,
+ "net_list": net_list,
+ "cloud_config": cloud_config or None,
+ "disk_list": disk_list,
+ "availability_zone_index": None, # TODO
+ "availability_zone_list": None, # TODO
+ }
+
+ return extra_dict
+
+ def calculate_diff_items(
+ self,
+ indata,
+ db_nsr,
+ db_ro_nsr,
+ db_nsr_update,
+ item,
+ tasks_by_target_record_id,
+ action_id,
+ nsr_id,
+ task_index,
+ vnfr_id=None,
+ vnfr=None,
+ ):
+ """Function that returns the incremental changes (creation, deletion)
+ related to a specific item `item` to be done. This function should be
+ called for NS instantiation, NS termination, NS update to add a new VNF
+ or a new VLD, remove a VNF or VLD, etc.
+ Item can be `net`, `flavor`, `image` or `vdu`.
+ It takes a list of target items from indata (which came from the REST API)
+ and compares with the existing items from db_ro_nsr, identifying the
+ incremental changes to be done. During the comparison, it calls the method
+ `process_params` (which was passed as parameter, and is particular for each
+ `item`)
+
+ Args:
+ indata (Dict[str, Any]): deployment info
+ db_nsr: NSR record from DB
+ db_ro_nsr (Dict[str, Any]): record from "ro_nsrs"
+ db_nsr_update (Dict[str, Any]): NSR info to update in DB
+ item (str): element to process (net, vdu...)
+ tasks_by_target_record_id (Dict[str, Any]):
+ [<target_record_id>, <task>]
+ action_id (str): action id
+ nsr_id (str): NSR id
+ task_index (number): task index to add to task name
+ vnfr_id (str): VNFR id
+ vnfr (Dict[str, Any]): VNFR info
+
+ Returns:
+ List: list with the incremental changes (deletes, creates) for each item
+ number: current task index
+ """
+
+ diff_items = []
+ db_path = ""
+ db_record = ""
+ target_list = []
+ existing_list = []
+ 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]
+ process_params = self.process_params_function_map[item]
+ if item in ("net", "vdu"):
+ # This case is specific for the NS VLD (not applied to VDU)
+ if vnfr is None:
+ db_record = "nsrs:{}:{}".format(nsr_id, db_path)
+ target_list = indata.get("ns", []).get(db_path, [])
+ existing_list = db_nsr.get(db_path, [])
+ # This case is common for VNF VLDs and VNF VDUs
+ else:
+ db_record = "vnfrs:{}:{}".format(vnfr_id, db_path)
+ target_vnf = next(
+ (vnf for vnf in indata.get("vnf", ()) if vnf["_id"] == vnfr_id),
+ None,
+ )
+ 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"):
+ 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():
+ if existing_viminfo is None:
+ continue
+
+ if target_item:
+ target_viminfo = target_item.get("vim_info", {}).get(target_vim)
+ else:
+ target_viminfo = None
+
+ if target_viminfo is None:
+ # must be deleted
+ self._assign_vim(target_vim)
+ target_record_id = "{}.{}".format(db_record, existing_item["id"])
+ item_ = item
+
+ if target_vim.startswith("sdn"):
+ # item must be sdn-net instead of net if target_vim is a sdn
+ item_ = "sdn_net"
+ target_record_id += ".sdn"
+
+ deployment_info = {
+ "action_id": action_id,
+ "nsr_id": nsr_id,
+ "task_index": task_index,
+ }
+
+ diff_items.append(
+ {
+ "deployment_info": deployment_info,
+ "target_id": target_vim,
+ "item": item_,
+ "action": "DELETE",
+ "target_record": f"{db_record}.{item_index}.vim_info.{target_vim}",
+ "target_record_id": target_record_id,
+ }
+ )
+ task_index += 1
+
+ # 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
+ else:
+ item_index += 1
+ db_nsr_update[db_path + ".{}".format(item_index)] = target_item
+ existing_list.append(target_item)
+ existing_item = None
+
+ for target_vim, target_viminfo in target_item.get("vim_info", {}).items():
+ existing_viminfo = None
+
+ if existing_item:
+ existing_viminfo = existing_item.get("vim_info", {}).get(target_vim)
+
+ if existing_viminfo is not None:
+ continue
+
+ target_record_id = "{}.{}".format(db_record, target_item["id"])
+ item_ = item
+
+ if target_vim.startswith("sdn"):
+ # item must be sdn-net instead of net if target_vim is a sdn
+ item_ = "sdn_net"
+ target_record_id += ".sdn"
+
+ kwargs = {}
+ self.logger.warning(
+ "ns.calculate_diff_items target_item={}".format(target_item)
+ )
+ if process_params == Ns._process_vdu_params:
+ self.logger.warning(
+ "calculate_diff_items self.fs={}".format(self.fs)
+ )
+ kwargs.update(
+ {
+ "vnfr_id": vnfr_id,