Up to date with master

Change-Id: Iade650e01f64f7626d79183a3a91bc24e73ee234
Signed-off-by: Mark Beierl <mark.beierl@canonical.com>
diff --git a/osm_lcm/ROclient.py b/osm_lcm/ROclient.py
index e3cb7f7..8d6f510 100644
--- a/osm_lcm/ROclient.py
+++ b/osm_lcm/ROclient.py
@@ -1268,7 +1268,7 @@
         """
 
         if isinstance(descriptor, str):
-            descriptor = self.parse(descriptor, descriptor_format)
+            descriptor = self._parse(descriptor, descriptor_format)
         elif descriptor:
             pass
         elif kwargs:
@@ -1311,7 +1311,7 @@
         """
 
         if isinstance(descriptor, str):
-            descriptor = self.parse(descriptor, descriptor_format)
+            descriptor = self._parse(descriptor, descriptor_format)
         elif descriptor:
             pass
         elif kwargs:
@@ -1363,7 +1363,7 @@
         if all_tenants:
             tenant_text = "/any"
         else:
-            tenant_text = "/" + self._get_tenant()
+            tenant_text = "/" + self._get_tenant(session)
 
         if "datacenter_id" in kwargs or "datacenter_name" in kwargs:
             datacenter = self._get_item_uuid(
@@ -1373,7 +1373,7 @@
                 all_tenants=all_tenants,
             )
         else:
-            datacenter = self.get_datacenter(session)
+            datacenter = self._get_datacenter(session)
 
         if action == "list":
             url = "{}{}/vim/{}/{}".format(self.uri, tenant_text, datacenter, item)
diff --git a/osm_lcm/data_utils/database/vim_account.py b/osm_lcm/data_utils/database/vim_account.py
index 5c61073..215dee4 100644
--- a/osm_lcm/data_utils/database/vim_account.py
+++ b/osm_lcm/data_utils/database/vim_account.py
@@ -29,10 +29,12 @@
 class VimAccountDB:
     db = None
 
-    def get_vim_account_with_id(vim_account_id):
-        if not VimAccountDB.db:
-            VimAccountDB.initialize_db()
-        return VimAccountDB.db.get_one("vim_accounts", {"_id": vim_account_id}) or {}
+    @classmethod
+    def get_vim_account_with_id(cls, vim_account_id):
+        if not cls.db:
+            cls.initialize_db()
+        return cls.db.get_one("vim_accounts", {"_id": vim_account_id}) or {}
 
-    def initialize_db():
-        VimAccountDB.db = Database().instance.db
+    @classmethod
+    def initialize_db(cls):
+        cls.db = Database().instance.db
diff --git a/osm_lcm/data_utils/database/wim_account.py b/osm_lcm/data_utils/database/wim_account.py
index 8b0b5f6..a1f1799 100644
--- a/osm_lcm/data_utils/database/wim_account.py
+++ b/osm_lcm/data_utils/database/wim_account.py
@@ -28,21 +28,24 @@
     db = None
     db_wims = {}
 
-    def initialize_db():
-        WimAccountDB.db = Database().instance.db
+    @classmethod
+    def initialize_db(cls):
+        cls.db = Database().instance.db
 
-    def get_wim_account_with_id(wim_account_id):
-        if not WimAccountDB.db:
-            WimAccountDB.initialize_db()
-        if wim_account_id in WimAccountDB.db_wims:
-            return WimAccountDB.db_wims[wim_account_id]
-        db_wim = WimAccountDB.db.get_one("wim_accounts", {"_id": wim_account_id}) or {}
-        WimAccountDB.db_wims[wim_account_id] = db_wim
+    @classmethod
+    def get_wim_account_with_id(cls, wim_account_id):
+        if not cls.db:
+            cls.initialize_db()
+        if wim_account_id in cls.db_wims:
+            return cls.db_wims[wim_account_id]
+        db_wim = cls.db.get_one("wim_accounts", {"_id": wim_account_id}) or {}
+        cls.db_wims[wim_account_id] = db_wim
         return db_wim
 
-    def get_all_wim_accounts():
-        if not WimAccountDB.db:
-            WimAccountDB.initialize_db()
-        db_wims_list = WimAccountDB.db.get_list("wim_accounts")
-        WimAccountDB.db_wims.update({db_wim["_id"]: db_wim for db_wim in db_wims_list})
-        return WimAccountDB.db_wims
+    @classmethod
+    def get_all_wim_accounts(cls):
+        if not cls.db:
+            cls.initialize_db()
+        db_wims_list = cls.db.get_list("wim_accounts")
+        cls.db_wims.update({db_wim["_id"]: db_wim for db_wim in db_wims_list})
+        return cls.db_wims
diff --git a/osm_lcm/data_utils/vca.py b/osm_lcm/data_utils/vca.py
index 0f3ec17..ff52d22 100644
--- a/osm_lcm/data_utils/vca.py
+++ b/osm_lcm/data_utils/vca.py
@@ -224,3 +224,7 @@
     @property
     def config_sw_installed(self) -> bool:
         return self.get("config_sw_installed", False)
+
+    @property
+    def target_element(self) -> str:
+        return self.get("target_element", "")
diff --git a/osm_lcm/lcm.py b/osm_lcm/lcm.py
index 5638943..6da333c 100644
--- a/osm_lcm/lcm.py
+++ b/osm_lcm/lcm.py
@@ -361,6 +361,11 @@
                 task = asyncio.ensure_future(self.vca.create(params, order_id))
                 self.lcm_tasks.register("vca", vca_id, order_id, "vca_create", task)
                 return
+            elif command == "edit" or command == "edited":
+                vca_id = params.get("_id")
+                task = asyncio.ensure_future(self.vca.edit(params, order_id))
+                self.lcm_tasks.register("vca", vca_id, order_id, "vca_edit", task)
+                return
             elif command == "delete" or command == "deleted":
                 vca_id = params.get("_id")
                 task = asyncio.ensure_future(self.vca.delete(params, order_id))
@@ -480,7 +485,7 @@
                             db_nsr["config-status"],
                             db_nsr["detailed-status"],
                             db_nsr["_admin"]["deployed"],
-                            self.lcm_ns_tasks.get(nsr_id),
+                            self.lcm_tasks.task_registry["ns"].get(nsr_id, ""),
                         )
                     )
                 except Exception as e:
@@ -542,7 +547,7 @@
                             db_nsir["config-status"],
                             db_nsir["detailed-status"],
                             db_nsir["_admin"]["deployed"],
-                            self.lcm_netslice_tasks.get(nsir_id),
+                            self.lcm_tasks.task_registry["nsi"].get(nsir_id, ""),
                         )
                     )
                 except Exception as e:
diff --git a/osm_lcm/ns.py b/osm_lcm/ns.py
index b7df0b6..73bea40 100644
--- a/osm_lcm/ns.py
+++ b/osm_lcm/ns.py
@@ -1014,16 +1014,16 @@
             # check if this network needs SDN assist
             if vld.get("pci-interfaces"):
                 db_vim = get_vim_account(ns_params["vimAccountId"])
-                sdnc_id = db_vim["config"].get("sdn-controller")
-                if sdnc_id:
-                    sdn_vld = "nsrs:{}:vld.{}".format(nsr_id, vld["id"])
-                    target_sdn = "sdn:{}".format(sdnc_id)
-                    target_vld["vim_info"][target_sdn] = {
-                        "sdn": True,
-                        "target_vim": target_vim,
-                        "vlds": [sdn_vld],
-                        "type": vld.get("type"),
-                    }
+                if vim_config := db_vim.get("config"):
+                    if sdnc_id := vim_config.get("sdn-controller"):
+                        sdn_vld = "nsrs:{}:vld.{}".format(nsr_id, vld["id"])
+                        target_sdn = "sdn:{}".format(sdnc_id)
+                        target_vld["vim_info"][target_sdn] = {
+                            "sdn": True,
+                            "target_vim": target_vim,
+                            "vlds": [sdn_vld],
+                            "type": vld.get("type"),
+                        }
 
             nsd_vnf_profiles = get_vnf_profiles(nsd)
             for nsd_vnf_profile in nsd_vnf_profiles:
@@ -1378,7 +1378,6 @@
             }
             desc = await self.RO.deploy(nsr_id, target)
             action_id = desc["action_id"]
-            db_nsr_update["_admin.deployed.RO.nsr_delete_action_id"] = action_id
             db_nsr_update["_admin.deployed.RO.nsr_status"] = "DELETING"
             self.logger.debug(
                 logging_text
@@ -1396,20 +1395,17 @@
                 stage,
                 operation="termination",
             )
-
-            db_nsr_update["_admin.deployed.RO.nsr_delete_action_id"] = None
             db_nsr_update["_admin.deployed.RO.nsr_status"] = "DELETED"
             # delete all nsr
             await self.RO.delete(nsr_id)
-        except Exception as e:
-            if isinstance(e, NgRoException) and e.http_code == 404:  # not found
+        except NgRoException as e:
+            if e.http_code == 404:  # not found
                 db_nsr_update["_admin.deployed.RO.nsr_id"] = None
                 db_nsr_update["_admin.deployed.RO.nsr_status"] = "DELETED"
-                db_nsr_update["_admin.deployed.RO.nsr_delete_action_id"] = None
                 self.logger.debug(
                     logging_text + "RO_action_id={} already deleted".format(action_id)
                 )
-            elif isinstance(e, NgRoException) and e.http_code == 409:  # conflict
+            elif e.http_code == 409:  # conflict
                 failed_detail.append("delete conflict: {}".format(e))
                 self.logger.debug(
                     logging_text
@@ -1421,6 +1417,11 @@
                     logging_text
                     + "RO_action_id={} delete error: {}".format(action_id, e)
                 )
+        except Exception as e:
+            failed_detail.append("delete error: {}".format(e))
+            self.logger.error(
+                logging_text + "RO_action_id={} delete error: {}".format(action_id, e)
+            )
 
         if failed_detail:
             stage[2] = "Error deleting from VIM"
