Bug 1234: OSM reports successful deployment when a charm relation fails 27/12827/1
authorPatricia Reinoso <patricia.reinoso@canonical.com>
Fri, 6 Jan 2023 22:28:44 +0000 (22:28 +0000)
committerPatricia Reinoso <patricia.reinoso@canonical.com>
Fri, 6 Jan 2023 22:31:07 +0000 (22:31 +0000)
Fix : raise exception when a problem occurs during
charm relation addition.

NS is marked as broken if charm relations cannot
be added.

Change-Id: I4dbf4ddde374fcb46b5b1c20ec253fbb25c1f22c
Signed-off-by: Patricia Reinoso <patricia.reinoso@canonical.com>
osm_lcm/ns.py
osm_lcm/tests/test_db_descriptors.py
osm_lcm/tests/test_ns.py

index 3e6bafc..b7df0b6 100644 (file)
@@ -2044,13 +2044,16 @@ class NsLcm(LcmBase):
             )
 
             # add relations for this VCA (wait for other peers related with this VCA)
-            await self._add_vca_relations(
+            is_relation_added = await self._add_vca_relations(
                 logging_text=logging_text,
                 nsr_id=nsr_id,
                 vca_type=vca_type,
                 vca_index=vca_index,
             )
 
+            if not is_relation_added:
+                raise LcmException("Relations could not be added to VCA.")
+
             # if SSH access is required, then get execution environment SSH public
             # if native charm we have waited already to VM be UP
             if vca_type in ("k8s_proxy_charm", "lxc_proxy_charm", "helm", "helm-v3"):
@@ -2245,7 +2248,7 @@ class NsLcm(LcmBase):
             self._write_configuration_status(
                 nsr_id=nsr_id, vca_index=vca_index, status="BROKEN"
             )
-            raise LcmException("{} {}".format(step, e)) from e
+            raise LcmException("{}. {}".format(step, e)) from e
 
     def _write_ns_status(
         self,
@@ -3213,11 +3216,14 @@ class NsLcm(LcmBase):
                 requirer_vca_id,
                 relation.requirer.endpoint,
             )
-            await self.vca_map[vca_type].add_relation(
-                provider=provider_relation_endpoint,
-                requirer=requirer_relation_endpoint,
-            )
-            # remove entry from relations list
+            try:
+                await self.vca_map[vca_type].add_relation(
+                    provider=provider_relation_endpoint,
+                    requirer=requirer_relation_endpoint,
+                )
+            except N2VCException as exception:
+                self.logger.error(exception)
+                raise LcmException(exception)
             return True
         return False
 
index d02b011..7f47231 100644 (file)
@@ -529,10 +529,11 @@ db_nsrs_text = """
                 type: lxc_proxy_charm
                 vnfd_id: hackfest3charmed-vnf
             -   application: alf-c-ab
-                ee_id: f48163a6-c807-47bc-9682-f72caef5af85.alf-c-ab
+                ee_id: "model_name.application_name.machine_id"
+                ee_descriptor_id: f48163a6-c807-47bc-9682-f72caef5af85.alf-c-ab
                 needed_terminate: True
                 detailed-status: Ready!
-                member-vnf-index: '2'
+                member-vnf-index: hackfest_vnf1
                 model: f48163a6-c807-47bc-9682-f72caef5af85
                 operational-status: active
                 primitive_id: null
@@ -543,6 +544,7 @@ db_nsrs_text = """
                 vdu_name: null
                 type: lxc_proxy_charm
                 vnfd_id: hackfest3charmed-vnf
+                config_sw_installed: true
             VCA-model-name: f48163a6-c807-47bc-9682-f72caef5af85
         modified: 1566823354.3716335
         nsState: INSTANTIATED
index 1082648..2dbc006 100644 (file)
@@ -21,6 +21,7 @@ import asyncio
 from copy import deepcopy
 import yaml
 import copy
+from n2vc.exceptions import N2VCException
 from os import getenv
 from osm_lcm import ns
 from osm_common.msgkafka import MsgKafka
@@ -30,6 +31,7 @@ from osm_lcm.lcm_utils import TaskRegistry
 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.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
@@ -97,7 +99,7 @@ def callable(a):
     return a
 
 
-class TestMyNS(asynctest.TestCase):
+class TestBaseNS(asynctest.TestCase):
     async def _n2vc_DeployCharms(
         self,
         model_name,
@@ -185,8 +187,17 @@ class TestMyNS(asynctest.TestCase):
         return str(uuid4())
 
     async def setUp(self):
-
-        # Mock DB
+        self.mock_db()
+        self.mock_kafka()
+        self.mock_filesystem()
+        self.mock_task_registry()
+        self.mock_vca_k8s()
+        self.create_nslcm_class()
+        self.mock_logging()
+        self.mock_vca_n2vc()
+        self.mock_ro()
+
+    def mock_db(self):
         if not getenv("OSMLCMTEST_DB_NOMOCK"):
             # Cleanup singleton Database instance
             Database.instance = None
@@ -194,18 +205,15 @@ class TestMyNS(asynctest.TestCase):
             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)
@@ -213,10 +221,10 @@ class TestMyNS(asynctest.TestCase):
             self.db.create_list("vnfrs", yaml.safe_load(descriptors.db_vnfrs_text))
             self.db_vim_accounts = yaml.safe_load(descriptors.db_vim_accounts_text)
 
-        # Mock kafka
+    def mock_kafka(self):
         self.msg = asynctest.Mock(MsgKafka())
 
-        # Mock filesystem
+    def mock_filesystem(self):
         if not getenv("OSMLCMTEST_FS_NOMOCK"):
             self.fs = asynctest.Mock(
                 Filesystem({"storage": {"driver": "local", "path": "/"}}).instance.fs
@@ -228,13 +236,13 @@ class TestMyNS(asynctest.TestCase):
             # self.fs.file_open.return_value.__enter__.return_value = asynctest.MagicMock()  # called on a python "with"
             # self.fs.file_open.return_value.__enter__.return_value.read.return_value = ""   # empty file
 
-        # Mock TaskRegistry
+    def mock_task_registry(self):
         self.lcm_tasks = asynctest.Mock(TaskRegistry())
         self.lcm_tasks.lock_HA.return_value = True
         self.lcm_tasks.waitfor_related_HA.return_value = None
         self.lcm_tasks.lookfor_related.return_value = ("", [])
 
-        # Mock VCA - K8s
+    def mock_vca_k8s(self):
         if not getenv("OSMLCMTEST_VCA_K8s_NOMOCK"):
             ns.K8sJujuConnector = asynctest.MagicMock(ns.K8sJujuConnector)
             ns.K8sHelmConnector = asynctest.MagicMock(ns.K8sHelmConnector)
@@ -244,17 +252,17 @@ class TestMyNS(asynctest.TestCase):
             ns.N2VCJujuConnector = asynctest.MagicMock(ns.N2VCJujuConnector)
             ns.LCMHelmConn = asynctest.MagicMock(ns.LCMHelmConn)
 
-        # Create NsLCM class
+    def create_nslcm_class(self):
         self.my_ns = ns.NsLcm(self.msg, self.lcm_tasks, lcm_config, self.loop)
         self.my_ns.fs = self.fs
         self.my_ns.db = self.db
         self.my_ns._wait_dependent_n2vc = asynctest.CoroutineMock()
 
-        # Mock logging
+    def mock_logging(self):
         if not getenv("OSMLCMTEST_LOGGING_NOMOCK"):
             self.my_ns.logger = asynctest.Mock(self.my_ns.logger)
 
-        # Mock VCA - N2VC
+    def mock_vca_n2vc(self):
         if not getenv("OSMLCMTEST_VCA_NOMOCK"):
             pub_key = getenv("OSMLCMTEST_NS_PUBKEY", "ssh-rsa test-pub-key t@osm.com")
             # self.my_ns.n2vc = asynctest.Mock(N2VC())
@@ -298,8 +306,11 @@ class TestMyNS(asynctest.TestCase):
             self.my_ns.n2vc.delete_namespace = asynctest.CoroutineMock(
                 return_value=None
             )
+            self.my_ns.n2vc.register_execution_environment = asynctest.CoroutineMock(
+                return_value="model-name.application-name.k8s"
+            )
 
-        # Mock RO
+    def mock_ro(self):
         if not getenv("OSMLCMTEST_RO_NOMOCK"):
             self.my_ns.RO = asynctest.Mock(
                 NgRoClient(self.loop, **lcm_config.RO.to_dict())
@@ -396,6 +407,8 @@ class TestMyNS(asynctest.TestCase):
     #     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"]
@@ -1180,11 +1193,7 @@ class TestMyNS(asynctest.TestCase):
             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 = [
@@ -1262,9 +1271,7 @@ class TestMyNS(asynctest.TestCase):
         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})
 
@@ -1306,20 +1313,12 @@ class TestMyNS(asynctest.TestCase):
         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})
 
