#######################################################################################
# Copyright ETSI Contributors and Others.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import asynctest
from asynctest.mock import patch
from copy import deepcopy
from datetime import timedelta
from unittest.mock import Mock
import uuid

from osm_common.dataclasses.temporal_dataclasses import (
    ChangeVnfInstantiationStateInput,
    ChangeVnfStateInput,
    CharmInfo,
    GetTaskQueueInput,
    GetTaskQueueOutput,
    GetVimCloudInput,
    GetVimCloudOutput,
    GetVnfDescriptorInput,
    GetVnfDescriptorOutput,
    GetVnfRecordInput,
    GetVnfRecordOutput,
    SetVnfModelInput,
    VduComputeConstraints,
    VduInstantiateInput,
    VnfInstantiateInput,
    VnfInstantiationState,
    VnfPrepareInput,
    VnfState,
)
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_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"
cloud = "microk8s"
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,
    "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"},
]
wf_input = VnfInstantiateInput(
    vnfr_uuid=vnfr_uuid,
    model_name=model_name,
    instantiation_config={},
)
vdu = {
    "id": vdu_id,
    "name": "hackfest_basic-VM",
    "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 = {
    "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},
}
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],
    "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)
sample_vdu_instantiate_input = VduInstantiateInput(
    vim_uuid=vim_account_id,
    model_name=model_name,
    charm_info=sample_charm_info,
    constraints=sample_constraints,
    cloud=cloud,
    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: SetVnfModelInput) -> None:
    pass


@activity.defn(name=ACTIVITY_SET_VNF_MODEL)
async def mock_set_vnf_model_raise_exception(
    set_vnf_model_input: SetVnfModelInput,
) -> None:
    raise TestException(f"{ACTIVITY_SET_VNF_MODEL} failed.")


@activity.defn(name=ACTIVITY_GET_TASK_QUEUE)
async def mock_get_task_queue(
    get_task_queue_input: GetTaskQueueInput,
) -> GetTaskQueueOutput:
    return GetTaskQueueOutput(LCM_TASK_QUEUE)


@activity.defn(name=ACTIVITY_GET_TASK_QUEUE)
async def mock_get_different_task_queue(
    get_task_queue_input: GetTaskQueueInput,
) -> GetTaskQueueOutput:
    return GetTaskQueueOutput(juju_task_queue)


@activity.defn(name=ACTIVITY_GET_TASK_QUEUE)
async def mock_get_task_queue_raise_exception(
    get_task_queue_input: GetTaskQueueInput,
) -> GetTaskQueueOutput:
    raise TestException(f"{ACTIVITY_GET_TASK_QUEUE} failed.")


@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_DESCRIPTOR)
async def mock_get_vnf_descriptor_empty_output(
    get_vnf_descriptor_input: GetVnfDescriptorInput,
) -> GetVnfDescriptorOutput:
    return GetVnfDescriptorOutput(vnfd={})


@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)
async def mock_get_vim_cloud(
    get_vim_cloud_input: GetVimCloudInput,
) -> GetVimCloudOutput:
    return GetVimCloudOutput(cloud=cloud)


@activity.defn(name=ACTIVITY_GET_VIM_CLOUD)
async def mock_get_vim_cloud_raise_exception(
    get_vim_cloud_input: GetVimCloudInput,
) -> GetVimCloudOutput:
    raise TestException(f"{ACTIVITY_GET_VIM_CLOUD} failed.")


# Mock Workflows
@workflow.defn(name=WORKFLOW_VNF_PREPARE, sandboxed=SANDBOXED)
class MockPrepareVnfWorkflow:
    @workflow.run
    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
    async def run(self, input: VduInstantiateInput) -> None:
        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 with self.env:
            async with Worker(
                self.client,
                debug_mode=DEBUG_MODE,
                task_queue=self.task_queue,
                workflows=[VnfPrepareWorkflow],
                activities=[mock_set_vnf_model],
            ):
                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,
                )

    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,
                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.assertEqual(
            str(err.exception.cause.cause), "TestException: set-vnf-model failed."
        )


