Bug 2097: Fix for flavor name
[osm/NBI.git] / osm_nbi / instance_topics.py
index ebef36e..16bd406 100644 (file)
@@ -14,6 +14,7 @@
 # limitations under the License.
 
 # import logging
+import json
 from uuid import uuid4
 from http import HTTPStatus
 from time import time
@@ -25,7 +26,11 @@ from osm_nbi.validation import (
     ns_terminate,
     ns_action,
     ns_scale,
+    ns_update,
+    ns_heal,
     nsi_instantiate,
+    ns_migrate,
+    ns_verticalscale,
 )
 from osm_nbi.base_topic import (
     BaseTopic,
@@ -239,14 +244,16 @@ class NsrTopic(BaseTopic):
                             where_, k
                         )
                     )
-                if "." in k or "$" in k:
+                if "$" in k:
                     raise EngineException(
-                        "Invalid param at {}:{}. Keys must not contain dots or $".format(
+                        "Invalid param at {}:{}. Keys must not contain $ symbol".format(
                             where_, k
                         )
                     )
                 if isinstance(v, (dict, tuple, list)):
                     additional_params[k] = "!!yaml " + safe_dump(v)
+            if kdu_name:
+                additional_params = json.dumps(additional_params)
 
         if descriptor:
             for df in descriptor.get("df", []):
@@ -334,7 +341,7 @@ class NsrTopic(BaseTopic):
             # Create VNFRs
             needed_vnfds = {}
             # TODO: Change for multiple df support
-            vnf_profiles = nsd.get("df", [[]])[0].get("vnf-profile", ())
+            vnf_profiles = nsd.get("df", [{}])[0].get("vnf-profile", ())
             for vnfp in vnf_profiles:
                 vnfd_id = vnfp.get("vnfd-id")
                 vnf_index = vnfp.get("id")
@@ -345,6 +352,9 @@ class NsrTopic(BaseTopic):
                 )
                 if vnfd_id not in needed_vnfds:
                     vnfd = self._get_vnfd_from_db(vnfd_id, session)
+                    if "revision" in vnfd["_admin"]:
+                        vnfd["revision"] = vnfd["_admin"]["revision"]
+                    vnfd.pop("_admin")
                     needed_vnfds[vnfd_id] = vnfd
                     nsr_descriptor["vnfd-id"].append(vnfd["_id"])
                 else:
@@ -394,7 +404,6 @@ class NsrTopic(BaseTopic):
         _filter = self._get_project_filter(session)
         _filter["id"] = vnfd_id
         vnfd = self.db.get_one("vnfds", _filter, fail_on_empty=True, fail_on_more=True)
-        vnfd.pop("_admin")
         return vnfd
 
     def _add_nsr_to_db(self, nsr_descriptor, rollback, session):
@@ -432,6 +441,81 @@ class NsrTopic(BaseTopic):
 
         return ns_k8s_namespace
 