@@ -1562,9 +1563,7 @@
         """
 
         self.logger.debug(logging_text + "Starting wait_vm_up_insert_key_ro")
-        ro_nsr_id = None
         ip_address = None
-        nb_tries = 0
         target_vdu_id = None
         ro_retries = 0
 
@@ -1652,74 +1651,24 @@
                     self.logger.error(logging_text + "Cannot inject ssh-ky to a PDU")
                     return ip_address
                 try:
-                    ro_vm_id = "{}-{}".format(
-                        db_vnfr["member-vnf-index-ref"], target_vdu_id
-                    )  # TODO add vdu_index
-                    if self.ro_config.ng:
-                        target = {
-                            "action": {
-                                "action": "inject_ssh_key",
-                                "key": pub_key,
-                                "user": user,
-                            },
-                            "vnf": [{"_id": vnfr_id, "vdur": [{"id": vdur["id"]}]}],
-                        }
-                        desc = await self.RO.deploy(nsr_id, target)
-                        action_id = desc["action_id"]
-                        await self._wait_ng_ro(
-                            nsr_id, action_id, timeout=600, operation="instantiation"
-                        )
-                        break
-                    else:
-                        # wait until NS is deployed at RO
-                        if not ro_nsr_id:
-                            db_nsrs = self.db.get_one("nsrs", {"_id": nsr_id})
-                            ro_nsr_id = deep_get(
-                                db_nsrs, ("_admin", "deployed", "RO", "nsr_id")
-                            )
-                        if not ro_nsr_id:
-                            continue
-                        result_dict = await self.RO.create_action(
-                            item="ns",
-                            item_id_name=ro_nsr_id,
-                            descriptor={
-                                "add_public_key": pub_key,
-                                "vms": [ro_vm_id],
-                                "user": user,
-                            },
-                        )
-                        # result_dict contains the format {VM-id: {vim_result: 200, description: text}}
-                        if not result_dict or not isinstance(result_dict, dict):
-                            raise LcmException(
-                                "Unknown response from RO when injecting key"
-                            )
-                        for result in result_dict.values():
-                            if result.get("vim_result") == 200:
-                                break
-                            else:
-                                raise ROclient.ROClientException(
-                                    "error injecting key: {}".format(
-                                        result.get("description")
-                                    )
-                                )
-                        break
+                    target = {
+                        "action": {
+                            "action": "inject_ssh_key",
+                            "key": pub_key,
+                            "user": user,
+                        },
+                        "vnf": [{"_id": vnfr_id, "vdur": [{"id": vdur["id"]}]}],
+                    }
+                    desc = await self.RO.deploy(nsr_id, target)
+                    action_id = desc["action_id"]
+                    await self._wait_ng_ro(
+                        nsr_id, action_id, timeout=600, operation="instantiation"
+                    )
+                    break
                 except NgRoException as e:
                     raise LcmException(
                         "Reaching max tries injecting key. Error: {}".format(e)
                     )
-                except ROclient.ROClientException as e:
-                    if not nb_tries:
-                        self.logger.debug(
-                            logging_text
-                            + "error injecting key: {}. Retrying until {} seconds".format(
-                                e, 20 * 10
-                            )
-                        )
-                    nb_tries += 1
-                    if nb_tries >= 20:
-                        raise LcmException(
-                            "Reaching max tries injecting key. Error: {}".format(e)
-                        )
             else:
                 break
 
@@ -2208,6 +2157,7 @@
                     vnfr_id=vnfr_id,
                     nsr_id=nsr_id,
                     target_ip=rw_mgmt_ip,
+                    element_type=element_type,
                     vnf_member_index=db_vnfr.get("member-vnf-index-ref", ""),
                     vdu_id=vdu_id,
                     vdu_index=vdu_index,
@@ -2461,6 +2411,8 @@
         # update operation on nslcmops
         db_nslcmop_update = {}
 
+        timeout_ns_deploy = self.timeout.ns_deploy
+
         nslcmop_operation_state = None
         db_vnfrs = {}  # vnf's info indexed by member-index
         # n2vc_info = {}
@@ -2501,8 +2453,6 @@
             ns_params = db_nslcmop.get("operationParams")
             if ns_params and ns_params.get("timeout_ns_deploy"):
                 timeout_ns_deploy = ns_params["timeout_ns_deploy"]
-            else:
-                timeout_ns_deploy = self.timeout.ns_deploy
 
             # read from db: ns
             stage[1] = "Getting nsr={} from db.".format(nsr_id)
@@ -3050,6 +3000,9 @@
         cached_vnfds: Dict[str, Any],
     ) -> List[Relation]:
         relations = []
+        if vca.target_element == "ns":
+            self.logger.debug("VCA is a NS charm, not a VNF.")
+            return relations
         vnf_profile = get_vnf_profile(nsd, vca.vnf_profile_id)
         vnf_profile_id = vnf_profile["id"]
         vnfd_id = vnf_profile["vnfd-id"]
@@ -4289,206 +4242,6 @@
             pass
         self._write_all_config_status(db_nsr=db_nsr, status="DELETED")
 
-    async def _terminate_RO(
-        self, logging_text, nsr_deployed, nsr_id, nslcmop_id, stage
-    ):
-        """
-        Terminates a deployment from RO
-        :param logging_text:
-        :param nsr_deployed: db_nsr._admin.deployed
-        :param nsr_id:
-        :param nslcmop_id:
-        :param stage: list of string with the content to write on db_nslcmop.detailed-status.
-            this method will update only the index 2, but it will write on database the concatenated content of the list
-        :return:
-        """
-        db_nsr_update = {}
-        failed_detail = []
-        ro_nsr_id = ro_delete_action = None
-        if nsr_deployed and nsr_deployed.get("RO"):
-            ro_nsr_id = nsr_deployed["RO"].get("nsr_id")
-            ro_delete_action = nsr_deployed["RO"].get("nsr_delete_action_id")
-        try:
-            if ro_nsr_id:
-                stage[2] = "Deleting ns from VIM."
-                db_nsr_update["detailed-status"] = " ".join(stage)
-                self._write_op_status(nslcmop_id, stage)
-                self.logger.debug(logging_text + stage[2])
-                self.update_db_2("nsrs", nsr_id, db_nsr_update)
-                self._write_op_status(nslcmop_id, stage)
-                desc = await self.RO.delete("ns", ro_nsr_id)
-                ro_delete_action = desc["action_id"]
-                db_nsr_update[
-                    "_admin.deployed.RO.nsr_delete_action_id"
-                ] = ro_delete_action
-                db_nsr_update["_admin.deployed.RO.nsr_id"] = None
-                db_nsr_update["_admin.deployed.RO.nsr_status"] = "DELETED"
-            if ro_delete_action:
-                # wait until NS is deleted from VIM
-                stage[2] = "Waiting ns deleted from VIM."
-                detailed_status_old = None
-                self.logger.debug(
-                    logging_text
-                    + stage[2]
-                    + " RO_id={} ro_delete_action={}".format(
-                        ro_nsr_id, ro_delete_action
-                    )
-                )
-                self.update_db_2("nsrs", nsr_id, db_nsr_update)
-                self._write_op_status(nslcmop_id, stage)
-
-                delete_timeout = 20 * 60  # 20 minutes
-                while delete_timeout > 0:
-                    desc = await self.RO.show(
-                        "ns",
-                        item_id_name=ro_nsr_id,
-                        extra_item="action",
-                        extra_item_id=ro_delete_action,
-                    )
-
-                    # deploymentStatus
-                    self._on_update_ro_db(nsrs_id=nsr_id, ro_descriptor=desc)
-
-                    ns_status, ns_status_info = self.RO.check_action_status(desc)
-                    if ns_status == "ERROR":
-                        raise ROclient.ROClientException(ns_status_info)
-                    elif ns_status == "BUILD":
-                        stage[2] = "Deleting from VIM {}".format(ns_status_info)
-                    elif ns_status == "ACTIVE":
-                        db_nsr_update["_admin.deployed.RO.nsr_delete_action_id"] = None
-                        db_nsr_update["_admin.deployed.RO.nsr_status"] = "DELETED"
-                        break
-                    else:
-                        assert (
-                            False
-                        ), "ROclient.check_action_status returns unknown {}".format(
-                            ns_status
-                        )
-                    if stage[2] != detailed_status_old:
-                        detailed_status_old = stage[2]
-                        db_nsr_update["detailed-status"] = " ".join(stage)
-                        self._write_op_status(nslcmop_id, stage)
-                        self.update_db_2("nsrs", nsr_id, db_nsr_update)
-                    await asyncio.sleep(5, loop=self.loop)
-                    delete_timeout -= 5
-                else:  # delete_timeout <= 0:
-                    raise ROclient.ROClientException(
-                        "Timeout waiting ns deleted from VIM"
-                    )
-
-        except Exception as e:
-            self.update_db_2("nsrs", nsr_id, db_nsr_update)
-            if (
-                isinstance(e, ROclient.ROClientException) and e.http_code == 404
-            ):  # not found
-                db_nsr_update["_admin.deployed.RO.nsr_id"] = None
-                db_nsr_update["_admin.deployed.RO.nsr_status"] = "DELETED"
-                db_nsr_update["_admin.deployed.RO.nsr_delete_action_id"] = None
-                self.logger.debug(
-                    logging_text + "RO_ns_id={} already deleted".format(ro_nsr_id)
-                )
-            elif (
-                isinstance(e, ROclient.ROClientException) and e.http_code == 409
-            ):  # conflict
-                failed_detail.append("delete conflict: {}".format(e))
-                self.logger.debug(
-                    logging_text
-                    + "RO_ns_id={} delete conflict: {}".format(ro_nsr_id, e)
-                )
-            else:
-                failed_detail.append("delete error: {}".format(e))
-                self.logger.error(
-                    logging_text + "RO_ns_id={} delete error: {}".format(ro_nsr_id, e)
-                )
-
-        # Delete nsd
-        if not failed_detail and deep_get(nsr_deployed, ("RO", "nsd_id")):
-            ro_nsd_id = nsr_deployed["RO"]["nsd_id"]
-            try:
-                stage[2] = "Deleting nsd from RO."
-                db_nsr_update["detailed-status"] = " ".join(stage)
-                self.update_db_2("nsrs", nsr_id, db_nsr_update)
-                self._write_op_status(nslcmop_id, stage)
-                await self.RO.delete("nsd", ro_nsd_id)
-                self.logger.debug(
-                    logging_text + "ro_nsd_id={} deleted".format(ro_nsd_id)
-                )
-                db_nsr_update["_admin.deployed.RO.nsd_id"] = None
-            except Exception as e:
-                if (
-                    isinstance(e, ROclient.ROClientException) and e.http_code == 404
-                ):  # not found
-                    db_nsr_update["_admin.deployed.RO.nsd_id"] = None
-                    self.logger.debug(
-                        logging_text + "ro_nsd_id={} already deleted".format(ro_nsd_id)
-                    )
-                elif (
-                    isinstance(e, ROclient.ROClientException) and e.http_code == 409
-                ):  # conflict
-                    failed_detail.append(
-                        "ro_nsd_id={} delete conflict: {}".format(ro_nsd_id, e)
-                    )
-                    self.logger.debug(logging_text + failed_detail[-1])
-                else:
-                    failed_detail.append(
-                        "ro_nsd_id={} delete error: {}".format(ro_nsd_id, e)
-                    )
-                    self.logger.error(logging_text + failed_detail[-1])
-
-        if not failed_detail and deep_get(nsr_deployed, ("RO", "vnfd")):
-            for index, vnf_deployed in enumerate(nsr_deployed["RO"]["vnfd"]):
-                if not vnf_deployed or not vnf_deployed["id"]:
-                    continue
-                try:
-                    ro_vnfd_id = vnf_deployed["id"]
-                    stage[
-                        2
-                    ] = "Deleting member_vnf_index={} ro_vnfd_id={} from RO.".format(
-                        vnf_deployed["member-vnf-index"], ro_vnfd_id
-                    )
-                    db_nsr_update["detailed-status"] = " ".join(stage)
-                    self.update_db_2("nsrs", nsr_id, db_nsr_update)
-                    self._write_op_status(nslcmop_id, stage)
-                    await self.RO.delete("vnfd", ro_vnfd_id)
-                    self.logger.debug(
-                        logging_text + "ro_vnfd_id={} deleted".format(ro_vnfd_id)
-                    )
-                    db_nsr_update["_admin.deployed.RO.vnfd.{}.id".format(index)] = None
-                except Exception as e:
-                    if (
-                        isinstance(e, ROclient.ROClientException) and e.http_code == 404
-                    ):  # not found
-                        db_nsr_update[
-                            "_admin.deployed.RO.vnfd.{}.id".format(index)
-                        ] = None
-                        self.logger.debug(
-                            logging_text
-                            + "ro_vnfd_id={} already deleted ".format(ro_vnfd_id)
-                        )
-                    elif (
-                        isinstance(e, ROclient.ROClientException) and e.http_code == 409
-                    ):  # conflict
-                        failed_detail.append(
-                            "ro_vnfd_id={} delete conflict: {}".format(ro_vnfd_id, e)
-                        )
-                        self.logger.debug(logging_text + failed_detail[-1])
-                    else:
-                        failed_detail.append(
-                            "ro_vnfd_id={} delete error: {}".format(ro_vnfd_id, e)
-                        )
-                        self.logger.error(logging_text + failed_detail[-1])
-
-        if failed_detail:
-            stage[2] = "Error deleting from VIM"
-        else:
-            stage[2] = "Deleted from VIM"
-        db_nsr_update["detailed-status"] = " ".join(stage)
-        self.update_db_2("nsrs", nsr_id, db_nsr_update)
-        self._write_op_status(nslcmop_id, stage)
-
-        if failed_detail:
-            raise LcmException("; ".join(failed_detail))
-
     async def terminate(self, nsr_id, nslcmop_id):
         # Try to lock HA task here
         task_is_locked_by_me = self.lcm_tasks.lock_HA("ns", "nslcmops", nslcmop_id)
@@ -4693,13 +4446,7 @@
                         logging_text, nsr_deployed, nsr_id, nslcmop_id, stage
                     )
                 )
-            else:
-                task_delete_ro = asyncio.ensure_future(
-                    self._terminate_RO(
-                        logging_text, nsr_deployed, nsr_id, nslcmop_id, stage
-                    )
-                )
-            tasks_dict_info[task_delete_ro] = "Removing deployment from VIM"
+                tasks_dict_info[task_delete_ro] = "Removing deployment from VIM"
 
             # rest of staff will be done at finally
 
@@ -5136,6 +4883,7 @@
         nslcmop_operation_state = None
         error_description_nslcmop = None
         exc = None
+        step = ""
         try:
             # wait for any previous tasks in process
             step = "Waiting for previous operations to terminate"
@@ -5810,6 +5558,7 @@
         exc = None
         change_type = "updated"
         detailed_status = ""
+        member_vnf_index = None
 
         try:
             # wait for any previous tasks in process
@@ -6314,7 +6063,10 @@
                         "nslcmop_id": nslcmop_id,
                         "operationState": nslcmop_operation_state,
                     }
-                    if change_type in ("vnf_terminated", "policy_updated"):
+                    if (
+                        change_type in ("vnf_terminated", "policy_updated")
+                        and member_vnf_index
+                    ):
                         msg.update({"vnf_member_index": member_vnf_index})
                     await self.msg.aiowrite("ns", change_type, msg, loop=self.loop)
                 except Exception as e:
@@ -7522,6 +7274,7 @@
         vnfr_id: str,
         nsr_id: str,
         target_ip: str,
+        element_type: str,
         vnf_member_index: str = "",
         vdu_id: str = "",
         vdu_index: int = None,
@@ -7538,6 +7291,7 @@
             vnfr_id (str): VNFR ID where this EE applies
             nsr_id (str): NSR ID where this EE applies
             target_ip (str): VDU/KDU instance IP address
+            element_type (str): NS or VNF or VDU or KDU
             vnf_member_index (str, optional): VNF index where this EE applies. Defaults to "".
             vdu_id (str, optional): VDU ID where this EE applies. Defaults to "".
             vdu_index (int, optional): VDU index where this EE applies. Defaults to None.
@@ -7550,7 +7304,11 @@
         Returns:
             _type_: Prometheus jobs
         """
-        self.logger.debug(f"KDU: {kdu_name}; KDU INDEX: {kdu_index}")
+        # default the vdur and kdur names to an empty string, to avoid any later
+        # problem with Prometheus when the element type is not VDU or KDU
+        vdur_name = ""
+        kdur_name = ""
+
         # look if exist a file called 'prometheus*.j2' and
         artifact_content = self.fs.dir_ls(artifact_path)
         job_file = next(
@@ -7566,51 +7324,51 @@
         with self.fs.file_open((artifact_path, job_file), "r") as f:
             job_data = f.read()
 
-        vdur_name = ""
-        kdur_name = ""
-        for r in range(360):
-            db_vnfr = self.db.get_one("vnfrs", {"_id": vnfr_id})
-            if vdu_id and vdu_index is not None:
-                vdur = next(
-                    (
-                        x
-                        for x in get_iterable(db_vnfr, "vdur")
-                        if (
-                            x.get("vdu-id-ref") == vdu_id
-                            and x.get("count-index") == vdu_index
-                        )
-                    ),
-                    {},
-                )
-                if vdur.get("name"):
-                    vdur_name = vdur.get("name")
-                    break
-            if kdu_name and kdu_index is not None:
-                kdur = next(
-                    (
-                        x
-                        for x in get_iterable(db_vnfr, "kdur")
-                        if (
-                            x.get("kdu-name") == kdu_name
-                            and x.get("count-index") == kdu_index
-                        )
-                    ),
-                    {},
-                )
-                if kdur.get("name"):
-                    kdur_name = kdur.get("name")
-                    break
+        # obtain the VDUR or KDUR, if the element type is VDU or KDU
+        if element_type in ("VDU", "KDU"):
+            for _ in range(360):
+                db_vnfr = self.db.get_one("vnfrs", {"_id": vnfr_id})
+                if vdu_id and vdu_index is not None:
+                    vdur = next(
+                        (
+                            x
+                            for x in get_iterable(db_vnfr, "vdur")
+                            if (
+                                x.get("vdu-id-ref") == vdu_id
+                                and x.get("count-index") == vdu_index
+                            )
+                        ),
+                        {},
+                    )
+                    if vdur.get("name"):
+                        vdur_name = vdur.get("name")
+                        break
+                if kdu_name and kdu_index is not None:
+                    kdur = next(
+                        (
+                            x
+                            for x in get_iterable(db_vnfr, "kdur")
+                            if (
+                                x.get("kdu-name") == kdu_name
+                                and x.get("count-index") == kdu_index
+                            )
+                        ),
+                        {},
+                    )
+                    if kdur.get("name"):
+                        kdur_name = kdur.get("name")
+                        break
 
-            await asyncio.sleep(10, loop=self.loop)
-        else:
-            if vdu_id and vdu_index is not None:
-                raise LcmException(
-                    f"Timeout waiting VDU with name={vdu_id} and index={vdu_index} to be intantiated"
-                )
-            if kdu_name and kdu_index is not None:
-                raise LcmException(
-                    f"Timeout waiting KDU with name={kdu_name} and index={kdu_index} to be intantiated"
-                )
+                await asyncio.sleep(10, loop=self.loop)
+            else:
+                if vdu_id and vdu_index is not None:
+                    raise LcmException(
+                        f"Timeout waiting VDU with name={vdu_id} and index={vdu_index} to be intantiated"
+                    )
+                if kdu_name and kdu_index is not None:
+                    raise LcmException(
+                        f"Timeout waiting KDU with name={kdu_name} and index={kdu_index} to be intantiated"
+                    )
 
         # TODO get_service
         _, _, service = ee_id.partition(".")  # remove prefix   "namespace."
@@ -7626,6 +7384,7 @@
             "VNF_MEMBER_INDEX": vnf_member_index,
             "VDUR_NAME": vdur_name,
             "KDUR_NAME": kdur_name,
+            "ELEMENT_TYPE": element_type,
         }
         job_list = parse_job(job_data, variables)
         # ensure job_name is using the vnfr_id. Adding the metadata nsr_id
