From f1833afa84404d58b38dfee80685c5ec7118d88e Mon Sep 17 00:00:00 2001 From: Patricia Reinoso Date: Mon, 17 Apr 2023 12:34:16 +0000 Subject: [PATCH] OSMENG-1007 Implement the Instantiate VDU workflow Change-Id: If77c2711ab5d5ce700d281bb46cd01ed9e88ad90 Signed-off-by: Patricia Reinoso --- osm_lcm/temporal/juju_paas_activities.py | 14 ++- osm_lcm/tests/test_juju_paas_activities.py | 62 ++++++++++-- osm_lcm/tests/test_vdu_workflow.py | 111 +++++++++++++++++++++ 3 files changed, 173 insertions(+), 14 deletions(-) create mode 100644 osm_lcm/tests/test_vdu_workflow.py diff --git a/osm_lcm/temporal/juju_paas_activities.py b/osm_lcm/temporal/juju_paas_activities.py index 98a0ae5..d8cc55c 100644 --- a/osm_lcm/temporal/juju_paas_activities.py +++ b/osm_lcm/temporal/juju_paas_activities.py @@ -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, ) diff --git a/osm_lcm/tests/test_juju_paas_activities.py b/osm_lcm/tests/test_juju_paas_activities.py index b943c26..3e56bd5 100644 --- a/osm_lcm/tests/test_juju_paas_activities.py +++ b/osm_lcm/tests/test_juju_paas_activities.py @@ -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 index 0000000..23de701 --- /dev/null +++ b/osm_lcm/tests/test_vdu_workflow.py @@ -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) -- 2.25.1