+    def _add_flavor_to_nsr(self, vdu, vnfd, nsr_descriptor, member_vnf_index, revision=None):
+        flavor_data = {}
+        guest_epa = {}
+        # Find this vdu compute and storage descriptors
+        vdu_virtual_compute = {}
+        vdu_virtual_storage = {}
+        for vcd in vnfd.get("virtual-compute-desc", ()):
+            if vcd.get("id") == vdu.get("virtual-compute-desc"):
+                vdu_virtual_compute = vcd
+        for vsd in vnfd.get("virtual-storage-desc", ()):
+            if vsd.get("id") == vdu.get("virtual-storage-desc", [[]])[0]:
+                vdu_virtual_storage = vsd
+        # Get this vdu vcpus, memory and storage info for flavor_data
+        if vdu_virtual_compute.get("virtual-cpu", {}).get(
+            "num-virtual-cpu"
+        ):
+            flavor_data["vcpu-count"] = vdu_virtual_compute["virtual-cpu"][
+                "num-virtual-cpu"
+            ]
+        if vdu_virtual_compute.get("virtual-memory", {}).get("size"):
+            flavor_data["memory-mb"] = (
+                float(vdu_virtual_compute["virtual-memory"]["size"])
+                * 1024.0
+            )
+        if vdu_virtual_storage.get("size-of-storage"):
+            flavor_data["storage-gb"] = vdu_virtual_storage[
+                "size-of-storage"
+            ]
+        # Get this vdu EPA info for guest_epa
+        if vdu_virtual_compute.get("virtual-cpu", {}).get("cpu-quota"):
+            guest_epa["cpu-quota"] = vdu_virtual_compute["virtual-cpu"][
+                "cpu-quota"
+            ]
+        if vdu_virtual_compute.get("virtual-cpu", {}).get("pinning"):
+            vcpu_pinning = vdu_virtual_compute["virtual-cpu"]["pinning"]
+            if vcpu_pinning.get("thread-policy"):
+                guest_epa["cpu-thread-pinning-policy"] = vcpu_pinning[
+                    "thread-policy"
+                ]
+            if vcpu_pinning.get("policy"):
+                cpu_policy = (
+                    "SHARED"
+                    if vcpu_pinning["policy"] == "dynamic"
+                    else "DEDICATED"
+                )
+                guest_epa["cpu-pinning-policy"] = cpu_policy
+        if vdu_virtual_compute.get("virtual-memory", {}).get("mem-quota"):
+            guest_epa["mem-quota"] = vdu_virtual_compute["virtual-memory"][
+                "mem-quota"
+            ]
+        if vdu_virtual_compute.get("virtual-memory", {}).get(
+            "mempage-size"
+        ):
+            guest_epa["mempage-size"] = vdu_virtual_compute[
+                "virtual-memory"
+            ]["mempage-size"]
+        if vdu_virtual_compute.get("virtual-memory", {}).get(
+            "numa-node-policy"
+        ):
+            guest_epa["numa-node-policy"] = vdu_virtual_compute[
+                "virtual-memory"
+            ]["numa-node-policy"]
+        if vdu_virtual_storage.get("disk-io-quota"):
+            guest_epa["disk-io-quota"] = vdu_virtual_storage[
+                "disk-io-quota"
+            ]
+
+        if guest_epa:
+            flavor_data["guest-epa"] = guest_epa
+
+        revision = revision if revision is not None else 1
+        flavor_data["name"] = vdu["id"][:56] + "-" + member_vnf_index + "-" + str(revision) + "-flv"
+        flavor_data["id"] = str(len(nsr_descriptor["flavor"]))
+        nsr_descriptor["flavor"].append(flavor_data)
+
     def _create_nsr_descriptor_from_nsd(self, nsd, ns_request, nsr_id, session):
         now = time()
         additional_params, _ = self._format_additional_params(
@@ -474,11 +558,14 @@ class NsrTopic(BaseTopic):
             "ssh-authorized-key": ns_request.get("ssh_keys"),  # TODO remove
             "flavor": [],
             "image": [],
+            "affinity-or-anti-affinity-group": [],
         }
+        if "revision" in nsd["_admin"]:
+            nsr_descriptor["revision"] = nsd["_admin"]["revision"]
+
         ns_request["nsr_id"] = nsr_id
         if ns_request and ns_request.get("config-units"):
             nsr_descriptor["config-units"] = ns_request["config-units"]
-
         # Create vld
         if nsd.get("virtual-link-desc"):
             nsr_vld = deepcopy(nsd.get("virtual-link-desc", []))
@@ -504,81 +591,11 @@ class NsrTopic(BaseTopic):
                         )
 
                 vnfd = self._get_vnfd_from_db(vnf_profile.get("vnfd-id"), session)
+                vnfd.pop("_admin")
 
                 for vdu in vnfd.get("vdu", ()):