@@ -1506,10 +1505,7 @@ class TestMyNS(asynctest.TestCase):
 
     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
@@ -1519,9 +1515,7 @@ class TestMyNS(asynctest.TestCase):
 
     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"
@@ -1529,5 +1523,107 @@ class TestMyNS(asynctest.TestCase):
         self.assertEqual(result, expected_result, "Wrong charm artifact path")
 
 
+class TestInstantiateN2VC(TestBaseNS):
+    async def setUp(self):
+        await super().setUp()
+        self.db_nsr = yaml.safe_load(descriptors.db_nsrs_text)[0]
+        self.db_vnfr = yaml.safe_load(descriptors.db_vnfrs_text)[0]
+        self.vca_index = 1
+        self.my_ns._write_configuration_status = Mock()
+
+    async def call_instantiate_N2VC(self):
+        logging_text = "N2VC Instantiation"
+        config_descriptor = {"config-access": {"ssh-access": {"default-user": "admin"}}}
+        base_folder = {"pkg-dir": "", "folder": "~"}
+        stage = ["Stage", "Message"]
+
+        await self.my_ns.instantiate_N2VC(
+            logging_text=logging_text,
+            vca_index=self.vca_index,
+            nsi_id="nsi_id",
+            db_nsr=self.db_nsr,
+            db_vnfr=self.db_vnfr,
+            vdu_id=None,
+            kdu_name=None,
+            vdu_index=None,
+            kdu_index=None,
+            config_descriptor=config_descriptor,
+            deploy_params={},
+            base_folder=base_folder,
+            nslcmop_id="nslcmop_id",
+            stage=stage,
+            vca_type="native_charm",
+            vca_name="vca_name",
+            ee_config_descriptor={},
+        )
+
+    def check_config_status(self, expected_status):
+        self.my_ns._write_configuration_status.assert_called_with(
+            nsr_id=self.db_nsr["_id"], vca_index=self.vca_index, status=expected_status
+        )
+
+    async def call_ns_add_relation(self):
+        ee_relation = EERelation(
+            {
+                "nsr-id": self.db_nsr["_id"],
+                "vdu-profile-id": None,
+                "kdu-resource-profile-id": None,
+                "vnf-profile-id": "hackfest_vnf1",
+                "execution-environment-ref": "f48163a6-c807-47bc-9682-f72caef5af85.alf-c-ab",
+                "endpoint": "127.0.0.1",
+            }
+        )
+
+        relation = Relation("relation-name", ee_relation, ee_relation)
+        cached_vnfrs = {"hackfest_vnf1": self.db_vnfr}
+
+        return await self.my_ns._add_relation(
+            relation=relation,
+            vca_type="native_charm",
+            db_nsr=self.db_nsr,
+            cached_vnfds={},
+            cached_vnfrs=cached_vnfrs,
+        )
+
+    async def test_add_relation_ok(self):
+        await self.call_instantiate_N2VC()
+        self.check_config_status(expected_status="READY")
+
+    async def test_add_relation_returns_false_raises_exception(self):
+        self.my_ns._add_vca_relations = asynctest.CoroutineMock(return_value=False)
+
+        with self.assertRaises(LcmException) as exception:
+            await self.call_instantiate_N2VC()
+
+        exception_msg = "Relations could not be added to VCA."
+        self.assertTrue(exception_msg in str(exception.exception))
+        self.check_config_status(expected_status="BROKEN")
+
+    async def test_add_relation_raises_lcm_exception(self):
+        exception_msg = "Relations FAILED"
+        self.my_ns._add_vca_relations = asynctest.CoroutineMock(
+            side_effect=LcmException(exception_msg)
+        )
+
+        with self.assertRaises(LcmException) as exception:
+            await self.call_instantiate_N2VC()
+
+        self.assertTrue(exception_msg in str(exception.exception))
+        self.check_config_status(expected_status="BROKEN")
+
+    async def test_n2vc_add_relation_fails_raises_exception(self):
+        exception_msg = "N2VC failed to add relations"
+        self.my_ns.n2vc.add_relation = asynctest.CoroutineMock(
+            side_effect=N2VCException(exception_msg)
+        )
+        with self.assertRaises(LcmException) as exception:
+            await self.call_ns_add_relation()
+        self.assertTrue(exception_msg in str(exception.exception))
+
+    async def test_n2vc_add_relation_ok_returns_true(self):
+        self.my_ns.n2vc.add_relation = asynctest.CoroutineMock(return_value=None)
+        self.assertTrue(await self.call_ns_add_relation())
+
+
 if __name__ == "__main__":
     asynctest.main()