From 911946db8fab0a46c2449dcdc21d48e7f0aa83ef Mon Sep 17 00:00:00 2001 From: Patricia Reinoso Date: Wed, 12 Apr 2023 15:54:43 +0000 Subject: [PATCH] OSMENG-992 - Implement create model activity in NS Workflow. An exception is raised if model already exists Change-Id: I84dd89850b28287dfefb1abc0d158cc72cd4eb34 Signed-off-by: Patricia Reinoso Signed-off-by: Mark Beierl --- osm_lcm/nglcm.py | 3 +- osm_lcm/temporal/juju_paas_activities.py | 18 ++--- osm_lcm/temporal/ns_activities.py | 34 +++++++- osm_lcm/temporal/ns_workflows.py | 29 ++++++- .../tests/test_charm_info_utils.py | 0 osm_lcm/tests/test_juju_paas_activities.py | 77 +++++++++++++++++++ osm_lcm/tests/test_ns_activities.py | 59 ++++++++++++++ 7 files changed, 203 insertions(+), 17 deletions(-) rename osm_lcm/{temporal => }/tests/test_charm_info_utils.py (100%) create mode 100644 osm_lcm/tests/test_juju_paas_activities.py create mode 100644 osm_lcm/tests/test_ns_activities.py diff --git a/osm_lcm/nglcm.py b/osm_lcm/nglcm.py index 4c21d6e..6627bc8 100644 --- a/osm_lcm/nglcm.py +++ b/osm_lcm/nglcm.py @@ -152,13 +152,14 @@ class NGLcm: VnfInstantiateWorkflow, ] activities = [ + ns_data_activity_instance.get_model_info, ns_data_activity_instance.prepare_vnf_records, ns_data_activity_instance.update_ns_state, ns_operation_instance.check_ns_instantiate_finished, ns_operation_instance.deploy_ns, nslcm_activity_instance.update_ns_lcm_operation_state, nslcm_activity_instance.no_op, - paas_connector_instance.create_model_if_doesnt_exist, + paas_connector_instance.create_model, paas_connector_instance.deploy_charm, paas_connector_instance.check_charm_status, paas_connector_instance.test_vim_connectivity, diff --git a/osm_lcm/temporal/juju_paas_activities.py b/osm_lcm/temporal/juju_paas_activities.py index ecd59e6..3c61ec7 100644 --- a/osm_lcm/temporal/juju_paas_activities.py +++ b/osm_lcm/temporal/juju_paas_activities.py @@ -18,15 +18,15 @@ from temporalio import activity from n2vc.temporal_libjuju import ConnectionInfo, Libjuju from osm_common.temporal_constants import ( ACTIVITY_TEST_VIM_CONNECTIVITY, - ACTIVITY_CREATE_MODEL_IF_DOESNT_EXIST, + ACTIVITY_CREATE_MODEL, ACTIVITY_DEPLOY_CHARM, ACTIVITY_CHECK_CHARM_STATUS, ) from osm_common.dataclasses.temporal_dataclasses import ( + CharmInfo, + ModelInfo, TestVimConnectivityInput, - CreateModelInput, VduInstantiateInput, - CharmInfo, ) @@ -116,19 +116,17 @@ class JujuPaasConnector: finally: await libjuju.disconnect_controller(controller) - @activity.defn(name=ACTIVITY_CREATE_MODEL_IF_DOESNT_EXIST) - async def create_model_if_doesnt_exist( - self, create_model_input: CreateModelInput - ) -> None: - # TODO: OSM-991 - """Connects to Juju Controller. Create a new model if model_name does not exist + @activity.defn(name=ACTIVITY_CREATE_MODEL) + async def create_model(self, create_model_input: ModelInfo) -> None: + """Connects to Juju Controller. Creates a new model. Collaborators: DB Read: vim_accounts Juju Controller: Connect and create model. Raises (Retryable): - ApplicationError If Juju controller is not reachable + ApplicationError If Juju controller is not reachable. + If the model already exists. Activity Lifecycle: This activity should complete relatively quickly (in a few seconds). diff --git a/osm_lcm/temporal/ns_activities.py b/osm_lcm/temporal/ns_activities.py index 1a2925c..6834eec 100644 --- a/osm_lcm/temporal/ns_activities.py +++ b/osm_lcm/temporal/ns_activities.py @@ -18,12 +18,14 @@ import logging from time import time from temporalio import activity from osm_common.temporal_constants import ( - ACTIVITY_UPDATE_NS_STATE, ACTIVITY_CHECK_NS_INSTANTIATION_FINISHED, - ACTIVITY_PREPARE_VNF_RECORDS, ACTIVITY_DEPLOY_NS, + ACTIVITY_GET_MODEL_INFO, + ACTIVITY_PREPARE_VNF_RECORDS, + ACTIVITY_UPDATE_NS_STATE, ) from osm_common.dataclasses.temporal_dataclasses import ( + ModelInfo, NsInstantiateInput, UpdateNsStateInput, VduInstantiateInput, @@ -45,7 +47,7 @@ class NsOperations: async def deploy_vnf(self, vnfr: dict): vim_id = vnfr.get("vim-account-id") - model_name = vnfr.get("namespace") + model_name = "model-name" vnfd_id = vnfr["vnfd-id"] vnfd = self.db.get_one("vnfds", {"_id": vnfd_id}) sw_image_descs = vnfd.get("sw-image-desc") @@ -106,6 +108,32 @@ class NsDbActivity: for vnfr in vnfrs: self._prepare_vnf_record(vnfr) + @activity.defn(name=ACTIVITY_GET_MODEL_INFO) + async def get_model_info( + self, ns_instantiate_input: NsInstantiateInput + ) -> ModelInfo: + """Returns a ModelInfo. Contains VIM ID and model name. + + Collaborators: + DB Read: nsrs + + Raises (Retryable): + DbException If the target DB record does not exist or DB is not reachable. + + Activity Lifecycle: + This activity will not report a heartbeat due to its + short-running nature. + + As this is a direct DB update, it is not recommended to have + any specific retry policy + + """ + ns_uuid = ns_instantiate_input.ns_uuid + nsr = self.db.get_one("nsrs", {"_id": ns_uuid}) + vim_uuid = nsr.get("datacenter") + model_name = self._get_namespace(ns_uuid, vim_uuid) + return ModelInfo(vim_uuid, model_name) + def _get_namespace(self, ns_id: str, vim_id: str) -> str: """The NS namespace is the combination if the NS ID and the VIM ID.""" return ns_id[-12:] + "-" + vim_id[-12:] diff --git a/osm_lcm/temporal/ns_workflows.py b/osm_lcm/temporal/ns_workflows.py index bf0be65..bef6766 100644 --- a/osm_lcm/temporal/ns_workflows.py +++ b/osm_lcm/temporal/ns_workflows.py @@ -15,20 +15,24 @@ # limitations under the License. from temporalio import workflow +from temporalio.converter import value_to_type from temporalio.exceptions import ActivityError from osm_common.dataclasses.temporal_dataclasses import ( + ModelInfo, NsInstantiateInput, NsLcmOperationInput, NsState, UpdateNsStateInput, ) from osm_common.temporal_constants import ( - WORKFLOW_NS_INSTANTIATE, - ACTIVITY_UPDATE_NS_STATE, + ACTIVITY_CREATE_MODEL, ACTIVITY_CHECK_NS_INSTANTIATION_FINISHED, ACTIVITY_DEPLOY_NS, + ACTIVITY_GET_MODEL_INFO, + ACTIVITY_UPDATE_NS_STATE, LCM_TASK_QUEUE, + WORKFLOW_NS_INSTANTIATE, ) from osm_lcm.temporal.lcm_workflows import LcmOperationWorkflow @@ -51,7 +55,26 @@ class NsInstantiateWorkflow(LcmOperationWorkflow): ns_state = UpdateNsStateInput(input.ns_uuid, NsState.INSTANTIATED, "Done") try: - # TODO: Create the model here OSM-991 + model_info = value_to_type( + ModelInfo, + await workflow.execute_activity( + activity=ACTIVITY_GET_MODEL_INFO, + arg=input, + activity_id=f"{ACTIVITY_GET_MODEL_INFO}-{input.ns_uuid}", + task_queue=LCM_TASK_QUEUE, + schedule_to_close_timeout=LcmOperationWorkflow.default_schedule_to_close_timeout, + retry_policy=LcmOperationWorkflow.no_retry_policy, + ), + ) + + await workflow.execute_activity( + activity=ACTIVITY_CREATE_MODEL, + arg=model_info, + activity_id=f"{ACTIVITY_CREATE_MODEL}-{input.ns_uuid}", + task_queue=LCM_TASK_QUEUE, + schedule_to_close_timeout=LcmOperationWorkflow.default_schedule_to_close_timeout, + retry_policy=LcmOperationWorkflow.no_retry_policy, + ) # TODO: Change this to a workflow OSM-990 await workflow.execute_activity( diff --git a/osm_lcm/temporal/tests/test_charm_info_utils.py b/osm_lcm/tests/test_charm_info_utils.py similarity index 100% rename from osm_lcm/temporal/tests/test_charm_info_utils.py rename to osm_lcm/tests/test_charm_info_utils.py diff --git a/osm_lcm/tests/test_juju_paas_activities.py b/osm_lcm/tests/test_juju_paas_activities.py new file mode 100644 index 0000000..7d94a2c --- /dev/null +++ b/osm_lcm/tests/test_juju_paas_activities.py @@ -0,0 +1,77 @@ +####################################################################################### +# 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 juju.errors import JujuError +from osm_common.dataclasses.temporal_dataclasses import ModelInfo +from osm_common.dbbase import DbException +from osm_lcm.temporal.juju_paas_activities import JujuPaasConnector +from n2vc.temporal_libjuju import ConnectionInfo +from temporalio.testing import ActivityEnvironment +from unittest.mock import Mock + +vim_id = "some-vim-uuid" +namespace = "some-namespace" +model_info = ModelInfo(vim_id, namespace) +connection_info = ConnectionInfo( + "1.2.3.4:17070", "user", "password", "cacert", "cloud_name", "cloud_credentials" +) + + +class TestJujuPaasConnector(asynctest.TestCase): + def setUp(self): + self.db = Mock() + self.env = ActivityEnvironment() + self.juju_paas_connector = JujuPaasConnector(self.db) + + @asynctest.mock.patch("osm_lcm.temporal.juju_paas_activities.Libjuju.add_model") + @asynctest.mock.patch( + "osm_lcm.temporal.juju_paas_activities.JujuPaasConnector._get_connection_info" + ) + async def test_create_model_nominal_case( + self, mock_get_connection_info, mock_add_model + ): + mock_get_connection_info.return_value = connection_info + await self.env.run(self.juju_paas_connector.create_model, model_info) + mock_get_connection_info.assert_called_once_with(vim_id) + mock_add_model.assert_called_once_with(namespace) + + @asynctest.mock.patch("osm_lcm.temporal.juju_paas_activities.Libjuju.add_model") + @asynctest.mock.patch( + "osm_lcm.temporal.juju_paas_activities.JujuPaasConnector._get_connection_info" + ) + async def test_create_model_raises_juju_exception( + self, mock_get_connection_info, mock_add_model + ): + mock_get_connection_info.return_value = connection_info + mock_add_model.side_effect = JujuError() + with self.assertRaises(JujuError): + await self.env.run(self.juju_paas_connector.create_model, model_info) + mock_get_connection_info.assert_called_once_with(vim_id) + mock_add_model.assert_called_once_with(namespace) + + @asynctest.mock.patch("osm_lcm.temporal.juju_paas_activities.Libjuju.add_model") + @asynctest.mock.patch( + "osm_lcm.temporal.juju_paas_activities.JujuPaasConnector._get_connection_info" + ) + async def test_create_model_raises_db_exception( + self, mock_get_connection_info, mock_add_model + ): + mock_get_connection_info.side_effect = DbException("not found") + with self.assertRaises(DbException): + await self.env.run(self.juju_paas_connector.create_model, model_info) + mock_get_connection_info.assert_called_once_with(vim_id) + mock_add_model.assert_not_called() diff --git a/osm_lcm/tests/test_ns_activities.py b/osm_lcm/tests/test_ns_activities.py new file mode 100644 index 0000000..b12693b --- /dev/null +++ b/osm_lcm/tests/test_ns_activities.py @@ -0,0 +1,59 @@ +####################################################################################### +# 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 ModelInfo, NsInstantiateInput +from osm_common.dbbase import DbException +from osm_lcm.temporal.ns_activities import NsDbActivity +from temporalio.testing import ActivityEnvironment +from unittest.mock import Mock + + +vim_id = "some-vim-uuid" +ns_id = "0123456789-9876543210" +nsr = { + "_id": ns_id, + "datacenter": vim_id, +} +input = NsInstantiateInput(ns_id, "op_id") +expected_namespace = "9-9876543210-ome-vim-uuid" +expected_model_info = ModelInfo(vim_id, expected_namespace) + + +class TestGetModelInfo(asynctest.TestCase): + def setUp(self): + self.db = Mock() + self.env = ActivityEnvironment() + self.ns_db_activity = NsDbActivity(self.db) + + async def test_nominal_case(self): + self.db.get_one.return_value = nsr + model_info = await self.env.run(self.ns_db_activity.get_model_info, input) + self.assertEqual(model_info, expected_model_info) + + async def test_db_raises_exception(self): + self.db.get_one.side_effect = DbException("not found") + with self.assertRaises(DbException): + model_info = await self.env.run(self.ns_db_activity.get_model_info, input) + self.assertIsNone(model_info) + + async def test_no_datacenter_raises_exception(self): + nsr = {"_id": ns_id} + self.db.get_one.return_value = nsr + with self.assertRaises(TypeError): + model_info = await self.env.run(self.ns_db_activity.get_model_info, input) + self.assertIsNone(model_info) -- 2.25.1