-                    flavor_data = {}
-                    guest_epa = {}
-                    # Find this vdu compute and storage descriptors
-                    vdu_virtual_compute = {}
-                    vdu_virtual_storage = {}
-                    for vcd in vnfd.get("virtual-compute-desc", ()):
-                        if vcd.get("id") == vdu.get("virtual-compute-desc"):
-                            vdu_virtual_compute = vcd
-                    for vsd in vnfd.get("virtual-storage-desc", ()):
-                        if vsd.get("id") == vdu.get("virtual-storage-desc", [[]])[0]:
-                            vdu_virtual_storage = vsd
-                    # Get this vdu vcpus, memory and storage info for flavor_data
-                    if vdu_virtual_compute.get("virtual-cpu", {}).get(
-                        "num-virtual-cpu"
-                    ):
-                        flavor_data["vcpu-count"] = vdu_virtual_compute["virtual-cpu"][
-                            "num-virtual-cpu"
-                        ]
-                    if vdu_virtual_compute.get("virtual-memory", {}).get("size"):
-                        flavor_data["memory-mb"] = (
-                            float(vdu_virtual_compute["virtual-memory"]["size"])
-                            * 1024.0
-                        )
-                    if vdu_virtual_storage.get("size-of-storage"):
-                        flavor_data["storage-gb"] = vdu_virtual_storage[
-                            "size-of-storage"
-                        ]
-                    # Get this vdu EPA info for guest_epa
-                    if vdu_virtual_compute.get("virtual-cpu", {}).get("cpu-quota"):
-                        guest_epa["cpu-quota"] = vdu_virtual_compute["virtual-cpu"][
-                            "cpu-quota"
-                        ]
-                    if vdu_virtual_compute.get("virtual-cpu", {}).get("pinning"):
-                        vcpu_pinning = vdu_virtual_compute["virtual-cpu"]["pinning"]
-                        if vcpu_pinning.get("thread-policy"):
-                            guest_epa["cpu-thread-pinning-policy"] = vcpu_pinning[
-                                "thread-policy"
-                            ]
-                        if vcpu_pinning.get("policy"):
-                            cpu_policy = (
-                                "SHARED"
-                                if vcpu_pinning["policy"] == "dynamic"
-                                else "DEDICATED"
-                            )
-                            guest_epa["cpu-pinning-policy"] = cpu_policy
-                    if vdu_virtual_compute.get("virtual-memory", {}).get("mem-quota"):
-                        guest_epa["mem-quota"] = vdu_virtual_compute["virtual-memory"][
-                            "mem-quota"
-                        ]
-                    if vdu_virtual_compute.get("virtual-memory", {}).get(
-                        "mempage-size"
-                    ):
-                        guest_epa["mempage-size"] = vdu_virtual_compute[
-                            "virtual-memory"
-                        ]["mempage-size"]
-                    if vdu_virtual_compute.get("virtual-memory", {}).get(
-                        "numa-node-policy"
-                    ):
-                        guest_epa["numa-node-policy"] = vdu_virtual_compute[
-                            "virtual-memory"
-                        ]["numa-node-policy"]
-                    if vdu_virtual_storage.get("disk-io-quota"):
-                        guest_epa["disk-io-quota"] = vdu_virtual_storage[
-                            "disk-io-quota"
-                        ]
-
-                    if guest_epa:
-                        flavor_data["guest-epa"] = guest_epa
-
-                    flavor_data["name"] = vdu["id"][:56] + "-flv"
-                    flavor_data["id"] = str(len(nsr_descriptor["flavor"]))
-                    nsr_descriptor["flavor"].append(flavor_data)
-
+                    member_vnf_index = vnf_profile.get("id")
+                    self._add_flavor_to_nsr(vdu, vnfd, nsr_descriptor, member_vnf_index)
                     sw_image_id = vdu.get("sw-image-desc")
                     if sw_image_id:
                         image_data = self._get_image_data_from_vnfd(vnfd, sw_image_id)
@@ -589,6 +606,29 @@ class NsrTopic(BaseTopic):
                         image_data = self._get_image_data_from_vnfd(vnfd, alt_image)
                         self._add_image_to_nsr(nsr_descriptor, image_data)
 
+                # Add Affinity or Anti-affinity group information to NSR
+                vdu_profiles = vnfd.get("df", [[]])[0].get("vdu-profile", ())
+                affinity_group_prefix_name = "{}-{}".format(
+                    nsr_descriptor["name"][:16], vnf_profile.get("id")[:16]
+                )
+
+                for vdu_profile in vdu_profiles:
+                    affinity_group_data = {}
+                    for affinity_group in vdu_profile.get(
+                        "affinity-or-anti-affinity-group", ()
+                    ):
+                        affinity_group_data = (
+                            self._get_affinity_or_anti_affinity_group_data_from_vnfd(
+                                vnfd, affinity_group["id"]
+                            )
+                        )
+                        affinity_group_data["member-vnf-index"] = vnf_profile.get("id")
+                        self._add_affinity_or_anti_affinity_group_to_nsr(
+                            nsr_descriptor,
+                            affinity_group_data,
+                            affinity_group_prefix_name,
+                        )
+
             for vld in nsr_vld:
                 vld["vnfd-connection-point-ref"] = all_vld_connection_point_data.get(
                     vld.get("id"), []
@@ -598,6 +638,51 @@ class NsrTopic(BaseTopic):
 
         return nsr_descriptor
 
+    def _get_affinity_or_anti_affinity_group_data_from_vnfd(
+        self, vnfd, affinity_group_id
+    ):
+        """
+        Gets affinity-or-anti-affinity-group info from df and returns the desired affinity group
+        """
+        affinity_group = utils.find_in_list(
+            vnfd.get("df", [[]])[0].get("affinity-or-anti-affinity-group", ()),
+            lambda ag: ag["id"] == affinity_group_id,
+        )
+        affinity_group_data = {}
+        if affinity_group:
+            if affinity_group.get("id"):
+                affinity_group_data["ag-id"] = affinity_group["id"]
+            if affinity_group.get("type"):
+                affinity_group_data["type"] = affinity_group["type"]
+            if affinity_group.get("scope"):
+                affinity_group_data["scope"] = affinity_group["scope"]
+        return affinity_group_data
+
+    def _add_affinity_or_anti_affinity_group_to_nsr(
+        self, nsr_descriptor, affinity_group_data, affinity_group_prefix_name
+    ):
+        """
+        Adds affinity-or-anti-affinity-group to nsr checking first it is not already added
+        """
+        affinity_group = next(
+            (
+                f
+                for f in nsr_descriptor["affinity-or-anti-affinity-group"]
+                if all(f.get(k) == affinity_group_data[k] for k in affinity_group_data)
+            ),
+            None,
+        )
+        if not affinity_group:
+            affinity_group_data["id"] = str(
+                len(nsr_descriptor["affinity-or-anti-affinity-group"])
+            )
+            affinity_group_data["name"] = "{}-{}".format(
+                affinity_group_prefix_name, affinity_group_data["ag-id"][:32]
+            )
+            nsr_descriptor["affinity-or-anti-affinity-group"].append(
+                affinity_group_data
+            )
+
     def _get_image_data_from_vnfd(self, vnfd, sw_image_id):
         sw_image_desc = utils.find_in_list(
             vnfd.get("sw-image-desc", ()), lambda sw: sw["id"] == sw_image_id
@@ -636,6 +721,7 @@ class NsrTopic(BaseTopic):
         nsr_descriptor,
         ns_request,
         ns_k8s_namespace,
+        revision=None,
     ):
         vnfr_id = str(uuid4())
         nsr_id = nsr_descriptor["id"]
@@ -660,6 +746,13 @@ class NsrTopic(BaseTopic):
             "connection-point": [],
             "ip-address": None,  # mgmt-interface filled by LCM
         }
