Bug 2174: VCA Update is not implemented 30/12830/8
authorDario Faccin <dario.faccin@canonical.com>
Tue, 10 Jan 2023 10:38:41 +0000 (10:38 +0000)
committercubag <gcuba@whitestack.com>
Tue, 24 Jan 2023 03:48:04 +0000 (04:48 +0100)
Fix: Handle the edit operation.
If no update on the VCA configuration is given as input, then the operation is
marked as completed.
Otherwise the validation of the new VCA configuration is performed,
which may cause the edit operation to fail.

Change-Id: Id13604664df3594874877d0cb2ad13b1ce6d2353
Signed-off-by: Dario Faccin <dario.faccin@canonical.com>
osm_lcm/lcm.py
osm_lcm/tests/test_vim_sdn.py
osm_lcm/vim_sdn.py

index e6c4e19..6da333c 100644 (file)
@@ -361,6 +361,11 @@ class Lcm:
                 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))
index f6b75e0..970dd00 100644 (file)
@@ -116,6 +116,164 @@ class TestVcaLcm(TestCase):
         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"
index d95df94..41a16e5 100644 (file)
@@ -1466,6 +1466,33 @@ class VcaLcm(LcmBase):
         )
         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):
@@ -1484,16 +1511,7 @@ class VcaLcm(LcmBase):
             )
             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"
@@ -1536,6 +1554,70 @@ class VcaLcm(LcmBase):
                 )
             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: