Feature 10916 Remove VNF Instance from NS - NS Update 76/11876/14
authorelumalai <deepika.e@tataelxsi.co.in>
Tue, 12 Apr 2022 07:17:32 +0000 (12:47 +0530)
committerelumalai <deepika.e@tataelxsi.co.in>
Wed, 25 May 2022 12:25:43 +0000 (14:25 +0200)
Added feature support to remove a VNF using NS Update
Added unit test cases

Change-Id: Ib8f8309544502fd8e580b8e62bcabb7dee64fddc
Signed-off-by: elumalai <deepika.e@tataelxsi.co.in>
osm_lcm/lcm.py
osm_lcm/ns.py
osm_lcm/tests/test_db_descriptors.py
osm_lcm/tests/test_ns.py

index 731488c..82947e9 100644 (file)
@@ -485,6 +485,7 @@ class Lcm:
             elif command == "deleted":
                 return  # TODO cleaning of task just in case should be done
             elif command in (
+                "vnf_terminated",
                 "terminated",
                 "instantiated",
                 "scaled",
@@ -493,6 +494,7 @@ class Lcm:
                 "migrated",
             ):  # "scaled-cooldown-time"
                 return
+
         elif topic == "nsi":  # netslice LCM processes (instantiate, terminate, etc)
             if command == "instantiate":
                 # self.logger.debug("Instantiating Network Slice {}".format(nsilcmop["netsliceInstanceId"]))
index 5c2d4c3..19b405b 100644 (file)
@@ -5299,6 +5299,105 @@ class NsLcm(LcmBase):
             self.lcm_tasks.remove("ns", nsr_id, nslcmop_id, "ns_action")
             return nslcmop_operation_state, detailed_status
 
+    async def terminate_vdus(
+        self, db_vnfr, member_vnf_index, db_nsr, update_db_nslcmops, stage, logging_text
+    ):
+        """This method terminates VDUs
+
+        Args:
+            db_vnfr: VNF instance record
+            member_vnf_index: VNF index to identify the VDUs to be removed
+            db_nsr: NS instance record
+            update_db_nslcmops: Nslcmop update record
+        """
+        vca_scaling_info = []
+        scaling_info = {"scaling_group_name": "vdu_autoscale", "vdu": [], "kdu": []}
+        scaling_info["scaling_direction"] = "IN"
+        scaling_info["vdu-delete"] = {}
+        scaling_info["kdu-delete"] = {}
+        db_vdur = db_vnfr.get("vdur")
+        vdur_list = copy(db_vdur)
+        count_index = 0
+        for index, vdu in enumerate(vdur_list):
+            vca_scaling_info.append(
+                {
+                    "osm_vdu_id": vdu["vdu-id-ref"],
+                    "member-vnf-index": member_vnf_index,
+                    "type": "delete",
+                    "vdu_index": count_index,
+                    })
+            scaling_info["vdu-delete"][vdu["vdu-id-ref"]] = count_index
+            scaling_info["vdu"].append(
+                {
+                    "name": vdu.get("name") or vdu.get("vdu-name"),
+                    "vdu_id": vdu["vdu-id-ref"],
+                    "interface": [],
+                    })
+            for interface in vdu["interfaces"]:
+                scaling_info["vdu"][index]["interface"].append(
+                    {
+                        "name": interface["name"],
+                        "ip_address": interface["ip-address"],
+                        "mac_address": interface.get("mac-address"),
+                        })
+            self.logger.info("NS update scaling info{}".format(scaling_info))
+            stage[2] = "Terminating VDUs"
+            if scaling_info.get("vdu-delete"):
+                # scale_process = "RO"
+                if self.ro_config.get("ng"):
+                    await self._scale_ng_ro(
+                        logging_text, db_nsr, update_db_nslcmops, db_vnfr, scaling_info, stage
+                    )
+
+    async def remove_vnf(
+        self, nsr_id, nslcmop_id, vnf_instance_id
+    ):
+        """This method is to Remove VNF instances from NS.
+
+        Args:
+            nsr_id: NS instance id
+            nslcmop_id: nslcmop id of update
+            vnf_instance_id: id of the VNF instance to be removed
+
+        Returns:
+            result: (str, str) COMPLETED/FAILED, details
+        """
+        try:
+            db_nsr_update = {}
+            logging_text = "Task ns={} update ".format(nsr_id)
+            check_vnfr_count = len(self.db.get_list("vnfrs", {"nsr-id-ref": nsr_id}))
+            self.logger.info("check_vnfr_count {}".format(check_vnfr_count))
+            if check_vnfr_count > 1:
+                stage = ["", "", ""]
+                step = "Getting nslcmop from database"
+                self.logger.debug(step + " after having waited for previous tasks to be completed")
+                # db_nslcmop = self.db.get_one("nslcmops", {"_id": nslcmop_id})
+                db_nsr = self.db.get_one("nsrs", {"_id": nsr_id})
+                db_vnfr = self.db.get_one("vnfrs", {"_id": vnf_instance_id})
+                member_vnf_index = db_vnfr["member-vnf-index-ref"]
+                """ db_vnfr = self.db.get_one(
+                    "vnfrs", {"member-vnf-index-ref": member_vnf_index, "nsr-id-ref": nsr_id}) """
+
+                update_db_nslcmops = self.db.get_one("nslcmops", {"_id": nslcmop_id})
+                await self.terminate_vdus(db_vnfr, member_vnf_index, db_nsr, update_db_nslcmops, stage, logging_text)
+
+                constituent_vnfr = db_nsr.get("constituent-vnfr-ref")
+                constituent_vnfr.remove(db_vnfr.get("_id"))
+                db_nsr_update["constituent-vnfr-ref"] = db_nsr.get("constituent-vnfr-ref")
+                self.update_db_2("nsrs", nsr_id, db_nsr_update)
+                self.db.del_one("vnfrs", {"_id": db_vnfr.get("_id")})
+                self.update_db_2("nsrs", nsr_id, db_nsr_update)
+                return "COMPLETED", "Done"
+            else:
+                step = "Terminate VNF Failed with"
+                raise LcmException("{} Cannot terminate the last VNF in this NS.".format(
+                    vnf_instance_id))
+        except (LcmException, asyncio.CancelledError):
+            raise
+        except Exception as e:
+            self.logger.debug("Error removing VNF {}".format(e))
+            return "FAILED", "Error removing VNF {}".format(e)
+
     async def _ns_charm_upgrade(
         self,
         ee_id,
@@ -5371,7 +5470,7 @@ class NsLcm(LcmBase):
         db_nsr_update = {}
         error_description_nslcmop = ""
         exc = None
-        change_type = ""
+        change_type = "updated"
         detailed_status = ""
 
         try:
@@ -5597,7 +5696,24 @@ class NsLcm(LcmBase):
                     )
             elif update_type == "REMOVE_VNF":
                 # This part is included in https://osm.etsi.org/gerrit/11876
