From: Gulsum Atici Date: Thu, 27 Apr 2023 13:41:43 +0000 (+0300) Subject: OSMENG-1048 Implement day1 configuration for VDU X-Git-Url: https://osm.etsi.org/gitweb/?a=commitdiff_plain;h=50d12e0005f5a385e9b9545c4834008032ca4a4a;p=osm%2FLCM.git OSMENG-1048 Implement day1 configuration for VDU Day1 config implementation, arranging existing unit tests and imports adding task and execution timeout policy for workflows in tests. Change-Id: Ie5a2626eec01176723d8130576facbf4934d5285 Signed-off-by: Gulsum Atici --- diff --git a/osm_lcm/nglcm.py b/osm_lcm/nglcm.py index 729b775..bad37f7 100644 --- a/osm_lcm/nglcm.py +++ b/osm_lcm/nglcm.py @@ -27,9 +27,6 @@ from os import path import yaml from osm_common.dbbase import DbException from osm_common.temporal_constants import LCM_TASK_QUEUE -from temporalio.client import Client -from temporalio.worker import Worker - from osm_lcm.data_utils.database.database import Database from osm_lcm.data_utils.lcm_config import LcmCfg from osm_lcm.lcm_utils import LcmException @@ -51,6 +48,8 @@ from osm_lcm.temporal.vnf_activities import ( VnfSendNotifications, ) from osm_lcm.temporal.vnf_workflows import VnfInstantiateWorkflow, VnfPrepareWorkflow +from temporalio.client import Client +from temporalio.worker import Worker class NGLcm: @@ -161,7 +160,8 @@ class NGLcm: ] activities = [ ns_data_activity_instance.update_ns_state, - ns_operation_instance.get_vnf_record_ids, + ns_operation_instance.get_vnf_details, + ns_operation_instance.get_ns_record, nslcm_activity_instance.update_ns_lcm_operation_state, nslcm_activity_instance.no_op, paas_connector_instance.create_model, @@ -175,7 +175,8 @@ class NGLcm: vnf_data_activity_instance.change_vnf_instantiation_state, vnf_operation_instance.get_task_queue, vnf_operation_instance.get_vim_cloud, - vnf_operation_instance.get_vnf_details, + vnf_operation_instance.get_vnf_descriptor, + vnf_operation_instance.get_vnf_record, vnf_send_notifications_instance.send_notification_for_vnf, vnf_data_activity_instance.set_vnf_model, ] diff --git a/osm_lcm/temporal/juju_paas_activities.py b/osm_lcm/temporal/juju_paas_activities.py index 3182317..5082254 100644 --- a/osm_lcm/temporal/juju_paas_activities.py +++ b/osm_lcm/temporal/juju_paas_activities.py @@ -17,6 +17,7 @@ import asyncio import base64 import logging from dataclasses import dataclass + from juju.application import Application from juju.controller import Controller from n2vc.config import EnvironConfig @@ -245,6 +246,7 @@ class JujuPaasConnector: application_name=application_name, channel=charm_info.channel, constraints=constraints if constraints else None, + config=deploy_charm_input.config, ) @activity.defn(name=ACTIVITY_CHECK_CHARM_STATUS) diff --git a/osm_lcm/temporal/lcm_activities.py b/osm_lcm/temporal/lcm_activities.py index 5f360f1..6ddb0f3 100644 --- a/osm_lcm/temporal/lcm_activities.py +++ b/osm_lcm/temporal/lcm_activities.py @@ -14,8 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. import logging -from temporalio import activity import time + from osm_common.dataclasses.temporal_dataclasses import ( NsLcmOperationInput, UpdateLcmOperationStateInput, @@ -25,6 +25,7 @@ from osm_common.temporal_constants import ( ACTIVITY_NSLCM_NO_OP, ) from osm_lcm.data_utils.database.database import Database +from temporalio import activity class NsLcmActivity: diff --git a/osm_lcm/temporal/lcm_workflows.py b/osm_lcm/temporal/lcm_workflows.py index 2dc7096..6c8c28f 100644 --- a/osm_lcm/temporal/lcm_workflows.py +++ b/osm_lcm/temporal/lcm_workflows.py @@ -17,9 +17,6 @@ import logging from abc import ABC, abstractmethod from datetime import timedelta -from temporalio import workflow -from temporalio.common import RetryPolicy -from temporalio.exceptions import ActivityError, ChildWorkflowError from osm_common.dataclasses.temporal_dataclasses import ( LcmOperationState, @@ -31,6 +28,9 @@ from osm_common.temporal_constants import ( ACTIVITY_UPDATE_LCM_OPERATION_STATE, WORKFLOW_NSLCM_NO_OP, ) +from temporalio import workflow +from temporalio.common import RetryPolicy +from temporalio.exceptions import ActivityError, ChildWorkflowError class LcmOperationWorkflow(ABC): @@ -84,11 +84,11 @@ class LcmOperationWorkflow(ABC): raise e except ChildWorkflowError as e: - err_details = str(e.cause.cause.cause.with_traceback(e.cause.__traceback__)) + err_details = str(e.cause.with_traceback(e.cause.__traceback__)) self.logger.error(err_details) await self.update_operation_state( LcmOperationState.FAILED, - error_message=str(e.cause.cause.message), + error_message=str(e.cause.message), detailed_status=err_details, ) raise e @@ -98,7 +98,7 @@ class LcmOperationWorkflow(ABC): await self.update_operation_state( LcmOperationState.FAILED, error_message=str(e), - detailed_status=err_details, + detailed_status=str(e), ) raise e diff --git a/osm_lcm/temporal/ns_activities.py b/osm_lcm/temporal/ns_activities.py index b937717..8c7c182 100644 --- a/osm_lcm/temporal/ns_activities.py +++ b/osm_lcm/temporal/ns_activities.py @@ -18,17 +18,19 @@ import logging from time import time from osm_common.dataclasses.temporal_dataclasses import ( - GetVnfRecordIdsInput, - GetVnfRecordIdsOutput, + GetNsRecordInput, + GetNsRecordOutput, + GetVnfDetailsInput, + GetVnfDetailsOutput, UpdateNsStateInput, ) from osm_common.temporal_constants import ( - ACTIVITY_GET_VNF_RECORD_IDS, + ACTIVITY_GET_NS_RECORD, + ACTIVITY_GET_VNF_DETAILS, ACTIVITY_UPDATE_NS_STATE, ) -from temporalio import activity - from osm_lcm.data_utils.database.database import Database +from temporalio import activity class NsOperations: @@ -36,12 +38,12 @@ class NsOperations: self.db: Database = db self.logger = logging.getLogger(f"lcm.act.{self.__class__.__name__}") - @activity.defn(name=ACTIVITY_GET_VNF_RECORD_IDS) - async def get_vnf_record_ids( - self, get_vnf_record_ids_input: GetVnfRecordIdsInput - ) -> GetVnfRecordIdsOutput: + @activity.defn(name=ACTIVITY_GET_VNF_DETAILS) + async def get_vnf_details( + self, get_vnf_details_input: GetVnfDetailsInput + ) -> GetVnfDetailsOutput: """ - Gets the list of VNF record IDs for a given NS record ID. + Gets the list of VNF record IDs, VNF member-index-refs for a given NS record ID. Collaborators: DB Read: vnfrs @@ -55,10 +57,36 @@ class NsOperations: Since this activity only reads from the DB, it is safe to retry, although you may wish to have some back-off policy. """ - vnfrs = self.db.get_list( - "vnfrs", {"nsr-id-ref": get_vnf_record_ids_input.ns_uuid} + vnfrs = self.db.get_list("vnfrs", {"nsr-id-ref": get_vnf_details_input.ns_uuid}) + return GetVnfDetailsOutput( + vnf_details=[(vnfr["id"], vnfr["member-vnf-index-ref"]) for vnfr in vnfrs] ) - return GetVnfRecordIdsOutput(vnfr_ids=[vnfr["id"] for vnfr in vnfrs]) + + @activity.defn(name=ACTIVITY_GET_NS_RECORD) + async def get_ns_record( + self, get_ns_record_input: GetNsRecordInput + ) -> GetNsRecordOutput: + """Gets the NS record from Database. + + Collaborators: + DB Read: nsrs + + Raises (retryable): + DbException: If DB read operations fail, the collection or DB record ID does not exist. + + Activity Lifecycle: + This activity should complete relatively quickly (less than 10 + second). + + This activity will not report a heartbeat due to its + short-running nature. + + This is an idempotent activity. + + """ + nsr = self.db.get_one("nsrs", {"_id": get_ns_record_input.nsr_uuid}) + self.logger.debug("Got the nsr from Database for VNF operations.") + return GetNsRecordOutput(nsr=nsr) class NsDbActivity: diff --git a/osm_lcm/temporal/ns_workflows.py b/osm_lcm/temporal/ns_workflows.py index 343a9c9..6281ee6 100644 --- a/osm_lcm/temporal/ns_workflows.py +++ b/osm_lcm/temporal/ns_workflows.py @@ -15,14 +15,13 @@ # limitations under the License. import asyncio -from temporalio import workflow -from temporalio.converter import value_to_type -from temporalio.exceptions import ActivityError, ChildWorkflowError import traceback from osm_common.dataclasses.temporal_dataclasses import ( - GetVnfRecordIdsInput, - GetVnfRecordIdsOutput, + GetNsRecordInput, + GetNsRecordOutput, + GetVnfDetailsInput, + GetVnfDetailsOutput, ModelInfo, NsLcmOperationInput, NsState, @@ -31,12 +30,16 @@ from osm_common.dataclasses.temporal_dataclasses import ( ) from osm_common.temporal_constants import ( ACTIVITY_CREATE_MODEL, - ACTIVITY_GET_VNF_RECORD_IDS, + ACTIVITY_GET_NS_RECORD, + ACTIVITY_GET_VNF_DETAILS, ACTIVITY_UPDATE_NS_STATE, WORKFLOW_NS_INSTANTIATE, WORKFLOW_VNF_INSTANTIATE, ) from osm_lcm.temporal.lcm_workflows import LcmOperationWorkflow +from temporalio import workflow +from temporalio.converter import value_to_type +from temporalio.exceptions import ActivityError, ChildWorkflowError @workflow.defn(name=WORKFLOW_NS_INSTANTIATE, sandboxed=LcmOperationWorkflow._SANDBOXED) @@ -50,8 +53,7 @@ class NsInstantiateWorkflow(LcmOperationWorkflow): async def workflow(self, input: NsLcmOperationInput) -> None: self.logger.info(f"Executing {WORKFLOW_NS_INSTANTIATE} with {input}") - # TODO: Can we clean up the input? Perhaps this workflow could receive NsInstantiateInput - # directly. + # TODO: Can we clean up the input? Perhaps this workflow could receive NsInstantiateInput directly. ns_uuid = input.nslcmop["nsInstanceId"] vim_uuid = input.nslcmop["operationParams"]["vimAccountId"] model_name = self._get_namespace(ns_uuid, vim_uuid) @@ -63,27 +65,40 @@ class NsInstantiateWorkflow(LcmOperationWorkflow): schedule_to_close_timeout=LcmOperationWorkflow.default_schedule_to_close_timeout, retry_policy=LcmOperationWorkflow.no_retry_policy, ) - - vnf_record_ids_output: GetVnfRecordIdsOutput = value_to_type( - GetVnfRecordIdsOutput, - await workflow.execute_activity( - activity=ACTIVITY_GET_VNF_RECORD_IDS, - arg=GetVnfRecordIdsInput(ns_uuid=ns_uuid), - activity_id=f"{ACTIVITY_GET_VNF_RECORD_IDS}-{ns_uuid}", + activities_results = await asyncio.gather( + workflow.execute_activity( + activity=ACTIVITY_GET_VNF_DETAILS, + arg=GetVnfDetailsInput(ns_uuid=ns_uuid), + activity_id=f"{ACTIVITY_GET_VNF_DETAILS}-{ns_uuid}", + schedule_to_close_timeout=LcmOperationWorkflow.default_schedule_to_close_timeout, + retry_policy=LcmOperationWorkflow.no_retry_policy, + ), + workflow.execute_activity( + activity=ACTIVITY_GET_NS_RECORD, + arg=GetNsRecordInput(nsr_uuid=ns_uuid), + activity_id=f"{ACTIVITY_GET_NS_RECORD}-{ns_uuid}", schedule_to_close_timeout=LcmOperationWorkflow.default_schedule_to_close_timeout, retry_policy=LcmOperationWorkflow.no_retry_policy, ), ) + get_vnf_details, get_ns_record = value_to_type( + GetVnfDetailsOutput, activities_results[0] + ), value_to_type(GetNsRecordOutput, activities_results[1]) + await asyncio.gather( *( workflow.execute_child_workflow( workflow=WORKFLOW_VNF_INSTANTIATE, arg=VnfInstantiateInput( - vnfr_uuid=vnfr_uuid, model_name=model_name + vnfr_uuid=vnfr_uuid, + model_name=model_name, + instantiation_config=NsInstantiateWorkflow.get_vnf_config( + vnf_member_index_ref, get_ns_record.nsr + ), ), id=f"{WORKFLOW_VNF_INSTANTIATE}-{vnfr_uuid}", ) - for vnfr_uuid in vnf_record_ids_output.vnfr_ids + for vnfr_uuid, vnf_member_index_ref in get_vnf_details.vnf_details ) ) @@ -94,7 +109,7 @@ class NsInstantiateWorkflow(LcmOperationWorkflow): raise e except ChildWorkflowError as e: - err_details = str(e.cause.cause.cause.with_traceback(e.cause.__traceback__)) + err_details = str(e.cause.with_traceback(e.cause.__traceback__)) await self.update_ns_state(ns_uuid, NsState.INSTANTIATED, err_details) self.logger.error(f"{WORKFLOW_NS_INSTANTIATE} failed with {err_details}") raise e @@ -125,3 +140,19 @@ class NsInstantiateWorkflow(LcmOperationWorkflow): 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:] + + @staticmethod + def get_vnf_config(vnf_member_index_ref: str, nsr: dict) -> dict: + """Get the VNF instantiation config + Args: + vnf_member_index_ref (str): VNF member-index-ref + nsr (dict): NS record + + Returns: + vnf_config (dict) VNF instantiation config + + """ + for vnf_config in nsr.get("instantiate_params", {}).get("vnf", {}): + if vnf_config.get("member-vnf-index") == vnf_member_index_ref: + return vnf_config + return {} diff --git a/osm_lcm/temporal/vdu_workflows.py b/osm_lcm/temporal/vdu_workflows.py index 0c91e99..7349535 100644 --- a/osm_lcm/temporal/vdu_workflows.py +++ b/osm_lcm/temporal/vdu_workflows.py @@ -16,9 +16,6 @@ from datetime import timedelta import logging -from temporalio import workflow -from temporalio.common import RetryPolicy -from temporalio.exceptions import ActivityError import traceback from osm_common.dataclasses.temporal_dataclasses import ( @@ -32,6 +29,9 @@ from osm_common.temporal_constants import ( LCM_TASK_QUEUE, WORKFLOW_VDU_INSTANTIATE, ) +from temporalio import workflow +from temporalio.common import RetryPolicy +from temporalio.exceptions import ActivityError _SANDBOXED = False retry_policy = RetryPolicy(maximum_attempts=3) diff --git a/osm_lcm/temporal/vim_activities.py b/osm_lcm/temporal/vim_activities.py index 4bd5514..e9db668 100644 --- a/osm_lcm/temporal/vim_activities.py +++ b/osm_lcm/temporal/vim_activities.py @@ -15,8 +15,8 @@ # limitations under the License. import logging -from temporalio import activity from time import time + from osm_common.temporal_constants import ( ACTIVITY_DELETE_VIM, ACTIVITY_UPDATE_VIM_OPERATION_STATE, @@ -28,6 +28,7 @@ from osm_common.dataclasses.temporal_dataclasses import ( UpdateVimStateInput, ) from osm_lcm.data_utils.database.database import Database +from temporalio import activity class VimDbActivity: diff --git a/osm_lcm/temporal/vim_workflows.py b/osm_lcm/temporal/vim_workflows.py index cb7d636..5d6b08c 100644 --- a/osm_lcm/temporal/vim_workflows.py +++ b/osm_lcm/temporal/vim_workflows.py @@ -16,9 +16,6 @@ from datetime import timedelta import logging -from temporalio import workflow -from temporalio.common import RetryPolicy -from temporalio.exceptions import ActivityError import traceback from osm_common.dataclasses.temporal_dataclasses import ( @@ -40,6 +37,9 @@ from osm_common.temporal_constants import ( WORKFLOW_VIM_UPDATE, WORKFLOW_VIM_DELETE, ) +from temporalio import workflow +from temporalio.common import RetryPolicy +from temporalio.exceptions import ActivityError _SANDBOXED = False retry_policy = RetryPolicy(maximum_attempts=3) diff --git a/osm_lcm/temporal/vnf_activities.py b/osm_lcm/temporal/vnf_activities.py index 2e8ab1c..9581db0 100644 --- a/osm_lcm/temporal/vnf_activities.py +++ b/osm_lcm/temporal/vnf_activities.py @@ -14,12 +14,15 @@ # limitations under the License. import logging +from typing import List, Any + from osm_common.temporal_constants import ( ACTIVITY_CHANGE_VNF_STATE, ACTIVITY_CHANGE_VNF_INSTANTIATION_STATE, ACTIVITY_GET_TASK_QUEUE, ACTIVITY_GET_VIM_CLOUD, - ACTIVITY_GET_VNF_DETAILS, + ACTIVITY_GET_VNF_DESCRIPTOR, + ACTIVITY_GET_VNF_RECORD, ACTIVITY_SEND_NOTIFICATION_FOR_VNF, ACTIVITY_SET_VNF_MODEL, VIM_TYPE_TASK_QUEUE_MAPPINGS, @@ -31,14 +34,20 @@ from osm_common.dataclasses.temporal_dataclasses import ( GetTaskQueueOutput, GetVimCloudInput, GetVimCloudOutput, - GetVnfDetailsInput, - GetVnfDetailsOutput, - VnfInstantiateInput, + GetVnfDescriptorInput, + GetVnfDescriptorOutput, + GetVnfRecordInput, + GetVnfRecordOutput, + SetVnfModelInput, + VduComputeConstraints, ) from osm_lcm.data_utils.database.database import Database from temporalio import activity +CONFIG_IDENTIFIER = "config::" + + class VnfOperations: def __init__(self, db: Database): self.db: Database = db @@ -81,7 +90,7 @@ class VnfOperations: """Finds the cloud by checking the VIM account of VNF. Collaborators: - DB Access Object + DB Read: vnfrs, vim_accounts Raises (retryable): DbException: If DB read operations fail, the collection or DB record ID does not exist. @@ -104,14 +113,40 @@ class VnfOperations: self.logger.debug(f"Got the cloud type {cloud} for VNF operations.") return GetVimCloudOutput(cloud=cloud) - @activity.defn(name=ACTIVITY_GET_VNF_DETAILS) - async def get_vnf_details( - self, get_vnf_details_input: GetVnfDetailsInput - ) -> GetVnfDetailsOutput: + @activity.defn(name=ACTIVITY_GET_VNF_RECORD) + async def get_vnf_record( + self, get_vnf_record_input: GetVnfRecordInput + ) -> GetVnfRecordOutput: + """Gets the VNF record and VNF descriptor from Database. + + Collaborators: + DB read: vnfrs + + Raises (retryable): + DbException: If DB read operations fail, the collection or DB record ID does not exist. + + Activity Lifecycle: + This activity should complete relatively quickly (less than 10 + second). + + This activity will not report a heartbeat due to its + short-running nature. + + This is an idempotent activity. + + """ + vnfr = self.db.get_one("vnfrs", {"_id": get_vnf_record_input.vnfr_uuid}) + self.logger.debug("Got the vnfr from Database for VNF operations.") + return GetVnfRecordOutput(vnfr=vnfr) + + @activity.defn(name=ACTIVITY_GET_VNF_DESCRIPTOR) + async def get_vnf_descriptor( + self, get_vnf_descriptor_input: GetVnfDescriptorInput + ) -> GetVnfDescriptorOutput: """Gets the VNF record and VNF descriptor from Database. Collaborators: - DB read: vnfrs, vnfds + DB read: vnfds Raises (retryable): DbException: If DB read operations fail, the collection or DB record ID does not exist. @@ -126,10 +161,67 @@ class VnfOperations: This is an idempotent activity. """ - vnfr = self.db.get_one("vnfrs", {"_id": get_vnf_details_input.vnfr_uuid}) - vnfd = self.db.get_one("vnfds", {"_id": vnfr["vnfd-id"]}) + vnfd = self.db.get_one("vnfds", {"_id": get_vnf_descriptor_input.vnfd_uuid}) self.logger.debug("Got the vnfr and vnfd from Database for VNF operations.") - return GetVnfDetailsOutput(vnfr=vnfr, vnfd=vnfd) + return GetVnfDescriptorOutput(vnfd=vnfd) + + @staticmethod + def get_vdu_instantiation_params( + vdu_id: str, vnf_instantiation_config: dict + ) -> dict: + for vdu in vnf_instantiation_config.get("vdu", []): + if vdu.get("id") == vdu_id: + return vdu.get("configurable-properties", {}) + return {} + + @staticmethod + def get_compute_constraints(vdu: dict, vnfd: dict) -> VduComputeConstraints: + compute_desc_id = vdu.get("virtual-compute-desc") + if not compute_desc_id: + return VduComputeConstraints(cores=0, mem=0) + flavor_details = VnfOperations._get_flavor_details(compute_desc_id, vnfd) + if not flavor_details: + return VduComputeConstraints(cores=0, mem=0) + + cpu_cores = flavor_details.get("virtual-cpu", {}).get("num-virtual-cpu", 0) + memory_gb = flavor_details.get("virtual-memory", {}).get("size", 0) + return VduComputeConstraints(cores=cpu_cores, mem=int(memory_gb)) + + @staticmethod + def _get_flavor_details(compute_desc_id: str, vnfd: dict) -> Any: + for flavor in vnfd.get("virtual-compute-desc", []): + if flavor.get("id") == compute_desc_id: + return flavor + return None + + @staticmethod + def get_application_config(vdu: dict, vdu_instantiation_config: dict) -> dict: + configurable_properties = vdu.get("configurable-properties", []) + + config_from_descriptor = VnfOperations._get_only_config_items( + VnfOperations._list_to_dict(configurable_properties) + ) + + config_from_instantiation = VnfOperations._get_only_config_items( + vdu_instantiation_config + ) + return {**config_from_descriptor, **config_from_instantiation} + + @staticmethod + def _get_only_config_items(config: dict) -> dict: + return { + key[len(CONFIG_IDENTIFIER) :]: value + for key, value in config.items() + if key.startswith(CONFIG_IDENTIFIER) + } + + @staticmethod + def _list_to_dict(indata: List[dict]) -> dict: + return { + item["key"]: item["value"] + for item in indata + if item.get("key") and item.get("value") + } class VnfDbActivity: @@ -208,7 +300,7 @@ class VnfDbActivity: ) @activity.defn(name=ACTIVITY_SET_VNF_MODEL) - async def set_vnf_model(self, set_vnf_model_input: VnfInstantiateInput) -> None: + async def set_vnf_model(self, set_vnf_model_input: SetVnfModelInput) -> None: """Updates the model name of VNF in VNFR. Collaborators: diff --git a/osm_lcm/temporal/vnf_workflows.py b/osm_lcm/temporal/vnf_workflows.py index c97f908..c121b05 100644 --- a/osm_lcm/temporal/vnf_workflows.py +++ b/osm_lcm/temporal/vnf_workflows.py @@ -13,14 +13,11 @@ # implied. # See the License for the specific language governing permissions and # limitations under the License. - +import asyncio from datetime import timedelta import logging -from temporalio import workflow -from temporalio.converter import value_to_type -from temporalio.common import RetryPolicy -from temporalio.exceptions import ActivityError, ChildWorkflowError import traceback +from typing import Tuple from osm_common.dataclasses.temporal_dataclasses import ( ChangeVnfInstantiationStateInput, @@ -29,22 +26,25 @@ from osm_common.dataclasses.temporal_dataclasses import ( GetTaskQueueOutput, GetVimCloudInput, GetVimCloudOutput, - GetVnfDetailsInput, - GetVnfDetailsOutput, - VduComputeConstraints, + GetVnfDescriptorInput, + GetVnfDescriptorOutput, + GetVnfRecordInput, + GetVnfRecordOutput, + SetVnfModelInput, VduInstantiateInput, VnfInstantiateInput, VnfInstantiationState, + VnfPrepareInput, VnfState, ) - from osm_common.temporal_constants import ( ACTIVITY_CHANGE_VNF_INSTANTIATION_STATE, ACTIVITY_SEND_NOTIFICATION_FOR_VNF, ACTIVITY_CHANGE_VNF_STATE, ACTIVITY_GET_TASK_QUEUE, ACTIVITY_GET_VIM_CLOUD, - ACTIVITY_GET_VNF_DETAILS, + ACTIVITY_GET_VNF_RECORD, + ACTIVITY_GET_VNF_DESCRIPTOR, ACTIVITY_SET_VNF_MODEL, LCM_TASK_QUEUE, WORKFLOW_VDU_INSTANTIATE, @@ -52,6 +52,11 @@ from osm_common.temporal_constants import ( WORKFLOW_VNF_PREPARE, ) from osm_lcm.temporal.juju_paas_activities import CharmInfoUtils +from osm_lcm.temporal.vnf_activities import VnfOperations +from temporalio import workflow +from temporalio.converter import value_to_type +from temporalio.common import RetryPolicy +from temporalio.exceptions import ActivityError, ChildWorkflowError _SANDBOXED = False retry_policy = RetryPolicy(maximum_attempts=3) @@ -107,21 +112,27 @@ class VnfInstantiateWorkflow: id=f"{WORKFLOW_VNF_PREPARE}-{input.vnfr_uuid}", ) - get_vnf_details = value_to_type( - GetVnfDetailsOutput, + get_vnf_record = value_to_type( + GetVnfRecordOutput, await workflow.execute_activity( - activity=ACTIVITY_GET_VNF_DETAILS, - arg=GetVnfDetailsInput(input.vnfr_uuid), - activity_id=f"{ACTIVITY_GET_VNF_DETAILS}-{input.vnfr_uuid}", + activity=ACTIVITY_GET_VNF_RECORD, + arg=GetVnfRecordInput(input.vnfr_uuid), + activity_id=f"{ACTIVITY_GET_VNF_RECORD}-{input.vnfr_uuid}", task_queue=vnf_task_queue.task_queue, schedule_to_close_timeout=default_schedule_to_close_timeout, retry_policy=retry_policy, ), ) - - get_cloud = value_to_type( - GetVimCloudOutput, - await workflow.execute_activity( + activities_results = await asyncio.gather( + workflow.execute_activity( + activity=ACTIVITY_GET_VNF_DESCRIPTOR, + arg=GetVnfDescriptorInput(get_vnf_record.vnfr["vnfd-id"]), + activity_id=f"{ACTIVITY_GET_VNF_DESCRIPTOR}-{get_vnf_record.vnfr['vnfd-id']}", + task_queue=vnf_task_queue.task_queue, + schedule_to_close_timeout=default_schedule_to_close_timeout, + retry_policy=retry_policy, + ), + workflow.execute_activity( activity=ACTIVITY_GET_VIM_CLOUD, arg=GetVimCloudInput(input.vnfr_uuid), activity_id=f"{ACTIVITY_GET_VIM_CLOUD}-{input.vnfr_uuid}", @@ -130,12 +141,16 @@ class VnfInstantiateWorkflow: retry_policy=retry_policy, ), ) + get_vnf_descriptor, get_cloud = value_to_type( + GetVnfDescriptorOutput, activities_results[0] + ), value_to_type(GetVimCloudOutput, activities_results[1]) await self.instantiate_vdus( - vnfr=get_vnf_details.vnfr, - vnfd=get_vnf_details.vnfd, + vnfr=get_vnf_record.vnfr, + vnfd=get_vnf_descriptor.vnfd, task_queue=vnf_task_queue.task_queue, cloud=get_cloud.cloud, + vnf_instantiation_config=input.instantiation_config, ) await self.update_vnf_instantiation_state( ChangeVnfInstantiationStateInput( @@ -170,7 +185,7 @@ class VnfInstantiateWorkflow: raise e except ChildWorkflowError as e: - err_details = str(e.cause.cause.with_traceback(e.cause.__traceback__)) + err_details = str(e.cause.with_traceback(e.cause.__traceback__)) await self.update_vnf_instantiation_state( ChangeVnfInstantiationStateInput( vnfr_uuid=input.vnfr_uuid, state=VnfInstantiationState.INSTANTIATED @@ -241,13 +256,23 @@ class VnfInstantiateWorkflow: ) @staticmethod - async def instantiate_vdus(vnfr: dict, vnfd: dict, task_queue: str, cloud: str): + async def instantiate_vdus( + vnfr: dict, + vnfd: dict, + task_queue: str, + cloud: str, + vnf_instantiation_config: dict, + ): for vdu in vnfd.get("vdu"): ( vdu_instantiate_input, vdu_instantiate_workflow_id, - ) = VnfInstantiateWorkflow.get_vdu_instantiate_input( - vnfr=vnfr, vnfd=vnfd, vdu=vdu, cloud=cloud + ) = VnfInstantiateWorkflow._get_vdu_instantiate_info( + vnfr=vnfr, + vnfd=vnfd, + vdu=vdu, + cloud=cloud, + vnf_instantiation_config=vnf_instantiation_config, ) await workflow.execute_child_workflow( workflow=WORKFLOW_VDU_INSTANTIATE, @@ -257,53 +282,26 @@ class VnfInstantiateWorkflow: ) @staticmethod - def get_flavor_details(compute_desc_id: str, vnfd: dict): - if not compute_desc_id: - return {} - flavor_details = next( - filter( - lambda flavor: flavor.get("id") == compute_desc_id, - vnfd.get("virtual-compute-desc", {}), - ), - {}, - ) - return flavor_details - - @staticmethod - def get_compute_constraints(vdu: dict, vnfd: dict) -> VduComputeConstraints: - compute_desc_id = vdu.get("virtual-compute-desc", "") - flavor_details = VnfInstantiateWorkflow.get_flavor_details( - compute_desc_id, vnfd - ) - if not flavor_details: - return VduComputeConstraints(cores=0, mem=0) - - cpu_cores = ( - flavor_details["virtual-cpu"].get("num-virtual-cpu", 0) - if flavor_details.get("virtual-cpu") - else 0 - ) - memory_gb = ( - flavor_details["virtual-memory"].get("size", 0) - if flavor_details.get("virtual-memory") - else 0 - ) - return VduComputeConstraints(cores=cpu_cores, mem=int(memory_gb)) - - @staticmethod - def get_vdu_instantiate_input(vnfr: dict, vnfd: dict, vdu: dict, cloud: str): + def _get_vdu_instantiate_info( + vnfr, vnfd, vdu, cloud, vnf_instantiation_config + ) -> Tuple[VduInstantiateInput, str]: + """Calculates the VDU instantiate input data without reaching Database.""" model_name = vnfr.get("namespace") vim_id = vnfr.get("vim-account-id") sw_image_descs = vnfd.get("sw-image-desc") vdu_info = CharmInfoUtils.get_charm_info(vdu, sw_image_descs) - compute_constraints = VnfInstantiateWorkflow.get_compute_constraints(vdu, vnfd) + vdu_instantiation_config = VnfOperations.get_vdu_instantiation_params( + vdu["id"], vnf_instantiation_config + ) + compute_constraints = VnfOperations.get_compute_constraints(vdu, vnfd) + config = VnfOperations.get_application_config(vdu, vdu_instantiation_config) vdu_instantiate_input = VduInstantiateInput( vim_uuid=vim_id, model_name=model_name, charm_info=vdu_info, constraints=compute_constraints, cloud=cloud, - config={}, + config=config, ) vdu_instantiate_workflow_id = ( vdu_instantiate_input.model_name @@ -326,11 +324,11 @@ class VnfPrepareWorkflow: self.logger = logging.getLogger(f"lcm.wfl.{self.__class__.__name__}") @workflow.run - async def run(self, wf_input: VnfInstantiateInput) -> None: + async def run(self, wf_input: VnfPrepareInput) -> None: try: await workflow.execute_activity( activity=ACTIVITY_SET_VNF_MODEL, - arg=wf_input, + arg=SetVnfModelInput(wf_input.vnfr_uuid, wf_input.model_name), activity_id=f"{ACTIVITY_SET_VNF_MODEL}-{wf_input.vnfr_uuid}", task_queue=LCM_TASK_QUEUE, schedule_to_close_timeout=default_schedule_to_close_timeout, diff --git a/osm_lcm/tests/test_juju_paas_activities.py b/osm_lcm/tests/test_juju_paas_activities.py index db1de7c..1aae706 100644 --- a/osm_lcm/tests/test_juju_paas_activities.py +++ b/osm_lcm/tests/test_juju_paas_activities.py @@ -18,6 +18,7 @@ import asynctest import asyncio from unittest import TestCase import unittest.mock as mock +from unittest.mock import ANY, AsyncMock, Mock from juju.application import Application from juju.controller import Controller @@ -36,7 +37,6 @@ 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 ANY, AsyncMock, Mock namespace = "some-namespace" vim_content = { @@ -259,9 +259,17 @@ class TestDeployCharm(TestJujuPaasActivitiesBase): entity_url = "ch:my-charm" cloud_k8s = "microk8s" cloud_other = "other" - config = {} + config = {"domain_name1": "osm.org", "domain_name2": "osm.com"} charm_info = CharmInfo(app_name, channel, entity_url) - vdu_instantiate_input_with_constraints_k8s = VduInstantiateInput( + vdu_instantiate_input_with_constraints_k8s_without_config = VduInstantiateInput( + vim_content["_id"], + namespace, + charm_info, + VduComputeConstraints(mem=1, cores=1), + cloud_k8s, + {}, + ) + vdu_instantiate_input_with_constraints_k8s_with_config = VduInstantiateInput( vim_content["_id"], namespace, charm_info, @@ -270,80 +278,82 @@ class TestDeployCharm(TestJujuPaasActivitiesBase): config, ) - async def test_deploy_charm_with_constraints_k8s_cloud(self): - await self.env.run( - self.juju_paas.deploy_charm, self.vdu_instantiate_input_with_constraints_k8s - ) - self.model.deploy.assert_called_once_with( - entity_url=self.entity_url, - application_name=self.app_name, - channel=self.channel, - constraints={"mem": 1024}, - ) - - async def test_deploy_charm_with_constraints_other_cloud(self): - await self.env.run( - self.juju_paas.deploy_charm, - VduInstantiateInput( - vim_content["_id"], - namespace, - self.charm_info, - VduComputeConstraints(mem=1, cores=1), - self.cloud_other, - self.config, + @parameterized.expand( + [ + ( + "k8s_cloud_without_config_with_constraints", + vdu_instantiate_input_with_constraints_k8s_without_config, + {"mem": 1024}, + {}, ), - ) - self.model.deploy.assert_called_once_with( - entity_url=self.entity_url, - application_name=self.app_name, - channel=self.channel, - constraints={"mem": 1024, "cores": 1}, - ) - - async def test_deploy_charm_without_constraints_k8s_cloud(self): - await self.env.run( - self.juju_paas.deploy_charm, - VduInstantiateInput( - vim_content["_id"], - namespace, - self.charm_info, - VduComputeConstraints(mem=0, cores=0), - self.cloud_k8s, - self.config, + ( + "k8s_cloud_with_config_with_constraints", + vdu_instantiate_input_with_constraints_k8s_with_config, + {"mem": 1024}, + config, ), - ) - self.model.deploy.assert_called_once_with( - entity_url=self.entity_url, - application_name=self.app_name, - channel=self.channel, - constraints=None, - ) - - async def test_deploy_charm_without_constraints_other_cloud(self): + ( + "k8s_cloud_with_config_without_constraints", + VduInstantiateInput( + vim_content["_id"], + namespace, + charm_info, + VduComputeConstraints(mem=0, cores=0), + cloud_k8s, + config, + ), + None, + config, + ), + ( + "other_cloud_without_config_with_constraints", + VduInstantiateInput( + vim_content["_id"], + namespace, + charm_info, + VduComputeConstraints(mem=1, cores=1), + cloud_other, + {}, + ), + {"mem": 1024, "cores": 1}, + {}, + ), + ( + "other_cloud_without_config_without_constraints", + VduInstantiateInput( + vim_content["_id"], + namespace, + charm_info, + VduComputeConstraints(mem=0, cores=0), + cloud_other, + {}, + ), + None, + {}, + ), + ] + ) + async def test_deploy_charm__model_deployed_with_expected_constraints_and_config( + self, _, vdu_instantiate_input, expected_constraint, expected_config + ): await self.env.run( self.juju_paas.deploy_charm, - VduInstantiateInput( - vim_content["_id"], - namespace, - self.charm_info, - VduComputeConstraints(mem=0, cores=0), - self.cloud_other, - self.config, - ), + vdu_instantiate_input, ) self.model.deploy.assert_called_once_with( entity_url=self.entity_url, application_name=self.app_name, channel=self.channel, - constraints=None, + constraints=expected_constraint, + config=expected_config, ) - async def test_deploy_charm_app_already_exists(self): + async def test_deploy_charm__app_already_exists__get_expected_error_details(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_with_constraints_k8s, + self.vdu_instantiate_input_with_constraints_k8s_without_config, ) self.model.deploy.assert_not_called() self.assertEqual( @@ -351,12 +361,12 @@ class TestDeployCharm(TestJujuPaasActivitiesBase): "Application {} already exists".format(self.app_name), ) - async def test_deploy_charm_raises_exception(self): + async def test_deploy_charm__juju_error_occured__app_is_not_deployed(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_with_constraints_k8s, + self.vdu_instantiate_input_with_constraints_k8s_without_config, ) self.model.deploy.assert_not_called() @@ -365,31 +375,45 @@ class TestGetApplicationConstraints(TestCase): constraints = VduComputeConstraints(mem=1, cores=1) no_constraints = VduComputeConstraints(mem=0, cores=0) - def test_get_application_constraints_k8s_cloud(self): - result = JujuPaasConnector._get_application_constraints( - self.constraints, "kubernetes" - ) - self.assertEqual(result, {"mem": 1024}) - - def test_get_application_constraints_aws_cloud(self): - result = JujuPaasConnector._get_application_constraints(self.constraints, "aws") - self.assertEqual(result, {"cores": 1, "mem": 1024}) - - def test_get_application_constraints_no_constraints_aws(self): - result = JujuPaasConnector._get_application_constraints( - self.no_constraints, "aws" - ) - self.assertEqual(result, {}) - - def test_get_application_constraints_no_constraints_microk8s(self): - result = JujuPaasConnector._get_application_constraints( - self.no_constraints, "microk8s" - ) - self.assertEqual(result, {}) - - def test_get_application_constraints_empty_cloud(self): - result = JujuPaasConnector._get_application_constraints(self.constraints, "") - self.assertEqual(result, {"cores": 1, "mem": 1024}) + @parameterized.expand( + [ + ( + "k8s_cloud_with_constraints", + constraints, + "kubernetes", + {"mem": 1024}, + ), + ( + "aws_cloud_with_constraints", + constraints, + "aws", + {"cores": 1, "mem": 1024}, + ), + ( + "aws_cloud_without_constraints", + no_constraints, + "aws", + {}, + ), + ( + "microk8s_without_constraints", + no_constraints, + "microk8s", + {}, + ), + ( + "empty_cloud_with_constraints", + constraints, + "", + {"cores": 1, "mem": 1024}, + ), + ] + ) + def test_get_application_constraints__get_expected_constraints( + self, _, constraints, cloud, expected_result + ): + result = JujuPaasConnector._get_application_constraints(constraints, cloud) + self.assertEqual(result, expected_result) class TestTestVimConnectivity(TestJujuPaasActivitiesBase): diff --git a/osm_lcm/tests/test_ns_activities.py b/osm_lcm/tests/test_ns_activities.py index 623f072..c3176ee 100644 --- a/osm_lcm/tests/test_ns_activities.py +++ b/osm_lcm/tests/test_ns_activities.py @@ -14,45 +14,90 @@ # See the License for the specific language governing permissions and # limitations under the License. - -from unittest.mock import Mock - import asynctest -from osm_common.dataclasses.temporal_dataclasses import GetVnfRecordIdsInput + +from osm_common.dataclasses.temporal_dataclasses import ( + GetNsRecordInput, + GetVnfDetailsInput, +) from osm_common.dbbase import DbException from temporalio.testing import ActivityEnvironment - from osm_lcm.temporal.ns_activities import NsOperations +from unittest.mock import Mock ns_uuid = "00000000-0000-0000-0000-000000000000" -get_vnf_record_ids_input = GetVnfRecordIdsInput(ns_uuid=ns_uuid) +get_vnf_details_input = GetVnfDetailsInput(ns_uuid=ns_uuid) +sample_vnf_details = [ + { + "id": "00000000-0000-0000-0000-000000000000", + "member-vnf-index-ref": "vnf1", + }, + { + "id": "00000000-0000-0000-0000-000000000000", + "member-vnf-index-ref": "vnf2", + }, +] +sample_nsr = { + "_id": ns_uuid, + "name": "sol006_juju24", + "name-ref": "sol006_juju24", + "short-name": "sol006_juju24", + "admin-status": "ENABLED", + "nsState": "NOT_INSTANTIATED", + "currentOperation": "IDLE", +} + + +class TestException(Exception): + pass -class TestGetModelInfo(asynctest.TestCase): +class TestGetVnfDetails(asynctest.TestCase): def setUp(self): self.db = Mock() self.env = ActivityEnvironment() self.ns_operations_activity = NsOperations(self.db) - async def test_get_vnfr_ids(self): - self.db.get_list.return_value = [ - {"id": "00000000-0000-0000-0000-000000000000"}, - {"id": "00000000-0000-0000-0000-000000000000"}, - ] + async def test_activity__succeded__get_expected_result(self): + self.db.get_list.return_value = sample_vnf_details result = await self.env.run( - self.ns_operations_activity.get_vnf_record_ids, get_vnf_record_ids_input + self.ns_operations_activity.get_vnf_details, get_vnf_details_input ) - assert result.vnfr_ids == [ - "00000000-0000-0000-0000-000000000000", - "00000000-0000-0000-0000-000000000000", - ] - - self.db.get_list.assert_called_with("vnfrs", {"nsr-id-ref": ns_uuid}) + self.assertEqual( + result.vnf_details, + [ + ("00000000-0000-0000-0000-000000000000", "vnf1"), + ("00000000-0000-0000-0000-000000000000", "vnf2"), + ], + ) - async def test_activity_raises_db_exception(self): - self.db.get_list.side_effect = DbException("not found") + async def test_activity__failed__raise_db_exception(self): + self.db.get_list.side_effect = DbException("not found.") with self.assertRaises(DbException): await self.env.run( - self.ns_operations_activity.get_vnf_record_ids, get_vnf_record_ids_input + self.ns_operations_activity.get_vnf_details, get_vnf_details_input + ) + + +class TestGetNsRecord(asynctest.TestCase): + async def setUp(self): + self.db = Mock() + self.env = ActivityEnvironment() + self.ns_operations_activity = NsOperations(self.db) + + async def test_activity__succeeded__get_expected_result(self): + self.db.get_one.return_value = sample_nsr + activity_result = await self.env.run( + self.ns_operations_activity.get_ns_record, + GetNsRecordInput(nsr_uuid=sample_nsr["_id"]), + ) + self.assertEqual(activity_result.nsr, sample_nsr) + + async def test_activity__failed__raise_test_exception(self): + self.db.get_one.side_effect = TestException("Can not connect to Database.") + with self.assertRaises(TestException): + await self.env.run( + self.ns_operations_activity.get_ns_record, + GetNsRecordInput(nsr_uuid=sample_nsr["_id"]), ) diff --git a/osm_lcm/tests/test_ns_workflows.py b/osm_lcm/tests/test_ns_workflows.py index 082b155..e5f38ef 100644 --- a/osm_lcm/tests/test_ns_workflows.py +++ b/osm_lcm/tests/test_ns_workflows.py @@ -13,15 +13,16 @@ # implied. # See the License for the specific language governing permissions and # limitations under the License. - - -from unittest.mock import Mock - import asynctest -from mock import AsyncMock, patch +import copy +from datetime import timedelta +from unittest.mock import Mock, patch + from osm_common.dataclasses.temporal_dataclasses import ( - GetVnfRecordIdsInput, - GetVnfRecordIdsOutput, + GetNsRecordInput, + GetNsRecordOutput, + GetVnfDetailsInput, + GetVnfDetailsOutput, LcmOperationState, ModelInfo, NsLcmOperationInput, @@ -32,23 +33,77 @@ from osm_common.dataclasses.temporal_dataclasses import ( ) from osm_common.temporal_constants import ( ACTIVITY_CREATE_MODEL, - ACTIVITY_GET_VNF_RECORD_IDS, + ACTIVITY_GET_NS_RECORD, + ACTIVITY_GET_VNF_DETAILS, ACTIVITY_UPDATE_LCM_OPERATION_STATE, ACTIVITY_UPDATE_NS_STATE, LCM_TASK_QUEUE, WORKFLOW_VNF_INSTANTIATE, ) +from osm_lcm.temporal.ns_workflows import NsInstantiateWorkflow from temporalio import activity, workflow from temporalio.client import WorkflowFailureError +from temporalio.common import RetryPolicy +from temporalio.exceptions import ( + ActivityError, + ChildWorkflowError, + RetryState, + TimeoutError, +) from temporalio.testing import WorkflowEnvironment from temporalio.worker import Worker -from osm_lcm.temporal.ns_workflows import NsInstantiateWorkflow -vnfr_ids = [ - "828d91ee-fa04-43bb-8471-f66ea74597e7", - "c4bbeb41-df7e-4daa-863d-c8fd29fac96d", +vnfr_uuid_1 = "828d91ee-fa04-43bb-8471-f66ea74597e7" +vnfr_uuid_2 = "c4bbeb41-df7e-4daa-863d-c8fd29fac96d" +vnf_member_index_ref_1 = "vnf-profile-id" +vnf_member_index_ref_2 = "vnf-profile-id-2" +vnf_details = [ + (vnfr_uuid_1, vnf_member_index_ref_1), + (vnfr_uuid_2, vnf_member_index_ref_2), ] +ns_uuid = "9c96e9c1-aae2-4c61-a85f-2ec8acb448fc" +vim_uuid = "a64f7c6c-bc27-4ec8-b664-5500a3324eca" +vdu_id = "test-vdu" +model_name = "2ec8acb448fc-5500a3324eca" +vnf_config = { + "member-vnf-index": vnf_member_index_ref_1, + "vdu": [ + { + "id": vdu_id, + "configurable-properties": { + "config::redirect-map": "https://osm.instantiation.params" + }, + } + ], +} +sample_nsr = { + "_id": ns_uuid, + "name": "sol006_juju23", + "name-ref": "sol006_juju23", + "short-name": "sol006_juju23", + "admin-status": "ENABLED", + "nsState": "NOT_INSTANTIATED", + "instantiate_params": { + "nsdId": ns_uuid, + "nsName": "sol006_juju23", + "nsDescription": "default description", + "vimAccountId": vim_uuid, + "vnf": [vnf_config], + }, +} +mock_get_namespace = Mock() +retry_policy = RetryPolicy( + initial_interval=timedelta(seconds=1), + backoff_coefficient=2.0, + maximum_interval=None, + maximum_attempts=1, + non_retryable_error_types=None, +) +SANDBOXED = False +DEBUG_MODE = True +TASK_TIMEOUT = timedelta(seconds=0.5) +EXECUTION_TIMEOUT = timedelta(seconds=1) class TestException(Exception): @@ -60,31 +115,72 @@ async def mock_create_model(create_model_input: ModelInfo) -> None: pass -@activity.defn(name=ACTIVITY_GET_VNF_RECORD_IDS) -async def mock_get_vnf_record_ids( - get_vnf_record_ids_input: GetVnfRecordIdsInput, -) -> None: - return GetVnfRecordIdsOutput(vnfr_ids=vnfr_ids) - - @activity.defn(name=ACTIVITY_CREATE_MODEL) async def mock_create_model_raises(create_model_input: ModelInfo) -> None: - raise TestException("Test exception") + raise TestException(f"{ACTIVITY_CREATE_MODEL} failed.") + + +@activity.defn(name=ACTIVITY_GET_VNF_DETAILS) +async def mock_get_vnf_details( + get_vnf_details_input: GetVnfDetailsInput, +) -> GetVnfDetailsOutput: + return GetVnfDetailsOutput(vnf_details=vnf_details) -@workflow.defn(name=WORKFLOW_VNF_INSTANTIATE, sandboxed=False) +@activity.defn(name=ACTIVITY_GET_VNF_DETAILS) +async def mock_get_vnf_details_raises( + et_vnf_details_input: GetVnfDetailsInput, +) -> GetVnfDetailsOutput: + raise TestException(f"{ACTIVITY_GET_VNF_DETAILS} failed.") + + +@activity.defn(name=ACTIVITY_GET_NS_RECORD) +async def mock_get_ns_record( + get_ns_record_input: GetNsRecordInput, +) -> GetNsRecordOutput: + return GetNsRecordOutput(nsr=sample_nsr) + + +@activity.defn(name=ACTIVITY_GET_NS_RECORD) +async def mock_get_ns_record_raise_exception( + get_ns_record_input: GetNsRecordInput, +) -> GetNsRecordOutput: + raise TestException(f"{ACTIVITY_GET_NS_RECORD} failed.") + + +@workflow.defn(name=WORKFLOW_VNF_INSTANTIATE, sandboxed=SANDBOXED) class MockVnfInstantiateWorkflow: @workflow.run async def run(self, input: VnfInstantiateInput) -> None: pass +@workflow.defn(name=WORKFLOW_VNF_INSTANTIATE, sandboxed=SANDBOXED) +class MockVnfInstantiateWorkflowFailed: + @workflow.run + async def run(self, input: VnfInstantiateInput) -> None: + raise ChildWorkflowError( + message=f"{WORKFLOW_VNF_INSTANTIATE} child workflow failed.", + namespace="default", + workflow_id="123", + run_id="1", + workflow_type=WORKFLOW_VNF_INSTANTIATE, + initiated_event_id=0, + started_event_id=0, + retry_state=RetryState.NON_RETRYABLE_FAILURE, + ) + + +@patch( + "osm_lcm.temporal.ns_workflows.NsInstantiateWorkflow._get_namespace", + new=mock_get_namespace, +) class TestNsInstantiateWorkflow(asynctest.TestCase): input = NsLcmOperationInput( nslcmop={ "_id": "1234", - "nsInstanceId": "5678", - "operationParams": {"vimAccountId": "9876"}, + "nsInstanceId": ns_uuid, + "operationParams": {"vimAccountId": vim_uuid}, } ) @@ -101,111 +197,414 @@ class TestNsInstantiateWorkflow(asynctest.TestCase): async def setUp(self): self.env = await WorkflowEnvironment.start_time_skipping() + self.client = self.env.client self.mock_update_lcm_operation_state_tracker = Mock() self.mock_update_ns_state_tracker = Mock() + self.workflows = [NsInstantiateWorkflow, MockVnfInstantiateWorkflow] + self.task_queue = LCM_TASK_QUEUE + mock_get_namespace.return_value = model_name - @patch("temporalio.workflow.execute_child_workflow") - async def test_instantiate_workflow(self, mock_execute_child_workflow: AsyncMock): - async with self.env as env: - async with Worker( - env.client, - task_queue=LCM_TASK_QUEUE, - workflows=[NsInstantiateWorkflow, MockVnfInstantiateWorkflow], - activities=[ - mock_create_model, - mock_get_vnf_record_ids, - self.mock_update_ns_state, - self.mock_update_lcm_operation_state, - ], - debug_mode=True, - ): - await env.client.execute_workflow( - NsInstantiateWorkflow, - arg=self.input, - id=self.input.nslcmop["nsInstanceId"], - task_queue=LCM_TASK_QUEUE, - ) + def get_worker(self, task_queue: str, workflows: list, activities: list) -> Worker: + return Worker( + self.client, + task_queue=task_queue, + workflows=workflows, + activities=activities, + debug_mode=DEBUG_MODE, + ) - self.assertEqual( - [ - call.kwargs["workflow"] == WORKFLOW_VNF_INSTANTIATE - and call.kwargs["arg"].vnfr_uuid - for call in mock_execute_child_workflow.call_args_list - ], - vnfr_ids, + async def execute_workflow(self): + return await self.client.execute_workflow( + NsInstantiateWorkflow, + arg=self.input, + id=self.input.nslcmop["nsInstanceId"], + task_queue=self.task_queue, + task_timeout=TASK_TIMEOUT, + execution_timeout=EXECUTION_TIMEOUT, ) + async def test_instantiate_workflow__successful__update_lcm_op_state(self): + activities = [ + mock_create_model, + mock_get_vnf_details, + mock_get_ns_record, + self.mock_update_ns_state, + self.mock_update_lcm_operation_state, + ] + async with self.env, self.get_worker( + self.task_queue, self.workflows, activities + ): + await self.execute_workflow() self.assert_lcm_op_states( self.mock_update_lcm_operation_state_tracker.call_args_list, [LcmOperationState.PROCESSING, LcmOperationState.COMPLETED], ) + async def test_instantiate_workflow__successful__update_ns_state(self): + activities = [ + mock_create_model, + mock_get_vnf_details, + mock_get_ns_record, + self.mock_update_ns_state, + self.mock_update_lcm_operation_state, + ] + async with self.env, self.get_worker( + self.task_queue, self.workflows, activities + ): + await self.execute_workflow() + self.assert_ns_states( + self.mock_update_ns_state_tracker.call_args_list, [NsState.INSTANTIATED] + ) + + async def test_instantiate_workflow__activity_failed__update_ns_state( + self, + ): + activities = [ + mock_create_model_raises, + mock_get_vnf_details, + mock_get_ns_record, + self.mock_update_ns_state, + self.mock_update_lcm_operation_state, + ] + with self.assertRaises(WorkflowFailureError): + async with self.env, self.get_worker( + self.task_queue, self.workflows, activities + ): + await self.execute_workflow() self.assert_ns_states( self.mock_update_ns_state_tracker.call_args_list, [NsState.INSTANTIATED] ) - async def test_instantiate_workflow_with_exception_updates_lcm_operation(self): - async with self.env as env: - async with Worker( - env.client, - task_queue=LCM_TASK_QUEUE, - workflows=[NsInstantiateWorkflow, MockVnfInstantiateWorkflow], - activities=[ - mock_create_model_raises, - mock_get_vnf_record_ids, - self.mock_update_ns_state, - self.mock_update_lcm_operation_state, - ], - debug_mode=True, + async def test_instantiate_workflow__activity_failed__update_lcm_op_state( + self, + ): + activities = [ + mock_create_model_raises, + mock_get_vnf_details, + mock_get_ns_record, + self.mock_update_ns_state, + self.mock_update_lcm_operation_state, + ] + with self.assertRaises(WorkflowFailureError): + async with self.env, self.get_worker( + self.task_queue, self.workflows, activities + ): + await self.execute_workflow() + self.assert_lcm_op_states( + self.mock_update_lcm_operation_state_tracker.call_args_list, + [LcmOperationState.PROCESSING, LcmOperationState.FAILED], + ) + + async def test_instantiate_workflow__create_model_activity_failed__raise_activity_error( + self, + ): + activities = [ + mock_create_model_raises, + mock_get_vnf_details, + mock_get_ns_record, + self.mock_update_ns_state, + self.mock_update_lcm_operation_state, + ] + with self.assertRaises(WorkflowFailureError) as err: + async with self.env, self.get_worker( + self.task_queue, self.workflows, activities + ): + await self.execute_workflow() + self.assertTrue(isinstance(err.exception.cause, ActivityError)) + + async def test_instantiate_workflow__create_model_activity_failed__error_details_expected( + self, + ): + activities = [ + mock_create_model_raises, + mock_get_vnf_details, + mock_get_ns_record, + self.mock_update_ns_state, + self.mock_update_lcm_operation_state, + ] + with self.assertRaises(WorkflowFailureError): + async with self.env, self.get_worker( + self.task_queue, self.workflows, activities + ): + await self.execute_workflow() + self.assert_ns_error_details( + self.mock_update_ns_state_tracker.call_args_list, + ["TestException: create-model failed."], + ) + + async def test_instantiate_workflow__get_ns_record_activity_failed__error_details_expected( + self, + ): + activities = [ + mock_create_model, + mock_get_vnf_details, + mock_get_ns_record_raise_exception, + self.mock_update_ns_state, + self.mock_update_lcm_operation_state, + ] + with self.assertRaises(WorkflowFailureError): + async with self.env, self.get_worker( + self.task_queue, self.workflows, activities ): - with self.assertRaises(WorkflowFailureError) as e_info: - await env.client.execute_workflow( - NsInstantiateWorkflow, - arg=self.input, - id=self.input.nslcmop["nsInstanceId"], - task_queue=LCM_TASK_QUEUE, - ) - self.assertEqual(e_info.exception.cause.cause.type, "TestException") + await self.execute_workflow() + self.assert_ns_error_details( + self.mock_update_ns_state_tracker.call_args_list, + ["TestException: get-ns-record failed."], + ) + async def test_instantiate_workflow__get_ns_record_activity_failed__raise_activity_error( + self, + ): + activities = [ + mock_create_model, + mock_get_vnf_details, + mock_get_ns_record_raise_exception, + self.mock_update_ns_state, + self.mock_update_lcm_operation_state, + ] + with self.assertRaises(WorkflowFailureError) as err: + async with self.env, self.get_worker( + self.task_queue, self.workflows, activities + ): + await self.execute_workflow() + self.assertTrue(isinstance(err.exception.cause, ActivityError)) + + async def test_instantiate_workflow__get_vnf_details_activity_failed__error_details_expected( + self, + ): + activities = [ + mock_create_model, + mock_get_vnf_details_raises, + mock_get_ns_record, + self.mock_update_ns_state, + self.mock_update_lcm_operation_state, + ] + with self.assertRaises(WorkflowFailureError): + async with self.env, self.get_worker( + self.task_queue, self.workflows, activities + ): + await self.execute_workflow() + self.assert_ns_error_details( + self.mock_update_ns_state_tracker.call_args_list, + ["TestException: get-vnf-details failed."], + ) + + async def test_instantiate_workflow__get_vnf_details_activity_failed__raise_activity_error( + self, + ): + activities = [ + mock_create_model, + mock_get_vnf_details_raises, + mock_get_ns_record, + self.mock_update_ns_state, + self.mock_update_lcm_operation_state, + ] + with self.assertRaises(WorkflowFailureError) as err: + async with self.env, self.get_worker( + self.task_queue, self.workflows, activities + ): + await self.execute_workflow() + self.assertTrue(isinstance(err.exception.cause, ActivityError)) + + @patch("osm_lcm.temporal.ns_workflows.NsInstantiateWorkflow.get_vnf_config") + async def test_instantiate_workflow__get_vnf_config_failed__workflow_times_out( + self, mock_get_vnf_config + ): + # Because of workflow task timeout policy, workflow times out + activities = [ + mock_create_model, + mock_get_vnf_details, + mock_get_ns_record, + self.mock_update_ns_state, + self.mock_update_lcm_operation_state, + ] + mock_get_vnf_config.side_effect = TestException("get_vnf_config failed.") + with self.assertRaises(WorkflowFailureError) as err: + async with self.env, self.get_worker( + self.task_queue, self.workflows, activities + ): + await self.execute_workflow() + self.assertTrue(isinstance(err.exception.cause, TimeoutError)) + + @patch("osm_lcm.temporal.ns_workflows.NsInstantiateWorkflow.get_vnf_config") + async def test_instantiate_workflow__get_vnf_config_failed__update_ns_state( + self, mock_get_vnf_config + ): + # Workflow task failure + activities = [ + mock_create_model, + mock_get_vnf_details, + mock_get_ns_record, + self.mock_update_ns_state, + self.mock_update_lcm_operation_state, + ] + mock_get_vnf_config.side_effect = TestException("get_vnf_config failed.") + with self.assertRaises(WorkflowFailureError): + async with self.env, self.get_worker( + self.task_queue, self.workflows, activities + ): + await self.execute_workflow() + self.assert_ns_states( + self.mock_update_ns_state_tracker.call_args_list, [NsState.INSTANTIATED] + ) + + @patch("osm_lcm.temporal.ns_workflows.NsInstantiateWorkflow.get_vnf_config") + async def test_instantiate_workflow__get_vnf_config_failed__update_lcm_op_state( + self, mock_get_vnf_config + ): + # Workflow task failure + activities = [ + mock_create_model, + mock_get_vnf_details, + mock_get_ns_record, + self.mock_update_ns_state, + self.mock_update_lcm_operation_state, + ] + mock_get_vnf_config.side_effect = TestException("get_vnf_config failed.") + with self.assertRaises(WorkflowFailureError): + async with self.env, self.get_worker( + self.task_queue, self.workflows, activities + ): + await self.execute_workflow() self.assert_lcm_op_states( self.mock_update_lcm_operation_state_tracker.call_args_list, [LcmOperationState.PROCESSING, LcmOperationState.FAILED], ) - async def test_instantiate_workflow_with_exception_updates_ns_state(self): - async with self.env as env: - async with Worker( - env.client, - task_queue=LCM_TASK_QUEUE, - workflows=[NsInstantiateWorkflow, MockVnfInstantiateWorkflow], - activities=[ - mock_create_model_raises, - mock_get_vnf_record_ids, - self.mock_update_ns_state, - self.mock_update_lcm_operation_state, - ], - debug_mode=True, + async def test_instantiate_workflow__child_wf_failed__raise_child_workflow_error( + self, + ): + activities = [ + mock_create_model, + mock_get_vnf_details, + mock_get_ns_record, + self.mock_update_ns_state, + self.mock_update_lcm_operation_state, + ] + with self.assertRaises(WorkflowFailureError) as err: + async with self.env, self.get_worker( + self.task_queue, + [NsInstantiateWorkflow, MockVnfInstantiateWorkflowFailed], + activities, + ): + await self.execute_workflow() + self.assertTrue(isinstance(err.exception.cause, ChildWorkflowError)) + + async def test_instantiate_workflow__child_wf_failed__error_details_expected( + self, + ): + activities = [ + mock_create_model, + mock_get_vnf_details, + mock_get_ns_record, + self.mock_update_ns_state, + self.mock_update_lcm_operation_state, + ] + with self.assertRaises(WorkflowFailureError): + async with self.env, self.get_worker( + self.task_queue, + [NsInstantiateWorkflow, MockVnfInstantiateWorkflowFailed], + activities, ): - with self.assertRaises(WorkflowFailureError) as e_info: - await env.client.execute_workflow( - NsInstantiateWorkflow, - arg=self.input, - id=self.input.nslcmop["nsInstanceId"], - task_queue=LCM_TASK_QUEUE, - ) - self.assertEqual(e_info.exception.cause.cause.type, "TestException") + await self.execute_workflow() + self.assert_ns_error_details( + self.mock_update_ns_state_tracker.call_args_list, + ["vnf-instantiate child workflow failed."], + ) + + async def test_instantiate_workflow__child_wf_failed__update_ns_state( + self, + ): + activities = [ + mock_create_model, + mock_get_vnf_details, + mock_get_ns_record, + self.mock_update_ns_state, + self.mock_update_lcm_operation_state, + ] + with self.assertRaises(WorkflowFailureError): + async with self.env, self.get_worker( + self.task_queue, + [NsInstantiateWorkflow, MockVnfInstantiateWorkflowFailed], + activities, + ): + await self.execute_workflow() self.assert_ns_states( self.mock_update_ns_state_tracker.call_args_list, [NsState.INSTANTIATED] ) + async def test_instantiate_workflow__child_wf_failed__update_lcm_op_state( + self, + ): + activities = [ + mock_create_model, + mock_get_vnf_details, + mock_get_ns_record, + self.mock_update_ns_state, + self.mock_update_lcm_operation_state, + ] + + with self.assertRaises(WorkflowFailureError): + async with self.env, self.get_worker( + self.task_queue, + [NsInstantiateWorkflow, MockVnfInstantiateWorkflowFailed], + activities, + ): + await self.execute_workflow() + self.assert_lcm_op_states( + self.mock_update_lcm_operation_state_tracker.call_args_list, + [LcmOperationState.PROCESSING, LcmOperationState.FAILED], + ) + def assert_ns_states(self, call_args_list, expected_states): """Asserts that the NS state was set to each of the expected states (in order).""" self.assertEqual( [call.args[0].state for call in call_args_list], expected_states ) + def assert_ns_error_details(self, call_args_list, expected_error): + """Asserts that the NS state was set to each of the expected states (in order).""" + self.assertEqual( + [call.args[0].message for call in call_args_list], expected_error + ) + def assert_lcm_op_states(self, call_args_list, expected_states): - """Asserts that the LCM operation was set to each of the exepcted states (in order).""" + """Asserts that the LCM operation was set to each of the expected states (in order).""" self.assertEqual( [call.args[0].op_state for call in call_args_list], expected_states ) + + +class TestGetVnfConfig(asynctest.TestCase): + def test_vnf_config__nsr_config_matches_with_vnf__return_expected_config(self): + expected_config = { + "member-vnf-index": "vnf-profile-id", + "vdu": [ + { + "configurable-properties": { + "config::redirect-map": "https://osm.instantiation.params" + }, + "id": "test-vdu", + } + ], + } + result = NsInstantiateWorkflow.get_vnf_config( + vnf_member_index_ref_1, sample_nsr + ) + self.assertEqual(result, expected_config) + + def test_vnf_config__nsr_does_not_have_config__expected_no_config(self): + nsr = copy.deepcopy(sample_nsr) + nsr["instantiate_params"] = {} + result = NsInstantiateWorkflow.get_vnf_config(vnf_member_index_ref_1, nsr) + self.assertEqual(result, {}) + + def test_vnf_config__empty_vnf_member_index_ref__expected_no_config(self): + result = NsInstantiateWorkflow.get_vnf_config("", sample_nsr) + self.assertEqual(result, {}) + + def test_vnf_config__nsr_config_does_not_match_with_vnf__expected_no_config(self): + result = NsInstantiateWorkflow.get_vnf_config( + vnf_member_index_ref_2, sample_nsr + ) + self.assertEqual(result, {}) diff --git a/osm_lcm/tests/test_vdu_workflow.py b/osm_lcm/tests/test_vdu_workflow.py index 22def4e..333f1de 100644 --- a/osm_lcm/tests/test_vdu_workflow.py +++ b/osm_lcm/tests/test_vdu_workflow.py @@ -15,6 +15,7 @@ # limitations under the License. import asynctest +from datetime import timedelta from osm_common.dataclasses.temporal_dataclasses import ( CharmInfo, @@ -29,9 +30,19 @@ from osm_common.temporal_constants import ( from osm_lcm.temporal.vdu_workflows import CheckCharmStatusInput, VduInstantiateWorkflow from temporalio import activity from temporalio.client import WorkflowFailureError +from temporalio.exceptions import ( + ActivityError, +) from temporalio.testing import WorkflowEnvironment from temporalio.worker import Worker +TASK_TIMEOUT = timedelta(seconds=0.5) +EXECUTION_TIMEOUT = timedelta(seconds=1) + + +class TestException(Exception): + pass + @activity.defn(name=ACTIVITY_DEPLOY_CHARM) async def deploy_charm_mocked(deploy_charm_input: VduInstantiateInput) -> None: @@ -40,7 +51,7 @@ async def deploy_charm_mocked(deploy_charm_input: VduInstantiateInput) -> None: @activity.defn(name=ACTIVITY_DEPLOY_CHARM) async def deploy_charm_mocked_raises(deploy_charm_input: VduInstantiateInput) -> None: - raise Exception() + raise TestException(f"{ACTIVITY_DEPLOY_CHARM} failed.") @activity.defn(name=ACTIVITY_CHECK_CHARM_STATUS) @@ -52,7 +63,7 @@ async def check_charm_status_mocked(check_charm_status: CheckCharmStatusInput) - async def check_charm_status_mocked_raises( check_charm_status: CheckCharmStatusInput, ) -> None: - raise Exception() + raise TestException(f"{ACTIVITY_CHECK_CHARM_STATUS} failed.") class TestVduWorkflows(asynctest.TestCase): @@ -63,13 +74,16 @@ class TestVduWorkflows(asynctest.TestCase): channel = "latest" entity_url = "ch:my-charm" cloud = "microk8s" - config = {} + config = {"domain_name1": "osm.org", "domain_name2": "osm.com"} constraints = VduComputeConstraints(mem=1, cores=1) charm_info = CharmInfo(app_name, channel, entity_url) - vdu_instantiate_input = VduInstantiateInput( + vdu_instantiate_input_without_config = VduInstantiateInput( + vim_id, namespace, charm_info, constraints, cloud, {} + ) + vdu_instantiate_input_with_config = VduInstantiateInput( vim_id, namespace, charm_info, constraints, cloud, config ) - worflow_id = namespace + "-" + app_name + workflow_id = namespace + "-" + app_name async def setUp(self): self.env = await WorkflowEnvironment.start_time_skipping() @@ -84,37 +98,87 @@ class TestVduWorkflows(asynctest.TestCase): debug_mode=True, ) - async def test_vdu_instantiate(self): + async def test_vdu_instantiate__without_config__successful(self): activities = [deploy_charm_mocked, check_charm_status_mocked] async with self.env, self.get_worker(activities): - result = await self.client.execute_workflow( + await self.client.execute_workflow( VduInstantiateWorkflow.run, - arg=self.vdu_instantiate_input, - id=self.worflow_id, + arg=self.vdu_instantiate_input_without_config, + id=self.workflow_id, task_queue=self.task_queue_name, + task_timeout=TASK_TIMEOUT, + execution_timeout=EXECUTION_TIMEOUT, ) - self.assertIsNone(result) - async def test_vdu_instantiate_deploy_raises(self): + async def test_vdu_instantiate__with_config__successful(self): + activities = [deploy_charm_mocked, check_charm_status_mocked] + async with self.env, self.get_worker(activities): + await self.client.execute_workflow( + VduInstantiateWorkflow.run, + arg=self.vdu_instantiate_input_with_config, + id=self.workflow_id, + task_queue=self.task_queue_name, + task_timeout=TASK_TIMEOUT, + execution_timeout=EXECUTION_TIMEOUT, + ) + + async def test_vdu_instantiate__deploy_charm_failed__raise_activity_error(self): + activities = [deploy_charm_mocked_raises, check_charm_status_mocked] + async with self.env, self.get_worker(activities): + with self.assertRaises(WorkflowFailureError) as err: + await self.client.execute_workflow( + VduInstantiateWorkflow.run, + arg=self.vdu_instantiate_input_without_config, + id=self.workflow_id, + task_queue=self.task_queue_name, + task_timeout=TASK_TIMEOUT, + execution_timeout=EXECUTION_TIMEOUT, + ) + self.assertTrue(err.exception.cause, ActivityError) + + async def test_vdu_instantiate__deploy_charm_failed__get_error_details(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( + with self.assertRaises(WorkflowFailureError) as err: + await self.client.execute_workflow( VduInstantiateWorkflow.run, - arg=self.vdu_instantiate_input, - id=self.worflow_id, + arg=self.vdu_instantiate_input_without_config, + id=self.workflow_id, task_queue=self.task_queue_name, + task_timeout=TASK_TIMEOUT, + execution_timeout=EXECUTION_TIMEOUT, ) - self.assertIsNone(result) + self.assertEqual( + str(err.exception.cause.cause), "TestException: deploy-charm failed." + ) - async def test_vdu_instantiate_check_status_raises(self): + async def test_vdu_instantiate__check_status__raise_activity_error(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( + with self.assertRaises(WorkflowFailureError) as err: + await self.client.execute_workflow( VduInstantiateWorkflow.run, - arg=self.vdu_instantiate_input, - id=self.worflow_id, + arg=self.vdu_instantiate_input_without_config, + id=self.workflow_id, task_queue=self.task_queue_name, + task_timeout=TASK_TIMEOUT, + execution_timeout=EXECUTION_TIMEOUT, ) - self.assertIsNone(result) + self.assertTrue(err.exception.cause, ActivityError) + + async def test_vdu_instantiate__check_status__get_error_details(self): + activities = [deploy_charm_mocked, check_charm_status_mocked_raises] + async with self.env, self.get_worker(activities): + with self.assertRaises(WorkflowFailureError) as err: + await self.client.execute_workflow( + VduInstantiateWorkflow.run, + arg=self.vdu_instantiate_input_without_config, + id=self.workflow_id, + task_queue=self.task_queue_name, + task_timeout=TASK_TIMEOUT, + execution_timeout=EXECUTION_TIMEOUT, + ) + self.assertEqual( + str(err.exception.cause.cause), + "TestException: check-charm-status failed.", + ) diff --git a/osm_lcm/tests/test_vim_sdn.py b/osm_lcm/tests/test_vim_sdn.py index 7bd6c65..8d68597 100644 --- a/osm_lcm/tests/test_vim_sdn.py +++ b/osm_lcm/tests/test_vim_sdn.py @@ -16,7 +16,6 @@ import asyncio from unittest import TestCase from unittest.mock import Mock, patch, MagicMock - from osm_common import msgbase from osm_common.dbbase import DbException from osm_lcm.vim_sdn import K8sClusterLcm, VcaLcm diff --git a/osm_lcm/tests/test_vim_workflows.py b/osm_lcm/tests/test_vim_workflows.py index e48b94c..9c43895 100644 --- a/osm_lcm/tests/test_vim_workflows.py +++ b/osm_lcm/tests/test_vim_workflows.py @@ -15,6 +15,9 @@ # limitations under the License. import asynctest +from datetime import timedelta +from unittest.mock import Mock + from osm_common.dataclasses.temporal_dataclasses import ( DeleteVimInput, TestVimConnectivityInput, @@ -44,7 +47,9 @@ from temporalio import activity from temporalio.client import WorkflowFailureError from temporalio.testing import WorkflowEnvironment from temporalio.worker import Worker -from unittest.mock import Mock + +TASK_TIMEOUT = timedelta(seconds=0.5) +EXECUTION_TIMEOUT = timedelta(seconds=1) class TestException(Exception): @@ -160,6 +165,8 @@ class TestVimWorkflow(TestVimWorkflowsBase): arg=self.vim_operation_input, id=self.worflow_id, task_queue=self.task_queue_name, + task_timeout=TASK_TIMEOUT, + execution_timeout=EXECUTION_TIMEOUT, ) self.check_vim_state_is_updated(expected_vim_state) self.check_vim_op_state_is_updated(expected_vim_op_state) @@ -180,6 +187,8 @@ class TestVimWorkflow(TestVimWorkflowsBase): arg=self.vim_operation_input, id=self.worflow_id, task_queue=self.task_queue_name, + task_timeout=TASK_TIMEOUT, + execution_timeout=EXECUTION_TIMEOUT, ) self.check_vim_state_is_updated(expected_vim_state) self.check_vim_op_state_is_updated(expected_vim_op_state) @@ -200,6 +209,8 @@ class TestVimWorkflow(TestVimWorkflowsBase): arg=self.vim_operation_input, id=self.worflow_id, task_queue=self.task_queue_name, + task_timeout=TASK_TIMEOUT, + execution_timeout=EXECUTION_TIMEOUT, ) self.check_vim_state_is_updated(expected_vim_state) self.check_vim_op_state_is_updated(expected_vim_op_state) @@ -221,6 +232,8 @@ class TestVimWorkflow(TestVimWorkflowsBase): arg=self.vim_operation_input, id=self.worflow_id, task_queue=self.task_queue_name, + task_timeout=TASK_TIMEOUT, + execution_timeout=EXECUTION_TIMEOUT, ) self.check_vim_state_is_updated(expected_vim_state) self.check_vim_op_state_is_updated(expected_vim_op_state) @@ -295,6 +308,8 @@ class TestVimDeleteWorkflow(TestVimWorkflowsBase): arg=self.vim_operation_input, id=self.worflow_id, task_queue=self.task_queue_name, + task_timeout=TASK_TIMEOUT, + execution_timeout=EXECUTION_TIMEOUT, ) self.assertIsNone(result) self.mock_delete_vim_record_tracker.assert_called_once() @@ -308,5 +323,7 @@ class TestVimDeleteWorkflow(TestVimWorkflowsBase): arg=self.vim_operation_input, id=self.worflow_id, task_queue=self.task_queue_name, + task_timeout=TASK_TIMEOUT, + execution_timeout=EXECUTION_TIMEOUT, ) self.assertIsNone(result) diff --git a/osm_lcm/tests/test_vnf_activities.py b/osm_lcm/tests/test_vnf_activities.py index 02fad62..0c346ee 100644 --- a/osm_lcm/tests/test_vnf_activities.py +++ b/osm_lcm/tests/test_vnf_activities.py @@ -14,19 +14,22 @@ # See the License for the specific language governing permissions and # limitations under the License. - import asynctest -from asyncio.exceptions import CancelledError from copy import deepcopy +from unittest import TestCase +from unittest.mock import Mock, patch + from osm_common.dataclasses.temporal_dataclasses import ( ChangeVnfInstantiationStateInput, ChangeVnfStateInput, GetTaskQueueInput, GetVimCloudInput, - GetVnfDetailsInput, - VnfInstantiateInput, + GetVnfDescriptorInput, + GetVnfRecordInput, + SetVnfModelInput, VnfInstantiationState, VnfState, + VduComputeConstraints, ) from osm_common.dbbase import DbException from osm_common.temporal_constants import ( @@ -34,29 +37,17 @@ from osm_common.temporal_constants import ( ) from osm_lcm.temporal.vnf_activities import VnfDbActivity, VnfOperations from temporalio.testing import ActivityEnvironment -from unittest.mock import Mock -vnfr_uuid = "d08d2da5-2120-476c-8538-deaeb4e88b3e" -model_name = "a-model-name" -vnf_instantiate_input = VnfInstantiateInput(vnfr_uuid=vnfr_uuid, model_name=model_name) + +vnfr_uuid = "9f472177-95c0-4335-b357-5cdc17a79965" +vnfd_uuid = "97784f19-d254-4252-946c-cf92d85443ca" +vim_uuid = "a64f7c6c-bc27-4ec8-b664-5500a3324eca" +model_name = "my-model-name" +set_vnf_model_input = SetVnfModelInput(vnfr_uuid=vnfr_uuid, model_name=model_name) cloud = "microk8s" -sample_vnfr = { - "_id": "9f472177-95c0-4335-b357-5cdc17a79965", - "id": "9f472177-95c0-4335-b357-5cdc17a79965", - "nsr-id-ref": "dcf4c922-5a73-41bf-a6ca-870c22d6336c", - "vnfd-ref": "jar_vnfd_scalable", - "vnfd-id": "f1b38eac-190c-485d-9a74-c6e169c929d8", - "vim-account-id": "9b0bedc3-ea8e-42fd-acc9-dd03f4dee73c", -} -sample_vnfd = { - "_id": "97784f19-d254-4252-946c-cf92d85443ca", - "id": "sol006-vnf", - "provider": "Canonical", - "product-name": "test-vnf", - "software-version": "1.0", -} +nsr_uuid = "583726d4-957d-47f5-8df5-199456f7afd0" sample_vim_record = { - "_id": "a64f7c6c-bc27-4ec8-b664-5500a3324eca", + "_id": vim_uuid, "name": "juju", "vim_type": "paas", "vim_url": "192.168.1.100:17070", @@ -70,6 +61,72 @@ sample_vim_record = { "ca_cert_content": "-----BEGIN-----", }, } +vim_account_id = "9b0bedc3-ea8e-42fd-acc9-dd03f4dee73c" +vdu_id = "hackfest_basic-VM" +vnf_index = "vnf-profile-id" +sample_vnfr = { + "_id": vnfr_uuid, + "id": vnfr_uuid, + "nsr-id-ref": "dcf4c922-5a73-41bf-a6ca-870c22d6336c", + "vnfd-ref": "jar_vnfd_scalable", + "vnfd-id": "f1b38eac-190c-485d-9a74-c6e169c929d8", + "vim-account-id": vim_account_id, + "namespace": model_name, + "member-vnf-index-ref": vnf_index, +} +vnf_config = { + "member-vnf-index": vnf_index, + "vdu": [ + { + "id": vdu_id, + "configurable-properties": { + "config::redirect-map": "https://osm.instantiation.params" + }, + } + ], +} +app_config = {"domain_name1": "osm.org", "domain_name2": "osm.com"} +config = [ + {"key": "config::domain_name1", "value": "osm.org"}, + {"key": "config::domain_name2", "value": "osm.com"}, + {"key": "track", "value": "latest"}, + {"key": "channel", "value": "stable"}, +] +vdu = { + "id": vdu_id, + "name": "hackfest_basic-VM", + "sw-image-desc": "ubuntu18.04", + "virtual-compute-desc": "hackfest_basic-VM-compute", + "virtual-storage-desc": ["hackfest_basic-VM-storage"], + "configurable-properties": config, +} +vnfd_id = "97784f19-d254-4252-946c-cf92d85443ca" +flavor_1 = { + "id": "compute-id-1", + "virtual-memory": {"size": "4"}, + "virtual-cpu": {"cpu-architecture": "x86", "num-virtual-cpu": 2}, +} +flavor_2 = { + "id": "compute-id-2", + "virtual-cpu": {"cpu-architecture": "x86", "num-virtual-cpu": 2}, +} +flavor_3 = { + "id": "compute-id-2", + "virtual-memory": {"size": "4"}, +} +sample_vnfd = { + "_id": vnfd_id, + "id": "sol006-vnf", + "provider": "Canonical", + "product-name": "test-vnf", + "software-version": "1.0", + "vdu": [vdu], + "virtual-compute-desc": [flavor_1, flavor_2], +} + + +class TestException(Exception): + pass class TestVnfDbActivity(asynctest.TestCase): @@ -78,7 +135,7 @@ class TestVnfDbActivity(asynctest.TestCase): self.env = ActivityEnvironment() self.vnf_db_activity = VnfDbActivity(self.db) - async def test_change_vnf_state_nominal_case(self): + async def test_change_vnf_state__successful__db_updated_as_expected(self): vnf_state = VnfState.STOPPED change_vnf_state_input = ChangeVnfStateInput(vnfr_uuid, vnf_state) await self.env.run( @@ -88,7 +145,7 @@ class TestVnfDbActivity(asynctest.TestCase): "vnfrs", {"_id": vnfr_uuid}, {"vnfState": vnf_state.name} ) - async def test_change_vnf_state_db_raises_exception(self): + async def test_change_vnf_state__failed__raises_db_exception(self): change_vnf_state_input = ChangeVnfStateInput(vnfr_uuid, VnfState.STARTED) self.db.set_one.side_effect = DbException("not found") with self.assertRaises(DbException): @@ -96,7 +153,9 @@ class TestVnfDbActivity(asynctest.TestCase): self.vnf_db_activity.change_vnf_state, change_vnf_state_input ) - async def test_change_vnf_instantiation_state_nominal_case(self): + async def test_change_vnf_instantiation_state__successful__db_updated_as_expected( + self, + ): instantiation_state = VnfInstantiationState.NOT_INSTANTIATED change_instantiation_input = ChangeVnfInstantiationStateInput( vnfr_uuid, instantiation_state @@ -111,28 +170,27 @@ class TestVnfDbActivity(asynctest.TestCase): {"instantiationState": instantiation_state.name}, ) - async def test_change_vnf_instantiation_state_db_raises_exception(self): + async def test_change_vnf_instantiation_state__failed__raises_db_exception(self): change_instantiation_input = ChangeVnfInstantiationStateInput( vnfr_uuid, VnfInstantiationState.INSTANTIATED ) self.db.set_one.side_effect = DbException("not found") with self.assertRaises(DbException): await self.env.run( - self.vnf_db_activity.change_vnf_state, change_instantiation_input + self.vnf_db_activity.change_vnf_instantiation_state, + change_instantiation_input, ) - async def test_set_vnf_model_nominal_case(self): - await self.env.run(self.vnf_db_activity.set_vnf_model, vnf_instantiate_input) + async def test_set_vnf_model__successful__db_updated_as_expected(self): + await self.env.run(self.vnf_db_activity.set_vnf_model, set_vnf_model_input) self.db.set_one.assert_called_with( "vnfrs", {"_id": vnfr_uuid}, {"namespace": model_name} ) - async def test_set_vnf_model_db_raises_exception(self): + async def test_set_vnf_model__failed__raises_db_exception(self): self.db.set_one.side_effect = DbException("not found") with self.assertRaises(DbException): - await self.env.run( - self.vnf_db_activity.set_vnf_model, vnf_instantiate_input - ) + await self.env.run(self.vnf_db_activity.set_vnf_model, set_vnf_model_input) class TestGetTaskQueue(asynctest.TestCase): @@ -142,7 +200,7 @@ class TestGetTaskQueue(asynctest.TestCase): self.env = ActivityEnvironment() self.get_task_queue_input = GetTaskQueueInput(vnfr_uuid=sample_vnfr["id"]) - async def test_activity_succeeded(self): + async def test_activity__succeeded__get_expected_task_queue(self): self.db.get_one.side_effect = [sample_vnfr, sample_vim_record] activity_result = await self.env.run( self.vnf_operations_instance.get_task_queue, @@ -150,7 +208,7 @@ class TestGetTaskQueue(asynctest.TestCase): ) self.assertEqual(activity_result.task_queue, LCM_TASK_QUEUE) - async def test_db_raises_exception(self): + async def test_activity__failed__raises_db_exception(self): self.db.get_one.side_effect = DbException("not found") with self.assertRaises(DbException): await self.env.run( @@ -158,7 +216,7 @@ class TestGetTaskQueue(asynctest.TestCase): self.get_task_queue_input, ) - async def test_invalid_task_queue(self): + async def test_activity__invalid_task_queue__raises_key_error(self): vim_record = deepcopy(sample_vim_record) vim_record["vim_type"] = "some-vim-type" self.db.get_one.side_effect = [sample_vnfr, vim_record] @@ -168,65 +226,51 @@ class TestGetTaskQueue(asynctest.TestCase): self.get_task_queue_input, ) - async def test_activity_cancelled(self): - self.env._cancelled = True - self.db.get_one.side_effect = [sample_vnfr, sample_vim_record] - with self.assertRaises(CancelledError): - activity_result = await self.env.run( - self.vnf_operations_instance.get_task_queue, - self.get_task_queue_input, - ) - self.assertEqual(activity_result, None) - -class TestVnfDetails(asynctest.TestCase): +class TestGetVnfDescriptor(asynctest.TestCase): async def setUp(self): self.db = Mock() self.vnf_operations_instance = VnfOperations(self.db) self.env = ActivityEnvironment() - async def test_activity_succeeded(self): - self.db.get_one.side_effect = [sample_vnfr, sample_vnfd] + async def test_activity__succeeded__get_expected_vnfd(self): + self.db.get_one.return_value = sample_vnfd activity_result = await self.env.run( - self.vnf_operations_instance.get_vnf_details, - GetVnfDetailsInput(vnfr_uuid=sample_vnfr["id"]), + self.vnf_operations_instance.get_vnf_descriptor, + GetVnfDescriptorInput(vnfd_uuid=vnfd_uuid), ) self.assertEqual(activity_result.vnfd, sample_vnfd) - self.assertEqual(activity_result.vnfr, sample_vnfr) - async def test_activity_failed_db_exception(self): + async def test_activity__failed__raises_db_exception(self): self.db.get_one.side_effect = DbException("Can not connect to Database.") - with self.assertRaises(DbException) as err: - activity_result = await self.env.run( - self.vnf_operations_instance.get_vnf_details, - GetVnfDetailsInput(vnfr_uuid=sample_vnfr["id"]), + with self.assertRaises(DbException): + await self.env.run( + self.vnf_operations_instance.get_vnf_descriptor, + GetVnfDescriptorInput(vnfd_uuid=vnfd_uuid), ) - self.assertEqual(activity_result, None) - self.assertEqual( - str(err.exception), "database exception Can not connect to Database." + + +class TestGetVnfRecord(asynctest.TestCase): + async def setUp(self): + self.db = Mock() + self.vnf_operations_instance = VnfOperations(self.db) + self.env = ActivityEnvironment() + + async def test_activity__succeeded__get_expected_vnfr(self): + self.db.get_one.return_value = sample_vnfr + activity_result = await self.env.run( + self.vnf_operations_instance.get_vnf_record, + GetVnfRecordInput(vnfr_uuid=vnfr_uuid), ) + self.assertEqual(activity_result.vnfr, sample_vnfr) - async def test_activity_failed_key_error(self): - vnfr = deepcopy(sample_vnfr) - vnfr.pop("vnfd-id") - self.db.get_one.side_effect = [vnfr, sample_vnfd] - with self.assertRaises(KeyError) as err: - activity_result = await self.env.run( - self.vnf_operations_instance.get_vnf_details, - GetVnfDetailsInput(vnfr_uuid=sample_vnfr["id"]), - ) - self.assertEqual(activity_result, None) - self.assertEqual(str(err.exception.args[0]), "vnfd-id") - - async def test_activity_cancelled(self): - self.env._cancelled = True - self.db.get_one.side_effect = [sample_vnfr, sample_vnfd] - with self.assertRaises(CancelledError): - activity_result = await self.env.run( - self.vnf_operations_instance.get_vnf_details, - GetVnfDetailsInput(vnfr_uuid=sample_vnfr["id"]), + async def test_activity__failed__raise_db_exception(self): + self.db.get_one.side_effect = DbException("Can not connect to Database.") + with self.assertRaises(DbException): + await self.env.run( + self.vnf_operations_instance.get_vnf_record, + GetVnfRecordInput(vnfr_uuid=vnfr_uuid), ) - self.assertEqual(activity_result, None) class TestGetVimCloud(asynctest.TestCase): @@ -235,7 +279,7 @@ class TestGetVimCloud(asynctest.TestCase): self.vnf_operations_instance = VnfOperations(self.db) self.env = ActivityEnvironment() - async def test_activity_succeeded(self): + async def test_activity__succeeded__get_vim_cloud(self): self.db.get_one.side_effect = [sample_vnfr, sample_vim_record] activity_result = await self.env.run( self.vnf_operations_instance.get_vim_cloud, @@ -243,7 +287,7 @@ class TestGetVimCloud(asynctest.TestCase): ) self.assertEqual(activity_result.cloud, cloud) - async def test_activity_vim_record_without_cloud(self): + async def test_activity__vim_record_without_cloud__no_cloud_info(self): vim_record = deepcopy(sample_vim_record) vim_record["config"].pop("cloud") self.db.get_one.side_effect = [sample_vnfr, vim_record] @@ -253,39 +297,205 @@ class TestGetVimCloud(asynctest.TestCase): ) self.assertEqual(activity_result.cloud, "") - async def test_activity_failed_db_exception(self): + async def test_activity__failed__raise_db_exception(self): self.db.get_one.side_effect = DbException("Can not connect to Database.") - with self.assertRaises(DbException) as err: - activity_result = await self.env.run( + with self.assertRaises(DbException): + await self.env.run( self.vnf_operations_instance.get_vim_cloud, GetVimCloudInput(vnfr_uuid=sample_vnfr["id"]), ) - self.assertEqual(activity_result, None) - self.assertEqual( - str(err.exception), "database exception Can not connect to Database." - ) - async def test_activity_failed_key_error(self): + async def test_activity__wrong_vim_record__raise_key_error(self): vim_record = deepcopy(sample_vim_record) vim_record.pop("config") self.db.get_one.side_effect = [sample_vnfr, vim_record] - with self.assertRaises(KeyError) as err: - activity_result = await self.env.run( + with self.assertRaises(KeyError): + await self.env.run( self.vnf_operations_instance.get_vim_cloud, GetVimCloudInput(vnfr_uuid=sample_vnfr["id"]), ) - self.assertEqual(activity_result, None) - self.assertEqual(str(err.exception.args[0]), "config") - async def test_activity_cancelled(self): - self.env._cancelled = True - self.db.get_one.side_effect = [sample_vnfr, sample_vim_record] - with self.assertRaises(CancelledError): - activity_result = await self.env.run( - self.vnf_operations_instance.get_vim_cloud, - GetVimCloudInput(vnfr_uuid=sample_vnfr["id"]), - ) - self.assertEqual(activity_result, None) + +class TestGetVduInstantiateInfoMethods(TestCase): + def test_get_flavor_details__successful__get_flavor(self): + compute_desc_id = "compute-id-1" + result = VnfOperations._get_flavor_details(compute_desc_id, sample_vnfd) + self.assertEqual(result, flavor_1) + + def test_get_flavor_details__empty_compute_desc__flavor_is_none(self): + compute_desc_id = "" + result = VnfOperations._get_flavor_details(compute_desc_id, sample_vnfd) + self.assertEqual(result, None) + + def test_get_flavor_details__compute_desc_not_found__flavor_is_none(self): + compute_desc_id = "compute-id-5" + result = VnfOperations._get_flavor_details(compute_desc_id, sample_vnfd) + self.assertEqual(result, None) + + def test_get_flavor_details__empty_vnfd__flavor_is_none(self): + compute_desc_id = "compute-id-5" + result = VnfOperations._get_flavor_details(compute_desc_id, {}) + self.assertEqual(result, None) + + def test_get_flavor_details__wrong_vnfd_format__flavor_is_none(self): + compute_desc_id = "compute-id-2" + sample_vnfd = { + "_id": vnfd_id, + "vdu": [vdu], + "virtual-compute-desc": [ + { + "virtual-memory": {"size": "4"}, + } + ], + } + result = VnfOperations._get_flavor_details(compute_desc_id, sample_vnfd) + self.assertEqual(result, None) + + @patch("osm_lcm.temporal.vnf_activities.VnfOperations._get_flavor_details") + def test_get_compute_constraints__succeeded__get_constraints( + self, mock_get_flavor_details + ): + mock_get_flavor_details.return_value = flavor_1 + result = VnfOperations.get_compute_constraints(vdu, sample_vnfd) + self.assertEqual(VduComputeConstraints(cores=2, mem=4), result) + + @patch("osm_lcm.temporal.vnf_activities.VnfOperations._get_flavor_details") + def test_get_compute_constraints__empty_flavor_details__no_constraints( + self, mock_get_flavor_details + ): + mock_get_flavor_details.return_value = {} + result = VnfOperations.get_compute_constraints(vdu, sample_vnfd) + self.assertEqual(VduComputeConstraints(cores=0, mem=0), result) + + @patch("osm_lcm.temporal.vnf_activities.VnfOperations._get_flavor_details") + def test_get_compute_constraints__flavor_details_is_none__no_constraints( + self, mock_get_flavor_details + ): + mock_get_flavor_details.return_value = None + result = VnfOperations.get_compute_constraints(vdu, sample_vnfd) + self.assertEqual(VduComputeConstraints(cores=0, mem=0), result) + + @patch("osm_lcm.temporal.vnf_activities.VnfOperations._get_flavor_details") + def test_get_compute_constraints__flavor_has_only_cpu__get_cpu_constraint( + self, mock_get_flavor_details + ): + mock_get_flavor_details.return_value = flavor_2 + result = VnfOperations.get_compute_constraints(vdu, sample_vnfd) + self.assertEqual(VduComputeConstraints(cores=2, mem=0), result) + + @patch("osm_lcm.temporal.vnf_activities.VnfOperations._get_flavor_details") + def test_get_compute_constraints__flavor_has_only_memory__get_memory_constraint( + self, mock_get_flavor_details + ): + mock_get_flavor_details.return_value = flavor_3 + result = VnfOperations.get_compute_constraints(vdu, sample_vnfd) + self.assertEqual(VduComputeConstraints(cores=0, mem=4), result) + + def test_list_to_dict__config_has_several_items__get_expected_dict(self): + config = [ + {"key": "config::domain_name1", "value": "osm.org"}, + {"key": "domain_name2", "value": "osm.com"}, + ] + result = VnfOperations._list_to_dict(config) + self.assertEqual( + result, {"config::domain_name1": "osm.org", "domain_name2": "osm.com"} + ) + + def test_list_to_dict__empty_input__get_empty_dict(self): + config = [] + result = VnfOperations._list_to_dict(config) + self.assertEqual(result, {}) + + def test_get_only_config_items__with_identifier__get_expected_config(self): + config = {"config::redirect-map": "https://osm.instantiation.params"} + result = VnfOperations._get_only_config_items(config) + self.assertEqual(result, {"redirect-map": "https://osm.instantiation.params"}) + + def test_get_only_config_items__without_identifier__no_config(self): + config = {"key": "domain_name1", "value": "osm.org"} + result = VnfOperations._get_only_config_items(config) + self.assertEqual(result, {}) + + def test_get_only_config_items__empty_input__no_config(self): + config = {} + result = VnfOperations._get_only_config_items(config) + self.assertEqual(result, {}) + + def test_get_vdu_instantiation_params__empty_vnf_config__get_params(self): + result = VnfOperations.get_vdu_instantiation_params(vdu_id, vnf_config) + self.assertEqual( + result, {"config::redirect-map": "https://osm.instantiation.params"} + ) + + def test_get_vdu_instantiation_params__empty_vnf_config__no_params(self): + result = VnfOperations.get_vdu_instantiation_params(vdu_id, {}) + self.assertEqual(result, {}) + + def test_get_vdu_instantiation_params__vdu_id_mismatch__no_params(self): + config = deepcopy(vnf_config) + config["vdu"][0]["id"] = "other-vdu-id" + result = VnfOperations.get_vdu_instantiation_params(vdu_id, config) + self.assertEqual(result, {}) + + def test_get_vdu_instantiation_params__empty_configurable_properties__no_params( + self, + ): + config = deepcopy(vnf_config) + config["vdu"][0]["configurable-properties"] = {} + result = VnfOperations.get_vdu_instantiation_params(vdu_id, config) + self.assertEqual(result, {}) + + @patch("osm_lcm.temporal.vnf_activities.VnfOperations._get_only_config_items") + @patch("osm_lcm.temporal.vnf_activities.VnfOperations._list_to_dict") + def test_get_application_config__instantiate_config_overrides_vdu_config__get_expected_application_config( + self, + mock_list_to_dict, + mock_get_only_config_items, + ): + vdu_instantiate_config = {"config::domain_name1": "osm.public"} + mock_get_only_config_items.side_effect = [ + { + "domain_name1": "osm.org", + "domain_name2": "osm.com", + }, + {"domain_name1": "osm.public"}, + ] + result = VnfOperations.get_application_config(vdu, vdu_instantiate_config) + self.assertEqual( + result, {"domain_name1": "osm.public", "domain_name2": "osm.com"} + ) + + @patch("osm_lcm.temporal.vnf_activities.VnfOperations._get_only_config_items") + @patch("osm_lcm.temporal.vnf_activities.VnfOperations._list_to_dict") + def test_get_application_config__empty_instantiate_config__get_descriptor_config( + self, + mock_list_to_dict, + mock_get_only_config_items, + ): + vdu_instantiate_config = {} + mock_get_only_config_items.side_effect = [ + { + "domain_name1": "osm.org", + "domain_name2": "osm.com", + }, + {}, + ] + result = VnfOperations.get_application_config(vdu, vdu_instantiate_config) + self.assertEqual(result, app_config) + + @patch("osm_lcm.temporal.vnf_activities.VnfOperations._get_only_config_items") + @patch("osm_lcm.temporal.vnf_activities.VnfOperations._list_to_dict") + def test_get_application_config__no_config__empty_application_config( + self, + mock_list_to_dict, + mock_get_only_config_items, + ): + vdu_instantiate_config = {} + vdu_copy = deepcopy(vdu) + vdu_copy["configurable-properties"] = [] + mock_get_only_config_items.side_effect = [{}, {}] + result = VnfOperations.get_application_config(vdu_copy, vdu_instantiate_config) + self.assertEqual(result, {}) if __name__ == "__main__": diff --git a/osm_lcm/tests/test_vnf_workflows.py b/osm_lcm/tests/test_vnf_workflows.py index 073b767..8601142 100644 --- a/osm_lcm/tests/test_vnf_workflows.py +++ b/osm_lcm/tests/test_vnf_workflows.py @@ -15,14 +15,11 @@ # limitations under the License. import asynctest +from asynctest.mock import patch from copy import deepcopy -from temporalio import activity -from temporalio import workflow -from temporalio.client import WorkflowFailureError -from temporalio.testing import WorkflowEnvironment -from temporalio.worker import Worker -from unittest import TestCase -from unittest.mock import Mock, patch, AsyncMock +from datetime import timedelta +from unittest.mock import Mock +import uuid from osm_common.dataclasses.temporal_dataclasses import ( ChangeVnfInstantiationStateInput, @@ -32,12 +29,16 @@ from osm_common.dataclasses.temporal_dataclasses import ( GetTaskQueueOutput, GetVimCloudInput, GetVimCloudOutput, - GetVnfDetailsInput, - GetVnfDetailsOutput, + GetVnfDescriptorInput, + GetVnfDescriptorOutput, + GetVnfRecordInput, + GetVnfRecordOutput, + SetVnfModelInput, VduComputeConstraints, VduInstantiateInput, VnfInstantiateInput, VnfInstantiationState, + VnfPrepareInput, VnfState, ) from osm_common.temporal_constants import ( @@ -45,27 +46,35 @@ from osm_common.temporal_constants import ( ACTIVITY_CHANGE_VNF_INSTANTIATION_STATE, ACTIVITY_GET_TASK_QUEUE, ACTIVITY_GET_VIM_CLOUD, - ACTIVITY_GET_VNF_DETAILS, + ACTIVITY_GET_VNF_DESCRIPTOR, + ACTIVITY_GET_VNF_RECORD, ACTIVITY_SEND_NOTIFICATION_FOR_VNF, ACTIVITY_SET_VNF_MODEL, LCM_TASK_QUEUE, WORKFLOW_VDU_INSTANTIATE, WORKFLOW_VNF_PREPARE, ) - from osm_lcm.temporal.vnf_workflows import VnfInstantiateWorkflow, VnfPrepareWorkflow +from temporalio import activity +from temporalio import workflow +from temporalio.client import WorkflowFailureError +from temporalio.exceptions import ( + ActivityError, + ChildWorkflowError, + RetryState, +) +from temporalio.testing import WorkflowEnvironment +from temporalio.worker import Worker + # The variables used in the tests model_name = "my-model-name" -wf_input = VnfInstantiateInput( - vnfr_uuid="86b53d92-4f5a-402e-8ac2-585ec6b64698", - model_name=model_name, -) cloud = "microk8s" -config = {} juju_task_queue = "juju_task_queue" vnfr_uuid = "9f472177-95c0-4335-b357-5cdc17a79965" vim_account_id = "9b0bedc3-ea8e-42fd-acc9-dd03f4dee73c" +vdu_id = "hackfest_basic-VM" +vnf_index = "vnf-profile-id" sample_vnfr = { "_id": vnfr_uuid, "id": vnfr_uuid, @@ -74,13 +83,38 @@ sample_vnfr = { "vnfd-id": "f1b38eac-190c-485d-9a74-c6e169c929d8", "vim-account-id": vim_account_id, "namespace": model_name, + "member-vnf-index-ref": vnf_index, +} +vnf_config = { + "member-vnf-index": vnf_index, + "vdu": [ + { + "id": vdu_id, + "configurable-properties": { + "config::redirect-map": "https://osm.instantiation.params" + }, + } + ], } +app_config = {"domain_name1": "osm.org", "domain_name2": "osm.com"} +config = [ + {"key": "config::domain_name1", "value": "osm.org"}, + {"key": "config::domain_name2", "value": "osm.com"}, + {"key": "track", "value": "latest"}, + {"key": "channel", "value": "stable"}, +] +wf_input = VnfInstantiateInput( + vnfr_uuid=vnfr_uuid, + model_name=model_name, + instantiation_config={}, +) vdu = { - "id": "hackfest_basic-VM", + "id": vdu_id, "name": "hackfest_basic-VM", - "sw-image-desc": "ubuntu18.04", + "sw-image-desc": "ubuntu20.04", "virtual-compute-desc": "hackfest_basic-VM-compute", "virtual-storage-desc": ["hackfest_basic-VM-storage"], + "configurable-properties": config, } vnfd_id = "97784f19-d254-4252-946c-cf92d85443ca" flavor_1 = { @@ -92,10 +126,6 @@ flavor_2 = { "id": "compute-id-2", "virtual-cpu": {"cpu-architecture": "x86", "num-virtual-cpu": 2}, } -flavor_3 = { - "id": "compute-id-2", - "virtual-memory": {"size": "4"}, -} sample_vnfd = { "_id": vnfd_id, "id": "sol006-vnf", @@ -104,6 +134,14 @@ sample_vnfd = { "software-version": "1.0", "vdu": [vdu], "virtual-compute-desc": [flavor_1, flavor_2], + "sw-image-desc": [ + { + "id": "ubuntu20.04", + "image": "ubuntu20.04", + "name": "ubuntu20.04", + "version": "1.0", + } + ], } sample_charm_info = CharmInfo(app_name="my-app", channel="latest", entity_url="my-url") sample_constraints = VduComputeConstraints(cores=1, mem=1) @@ -113,25 +151,53 @@ sample_vdu_instantiate_input = VduInstantiateInput( charm_info=sample_charm_info, constraints=sample_constraints, cloud=cloud, - config=config, + config={}, ) +sample_vdu_instantiate_wf_id_1 = "vdu-instantiate-workflow-id-1" +sample_vdu_instantiate_input_with_config = VduInstantiateInput( + vim_uuid=vim_account_id, + model_name=model_name, + charm_info=sample_charm_info, + constraints=sample_constraints, + cloud=cloud, + config=app_config, +) + SANDBOXED = False DEBUG_MODE = True +TASK_TIMEOUT = timedelta(seconds=0.5) +EXECUTION_TIMEOUT = timedelta(seconds=1) class TestException(Exception): pass +class GetApplicationConfigException(Exception): + pass + + +class GetVduInstantiationParamsException(Exception): + pass + + +class GetCharmInfoException(Exception): + pass + + +class GetComputeConstraintsException(Exception): + pass + + # Mock Activities @activity.defn(name=ACTIVITY_SET_VNF_MODEL) -async def mock_set_vnf_model(set_vnf_model_input: VnfInstantiateInput) -> None: +async def mock_set_vnf_model(set_vnf_model_input: SetVnfModelInput) -> None: pass @activity.defn(name=ACTIVITY_SET_VNF_MODEL) async def mock_set_vnf_model_raise_exception( - set_vnf_model_input: VnfInstantiateInput, + set_vnf_model_input: SetVnfModelInput, ) -> None: raise TestException(f"{ACTIVITY_SET_VNF_MODEL} failed.") @@ -157,25 +223,39 @@ async def mock_get_task_queue_raise_exception( raise TestException(f"{ACTIVITY_GET_TASK_QUEUE} failed.") -@activity.defn(name=ACTIVITY_GET_VNF_DETAILS) -async def mock_get_vnf_details( - get_vnf_details_input: GetVnfDetailsInput, -) -> GetVnfDetailsOutput: - return GetVnfDetailsOutput(vnfr=sample_vnfr, vnfd=sample_vnfd) +@activity.defn(name=ACTIVITY_GET_VNF_DESCRIPTOR) +async def mock_get_vnf_descriptor( + get_vnf_descriptor_input: GetVnfDescriptorInput, +) -> GetVnfDescriptorOutput: + return GetVnfDescriptorOutput(vnfd=sample_vnfd) -@activity.defn(name=ACTIVITY_GET_VNF_DETAILS) -async def mock_get_vnf_details_empty_output( - get_vnf_details_input: GetVnfDetailsInput, -) -> GetVnfDetailsOutput: - return GetVnfDetailsOutput(vnfr={}, vnfd={}) +@activity.defn(name=ACTIVITY_GET_VNF_DESCRIPTOR) +async def mock_get_vnf_descriptor_empty_output( + get_vnf_descriptor_input: GetVnfDescriptorInput, +) -> GetVnfDescriptorOutput: + return GetVnfDescriptorOutput(vnfd={}) -@activity.defn(name=ACTIVITY_GET_VNF_DETAILS) -async def mock_get_vnf_details_raise_exception( - get_vnf_details_input: GetVnfDetailsInput, -) -> GetVnfDetailsOutput: - raise TestException(f"{ACTIVITY_GET_VNF_DETAILS} failed.") +@activity.defn(name=ACTIVITY_GET_VNF_DESCRIPTOR) +async def mock_get_vnf_descriptor_raise_exception( + get_vnf_descriptor_input: GetVnfDescriptorInput, +) -> GetVnfDescriptorOutput: + raise TestException(f"{ACTIVITY_GET_VNF_DESCRIPTOR} failed.") + + +@activity.defn(name=ACTIVITY_GET_VNF_RECORD) +async def mock_get_vnf_record( + get_vnf_record_input: GetVnfRecordInput, +) -> GetVnfRecordOutput: + return GetVnfRecordOutput(vnfr=sample_vnfr) + + +@activity.defn(name=ACTIVITY_GET_VNF_RECORD) +async def mock_get_vnf_record_raise_exception( + get_vnf_record_input: GetVnfRecordInput, +) -> GetVnfRecordOutput: + raise TestException(f"{ACTIVITY_GET_VNF_RECORD} failed.") @activity.defn(name=ACTIVITY_GET_VIM_CLOUD) @@ -192,14 +272,30 @@ async def mock_get_vim_cloud_raise_exception( raise TestException(f"{ACTIVITY_GET_VIM_CLOUD} failed.") -# Mock Workflowa +# Mock Workflows @workflow.defn(name=WORKFLOW_VNF_PREPARE, sandboxed=SANDBOXED) class MockPrepareVnfWorkflow: @workflow.run - async def run(self, input: VnfInstantiateInput) -> None: + async def run(self, input: VnfPrepareInput) -> None: pass +@workflow.defn(name=WORKFLOW_VNF_PREPARE, sandboxed=SANDBOXED) +class MockPrepareVnfWorkflowFailed: + @workflow.run + async def run(self, input: VnfPrepareInput) -> None: + raise ChildWorkflowError( + message=f"{WORKFLOW_VNF_PREPARE} child workflow failed.", + namespace="default", + workflow_id="123", + run_id="1", + workflow_type=WORKFLOW_VNF_PREPARE, + initiated_event_id=0, + started_event_id=0, + retry_state=RetryState.NON_RETRYABLE_FAILURE, + ) + + @workflow.defn(name=WORKFLOW_VDU_INSTANTIATE, sandboxed=SANDBOXED) class MockVduInstantiateWorkflow: @workflow.run @@ -207,13 +303,29 @@ class MockVduInstantiateWorkflow: pass +@workflow.defn(name=WORKFLOW_VDU_INSTANTIATE, sandboxed=SANDBOXED) +class MockVduInstantiateWorkflowFailed: + @workflow.run + async def run(self, input: VduInstantiateInput) -> None: + raise ChildWorkflowError( + message=f"{WORKFLOW_VDU_INSTANTIATE} child workflow failed.", + namespace="default", + workflow_id="123", + run_id="1", + workflow_type=WORKFLOW_VDU_INSTANTIATE, + initiated_event_id=0, + started_event_id=0, + retry_state=RetryState.NON_RETRYABLE_FAILURE, + ) + + class TestVnfPrepareWorkflow(asynctest.TestCase): async def setUp(self): self.env = await WorkflowEnvironment.start_time_skipping() self.client = self.env.client self.task_queue = LCM_TASK_QUEUE - async def test_vnf_prepare_workflow_successful(self): + async def test_vnf_prepare_workflow__successful(self): async with self.env: async with Worker( self.client, @@ -227,9 +339,35 @@ class TestVnfPrepareWorkflow(asynctest.TestCase): arg=wf_input, id=f"{WORKFLOW_VNF_PREPARE}-{wf_input.vnfr_uuid}", task_queue=self.task_queue, + task_timeout=TASK_TIMEOUT, + execution_timeout=EXECUTION_TIMEOUT, ) - async def test_vnf_prepare_workflow_fails(self): + async def test_vnf_prepare_workflow__activity_set_vnf_model_fails__raise_activity_error( + self, + ): + async with self.env: + async with Worker( + self.client, + debug_mode=DEBUG_MODE, + task_queue=self.task_queue, + workflows=[VnfPrepareWorkflow], + activities=[mock_set_vnf_model_raise_exception], + ): + with self.assertRaises(WorkflowFailureError) as err: + await self.client.execute_workflow( + VnfPrepareWorkflow.run, + arg=wf_input, + id=f"{WORKFLOW_VNF_PREPARE}-{wf_input.vnfr_uuid}", + task_queue=self.task_queue, + task_timeout=TASK_TIMEOUT, + execution_timeout=EXECUTION_TIMEOUT, + ) + self.assertTrue(isinstance(err.exception.cause, ActivityError)) + + async def test_vnf_prepare_workflow__activity_set_vnf_model_fails__get_expected_error_details( + self, + ): async with self.env: async with Worker( self.client, @@ -244,11 +382,11 @@ class TestVnfPrepareWorkflow(asynctest.TestCase): arg=wf_input, id=f"{WORKFLOW_VNF_PREPARE}-{wf_input.vnfr_uuid}", task_queue=self.task_queue, + task_timeout=TASK_TIMEOUT, + execution_timeout=EXECUTION_TIMEOUT, ) - self.assertEqual(str(err.exception), "Workflow execution failed") self.assertEqual( - str(err.exception.cause.cause.message), - "set-vnf-model failed.", + str(err.exception.cause.cause), "TestException: set-vnf-model failed." ) @@ -275,55 +413,33 @@ class TestVnfInstantiateWorkflow(asynctest.TestCase): debug_mode=DEBUG_MODE, ) - async def execute_workflow(self): + async def execute_workflow(self, wf_input=wf_input): return await self.client.execute_workflow( VnfInstantiateWorkflow.run, - arg=VnfInstantiateInput(vnfr_uuid=vnfr_uuid, model_name="a-model-name"), - id="wf1", + arg=wf_input, + id=uuid.uuid4().hex, task_queue=self.task_queue, + task_timeout=TASK_TIMEOUT, + execution_timeout=EXECUTION_TIMEOUT, ) - def check_state_change_call_counts(self): - self.assertEqual(self.mock_change_vnf_instantiation_state_tracker.call_count, 2) - self.assertEqual(self.mock_change_vnf_state_tracker.call_count, 1) - self.assertEqual(self.mock_send_notification_for_vnf_tracker.call_count, 2) - - def workflow_is_succeeded(self): + def vnf_instantiation_state_is_updated(self, state): call_mock_change_vnf_instantiation_state_tracker = ( self.mock_change_vnf_instantiation_state_tracker.call_args_list ) - call_mock_change_vnf_state_tracker = ( - self.mock_change_vnf_state_tracker.call_args_list - ) self.assertEqual( call_mock_change_vnf_instantiation_state_tracker[1].args[0], - ChangeVnfInstantiationStateInput( - vnfr_uuid=vnfr_uuid, state=VnfInstantiationState.INSTANTIATED - ), - ) - self.assertEqual( - call_mock_change_vnf_state_tracker[0].args[0], - ChangeVnfStateInput(vnfr_uuid=vnfr_uuid, state=VnfState.STARTED), + ChangeVnfInstantiationStateInput(vnfr_uuid=vnfr_uuid, state=state), ) - def workflow_is_failed(self, error): - call_mock_change_vnf_instantiation_state_tracker = ( - self.mock_change_vnf_instantiation_state_tracker.call_args_list - ) + def vnf_state_is_updated(self, state): call_mock_change_vnf_state_tracker = ( self.mock_change_vnf_state_tracker.call_args_list ) - self.assertEqual( - call_mock_change_vnf_instantiation_state_tracker[1].args[0], - ChangeVnfInstantiationStateInput( - vnfr_uuid=vnfr_uuid, state=VnfInstantiationState.INSTANTIATED - ), - ) self.assertEqual( call_mock_change_vnf_state_tracker[0].args[0], - ChangeVnfStateInput(vnfr_uuid=vnfr_uuid, state=VnfState.STOPPED), + ChangeVnfStateInput(vnfr_uuid=vnfr_uuid, state=state), ) - self.assertEqual(str(error.exception), "Workflow execution failed") @activity.defn(name=ACTIVITY_CHANGE_VNF_STATE) async def mock_change_vnf_state(self, nf_state_input: ChangeVnfStateInput) -> None: @@ -355,34 +471,70 @@ class TestVnfInstantiateWorkflow(asynctest.TestCase): async def mock_send_notification_for_vnf(self, input) -> None: self.mock_send_notification_for_vnf_tracker() - @asynctest.mock.patch( - "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow.instantiate_vdus" + @patch( + "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow._get_vdu_instantiate_info" ) - async def test_vnf_instantiate_workflow_successful(self, mock_instantiate_vdus): + async def test_vnf_instantiate_workflow__successful__updates_vnf_instantiation_state( + self, mock_get_vdu_instantiate_info + ): + workflows = [ + VnfInstantiateWorkflow, + MockPrepareVnfWorkflow, + MockVduInstantiateWorkflow, + ] activities = [ mock_get_task_queue, self.mock_change_vnf_instantiation_state, self.mock_change_vnf_state, self.mock_send_notification_for_vnf, mock_set_vnf_model, - mock_get_vnf_details, + mock_get_vnf_descriptor, + mock_get_vnf_record, mock_get_vim_cloud, ] + mock_get_vdu_instantiate_info.return_value = ( + sample_vdu_instantiate_input_with_config, + sample_vdu_instantiate_wf_id_1, + ) async with self.env, self.get_worker( - self.task_queue, self.workflows, activities - ): + self.task_queue, workflows, activities + ), self.get_worker(self.task_queue, workflows, activities): await self.execute_workflow() - self.check_state_change_call_counts() - self.workflow_is_succeeded() - mock_instantiate_vdus.assert_called_once_with( - vnfr=sample_vnfr, vnfd=sample_vnfd, task_queue=LCM_TASK_QUEUE, cloud=cloud - ) + self.vnf_instantiation_state_is_updated(VnfInstantiationState.INSTANTIATED) - @asynctest.mock.patch( - "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow.instantiate_vdus" + @patch( + "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow._get_vdu_instantiate_info" ) - async def test_vnf_instantiate_workflow_change_vnf_instantiation_state_exception( - self, mock_instantiate_vdus + async def test_vnf_instantiate_workflow__successful__updates_vnf_state( + self, mock_get_vdu_instantiate_info + ): + workflows = [ + VnfInstantiateWorkflow, + MockPrepareVnfWorkflow, + MockVduInstantiateWorkflow, + ] + activities = [ + mock_get_task_queue, + self.mock_change_vnf_instantiation_state, + self.mock_change_vnf_state, + self.mock_send_notification_for_vnf, + mock_set_vnf_model, + mock_get_vnf_descriptor, + mock_get_vnf_record, + mock_get_vim_cloud, + ] + mock_get_vdu_instantiate_info.return_value = ( + sample_vdu_instantiate_input_with_config, + sample_vdu_instantiate_wf_id_1, + ) + async with self.env, self.get_worker( + self.task_queue, workflows, activities + ), self.get_worker(self.task_queue, workflows, activities): + await self.execute_workflow() + self.vnf_instantiation_state_is_updated(VnfState.STARTED) + + async def test_vnf_instantiate_workflow__activity_change_vnf_instantiation_state_failed__raise_activity_error( + self, ): activities = [ mock_get_task_queue, @@ -390,7 +542,8 @@ class TestVnfInstantiateWorkflow(asynctest.TestCase): self.mock_change_vnf_state, self.mock_send_notification_for_vnf, mock_set_vnf_model, - mock_get_vnf_details, + mock_get_vnf_descriptor, + mock_get_vnf_record, mock_get_vim_cloud, ] async with self.env, self.get_worker( @@ -398,30 +551,30 @@ class TestVnfInstantiateWorkflow(asynctest.TestCase): ): with self.assertRaises(WorkflowFailureError) as err: await self.execute_workflow() - self.assertEqual(self.mock_change_vnf_instantiation_state_tracker.call_count, 6) - self.assertEqual(self.mock_change_vnf_state_tracker.call_count, 0) - self.assertEqual(self.mock_send_notification_for_vnf_tracker.call_count, 0) - call_mock_change_vnf_instantiation_state_tracker = ( - self.mock_change_vnf_instantiation_state_tracker.call_args_list - ) - self.assertEqual( - call_mock_change_vnf_instantiation_state_tracker[1].args[0], - ChangeVnfInstantiationStateInput( - vnfr_uuid=vnfr_uuid, state=VnfInstantiationState.NOT_INSTANTIATED - ), - ) - mock_instantiate_vdus.assert_not_called() - self.assertEqual(str(err.exception), "Workflow execution failed") - self.assertEqual( - str(err.exception.cause.cause.message), - "change-vnf-instantiation-state failed.", - ) + self.assertTrue(isinstance(err.exception.cause, ActivityError)) - @asynctest.mock.patch( - "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow.instantiate_vdus" - ) - async def test_vnf_instantiate_workflow_change_vnf_state_exception( - self, mock_instantiate_vdus + async def test_vnf_instantiate_workflow__activity_change_vnf_instantiation_state_failed__vnf_state_not_updated( + self, + ): + activities = [ + mock_get_task_queue, + self.mock_change_vnf_instantiation_state_exception, + self.mock_change_vnf_state, + self.mock_send_notification_for_vnf, + mock_set_vnf_model, + mock_get_vnf_descriptor, + mock_get_vnf_record, + mock_get_vim_cloud, + ] + async with self.env, self.get_worker( + self.task_queue, self.workflows, activities + ): + with self.assertRaises(WorkflowFailureError): + await self.execute_workflow() + self.mock_change_vnf_state_tracker.assert_not_called() + + async def test_vnf_instantiate_workflow__change_vnf_state_failed__raise_activity_error( + self, ): activities = [ mock_get_task_queue, @@ -429,7 +582,8 @@ class TestVnfInstantiateWorkflow(asynctest.TestCase): self.mock_change_vnf_state_exception, self.mock_send_notification_for_vnf, mock_set_vnf_model, - mock_get_vnf_details, + mock_get_vnf_descriptor, + mock_get_vnf_record, mock_get_vim_cloud, ] async with self.env, self.get_worker( @@ -437,447 +591,699 @@ class TestVnfInstantiateWorkflow(asynctest.TestCase): ): with self.assertRaises(WorkflowFailureError) as err: await self.execute_workflow() - self.assertEqual(self.mock_change_vnf_instantiation_state_tracker.call_count, 3) - self.assertEqual(self.mock_change_vnf_state_tracker.call_count, 6) - self.assertEqual(self.mock_send_notification_for_vnf_tracker.call_count, 1) - # using workflow_is_succeeded method since vnfInstantationState is being changed to INSTANTIATED and - # vnfState to STARTED (not STOPPED) - self.workflow_is_succeeded() - self.assertEqual( - str(err.exception.cause.cause.message), - "change-vnf-state failed.", - ) + self.assertTrue(isinstance(err.exception.cause, ActivityError)) - @asynctest.mock.patch( - "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow.instantiate_vdus" - ) - async def test_vnf_instantiate_workflow_empty_vnf_details( - self, mock_instantiate_vdus + async def test_vnf_instantiate_workflow__get_task_queue_failed__raises_activity_error( + self, ): activities = [ - mock_get_task_queue, + mock_get_task_queue_raise_exception, self.mock_change_vnf_instantiation_state, self.mock_change_vnf_state, self.mock_send_notification_for_vnf, mock_set_vnf_model, - mock_get_vnf_details_empty_output, + mock_get_vnf_descriptor, + mock_get_vnf_record, mock_get_vim_cloud, ] async with self.env, self.get_worker( self.task_queue, self.workflows, activities ): - await self.execute_workflow() - self.check_state_change_call_counts() - self.workflow_is_succeeded() - mock_instantiate_vdus.assert_called_once_with( - vnfr={}, vnfd={}, task_queue=LCM_TASK_QUEUE, cloud=cloud - ) + with self.assertRaises(WorkflowFailureError) as err: + await self.execute_workflow() + self.assertTrue(isinstance(err.exception.cause, ActivityError)) - @asynctest.mock.patch( - "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow.instantiate_vdus" - ) - async def test_vnf_instantiate_workflow_get_task_queue_raises_exception( - self, mock_instantiate_vdus + async def test_vnf_instantiate_workflow__wf_vnf_prepare_failed__raises_child_wf_error( + self, ): activities = [ - mock_get_task_queue_raise_exception, + mock_get_task_queue, self.mock_change_vnf_instantiation_state, self.mock_change_vnf_state, self.mock_send_notification_for_vnf, mock_set_vnf_model, - mock_get_vnf_details, + mock_get_vnf_descriptor, + mock_get_vnf_record, mock_get_vim_cloud, ] async with self.env, self.get_worker( - self.task_queue, self.workflows, activities + self.task_queue, + [ + VnfInstantiateWorkflow, + MockPrepareVnfWorkflowFailed, + MockVduInstantiateWorkflow, + ], + activities, ): with self.assertRaises(WorkflowFailureError) as err: await self.execute_workflow() - self.check_state_change_call_counts() - self.workflow_is_failed(err) - mock_instantiate_vdus.assert_not_called() - self.assertEqual( - str(err.exception.cause.cause.message), - "get_task_queue failed.", - ) + self.assertTrue(isinstance(err.exception.cause.cause, ChildWorkflowError)) - @asynctest.mock.patch( - "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow.instantiate_vdus" - ) - async def test_vnf_instantiate_workflow_get_vnf_details_raises_exception( - self, mock_instantiate_vdus + async def test_vnf_instantiate_workflow__wf_vdu_instantiate_failed__raises_child_wf_error( + self, ): - workflows = [VnfInstantiateWorkflow, MockPrepareVnfWorkflow] activities = [ mock_get_task_queue, self.mock_change_vnf_instantiation_state, self.mock_change_vnf_state, self.mock_send_notification_for_vnf, mock_set_vnf_model, - mock_get_vnf_details_raise_exception, + mock_get_vnf_descriptor, + mock_get_vnf_record, mock_get_vim_cloud, ] - async with self.env, self.get_worker(self.task_queue, workflows, activities): + async with self.env, self.get_worker( + self.task_queue, + [ + VnfInstantiateWorkflow, + MockPrepareVnfWorkflow, + MockVduInstantiateWorkflowFailed, + ], + activities, + ): with self.assertRaises(WorkflowFailureError) as err: await self.execute_workflow() - self.check_state_change_call_counts() - self.workflow_is_failed(err) - mock_instantiate_vdus.assert_not_called() - self.assertEqual( - str(err.exception.cause.cause.message), - "get_vnf_details failed.", - ) + self.assertTrue(isinstance(err.exception.cause, ChildWorkflowError)) - @asynctest.mock.patch( - "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow.instantiate_vdus" - ) - async def test_vnf_instantiate_workflow_get_vim_cloud_raises_exception( - self, mock_instantiate_vdus + async def test_vnf_instantiate_workflow__get_vnf_descriptor_failed__raises_activity_error( + self, ): - workflows = [VnfInstantiateWorkflow, MockPrepareVnfWorkflow] activities = [ mock_get_task_queue, self.mock_change_vnf_instantiation_state, self.mock_change_vnf_state, self.mock_send_notification_for_vnf, mock_set_vnf_model, - mock_get_vnf_details, - mock_get_vim_cloud_raise_exception, + mock_get_vnf_descriptor_raise_exception, + mock_get_vnf_record, + mock_get_vim_cloud, ] - async with self.env, self.get_worker(self.task_queue, workflows, activities): + async with self.env, self.get_worker( + self.task_queue, self.workflows, activities + ): with self.assertRaises(WorkflowFailureError) as err: await self.execute_workflow() - self.check_state_change_call_counts() - self.workflow_is_failed(err) - mock_instantiate_vdus.assert_not_called() - self.assertEqual( - str(err.exception.cause.cause.message), - "get-vim-cloud failed.", - ) + self.assertTrue(isinstance(err.exception.cause, ActivityError)) - @asynctest.mock.patch( - "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow.get_vdu_instantiate_input" - ) - async def test_vnf_instantiate_workflow_calls_vdu_instantiate_workflow( - self, mock_vdu_instantiate_input + async def test_vnf_instantiate_workflow__get_vnf_record_failed__raises_activity_error( + self, ): - mock_vdu_instantiate_input.return_value = ( - sample_vdu_instantiate_input, - "vdu-instantiate-workflow-id", - ) activities = [ mock_get_task_queue, self.mock_change_vnf_instantiation_state, self.mock_change_vnf_state, self.mock_send_notification_for_vnf, mock_set_vnf_model, - mock_get_vnf_details, + mock_get_vnf_descriptor, + mock_get_vnf_record_raise_exception, mock_get_vim_cloud, ] async with self.env, self.get_worker( self.task_queue, self.workflows, activities ): + with self.assertRaises(WorkflowFailureError) as err: + await self.execute_workflow() + self.assertTrue(isinstance(err.exception.cause, ActivityError)) + + async def test_vnf_instantiate_workflow__get_vim_cloud_failed__raises_activity_error( + self, + ): + activities = [ + mock_get_task_queue, + self.mock_change_vnf_instantiation_state, + self.mock_change_vnf_state, + self.mock_send_notification_for_vnf, + mock_set_vnf_model, + mock_get_vnf_descriptor, + mock_get_vnf_record, + mock_get_vim_cloud_raise_exception, + ] + async with self.env, self.get_worker( + self.task_queue, self.workflows, activities + ): + with self.assertRaises(WorkflowFailureError) as err: + await self.execute_workflow() + self.assertTrue(isinstance(err.exception.cause, ActivityError)) + + @patch("osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow.instantiate_vdus") + async def test_vnf_instantiate_workflow__use_different_task_queue__use_juju_task_queue( + self, mock_instantiate_vdus + ): + workflows = [VnfInstantiateWorkflow, MockPrepareVnfWorkflow] + activities = [ + mock_get_different_task_queue, + self.mock_change_vnf_instantiation_state, + self.mock_change_vnf_state, + self.mock_send_notification_for_vnf, + mock_set_vnf_model, + mock_get_vnf_descriptor, + mock_get_vnf_record, + mock_get_vim_cloud, + ] + async with self.env, self.get_worker( + self.task_queue, workflows, activities + ), self.get_worker(juju_task_queue, workflows, activities): await self.execute_workflow() - self.check_state_change_call_counts() - self.workflow_is_succeeded() - call_mock_vdu_instantiate_input = mock_vdu_instantiate_input.call_args_list - self.assertEqual(call_mock_vdu_instantiate_input[0][1]["vnfr"], sample_vnfr) - self.assertEqual(call_mock_vdu_instantiate_input[0][1]["vnfd"], sample_vnfd) - self.assertEqual(call_mock_vdu_instantiate_input[0][1]["vdu"], vdu) + mock_instantiate_vdus.assert_called_once_with( + vnfr=sample_vnfr, + vnfd=sample_vnfd, + task_queue=juju_task_queue, + cloud=cloud, + vnf_instantiation_config={}, + ) - @asynctest.mock.patch( - "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow.get_vdu_instantiate_input" + +class TestInstantiateVdus(asynctest.TestCase): + @patch( + "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow._get_vdu_instantiate_info" ) - @asynctest.mock.patch("temporalio.workflow.execute_child_workflow") - async def test_instantiate_vdus_single_vdu( - self, mock_execute_child_workflow: AsyncMock, mock_get_vdu_instantiate_input + @patch("temporalio.workflow.execute_child_workflow") + async def test_instantiate_vdus__empty_vnf_instantiation_config__child_wf_executed_with_expected_vdu_instantiate_input( + self, + mock_execute_child_workflow, + mock_get_vdu_instantiate_info, ): - mock_get_vdu_instantiate_input.return_value = ( + mock_get_vdu_instantiate_info.return_value = ( sample_vdu_instantiate_input, - "vdu-instantiate-workflow-id", + sample_vdu_instantiate_wf_id_1, ) await VnfInstantiateWorkflow.instantiate_vdus( - sample_vnfr, sample_vnfd, LCM_TASK_QUEUE, cloud - ) - self.assertEqual(mock_execute_child_workflow.call_count, 1) - mock_execute_child_workflow.assert_called_once_with( - workflow=WORKFLOW_VDU_INSTANTIATE, - arg=sample_vdu_instantiate_input, - task_queue=LCM_TASK_QUEUE, - id="vdu-instantiate-workflow-id", + sample_vnfr, sample_vnfd, LCM_TASK_QUEUE, cloud, {} ) - mock_get_vdu_instantiate_input.assert_called_once_with( - vnfr=sample_vnfr, vnfd=sample_vnfd, vdu=vdu, cloud=cloud + call_mock_execute_child_workflow = mock_execute_child_workflow.call_args_list + self.assertEqual( + call_mock_execute_child_workflow[0].kwargs, + { + "arg": sample_vdu_instantiate_input, + "id": sample_vdu_instantiate_wf_id_1, + "task_queue": LCM_TASK_QUEUE, + "workflow": "vdu-instantiate", + }, ) - @asynctest.mock.patch( - "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow.get_vdu_instantiate_input" + @patch( + "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow._get_vdu_instantiate_info" ) - @asynctest.mock.patch("temporalio.workflow.execute_child_workflow") - async def test_instantiate_vdus_multiple_vdus( - self, mock_execute_child_workflow: AsyncMock, mock_get_vdu_instantiate_input + @patch("temporalio.workflow.execute_child_workflow") + async def test_instantiate_vdus__vnf_instantiation_config_exists__child_wf_executed_with_expected_vdu_instantiate_input( + self, + mock_execute_child_workflow, + mock_get_vdu_instantiate_info, ): - sample_vnfd_multi_vdu = deepcopy(sample_vnfd) - sample_vnfd_multi_vdu["vdu"] = [vdu, vdu] - mock_get_vdu_instantiate_input.side_effect = [ - (sample_vdu_instantiate_input, "vdu-instantiate-workflow-id-1"), - (sample_vdu_instantiate_input, "vdu-instantiate-workflow-id-2"), - ] + mock_get_vdu_instantiate_info.return_value = ( + sample_vdu_instantiate_input_with_config, + sample_vdu_instantiate_wf_id_1, + ) await VnfInstantiateWorkflow.instantiate_vdus( - sample_vnfr, sample_vnfd_multi_vdu, LCM_TASK_QUEUE, cloud + sample_vnfr, sample_vnfd, LCM_TASK_QUEUE, cloud, vnf_config ) - self.assertEqual(mock_execute_child_workflow.call_count, 2) call_mock_execute_child_workflow = mock_execute_child_workflow.call_args_list self.assertEqual( - call_mock_execute_child_workflow[0][1], + call_mock_execute_child_workflow[0].kwargs, { - "arg": sample_vdu_instantiate_input, - "id": "vdu-instantiate-workflow-id-1", - "task_queue": "lcm-task-queue", + "arg": sample_vdu_instantiate_input_with_config, + "id": sample_vdu_instantiate_wf_id_1, + "task_queue": LCM_TASK_QUEUE, "workflow": "vdu-instantiate", }, ) - self.assertEqual( - call_mock_execute_child_workflow[1][1], - { - "arg": sample_vdu_instantiate_input, - "id": "vdu-instantiate-workflow-id-2", - "task_queue": "lcm-task-queue", - "workflow": "vdu-instantiate", - }, + + @patch( + "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow._get_vdu_instantiate_info" + ) + @patch("temporalio.workflow.execute_child_workflow") + async def test_instantiate_vdus__vnfd_without_vdu__child_wf_is_not_executed( + self, + mock_execute_child_workflow, + mock_get_vdu_instantiate_info, + ): + vnfd = deepcopy(sample_vnfd) + vnfd["vdu"] = [] + await VnfInstantiateWorkflow.instantiate_vdus( + sample_vnfr, vnfd, LCM_TASK_QUEUE, cloud, {} ) - call_mock_get_vdu_instantiate_input = ( - mock_get_vdu_instantiate_input.call_args_list + mock_execute_child_workflow.assert_not_called() + + @patch( + "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow._get_vdu_instantiate_info" + ) + @patch("temporalio.workflow.execute_child_workflow") + async def test_instantiate_vdus__use_juju_task_queue__child_wf_executed_with_juju_task_queue( + self, + mock_execute_child_workflow, + mock_get_vdu_instantiate_info, + ): + mock_get_vdu_instantiate_info.return_value = ( + sample_vdu_instantiate_input_with_config, + sample_vdu_instantiate_wf_id_1, ) - self.assertEqual( - call_mock_get_vdu_instantiate_input[0][1], - { - "vnfr": sample_vnfr, - "vnfd": sample_vnfd_multi_vdu, - "vdu": vdu, - "cloud": cloud, - }, + await VnfInstantiateWorkflow.instantiate_vdus( + sample_vnfr, sample_vnfd, juju_task_queue, cloud, vnf_config ) + call_mock_execute_child_workflow = mock_execute_child_workflow.call_args_list self.assertEqual( - call_mock_get_vdu_instantiate_input[1][1], + call_mock_execute_child_workflow[0].kwargs, { - "vnfr": sample_vnfr, - "vnfd": sample_vnfd_multi_vdu, - "vdu": vdu, - "cloud": cloud, + "arg": sample_vdu_instantiate_input_with_config, + "id": sample_vdu_instantiate_wf_id_1, + "task_queue": juju_task_queue, + "workflow": "vdu-instantiate", }, ) + +class TestGetVduInstantiateInfo(asynctest.TestCase): @asynctest.mock.patch( - "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow.instantiate_vdus" + "osm_lcm.temporal.vnf_activities.VnfOperations.get_application_config" ) - async def test_vnf_instantiate_workflow_successful_use_different_task_queue( - self, mock_instantiate_vdus - ): - workflows = [VnfInstantiateWorkflow, MockPrepareVnfWorkflow] - activities = [ - mock_get_different_task_queue, - self.mock_change_vnf_instantiation_state, - self.mock_change_vnf_state, - self.mock_send_notification_for_vnf, - mock_set_vnf_model, - mock_get_vnf_details, - mock_get_vim_cloud, - ] - async with self.env, self.get_worker( - self.task_queue, workflows, activities - ), self.get_worker(juju_task_queue, workflows, activities): - await self.execute_workflow() - self.check_state_change_call_counts() - self.workflow_is_succeeded() - mock_instantiate_vdus.assert_called_once_with( - vnfr=sample_vnfr, vnfd=sample_vnfd, task_queue=juju_task_queue, cloud=cloud - ) - - -class TestGetVduInstantiateInputMethods(TestCase): - def test_get_flavor_details_successful(self): - compute_desc_id = "compute-id-1" - result = VnfInstantiateWorkflow.get_flavor_details(compute_desc_id, sample_vnfd) - self.assertEqual(result, flavor_1) - - def test_get_flavor_details_empty_compute_desc(self): - compute_desc_id = "" - result = VnfInstantiateWorkflow.get_flavor_details(compute_desc_id, sample_vnfd) - self.assertEqual(result, {}) - - def test_get_flavor_details_compute_desc_not_found(self): - compute_desc_id = "compute-id-5" - result = VnfInstantiateWorkflow.get_flavor_details(compute_desc_id, sample_vnfd) - self.assertEqual(result, {}) - - def test_get_flavor_details_empty_vnfd(self): - compute_desc_id = "compute-id-5" - result = VnfInstantiateWorkflow.get_flavor_details(compute_desc_id, {}) - self.assertEqual(result, {}) - - def test_get_flavor_details_wrong_vnfd_format(self): - compute_desc_id = "compute-id-2" - sample_vnfd = { - "_id": vnfd_id, - "vdu": [vdu], - "virtual-compute-desc": [ - { - "virtual-memory": {"size": "4"}, - } - ], - } - result = VnfInstantiateWorkflow.get_flavor_details(compute_desc_id, sample_vnfd) - self.assertEqual(result, {}) - - @patch("osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow.get_flavor_details") - def test_get_compute_constraints(self, mock_get_flavor_details): - mock_get_flavor_details.return_value = flavor_1 - result = VnfInstantiateWorkflow.get_compute_constraints(vdu, sample_vnfd) - self.assertEqual(VduComputeConstraints(cores=2, mem=4), result) - - @patch("osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow.get_flavor_details") - def test_get_compute_constraints_empty_flavor_details( - self, mock_get_flavor_details - ): - mock_get_flavor_details.return_value = {} - result = VnfInstantiateWorkflow.get_compute_constraints(vdu, sample_vnfd) - self.assertEqual(VduComputeConstraints(cores=0, mem=0), result) - - @patch("osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow.get_flavor_details") - def test_get_compute_constraints_flavor_details_is_none( - self, mock_get_flavor_details - ): - mock_get_flavor_details.return_value = None - result = VnfInstantiateWorkflow.get_compute_constraints(vdu, sample_vnfd) - self.assertEqual(VduComputeConstraints(cores=0, mem=0), result) - - @patch("osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow.get_flavor_details") - def test_get_compute_constraints_flavor_has_only_cpu(self, mock_get_flavor_details): - mock_get_flavor_details.return_value = flavor_2 - result = VnfInstantiateWorkflow.get_compute_constraints(vdu, sample_vnfd) - self.assertEqual(VduComputeConstraints(cores=2, mem=0), result) - - @patch("osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow.get_flavor_details") - def test_get_compute_constraints_flavor_has_only_memory( - self, mock_get_flavor_details + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_vdu_instantiation_params" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.juju_paas_activities.CharmInfoUtils.get_charm_info" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_compute_constraints" + ) + async def test_get_vdu_instantiate_info__empty_vnf_instantiation_config__get_expected_vdu_inst_input_and_wf_id( + self, + mock_get_compute_constraints, + mock_get_charm_info, + mock_get_vdu_instantiation_params, + mock_get_application_config, ): - mock_get_flavor_details.return_value = flavor_3 - result = VnfInstantiateWorkflow.get_compute_constraints(vdu, sample_vnfd) - self.assertEqual(VduComputeConstraints(cores=0, mem=4), result) + mock_get_compute_constraints.return_value = sample_constraints + mock_get_charm_info.return_value = sample_charm_info + mock_get_vdu_instantiation_params.return_value = {} + mock_get_application_config.return_value = {} + ( + vdu_instantiate_input, + vdu_instantiate_workflow_id, + ) = VnfInstantiateWorkflow._get_vdu_instantiate_info( + vnfr=sample_vnfr, + vnfd=sample_vnfd, + vdu=vdu, + cloud=cloud, + vnf_instantiation_config={}, + ) + self.assertTupleEqual( + (vdu_instantiate_input, vdu_instantiate_workflow_id), + (sample_vdu_instantiate_input, "my-model-name-my-app"), + ) - @patch("osm_lcm.temporal.juju_paas_activities.CharmInfoUtils.get_charm_info") - @patch( - "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow.get_compute_constraints" + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_application_config" ) - def test_get_vdu_instantiate_input( - self, mock_get_compute_constraints, mock_get_charm_info + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_vdu_instantiation_params" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.juju_paas_activities.CharmInfoUtils.get_charm_info" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_compute_constraints" + ) + async def test_get_vdu_instantiate_info__with_vnf_instantiation_config__get_expected_vdu_inst_input_and_wf_id( + self, + mock_get_compute_constraints, + mock_get_charm_info, + mock_get_vdu_instantiation_params, + mock_get_application_config, ): mock_get_compute_constraints.return_value = sample_constraints mock_get_charm_info.return_value = sample_charm_info + mock_get_vdu_instantiation_params.return_value = config + mock_get_application_config.return_value = app_config ( vdu_instantiate_input, - vdu_instantiate_wf_id, - ) = VnfInstantiateWorkflow.get_vdu_instantiate_input( - sample_vnfr, sample_vnfd, vdu, cloud + vdu_instantiate_workflow_id, + ) = VnfInstantiateWorkflow._get_vdu_instantiate_info( + vnfr=sample_vnfr, + vnfd=sample_vnfd, + vdu=vdu, + cloud=cloud, + vnf_instantiation_config=vnf_config, + ) + self.assertTupleEqual( + (vdu_instantiate_input, vdu_instantiate_workflow_id), + (sample_vdu_instantiate_input_with_config, "my-model-name-my-app"), ) - self.assertEqual(vdu_instantiate_input, sample_vdu_instantiate_input) - self.assertEqual(vdu_instantiate_wf_id, "my-model-name-my-app") - mock_get_charm_info.assert_called_once() - mock_get_compute_constraints.assert_called_once() - @patch("osm_lcm.temporal.juju_paas_activities.CharmInfoUtils.get_charm_info") - @patch( - "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow.get_compute_constraints" + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_application_config" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_vdu_instantiation_params" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.juju_paas_activities.CharmInfoUtils.get_charm_info" ) - def test_get_vdu_instantiate_input_vnfr_without_namespace( - self, mock_get_compute_constraints, mock_get_charm_info + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_compute_constraints" + ) + async def test_get_vdu_instantiate_info__vnfr_without_namespace__raise_type_error( + self, + mock_get_compute_constraints, + mock_get_charm_info, + mock_get_vdu_instantiation_params, + mock_get_application_config, ): vnfr = deepcopy(sample_vnfr) vnfr.pop("namespace") mock_get_compute_constraints.return_value = sample_constraints mock_get_charm_info.return_value = sample_charm_info - with self.assertRaises(TypeError) as err: - VnfInstantiateWorkflow.get_vdu_instantiate_input( - vnfr, sample_vnfd, vdu, cloud + with self.assertRaises(TypeError): + VnfInstantiateWorkflow._get_vdu_instantiate_info( + vnfr=vnfr, + vnfd=sample_vnfd, + vdu=vdu, + cloud=cloud, + vnf_instantiation_config={}, ) - self.assertEqual( - str(err.exception), - "unsupported operand type(s) for +: 'NoneType' and 'str'", - ) - mock_get_charm_info.assert_called_once() - mock_get_compute_constraints.assert_called_once() - @patch("osm_lcm.temporal.juju_paas_activities.CharmInfoUtils.get_charm_info") - @patch( - "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow.get_compute_constraints" + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_application_config" ) - def test_get_vdu_instantiate_input_app_name_is_empty( - self, mock_get_compute_constraints, mock_get_charm_info + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_vdu_instantiation_params" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.juju_paas_activities.CharmInfoUtils.get_charm_info" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_compute_constraints" + ) + async def test_get_vdu_instantiate_info__app_name_is_empty__expected_wf_id( + self, + mock_get_compute_constraints, + mock_get_charm_info, + mock_get_vdu_instantiation_params, + mock_get_application_config, ): mock_get_compute_constraints.return_value = sample_constraints charm_info = CharmInfo(app_name="", channel="latest", entity_url="my-url") + mock_get_application_config.return_value = app_config mock_get_charm_info.return_value = charm_info ( - vdu_instantiate_input, - vdu_instantiate_wf_id, - ) = VnfInstantiateWorkflow.get_vdu_instantiate_input( - sample_vnfr, sample_vnfd, vdu, cloud + _, + vdu_instantiate_workflow_id, + ) = VnfInstantiateWorkflow._get_vdu_instantiate_info( + vnfr=sample_vnfr, + vnfd=sample_vnfd, + vdu=vdu, + cloud=cloud, + vnf_instantiation_config={}, ) - self.assertEqual( - vdu_instantiate_input, - VduInstantiateInput( - vim_uuid=vim_account_id, - model_name=model_name, - charm_info=charm_info, - constraints=sample_constraints, - cloud=cloud, - config=config, - ), + self.assertEqual(vdu_instantiate_workflow_id, "my-model-name-") + + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_application_config" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_vdu_instantiation_params" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.juju_paas_activities.CharmInfoUtils.get_charm_info" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_compute_constraints" + ) + async def test_get_vdu_instantiate_info__get_compute_constraints_failed__no_vdu_inst_wf_input_and_vdu_wf_id( + self, + mock_get_compute_constraints, + mock_get_charm_info, + mock_get_vdu_instantiation_params, + mock_get_application_config, + ): + mock_get_compute_constraints.side_effect = GetComputeConstraintsException( + "get_compute_constraints failed" ) - self.assertEqual(vdu_instantiate_wf_id, "my-model-name-") - mock_get_charm_info.assert_called_once() - mock_get_compute_constraints.assert_called_once() + mock_get_charm_info.return_value = sample_charm_info + with self.assertRaises(Exception): + ( + vdu_instantiate_input, + vdu_instantiate_workflow_id, + ) = VnfInstantiateWorkflow._get_vdu_instantiate_info( + vnfr=sample_vnfr, + vnfd=sample_vnfd, + vdu=vdu, + cloud=cloud, + vnf_instantiation_config={}, + ) + self.assertEqual( + (vdu_instantiate_input, vdu_instantiate_workflow_id), (None, None) + ) - @patch("osm_lcm.temporal.juju_paas_activities.CharmInfoUtils.get_charm_info") - @patch( - "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow.get_compute_constraints" + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_application_config" ) - def test_get_vdu_instantiate_input_get_compute_constraints_raises( - self, mock_get_compute_constraints, mock_get_charm_info + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_vdu_instantiation_params" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.juju_paas_activities.CharmInfoUtils.get_charm_info" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_compute_constraints" + ) + async def test_get_vdu_instantiate_info__get_compute_constraints_failed__raise_compute_constraints_exception( + self, + mock_get_compute_constraints, + mock_get_charm_info, + mock_get_vdu_instantiation_params, + mock_get_application_config, ): - mock_get_compute_constraints.side_effect = TestException( + mock_get_compute_constraints.side_effect = GetComputeConstraintsException( "get_compute_constraints failed" ) mock_get_charm_info.return_value = sample_charm_info - with self.assertRaises(TestException) as err: - result = VnfInstantiateWorkflow.get_vdu_instantiate_input( - sample_vnfr, sample_vnfd, vdu, cloud + with self.assertRaises(GetComputeConstraintsException): + VnfInstantiateWorkflow._get_vdu_instantiate_info( + vnfr=sample_vnfr, + vnfd=sample_vnfd, + vdu=vdu, + cloud=cloud, + vnf_instantiation_config={}, ) - self.assertEqual(result, None) - self.assertEqual(str(err.exception), "get_compute_constraints failed") - mock_get_charm_info.assert_called_once() - mock_get_compute_constraints.assert_called_once() - @patch("osm_lcm.temporal.juju_paas_activities.CharmInfoUtils.get_charm_info") - @patch( - "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow.get_compute_constraints" + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_application_config" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_vdu_instantiation_params" ) - def test_get_vdu_instantiate_input_get_charm_info_raises( - self, mock_get_compute_constraints, mock_get_charm_info + @asynctest.mock.patch( + "osm_lcm.temporal.juju_paas_activities.CharmInfoUtils.get_charm_info" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_compute_constraints" + ) + async def test_get_vdu_instantiate_info__get_charm_info_failed__raise_get_charm_info_exception( + self, + mock_get_compute_constraints, + mock_get_charm_info, + mock_get_vdu_instantiation_params, + mock_get_application_config, ): mock_get_compute_constraints.return_value = sample_constraints - mock_get_charm_info.side_effect = TestException( + mock_get_charm_info.side_effect = GetCharmInfoException( "get_compute_constraints failed" ) - with self.assertRaises(TestException) as err: - result = VnfInstantiateWorkflow.get_vdu_instantiate_input( - sample_vnfr, sample_vnfd, vdu, cloud + with self.assertRaises(GetCharmInfoException): + VnfInstantiateWorkflow._get_vdu_instantiate_info( + vnfr=sample_vnfr, + vnfd=sample_vnfd, + vdu=vdu, + cloud=cloud, + vnf_instantiation_config={}, + ) + + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_application_config" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_vdu_instantiation_params" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.juju_paas_activities.CharmInfoUtils.get_charm_info" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_compute_constraints" + ) + async def test_get_vdu_instantiate_info__get_charm_info_failed__no_vdu_inst_wf_input_and_vdu_wf_id( + self, + mock_get_compute_constraints, + mock_get_charm_info, + mock_get_vdu_instantiation_params, + mock_get_application_config, + ): + mock_get_compute_constraints.return_value = sample_constraints + mock_get_charm_info.side_effect = GetCharmInfoException( + "get_compute_constraints failed" + ) + with self.assertRaises(Exception): + ( + vdu_instantiate_input, + vdu_instantiate_workflow_id, + ) = VnfInstantiateWorkflow._get_vdu_instantiate_info( + vnfr=sample_vnfr, + vnfd=sample_vnfd, + vdu=vdu, + cloud=cloud, + vnf_instantiation_config={}, + ) + self.assertEqual( + (vdu_instantiate_input, vdu_instantiate_workflow_id), (None, None) + ) + + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_application_config" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_vdu_instantiation_params" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.juju_paas_activities.CharmInfoUtils.get_charm_info" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_compute_constraints" + ) + async def test_get_vdu_instantiate_info__get_vdu_instantiation_params_failed__raise_get_vdu_instantiation_params_exception( + self, + mock_get_compute_constraints, + mock_get_charm_info, + mock_get_vdu_instantiation_params, + mock_get_application_config, + ): + mock_get_compute_constraints.return_value = sample_constraints + mock_get_charm_info.return_value = sample_charm_info + mock_get_vdu_instantiation_params.side_effect = ( + GetVduInstantiationParamsException("get_vdu_instantiation_params failed") + ) + mock_get_application_config.return_value = {} + with self.assertRaises(GetVduInstantiationParamsException): + VnfInstantiateWorkflow._get_vdu_instantiate_info( + vnfr=sample_vnfr, + vnfd=sample_vnfd, + vdu=vdu, + cloud=cloud, + vnf_instantiation_config={}, + ) + + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_application_config" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_vdu_instantiation_params" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.juju_paas_activities.CharmInfoUtils.get_charm_info" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_compute_constraints" + ) + async def test_get_vdu_instantiate_info__get_vdu_instantiation_params_failed__no_vdu_inst_wf_input_and_vdu_wf_id( + self, + mock_get_compute_constraints, + mock_get_charm_info, + mock_get_vdu_instantiation_params, + mock_get_application_config, + ): + mock_get_compute_constraints.return_value = sample_constraints + mock_get_charm_info.return_value = sample_charm_info + mock_get_vdu_instantiation_params.side_effect = ( + GetVduInstantiationParamsException("get_vdu_instantiation_params failed") + ) + mock_get_application_config.return_value = {} + with self.assertRaises(Exception): + ( + vdu_instantiate_input, + vdu_instantiate_workflow_id, + ) = VnfInstantiateWorkflow._get_vdu_instantiate_info( + vnfr=sample_vnfr, + vnfd=sample_vnfd, + vdu=vdu, + cloud=cloud, + vnf_instantiation_config={}, + ) + + self.assertEqual( + (vdu_instantiate_input, vdu_instantiate_workflow_id), (None, None) + ) + + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_application_config" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_vdu_instantiation_params" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.juju_paas_activities.CharmInfoUtils.get_charm_info" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_compute_constraints" + ) + async def test_get_vdu_instantiate_info__get_application_config_failed__raise_get_application_config_exception( + self, + mock_get_compute_constraints, + mock_get_charm_info, + mock_get_vdu_instantiation_params, + mock_get_application_config, + ): + mock_get_compute_constraints.return_value = sample_constraints + mock_get_charm_info.return_value = sample_charm_info + mock_get_vdu_instantiation_params.return_value = config + mock_get_application_config.side_effect = GetApplicationConfigException( + "get_vdu_instantiation_params failed" + ) + with self.assertRaises(GetApplicationConfigException): + VnfInstantiateWorkflow._get_vdu_instantiate_info( + vnfr=sample_vnfr, + vnfd=sample_vnfd, + vdu=vdu, + cloud=cloud, + vnf_instantiation_config={}, + ) + + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_application_config" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_vdu_instantiation_params" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.juju_paas_activities.CharmInfoUtils.get_charm_info" + ) + @asynctest.mock.patch( + "osm_lcm.temporal.vnf_activities.VnfOperations.get_compute_constraints" + ) + async def test_get_vdu_instantiate_info__get_application_config_failed__no_vdu_inst_wf_input_and_vdu_wf_id( + self, + mock_get_compute_constraints, + mock_get_charm_info, + mock_get_vdu_instantiation_params, + mock_get_application_config, + ): + mock_get_compute_constraints.return_value = sample_constraints + mock_get_charm_info.return_value = sample_charm_info + mock_get_vdu_instantiation_params.return_value = config + mock_get_application_config.side_effect = GetApplicationConfigException( + "get_vdu_instantiation_params failed" + ) + with self.assertRaises(Exception): + ( + vdu_instantiate_input, + vdu_instantiate_workflow_id, + ) = VnfInstantiateWorkflow._get_vdu_instantiate_info( + vnfr=sample_vnfr, + vnfd=sample_vnfd, + vdu=vdu, + cloud=cloud, + vnf_instantiation_config={}, + ) + self.assertEqual( + (vdu_instantiate_input, vdu_instantiate_workflow_id), (None, None) ) - self.assertEqual(result, None) - self.assertEqual(str(err.exception), "get_compute_constraints failed") - mock_get_charm_info.assert_called_once() - mock_get_compute_constraints.assert_not_called() if __name__ == "__main__":