class TestVnfInstantiateWorkflow(asynctest.TestCase):
    async def setUp(self):
        self.env = await WorkflowEnvironment.start_time_skipping()
        self.client = self.env.client
        self.mock_change_vnf_instantiation_state_tracker = Mock()
        self.mock_change_vnf_state_tracker = Mock()
        self.mock_send_notification_for_vnf_tracker = Mock()
        self.workflows = [
            VnfInstantiateWorkflow,
            MockPrepareVnfWorkflow,
            MockVduInstantiateWorkflow,
        ]
        self.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,
        )

    async def execute_workflow(self, wf_input=wf_input):
        return await self.client.execute_workflow(
            VnfInstantiateWorkflow.run,
            arg=wf_input,
            id=uuid.uuid4().hex,
            task_queue=self.task_queue,
            task_timeout=TASK_TIMEOUT,
            execution_timeout=EXECUTION_TIMEOUT,
        )

    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
        )
        self.assertEqual(
            call_mock_change_vnf_instantiation_state_tracker[1].args[0],
            ChangeVnfInstantiationStateInput(vnfr_uuid=vnfr_uuid, state=state),
        )

    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_state_tracker[0].args[0],
            ChangeVnfStateInput(vnfr_uuid=vnfr_uuid, state=state),
        )

    @activity.defn(name=ACTIVITY_CHANGE_VNF_STATE)
    async def mock_change_vnf_state(self, nf_state_input: ChangeVnfStateInput) -> None:
        self.mock_change_vnf_state_tracker(nf_state_input)

    @activity.defn(name=ACTIVITY_CHANGE_VNF_STATE)
    async def mock_change_vnf_state_exception(
        self, nf_state_input: ChangeVnfStateInput
    ) -> None:
        self.mock_change_vnf_state_tracker(nf_state_input)
        raise TestException(f"{ACTIVITY_CHANGE_VNF_STATE} failed.")

    @activity.defn(name=ACTIVITY_CHANGE_VNF_INSTANTIATION_STATE)
    async def mock_change_vnf_instantiation_state(
        self,
        nf_instantiation_state_input: ChangeVnfInstantiationStateInput,
    ) -> None:
        self.mock_change_vnf_instantiation_state_tracker(nf_instantiation_state_input)

    @activity.defn(name=ACTIVITY_CHANGE_VNF_INSTANTIATION_STATE)
    async def mock_change_vnf_instantiation_state_exception(
        self,
        nf_instantiation_state_input: ChangeVnfInstantiationStateInput,
    ) -> None:
        self.mock_change_vnf_instantiation_state_tracker(nf_instantiation_state_input)
        raise TestException(f"{ACTIVITY_CHANGE_VNF_INSTANTIATION_STATE} failed.")

    @activity.defn(name=ACTIVITY_SEND_NOTIFICATION_FOR_VNF)
    async def mock_send_notification_for_vnf(self, input) -> None:
        self.mock_send_notification_for_vnf_tracker()

    @patch(
        "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow._get_vdu_instantiate_info"
    )
    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_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(VnfInstantiationState.INSTANTIATED)

    @patch(
        "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow._get_vdu_instantiate_info"
    )
    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,
            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) as err:
                await self.execute_workflow()
            self.assertTrue(isinstance(err.exception.cause, ActivityError))

    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,
            self.mock_change_vnf_instantiation_state,
            self.mock_change_vnf_state_exception,
            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) as err:
                await self.execute_workflow()
            self.assertTrue(isinstance(err.exception.cause, ActivityError))

    async def test_vnf_instantiate_workflow__get_task_queue_failed__raises_activity_error(
        self,
    ):
        activities = [
            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_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) as err:
                await self.execute_workflow()
            self.assertTrue(isinstance(err.exception.cause, ActivityError))

    async def test_vnf_instantiate_workflow__wf_vnf_prepare_failed__raises_child_wf_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,
        ]
        async with self.env, self.get_worker(
            self.task_queue,
            [
                VnfInstantiateWorkflow,
                MockPrepareVnfWorkflowFailed,
                MockVduInstantiateWorkflow,
            ],
            activities,
        ):
            with self.assertRaises(WorkflowFailureError) as err:
                await self.execute_workflow()
            self.assertTrue(isinstance(err.exception.cause.cause, ChildWorkflowError))

    async def test_vnf_instantiate_workflow__wf_vdu_instantiate_failed__raises_child_wf_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,
        ]
        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.assertTrue(isinstance(err.exception.cause, ChildWorkflowError))

    async def test_vnf_instantiate_workflow__get_vnf_descriptor_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_raise_exception,
            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) as err:
                await self.execute_workflow()
            self.assertTrue(isinstance(err.exception.cause, ActivityError))

    async def test_vnf_instantiate_workflow__get_vnf_record_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_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()
        mock_instantiate_vdus.assert_called_once_with(
            vnfr=sample_vnfr,
            vnfd=sample_vnfd,
            task_queue=juju_task_queue,
            cloud=cloud,
            vnf_instantiation_config={},
        )


class TestInstantiateVdus(asynctest.TestCase):
    @patch(
        "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow._get_vdu_instantiate_info"
    )
    @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_info.return_value = (
            sample_vdu_instantiate_input,
            sample_vdu_instantiate_wf_id_1,
        )
        await VnfInstantiateWorkflow.instantiate_vdus(
            sample_vnfr, sample_vnfd, LCM_TASK_QUEUE, 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",
            },
        )

    @patch(
        "osm_lcm.temporal.vnf_workflows.VnfInstantiateWorkflow._get_vdu_instantiate_info"
    )
    @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,
    ):
        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, LCM_TASK_QUEUE, cloud, vnf_config
        )
        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_with_config,
                "id": sample_vdu_instantiate_wf_id_1,
                "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, {}
        )
        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,
        )
        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_execute_child_workflow[0].kwargs,
            {
                "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_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__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_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"),
        )

    @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__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_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"),
        )

    @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__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):
            VnfInstantiateWorkflow._get_vdu_instantiate_info(
                vnfr=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__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_workflow_id,
        ) = VnfInstantiateWorkflow._get_vdu_instantiate_info(
            vnfr=sample_vnfr,
            vnfd=sample_vnfd,
            vdu=vdu,
            cloud=cloud,
            vnf_instantiation_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"
        )
        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)
            )

    @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__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 = GetComputeConstraintsException(
            "get_compute_constraints failed"
        )
        mock_get_charm_info.return_value = sample_charm_info
        with self.assertRaises(GetComputeConstraintsException):
            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__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 = GetCharmInfoException(
            "get_compute_constraints failed"
        )
        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)
            )


if __name__ == "__main__":
    asynctest.main()
