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.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"""

View File

@ -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
)

View File

@ -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
)