+
+        # Revision backwards compatility.  Only specify the revision in the record if
+        # the original VNFD has a revision.
+        if "revision" in vnfd:
+            vnfr_descriptor["revision"] = vnfd["revision"]
+
+
         vnf_k8s_namespace = ns_k8s_namespace
         if vnf_params:
             if vnf_params.get("k8s-namespace"):
@@ -767,6 +860,14 @@ class NsrTopic(BaseTopic):
             additional_params, vdu_params = self._format_additional_params(
                 ns_request, vnf_index, vdu_id=vdu["id"], descriptor=vnfd
             )
+
+            try:
+                vdu_virtual_storage_descriptors = utils.filter_in_list(
+                    vnfd.get("virtual-storage-desc", []),
+                    lambda stg_desc: stg_desc["id"] in vdu["virtual-storage-desc"]
+                )
+            except Exception:
+                vdu_virtual_storage_descriptors = []
             vdur = {
                 "vdu-id-ref": vdu["id"],
                 # TODO      "name": ""     Name of the VDU in the VIM
@@ -776,6 +877,7 @@ class NsrTopic(BaseTopic):
                 "interfaces": [],
                 "additionalParams": additional_params,
                 "vdu-name": vdu["name"],
+                "virtual-storages": vdu_virtual_storage_descriptors
             }
             if vdu_params and vdu_params.get("config-units"):
                 vdur["config-units"] = vdu_params["config-units"]
@@ -797,7 +899,10 @@ class NsrTopic(BaseTopic):
                 vdur["internal-connection-point"].append(vdu_icp)
 
                 for iface in icp.get("virtual-network-interface-requirement", ()):
-                    iface_fields = ("name", "mac-address")
+                    # Name, mac-address and interface position is taken from VNFD
+                    # and included into VNFR. By this way RO can process this information
+                    # while creating the VDU.
+                    iface_fields = ("name", "mac-address", "position")
                     vdu_iface = {
                         x: iface[x] for x in iface_fields if iface.get(x) is not None
                     }
@@ -921,7 +1026,8 @@ class NsrTopic(BaseTopic):
                     alt_image_ids.append(nsr_sw_image_data["id"])
                 vdur["alt-image-ids"] = alt_image_ids
 