-                pass
+                vnf_instance_id = db_nslcmop["operationParams"]["removeVnfInstanceId"]
+                db_vnfr = self.db.get_one("vnfrs", {"_id": vnf_instance_id})
+                member_vnf_index = db_vnfr["member-vnf-index-ref"]
+                step = "Removing VNF"
+                (result, detailed_status) = await self.remove_vnf(nsr_id, nslcmop_id, vnf_instance_id)
+                if result == "FAILED":
+                    nslcmop_operation_state = result
+                    error_description_nslcmop = detailed_status
+                db_nslcmop_update["detailed-status"] = detailed_status
+                change_type = "vnf_terminated"
+                if not nslcmop_operation_state:
+                    nslcmop_operation_state = "COMPLETED"
+                self.logger.debug(
+                    logging_text
+                    + " task Done with result {} {}".format(
+                        nslcmop_operation_state, detailed_status
+                    )
+                )
 
             #  If nslcmop_operation_state is None, so any operation is not failed.
             #  All operations are executed in overall.
@@ -5650,16 +5766,14 @@ class NsLcm(LcmBase):
 
             if nslcmop_operation_state:
                 try:
-                    await self.msg.aiowrite(
-                        "ns",
-                        "updated",
-                        {
+                    msg = {
                             "nsr_id": nsr_id,
                             "nslcmop_id": nslcmop_id,
                             "operationState": nslcmop_operation_state,
-                        },
-                        loop=self.loop,
-                    )
+                        }
+                    if change_type in ("vnf_terminated"):
+                        msg.update({"vnf_member_index": member_vnf_index})
+                    await self.msg.aiowrite("ns", change_type, msg, loop=self.loop)
                 except Exception as e:
                     self.logger.error(
                         logging_text + "kafka_write notification Exception {}".format(e)
index 457ea30..2e001c0 100644 (file)
@@ -316,6 +316,39 @@ db_nslcmops_text = """
     operationState: COMPLETED
     startTime: 1575034637.0445576
     statusEnteredTime: 1575034663.8484545
+
+-   _admin:
+        created: 1566823354.4148262
+        modified: 1566823354.4148262
+        projects_read:
+        - 25b5aebf-3da1-49ed-99de-1d2b4a86d6e4
+        projects_write:
+        - 25b5aebf-3da1-49ed-99de-1d2b4a86d6e4
+        worker: 86434c2948e2
+        operations:
+        -   member_vnf_index: '1'
+            primitive: touch
+            primitive_params: /home/ubuntu/last-touch-1
+            operationState: COMPLETED
+            detailed-status: Done
+    _id: a639fac7-e0bb-4225-8ecb-c1f8efcc125f
+    detailed-status: done
+    id: a639fac7-e0bb-4225-8ecb-c1f8efcc125f
+    isAutomaticInvocation: false
+    isCancelPending: false
+    lcmOperationType: update
+    links:
+        nsInstance: /osm/nslcm/v1/ns_instances/f48163a6-c807-47bc-9682-f72caef5af85
+        self: /osm/nslcm/v1/ns_lcm_op_occs/a639fac7-e0bb-4225-8ecb-c1f8efcc125f
+    nsInstanceId: f48163a6-c807-47bc-9682-f72caef5af85
+    operationParams:
+      lcmOperationType: update
+      nsInstanceId: f48163a6-c807-47bc-9682-f72caef5af85
+      removeVnfInstanceId: 88d90b0c-faff-4b9f-bccd-017f33985984
+      updateType: REMOVE_VNF
+    operationState: FAILED
+    startTime: 1566823354.414689
+    statusEnteredTime: 1566824534.5112448
 """
 
 db_nsrs_text = """
@@ -2340,6 +2373,22 @@ db_nslcmops_scale_text = """
     detailed-status: done
 """
 
+ro_update_action_text = """
+action_id: e62fc036-6e6f-4a6f-885e-bc12e2fbe75d
+details: progress 1/1
+nsr_id: 31dbfa80-80a8-4f2a-a557-626904df3402
+status: DONE
+tasks:
+- action: DELETE
+  action_id: e62fc036-6e6f-4a6f-885e-bc12e2fbe75d
+  item: vdu
+  nsr_id: 31dbfa80-80a8-4f2a-a557-626904df3402
+  status: FINISHED
+  target_record: vnfrs:5bbe7015-ae98-4e09-9316-76f3bf218353:vdur.0.vim_info.vim:2a3dc443-415b-4865-8420-f804b993c5a3
+  target_record_id: vnfrs:5bbe7015-ae98-4e09-9316-76f3bf218353:vdur.e03e2281-c70e-44ef-ac3b-052b81efd31d
+  task_id: e62fc036-6e6f-4a6f-885e-bc12e2fbe75d:0
+"""
+
 test_ids = {
     # contains the ids of ns and operations of every test
     "TEST-A": {
@@ -2363,4 +2412,9 @@ test_ids = {
         "instantiate": "4013bbd2-b151-40ee-bcef-7e24ce5432f6",
         "terminate": None,
     },
+    "TEST-UPDATE": {
+        "ns": "f48163a6-c807-47bc-9682-f72caef5af85",
+        "vnf": "88d90b0c-faff-4b9f-bccd-017f33985984",
+        "removeVnf": "a639fac7-e0bb-4225-8ecb-c1f8efcc125f",
+    },
 }
index 86e2136..7613a88 100644 (file)
@@ -120,6 +120,13 @@ class TestMyNS(asynctest.TestCase):
     def _ro_status(self, *args, **kwargs):
         print("Args > {}".format(args))
         print("kwargs > {}".format(kwargs))
+        if args:
+            if "update" in args:
+                ro_ns_desc = yaml.load(
+                    descriptors.ro_update_action_text, Loader=yaml.Loader
+                )
+                while True:
+                    yield ro_ns_desc
         if kwargs.get("delete"):
             ro_ns_desc = yaml.load(
                 descriptors.ro_delete_action_text, Loader=yaml.Loader
@@ -783,6 +790,24 @@ class TestMyNS(asynctest.TestCase):
             or expected_kdu_model in nsr_kdu_model_result
         )
 
+    # Test remove_vnf() and related methods
+    @asynctest.fail_on(active_handles=True)  # all async tasks must be completed
+    async def test_remove_vnf(self):
+        # Test REMOVE_VNF
+        nsr_id = descriptors.test_ids["TEST-UPDATE"]["ns"]
+        nslcmop_id = descriptors.test_ids["TEST-UPDATE"]["removeVnf"]
+        vnf_instance_id = descriptors.test_ids["TEST-UPDATE"]["vnf"]
+        self.my_ns.RO.status = asynctest.CoroutineMock(self.my_ns.RO.status, side_effect=self._ro_status("update"))
+        await self.my_ns.update(nsr_id, nslcmop_id)
+        expected_value = "COMPLETED"
+        return_value = self.db.get_one("nslcmops", {"_id": nslcmop_id}).get(
+            "operationState"
+        )
+        self.assertEqual(return_value, expected_value)
+        with self.assertRaises(Exception) as context:
+            self.db.get_one("vnfrs", {"_id": vnf_instance_id})
+        self.assertTrue("database exception Not found entry with filter" in str(context.exception))
+
     # async def test_instantiate_pdu(self):
     #     nsr_id = descriptors.test_ids["TEST-A"]["ns"]
     #     nslcmop_id = descriptors.test_ids["TEST-A"]["instantiate"]