diff --git a/distributedcloud/dcmanager/tests/base.py b/distributedcloud/dcmanager/tests/base.py index ecf81548d..db91424f3 100644 --- a/distributedcloud/dcmanager/tests/base.py +++ b/distributedcloud/dcmanager/tests/base.py @@ -198,6 +198,11 @@ class DCManagerTestCase(base.BaseTestCase): self.mock_sysinv_client = mock_patch.start() self.addCleanup(mock_patch.stop) + def _mock_read_from_cache(self, target): + mock_patch = mock.patch.object(target, '_read_from_cache') + self.mock_read_from_cache = mock_patch.start() + self.addCleanup(mock_patch.stop) + def _mock_get_network_address_pool(self): """Mock phased subcloud deploy's get_network_address_pool""" diff --git a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_finish_strategy.py b/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_finish_strategy.py index 97403d5e2..0ba0e99fe 100644 --- a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_finish_strategy.py +++ b/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_finish_strategy.py @@ -3,38 +3,66 @@ # # SPDX-License-Identifier: Apache-2.0 # + import mock from dcmanager.common import consts +from dcmanager.orchestrator.states.base import BaseState from dcmanager.orchestrator.states.software.finish_strategy import \ FinishStrategyState from dcmanager.tests.unit.orchestrator.states.software.test_base import \ TestSoftwareOrchestrator -REGION_ONE_RELEASES = {"DC.1": {"sw_version": "20.12", - "state": "committed"}, - "DC.2": {"sw_version": "20.12", - "state": "committed"}, - "DC.3": {"sw_version": "20.12", - "state": "committed"}, - "DC.8": {"sw_version": "20.12", - "state": "committed"}} +REGION_ONE_RELEASES = { + "DC.1": { + "sw_version": "20.12", + "state": "committed" + }, + "DC.2": { + "sw_version": "20.12", + "state": "committed" + }, + "DC.3": { + "sw_version": "20.12", + "state": "committed" + }, + "DC.8": { + "sw_version": "20.12", + "state": "committed" + } +} -SUBCLOUD_RELEASES = {"DC.1": {"sw_version": "20.12", - "state": "committed"}, - "DC.2": {"sw_version": "20.12", - "state": "committed"}, - "DC.3": {"sw_version": "20.12", - "state": "deployed"}, - "DC.9": {"sw_version": "20.12", - "state": "available"}} +SUBCLOUD_RELEASES = { + "DC.1": { + "sw_version": "20.12", + "state": "committed" + }, + "DC.2": { + "sw_version": "20.12", + "state": "committed" + }, + "DC.3": { + "sw_version": "20.12", + "state": "deployed" + }, + "DC.9": { + "sw_version": "20.12", + "state": "available" + }, + "DC.10": { + "sw_version": "20.12", + "state": "deployed" + } +} class TestFinishStrategyState(TestSoftwareOrchestrator): def setUp(self): super().setUp() + self._mock_read_from_cache(FinishStrategyState) + self.on_success_state = consts.STRATEGY_STATE_COMPLETE # Add the subcloud being processed by this unit test @@ -51,10 +79,10 @@ class TestFinishStrategyState(TestSoftwareOrchestrator): self.software_client.commit_patch = mock.MagicMock() self._read_from_cache = mock.MagicMock() - @mock.patch.object(FinishStrategyState, '_read_from_cache') - def test_finish_strategy_success(self, mock_read_from_cache): + def test_finish_strategy_success(self): """Test software finish strategy when the API call succeeds.""" - mock_read_from_cache.return_value = REGION_ONE_RELEASES + + self.mock_read_from_cache.return_value = REGION_ONE_RELEASES self.software_client.query.side_effect = [SUBCLOUD_RELEASES] @@ -71,10 +99,10 @@ class TestFinishStrategyState(TestSoftwareOrchestrator): self.assert_step_updated(self.strategy_step.subcloud_id, self.on_success_state) - @mock.patch.object(FinishStrategyState, '_read_from_cache') - def test_finish_strategy_no_operation_required(self, mock_read_from_cache): + def test_finish_strategy_no_operation_required(self): """Test software finish strategy when no operation is required.""" - mock_read_from_cache.return_value = REGION_ONE_RELEASES + + self.mock_read_from_cache.return_value = REGION_ONE_RELEASES self.software_client.query.side_effect = [REGION_ONE_RELEASES] @@ -88,3 +116,62 @@ class TestFinishStrategyState(TestSoftwareOrchestrator): # On success, the state should transition to the next state self.assert_step_updated(self.strategy_step.subcloud_id, self.on_success_state) + + def test_finish_strategy_fails_when_query_exception(self): + """Test finish strategy fails when software client query raises exception""" + + self.mock_read_from_cache.return_value = REGION_ONE_RELEASES + + self.software_client.query.side_effect = Exception() + + self.worker.perform_state_action(self.strategy_step) + + self.assert_step_updated( + self.strategy_step.subcloud_id, consts.STRATEGY_STATE_FAILED + ) + + def test_finish_strategy_fails_when_delete_exception(self): + """Test finish strategy fails when software client delete raises exception""" + + self.mock_read_from_cache.return_value = REGION_ONE_RELEASES + + self.software_client.query.side_effect = [SUBCLOUD_RELEASES] + self.software_client.delete.side_effect = Exception() + + self.worker.perform_state_action(self.strategy_step) + + self.assert_step_updated( + self.strategy_step.subcloud_id, consts.STRATEGY_STATE_FAILED + ) + + @mock.patch.object(BaseState, 'stopped') + def test_finish_strategy_fails_when_stopped(self, mock_base_stopped): + """Test finish strategy fails when stopped""" + self.mock_read_from_cache.return_value = REGION_ONE_RELEASES + + self.software_client.query.side_effect = [SUBCLOUD_RELEASES] + + mock_base_stopped.return_value = True + + self.worker.perform_state_action(self.strategy_step) + + self.assert_step_updated( + self.strategy_step.subcloud_id, consts.STRATEGY_STATE_FAILED + ) + + def test_finish_strategy_fails_when_commit_patch_exception(self): + """Test finish strategy fails when software client commit_patch + + raises exception + """ + + self.mock_read_from_cache.return_value = REGION_ONE_RELEASES + + self.software_client.query.side_effect = [SUBCLOUD_RELEASES] + self.software_client.commit_patch.side_effect = Exception() + + self.worker.perform_state_action(self.strategy_step) + + self.assert_step_updated( + self.strategy_step.subcloud_id, consts.STRATEGY_STATE_FAILED + ) diff --git a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_install_license.py b/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_install_license.py index d8bba31ed..92b350f99 100644 --- a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_install_license.py +++ b/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_install_license.py @@ -6,7 +6,9 @@ import mock +from dccommon import consts as dccommon_consts from dcmanager.common import consts +from dcmanager.db import api as db_api from dcmanager.tests.unit.orchestrator.states.software.test_base import \ TestSoftwareOrchestrator @@ -15,6 +17,8 @@ MISSING_LICENSE_RESPONSE = { "error": "License file not found. A license may not have been installed.", } +GENERIC_ERROR_RESPONSE = {"content": "", "error": "Invalid license"} + LICENSE_VALID_RESPONSE = {"content": "A valid license", "error": ""} ALTERNATE_LICENSE_RESPONSE = {"content": "A different valid license", "error": ""} @@ -69,7 +73,7 @@ class TestInstallLicenseState(TestSoftwareOrchestrator): self.strategy_step.subcloud_id, consts.STRATEGY_STATE_FAILED ) - def test_install_license_success(self): + def test_install_license_succeeds(self): """Test the install license step succeeds. The license will be installed on the subcloud when system controller @@ -147,8 +151,8 @@ class TestInstallLicenseState(TestSoftwareOrchestrator): self.strategy_step.subcloud_id, self.on_success_state ) - def test_install_license_skip_when_no_sys_controller_lic(self): - """Test license install skipped when no license on system controller.""" + def test_install_license_skips_with_sys_controller_without_license(self): + """Test license install skips when sys controller doesn't have a license""" # Only makes one query: to system controller self.sysinv_client.get_license.return_value = MISSING_LICENSE_RESPONSE @@ -163,3 +167,28 @@ class TestInstallLicenseState(TestSoftwareOrchestrator): self.assert_step_updated( self.strategy_step.subcloud_id, self.on_success_state ) + + def test_install_license_fails_with_generic_error_response(self): + """Test license install fails with generic error response""" + + # Only makes one query: to system controller + self.sysinv_client.get_license.return_value = GENERIC_ERROR_RESPONSE + + # invoke the strategy state operation on the orch thread + self.worker.perform_state_action(self.strategy_step) + + subcloud = db_api.subcloud_get(self.ctx, self.subcloud.id) + + self.assertEqual( + subcloud.error_description, "An unexpected error occurred querying the " + f"license {dccommon_consts.SYSTEM_CONTROLLER_NAME}. " + f"Detail: {GENERIC_ERROR_RESPONSE['error']}" + ) + + # Should skip install_license API call + self.sysinv_client.install_license.assert_not_called() + + # Verify it successfully moves to the next step + self.assert_step_updated( + self.strategy_step.subcloud_id, consts.STRATEGY_STATE_FAILED + )