-            flavor_data_name = vdu["id"][:56] + "-flv"
+            revision = revision if revision is not None else 1
+            flavor_data_name = vdu["id"][:56] + "-" + vnf_index + "-" + str(revision) + "-flv"
             nsr_flavor_desc = utils.find_in_list(
                 nsr_descriptor["flavor"],
                 lambda flavor: flavor["name"] == flavor_data_name,
@@ -930,6 +1036,49 @@ class NsrTopic(BaseTopic):
             if nsr_flavor_desc:
                 vdur["ns-flavor-id"] = nsr_flavor_desc["id"]
 
+            # Adding Affinity groups information to vdur
+            try:
+                vdu_profile_affinity_group = utils.find_in_list(
+                    vnfd.get("df")[0]["vdu-profile"],
+                    lambda a_vdu: a_vdu["id"] == vdu["id"],
+                )
+            except Exception:
+                vdu_profile_affinity_group = None
+
+            if vdu_profile_affinity_group:
+                affinity_group_ids = []
+                for affinity_group in vdu_profile_affinity_group.get(
+                    "affinity-or-anti-affinity-group", ()
+                ):
+                    vdu_affinity_group = utils.find_in_list(
+                        vdu_profile_affinity_group.get(
+                            "affinity-or-anti-affinity-group", ()
+                        ),
+                        lambda ag_fp: ag_fp["id"] == affinity_group["id"],
+                    )
+                    nsr_affinity_group = utils.find_in_list(
+                        nsr_descriptor["affinity-or-anti-affinity-group"],
+                        lambda nsr_ag: (
+                            nsr_ag.get("ag-id") == vdu_affinity_group.get("id")
+                            and nsr_ag.get("member-vnf-index")
+                            == vnfr_descriptor.get("member-vnf-index-ref")
+                        ),
+                    )
+                    # Update Affinity Group VIM name if VDU instantiation parameter is present
+                    if vnf_params and vnf_params.get("affinity-or-anti-affinity-group"):
+                        vnf_params_affinity_group = utils.find_in_list(
+                            vnf_params["affinity-or-anti-affinity-group"],
+                            lambda vnfp_ag: (
+                                vnfp_ag.get("id") == vdu_affinity_group.get("id")
+                            ),
+                        )
+                        if vnf_params_affinity_group.get("vim-affinity-group-id"):
+                            nsr_affinity_group[
+                                "vim-affinity-group-id"
+                            ] = vnf_params_affinity_group["vim-affinity-group-id"]
+                    affinity_group_ids.append(nsr_affinity_group["id"])
+                vdur["affinity-or-anti-affinity-group-id"] = affinity_group_ids
+
             if vdu_instantiation_level:
                 count = vdu_instantiation_level.get("number-of-instances")
             else:
@@ -938,9 +1087,9 @@ class NsrTopic(BaseTopic):
             for index in range(0, count):
                 vdur = deepcopy(vdur)
                 for iface in vdur["interfaces"]:
-                    if iface.get("ip-address"):
+                    if iface.get("ip-address") and index != 0:
                         iface["ip-address"] = increment_ip_mac(iface["ip-address"])
-                    if iface.get("mac-address"):
+                    if iface.get("mac-address") and index != 0:
                         iface["mac-address"] = increment_ip_mac(iface["mac-address"])
 
                 vdur["_id"] = str(uuid4())
@@ -1021,18 +1170,23 @@ class NsLcmOpTopic(BaseTopic):
     operation_schema = {  # mapping between operation and jsonschema to validate
         "instantiate": ns_instantiate,
         "action": ns_action,
+        "update": ns_update,
         "scale": ns_scale,
+        "heal": ns_heal,
         "terminate": ns_terminate,
+        "migrate": ns_migrate,
+        "verticalscale": ns_verticalscale,
     }
 
     def __init__(self, db, fs, msg, auth):
         BaseTopic.__init__(self, db, fs, msg, auth)
+        self.nsrtopic = NsrTopic(db, fs, msg, auth)
 
     def _check_ns_operation(self, session, nsr, operation, indata):
         """
         Check that user has enter right parameters for the operation
         :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
-        :param operation: it can be: instantiate, terminate, action, TODO: update, heal
+        :param operation: it can be: instantiate, terminate, action, update, heal
         :param indata: descriptor with the parameters of the operation
         :return: None
         """
@@ -1040,6 +1194,10 @@ class NsLcmOpTopic(BaseTopic):
             self._check_action_ns_operation(indata, nsr)
         elif operation == "scale":
             self._check_scale_ns_operation(indata, nsr)
+        elif operation == "update":
+            self._check_update_ns_operation(indata, nsr)
+        elif operation == "heal":
+            self._check_heal_ns_operation(indata, nsr)
         elif operation == "instantiate":
             self._check_instantiate_ns_operation(indata, nsr, session)
 
@@ -1132,6 +1290,96 @@ class NsLcmOpTopic(BaseTopic):
                 )
             )
 
