OSMENG-1007 Implement the Instantiate VDU workflow 34/13234/4
authorPatricia Reinoso <patricia.reinoso@canonical.com>
Mon, 17 Apr 2023 12:34:16 +0000 (12:34 +0000)
committerPatricia Reinoso <patricia.reinoso@canonical.com>
Wed, 19 Apr 2023 14:46:08 +0000 (14:46 +0000)
Change-Id: If77c2711ab5d5ce700d281bb46cd01ed9e88ad90
Signed-off-by: Patricia Reinoso <patricia.reinoso@canonical.com>
osm_lcm/temporal/juju_paas_activities.py
osm_lcm/tests/test_juju_paas_activities.py
osm_lcm/tests/test_vdu_workflow.py [new file with mode: 0644]

index 98a0ae5..d8cc55c 100644 (file)
@@ -169,6 +169,7 @@ class JujuPaasConnector:
 
         Raises  (Retryable):
             ApplicationError    If Juju controller is not reachable
+                                If application already exists
 
         Activity Lifecycle:
             This activity should complete relatively quickly (in a few seconds).
@@ -183,12 +184,15 @@ class JujuPaasConnector:
             and wait on each connection attempt.
         """
         model_name = deploy_charm_input.model_name
-        libjuju = self._get_libjuju(deploy_charm_input.vim_uuid)
         charm_info = deploy_charm_input.charm_info
-        await libjuju.deploy_charm(
-            application_name=charm_info.app_name,
-            path=charm_info.entity_url,
-            model_name=model_name,
+        application_name = charm_info.app_name
+        controller = await self._get_controller(deploy_charm_input.vim_uuid)
+        model = await controller.get_model(model_name)
+        if application_name in model.applications:
+            raise Exception("Application {} already exists".format(application_name))
+        await model.deploy(
+            entity_url=charm_info.entity_url,
+            application_name=application_name,
             channel=charm_info.channel,
         )
 
index b943c26..3e56bd5 100644 (file)
@@ -19,17 +19,22 @@ import asyncio
 import unittest.mock as mock
 
 from juju.application import Application
+from juju.model import Model
 from juju.errors import JujuError
 from juju.unit import Unit
 from n2vc.temporal_libjuju import ConnectionInfo
-from osm_common.dataclasses.temporal_dataclasses import CheckCharmStatusInput, ModelInfo
+from osm_common.dataclasses.temporal_dataclasses import (
+    CharmInfo,
+    CheckCharmStatusInput,
+    ModelInfo,
+    VduInstantiateInput,
+)
 from osm_common.dbbase import DbException
 from osm_lcm.temporal.juju_paas_activities import JujuPaasConnector
 from parameterized import parameterized
 from temporalio.testing import ActivityEnvironment
 from unittest.mock import AsyncMock, Mock
 
-
 vim_id = "some-vim-uuid"
 namespace = "some-namespace"
 model_info = ModelInfo(vim_id, namespace)
@@ -84,16 +89,13 @@ class TestJujuPaasConnector(asynctest.TestCase):
         mock_add_model.assert_not_called()
 
 
-class JujuPaasActivitiesTest(asynctest.TestCase):
+class TestJujuPaasActivitiesBase(asynctest.TestCase):
     def setUp(self) -> None:
         self.db = Mock()
         self.env = ActivityEnvironment()
-        self.env.on_heartbeat = self.on_heartbeat
-        self.heartbeat_count = 0
-        self.heartbeat_maximum = 5
-
         self.controller = AsyncMock()
-        self.model = Mock()
+        self.model = Mock(spec=Model)
+        self.model.applications = {}
         self.controller.get_model.return_value = self.model
 
         async def get_controller(_: str):
@@ -102,11 +104,21 @@ class JujuPaasActivitiesTest(asynctest.TestCase):
         self.juju_paas = JujuPaasConnector(self.db)
         self.juju_paas._get_controller = get_controller
 
-        self.application_name = "application"
+    def add_application(self, application_name) -> None:
+        self.application_name = application_name
         self.application = Mock(spec=Application)
         self.application.name = self.application_name
         self.model.applications = {self.application_name: self.application}
 
+
+class TestCheckCharmStatus(TestJujuPaasActivitiesBase):
+    def setUp(self) -> None:
+        super().setUp()
+        self.env.on_heartbeat = self.on_heartbeat
+        self.heartbeat_count = 0
+        self.heartbeat_maximum = 5
+        self.add_application("application")
+
     def on_heartbeat(self, *args, **kwargs):
         self.heartbeat_count += 1
         if self.heartbeat_count > self.heartbeat_maximum:
@@ -199,3 +211,35 @@ class JujuPaasActivitiesTest(asynctest.TestCase):
         )
 
         await self.env.run(self.juju_paas.check_charm_status, arg)
+
+
+class TestDeployCharm(TestJujuPaasActivitiesBase):
+    app_name = "my_app_name"
+    channel = "latest"
+    entity_url = "ch:my-charm"
+    charm_info = CharmInfo(app_name, channel, entity_url)
+    vdu_instantiate_input = VduInstantiateInput(vim_id, namespace, charm_info)
+
+    async def test_deploy_charm_nominal_case(self):
+        await self.env.run(self.juju_paas.deploy_charm, self.vdu_instantiate_input)
+        self.model.deploy.assert_called_once_with(
+            entity_url=self.entity_url,
+            application_name=self.app_name,
+            channel=self.channel,
+        )
+
+    async def test_deploy_charm_app_already_exists(self):
+        self.add_application(self.app_name)
+        with self.assertRaises(Exception) as err:
+            await self.env.run(self.juju_paas.deploy_charm, self.vdu_instantiate_input)
+        self.model.deploy.assert_not_called()
+        self.assertEqual(
+            str(err.exception.args[0]),
+            "Application {} already exists".format(self.app_name),
+        )
+
+    async def test_deploy_charm_raises_exception(self):
+        self.controller.get_model.side_effect = JujuError()
+        with self.assertRaises(JujuError):
+            await self.env.run(self.juju_paas.deploy_charm, self.vdu_instantiate_input)
+        self.model.deploy.assert_not_called()
diff --git a/osm_lcm/tests/test_vdu_workflow.py b/osm_lcm/tests/test_vdu_workflow.py
new file mode 100644 (file)
index 0000000..23de701
--- /dev/null
@@ -0,0 +1,111 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import asynctest
+
+from osm_common.dataclasses.temporal_dataclasses import CharmInfo, VduInstantiateInput
+from osm_common.temporal_constants import (
+    ACTIVITY_DEPLOY_CHARM,
+    ACTIVITY_CHECK_CHARM_STATUS,
+    LCM_TASK_QUEUE,
+)
+from osm_lcm.temporal.vdu_workflows import CheckCharmStatusInput, VduInstantiateWorkflow
+from temporalio import activity
+from temporalio.client import WorkflowFailureError
+from temporalio.testing import WorkflowEnvironment
+from temporalio.worker import Worker
+
+
+@activity.defn(name=ACTIVITY_DEPLOY_CHARM)
+async def deploy_charm_mocked(deploy_charm_input: VduInstantiateInput) -> None:
+    pass
+
+
+@activity.defn(name=ACTIVITY_DEPLOY_CHARM)
+async def deploy_charm_mocked_raises(deploy_charm_input: VduInstantiateInput) -> None:
+    raise Exception()
+
+
+@activity.defn(name=ACTIVITY_CHECK_CHARM_STATUS)
+async def check_charm_status_mocked(check_charm_status: CheckCharmStatusInput) -> None:
+    pass
+
+
+@activity.defn(name=ACTIVITY_CHECK_CHARM_STATUS)
+async def check_charm_status_mocked_raises(
+    check_charm_status: CheckCharmStatusInput,
+) -> None:
+    raise Exception()
+
+
+class TestVduWorkflows(asynctest.TestCase):
+    task_queue_name = LCM_TASK_QUEUE
+    vim_id = "some-vim-uuid"
+    namespace = "some-namespace"
+    app_name = "my_app_name"
+    channel = "latest"
+    entity_url = "ch:my-charm"
+    charm_info = CharmInfo(app_name, channel, entity_url)
+    vdu_instantiate_input = VduInstantiateInput(vim_id, namespace, charm_info)
+    worflow_id = namespace + "-" + app_name
+
+    async def setUp(self):
+        self.env = await WorkflowEnvironment.start_time_skipping()
+        self.client = self.env.client
+
+    def get_worker(self, activities: list) -> Worker:
+        return Worker(
+            self.client,
+            task_queue=self.task_queue_name,
+            workflows=[VduInstantiateWorkflow],
+            activities=activities,
+            debug_mode=True,
+        )
+
+    async def test_vdu_instantiate(self):
+        activities = [deploy_charm_mocked, check_charm_status_mocked]
+        async with self.env, self.get_worker(activities):
+            result = await self.client.execute_workflow(
+                VduInstantiateWorkflow.run,
+                arg=self.vdu_instantiate_input,
+                id=self.worflow_id,
+                task_queue=self.task_queue_name,
+            )
+            self.assertIsNone(result)
+
+    async def test_vdu_instantiate_deploy_raises(self):
+        activities = [deploy_charm_mocked_raises, check_charm_status_mocked]
+        async with self.env, self.get_worker(activities):
+            with self.assertRaises(WorkflowFailureError):
+                result = await self.client.execute_workflow(
+                    VduInstantiateWorkflow.run,
+                    arg=self.vdu_instantiate_input,
+                    id=self.worflow_id,
+                    task_queue=self.task_queue_name,
+                )
+                self.assertIsNone(result)
+
+    async def test_vdu_instantiate_check_status_raises(self):
+        activities = [deploy_charm_mocked, check_charm_status_mocked_raises]
+        async with self.env, self.get_worker(activities):
+            with self.assertRaises(WorkflowFailureError):
+                result = await self.client.execute_workflow(
+                    VduInstantiateWorkflow.run,
+                    arg=self.vdu_instantiate_input,
+                    id=self.worflow_id,
+                    task_queue=self.task_queue_name,
+                )
+                self.assertIsNone(result)