Merge "Improve unit test coverage for dcmanager's orchestrator/states/software"

This commit is contained in:
Zuul 2024-03-27 13:48:21 +00:00 committed by Gerrit Code Review
commit 28903f8bfd
3 changed files with 146 additions and 25 deletions

View File

@ -198,6 +198,11 @@ class DCManagerTestCase(base.BaseTestCase):
self.mock_sysinv_client = mock_patch.start() self.mock_sysinv_client = mock_patch.start()
self.addCleanup(mock_patch.stop) 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): def _mock_get_network_address_pool(self):
"""Mock phased subcloud deploy's get_network_address_pool""" """Mock phased subcloud deploy's get_network_address_pool"""

View File

@ -3,38 +3,66 @@
# #
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# #
import mock import mock
from dcmanager.common import consts from dcmanager.common import consts
from dcmanager.orchestrator.states.base import BaseState
from dcmanager.orchestrator.states.software.finish_strategy import \ from dcmanager.orchestrator.states.software.finish_strategy import \
FinishStrategyState FinishStrategyState
from dcmanager.tests.unit.orchestrator.states.software.test_base import \ from dcmanager.tests.unit.orchestrator.states.software.test_base import \
TestSoftwareOrchestrator TestSoftwareOrchestrator
REGION_ONE_RELEASES = {"DC.1": {"sw_version": "20.12", REGION_ONE_RELEASES = {
"state": "committed"}, "DC.1": {
"DC.2": {"sw_version": "20.12", "sw_version": "20.12",
"state": "committed"}, "state": "committed"
"DC.3": {"sw_version": "20.12", },
"state": "committed"}, "DC.2": {
"DC.8": {"sw_version": "20.12", "sw_version": "20.12",
"state": "committed"}} "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", SUBCLOUD_RELEASES = {
"state": "committed"}, "DC.1": {
"DC.2": {"sw_version": "20.12", "sw_version": "20.12",
"state": "committed"}, "state": "committed"
"DC.3": {"sw_version": "20.12", },
"state": "deployed"}, "DC.2": {
"DC.9": {"sw_version": "20.12", "sw_version": "20.12",
"state": "available"}} "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): class TestFinishStrategyState(TestSoftwareOrchestrator):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self._mock_read_from_cache(FinishStrategyState)
self.on_success_state = consts.STRATEGY_STATE_COMPLETE self.on_success_state = consts.STRATEGY_STATE_COMPLETE
# Add the subcloud being processed by this unit test # Add the subcloud being processed by this unit test
@ -51,10 +79,10 @@ class TestFinishStrategyState(TestSoftwareOrchestrator):
self.software_client.commit_patch = mock.MagicMock() self.software_client.commit_patch = mock.MagicMock()
self._read_from_cache = mock.MagicMock() self._read_from_cache = mock.MagicMock()
@mock.patch.object(FinishStrategyState, '_read_from_cache') def test_finish_strategy_success(self):
def test_finish_strategy_success(self, mock_read_from_cache):
"""Test software finish strategy when the API call succeeds.""" """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] 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.assert_step_updated(self.strategy_step.subcloud_id,
self.on_success_state) self.on_success_state)
@mock.patch.object(FinishStrategyState, '_read_from_cache') def test_finish_strategy_no_operation_required(self):
def test_finish_strategy_no_operation_required(self, mock_read_from_cache):
"""Test software finish strategy when no operation is required.""" """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] 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 # On success, the state should transition to the next state
self.assert_step_updated(self.strategy_step.subcloud_id, self.assert_step_updated(self.strategy_step.subcloud_id,
self.on_success_state) 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
)

View File

@ -6,7 +6,9 @@
import mock import mock
from dccommon import consts as dccommon_consts
from dcmanager.common import consts from dcmanager.common import consts
from dcmanager.db import api as db_api
from dcmanager.tests.unit.orchestrator.states.software.test_base import \ from dcmanager.tests.unit.orchestrator.states.software.test_base import \
TestSoftwareOrchestrator TestSoftwareOrchestrator
@ -15,6 +17,8 @@ MISSING_LICENSE_RESPONSE = {
"error": "License file not found. A license may not have been installed.", "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": ""} LICENSE_VALID_RESPONSE = {"content": "A valid license", "error": ""}
ALTERNATE_LICENSE_RESPONSE = {"content": "A different 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 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. """Test the install license step succeeds.
The license will be installed on the subcloud when system controller 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 self.strategy_step.subcloud_id, self.on_success_state
) )
def test_install_license_skip_when_no_sys_controller_lic(self): def test_install_license_skips_with_sys_controller_without_license(self):
"""Test license install skipped when no license on system controller.""" """Test license install skips when sys controller doesn't have a license"""
# Only makes one query: to system controller # Only makes one query: to system controller
self.sysinv_client.get_license.return_value = MISSING_LICENSE_RESPONSE self.sysinv_client.get_license.return_value = MISSING_LICENSE_RESPONSE
@ -163,3 +167,28 @@ class TestInstallLicenseState(TestSoftwareOrchestrator):
self.assert_step_updated( self.assert_step_updated(
self.strategy_step.subcloud_id, self.on_success_state 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
)