+    def _check_update_ns_operation(self, indata, nsr) -> None:
+        """Validates the ns-update request according to updateType
+
+        If updateType is CHANGE_VNFPKG:
+        - it checks the vnfInstanceId, whether it's available under ns instance
+        - it checks the vnfdId whether it matches with the vnfd-id in the vnf-record of specified VNF.
+        Otherwise exception will be raised.
+        If updateType is REMOVE_VNF:
+        - it checks if the vnfInstanceId is available in the ns instance
+        - Otherwise exception will be raised.
+
+        Args:
+            indata: includes updateType such as CHANGE_VNFPKG,
+            nsr: network service record
+
+        Raises:
+           EngineException:
+                a meaningful error if given update parameters are not proper such as
+                "Error in validating ns-update request: <ID> does not match
+                with the vnfd-id of vnfinstance
+                http_code=HTTPStatus.UNPROCESSABLE_ENTITY"
+
+        """
+        try:
+            if indata["updateType"] == "CHANGE_VNFPKG":
+                # vnfInstanceId, nsInstanceId, vnfdId are mandatory
+                vnf_instance_id = indata["changeVnfPackageData"]["vnfInstanceId"]
+                ns_instance_id = indata["nsInstanceId"]
+                vnfd_id_2update = indata["changeVnfPackageData"]["vnfdId"]
+
+                if vnf_instance_id not in nsr["constituent-vnfr-ref"]:
+
+                    raise EngineException(
+                        f"Error in validating ns-update request: vnf {vnf_instance_id} does not "
+                        f"belong to NS {ns_instance_id}",
+                        http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
+                    )
+
+                # Getting vnfrs through the ns_instance_id
+                vnfrs = self.db.get_list("vnfrs", {"nsr-id-ref": ns_instance_id})
+                constituent_vnfd_id = next(
+                    (
+                        vnfr["vnfd-id"]
+                        for vnfr in vnfrs
+                        if vnfr["id"] == vnf_instance_id
+                    ),
+                    None,
+                )
+
+                # Check the given vnfd-id belongs to given vnf instance
+                if constituent_vnfd_id and (vnfd_id_2update != constituent_vnfd_id):
+
+                    raise EngineException(
+                        f"Error in validating ns-update request: vnfd-id {vnfd_id_2update} does not "
+                        f"match with the vnfd-id: {constituent_vnfd_id} of VNF instance: {vnf_instance_id}",
+                        http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
+                    )
+
+                # Validating the ns update timeout
+                if (
+                    indata.get("timeout_ns_update")
+                    and indata["timeout_ns_update"] < 300
+                ):
+                    raise EngineException(
+                        "Error in validating ns-update request: {} second is not enough "
+                        "to upgrade the VNF instance: {}".format(
+                            indata["timeout_ns_update"], vnf_instance_id
+                        ),
+                        http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
+                    )
+            elif indata["updateType"] == "REMOVE_VNF":
+                vnf_instance_id = indata["removeVnfInstanceId"]
+                ns_instance_id = indata["nsInstanceId"]
+                if vnf_instance_id not in nsr["constituent-vnfr-ref"]:
+                    raise EngineException(
+                        "Invalid VNF Instance Id. '{}' is not "
+                        "present in the NS '{}'".format(vnf_instance_id, ns_instance_id)
+                    )
+
+        except (
+            DbException,
+            AttributeError,
+            IndexError,
+            KeyError,
+            ValueError,
+        ) as e:
+            raise type(e)(
+                "Ns update request could not be processed with error: {}.".format(e)
+            )
+
     def _check_scale_ns_operation(self, indata, nsr):
         vnfd = self._get_vnfd_from_vnf_member_index(
             indata["scaleVnfData"]["scaleByStepData"]["member-vnf-index"], nsr["_id"]
@@ -1152,6 +1400,9 @@ class NsLcmOpTopic(BaseTopic):
                 )
             )
 
+    def _check_heal_ns_operation(self, indata, nsr):
+        return
+
     def _check_instantiate_ns_operation(self, indata, nsr, session):
         vnf_member_index_to_vnfd = {}  # map between vnf_member_index to vnf descriptor.
         vim_accounts = []
@@ -1202,7 +1453,14 @@ class NsLcmOpTopic(BaseTopic):
                 "Invalid parameter member_vnf_index='{}' is not one of the "
                 "nsd:constituent-vnfd".format(member_vnf_index)
             )
-        vnfd = self.db.get_one("vnfds", {"_id": vnfr["vnfd-id"]}, fail_on_empty=False)
+
+        ## Backwards compatibility: if there is no revision, get it from the one and only VNFD entry
+        if "revision" in vnfr:
+            vnfd_revision = vnfr["vnfd-id"] + ":" + str(vnfr["revision"])
+            vnfd = self.db.get_one("vnfds_revisions", {"_id": vnfd_revision}, fail_on_empty=False)
+        else:
+            vnfd = self.db.get_one("vnfds", {"_id": vnfr["vnfd-id"]}, fail_on_empty=False)
+
         if not vnfd:
             raise EngineException(
                 "vnfd id={} has been deleted!. Operation cannot be performed".format(
@@ -1239,7 +1497,7 @@ class NsLcmOpTopic(BaseTopic):
                 if in_vdu["id"] == vdu["id"]:
                     for volume in get_iterable(in_vdu.get("volume")):
                         for volumed in get_iterable(vdu.get("virtual-storage-desc")):
-                            if volumed["id"] == volume["name"]:
+                            if volumed == volume["name"]:
                                 break
                         else:
                             raise EngineException(
@@ -1259,7 +1517,7 @@ class NsLcmOpTopic(BaseTopic):
                         ):
                             vdu_if_names.add(iface.get("name"))
 
-                    for in_iface in get_iterable(in_vdu["interface"]):
+                    for in_iface in get_iterable(in_vdu.get("interface")):
                         if in_iface["name"] in vdu_if_names:
                             break
                         else:
@@ -1326,6 +1584,18 @@ class NsLcmOpTopic(BaseTopic):
             )
         vim_accounts.append(vim_account)
 
+    def _get_vim_account(self, vim_id: str, session):
+        try:
+            db_filter = self._get_project_filter(session)
+            db_filter["_id"] = vim_id
+            return self.db.get_one("vim_accounts", db_filter)
+        except Exception:
+            raise EngineException(
+                "Invalid vimAccountId='{}' not present for the project".format(
+                    vim_id
+                )
+            )
+
     def _check_valid_wim_account(self, wim_account, wim_accounts, session):
         if not isinstance(wim_account, str):
             return
@@ -1588,6 +1858,68 @@ class NsLcmOpTopic(BaseTopic):
             # TODO check that this forcing is not incompatible with other forcing
         return ifaces_forcing_vim_network
 
+    def _update_vnfrs_from_nsd(self, nsr):
+        try:
+            nsr_id = nsr["_id"]
+            nsd = nsr["nsd"]
+
+            step = "Getting vnf_profiles from nsd"
+            vnf_profiles = nsd.get("df", [{}])[0].get("vnf-profile", ())
+            vld_fixed_ip_connection_point_data = {}
+
+            step = "Getting ip-address info from vnf_profile if it exists"
+            for vnfp in vnf_profiles:
+                # Checking ip-address info from nsd.vnf_profile and storing
+                for vlc in vnfp.get("virtual-link-connectivity", ()):
+                    for cpd in vlc.get("constituent-cpd-id", ()):
+                        if cpd.get("ip-address"):
+                            step = "Storing ip-address info"
+                            vld_fixed_ip_connection_point_data.update({vlc.get("virtual-link-profile-id") + '.' + cpd.get("constituent-base-element-id"): {
+                                "vnfd-connection-point-ref": cpd.get(
+                                    "constituent-cpd-id"),
+                                    "ip-address": cpd.get(
+                                    "ip-address")}})
+
+            # Inserting ip address to vnfr
+            if len(vld_fixed_ip_connection_point_data) > 0:
+                step = "Getting vnfrs"
+                vnfrs = self.db.get_list("vnfrs", {"nsr-id-ref": nsr_id})
+                for item in vld_fixed_ip_connection_point_data.keys():
+                    step = "Filtering vnfrs"
+                    vnfr = next(filter(lambda vnfr: vnfr["member-vnf-index-ref"] == item.split('.')[1], vnfrs), None)
+                    if vnfr:
+                        vnfr_update = {}
+                        for vdur_index, vdur in enumerate(vnfr["vdur"]):
+                            for iface_index, iface in enumerate(vdur["interfaces"]):
+                                step = "Looking for matched interface"
+                                if (
+                                        iface.get("external-connection-point-ref")
+                                        == vld_fixed_ip_connection_point_data[item].get("vnfd-connection-point-ref") and
+                                        iface.get("ns-vld-id") == item.split('.')[0]
+
+                                ):
+                                    vnfr_update_text = "vdur.{}.interfaces.{}".format(
+                                        vdur_index, iface_index
+                                    )
+                                    step = "Storing info in order to update vnfr"
+                                    vnfr_update[
+                                        vnfr_update_text + ".ip-address"
+                                        ] = increment_ip_mac(
+                                        vld_fixed_ip_connection_point_data[item].get("ip-address"),
+                                        vdur.get("count-index", 0), )
+                                    vnfr_update[vnfr_update_text + ".fixed-ip"] = True
+
+                        step = "updating vnfr at database"
+                        self.db.set_one("vnfrs", {"_id": vnfr["_id"]}, vnfr_update)
+        except (
+                ValidationError,
+                EngineException,
+                DbException,
+                MsgException,
+                FsException,
+        ) as e:
+            raise type(e)("{} while '{}'".format(e, step), http_code=e.http_code)
+
     def _update_vnfrs(self, session, rollback, nsr, indata):
         # get vnfr
         nsr_id = nsr["_id"]
@@ -1600,15 +1932,14 @@ class NsLcmOpTopic(BaseTopic):
             # update vim-account-id
 
             vim_account = indata["vimAccountId"]
-            vca_id = indata.get("vcaId")
+            vca_id = self._get_vim_account(vim_account, session).get("vca")
             # check instantiate parameters
             for vnf_inst_params in get_iterable(indata.get("vnf")):
                 if vnf_inst_params["member-vnf-index"] != member_vnf_index:
                     continue
                 if vnf_inst_params.get("vimAccountId"):
                     vim_account = vnf_inst_params.get("vimAccountId")
-                if vnf_inst_params.get("vcaId"):
-                    vca_id = vnf_inst_params.get("vcaId")
+                    vca_id = self._get_vim_account(vim_account, session).get("vca")
 
                 # get vnf.vdu.interface instantiation params to update vnfr.vdur.interfaces ip, mac
                 for vdu_inst_param in get_iterable(vnf_inst_params.get("vdu")):
@@ -1788,7 +2119,7 @@ class NsLcmOpTopic(BaseTopic):
         """
         Creates a ns-lcm-opp content to be stored at database.
         :param nsr_id: internal id of the instance
-        :param operation: instantiate, terminate, scale, action, ...
+        :param operation: instantiate, terminate, scale, action, update ...
         :param params: user parameters for the operation
         :return: dictionary following SOL005 format
         """
@@ -1844,7 +2175,7 @@ class NsLcmOpTopic(BaseTopic):
         :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param indata: descriptor with the parameters of the operation. It must contains among others
             nsInstanceId: _id of the nsr to perform the operation
-            operation: it can be: instantiate, terminate, action, TODO: update, heal
+            operation: it can be: instantiate, terminate, action, update TODO: heal
         :param kwargs: used to override the indata descriptor
         :param headers: http request headers
         :return: id of the nslcmops
@@ -1906,10 +2237,55 @@ class NsLcmOpTopic(BaseTopic):
                         HTTPStatus.CONFLICT,
                     )
             self._check_ns_operation(session, nsr, operation, indata)