diff --git a/osm_lcm/osm_config.py b/osm_lcm/osm_config.py
index 7dd8f63..21b584a 100644
--- a/osm_lcm/osm_config.py
+++ b/osm_lcm/osm_config.py
@@ -28,6 +28,7 @@
     services: List[Dict]
 
     @validator("services")
+    @classmethod
     def parse_services(cls, services: Dict[str, Any]):
         return {
             service["name"]: {
diff --git a/osm_lcm/tests/test_db_descriptors.py b/osm_lcm/tests/test_db_descriptors.py
index 7f47231..4e2dedb 100644
--- a/osm_lcm/tests/test_db_descriptors.py
+++ b/osm_lcm/tests/test_db_descriptors.py
@@ -2252,6 +2252,27 @@
                     - data-type: STRING
                       default-value: <touch_filename2>
                       name: filename
+              relation:
+                - entities:
+                  - endpoint: interface
+                    id: mgmtVM
+                  - endpoint: interface
+                    id: dataVM
+                  name: relation
+            - id: mgmtVM
+              execution-environment-list:
+              - id: simple-ee
+                juju:
+                  charm: simple_mgmtVM
+                  proxy: false
+                external-connection-point-ref: mgmt
+            - id: dataVM
+              execution-environment-list:
+              - id: simple-ee
+                juju:
+                  charm: simple_dataVM
+                  proxy: false
+                external-connection-point-ref: mgmt
 
 -   _admin:
         created: 1575031727.5383403
diff --git a/osm_lcm/tests/test_ns.py b/osm_lcm/tests/test_ns.py
index f44dbf6..7e72700 100644
--- a/osm_lcm/tests/test_ns.py
+++ b/osm_lcm/tests/test_ns.py
@@ -31,7 +31,7 @@
 from osm_lcm.ng_ro import NgRoClient
 from osm_lcm.data_utils.database.database import Database
 from osm_lcm.data_utils.filesystem.filesystem import Filesystem
-from osm_lcm.data_utils.vca import Relation, EERelation
+from osm_lcm.data_utils.vca import Relation, EERelation, DeployedVCA
 from osm_lcm.data_utils.vnfd import find_software_version
 from osm_lcm.lcm_utils import check_juju_bundle_existence, get_charm_artifact_path
 from osm_lcm.lcm_utils import LcmException
@@ -99,7 +99,7 @@
     return a
 
 
-class TestMyNS(asynctest.TestCase):
+class TestBaseNS(asynctest.TestCase):
     async def _n2vc_DeployCharms(
         self,
         model_name,
@@ -205,18 +205,15 @@
             self.db = Database({"database": {"driver": "memory"}}).instance.db
             self.db.create_list("vnfds", yaml.safe_load(descriptors.db_vnfds_text))
             self.db.create_list(
-                "vnfds_revisions",
-                yaml.safe_load(descriptors.db_vnfds_revisions_text),
+                "vnfds_revisions", yaml.safe_load(descriptors.db_vnfds_revisions_text)
             )
             self.db.create_list("nsds", yaml.safe_load(descriptors.db_nsds_text))
             self.db.create_list("nsrs", yaml.safe_load(descriptors.db_nsrs_text))
             self.db.create_list(
-                "vim_accounts",
-                yaml.safe_load(descriptors.db_vim_accounts_text),
+                "vim_accounts", yaml.safe_load(descriptors.db_vim_accounts_text)
             )
             self.db.create_list(
-                "k8sclusters",
-                yaml.safe_load(descriptors.db_k8sclusters_text),
+                "k8sclusters", yaml.safe_load(descriptors.db_k8sclusters_text)
             )
             self.db.create_list(
                 "nslcmops", yaml.safe_load(descriptors.db_nslcmops_text)
@@ -410,6 +407,8 @@
     #     await self.test_instantiate()
     #     # this will check that the initial-congig-primitive 'not_to_be_called' is not called
 
+
+class TestMyNS(TestBaseNS):
     @asynctest.fail_on(active_handles=True)
     async def test_start_stop_rebuild_pass(self):
         nsr_id = descriptors.test_ids["TEST-OP-VNF"]["ns"]
@@ -515,13 +514,16 @@
                         ):
                             if (
                                 v.get("execution-environment-list")
-                                and "juju" in v["execution-environment-list"][k]
+                                and "juju" in v["execution-environment-list"][0]
                             ):
                                 expected_value = self.db.get_list("nsrs")[i][
                                     "vcaStatus"
                                 ]
                                 await self.my_ns._on_update_n2vc_db(
-                                    "nsrs", {"_id": nsr_id}, "_admin.deployed.VCA.0", {}
+                                    "nsrs",
+                                    {"_id": nsr_id},
+                                    "_admin.deployed.VCA.{}".format(k),
+                                    {},
                                 )
                                 return_value = self.db.get_list("nsrs")[i]["vcaStatus"]
         self.assertEqual(return_value, expected_value)
@@ -1194,11 +1196,7 @@
             q_filter={"_id": vnfd_id + ":1"},
             update_dict={"_admin.revision": 1, "kdu": []},
         )
-        self.db.set_one(
-            "vnfrs",
-            q_filter={"_id": vnfr_id},
-            update_dict={"revision": 1},
-        )
+        self.db.set_one("vnfrs", q_filter={"_id": vnfr_id}, update_dict={"revision": 1})
         mock_charm_hash.return_value = False
 
         mock_charm_artifact.side_effect = [
@@ -1276,9 +1274,7 @@
         self.db.set_one(
             "nsrs",
             q_filter={"_id": nsr_id},
-            update_dict={
-                "_admin.deployed.VCA.0.kdu_name": "native-kdu",
-            },
+            update_dict={"_admin.deployed.VCA.0.kdu_name": "native-kdu"},
         )
         self.db.set_one("vnfrs", q_filter={"_id": vnfr_id}, update_dict={"revision": 1})
 
@@ -1320,20 +1316,12 @@
         self.db.set_one(
             "vnfds",
             q_filter={"_id": vnfd_id},
-            update_dict={
-                "_admin.revision": 3,
-                "software-version": "1.0",
-                "kdu": [],
-            },
+            update_dict={"_admin.revision": 3, "software-version": "1.0", "kdu": []},
         )
         self.db.set_one(
             "vnfds_revisions",
             q_filter={"_id": vnfd_id + ":1"},
-            update_dict={
-                "_admin.revision": 1,
-                "software-version": "1.0",
-                "kdu": [],
-            },
+            update_dict={"_admin.revision": 1, "software-version": "1.0", "kdu": []},
         )
         self.db.set_one("vnfrs", q_filter={"_id": vnfr_id}, update_dict={"revision": 1})
 
@@ -1520,10 +1508,7 @@
 
     def test_ns_update_check_juju_charm_artifacts_base_folder_wth_pkgdir(self):
         """Check charm artifacts"""
-        base_folder = {
-            "folder": vnfd_id,
-            "pkg-dir": "hackfest_3charmed_vnfd",
-        }
+        base_folder = {"folder": vnfd_id, "pkg-dir": "hackfest_3charmed_vnfd"}
         charm_name = "simple"
         charm_type = "lxc_proxy_charm"
         revision = 3
@@ -1533,9 +1518,7 @@
 
     def test_ns_update_check_juju_charm_artifacts_base_folder_wthout_pkgdir(self):
         """Check charm artifacts, SOL004 packages"""
-        base_folder = {
-            "folder": vnfd_id,
-        }
+        base_folder = {"folder": vnfd_id}
         charm_name = "basic"
         charm_type, revision = "", ""
         expected_result = f"{vnfd_id}/Scripts/helm-charts/basic"
@@ -1543,7 +1526,7 @@
         self.assertEqual(result, expected_result, "Wrong charm artifact path")
 
 
-class TestInstantiateN2VC(TestMyNS):
+class TestInstantiateN2VC(TestBaseNS):
     async def setUp(self):
         await super().setUp()
         self.db_nsr = yaml.safe_load(descriptors.db_nsrs_text)[0]
@@ -1566,6 +1549,7 @@
             vdu_id=None,
             kdu_name=None,
             vdu_index=None,
+            kdu_index=None,
             config_descriptor=config_descriptor,
             deploy_params={},
             base_folder=base_folder,
@@ -1644,5 +1628,63 @@
         self.assertTrue(await self.call_ns_add_relation())
 
 
+class TestGetVNFRelations(TestBaseNS):
+    async def setUp(self):
+        await super().setUp()
+        self.db_nsd = yaml.safe_load(descriptors.db_nsds_text)[0]
+
+    def test_ns_charm_vca_returns_empty_relations(self):
+        ns_charm_vca = {"member-vnf-index": None, "target_element": "ns"}
+        nsr_id = self.db_nsd["id"]
+        deployed_vca = DeployedVCA(nsr_id, ns_charm_vca)
+
+        expected_relations = []
+        self.assertEqual(
+            expected_relations,
+            self.my_ns._get_vnf_relations(
+                nsr_id=nsr_id, nsd=self.db_nsd, vca=deployed_vca, cached_vnfds={}
+            ),
+        )
+
+    def test_vnf_returns_relation(self):
+        vnf_vca = {
+            "member-vnf-index": "1",
+            "target_element": "vnf/0",
+            "ee_descriptor_id": "simple-ee",
+            "vdu_id": "mgmtVM",
+        }
+        nsr_id = self.db_nsd["id"]
+        deployed_vca = DeployedVCA(nsr_id, vnf_vca)
+
+        provider_dict = {
+            "nsr-id": nsr_id,
+            "vnf-profile-id": "1",
+            "vdu-profile-id": "mgmtVM",
+            "kdu-resource-profile-id": None,
+            "execution-environment-ref": "simple-ee",
+            "endpoint": "interface",
+        }
+
+        requirer_dict = {
+            "nsr-id": nsr_id,
+            "vnf-profile-id": "1",
+            "vdu-profile-id": "dataVM",
+            "kdu-resource-profile-id": None,
+            "execution-environment-ref": "simple-ee",
+            "endpoint": "interface",
+        }
+
+        provider = EERelation(provider_dict)
+        requirer = EERelation(requirer_dict)
+        relation = Relation("relation", provider, requirer)
+
+        relations_found = self.my_ns._get_vnf_relations(
+            nsr_id=nsr_id, nsd=self.db_nsd, vca=deployed_vca, cached_vnfds={}
+        )
+
+        self.assertEqual(1, len(relations_found))
+        self.assertEqual(relation, relations_found[0])
+
+
 if __name__ == "__main__":
     asynctest.main()
diff --git a/osm_lcm/tests/test_vim_sdn.py b/osm_lcm/tests/test_vim_sdn.py
index f6b75e0..970dd00 100644
--- a/osm_lcm/tests/test_vim_sdn.py
+++ b/osm_lcm/tests/test_vim_sdn.py
@@ -116,6 +116,164 @@
         self.lcm_tasks.unlock_HA.assert_not_called()
         self.lcm_tasks.remove.assert_called_with("vca", "id", "order-id")
 
+    def test_vca_lcm_edit_success_no_config(self):
+        vca_content = {
+            "op_id": "order-id",
+            "_id": "id",
+            "description": "test-description",
+        }
+        db_vca = {
+            "_id": "vca-id",
+            "secret": "secret",
+            "cacert": "cacert",
+            "schema_version": "1.11",
+        }
+        order_id = "order-id"
+        self.lcm_tasks.lock_HA.return_value = True
+        self.vca_lcm.db.get_one.return_value = db_vca
+        self.vca_lcm.n2vc.validate_vca = AsyncMock()
+        self.vca_lcm.update_db_2 = Mock()
+        self.loop.run_until_complete(self.vca_lcm.edit(vca_content, order_id))
+        self.vca_lcm.n2vc.validate_vca.assert_not_called()
+        self.lcm_tasks.unlock_HA.assert_called_with(
+            "vca",
+            "edit",
+            "order-id",
+            operationState="COMPLETED",
+            detailed_status="Edited",
+        )
+        self.vca_lcm.update_db_2.assert_called_with(
+            "vca",
+            "id",
+            {},
+        )
+        self.lcm_tasks.remove.assert_called_with("vca", "id", "order-id")
+
+    def test_vca_lcm_edit_success_config(self):
+        vca_content = {"op_id": "order-id", "_id": "id", "cacert": "editcacert"}
+        db_vca = {
+            "_id": "vca-id",
+            "secret": "secret",
+            "cacert": "cacert",
+            "schema_version": "1.11",
+        }
+        order_id = "order-id"
+        self.lcm_tasks.lock_HA.return_value = True
+        self.vca_lcm.db.get_one.return_value = db_vca
+        self.vca_lcm.n2vc.validate_vca = AsyncMock()
+        self.vca_lcm.update_db_2 = Mock()
+        self.loop.run_until_complete(self.vca_lcm.edit(vca_content, order_id))
+        self.vca_lcm.n2vc.validate_vca.assert_called()
+        self.lcm_tasks.unlock_HA.assert_called_with(
+            "vca",
+            "edit",
+            "order-id",
+            operationState="COMPLETED",
+            detailed_status="Edited",
+        )
+        self.vca_lcm.update_db_2.assert_called_with(
+            "vca",
+            "id",
+            {
+                "_admin.operationalState": "ENABLED",
+                "_admin.detailed-status": "Connectivity: ok",
+            },
+        )
+        self.lcm_tasks.remove.assert_called_with("vca", "id", "order-id")
+
+    def test_vca_lcm_edit_exception_no_config(self):
+        vca_content = {
+            "op_id": "order-id",
+            "_id": "id",
+            "description": "new-description",
+        }
+        db_vca = {
+            "_id": "vca-id",
+            "secret": "secret",
+            "cacert": "cacert",
+            "schema_version": "1.11",
+        }
+        order_id = "order-id"
+        self.lcm_tasks.lock_HA.return_value = True
+        self.vca_lcm.db.get_one.return_value = db_vca
+        self.vca_lcm.n2vc.validate_vca = AsyncMock()
+        # validate_vca should not be called in this case
+        self.vca_lcm.n2vc.validate_vca.side_effect = Exception("failed")
+        self.vca_lcm.update_db_2 = Mock()
+        self.loop.run_until_complete(self.vca_lcm.edit(vca_content, order_id))
+        self.lcm_tasks.lock_HA.assert_called_with("vca", "edit", "order-id")
+        self.lcm_tasks.unlock_HA.assert_called_with(
+            "vca",
+            "edit",
+            "order-id",
+            operationState="COMPLETED",
+            detailed_status="Edited",
+        )
+        self.lcm_tasks.remove.assert_called_with("vca", "id", "order-id")
+
+    def test_vca_lcm_edit_exception_config(self):
+        vca_content = {"op_id": "order-id", "_id": "id", "user": "new-user"}
+        db_vca = {
+            "_id": "vca-id",
+            "secret": "secret",
+            "cacert": "cacert",
+            "schema_version": "1.11",
+        }
+        order_id = "order-id"
+        self.lcm_tasks.lock_HA.return_value = True
+        self.vca_lcm.db.get_one.return_value = db_vca
+        self.vca_lcm.n2vc.validate_vca = AsyncMock()
+        # validate_vca should be called in this case
+        self.vca_lcm.n2vc.validate_vca.side_effect = Exception("failed")
+        self.vca_lcm.update_db_2 = Mock()
+        self.loop.run_until_complete(self.vca_lcm.edit(vca_content, order_id))
+        self.lcm_tasks.lock_HA.assert_called_with("vca", "edit", "order-id")
+        self.lcm_tasks.unlock_HA.assert_called_with(
+            "vca",
+            "edit",
+            "order-id",
+            operationState="FAILED",
+            detailed_status="Failed with exception: failed",
+        )
+        self.vca_lcm.update_db_2.assert_called_with(
+            "vca",
+            "id",
+            {
+                "_admin.operationalState": "ERROR",
+                "_admin.detailed-status": "Failed with exception: failed",
+            },
+        )
+        self.lcm_tasks.remove.assert_called_with("vca", "id", "order-id")
+
+    def test_vca_lcm_edit_db_exception(self):
+        vca_content = {
+            "op_id": "order-id",
+            "_id": "id",
+            "description": "new-description",
+        }
+        db_vca = {
+            "_id": "vca-id",
+            "secret": "secret",
+            "cacert": "cacert",
+            "schema_version": "1.11",
+        }
+        order_id = "order-id"
+        self.lcm_tasks.lock_HA.return_value = True
+        self.vca_lcm.db.get_one.return_value = db_vca
+        self.vca_lcm.n2vc.validate_vca = AsyncMock()
+        self.vca_lcm.update_db_2 = Mock()
+        self.vca_lcm.update_db_2.side_effect = DbException("failed")
+        self.loop.run_until_complete(self.vca_lcm.edit(vca_content, order_id))
+        self.vca_lcm.n2vc.validate_vca.assert_not_called()
+        self.lcm_tasks.lock_HA.assert_called_with("vca", "edit", "order-id")
+        self.vca_lcm.update_db_2.assert_called_with(
+            "vca",
+            "id",
+            {},
+        )
+        self.lcm_tasks.unlock_HA.assert_not_called()
+        self.lcm_tasks.remove.assert_called_with("vca", "id", "order-id")
+
     def test_vca_lcm_delete(self):
         vca_content = {"op_id": "order-id", "_id": "id"}
         order_id = "order-id"
diff --git a/osm_lcm/vim_sdn.py b/osm_lcm/vim_sdn.py
index 0c22305..41a16e5 100644
--- a/osm_lcm/vim_sdn.py
+++ b/osm_lcm/vim_sdn.py
@@ -1006,7 +1006,7 @@
         logging_text = "Task sdn_delete={} ".format(sdn_id)
         self.logger.debug(logging_text + "Enter")
 
-        db_sdn = None
+        db_sdn = {}
         db_sdn_update = {}
         exc = None
         step = "Getting sdn from db"
@@ -1039,7 +1039,7 @@
                     logging_text + "Skipping. There is not RO information at database"
                 )
             self.db.del_one("sdns", {"_id": sdn_id})
-            db_sdn = None
+            db_sdn = {}
             self.logger.debug("sdn_delete task sdn_id={} Exit Ok".format(sdn_id))
             return
 
@@ -1466,6 +1466,33 @@
         )
         return db_vca
 
+    async def _validate_vca(self, db_vca_id: str) -> None:
+        task = asyncio.ensure_future(
+            asyncio.wait_for(
+                self.n2vc.validate_vca(db_vca_id),
+                timeout=self.timeout_create,
+            )
+        )
+        await asyncio.wait([task], return_when=asyncio.FIRST_COMPLETED)
+        if task.exception():
+            raise task.exception()
+
+    def _is_vca_config_update(self, update_options) -> bool:
+        return any(
+            word in update_options.keys()
+            for word in [
+                "cacert",
+                "endpoints",
+                "lxd-cloud",
+                "lxd-credentials",
+                "k8s-cloud",
+                "k8s-credentials",
+                "model-config",
+                "user",
+                "secret",
+            ]
+        )
+
     async def create(self, vca_content, order_id):
         op_id = vca_content.pop("op_id", None)
         if not self.lcm_tasks.lock_HA("vca", "create", op_id):
@@ -1474,25 +1501,17 @@
         vca_id = vca_content["_id"]
         self.logger.debug("Task vca_create={} {}".format(vca_id, "Enter"))
 
-        db_vca = None
         db_vca_update = {}
 
+        operation_state = "FAILED"
+        operation_details = ""
         try:
             self.logger.debug(
                 "Task vca_create={} {}".format(vca_id, "Getting vca from db")
             )
             db_vca = self._get_vca_by_id(vca_id)
 
-            task = asyncio.ensure_future(
-                asyncio.wait_for(
-                    self.n2vc.validate_vca(db_vca["_id"]),
-                    timeout=self.timeout_create,
-                )
-            )
-
-            await asyncio.wait([task], return_when=asyncio.FIRST_COMPLETED)
-            if task.exception():
-                raise task.exception()
+            await self._validate_vca(db_vca["_id"])
             self.logger.debug(
                 "Task vca_create={} {}".format(
                     vca_id, "vca registered and validated successfully"
@@ -1514,7 +1533,6 @@
             self.logger.error("Task vca_create={} {}".format(vca_id, error_msg))
             db_vca_update["_admin.operationalState"] = "ERROR"
             db_vca_update["_admin.detailed-status"] = error_msg
-            operation_state = "FAILED"
             operation_details = error_msg
         finally:
             try:
@@ -1536,6 +1554,70 @@
                 )
             self.lcm_tasks.remove("vca", vca_id, order_id)
 
+    async def edit(self, vca_content, order_id):
+        op_id = vca_content.pop("op_id", None)
+        if not self.lcm_tasks.lock_HA("vca", "edit", op_id):
+            return
+
+        vca_id = vca_content["_id"]
+        self.logger.debug("Task vca_edit={} {}".format(vca_id, "Enter"))
+
+        db_vca = None
+        db_vca_update = {}
+
+        operation_state = "FAILED"
+        operation_details = ""
+        try:
+            self.logger.debug(
+                "Task vca_edit={} {}".format(vca_id, "Getting vca from db")
+            )
+            db_vca = self._get_vca_by_id(vca_id)
+            if self._is_vca_config_update(vca_content):
+                await self._validate_vca(db_vca["_id"])
+                self.logger.debug(
+                    "Task vca_edit={} {}".format(
+                        vca_id, "vca registered and validated successfully"
+                    )
+                )
+                db_vca_update["_admin.operationalState"] = "ENABLED"
+                db_vca_update["_admin.detailed-status"] = "Connectivity: ok"
+
+            operation_details = "Edited"
+            operation_state = "COMPLETED"
+
+            self.logger.debug(
+                "Task vca_edit={} {}".format(
+                    vca_id, "Done. Result: {}".format(operation_state)
+                )
+            )
+
+        except Exception as e:
+            error_msg = "Failed with exception: {}".format(e)
+            self.logger.error("Task vca_edit={} {}".format(vca_id, error_msg))
+            db_vca_update["_admin.operationalState"] = "ERROR"
+            db_vca_update["_admin.detailed-status"] = error_msg
+            operation_state = "FAILED"
+            operation_details = error_msg
+        finally:
+            try:
+                self.update_db_2("vca", vca_id, db_vca_update)
+
+                # Register the operation and unlock
+                self.lcm_tasks.unlock_HA(
+                    "vca",
+                    "edit",
+                    op_id,
+                    operationState=operation_state,
+                    detailed_status=operation_details,
+                )
+            except DbException as e:
+                self.logger.error(
+                    "Task vca_edit={} {}".format(
+                        vca_id, "Cannot update database: {}".format(e)
+                    )
+                )
+            self.lcm_tasks.remove("vca", vca_id, order_id)
+
     async def delete(self, vca_content, order_id):
 
         # HA tasks and backward compatibility:
@@ -1549,6 +1631,9 @@
         db_vca_update = {}
         vca_id = vca_content["_id"]
 
+        operation_state = "FAILED"
+        operation_details = ""
+
         try:
             self.logger.debug(
                 "Task vca_delete={} {}".format(vca_id, "Deleting vca from db")
@@ -1568,7 +1653,6 @@
             self.logger.error("Task vca_delete={} {}".format(vca_id, error_msg))
             db_vca_update["_admin.operationalState"] = "ERROR"
             db_vca_update["_admin.detailed-status"] = error_msg
-            operation_state = "FAILED"
             operation_details = error_msg
         finally:
             try:
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 39172fd..2ec95c2 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -35,12 +35,7 @@
     #   requests
 cffi==1.15.1
     # via
-<<<<<<< HEAD
     #   -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=paas
-    #   bcrypt
-=======
-    #   -r https://osm.etsi.org/gitweb/?p=osm/N2VC.git;a=blob_plain;f=requirements.txt;hb=master
->>>>>>> fb1e25f (Fix black issues and run tox properly in stage-test)
     #   cryptography
     #   pynacl
 charset-normalizer==2.1.1
diff --git a/tox.ini b/tox.ini
index b014de1..1cb2253 100644
--- a/tox.ini
+++ b/tox.ini
@@ -67,7 +67,7 @@
         -r{toxinidir}/requirements-test.txt
         pylint
 commands =
-    - pylint -E osm_lcm
+      pylint -E osm_lcm --extension-pkg-whitelist=pydantic # issue with pydantic (https://github.com/pydantic/pydantic/issues/1961)
 
 
 #######################################################################################