diff --git a/coriolis/minion_manager/rpc/tasks.py b/coriolis/minion_manager/rpc/tasks.py index f52e9482a..a3daef2ab 100644 --- a/coriolis/minion_manager/rpc/tasks.py +++ b/coriolis/minion_manager/rpc/tasks.py @@ -245,7 +245,7 @@ def _get_task_name(self, action_id): def _confirm_machine_allocation_for_action( self, context, action_id, machine_allocations): raise NotImplementedError( - "No minion allocation confrimation operation defined") + "No minion allocation confirmation operation defined") def execute(self, context): machines_cache = {} @@ -646,7 +646,7 @@ def execute(self, context, origin, destination, task_info): raise exception.InvalidMinionPoolSelection( "[Task '%s'] Minion pool '%s' doesn't exist in the DB. " "It cannot have shared resources deployed for it." % ( - self._task_name)) + self._task_name, self._minion_pool_id)) if minion_pool.shared_resources: raise exception.InvalidMinionPoolState( "[Task '%s'] Minion pool already has shared resources " @@ -1096,7 +1096,7 @@ def execute(self, context, origin, destination, task_info): self._task_name, self._minion_machine_id) base_msg = ( "Could not find minion machine DB entry with ID '%s' for " - "healtcheck." % self._minion_machine_id) + "healthcheck." % self._minion_machine_id) self._add_minion_pool_event( context, "%s Reporting healthcheck as failed" % base_msg, @@ -1127,7 +1127,7 @@ def execute(self, context, origin, destination, task_info): self._add_minion_pool_event( context, - "Healthchecking minion machine with internal pool ID '%s'" % ( + "Healthchecking minion machine with internal pool ID '%s'" % ( self._minion_machine_id)) execution_info = { @@ -1138,7 +1138,7 @@ def execute(self, context, origin, destination, task_info): context, origin, destination, execution_info) self._add_minion_pool_event( context, - "Successfully healtchecked minion machine with internal " + "Successfully healthchecked minion machine with internal " "pool ID '%s'" % self._minion_machine_id) self._set_minion_machine_allocation_status( context, self._minion_pool_id, self._minion_machine_id, @@ -1146,11 +1146,11 @@ def execute(self, context, origin, destination, task_info): except Exception as ex: self._add_minion_pool_event( context, - "Healtcheck for machine with internal pool ID '%s' has " + "Healthcheck for machine with internal pool ID '%s' has " "failed." % (self._minion_machine_id), level=constants.TASK_EVENT_WARNING) LOG.debug( - "[Task '%s'] Healtcheck failed for machine '%s' of pool '%s'. " + "[Task '%s'] Healthcheck failed for machine '%s' of pool '%s'." "Full trace was:\n%s", self._task_name, self._minion_machine_id, self._minion_pool_id, utils.get_exception_details()) @@ -1191,7 +1191,7 @@ def __call__(self, history): HealthcheckMinionMachineTask.get_healthcheck_task_name( self._minion_pool_id, self._minion_machine_id)) - if not history and healthcheck_task_name not in history: + if not history or healthcheck_task_name not in history: LOG.warn( "Could not find healthceck result for minion machine '%s' " "of pool '%s' (task name '%s'). NOT greenlighting futher " @@ -1287,7 +1287,7 @@ def execute(self, context, origin, destination, task_info): context, "Exception occurred while powering on minion machine with " "internal pool ID '%s'. The minion machine will be marked " - "as ERROR'd and automatically redeployed later" % ( + "as ERROR'd and automatically redeployed later." % ( self._minion_machine_id), level=constants.TASK_EVENT_ERROR) self._set_minion_machine_allocation_status( diff --git a/coriolis/tests/minion_manager/rpc/test_client.py b/coriolis/tests/minion_manager/rpc/test_client.py new file mode 100644 index 000000000..c2e56936a --- /dev/null +++ b/coriolis/tests/minion_manager/rpc/test_client.py @@ -0,0 +1,324 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolis.minion_manager.rpc import client +from coriolis.tests import test_base + + +CREATE_MINION_POOL_ARGS = { + "name": "pool_name", + "endpoint_id": "endpoint_id", + "pool_platform": "platform", + "pool_os_type": "os_type", + "environment_options": {"opt1": "value1", "opt2": "value2"}, + "minimum_minions": 1, + "maximum_minions": 2, + "minion_max_idle_time": 3, + "minion_retention_strategy": "strategy" +} + + +UPDATE_MINION_POOL_PROGRESS_UPDATE_ARGS = { + "minion_pool_id": "pool_id", + "progress_update_index": 1, + "new_current_step": 2 +} + +ADD_MINION_POOL_EVENT_ARGS = { + "minion_pool_id": "pool_id", + "level": "INFO", + "message": "test_message" +} + +ENDPOINT_OPT_ARGS = { + "endpoint_id": "endpoint_id", + "env": "env", + "option_names": ["opt1", "opt2"] +} + +POOL_VALIDATION_ARGS = { + "endpoint_id": "endpoint_id", + "pool_environment": "pool_env" +} + + +class MinionManagerClientTestCase(test_base.CoriolisRPCClientTestCase): + """Test case for the Coriolis Minion Manager RPC client.""" + + def setUp(self): + super(MinionManagerClientTestCase, self).setUp() + self.minion_pool_id = mock.sentinel.minion_pool_id + self.message = mock.sentinel.message + self.initial_step = mock.sentinel.initial_step + self.total_steps = mock.sentinel.total_steps + self.ctxt = mock.sentinel.ctxt + self.client = client.MinionManagerClient() + + @mock.patch('coriolis.minion_manager.rpc.client.CONF') + @mock.patch.object(client.messaging, 'Target') + def test__init__(self, mock_target, mock_conf): + expected_timeout = 120 + mock_conf.minion_manager.minion_mananger_rpc_timeout = expected_timeout + + result = client.MinionManagerClient() + mock_target.assert_called_once_with( + topic='coriolis_minion_manager', version=client.VERSION) + + self.assertEqual(result._target, mock_target.return_value) + self.assertEqual(result._timeout, expected_timeout) + + def test__init__with_timeout(self): + result = client.MinionManagerClient(timeout=120) + self.assertEqual(result._timeout, 120) + + def test_add_minion_pool_progress_update(self): + args = { + "minion_pool_id": "pool_id", + "message": "test_message", + "initial_step": 1, + "total_steps": 2, + } + with mock.patch.object(self.client, '_cast') as mock_cast: + self.client.add_minion_pool_progress_update( + self.ctxt, **args + ) + mock_cast.assert_called_once_with( + self.ctxt, 'add_minion_pool_progress_update', **args + ) + + with mock.patch.object(self.client, '_call') as mock_call: + self.client.add_minion_pool_progress_update( + self.ctxt, return_event=True, **args + ) + mock_call.assert_called_once_with( + self.ctxt, 'add_minion_pool_progress_update', **args + ) + + def test_update_minion_pool_progress_update(self): + args = { + **UPDATE_MINION_POOL_PROGRESS_UPDATE_ARGS, + "new_total_steps": None, + "new_message": None + } + self._test( + self.client.update_minion_pool_progress_update, args, + rpc_op='_cast', + ) + + def test_add_minion_pool_event(self): + self._test( + self.client.add_minion_pool_event, ADD_MINION_POOL_EVENT_ARGS, + rpc_op='_cast', + ) + + def test_get_diagnostics(self): + self._test(self.client.get_diagnostics, {}) + + def test_validate_minion_pool_selections_for_action(self): + args = {"action": "test_action"} + self._test( + self.client.validate_minion_pool_selections_for_action, args + ) + + def test_allocate_minion_machines_for_replica(self): + args = {"replica": "test_replica"} + self._test( + self.client.allocate_minion_machines_for_replica, args, + rpc_op='_cast', + ) + + def test_allocate_minion_machines_for_migration(self): + args = { + "migration": "test_migration", + "include_transfer_minions": True, + "include_osmorphing_minions": True + } + self._test( + self.client.allocate_minion_machines_for_migration, args, + rpc_op='_cast', + ) + + def test_deallocate_minion_machine(self): + args = {"minion_machine_id": "test_id"} + self._test( + self.client.deallocate_minion_machine, args, + rpc_op='_cast', + ) + + def test_deallocate_minion_machines_for_action(self): + args = {"action_id": "test_id"} + self._test( + self.client.deallocate_minion_machines_for_action, args, + rpc_op='_cast', + ) + + def test_create_minion_pool(self): + args = { + **CREATE_MINION_POOL_ARGS, + "notes": None, + "skip_allocation": False + } + self._test(self.client.create_minion_pool, args) + + def test_set_up_shared_minion_pool_resources(self): + args = { + "minion_pool_id": self.minion_pool_id + } + self._test( + self.client.set_up_shared_minion_pool_resources, args, + ) + + def test_tear_down_shared_minion_pool_resources(self): + args = { + "minion_pool_id": self.minion_pool_id, + "force": False + } + self._test( + self.client.tear_down_shared_minion_pool_resources, args, + ) + + def test_allocate_minion_pool(self): + args = { + "minion_pool_id": self.minion_pool_id + } + self._test(self.client.allocate_minion_pool, args) + + def test_refresh_minion_pool(self): + args = { + "minion_pool_id": self.minion_pool_id + } + self._test(self.client.refresh_minion_pool, args) + + def test_deallocate_minion_pool(self): + args = { + "minion_pool_id": self.minion_pool_id, + "force": False + } + self._test(self.client.deallocate_minion_pool, args) + + def test_get_minion_pools(self): + self._test(self.client.get_minion_pools, {}) + + def test_get_minion_pool(self): + args = { + "minion_pool_id": self.minion_pool_id + } + self._test(self.client.get_minion_pool, args) + + def test_update_minion_pool(self): + args = { + "minion_pool_id": self.minion_pool_id, + "updated_values": {"opt1": "value1"} + } + self._test(self.client.update_minion_pool, args) + + def test_delete_minion_pool(self): + args = { + "minion_pool_id": self.minion_pool_id + } + self._test(self.client.delete_minion_pool, args) + + def test_get_endpoint_source_minion_pool_options(self): + self._test( + self.client.get_endpoint_source_minion_pool_options, + ENDPOINT_OPT_ARGS + ) + + def test_get_endpoint_destination_minion_pool_options(self): + self._test( + self.client.get_endpoint_destination_minion_pool_options, + ENDPOINT_OPT_ARGS + ) + + def test_validate_endpoint_source_minion_pool_options(self): + self._test( + self.client.validate_endpoint_source_minion_pool_options, + POOL_VALIDATION_ARGS + ) + + def test_validate_endpoint_destination_minion_pool_options(self): + self._test( + self.client.validate_endpoint_destination_minion_pool_options, + POOL_VALIDATION_ARGS + ) + + +class MinionManagerPoolRpcEventHandlerTestCase(test_base.CoriolisBaseTestCase): + """Test case for the Coriolis Minion Manager RPC event handler.""" + + def setUp(self): + super(MinionManagerPoolRpcEventHandlerTestCase, self).setUp() + self.ctxt = mock.sentinel.ctxt + self.pool_id = mock.sentinel.pool_id + self.message = mock.sentinel.message + + self.client = client.MinionManagerPoolRpcEventHandler( + self.ctxt, self.pool_id) + + @mock.patch.object(client, 'MinionManagerClient') + def test__rpc_minion_manager_client(self, mock_client): + result = self.client._rpc_minion_manager_client + + mock_client.assert_called_once_with() + self.assertEqual(result, mock_client.return_value) + + @mock.patch.object(client, 'MinionManagerClient') + def test__rpc_minion_manager_client_instantiated(self, mock_client): + self.client._rpc_minion_manager_client_instance = mock.sentinel.client + + result = self.client._rpc_minion_manager_client + + mock_client.assert_not_called() + self.assertEqual(result, mock.sentinel.client) + + def test_get_progress_update_identifier(self): + progress_update = {"index": 2} + + result = client.MinionManagerPoolRpcEventHandler.\ + get_progress_update_identifier(progress_update) + + self.assertEqual(result, progress_update['index']) + + @mock.patch.object( + client.MinionManagerClient, 'add_minion_pool_progress_update' + ) + def test_add_progress_update(self, mock_add_minion_pool_progress_update): + result = self.client.add_progress_update( + self.message, initial_step=1, total_steps=3, return_event=False + ) + + mock_add_minion_pool_progress_update.assert_called_once_with( + self.ctxt, self.pool_id, self.message, initial_step=1, + total_steps=3, return_event=False + ) + self.assertEqual( + result, mock_add_minion_pool_progress_update.return_value + ) + + @mock.patch.object( + client.MinionManagerClient, 'update_minion_pool_progress_update' + ) + def test_update_progress_update(self, + mock_update_minion_pool_progress_update): + self.client.update_progress_update( + mock.sentinel.update_identifier, mock.sentinel.new_current_step + ) + + mock_update_minion_pool_progress_update.assert_called_once_with( + self.ctxt, self.pool_id, mock.sentinel.update_identifier, + mock.sentinel.new_current_step, new_total_steps=None, + new_message=None + ) + + @mock.patch.object(client.MinionManagerClient, 'add_minion_pool_event') + def test_add_event(self, mock_add_minion_pool_event): + result = self.client.add_event( + self.message, level=client.constants.TASK_EVENT_INFO) + + mock_add_minion_pool_event.assert_called_once_with( + self.ctxt, self.pool_id, client.constants.TASK_EVENT_INFO, + self.message + ) + self.assertIsNone(result) diff --git a/coriolis/tests/minion_manager/rpc/test_tasks.py b/coriolis/tests/minion_manager/rpc/test_tasks.py new file mode 100644 index 000000000..e32e56757 --- /dev/null +++ b/coriolis/tests/minion_manager/rpc/test_tasks.py @@ -0,0 +1,2038 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from taskflow.types import failure + +from coriolis.conductor.rpc.client import ConductorClient +from coriolis import exception +from coriolis.minion_manager.rpc.client import MinionManagerClient +from coriolis.minion_manager.rpc import tasks +from coriolis.taskflow import base +from coriolis.tests import test_base + + +class CoriolisTestException(Exception): + pass + + +class MinionManagerTaskEventMixinTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis MinionManagerTaskEventMixin class.""" + + def setUp(self): + super(MinionManagerTaskEventMixinTestCase, self).setUp() + self.task = tasks.MinionManagerTaskEventMixin() + self.task._minion_pool_id = mock.sentinel.minion_pool_id + + def test__conductor_client(self): + with mock.patch( + 'coriolis.conductor.rpc.client.ConductorClient', + return_value=mock.MagicMock(spec=ConductorClient)) as \ + mock_Conductor_client: + result = self.task._conductor_client + + self.assertEqual( + result, mock_Conductor_client.return_value + ) + mock_Conductor_client.assert_called_once_with() + + def test__conductor_client_already_set(self): + with mock.patch( + 'coriolis.conductor.rpc.client.ConductorClient') as \ + mock_Conductor_client: + self.task._conductor_client_instance = mock.MagicMock( + spec=ConductorClient) + + result = self.task._conductor_client + + self.assertEqual( + result, self.task._conductor_client_instance + ) + mock_Conductor_client.assert_not_called() + + def test__minion_manager_client(self): + with mock.patch( + 'coriolis.minion_manager.rpc.client.MinionManagerClient', + return_value=mock.MagicMock(spec=MinionManagerClient)) as \ + mock_Minion_Manager_Client: + result = self.task._minion_manager_client + + self.assertEqual( + result, mock_Minion_Manager_Client.return_value + ) + mock_Minion_Manager_Client.assert_called_once_with() + + def test__minion_manager_client_already_set(self): + with mock.patch( + 'coriolis.minion_manager.rpc.client.MinionManagerClient') as \ + mock_Minion_Manager_Client: + self.task._minion_manager_client_instance = mock.MagicMock( + spec=MinionManagerClient) + + result = self.task._minion_manager_client + + self.assertEqual( + result, self.task._minion_manager_client_instance + ) + mock_Minion_Manager_Client.assert_not_called() + + @mock.patch.object(tasks.db_api, 'add_minion_pool_event') + def test__add_minion_pool_event(self, mock_add_minion_pool_event): + result = self.task._add_minion_pool_event( + mock.sentinel.context, mock.sentinel.message, + level=tasks.constants.TASK_EVENT_INFO) + self.assertIsNone(result) + + mock_add_minion_pool_event.assert_called_once_with( + mock.sentinel.context, mock.sentinel.minion_pool_id, + tasks.constants.TASK_EVENT_INFO, mock.sentinel.message + ) + + @mock.patch.object(tasks.db_api, 'get_minion_machine') + def test__get_minion_machine(self, mock_get_minion_machine): + result = self.task._get_minion_machine( + mock.sentinel.context, mock.sentinel.minion_machine_id) + self.assertEqual(result, mock_get_minion_machine.return_value) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, mock.sentinel.minion_machine_id) + + @mock.patch.object(tasks.db_api, 'get_minion_machine') + def test__get_minion_machine_not_found(self, mock_get_minion_machine): + mock_get_minion_machine.return_value = None + + self.assertRaises( + tasks.exception.NotFound, self.task._get_minion_machine, + mock.sentinel.context, mock.sentinel.minion_machine_id, + raise_if_not_found=True) + + @mock.patch.object(tasks.db_api, 'set_minion_pool_status') + def test__set_minion_pool_status(self, mock_set_minion_pool_status): + with mock.patch.object( + tasks.minion_manager_utils, 'get_minion_pool_lock') as \ + mock_get_minion_pool_lock: + result = self.task._set_minion_pool_status( + mock.sentinel.context, mock.sentinel.minion_pool_id, + mock.sentinel.new_status) + self.assertIsNone(result) + + mock_get_minion_pool_lock.assert_called_once_with( + mock.sentinel.minion_pool_id, external=True) + + mock_set_minion_pool_status.assert_called_once_with( + mock.sentinel.context, mock.sentinel.minion_pool_id, + mock.sentinel.new_status) + + @mock.patch.object(tasks.db_api, 'update_minion_machine') + def test__update_minion_machine(self, mock_update_minion_machine): + with mock.patch.object( + tasks.minion_manager_utils, 'get_minion_pool_lock') as \ + mock_get_minion_pool_lock: + result = self.task._update_minion_machine( + mock.sentinel.ctxt, mock.sentinel.minion_pool_id, + mock.sentinel.minion_machine_id, mock.sentinel.updated_values) + self.assertIsNone(result) + + mock_get_minion_pool_lock.assert_called_once_with( + mock.sentinel.minion_pool_id, external=True) + + mock_update_minion_machine.assert_called_once_with( + mock.sentinel.ctxt, mock.sentinel.minion_machine_id, + mock.sentinel.updated_values) + + @mock.patch.object(tasks.db_api, 'set_minion_machine_allocation_status') + def test__set_minion_machine_allocation_status( + self, mock_set_minion_machine_allocation_status): + with mock.patch.object( + tasks.minion_manager_utils, 'get_minion_pool_lock') as \ + mock_get_minion_pool_lock: + result = self.task._set_minion_machine_allocation_status( + mock.sentinel.ctxt, mock.sentinel.minion_pool_id, + mock.sentinel.minion_machine_id, mock.sentinel.new_status) + self.assertIsNone(result) + + mock_get_minion_pool_lock.assert_called_once_with( + mock.sentinel.minion_pool_id, external=True) + + mock_set_minion_machine_allocation_status.assert_called_once_with( + mock.sentinel.ctxt, mock.sentinel.minion_machine_id, + mock.sentinel.new_status) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_update_minion_machine' + ) + def test__set_minion_machine_power_status( + self, mock_update_minion_machine): + result = self.task._set_minion_machine_power_status( + mock.sentinel.ctxt, mock.sentinel.minion_pool_id, + mock.sentinel.minion_machine_id, mock.sentinel.new_status) + self.assertIsNone(result) + + mock_update_minion_machine.assert_called_once_with( + mock.sentinel.ctxt, mock.sentinel.minion_pool_id, + mock.sentinel.minion_machine_id, + {'power_status': mock.sentinel.new_status} + ) + + +class BaseReportMinionAllocationFailureForActionTaskTestCase( + test_base.CoriolisBaseTestCase): + @mock.patch.object( + tasks._BaseReportMinionAllocationFailureForActionTask, + '__abstractmethods__', set() + ) + @mock.patch.object( + tasks._BaseReportMinionAllocationFailureForActionTask, '_get_task_name' + ) + def setUp(self, mock_get_task_name): + super( + BaseReportMinionAllocationFailureForActionTaskTestCase, + self).setUp() + self.task = tasks._BaseReportMinionAllocationFailureForActionTask( + mock.sentinel.action_id) + + def test_execute(self): + result = self.task.execute(mock.sentinel.context) + self.assertIsNone(result) + + @mock.patch.object( + tasks._BaseReportMinionAllocationFailureForActionTask, + '_report_machine_allocation_failure' + ) + @mock.patch.object( + MinionManagerClient, 'deallocate_minion_machines_for_action' + ) + def test_revert(self, mock_deallocate_minion_machines_for_action, + mock_report_machine_allocation_failure): + result = self.task.revert( + mock.sentinel.context, mock.sentinel.flow_failures) + self.assertIsNone(result) + + mock_deallocate_minion_machines_for_action.assert_called_once_with( + mock.sentinel.context, mock.sentinel.action_id) + mock_report_machine_allocation_failure.assert_called_once_with( + mock.sentinel.context, mock.sentinel.action_id, + "No flow failures provided.") + + +class ReportMinionAllocationFailureForMigrationTaskTestCase( + test_base.CoriolisBaseTestCase): + def setUp(self): + super(ReportMinionAllocationFailureForMigrationTaskTestCase, + self).setUp() + self.action_id = mock.sentinel.action_id + self.task = tasks.ReportMinionAllocationFailureForMigrationTask( + self.action_id) + + def test_get_task_name(self): + result = self.task._get_task_name(self.action_id) + self.assertEqual( + result, + f"migration-{self.action_id}-minion-allocation-failure" + ) + + @mock.patch.object( + ConductorClient, 'report_migration_minions_allocation_error' + ) + def test__report_machine_allocation_failure( + self, mock_report_migration_minions_allocation_error): + result = self.task._report_machine_allocation_failure( + mock.sentinel.context, self.action_id, mock.sentinel.failure_str) + self.assertIsNone(result) + + mock_report_migration_minions_allocation_error.assert_called_once_with( + mock.sentinel.context, self.action_id, mock.sentinel.failure_str + ) + + +class ReportMinionAllocationFailureForReplicaTaskTestCase( + test_base.CoriolisBaseTestCase): + def setUp(self): + super(ReportMinionAllocationFailureForReplicaTaskTestCase, + self).setUp() + self.action_id = mock.sentinel.action_id + self.task = tasks.ReportMinionAllocationFailureForReplicaTask( + self.action_id) + + def test_get_task_name(self): + result = self.task._get_task_name(self.action_id) + self.assertEqual( + result, + f"replica-{self.action_id}-minion-allocation-failure" + ) + + @mock.patch.object( + ConductorClient, 'report_replica_minions_allocation_error' + ) + def test__report_machine_allocation_failure( + self, mock_report_replica_minions_allocation_error): + result = self.task._report_machine_allocation_failure( + mock.sentinel.context, self.action_id, mock.sentinel.failure_str) + + self.assertIsNone(result) + mock_report_replica_minions_allocation_error.assert_called_once_with( + mock.sentinel.context, self.action_id, mock.sentinel.failure_str + ) + + +class BaseConfirmMinionAllocationForActionTaskTestCase( + test_base.CoriolisBaseTestCase): + + @mock.patch.object( + tasks._BaseConfirmMinionAllocationForActionTask, + '__abstractmethods__', set() + ) + @mock.patch.object( + tasks._BaseConfirmMinionAllocationForActionTask, '_get_task_name' + ) + def setUp(self, mock_get_task_name): + super(BaseConfirmMinionAllocationForActionTaskTestCase, self).setUp() + self.minion_machine = mock.MagicMock() + self.minion_machine.id = mock.sentinel.minion_machine_id + self.minion_machine.pool_id = mock.sentinel.pool_id + self.minion_machine.allocated_action = mock.sentinel.action_id + self.minion_machine.allocation_status = ( + tasks.constants.MINION_MACHINE_STATUS_IN_USE) + self.minion_machine.power_status = ( + tasks.constants.MINION_MACHINE_POWER_STATUS_POWERED_ON) + + self.allocated_machine_id_mappings = { + mock.sentinel.instance_id: { + 'origin_minion_id': mock.sentinel.origin_minion_id, + 'destination_minion_id': mock.sentinel.destination_minion_id, + 'osmorphing_minion_id': mock.sentinel.osmorphing_minion_id, + } + } + + self.task = tasks._BaseConfirmMinionAllocationForActionTask( + mock.sentinel.action_id, self.allocated_machine_id_mappings) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + @mock.patch.object( + tasks._BaseConfirmMinionAllocationForActionTask, + '_confirm_machine_allocation_for_action' + ) + def test_execute(self, mock_confirm_allocation, mock_get_minion_machine): + mock_get_minion_machine.return_value = self.minion_machine + result = self.task.execute(mock.sentinel.context) + + mock_get_minion_machine.assert_has_calls([ + mock.call(mock.sentinel.context, mock.sentinel.origin_minion_id, + raise_if_not_found=True), + mock.call(mock.sentinel.context, + mock.sentinel.destination_minion_id, + raise_if_not_found=True), + mock.call(mock.sentinel.context, + mock.sentinel.osmorphing_minion_id, + raise_if_not_found=True)], any_order=True) + + expected_machine_allocations = { + mock.sentinel.instance_id: { + 'origin_minion': self.minion_machine.to_dict(), + 'destination_minion': self.minion_machine.to_dict(), + 'osmorphing_minion': self.minion_machine.to_dict(), + } + } + + mock_confirm_allocation.assert_called_once_with( + mock.sentinel.context, mock.sentinel.action_id, + expected_machine_allocations + ) + + self.assertIsNone(result) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + def test_execute_raises_exception_when_allocation_status_is_not_in_use( + self, mock_get_minion_machine): + mock_get_minion_machine.return_value = self.minion_machine + self.minion_machine.allocation_status = 'DEALLOCATED' + + self.assertRaises( + exception.InvalidMinionMachineState, + self.task.execute, mock.sentinel.context) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, mock.sentinel.origin_minion_id, + raise_if_not_found=True) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + def test_execute_raises_exception_when_allocated_action_is_not_correct( + self, mock_get_minion_machine): + mock_get_minion_machine.return_value = self.minion_machine + self.minion_machine.allocated_action = 'ANOTHER_ACTION' + + self.assertRaises( + exception.InvalidMinionMachineState, + self.task.execute, mock.sentinel.context) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, mock.sentinel.origin_minion_id, + raise_if_not_found=True) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + def test_execute_raises_exception_when_power_status_is_not_powered_on( + self, mock_get_minion_machine): + mock_get_minion_machine.return_value = self.minion_machine + self.minion_machine.power_status = 'POWERED_OFF' + + self.assertRaises( + exception.InvalidMinionMachineState, + self.task.execute, mock.sentinel.context) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, mock.sentinel.origin_minion_id, + raise_if_not_found=True) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + @mock.patch.object( + tasks._BaseConfirmMinionAllocationForActionTask, + '_confirm_machine_allocation_for_action' + ) + def test_execute_no_machine_allocation( + self, mock_confirm_allocation, mock_get_minion_machine): + mock_get_minion_machine.return_value = self.minion_machine + self.allocated_machine_id_mappings[ + mock.sentinel.instance_id] = {} + + result = self.task.execute(mock.sentinel.context) + + self.assertIsNone(result) + mock_get_minion_machine.assert_not_called() + mock_confirm_allocation.assert_called_once_with( + mock.sentinel.context, mock.sentinel.action_id, {}) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + def test_execute_raises_exception_when_required_properties_are_missing( + self, mock_get_minion_machine): + mock_get_minion_machine.return_value = self.minion_machine + self.minion_machine.provider_properties = None + + self.assertRaises( + exception.InvalidMinionMachineState, + self.task.execute, mock.sentinel.context) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, mock.sentinel.origin_minion_id, + raise_if_not_found=True) + + @mock.patch.object( + tasks._BaseConfirmMinionAllocationForActionTask, + '_confirm_machine_allocation_for_action' + ) + @mock.patch.object( + tasks._BaseConfirmMinionAllocationForActionTask, '_get_action_label' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + def test_execute_with_exception_not_found( + self, mock_get_minion_machine, mock_get_action_label, + mock_confirm_allocation): + mock_get_minion_machine.return_value = self.minion_machine + mock_confirm_allocation.side_effect = exception.NotFound() + + self.assertRaises( + exception.MinionMachineAllocationFailure, + self.task.execute, mock.sentinel.context) + + mock_get_action_label.assert_called_once_with() + + @mock.patch.object( + tasks._BaseConfirmMinionAllocationForActionTask, '_get_action_label' + ) + @mock.patch.object( + tasks._BaseConfirmMinionAllocationForActionTask, + '_confirm_machine_allocation_for_action' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + def test_execute_raises_exception_when_invalid_migration_state( + self, mock_get_minion_machine, mock_confirm_allocation, + mock_get_action_label): + mock_get_minion_machine.return_value = self.minion_machine + mock_confirm_allocation.side_effect = [ + exception.InvalidReplicaState(reason='Invalid state')] + + self.assertRaises( + exception.MinionMachineAllocationFailure, + self.task.execute, mock.sentinel.context) + + mock_get_action_label.assert_called_once_with() + + +class ConfirmMinionAllocationForMigrationTaskTestCase( + test_base.CoriolisBaseTestCase): + + def setUp(self): + super(ConfirmMinionAllocationForMigrationTaskTestCase, self).setUp() + self.action_id = mock.sentinel.action_id + + self.task = tasks.ConfirmMinionAllocationForMigrationTask( + mock.sentinel.action_id, + mock.sentinel.allocated_machine_id_mappings + ) + + def test__get_action_label(self): + result = self.task._get_action_label() + + self.assertEqual(result, 'migration') + + def test_get_task_name(self): + result = self.task._get_task_name(self.action_id) + + self.assertEqual( + result, + f"migration-{self.action_id}-minion-allocation-confirmation" + ) + + @mock.patch.object( + ConductorClient, 'confirm_migration_minions_allocation' + ) + def test__confirm_machine_allocation_for_action( + self, mock_confirm_migration_minions_allocation): + result = self.task._confirm_machine_allocation_for_action( + mock.sentinel.context, self.action_id, + mock.sentinel.machine_allocations) + + self.assertIsNone(result) + mock_confirm_migration_minions_allocation.assert_called_once_with( + mock.sentinel.context, self.action_id, + mock.sentinel.machine_allocations) + + +class ConfirmMinionAllocationForReplicaTaskTestCase( + test_base.CoriolisBaseTestCase): + + def setUp(self): + super(ConfirmMinionAllocationForReplicaTaskTestCase, self).setUp() + self.task = tasks.ConfirmMinionAllocationForReplicaTask( + mock.sentinel.action_id, + mock.sentinel.allocate_machine_id_mappings + ) + + def test__get_action_label(self): + result = self.task._get_action_label() + + self.assertEqual(result, 'replica') + + def test_get_task_name(self): + result = self.task._get_task_name(mock.sentinel.action_id) + + self.assertEqual( + result, + f"replica-{mock.sentinel.action_id}-minion-allocation-confirmation" + ) + + @mock.patch.object( + ConductorClient, 'confirm_replica_minions_allocation' + ) + def test__confirm_machine_allocation_for_action( + self, mock_confirm_replica_minions_allocation): + result = self.task._confirm_machine_allocation_for_action( + mock.sentinel.context, mock.sentinel.action_id, + mock.sentinel.machine_allocations) + + self.assertIsNone(result) + mock_confirm_replica_minions_allocation.assert_called_once_with( + mock.sentinel.context, mock.sentinel.action_id, + mock.sentinel.machine_allocations) + + +class UpdateMinionPoolStatusTaskTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis UpdateMinionPoolStatusTask class.""" + + def setUp(self): + super(UpdateMinionPoolStatusTaskTestCase, self).setUp() + self.status_to_revert_to = mock.sentinel.status_to_revert_to + + self.task = tasks.UpdateMinionPoolStatusTask( + mock.sentinel.minion_pool_id, mock.sentinel.status, + status_to_revert_to=self.status_to_revert_to) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_add_minion_pool_event' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_set_minion_pool_status' + ) + @mock.patch.object(tasks.db_api, 'get_minion_pool') + def test_execute(self, mock_get_minion_pool, mock_set_minion_pool_status, + mock_add_minion_pool_event): + result = self.task.execute(mock.sentinel.context) + self.assertEqual(result, self.task._target_status) + + mock_get_minion_pool.assert_called_once_with( + mock.sentinel.context, mock.sentinel.minion_pool_id, + include_machines=False, include_events=False, + include_progress_updates=False) + + mock_set_minion_pool_status.assert_called_once_with( + mock.sentinel.context, mock.sentinel.minion_pool_id, + self.task._target_status) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_set_minion_pool_status' + ) + @mock.patch.object(tasks.db_api, 'get_minion_pool') + def test_execute_when_previous_status_equals_target_status( + self, mock_get_minion_pool, mock_set_minion_pool_status): + self.task._previous_status = self.task._target_status + + result = self.task.execute(mock.sentinel.context) + self.assertEqual(result, self.task._target_status) + + mock_get_minion_pool.assert_not_called() + mock_set_minion_pool_status.assert_not_called() + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_add_minion_pool_event' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_set_minion_pool_status' + ) + @mock.patch.object(tasks.db_api, 'get_minion_pool') + def test_revert(self, mock_get_minion_pool, mock_set_minion_pool_status, + mock_add_minion_pool_event): + mock_get_minion_pool.return_value.status = 'DEALLOCATED' + + result = self.task.revert(mock.sentinel.context) + self.assertIsNone(result) + + mock_get_minion_pool.assert_called_once_with( + mock.sentinel.context, mock.sentinel.minion_pool_id, + include_machines=False, include_events=False, + include_progress_updates=False) + + mock_set_minion_pool_status.assert_called_once_with( + mock.sentinel.context, mock.sentinel.minion_pool_id, + self.status_to_revert_to) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_set_minion_pool_status' + ) + @mock.patch.object(tasks.db_api, 'get_minion_pool') + def test_revert_no_minion_pool(self, mock_get_minion_pool, + mock_set_minion_pool_status): + mock_get_minion_pool.return_value = None + + result = self.task.revert(mock.sentinel.context) + self.assertIsNone(result) + + mock_set_minion_pool_status.assert_not_called() + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_set_minion_pool_status' + ) + @mock.patch.object(tasks.db_api, 'get_minion_pool') + def test_revert_when_previous_status_equals_target_status( + self, mock_get_minion_pool, mock_set_minion_pool_status): + mock_get_minion_pool.return_value.status = self.status_to_revert_to + + result = self.task.revert(mock.sentinel.context) + self.assertIsNone(result) + + mock_get_minion_pool.assert_called_once_with( + mock.sentinel.context, mock.sentinel.minion_pool_id, + include_machines=False, include_events=False, + include_progress_updates=False) + + mock_set_minion_pool_status.assert_not_called() + + +class BaseMinionManangerTaskTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis BaseMinionManangerTask class.""" + + @mock.patch.object( + tasks.BaseMinionManangerTask, '__abstractmethods__', set() + ) + @mock.patch.object(tasks.BaseMinionManangerTask, '_get_task_name') + def setUp(self, mock_get_task_name): + super(BaseMinionManangerTaskTestCase, self).setUp() + self.task = tasks.BaseMinionManangerTask( + mock.sentinel.minion_pool_id, mock.sentinel.minion_machine_id, + mock.sentinel.main_task_runner_type) + + @mock.patch.object(base.BaseRunWorkerTask, 'execute') + def test_execute(self, mock_execute): + result = self.task.execute( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, mock.sentinel.task_info) + self.assertEqual(result, mock_execute.return_value) + + mock_execute.assert_called_once_with( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, mock.sentinel.task_info + ) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_add_minion_pool_event') + @mock.patch.object(base.BaseRunWorkerTask, 'revert') + def test_revert(self, mock_revert, mock_add_minion_pool_event): + result = self.task.revert( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, mock.sentinel.task_info) + self.assertIsNone(result) + + mock_revert.assert_called_once_with( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, mock.sentinel.task_info + ) + + +class ValidateMinionPoolOptionsTaskTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis ValidateMinionPoolOptionsTask class.""" + + def setUp(self): + super(ValidateMinionPoolOptionsTaskTestCase, self).setUp() + self.minion_pool_id = 'test_pool_id' + self.minion_pool_type = tasks.constants.PROVIDER_PLATFORM_SOURCE + + self.task = tasks.ValidateMinionPoolOptionsTask( + self.minion_pool_id, mock.sentinel.minion_machine_id, + self.minion_pool_type) + + def test__init__with_different_minion_pool_type(self): + self.minion_pool_type = "destination" + + task = tasks.ValidateMinionPoolOptionsTask( + self.minion_pool_id, mock.sentinel.minion_machine_id, + self.minion_pool_type) + + self.assertEqual( + task._main_task_runner_type, + tasks.constants.TASK_TYPE_VALIDATE_DESTINATION_MINION_POOL_OPTIONS + ) + + def test__get_task_name(self): + result = self.task._get_task_name( + self.minion_pool_id, mock.sentinel.minion_machine_id) + + self.assertEqual( + result, + tasks.MINION_POOL_VALIDATION_TASK_NAME_FORMAT % self.minion_pool_id + ) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_add_minion_pool_event' + ) + @mock.patch.object(tasks.BaseMinionManangerTask, 'execute') + def test_execute(self, mock_execute, mock_add_minion_pool_event): + result = self.task.execute( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, mock.sentinel.task_info) + self.assertIsNone(result) + + mock_execute.assert_called_once_with( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, mock.sentinel.task_info + ) + + @mock.patch.object(tasks.BaseMinionManangerTask, 'revert') + def test_revert(self, mock_revert): + result = self.task.revert( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, mock.sentinel.task_info) + self.assertIsNone(result) + + mock_revert.assert_called_once_with( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, mock.sentinel.task_info + ) + + +class AllocateSharedPoolResourcesTaskTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis AllocateSharedPoolResourcesTask class.""" + + def setUp(self): + super(AllocateSharedPoolResourcesTaskTestCase, self).setUp() + self.minion_pool_id = 'test_pool_id' + self.minion_pool_type = tasks.constants.PROVIDER_PLATFORM_SOURCE + self.task_info = { + 'pool_shared_resources': True + } + + self.task = tasks.AllocateSharedPoolResourcesTask( + self.minion_pool_id, mock.sentinel.minion_machine_id, + self.minion_pool_type) + + def test__init__with_different_minion_pool_type(self): + self.minion_pool_type = "destination" + + task = tasks.AllocateSharedPoolResourcesTask( + self.minion_pool_id, mock.sentinel.minion_machine_id, + self.minion_pool_type) + + self.assertEqual( + task._main_task_runner_type, + tasks.constants.TASK_TYPE_SET_UP_DESTINATION_POOL_SHARED_RESOURCES + ) + self.assertEqual( + task._cleanup_task_runner_type, + tasks.constants.TASK_TYPE_TEAR_DOWN_DESTINATION_POOL_SHARED_RESOURCES # noqa: E501 + ) + + def test__get_task_name(self): + result = self.task._get_task_name( + self.minion_pool_id, mock.sentinel.minion_machine_id) + + self.assertEqual( + result, + tasks.MINION_POOL_ALLOCATE_SHARED_RESOURCES_TASK_NAME_FORMAT % + self.minion_pool_id + ) + + @mock.patch.object(tasks.db_api, 'get_minion_pool') + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_add_minion_pool_event' + ) + @mock.patch.object(tasks.BaseMinionManangerTask, 'execute') + @mock.patch.object(tasks.db_api, 'update_minion_pool') + @mock.patch.object(tasks.minion_manager_utils, 'get_minion_pool_lock') + def test_execute(self, mock_get_minion_pool_lock, mock_update_minion_pool, + mock_execute, mock_add_minion_pool_event, + mock_get_minion_pool): + mock_execute.return_value = { + 'pool_shared_resources': {"resource1": "id1"} + } + mock_get_minion_pool.return_value.shared_resources = {} + + result = self.task.execute( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + self.assertEqual( + result, {'pool_shared_resources': {"resource1": "id1"}}) + + mock_get_minion_pool_lock.assert_called_with( + self.minion_pool_id, external=True) + mock_get_minion_pool.assert_called_once_with( + mock.sentinel.context, self.minion_pool_id) + mock_update_minion_pool.assert_called_once_with( + mock.sentinel.context, self.minion_pool_id, + {'shared_resources': {"resource1": "id1"}}) + + @mock.patch.object(tasks.db_api, 'get_minion_pool') + @mock.patch.object(tasks.BaseMinionManangerTask, 'execute') + @mock.patch.object(tasks.db_api, 'update_minion_pool') + @mock.patch.object(tasks.minion_manager_utils, 'get_minion_pool_lock') + def test_execute_shared_resources_already_allocated( + self, mock_get_minion_pool_lock, mock_update_minion_pool, + mock_execute, mock_get_minion_pool): + mock_get_minion_pool.return_value.shared_resources = True + + self.assertRaises( + exception.InvalidMinionPoolState, self.task.execute, + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + + mock_get_minion_pool_lock.assert_called_once_with( + self.minion_pool_id, external=True) + mock_get_minion_pool.assert_called_once_with( + mock.sentinel.context, self.minion_pool_id) + mock_execute.assert_not_called() + mock_update_minion_pool.assert_not_called() + + @mock.patch.object(tasks.db_api, 'get_minion_pool') + @mock.patch.object(tasks.BaseMinionManangerTask, 'execute') + @mock.patch.object(tasks.db_api, 'update_minion_pool') + @mock.patch.object(tasks.minion_manager_utils, 'get_minion_pool_lock') + def test_execute_no_minion_pool( + self, mock_get_minion_pool_lock, mock_update_minion_pool, + mock_execute, mock_get_minion_pool): + mock_get_minion_pool.return_value = None + + self.assertRaises( + exception.InvalidMinionPoolSelection, self.task.execute, + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + + mock_get_minion_pool_lock.assert_called_once_with( + self.minion_pool_id, external=True) + mock_get_minion_pool.assert_called_once_with( + mock.sentinel.context, self.minion_pool_id) + mock_execute.assert_not_called() + mock_update_minion_pool.assert_not_called() + + @mock.patch.object(tasks.BaseMinionManangerTask, 'revert') + @mock.patch.object(tasks.minion_manager_utils, 'get_minion_pool_lock') + @mock.patch.object(tasks.db_api, 'update_minion_pool') + def test_revert(self, mock_update_minion_pool, mock_get_minion_pool_lock, + mock_revert): + self.task_info = {} + self.update_values = { + 'pool_shared_resources': None + } + + result = self.task.revert( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + self.assertIsNone(result) + self.assertEqual(self.task_info['pool_shared_resources'], {}) + + mock_revert.assert_called_once_with( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info + ) + mock_get_minion_pool_lock.assert_called_once_with( + self.minion_pool_id, external=True) + mock_update_minion_pool.assert_called_once_with( + mock.sentinel.context, self.minion_pool_id, self.update_values) + + +class DeallocateSharedPoolResourcesTaskTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis DeallocateSharedPoolResourcesTask class.""" + + def setUp(self): + super(DeallocateSharedPoolResourcesTaskTestCase, self).setUp() + self.minion_pool_id = 'test_pool_id' + self.task_info = { + 'pool_shared_resources': {"resource1": "id1"}, + 'pool_environment_options': {'option1': 'value1'} + } + self.minion_pool_type = tasks.constants.PROVIDER_PLATFORM_SOURCE + + self.task = tasks.DeallocateSharedPoolResourcesTask( + self.minion_pool_id, mock.sentinel.minion_machine_id, + self.minion_pool_type) + + def test__init__with_different_minion_pool_type(self): + self.minion_pool_type = "destination" + + task = tasks.DeallocateSharedPoolResourcesTask( + self.minion_pool_id, mock.sentinel.minion_machine_id, + self.minion_pool_type) + + self.assertEqual( + task._main_task_runner_type, + tasks.constants.TASK_TYPE_TEAR_DOWN_DESTINATION_POOL_SHARED_RESOURCES # noqa: E501 + ) + + def test__get_task_name(self): + result = self.task._get_task_name( + self.minion_pool_id, mock.sentinel.minion_machine_id) + + self.assertEqual( + result, + tasks.MINION_POOL_DEALLOCATE_SHARED_RESOURCES_TASK_NAME_FORMAT % + self.minion_pool_id + ) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_add_minion_pool_event' + ) + @mock.patch.object(tasks.db_api, 'update_minion_pool') + @mock.patch.object(tasks.BaseMinionManangerTask, 'execute') + def test_execute(self, mock_execute, mock_update_minion_pool, + mock_add_minion_pool_event): + self.update_values = { + 'shared_resources': None, + } + + result = self.task.execute( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + self.assertEqual(result, self.task_info) + + mock_execute.assert_called_once_with( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + mock_update_minion_pool.assert_called_once_with( + mock.sentinel.context, self.minion_pool_id, self.update_values) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_add_minion_pool_event' + ) + @mock.patch.object(tasks.db_api, 'update_minion_pool') + @mock.patch.object(tasks.BaseMinionManangerTask, 'execute') + def test_execute_no_pool_shared_resources( + self, mock_execute, mock_update_minion_pool, + mock_add_minion_pool_event): + self.task_info = {} + self.assertRaises( + exception.InvalidInput, self.task.execute, + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + + mock_execute.assert_not_called() + mock_update_minion_pool.assert_not_called() + + +class AllocateMinionMachineTaskTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis AllocateMinionMachineTask class.""" + + def setUp(self): + super(AllocateMinionMachineTaskTestCase, self).setUp() + self.minion_machine = mock.MagicMock() + self.minion_machine.id = 'test_machine_id' + self.minion_machine.pool_id = 'test_pool_id' + self.minion_machine.allocation_status = ( + tasks.constants.MINION_MACHINE_STATUS_UNINITIALIZED) + self.minion_pool_type = tasks.constants.PROVIDER_PLATFORM_SOURCE + self.minion_machine.allocated_action = mock.sentinel.allocation_action + self.minion_pool_id = 'test_pool_id' + self.minion_machine_id = 'test_machine_id' + self.task_info = { + 'pool_environment_options': 'test_env_options', + 'pool_shared_resources': True, + 'pool_identifier': 'test_identifier', + 'pool_os_type': 'linux', + } + self.mock_failure = mock.MagicMock(spec=failure.Failure) + + self.task = tasks.AllocateMinionMachineTask( + self.minion_pool_id, self.minion_machine_id, + self.minion_pool_type) + + self.task._allocate_to_action = self.minion_machine.allocated_action + + def test__init__with_different_minion_pool_type(self): + self.minion_pool_type = "destination" + + task = tasks.AllocateMinionMachineTask( + self.minion_pool_id, self.minion_machine_id, + self.minion_pool_type) + + self.assertEqual( + task._main_task_runner_type, + tasks.constants.TASK_TYPE_CREATE_DESTINATION_MINION_MACHINE + ) + self.assertEqual( + task._cleanup_task_runner_type, + tasks.constants.TASK_TYPE_DELETE_DESTINATION_MINION_MACHINE + ) + + def test__get_task_name(self): + result = self.task._get_task_name( + self.minion_pool_id, self.minion_machine_id) + + self.assertEqual( + result, + tasks.MINION_POOL_ALLOCATE_MACHINE_TASK_NAME_FORMAT % + (self.minion_pool_id, self.minion_machine_id) + ) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_update_minion_machine' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_add_minion_pool_event' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, + '_set_minion_machine_allocation_status' + ) + @mock.patch.object(tasks.timeutils, 'utcnow') + @mock.patch.object(tasks.models, 'MinionMachine') + @mock.patch.object(tasks.db_api, 'add_minion_machine') + @mock.patch.object(tasks.BaseMinionManangerTask, 'execute') + def test_execute( + self, mock_execute, mock_add_minion_machine, mock_minion_machine, + mock_utcnow, mock_set_minion_machine_allocation, + mock_add_minion_pool_event, mock_update_minion_machine, + mock_get_minion_machine): + mock_get_minion_machine.return_value = self.minion_machine + mock_execute.return_value = { + 'minion_connection_info': 'test_connection_info', + 'minion_provider_properties': 'test_provider_properties', + 'minion_backup_writer_connection_info': ( + 'test_backup_writer_connection_info'), + } + expected_updated_values = { + 'last_used_at': mock_utcnow.return_value, + 'allocation_status': ( + tasks.constants.MINION_MACHINE_STATUS_IN_USE), + 'power_status': ( + tasks.constants.MINION_MACHINE_POWER_STATUS_POWERED_ON), + 'connection_info': 'test_connection_info', + 'provider_properties': 'test_provider_properties', + 'backup_writer_connection_info': ( + 'test_backup_writer_connection_info'), + 'allocated_action': self.task._allocate_to_action + } + + result = self.task.execute( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + self.assertEqual(result, self.task_info) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id, + raise_if_not_found=False) + mock_set_minion_machine_allocation.assert_called_once_with( + mock.sentinel.context, self.minion_pool_id, self.minion_machine_id, + tasks.constants.MINION_MACHINE_STATUS_ALLOCATING) + mock_minion_machine.assert_not_called() + mock_add_minion_machine.assert_not_called() + mock_execute.assert_called_once_with( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + mock_update_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_pool_id, self.minion_machine_id, + expected_updated_values) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + def test_execute_when_allocation_status_is_not_uninitialized( + self, mock_get_minion_machine): + mock_get_minion_machine.return_value = self.minion_machine + self.minion_machine.allocation_status = 'ALLOCATING' + self.assertRaises( + exception.InvalidMinionMachineState, self.task.execute, + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id, + raise_if_not_found=False) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + def test_execute_when_minion_machine_belongs_to_different_pool( + self, mock_get_minion_machine): + mock_get_minion_machine.return_value = self.minion_machine + self.minion_machine.pool_id = 'DIFFERENT_POOL_ID' + + self.assertRaises( + exception.InvalidMinionMachineState, self.task.execute, + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id, + raise_if_not_found=False) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + def test_execute_when_minion_machine_already_allocated( + self, mock_get_minion_machine): + mock_get_minion_machine.return_value = self.minion_machine + self.task._allocate_to_action = 'ANOTHER_ACTION' + + self.assertRaises( + exception.InvalidMinionMachineState, self.task.execute, + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id, + raise_if_not_found=False) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_update_minion_machine' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_add_minion_pool_event' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, + '_set_minion_machine_allocation_status' + ) + @mock.patch.object(tasks.models, 'MinionMachine') + @mock.patch.object(tasks.db_api, 'add_minion_machine') + @mock.patch.object(tasks.BaseMinionManangerTask, 'execute') + def test_execute_add_new_minion_machine( + self, mock_execute, mock_add_minion_machine, mock_minion_machine, + mock_set_minion_machine_allocation, mock_add_minion_pool_event, + mock_update_minion_machine, mock_get_minion_machine): + mock_get_minion_machine.return_value = None + mock_minion_machine.return_value = self.minion_machine + mock_execute.side_effect = CoriolisTestException + + self.assertRaises(CoriolisTestException, self.task.execute, + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id, + raise_if_not_found=False) + mock_minion_machine.assert_called_once_with() + mock_add_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine) + mock_set_minion_machine_allocation.assert_called_once_with( + mock.sentinel.context, self.minion_pool_id, self.minion_machine_id, + tasks.constants.MINION_MACHINE_STATUS_ERROR_DEPLOYING) + mock_update_minion_machine.assert_not_called() + + @mock.patch.object(tasks.minion_manager_utils, 'get_minion_pool_lock') + @mock.patch.object(tasks.db_api, 'get_minion_machine') + @mock.patch.object(tasks.db_api, 'delete_minion_machine') + @mock.patch.object(tasks.BaseMinionManangerTask, 'revert') + def test_revert(self, mock_revert, mock_delete_minion_machine, + mock_get_minion_machine, mock_get_minion_pool_lock): + self.task_info['minion_provider_properties'] = ( + mock_get_minion_machine.return_value.provider_properties) + mock_get_minion_machine.return_value = self.minion_machine + self.mock_failure.traceback_str = 'test_traceback' + kwargs = {'result': self.mock_failure} + + result = self.task.revert( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info, **kwargs) + self.assertIsNone(result) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id) + mock_get_minion_pool_lock.assert_called_once_with( + self.minion_pool_id, external=True) + mock_delete_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id) + mock_revert.assert_called_once_with( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info, **kwargs + ) + + @mock.patch.object(tasks.minion_manager_utils, 'get_minion_pool_lock') + @mock.patch.object(tasks.db_api, 'get_minion_machine') + @mock.patch.object(tasks.db_api, 'delete_minion_machine') + @mock.patch.object(tasks.BaseMinionManangerTask, 'revert') + def test_revert_failed_deletion( + self, mock_revert, mock_delete_minion_machine, + mock_get_minion_machine, mock_get_minion_pool_lock): + mock_get_minion_machine.return_value = None + mock_delete_minion_machine.side_effect = CoriolisTestException + + result = self.task.revert( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + self.assertIsNone(result) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id) + mock_get_minion_pool_lock.assert_called_once_with( + self.minion_pool_id, external=True) + mock_revert.assert_not_called() + + @mock.patch.object(tasks.minion_manager_utils, 'get_minion_pool_lock') + @mock.patch.object(tasks.db_api, 'get_minion_machine') + @mock.patch.object(tasks.db_api, 'delete_minion_machine') + @mock.patch.object(tasks.BaseMinionManangerTask, 'revert') + def test_revert_unexpected_result_type( + self, mock_revert, mock_delete_minion_machine, + mock_get_minion_machine, mock_get_minion_pool_lock): + self.task_info['minion_provider_properties'] = ( + mock_get_minion_machine.return_value.provider_properties) + mock_get_minion_machine.return_value = self.minion_machine + kwargs = {'result': 'test_result'} + + result = self.task.revert( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info, **kwargs) + self.assertIsNone(result) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id) + mock_get_minion_pool_lock.assert_called_once_with( + self.minion_pool_id, external=True) + mock_delete_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id) + mock_revert.assert_called_once_with( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info, **kwargs + ) + + @mock.patch.object(tasks.minion_manager_utils, 'get_minion_pool_lock') + @mock.patch.object(tasks.db_api, 'get_minion_machine') + @mock.patch.object(tasks.db_api, 'delete_minion_machine') + @mock.patch.object(tasks.BaseMinionManangerTask, 'revert') + def test_revert_raises_unhandled_exception( + self, mock_revert, mock_delete_minion_machine, + mock_get_minion_machine, mock_get_minion_pool_lock): + mock_get_minion_machine.return_value = self.minion_machine + mock_revert.side_effect = CoriolisTestException + + self.assertRaises( + CoriolisTestException, self.task.revert, + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + + mock_get_minion_pool_lock.assert_called_once_with( + self.minion_pool_id, external=True) + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id) + mock_delete_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id) + + @mock.patch.object(tasks.minion_manager_utils, 'get_minion_pool_lock') + @mock.patch.object(tasks.db_api, 'get_minion_machine') + @mock.patch.object(tasks.db_api, 'delete_minion_machine') + @mock.patch.object(tasks.BaseMinionManangerTask, 'revert') + def test_revert_no_raise_on_cleanup_failure( + self, mock_revert, mock_delete_minion_machine, + mock_get_minion_machine, mock_get_minion_pool_lock): + mock_get_minion_machine.return_value = self.minion_machine + self.task._raise_on_cleanup_failure = False + mock_revert.side_effect = CoriolisTestException + + result = self.task.revert( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + self.assertIsNone(result) + + mock_get_minion_pool_lock.assert_called_once_with( + self.minion_pool_id, external=True) + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id) + mock_delete_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id) + + +class DeallocateMinionMachineTaskTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis DeallocateMinionMachineTask class.""" + + def setUp(self): + super(DeallocateMinionMachineTaskTestCase, self).setUp() + self.minion_machine = mock.MagicMock() + self.minion_machine.id = 'test_machine_id' + self.minion_machine.pool_id = 'test_pool_id' + self.minion_machine.allocation_status = ( + tasks.constants.MINION_MACHINE_STATUS_IN_USE) + self.minion_pool_type = tasks.constants.PROVIDER_PLATFORM_SOURCE + self.minion_machine.allocated_action = mock.sentinel.allocation_action + self.minion_pool_id = 'test_pool_id' + self.minion_machine_id = 'test_machine_id' + self.task_info = { + 'minion_provider_properties': None, + } + self.mock_failure = mock.MagicMock(spec=failure.Failure) + + self.task = tasks.DeallocateMinionMachineTask( + self.minion_pool_id, self.minion_machine_id, + self.minion_pool_type) + + self.task._deallocate_from_action = ( + self.minion_machine.allocated_action) + + def test__init__with_different_minion_pool_type(self): + self.minion_pool_type = "destination" + + task = tasks.DeallocateMinionMachineTask( + self.minion_pool_id, self.minion_machine_id, + self.minion_pool_type) + + self.assertEqual( + task._main_task_runner_type, + tasks.constants.TASK_TYPE_DELETE_DESTINATION_MINION_MACHINE + ) + + def test__get_task_name(self): + result = self.task._get_task_name( + self.minion_pool_id, self.minion_machine_id) + + self.assertEqual( + result, + tasks.MINION_POOL_DEALLOCATE_MACHINE_TASK_NAME_FORMAT % + (self.minion_pool_id, self.minion_machine_id) + ) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_add_minion_pool_event' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, + '_set_minion_machine_allocation_status' + ) + @mock.patch.object(tasks.db_api, 'delete_minion_machine') + @mock.patch.object(tasks.BaseMinionManangerTask, 'execute') + @mock.patch.object(tasks.minion_manager_utils, 'get_minion_pool_lock') + def test_execute( + self, mock_get_minion_pool_lock, mock_execute, + mock_delete_minion_machine, mock_set_minion_machine_allocation, + mock_add_minion_pool_event, mock_get_minion_machine): + mock_get_minion_machine.return_value = self.minion_machine + self.minion_machine.provider_properties = None + + result = self.task.execute( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + self.assertEqual(result, self.task_info) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id) + mock_set_minion_machine_allocation.assert_not_called() + mock_execute.assert_not_called() + mock_delete_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id) + mock_get_minion_pool_lock.assert_called_once_with( + self.minion_pool_id, external=True) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + def test_execute_no_machine(self, mock_get_minion_machine): + mock_get_minion_machine.return_value = None + + result = self.task.execute( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + self.assertEqual(result, self.task_info) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_add_minion_pool_event' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, + '_set_minion_machine_allocation_status' + ) + @mock.patch.object(tasks.db_api, 'delete_minion_machine') + @mock.patch.object(tasks.BaseMinionManangerTask, 'execute') + @mock.patch.object(tasks.minion_manager_utils, 'get_minion_pool_lock') + def test_execute_with_provider_properties( + self, mock_get_minion_pool_lock, mock_execute, + mock_delete_minion_machine, mock_set_minion_machine_allocation, + mock_add_minion_pool_event, mock_get_minion_machine): + mock_get_minion_machine.return_value = self.minion_machine + self.task_info['minion_provider_properties'] = ( + mock_get_minion_machine.return_value.provider_properties) + + result = self.task.execute( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + self.assertEqual(result, self.task_info) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id) + mock_set_minion_machine_allocation.assert_called_once_with( + mock.sentinel.context, self.minion_pool_id, self.minion_machine_id, + tasks.constants.MINION_MACHINE_STATUS_DEALLOCATING) + mock_execute.assert_called_once_with( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + mock_delete_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id) + mock_get_minion_pool_lock.assert_called_once_with( + self.minion_pool_id, external=True) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_add_minion_pool_event' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, + '_set_minion_machine_allocation_status' + ) + @mock.patch.object(tasks.db_api, 'delete_minion_machine') + @mock.patch.object(tasks.BaseMinionManangerTask, 'execute') + @mock.patch.object(tasks.minion_manager_utils, 'get_minion_pool_lock') + def test_execute_with_exception( + self, mock_get_minion_pool_lock, mock_execute, + mock_delete_minion_machine, mock_set_minion_machine_allocation, + mock_add_minion_pool_event, mock_get_minion_machine): + mock_get_minion_machine.return_value = self.minion_machine + mock_execute.side_effect = CoriolisTestException + + self.assertRaises( + CoriolisTestException, self.task.execute, + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id) + mock_set_minion_machine_allocation.assert_called_once_with( + mock.sentinel.context, self.minion_pool_id, self.minion_machine_id, + tasks.constants.MINION_MACHINE_STATUS_DEALLOCATING) + mock_delete_minion_machine.assert_not_called() + mock_get_minion_pool_lock.assert_not_called() + + +class HealthcheckMinionMachineTaskTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis HealthcheckMinionMachineTask class.""" + + def setUp(self): + super(HealthcheckMinionMachineTaskTestCase, self).setUp() + self.minion_machine = mock.MagicMock() + self.minion_machine.id = 'test_machine_id' + self.minion_machine.pool_id = 'test_pool_id' + self.minion_pool_type = tasks.constants.PROVIDER_PLATFORM_SOURCE + self.minion_machine.allocated_action = mock.sentinel.allocation_action + self.minion_pool_id = 'test_pool_id' + self.minion_machine_id = 'test_machine_id' + self.task = tasks.HealthcheckMinionMachineTask( + self.minion_pool_id, self.minion_machine_id, self.minion_pool_type, + machine_status_on_success=( + tasks.constants.MINION_MACHINE_STATUS_AVAILABLE)) + + def test__init__with_different_minion_pool_type(self): + self.minion_pool_type = "destination" + + task = tasks.HealthcheckMinionMachineTask( + self.minion_pool_id, self.minion_machine_id, + self.minion_pool_type) + + self.assertEqual( + task._main_task_runner_type, + tasks.constants.TASK_TYPE_HEALTHCHECK_DESTINATION_MINION + ) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_add_minion_pool_event' + ) + @mock.patch.object(tasks.BaseMinionManangerTask, 'execute') + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, + '_set_minion_machine_allocation_status' + ) + def test_execute( + self, mock_set_minion_machine_allocation, mock_execute, + mock_add_minion_pool_event, mock_get_minion_machine): + mock_get_minion_machine.return_value = self.minion_machine + execution_info = { + 'minion_provider_properties': ( + mock_get_minion_machine.return_value.provider_properties), + 'minion_connection_info': ( + mock_get_minion_machine.return_value.connection_info), + } + + result = self.task.execute( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, mock.sentinel.task_info) + self.assertEqual(result['healthy'], True) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id, + raise_if_not_found=False) + mock_execute.assert_called_once_with( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, execution_info) + mock_set_minion_machine_allocation.assert_called_once_with( + mock.sentinel.context, self.minion_pool_id, self.minion_machine_id, + tasks.constants.MINION_MACHINE_STATUS_AVAILABLE) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_add_minion_pool_event' + ) + @mock.patch.object(tasks.BaseMinionManangerTask, 'execute') + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, + '_set_minion_machine_allocation_status' + ) + def test_execute_no_machine( + self, mock_set_minion_machine_allocation, mock_execute, + mock_add_minion_pool_event, mock_get_minion_machine): + mock_get_minion_machine.return_value = None + + result = self.task.execute( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, mock.sentinel.task_info) + self.assertEqual(result['healthy'], False) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id, + raise_if_not_found=False) + mock_set_minion_machine_allocation.assert_not_called() + mock_execute.assert_not_called() + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_add_minion_pool_event' + ) + def test_execute_no_machine_with_fail_on_error( + self, mock_add_minion_pool_event, mock_get_minion_machine): + self.task._fail_on_error = True + mock_get_minion_machine.return_value = None + + self.assertRaises( + exception.InvalidMinionMachineState, self.task.execute, + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, mock.sentinel.task_info) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id, + raise_if_not_found=False) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_add_minion_pool_event' + ) + @mock.patch.object(tasks.BaseMinionManangerTask, 'execute') + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, + '_set_minion_machine_allocation_status' + ) + def test_execute_with_allocation_status( + self, mock_set_minion_machine_allocation, mock_execute, + mock_add_minion_pool_event, mock_get_minion_machine): + self.minion_machine.allocation_status = ( + tasks.constants.MINION_MACHINE_STATUS_ERROR) + mock_get_minion_machine.return_value = self.minion_machine + + result = self.task.execute( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, mock.sentinel.task_info) + self.assertEqual(result['healthy'], False) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id, + raise_if_not_found=False) + mock_set_minion_machine_allocation.assert_not_called() + mock_execute.assert_not_called() + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_add_minion_pool_event' + ) + def test_execute_with_allocation_status_and_fail_on_error( + self, mock_add_minion_pool_event, mock_get_minion_machine): + self.minion_machine.allocation_status = ( + tasks.constants.MINION_MACHINE_STATUS_ERROR) + self.task._fail_on_error = True + mock_get_minion_machine.return_value = self.minion_machine + + self.assertRaises( + exception.InvalidMinionMachineState, self.task.execute, + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, mock.sentinel.task_info) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id, + raise_if_not_found=False) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_add_minion_pool_event' + ) + @mock.patch.object(tasks.BaseMinionManangerTask, 'execute') + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, + '_set_minion_machine_allocation_status' + ) + def test_execute_with_exception( + self, mock_set_minion_machine_allocation, mock_execute, + mock_add_minion_pool_event, mock_get_minion_machine): + mock_get_minion_machine.return_value = self.minion_machine + mock_execute.side_effect = CoriolisTestException + + result = self.task.execute( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, mock.sentinel.task_info) + self.assertEqual(result['healthy'], False) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id, + raise_if_not_found=False) + mock_set_minion_machine_allocation.assert_called_once_with( + mock.sentinel.context, self.minion_pool_id, self.minion_machine_id, + tasks.constants.MINION_MACHINE_STATUS_ERROR) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_add_minion_pool_event' + ) + @mock.patch.object(tasks.BaseMinionManangerTask, 'execute') + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, + '_set_minion_machine_allocation_status' + ) + def test_execute_with_exception_and_fail_on_error( + self, mock_set_minion_machine_allocation, mock_execute, + mock_add_minion_pool_event, mock_get_minion_machine): + mock_get_minion_machine.return_value = self.minion_machine + mock_execute.side_effect = CoriolisTestException + self.task._fail_on_error = True + + self.assertRaises( + CoriolisTestException, self.task.execute, + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, mock.sentinel.task_info) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id, + raise_if_not_found=False) + mock_set_minion_machine_allocation.assert_called_once_with( + mock.sentinel.context, self.minion_pool_id, self.minion_machine_id, + tasks.constants.MINION_MACHINE_STATUS_ERROR) + + def test__get_task_name(self): + result = self.task._get_task_name( + self.minion_pool_id, self.minion_machine_id) + + self.assertEqual( + result, + tasks.MINION_POOL_HEALTHCHECK_MACHINE_TASK_NAME_FORMAT % + (self.minion_pool_id, self.minion_machine_id) + ) + + +class MinionMachineHealtchcheckDeciderTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis MinionMachineHealtchcheckDecider class.""" + + def setUp(self): + super(MinionMachineHealtchcheckDeciderTestCase, self).setUp() + self.minion_pool_id = 'test_pool_id' + self.minion_machine_id = 'test_machine_id' + self.healthcheck_task_name = ( + tasks.MINION_POOL_HEALTHCHECK_MACHINE_TASK_NAME_FORMAT % + (self.minion_pool_id, self.minion_machine_id) + ) + + self.task = tasks.MinionMachineHealtchcheckDecider( + self.minion_pool_id, self.minion_machine_id, + on_successful_healthcheck=True) + + def test_call_with_empty_history(self): + history = {} + + result = self.task.__call__(history) + self.assertFalse(result) + + def test_call_with_healthy_result(self): + history = { + self.healthcheck_task_name: { + "healthy": True + } + } + + result = self.task.__call__(history) + self.assertEqual(result, self.task._on_success) + + def test_call_with_unhealthy_result(self): + history = { + self.healthcheck_task_name: { + "healthy": False + } + } + + result = self.task.__call__(history) + self.assertEqual(result, not self.task._on_success) + + +class PowerOnMinionMachineTaskTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis PowerOnMinionMachineTask class.""" + + def setUp(self): + super(PowerOnMinionMachineTaskTestCase, self).setUp() + self.minion_machine = mock.MagicMock() + self.minion_machine.id = 'test_machine_id' + self.minion_machine.pool_id = 'test_pool_id' + self.minion_machine.allocated_action = mock.sentinel.allocation_action + self.minion_pool_id = 'test_pool_id' + self.minion_machine_id = 'test_machine_id' + self.task_info = { + 'minion_provider_properties': None, + } + self.minion_pool_type = tasks.constants.PROVIDER_PLATFORM_SOURCE + self.minion_machine.power_status = ( + tasks.constants.MINION_MACHINE_POWER_STATUS_POWERED_OFF) + + self.task = tasks.PowerOnMinionMachineTask( + self.minion_pool_id, self.minion_machine_id, self.minion_pool_type) + + def test__init__with_different_minion_pool_type(self): + self.minion_pool_type = "destination" + + task = tasks.PowerOnMinionMachineTask( + self.minion_pool_id, self.minion_machine_id, + self.minion_pool_type) + + self.assertEqual( + task._main_task_runner_type, + tasks.constants.TASK_TYPE_POWER_ON_DESTINATION_MINION + ) + + def test__get_task_name(self): + result = self.task._get_task_name( + self.minion_pool_id, self.minion_machine_id) + + self.assertEqual( + result, + tasks.MINION_POOL_POWER_ON_MACHINE_TASK_NAME_FORMAT % + (self.minion_pool_id, self.minion_machine_id) + ) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_set_minion_machine_power_status' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_add_minion_pool_event' + ) + @mock.patch.object(tasks.BaseMinionManangerTask, 'execute') + def test_execute(self, mock_execute, mock_add_minion_pool_event, + mock_set_minion_machine_power_status, + mock_get_minion_machine): + mock_get_minion_machine.return_value = self.minion_machine + self.minion_machine.power_status = ( + tasks.constants.MINION_MACHINE_POWER_STATUS_POWERED_OFF) + self.task_info['minion_provider_properties'] = ( + mock_get_minion_machine.return_value.provider_properties) + + result = self.task.execute( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + self.assertEqual(result, self.task_info) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id, + raise_if_not_found=True) + mock_set_minion_machine_power_status.assert_has_calls([ + mock.call( + mock.sentinel.context, self.minion_pool_id, + self.minion_machine_id, + tasks.constants.MINION_MACHINE_POWER_STATUS_POWERING_ON + ), + mock.call( + mock.sentinel.context, self.minion_pool_id, + self.minion_machine_id, + tasks.constants.MINION_MACHINE_POWER_STATUS_POWERED_ON) + ]) + mock_execute.assert_called_once_with( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + def test_execute_power_on(self, mock_get_minion_machine): + self.minion_machine.power_status = ( + tasks.constants.MINION_MACHINE_POWER_STATUS_POWERED_ON) + mock_get_minion_machine.return_value = self.minion_machine + + result = self.task.execute( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + self.assertEqual(result, self.task_info) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id, + raise_if_not_found=True) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + def test_execute_invalid_power_status(self, mock_get_minion_machine): + mock_get_minion_machine.return_value = self.minion_machine + self.minion_machine.power_status = 'INVALID_POWER_STATUS' + + self.assertRaises( + exception.InvalidMinionMachineState, self.task.execute, + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id, + raise_if_not_found=True) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_set_minion_machine_power_status' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_add_minion_pool_event' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, + '_set_minion_machine_allocation_status' + ) + def test_execute_with_exception( + self, mock_set_minion_machine_allocation, + mock_get_minion_machine, mock_add_minion_pool_event, + mock_set_minion_machine_power_status): + mock_get_minion_machine.return_value = self.minion_machine + mock_set_minion_machine_power_status.side_effect = ( + exception.CoriolisException) + + self.assertRaises( + exception.CoriolisException, self.task.execute, + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id, + raise_if_not_found=True) + mock_set_minion_machine_allocation.assert_called_once_with( + mock.sentinel.context, self.minion_pool_id, self.minion_machine_id, + tasks.constants.MINION_MACHINE_STATUS_POWER_ERROR) + + +class PowerOffMinionMachineTaskTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis PowerOffMinionMachineTask class.""" + + def setUp(self): + super(PowerOffMinionMachineTaskTestCase, self).setUp() + self.minion_machine = mock.MagicMock() + self.minion_machine.id = 'test_machine_id' + self.minion_machine.pool_id = 'test_pool_id' + self.minion_machine.allocated_action = mock.sentinel.allocation_action + self.minion_pool_type = tasks.constants.PROVIDER_PLATFORM_SOURCE + self.minion_pool_id = 'test_pool_id' + self.minion_machine_id = 'test_machine_id' + self.task_info = { + 'minion_provider_properties': None, + } + self.minion_machine.power_status = ( + tasks.constants.MINION_MACHINE_POWER_STATUS_POWERED_ON) + + self.task = tasks.PowerOffMinionMachineTask( + self.minion_pool_id, self.minion_machine_id, self.minion_pool_type) + + def test__init__with_different_minion_pool_type(self): + self.minion_pool_type = "destination" + + task = tasks.PowerOffMinionMachineTask( + self.minion_pool_id, self.minion_machine_id, + self.minion_pool_type) + + self.assertEqual( + task._main_task_runner_type, + tasks.constants.TASK_TYPE_POWER_OFF_DESTINATION_MINION + ) + + def test__get_task_name(self): + result = self.task._get_task_name( + self.minion_pool_id, self.minion_machine_id) + + self.assertEqual( + result, + tasks.MINION_POOL_POWER_OFF_MACHINE_TASK_NAME_FORMAT % + (self.minion_pool_id, self.minion_machine_id) + ) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_set_minion_machine_power_status' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_add_minion_pool_event' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, + '_set_minion_machine_allocation_status' + ) + @mock.patch.object(tasks.BaseMinionManangerTask, 'execute') + def test_execute(self, mock_execute, mock_set_minion_machine_allocation, + mock_get_minion_machine, mock_add_minion_pool_event, + mock_set_minion_machine_power_status): + mock_get_minion_machine.return_value = self.minion_machine + self.task_info['minion_provider_properties'] = ( + mock_get_minion_machine.return_value.provider_properties) + + result = self.task.execute( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + self.assertEqual(result, self.task_info) + + mock_execute.assert_called_once_with( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id, + raise_if_not_found=True) + mock_set_minion_machine_power_status.assert_has_calls([ + mock.call( + mock.sentinel.context, self.minion_pool_id, + self.minion_machine_id, + tasks.constants.MINION_MACHINE_POWER_STATUS_POWERING_OFF + ), + mock.call( + mock.sentinel.context, self.minion_pool_id, + self.minion_machine_id, + tasks.constants.MINION_MACHINE_POWER_STATUS_POWERED_OFF) + ]) + mock_set_minion_machine_allocation.assert_called_once_with( + mock.sentinel.context, self.minion_pool_id, self.minion_machine_id, + tasks.constants.MINION_MACHINE_STATUS_AVAILABLE) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + def test_execute_power_off(self, mock_get_minion_machine): + self.minion_machine.power_status = ( + tasks.constants.MINION_MACHINE_POWER_STATUS_POWERED_OFF) + mock_get_minion_machine.return_value = self.minion_machine + + result = self.task.execute( + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + self.assertEqual(result, self.task_info) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id, + raise_if_not_found=True) + + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_set_minion_machine_power_status' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_add_minion_pool_event' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, '_get_minion_machine' + ) + @mock.patch.object( + tasks.MinionManagerTaskEventMixin, + '_set_minion_machine_allocation_status' + ) + def test_execute_with_exception( + self, mock_set_minion_machine_allocation, + mock_get_minion_machine, mock_add_minion_pool_event, + mock_set_minion_machine_power_status): + mock_get_minion_machine.return_value = self.minion_machine + mock_set_minion_machine_power_status.side_effect = ( + exception.CoriolisException) + + self.assertRaises( + exception.CoriolisException, self.task.execute, + mock.sentinel.context, mock.sentinel.origin, + mock.sentinel.destination, self.task_info) + + mock_get_minion_machine.assert_called_once_with( + mock.sentinel.context, self.minion_machine_id, + raise_if_not_found=True) + mock_set_minion_machine_allocation.assert_called_once_with( + mock.sentinel.context, self.minion_pool_id, self.minion_machine_id, + tasks.constants.MINION_MACHINE_STATUS_POWER_ERROR)