+            if (indata.get("primitive_params")):
+                indata["primitive_params"] = json.dumps(indata["primitive_params"])
+            elif (indata.get("additionalParamsForVnf")):
+                indata["additionalParamsForVnf"] = json.dumps(indata["additionalParamsForVnf"])
 
             if operation == "instantiate":
+                self._update_vnfrs_from_nsd(nsr)
                 self._update_vnfrs(session, rollback, nsr, indata)
-
+            if (operation == "update") and (indata["updateType"] == "CHANGE_VNFPKG"):
+                nsr_update = {}
+                vnfd_id = indata["changeVnfPackageData"]["vnfdId"]
+                vnfd = self.db.get_one("vnfds", {"_id": vnfd_id})
+                nsd = self.db.get_one("nsds", {"_id": nsr["nsd-id"]})
+                ns_request = nsr["instantiate_params"]
+                vnfr = self.db.get_one("vnfrs", {"_id": indata["changeVnfPackageData"]["vnfInstanceId"]})
+                latest_vnfd_revision = vnfd["_admin"].get("revision", 1)
+                vnfr_vnfd_revision = vnfr.get("revision", 1)
+                if latest_vnfd_revision != vnfr_vnfd_revision:
+                    old_vnfd_id = vnfd_id + ":" + str(vnfr_vnfd_revision)
+                    old_db_vnfd = self.db.get_one("vnfds_revisions", {"_id": old_vnfd_id})
+                    old_sw_version = old_db_vnfd.get("software-version", "1.0")
+                    new_sw_version = vnfd.get("software-version", "1.0")
+                    if new_sw_version != old_sw_version:
+                        vnf_index = vnfr["member-vnf-index-ref"]
+                        self.logger.info("nsr {}".format(nsr))
+                        for vdu in vnfd["vdu"]:
+                            self.nsrtopic._add_flavor_to_nsr(vdu, vnfd, nsr, vnf_index, latest_vnfd_revision)
+                            sw_image_id = vdu.get("sw-image-desc")
+                            if sw_image_id:
+                                image_data = self.nsrtopic._get_image_data_from_vnfd(vnfd, sw_image_id)
+                                self.nsrtopic._add_image_to_nsr(nsr, image_data)
+                            for alt_image in vdu.get("alternative-sw-image-desc", ()):
+                                image_data = self.nsrtopic._get_image_data_from_vnfd(vnfd, alt_image)
+                                self.nsrtopic._add_image_to_nsr(nsr, image_data)
+                        nsr_update["image"] = nsr["image"]
+                        nsr_update["flavor"] = nsr["flavor"]
+                        self.db.set_one("nsrs", {"_id": nsr["_id"]}, nsr_update)
+                        ns_k8s_namespace = self.nsrtopic._get_ns_k8s_namespace(nsd, ns_request, session)
+                        vnfr_descriptor = self.nsrtopic._create_vnfr_descriptor_from_vnfd(
+                            nsd,
+                            vnfd,
+                            vnfd_id,
+                            vnf_index,
+                            nsr,
+                            ns_request,
+                            ns_k8s_namespace,
+                            latest_vnfd_revision,
+                        )
+                        indata["newVdur"] = vnfr_descriptor["vdur"]
             nslcmop_desc = self._create_nslcmop(nsInstanceId, operation, indata)
             _id = nslcmop_desc["_id"]
             self.format_on_new(