From e741c5d1314d8051f54ccde7fda31f15a8b7a72a Mon Sep 17 00:00:00 2001 From: Christopher Souza Date: Mon, 25 Sep 2023 08:01:31 -0300 Subject: [PATCH] Create finish software strategy Create finish software strategy for the upgrade orchestrator, this state will commit the patches from the subcloud that are committed in systemcontroller and it will delete releases from the subcloud that are available or unavailable and not present in the systemcontroller. Story: 2010676 Task: 48825 Test Case (Patching only): PASS: Apply and commit a patch to the systemcontroller and apply the same patch to the subcloud and run the upgrade orchestration and verify that the patch from the subcloud is committed. PASS: Upload a patch to the subcloud and run the upgrade orchestration and verify that the patch is deleted from the subcloud. PASS: Upload a patch to subcloud, protect the patch metadata file with chattr +i, create and apply a upgrade strategy and verify that the orchestrator failed to delete that patch and the correct error message is presented. Signed-off-by: Christopher Souza Change-Id: I2dae16800235891fc863151b307313732e8d49cd --- .../states/software/finish_strategy.py | 72 ++++++++++++++++++- .../states/software/test_finish_strategy.py | 67 ++++++++++++++++- 2 files changed, 134 insertions(+), 5 deletions(-) diff --git a/distributedcloud/dcmanager/orchestrator/states/software/finish_strategy.py b/distributedcloud/dcmanager/orchestrator/states/software/finish_strategy.py index 0ac19395a..395704147 100644 --- a/distributedcloud/dcmanager/orchestrator/states/software/finish_strategy.py +++ b/distributedcloud/dcmanager/orchestrator/states/software/finish_strategy.py @@ -3,13 +3,16 @@ # # SPDX-License-Identifier: Apache-2.0 # - +from dccommon.drivers.openstack import software_v1 from dcmanager.common import consts +from dcmanager.common.exceptions import StrategyStoppedException from dcmanager.orchestrator.states.base import BaseState +from dcmanager.orchestrator.states.software.cache.cache_specifications import \ + REGION_ONE_RELEASE_USM_CACHE_TYPE class FinishStrategyState(BaseState): - """Finish Strategy software orchestration state""" + """Finish Software Strategy software orchestration state""" def __init__(self, region_name): super(FinishStrategyState, self).__init__( @@ -18,5 +21,68 @@ class FinishStrategyState(BaseState): ) def perform_state_action(self, strategy_step): - """Finish Strategy region status""" + """Finish Software Strategy""" + + self.info_log(strategy_step, "Finishing software strategy") + + regionone_committed_releases = self._read_from_cache( + REGION_ONE_RELEASE_USM_CACHE_TYPE, + state=software_v1.COMMITTED + ) + + self.debug_log(strategy_step, + "regionone_committed_releases: %s" % regionone_committed_releases) + + try: + software_client = self.get_software_client(self.region_name) + subcloud_releases = software_client.query() + except Exception: + message = ("Cannot retrieve subcloud releases. Please see logs for" + " details.") + self.exception_log(strategy_step, message) + raise Exception(message) + + self.debug_log(strategy_step, + "Releases for subcloud: %s" % subcloud_releases) + + releases_to_commit = list() + releases_to_delete = list() + + # For this subcloud, determine which releases should be committed and + # which should be deleted. + for release_id in subcloud_releases: + if (subcloud_releases[release_id]['state'] == + software_v1.AVAILABLE or + subcloud_releases[release_id]['state'] == + software_v1.UNAVAILABLE): + releases_to_delete.append(release_id) + elif subcloud_releases[release_id]['state'] == \ + software_v1.DEPLOYED: + if release_id in regionone_committed_releases: + releases_to_commit.append(release_id) + + if releases_to_delete: + self.info_log(strategy_step, "Deleting releases %s" % releases_to_delete) + try: + software_client.delete(releases_to_delete) + except Exception: + message = ("Cannot delete releases from subcloud. Please see logs for" + " details.") + self.exception_log(strategy_step, message) + raise Exception(message) + + if self.stopped(): + raise StrategyStoppedException() + + if releases_to_commit: + self.info_log(strategy_step, + "Committing releases %s to subcloud" % releases_to_commit) + try: + software_client.commit_patch(releases_to_commit) + except Exception: + message = ("Cannot commit releases to subcloud. Please see logs for" + " details.") + self.exception_log(strategy_step, message) + raise Exception(message) + return self.next_state 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 dbf7b100b..939466d20 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,14 +3,41 @@ # # SPDX-License-Identifier: Apache-2.0 # +import mock +from oslo_config import cfg from dcmanager.common import consts +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"}} + +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"}} + + class TestFinishStrategyState(TestSoftwareOrchestrator): def setUp(self): + p = mock.patch.object(cfg.CONF, 'use_usm') + self.mock_use_usm = p.start() + self.mock_use_usm.return_value = True + self.addCleanup(p.stop) super(TestFinishStrategyState, self).setUp() self.on_success_state = consts.STRATEGY_STATE_COMPLETE @@ -22,11 +49,47 @@ class TestFinishStrategyState(TestSoftwareOrchestrator): self.strategy_step = self.setup_strategy_step( self.subcloud.id, consts.STRATEGY_STATE_SW_FINISH_STRATEGY) - def test_finish_strategy_success(self): - """Test finish strategy when the API call succeeds.""" + # Add mock API endpoints for software client calls + # invoked by this state + self.software_client.query = mock.MagicMock() + self.software_client.delete = mock.MagicMock() + 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): + """Test software finish strategy when the API call succeeds.""" + mock_read_from_cache.return_value = REGION_ONE_RELEASES + + self.software_client.query.side_effect = [SUBCLOUD_RELEASES] + + # invoke the strategy state operation on the orch thread self.worker.perform_state_action(self.strategy_step) + call_args, _ = self.software_client.delete.call_args_list[0] + self.assertItemsEqual(['DC.9'], call_args[0]) + + call_args, _ = self.software_client.commit_patch.call_args_list[0] + self.assertItemsEqual(['DC.3'], call_args[0]) + + # On success, the state should transition to the next state + 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): + """Test software finish strategy when no operation is required.""" + mock_read_from_cache.return_value = REGION_ONE_RELEASES + + self.software_client.query.side_effect = [REGION_ONE_RELEASES] + + # invoke the strategy state operation on the orch thread + self.worker.perform_state_action(self.strategy_step) + + self.software_client.delete.assert_not_called() + + self.software_client.commit_patch.assert_not_called() + # On success, the state should transition to the next state self.assert_step_updated(self.strategy_step.subcloud_id, self.on_success_state)