From 9e42f104707e8e0b8f89a11aa8b6dc509058feff Mon Sep 17 00:00:00 2001 From: Hugo Brito Date: Thu, 8 Feb 2024 18:55:58 -0300 Subject: [PATCH] Clean software orchestration states The VIM orchestration will handle the following commands for USM: - software deploy precheck - software deploy start - software deploy host - every lock/unlock/swact needed - software deploy activate - software deploy complete Now, the DC orchestration doesn't need the states to handle the operations above. This commit deletes all unneeded states. Test Plan: PASS: Execute the software orchestration successfully: - Enable the USM orchestration (set use_usm parameter to True) - dcorch configuration file - dcmanager configuration file - Create and apply an upgrade-strategy - Check that 5 states were executed PASS. Tox test successful. Story: 2010676 Task: 49555 Change-Id: I67db35b6807f9ddcedab7c9742816c782daecd68 Signed-off-by: Hugo Brito --- distributedcloud/dcmanager/common/consts.py | 10 - .../orchestrator/software_orch_thread.py | 48 +--- .../software/apply_vim_software_strategy.py | 6 +- .../states/software/deploy_activate.py | 22 -- .../states/software/deploy_complete.py | 22 -- .../states/software/deploy_host.py | 22 -- .../states/software/deploy_pre_check.py | 22 -- .../states/software/deploy_start.py | 94 -------- .../states/software/finish_strategy.py | 22 +- .../states/software/install_license.py | 92 +++++++- .../states/software/lock_controller.py | 27 --- .../orchestrator/states/software/pre_check.py | 4 +- .../states/software/swact_controller0.py | 19 -- .../states/software/swact_controller1.py | 18 -- .../states/software/unlock_controller.py | 22 -- .../orchestrator/states/software/upload.py | 155 ------------- .../test_apply_vim_software_strategy.py | 10 +- .../orchestrator/states/software/test_base.py | 8 +- .../test_create_vim_software_strategy.py | 8 +- .../states/software/test_deploy_activate.py | 32 --- .../states/software/test_deploy_complete.py | 32 --- .../states/software/test_deploy_host.py | 32 --- .../states/software/test_deploy_pre_check.py | 32 --- .../states/software/test_deploy_start.py | 133 ------------ .../states/software/test_finish_strategy.py | 5 +- .../states/software/test_install_license.py | 146 ++++++++++++- .../states/software/test_lock_controller.py | 181 ---------------- .../states/software/test_pre_check.py | 4 +- .../states/software/test_swact_controller.py | 148 ------------- .../states/software/test_unlock_controller.py | 32 --- .../states/software/test_upload.py | 205 ------------------ 31 files changed, 264 insertions(+), 1349 deletions(-) delete mode 100644 distributedcloud/dcmanager/orchestrator/states/software/deploy_activate.py delete mode 100644 distributedcloud/dcmanager/orchestrator/states/software/deploy_complete.py delete mode 100644 distributedcloud/dcmanager/orchestrator/states/software/deploy_host.py delete mode 100644 distributedcloud/dcmanager/orchestrator/states/software/deploy_pre_check.py delete mode 100644 distributedcloud/dcmanager/orchestrator/states/software/deploy_start.py delete mode 100644 distributedcloud/dcmanager/orchestrator/states/software/lock_controller.py delete mode 100644 distributedcloud/dcmanager/orchestrator/states/software/swact_controller0.py delete mode 100644 distributedcloud/dcmanager/orchestrator/states/software/swact_controller1.py delete mode 100644 distributedcloud/dcmanager/orchestrator/states/software/unlock_controller.py delete mode 100644 distributedcloud/dcmanager/orchestrator/states/software/upload.py delete mode 100644 distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_deploy_activate.py delete mode 100644 distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_deploy_complete.py delete mode 100644 distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_deploy_host.py delete mode 100644 distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_deploy_pre_check.py delete mode 100644 distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_deploy_start.py delete mode 100644 distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_lock_controller.py delete mode 100644 distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_swact_controller.py delete mode 100644 distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_unlock_controller.py delete mode 100644 distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_upload.py diff --git a/distributedcloud/dcmanager/common/consts.py b/distributedcloud/dcmanager/common/consts.py index 25948563e..5f618280e 100644 --- a/distributedcloud/dcmanager/common/consts.py +++ b/distributedcloud/dcmanager/common/consts.py @@ -154,16 +154,6 @@ STRATEGY_STATE_DELETING_LOAD = "deleting load" # Software orchestration states STRATEGY_STATE_SW_PRE_CHECK = "software pre check" STRATEGY_STATE_SW_INSTALL_LICENSE = "software install license" -STRATEGY_STATE_SW_UPLOAD = "software upload" -STRATEGY_STATE_SW_DEPLOY_PRE_CHECK = "software deploy pre check" -STRATEGY_STATE_SW_DEPLOY_START = "software deploy start" -STRATEGY_STATE_SW_LOCK_CONTROLLER = "software lock controller" -STRATEGY_STATE_SW_UNLOCK_CONTROLLER = "software unlock controller" -STRATEGY_STATE_SW_SWACT_CONTROLLER_0 = "software swact controller-0" -STRATEGY_STATE_SW_SWACT_CONTROLLER_1 = "software swact controller-1" -STRATEGY_STATE_SW_DEPLOY_HOST = "software deploy host" -STRATEGY_STATE_SW_DEPLOY_ACTIVATE = "software deploy activate" -STRATEGY_STATE_SW_DEPLOY_COMPLETE = "software deploy complete" STRATEGY_STATE_SW_CREATE_VIM_STRATEGY = "create VIM software strategy" STRATEGY_STATE_SW_APPLY_VIM_STRATEGY = "apply VIM software strategy" STRATEGY_STATE_SW_FINISH_STRATEGY = "finish software strategy" diff --git a/distributedcloud/dcmanager/orchestrator/software_orch_thread.py b/distributedcloud/dcmanager/orchestrator/software_orch_thread.py index 64c3c3774..f22dc97ab 100644 --- a/distributedcloud/dcmanager/orchestrator/software_orch_thread.py +++ b/distributedcloud/dcmanager/orchestrator/software_orch_thread.py @@ -1,11 +1,9 @@ # -# Copyright (c) 2023 Wind River Systems, Inc. +# Copyright (c) 2023-2024 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # -from oslo_log import log as logging - from dccommon.drivers.openstack import vim from dcmanager.common import consts from dcmanager.orchestrator.orch_thread import OrchThread @@ -15,34 +13,11 @@ from dcmanager.orchestrator.states.software.cache.shared_cache_repository import SharedCacheRepository from dcmanager.orchestrator.states.software.create_vim_software_strategy \ import CreateVIMSoftwareStrategyState -from dcmanager.orchestrator.states.software.deploy_activate \ - import DeployActivateState -from dcmanager.orchestrator.states.software.deploy_complete \ - import DeployCompleteState -from dcmanager.orchestrator.states.software.deploy_host \ - import DeployHostState -from dcmanager.orchestrator.states.software.deploy_pre_check \ - import DeployPreCheckState -from dcmanager.orchestrator.states.software.deploy_start \ - import DeployStartState from dcmanager.orchestrator.states.software.finish_strategy \ import FinishStrategyState from dcmanager.orchestrator.states.software.install_license \ import InstallLicenseState -from dcmanager.orchestrator.states.software.lock_controller \ - import LockControllerState -from dcmanager.orchestrator.states.software.pre_check \ - import PreCheckState -from dcmanager.orchestrator.states.software.swact_controller0 \ - import SwactController0State -from dcmanager.orchestrator.states.software.swact_controller1 \ - import SwactController1State -from dcmanager.orchestrator.states.software.unlock_controller \ - import UnlockControllerState -from dcmanager.orchestrator.states.software.upload \ - import UploadState - -LOG = logging.getLogger(__name__) +from dcmanager.orchestrator.states.software.pre_check import PreCheckState class SoftwareOrchThread(OrchThread): @@ -66,26 +41,16 @@ class SoftwareOrchThread(OrchThread): STATE_OPERATORS = { consts.STRATEGY_STATE_SW_PRE_CHECK: PreCheckState, consts.STRATEGY_STATE_SW_INSTALL_LICENSE: InstallLicenseState, - consts.STRATEGY_STATE_SW_UPLOAD: UploadState, - consts.STRATEGY_STATE_SW_DEPLOY_PRE_CHECK: DeployPreCheckState, - consts.STRATEGY_STATE_SW_DEPLOY_START: DeployStartState, - consts.STRATEGY_STATE_SW_LOCK_CONTROLLER: LockControllerState, - consts.STRATEGY_STATE_SW_DEPLOY_HOST: DeployHostState, - consts.STRATEGY_STATE_SW_UNLOCK_CONTROLLER: UnlockControllerState, - consts.STRATEGY_STATE_SW_SWACT_CONTROLLER_0: SwactController0State, consts.STRATEGY_STATE_SW_CREATE_VIM_STRATEGY: CreateVIMSoftwareStrategyState, consts.STRATEGY_STATE_SW_APPLY_VIM_STRATEGY: ApplyVIMSoftwareStrategyState, - consts.STRATEGY_STATE_SW_SWACT_CONTROLLER_1: SwactController1State, - consts.STRATEGY_STATE_SW_DEPLOY_ACTIVATE: DeployActivateState, - consts.STRATEGY_STATE_SW_DEPLOY_COMPLETE: DeployCompleteState, consts.STRATEGY_STATE_SW_FINISH_STRATEGY: FinishStrategyState, } def __init__(self, strategy_lock, audit_rpc_client): - super(SoftwareOrchThread, self).__init__( + super().__init__( strategy_lock, audit_rpc_client, - consts.SW_UPDATE_TYPE_UPGRADE, # software update strategy type + consts.SW_UPDATE_TYPE_SOFTWARE, # software update strategy type vim.STRATEGY_NAME_SW_UPGRADE, # strategy type used by vim consts.STRATEGY_STATE_SW_PRE_CHECK) # starting state @@ -100,10 +65,9 @@ class SoftwareOrchThread(OrchThread): def pre_apply_setup(self): # Restart caches for next strategy self._shared_caches.initialize_caches() - super(SoftwareOrchThread, self).pre_apply_setup() + super().pre_apply_setup() def determine_state_operator(self, strategy_step): - state = super(SoftwareOrchThread, self).determine_state_operator( - strategy_step) + state = super().determine_state_operator(strategy_step) state.add_shared_caches(self._shared_caches) return state diff --git a/distributedcloud/dcmanager/orchestrator/states/software/apply_vim_software_strategy.py b/distributedcloud/dcmanager/orchestrator/states/software/apply_vim_software_strategy.py index 8aa535d6a..0e1ebdaf3 100644 --- a/distributedcloud/dcmanager/orchestrator/states/software/apply_vim_software_strategy.py +++ b/distributedcloud/dcmanager/orchestrator/states/software/apply_vim_software_strategy.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2023 Wind River Systems, Inc. +# Copyright (c) 2023-2024 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -12,8 +12,8 @@ class ApplyVIMSoftwareStrategyState(BaseState): """Apply VIM Software Strategy software orchestration state""" def __init__(self, region_name): - super(ApplyVIMSoftwareStrategyState, self).__init__( - next_state=consts.STRATEGY_STATE_SW_SWACT_CONTROLLER_1, + super().__init__( + next_state=consts.STRATEGY_STATE_SW_FINISH_STRATEGY, region_name=region_name ) diff --git a/distributedcloud/dcmanager/orchestrator/states/software/deploy_activate.py b/distributedcloud/dcmanager/orchestrator/states/software/deploy_activate.py deleted file mode 100644 index 88fbb2822..000000000 --- a/distributedcloud/dcmanager/orchestrator/states/software/deploy_activate.py +++ /dev/null @@ -1,22 +0,0 @@ -# -# Copyright (c) 2023 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -from dcmanager.common import consts -from dcmanager.orchestrator.states.base import BaseState - - -class DeployActivateState(BaseState): - """Deploy activate software orchestration state""" - - def __init__(self, region_name): - super(DeployActivateState, self).__init__( - next_state=consts.STRATEGY_STATE_SW_DEPLOY_COMPLETE, - region_name=region_name, - ) - - def perform_state_action(self, strategy_step): - """Deploy Activate region status""" - return self.next_state diff --git a/distributedcloud/dcmanager/orchestrator/states/software/deploy_complete.py b/distributedcloud/dcmanager/orchestrator/states/software/deploy_complete.py deleted file mode 100644 index eb764a4a7..000000000 --- a/distributedcloud/dcmanager/orchestrator/states/software/deploy_complete.py +++ /dev/null @@ -1,22 +0,0 @@ -# -# Copyright (c) 2023 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -from dcmanager.common import consts -from dcmanager.orchestrator.states.base import BaseState - - -class DeployCompleteState(BaseState): - """Deploy complete software orchestration state""" - - def __init__(self, region_name): - super(DeployCompleteState, self).__init__( - next_state=consts.STRATEGY_STATE_SW_FINISH_STRATEGY, - region_name=region_name, - ) - - def perform_state_action(self, strategy_step): - """Deploy complete region status""" - return self.next_state diff --git a/distributedcloud/dcmanager/orchestrator/states/software/deploy_host.py b/distributedcloud/dcmanager/orchestrator/states/software/deploy_host.py deleted file mode 100644 index 8df969d78..000000000 --- a/distributedcloud/dcmanager/orchestrator/states/software/deploy_host.py +++ /dev/null @@ -1,22 +0,0 @@ -# -# Copyright (c) 2023 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -from dcmanager.common import consts -from dcmanager.orchestrator.states.base import BaseState - - -class DeployHostState(BaseState): - """Deploy host software orchestration state""" - - def __init__(self, region_name): - super(DeployHostState, self).__init__( - next_state=consts.STRATEGY_STATE_SW_UNLOCK_CONTROLLER, - region_name=region_name, - ) - - def perform_state_action(self, strategy_step): - """Deploy host region status""" - return self.next_state diff --git a/distributedcloud/dcmanager/orchestrator/states/software/deploy_pre_check.py b/distributedcloud/dcmanager/orchestrator/states/software/deploy_pre_check.py deleted file mode 100644 index 175dab8b5..000000000 --- a/distributedcloud/dcmanager/orchestrator/states/software/deploy_pre_check.py +++ /dev/null @@ -1,22 +0,0 @@ -# -# Copyright (c) 2023 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -from dcmanager.common import consts -from dcmanager.orchestrator.states.base import BaseState - - -class DeployPreCheckState(BaseState): - """Deploy pre check software orchestration state""" - - def __init__(self, region_name): - super(DeployPreCheckState, self).__init__( - next_state=consts.STRATEGY_STATE_SW_DEPLOY_START, - region_name=region_name, - ) - - def perform_state_action(self, strategy_step): - """Deploy pre check region status""" - return self.next_state diff --git a/distributedcloud/dcmanager/orchestrator/states/software/deploy_start.py b/distributedcloud/dcmanager/orchestrator/states/software/deploy_start.py deleted file mode 100644 index 1b4b88fcb..000000000 --- a/distributedcloud/dcmanager/orchestrator/states/software/deploy_start.py +++ /dev/null @@ -1,94 +0,0 @@ -# -# Copyright (c) 2023-2024 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -from dccommon.drivers.openstack import software_v1 -from dcmanager.common import consts -from dcmanager.orchestrator.states.base import BaseState -from dcmanager.orchestrator.states.software.cache.cache_specifications import \ - REGION_ONE_RELEASE_USM_CACHE_TYPE - -# Max time: 1 minute = 6 queries x 10 seconds between -DEFAULT_MAX_QUERIES = 6 -DEFAULT_SLEEP_DURATION = 10 - - -class DeployStartState(BaseState): - """Software orchestration state for deploy start releases""" - - def __init__(self, region_name): - super(DeployStartState, self).__init__( - next_state=consts.STRATEGY_STATE_SW_DEPLOY_HOST, - region_name=region_name) - self.max_queries = DEFAULT_MAX_QUERIES - self.sleep_duration = DEFAULT_SLEEP_DURATION - - def _get_deployed_controller_patches(self): - regionone_releases = self._read_from_cache(REGION_ONE_RELEASE_USM_CACHE_TYPE) - deployed_releases = {} - for release_id, release_info in regionone_releases.items(): - if release_info['state'] == software_v1.DEPLOYED: - deployed_releases[release_id] = release_info - return deployed_releases - - def perform_state_action(self, strategy_step): - """Deploy start releases in this subcloud""" - self.info_log(strategy_step, "Applying releases") - deployed_releases = self._get_deployed_controller_patches() - self.debug_log(strategy_step, - f"SystemController deployed releases: {deployed_releases}") - - # Find the max version deployed on the SystemController - max_version = None - for deployed_releases_values in deployed_releases.values(): - release_sw_version = deployed_releases_values['sw_version'] - if max_version is None or release_sw_version > max_version: - max_version = release_sw_version - - # Retrieve all subcloud releases - try: - subcloud_releases = self.get_software_client(self.region_name).query() - self.debug_log(strategy_step, - f"Subcloud releases: {subcloud_releases}") - except Exception: - message = ("Cannot retrieve subcloud releases. Please see logs for " - "details.") - self.exception_log(strategy_step, message) - raise Exception(message) - - deploy_start_release = None - - for release_id in subcloud_releases: - is_reboot_required = subcloud_releases[release_id][ - 'reboot_required'] == "Y" - is_available = subcloud_releases[release_id]['state'] == ( - software_v1.AVAILABLE) - is_deployed = subcloud_releases[release_id]['state'] == ( - software_v1.DEPLOYED) - release_sw_version = subcloud_releases[release_id]['sw_version'] - - # Check if any release is reboot required - if deployed_releases.get( - release_id) and is_available and is_reboot_required: - self.override_next_state(consts.STRATEGY_STATE_SW_LOCK_CONTROLLER) - - # Get the only release needed to be deployed - if (is_deployed and release_sw_version == max_version) or ( - is_available and release_sw_version == max_version): - deploy_start_release = release_id - - if deploy_start_release: - self.info_log(strategy_step, - f"Deploy start release {deploy_start_release} to subcloud") - try: - self.get_software_client(self.region_name).deploy_start( - deploy_start_release) - except Exception: - message = ( - "Cannot deploy start 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/orchestrator/states/software/finish_strategy.py b/distributedcloud/dcmanager/orchestrator/states/software/finish_strategy.py index 14a5f9607..c1d2b7d78 100644 --- a/distributedcloud/dcmanager/orchestrator/states/software/finish_strategy.py +++ b/distributedcloud/dcmanager/orchestrator/states/software/finish_strategy.py @@ -15,7 +15,7 @@ class FinishStrategyState(BaseState): """Finish Software Strategy software orchestration state""" def __init__(self, region_name): - super(FinishStrategyState, self).__init__( + super().__init__( next_state=consts.STRATEGY_STATE_COMPLETE, region_name=region_name, ) @@ -32,20 +32,20 @@ class FinishStrategyState(BaseState): self.debug_log( strategy_step, - "regionone_committed_releases: %s" % regionone_committed_releases + f"regionone_committed_releases: {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.") + 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) + f"Releases for subcloud: {subcloud_releases}") releases_to_commit = list() releases_to_delete = list() @@ -58,13 +58,13 @@ class FinishStrategyState(BaseState): subcloud_releases[release_id]['state'] == software_v1.UNAVAILABLE): releases_to_delete.append(release_id) - elif subcloud_releases[release_id]['state'] == \ - software_v1.DEPLOYED: + 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) + self.info_log(strategy_step, f"Deleting releases {releases_to_delete}") try: software_client.delete(releases_to_delete) except Exception: @@ -78,12 +78,12 @@ class FinishStrategyState(BaseState): if releases_to_commit: self.info_log(strategy_step, - "Committing releases %s to subcloud" % releases_to_commit) + f"Committing releases {releases_to_commit} to subcloud") try: software_client.commit_patch(releases_to_commit) except Exception: - message = ("Cannot commit releases to subcloud. Please see logs for" - " details.") + message = ("Cannot commit releases to subcloud. Please see logs for " + "details.") self.exception_log(strategy_step, message) raise Exception(message) diff --git a/distributedcloud/dcmanager/orchestrator/states/software/install_license.py b/distributedcloud/dcmanager/orchestrator/states/software/install_license.py index 7b7ef8651..76849e0d8 100644 --- a/distributedcloud/dcmanager/orchestrator/states/software/install_license.py +++ b/distributedcloud/dcmanager/orchestrator/states/software/install_license.py @@ -1,22 +1,102 @@ # -# Copyright (c) 2023 Wind River Systems, Inc. +# Copyright (c) 2020-2024 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # +from dccommon import consts as dccommon_consts from dcmanager.common import consts +from dcmanager.common import exceptions +from dcmanager.db import api as db_api from dcmanager.orchestrator.states.base import BaseState +from dcmanager.orchestrator.states.software.cache.cache_specifications import \ + REGION_ONE_LICENSE_CACHE_TYPE + +# When a license is not installed, this will be part of the API error string +LICENSE_FILE_NOT_FOUND_SUBSTRING = "License file not found" class InstallLicenseState(BaseState): - """Install license software orchestration state""" + """Software orchestration state action for installing a license""" def __init__(self, region_name): - super(InstallLicenseState, self).__init__( - next_state=consts.STRATEGY_STATE_SW_UPLOAD, - region_name=region_name, + super().__init__( + next_state=consts.STRATEGY_STATE_SW_CREATE_VIM_STRATEGY, + region_name=region_name ) + @staticmethod + def license_up_to_date(target_license, existing_license): + return target_license == existing_license + def perform_state_action(self, strategy_step): - """Install license region status""" + """Install the License for a software deploy in this subcloud + + Returns the next state in the state machine on success. + Any exceptions raised by this method set the strategy to FAILED. + """ + + # check if the system controller has a license + system_controller_license = self._read_from_cache( + REGION_ONE_LICENSE_CACHE_TYPE) + # get_license returns a dictionary with keys: content and error + # 'content' can be an empty string in success or failure case. + # 'error' is an empty string only in success case. + target_license = system_controller_license.get('content') + target_error = system_controller_license.get('error') + + # If the system controller does not have a license, do not attempt + # to install licenses on subclouds, simply proceed to the next stage + if len(target_error) != 0: + if LICENSE_FILE_NOT_FOUND_SUBSTRING in target_error: + self.info_log(strategy_step, + f"System Controller License missing: {target_error}.") + return self.next_state + else: + # An unexpected error occurred querying the license + message = ('An unexpected error occurred querying the license ' + f'{dccommon_consts.SYSTEM_CONTROLLER_NAME}. ' + f'Detail: {target_error}') + db_api.subcloud_update( + self.context, strategy_step.subcloud_id, + error_description=message[0:consts.ERROR_DESCRIPTION_LENGTH]) + raise exceptions.LicenseInstallError( + subcloud_id=dccommon_consts.SYSTEM_CONTROLLER_NAME, + error_message=target_error) + + # retrieve the keystone session for the subcloud and query its license + subcloud_sysinv_client = self.get_sysinv_client( + strategy_step.subcloud.region_name) + subcloud_license_response = subcloud_sysinv_client.get_license() + subcloud_license = subcloud_license_response.get('content') + subcloud_error = subcloud_license_response.get('error') + + # Skip license install if the license is already up to date + # If there was not an error, there might be a license + if len(subcloud_error) == 0: + if self.license_up_to_date(target_license, subcloud_license): + self.info_log(strategy_step, "License up to date.") + return self.next_state + else: + self.debug_log(strategy_step, "License mismatch. Updating.") + else: + self.debug_log(strategy_step, "License missing. Installing.") + + # Install the license + install_rc = subcloud_sysinv_client.install_license(target_license) + install_error = install_rc.get('error') + if len(install_error) != 0: + # Save error response from sysinv into subcloud error description. + # Provide exception with sysinv error response to strategy_step details + message = ('Error installing license on subcloud ' + f'{strategy_step.subcloud.name}. Detail: {install_error}') + db_api.subcloud_update( + self.context, strategy_step.subcloud_id, + error_description=message[0:consts.ERROR_DESCRIPTION_LENGTH]) + raise exceptions.LicenseInstallError( + subcloud_id=strategy_step.subcloud_id, + error_message=install_error) + + # The license has been successfully installed. Move to the next stage + self.info_log(strategy_step, "License installed.") return self.next_state diff --git a/distributedcloud/dcmanager/orchestrator/states/software/lock_controller.py b/distributedcloud/dcmanager/orchestrator/states/software/lock_controller.py deleted file mode 100644 index e5ca17b64..000000000 --- a/distributedcloud/dcmanager/orchestrator/states/software/lock_controller.py +++ /dev/null @@ -1,27 +0,0 @@ -# -# Copyright (c) 2023 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -from dcmanager.common import consts -from dcmanager.orchestrator.states.lock_host import LockHostState - - -class LockControllerState(LockHostState): - """Lock controller software orchestration state""" - - def __init__(self, region_name): - super(LockControllerState, self).__init__( - next_state=consts.STRATEGY_STATE_SW_DEPLOY_HOST, - region_name=region_name, - hostname=self.get_hostname(region_name) - ) - - def get_hostname(self, region_name): - subcloud_type = self.get_sysinv_client( - region_name).get_system().system_mode - if subcloud_type == consts.SYSTEM_MODE_SIMPLEX: - return "controller-0" - else: - return "controller-1" diff --git a/distributedcloud/dcmanager/orchestrator/states/software/pre_check.py b/distributedcloud/dcmanager/orchestrator/states/software/pre_check.py index 592a9a07d..a74a30822 100644 --- a/distributedcloud/dcmanager/orchestrator/states/software/pre_check.py +++ b/distributedcloud/dcmanager/orchestrator/states/software/pre_check.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2023 Wind River Systems, Inc. +# Copyright (c) 2023-2024 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -12,7 +12,7 @@ class PreCheckState(BaseState): """Pre check software orchestration state""" def __init__(self, region_name): - super(PreCheckState, self).__init__( + super().__init__( next_state=consts.STRATEGY_STATE_SW_INSTALL_LICENSE, region_name=region_name, ) diff --git a/distributedcloud/dcmanager/orchestrator/states/software/swact_controller0.py b/distributedcloud/dcmanager/orchestrator/states/software/swact_controller0.py deleted file mode 100644 index 9fdb9e5ef..000000000 --- a/distributedcloud/dcmanager/orchestrator/states/software/swact_controller0.py +++ /dev/null @@ -1,19 +0,0 @@ -# -# Copyright (c) 2023 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -from dcmanager.common import consts -from dcmanager.orchestrator.states.swact_host import SwactHostState - - -class SwactController0State(SwactHostState): - """Software orchestration state to swact away from controller-0""" - - def __init__(self, region_name): - super(SwactController0State, self).__init__( - next_state=consts.STRATEGY_STATE_SW_CREATE_VIM_STRATEGY, - region_name=region_name, - active="controller-1", - standby="controller-0",) diff --git a/distributedcloud/dcmanager/orchestrator/states/software/swact_controller1.py b/distributedcloud/dcmanager/orchestrator/states/software/swact_controller1.py deleted file mode 100644 index 22f8189ef..000000000 --- a/distributedcloud/dcmanager/orchestrator/states/software/swact_controller1.py +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2023 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# -from dcmanager.common import consts -from dcmanager.orchestrator.states.swact_host import SwactHostState - - -class SwactController1State(SwactHostState): - """Software orchestration state to swact away from controller-1""" - - def __init__(self, region_name): - super(SwactController1State, self).__init__( - next_state=consts.STRATEGY_STATE_SW_DEPLOY_ACTIVATE, - region_name=region_name, - active="controller-0", - standby="controller-1") diff --git a/distributedcloud/dcmanager/orchestrator/states/software/unlock_controller.py b/distributedcloud/dcmanager/orchestrator/states/software/unlock_controller.py deleted file mode 100644 index c0d60de0d..000000000 --- a/distributedcloud/dcmanager/orchestrator/states/software/unlock_controller.py +++ /dev/null @@ -1,22 +0,0 @@ -# -# Copyright (c) 2023 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -from dcmanager.common import consts -from dcmanager.orchestrator.states.base import BaseState - - -class UnlockControllerState(BaseState): - """Unlock controller software orchestration state""" - - def __init__(self, region_name): - super(UnlockControllerState, self).__init__( - next_state=consts.STRATEGY_STATE_SW_DEPLOY_ACTIVATE, - region_name=region_name, - ) - - def perform_state_action(self, strategy_step): - """Unlock controller region status""" - return self.next_state diff --git a/distributedcloud/dcmanager/orchestrator/states/software/upload.py b/distributedcloud/dcmanager/orchestrator/states/software/upload.py deleted file mode 100644 index ff890d367..000000000 --- a/distributedcloud/dcmanager/orchestrator/states/software/upload.py +++ /dev/null @@ -1,155 +0,0 @@ -# -# Copyright (c) 2023-2024 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -import os -import time - -from dccommon.drivers.openstack import software_v1 -from dcmanager.common import consts -from dcmanager.common.exceptions import StrategyStoppedException -from dcmanager.common import utils -from dcmanager.orchestrator.states.base import BaseState -from dcmanager.orchestrator.states.software.cache.cache_specifications import \ - REGION_ONE_RELEASE_USM_CACHE_TYPE - -# Max time: 30 minutes = 180 queries x 10 seconds between -DEFAULT_MAX_QUERIES = 180 -DEFAULT_SLEEP_DURATION = 10 - - -class UploadState(BaseState): - """Software orchestration state for uploading releases""" - - def __init__(self, region_name): - super(UploadState, self).__init__( - next_state=consts.STRATEGY_STATE_SW_DEPLOY_PRE_CHECK, - region_name=region_name) - self.sleep_duration = DEFAULT_SLEEP_DURATION - self.max_queries = DEFAULT_MAX_QUERIES - - def _get_major_minor_versions(self, release_sw_version): - return release_sw_version.rsplit('.', 1) - - def _find_missing_patches(self, subcloud_releases, - potential_missing_patches): - - return [potential_missing_patch for potential_missing_patch - in potential_missing_patches - if potential_missing_patch not in subcloud_releases] - - def perform_state_action(self, strategy_step): - """Upload releases in this subcloud""" - self.info_log(strategy_step, "Uploading releases") - - regionone_releases = self._read_from_cache(REGION_ONE_RELEASE_USM_CACHE_TYPE) - applied_releases_ids = list() - for release_id in regionone_releases: - if regionone_releases[release_id]['state'] in [ - software_v1.DEPLOYED, - software_v1.COMMITTED]: - applied_releases_ids.append(release_id) - - # Retrieve all subcloud releases - try: - subcloud_releases = self.get_software_client( - self.region_name).query() - except Exception: - message = ("Cannot retrieve subcloud releases. Please " - "see /var/log/software.log for details.") - self.exception_log(strategy_step, message) - raise Exception(message) - - releases_to_upload = [] - - # RegionOne applied releases not present on the subcloud needs to be uploaded - for release_id in applied_releases_ids: - if release_id not in subcloud_releases: - self.info_log(strategy_step, (f"Release {release_id} missing from " - "subloud")) - releases_to_upload.append(release_id) - - if releases_to_upload: - self.info_log(strategy_step, - f"Uploading releases {releases_to_upload} to subcloud") - - files_to_upload = [] - potential_missing_patches = [] - iso_release = None - for release in releases_to_upload: - major_sw_version, minor_sw_version = self._get_major_minor_versions( - regionone_releases[release]['sw_version']) - - # when minor is 0, it means that the release is an iso - if minor_sw_version == consts.ISO_VERSION: - iso_path, sig_path = utils.get_vault_load_files(major_sw_version) - files_to_upload.extend([iso_path, sig_path]) - iso_release = release - else: - patch_path = (f"{consts.RELEASE_VAULT_DIR}/" - f"{major_sw_version}/{release}.patch") - if not os.path.isfile(patch_path): - # patch wasn't found but it may be included in an iso - potential_missing_patches.append(release) - else: - files_to_upload.append(patch_path) - if files_to_upload: - try: - self.get_software_client( - self.region_name).upload(files_to_upload) - except Exception: - message = ("Cannot upload releases to subcloud. Please " - "see /var/log/software.log for details.") - self.exception_log(strategy_step, message) - raise Exception(message) - - if self.stopped(): - self.info_log(strategy_step, "Exiting because task was stopped") - raise StrategyStoppedException() - - if iso_release: - audit_counter = 0 - while True: - time.sleep(self.sleep_duration) - - if self.stopped(): - raise StrategyStoppedException() - - try: - subcloud_releases = self.get_software_client( - self.region_name).query() - except Exception: - self.debug_log(strategy_step, "failed to retrieve releases.") - - if iso_release in subcloud_releases: - if potential_missing_patches: - # Retrieve patches that are present in the system - # controller and not in the subcloud after uploading - # load to the subcloud. - missing_patches = self. \ - _find_missing_patches(subcloud_releases, - potential_missing_patches) - - if missing_patches: - message = (f"Release files {missing_patches} " - "are missing") - self.error_log(strategy_step, message) - raise Exception(message) - break - audit_counter += 1 - if audit_counter >= self.max_queries: - details = ("Timeout waiting for load import to complete. " - "Please check software.log on the subcloud.") - self.exception_log(strategy_step, details) - raise Exception(details) - else: - # No load was uploaded therefore the patches are really missing. - if potential_missing_patches: - message = (f"Release files {potential_missing_patches} " - "are missing") - self.error_log(strategy_step, message) - raise Exception(message) - - return self.next_state diff --git a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_apply_vim_software_strategy.py b/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_apply_vim_software_strategy.py index 3d43caa8c..098ea5495 100644 --- a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_apply_vim_software_strategy.py +++ b/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_apply_vim_software_strategy.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2023 Wind River Systems, Inc. +# Copyright (c) 2023-2024 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -11,9 +11,9 @@ from dcmanager.tests.unit.orchestrator.states.software.test_base import \ class TestApplyVIMSoftwareStrategyState(TestSoftwareOrchestrator): def setUp(self): - super(TestApplyVIMSoftwareStrategyState, self).setUp() + super().setUp() - self.on_success_state = consts.STRATEGY_STATE_SW_SWACT_CONTROLLER_1 + self.on_success_state = consts.STRATEGY_STATE_SW_FINISH_STRATEGY # Add the subcloud being processed by this unit test self.subcloud = self.setup_subcloud() @@ -28,5 +28,5 @@ class TestApplyVIMSoftwareStrategyState(TestSoftwareOrchestrator): self.worker.perform_state_action(self.strategy_step) # On success, the state should transition to the next state - self.assert_step_updated(self.strategy_step.subcloud_id, - self.on_success_state) + self.assert_step_updated( + self.strategy_step.subcloud_id, self.on_success_state) diff --git a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_base.py b/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_base.py index a958e571b..f5d2ee299 100644 --- a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_base.py +++ b/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_base.py @@ -18,15 +18,15 @@ class TestSoftwareOrchestrator(TestSwUpdate): DEFAULT_STRATEGY_TYPE = consts.SW_UPDATE_TYPE_SOFTWARE def setUp(self): - super(TestSoftwareOrchestrator, self).setUp() + super().setUp() # Modify cache helpers to return client mocks self.software_cache_client_mock = mock.patch( - "%s.get_software_client" % CACHE_CLIENT_PATH, + f"{CACHE_CLIENT_PATH}.get_software_client", return_value=self.software_client, ) self.sysinv_cache_client_mock = mock.patch( - "%s.get_sysinv_client" % CACHE_CLIENT_PATH, + f"{CACHE_CLIENT_PATH}.get_sysinv_client", return_value=self.sysinv_client ) self.software_cache_client_mock.start() @@ -35,4 +35,4 @@ class TestSoftwareOrchestrator(TestSwUpdate): def tearDown(self): self.software_cache_client_mock.stop() self.sysinv_cache_client_mock.stop() - super(TestSoftwareOrchestrator, self).tearDown() + super().tearDown() diff --git a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_create_vim_software_strategy.py b/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_create_vim_software_strategy.py index e0ed3fac0..a9f8248dc 100644 --- a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_create_vim_software_strategy.py +++ b/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_create_vim_software_strategy.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2023 Wind River Systems, Inc. +# Copyright (c) 2023-2024 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -11,7 +11,7 @@ from dcmanager.tests.unit.orchestrator.states.software.test_base import \ class TestCreateVIMSoftwareStrategyState(TestSoftwareOrchestrator): def setUp(self): - super(TestCreateVIMSoftwareStrategyState, self).setUp() + super().setUp() self.on_success_state = consts.STRATEGY_STATE_SW_APPLY_VIM_STRATEGY @@ -28,5 +28,5 @@ class TestCreateVIMSoftwareStrategyState(TestSoftwareOrchestrator): self.worker.perform_state_action(self.strategy_step) # On success, the state should transition to the next state - self.assert_step_updated(self.strategy_step.subcloud_id, - self.on_success_state) + self.assert_step_updated( + self.strategy_step.subcloud_id, self.on_success_state) diff --git a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_deploy_activate.py b/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_deploy_activate.py deleted file mode 100644 index 411d4b4ac..000000000 --- a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_deploy_activate.py +++ /dev/null @@ -1,32 +0,0 @@ -# -# Copyright (c) 2023 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -from dcmanager.common import consts -from dcmanager.tests.unit.orchestrator.states.software.test_base import \ - TestSoftwareOrchestrator - - -class TestDeployActivateState(TestSoftwareOrchestrator): - def setUp(self): - super(TestDeployActivateState, self).setUp() - - self.on_success_state = consts.STRATEGY_STATE_SW_DEPLOY_COMPLETE - - # Add the subcloud being processed by this unit test - self.subcloud = self.setup_subcloud() - - # Add the strategy_step state being processed by this unit test - self.strategy_step = self.setup_strategy_step( - self.subcloud.id, consts.STRATEGY_STATE_SW_DEPLOY_ACTIVATE) - - def test_deploy_activate_success(self): - """Test deploy activate when the API call succeeds.""" - - self.worker.perform_state_action(self.strategy_step) - - # On success, the state should transition to the next state - self.assert_step_updated(self.strategy_step.subcloud_id, - self.on_success_state) diff --git a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_deploy_complete.py b/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_deploy_complete.py deleted file mode 100644 index d3edd934c..000000000 --- a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_deploy_complete.py +++ /dev/null @@ -1,32 +0,0 @@ -# -# Copyright (c) 2023 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -from dcmanager.common import consts -from dcmanager.tests.unit.orchestrator.states.software.test_base import \ - TestSoftwareOrchestrator - - -class TestDeployCompleteState(TestSoftwareOrchestrator): - def setUp(self): - super(TestDeployCompleteState, self).setUp() - - self.on_success_state = consts.STRATEGY_STATE_SW_FINISH_STRATEGY - - # Add the subcloud being processed by this unit test - self.subcloud = self.setup_subcloud() - - # Add the strategy_step state being processed by this unit test - self.strategy_step = self.setup_strategy_step( - self.subcloud.id, consts.STRATEGY_STATE_SW_DEPLOY_COMPLETE) - - def test_deploy_complete_success(self): - """Test deploy complete when the API call succeeds.""" - - self.worker.perform_state_action(self.strategy_step) - - # On success, the state should transition to the next state - self.assert_step_updated(self.strategy_step.subcloud_id, - self.on_success_state) diff --git a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_deploy_host.py b/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_deploy_host.py deleted file mode 100644 index 873fcb031..000000000 --- a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_deploy_host.py +++ /dev/null @@ -1,32 +0,0 @@ -# -# Copyright (c) 2023 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -from dcmanager.common import consts -from dcmanager.tests.unit.orchestrator.states.software.test_base import \ - TestSoftwareOrchestrator - - -class TestDeployHostState(TestSoftwareOrchestrator): - def setUp(self): - super(TestDeployHostState, self).setUp() - - self.on_success_state = consts.STRATEGY_STATE_SW_UNLOCK_CONTROLLER - - # Add the subcloud being processed by this unit test - self.subcloud = self.setup_subcloud() - - # Add the strategy_step state being processed by this unit test - self.strategy_step = self.setup_strategy_step( - self.subcloud.id, consts.STRATEGY_STATE_SW_DEPLOY_HOST) - - def test_deploy_host_success(self): - """Test deploy host when the API call succeeds.""" - - self.worker.perform_state_action(self.strategy_step) - - # On success, the state should transition to the next state - self.assert_step_updated(self.strategy_step.subcloud_id, - self.on_success_state) diff --git a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_deploy_pre_check.py b/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_deploy_pre_check.py deleted file mode 100644 index e35336ba4..000000000 --- a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_deploy_pre_check.py +++ /dev/null @@ -1,32 +0,0 @@ -# -# Copyright (c) 2023 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -from dcmanager.common import consts -from dcmanager.tests.unit.orchestrator.states.software.test_base import \ - TestSoftwareOrchestrator - - -class TestDeployPreCheckState(TestSoftwareOrchestrator): - def setUp(self): - super(TestDeployPreCheckState, self).setUp() - - self.on_success_state = consts.STRATEGY_STATE_SW_DEPLOY_START - - # Add the subcloud being processed by this unit test - self.subcloud = self.setup_subcloud() - - # Add the strategy_step state being processed by this unit test - self.strategy_step = self.setup_strategy_step( - self.subcloud.id, consts.STRATEGY_STATE_SW_DEPLOY_PRE_CHECK) - - def test_deploy_pre_check_success(self): - """Test deploy pre check when the API call succeeds.""" - - self.worker.perform_state_action(self.strategy_step) - - # On success, the state should transition to the next state - self.assert_step_updated(self.strategy_step.subcloud_id, - self.on_success_state) diff --git a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_deploy_start.py b/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_deploy_start.py deleted file mode 100644 index e9a966b37..000000000 --- a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_deploy_start.py +++ /dev/null @@ -1,133 +0,0 @@ -# -# Copyright (c) 2023 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -import mock - -from dcmanager.common import consts -from dcmanager.orchestrator.states.software.deploy_start import DeployStartState -from dcmanager.tests.unit.orchestrator.states.fakes import FakeSystem -from dcmanager.tests.unit.orchestrator.states.software.test_base import \ - TestSoftwareOrchestrator - -REGION_ONE_RR_RELEASES = { - "stx_23.09.0": { - "sw_version": "23.09.0", - "state": "deployed", - "reboot_required": "Y", - }, - "stx_23.09.1": { - "sw_version": "23.09.1", - "state": "deployed", - "reboot_required": "N", - }, - "stx_23.09.2": { - "sw_version": "23.09.1", - "state": "deployed", - "reboot_required": "N", - }, -} - -REGION_ONE_NRR_RELEASES = { - "stx_23.09.0": { - "sw_version": "23.09.0", - "state": "deployed", - "reboot_required": "N", - }, - "stx_23.09.1": { - "sw_version": "23.09.1", - "state": "deployed", - "reboot_required": "N", - }, -} - -SUBCLOUD_RR_RELEASES = { - "stx_23.09.0": { - "sw_version": "23.09.0", - "state": "available", - "reboot_required": "Y", - }, - "stx_23.09.1": { - "sw_version": "23.09.1", - "state": "available", - "reboot_required": "N", - }, - "stx_23.09.2": { - "sw_version": "23.09.1", - "state": "available", - "reboot_required": "N", - }, -} - -SUBCLOUD_NRR_RELEASES = { - "stx_23.09.0": { - "sw_version": "23.09.0", - "state": "available", - "reboot_required": "N", - }, - "stx_23.09.1": { - "sw_version": "23.09.1", - "state": "available", - "reboot_required": "N", - }, -} - - -class TestDeployStartState(TestSoftwareOrchestrator): - def setUp(self): - super(TestDeployStartState, self).setUp() - - self.on_success_state = consts.STRATEGY_STATE_SW_DEPLOY_HOST - self.on_success_lock_state = consts.STRATEGY_STATE_SW_LOCK_CONTROLLER - - # Add the subcloud being processed by this unit test - self.subcloud = self.setup_subcloud() - - # Add the strategy_step state being processed by this unit test - self.strategy_step = self.setup_strategy_step( - self.subcloud.id, consts.STRATEGY_STATE_SW_DEPLOY_START - ) - - # Add mock API endpoints for sysinv client calls invoked by this state - self.sysinv_client.get_system = mock.MagicMock() - system_values = FakeSystem() - system_values.system_mode = consts.SYSTEM_MODE_SIMPLEX - self.sysinv_client.get_system.return_value = system_values - - # Add mock API endpoints for software client calls invoked by this state - self.software_client.query = mock.MagicMock() - self.software_client.deploy_start = mock.MagicMock() - - self._read_from_cache = mock.MagicMock() - - @mock.patch.object(DeployStartState, "_read_from_cache") - def test_deploy_start_nrr_success(self, mock_read_from_cache): - """Test deploy start when the API call succeeds.""" - mock_read_from_cache.return_value = REGION_ONE_NRR_RELEASES - self.software_client.query.side_effect = [SUBCLOUD_NRR_RELEASES] - - self.worker.perform_state_action(self.strategy_step) - - self.software_client.deploy_start.assert_called_once_with("stx_23.09.1") - - # 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(DeployStartState, "_read_from_cache") - def test_deploy_start_rr_success(self, mock_read_from_cache): - """Test deploy start when the API call succeeds.""" - mock_read_from_cache.return_value = REGION_ONE_RR_RELEASES - self.software_client.query.side_effect = [SUBCLOUD_RR_RELEASES] - - self.worker.perform_state_action(self.strategy_step) - - self.software_client.deploy_start.assert_called_once_with("stx_23.09.2") - - # On success, the state should transition to the next state - self.assert_step_updated( - self.strategy_step.subcloud_id, self.on_success_lock_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 939466d20..2719a7a84 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 @@ -1,9 +1,10 @@ # -# Copyright (c) 2023 Wind River Systems, Inc. +# Copyright (c) 2023-2024 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # import mock + from oslo_config import cfg from dcmanager.common import consts @@ -38,7 +39,7 @@ class TestFinishStrategyState(TestSoftwareOrchestrator): self.mock_use_usm = p.start() self.mock_use_usm.return_value = True self.addCleanup(p.stop) - super(TestFinishStrategyState, self).setUp() + super().setUp() self.on_success_state = consts.STRATEGY_STATE_COMPLETE 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 a3bd735e9..d8bba31ed 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 @@ -1,35 +1,165 @@ # -# Copyright (c) 2023 Wind River Systems, Inc. +# Copyright (c) 2023-2024 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # +import mock + from dcmanager.common import consts from dcmanager.tests.unit.orchestrator.states.software.test_base import \ TestSoftwareOrchestrator +MISSING_LICENSE_RESPONSE = { + "content": "", + "error": "License file not found. A license may not have been installed.", +} + +LICENSE_VALID_RESPONSE = {"content": "A valid license", "error": ""} + +ALTERNATE_LICENSE_RESPONSE = {"content": "A different valid license", "error": ""} + class TestInstallLicenseState(TestSoftwareOrchestrator): def setUp(self): - super(TestInstallLicenseState, self).setUp() + super().setUp() # next state after install a license is 'upload' - self.on_success_state = consts.STRATEGY_STATE_SW_UPLOAD + self.on_success_state = consts.STRATEGY_STATE_SW_CREATE_VIM_STRATEGY # Add the subcloud being processed by this unit test self.subcloud = self.setup_subcloud() # Add the strategy_step state being processed by this unit test self.strategy_step = self.setup_strategy_step( - self.subcloud.id, consts.STRATEGY_STATE_SW_INSTALL_LICENSE) + self.subcloud.id, consts.STRATEGY_STATE_SW_INSTALL_LICENSE + ) - def test_upgrade_subcloud_license_install_success(self): - """Test the install license step succeeds.""" + # Add mock API endpoints for sysinv client calls invoked by this state + self.sysinv_client.get_license = mock.MagicMock() + self.sysinv_client.install_license = mock.MagicMock() + + def test_install_license_failure(self): + """Test the installing license step where the install fails. + + The system controller has a license, but the API call to install on the + subcloud fails. + """ + + # Order of get_license calls: + # first license query is to system controller + # second license query is to subcloud (should be missing) + self.sysinv_client.get_license.side_effect = [ + LICENSE_VALID_RESPONSE, + MISSING_LICENSE_RESPONSE, + ] + + # Simulate a license install failure on the subcloud + self.sysinv_client.install_license.return_value = MISSING_LICENSE_RESPONSE # invoke the strategy state operation on the orch thread self.worker.perform_state_action(self.strategy_step) + # verify the license install was invoked + self.sysinv_client.install_license.assert_called() + + # Verify a install_license failure leads to a state failure + self.assert_step_updated( + self.strategy_step.subcloud_id, consts.STRATEGY_STATE_FAILED + ) + + def test_install_license_success(self): + """Test the install license step succeeds. + + The license will be installed on the subcloud when system controller + has a license, the subcloud does not have a license, and the API call + succeeds. + """ + + # Order of get_license calls: + # first license query is to system controller + # second license query is to subcloud (should be missing) + self.sysinv_client.get_license.side_effect = [ + LICENSE_VALID_RESPONSE, + MISSING_LICENSE_RESPONSE, + ] + + # A license install should return a success + self.sysinv_client.install_license.return_value = LICENSE_VALID_RESPONSE + + # invoke the strategy state operation on the orch thread + self.worker.perform_state_action(self.strategy_step) + + # verify the license install was invoked + self.sysinv_client.install_license.assert_called() + # On success, the next state after installing license is importing load - self.assert_step_updated(self.strategy_step.subcloud_id, - self.on_success_state) + self.assert_step_updated( + self.strategy_step.subcloud_id, self.on_success_state + ) + + def test_install_license_skip_existing(self): + """Test the install license step skipped due to license up to date""" + + # Order of get_license calls: + # first license query is to system controller + # second license query is to subcloud + self.sysinv_client.get_license.side_effect = [ + LICENSE_VALID_RESPONSE, + LICENSE_VALID_RESPONSE, + ] + + # invoke the strategy state operation on the orch thread + self.worker.perform_state_action(self.strategy_step) + + # A license install should not have been attempted due to the license + # already being up to date + self.sysinv_client.install_license.assert_not_called() + + # On success, the next state after installing license is importing load + self.assert_step_updated( + self.strategy_step.subcloud_id, self.on_success_state + ) + + def test_install_license_overrides_mismatched_license(self): + """Test the install license overrides a mismatched license""" + + # Order of get_license calls: + # first license query is to system controller + # second license query is to subcloud (should be valid but different) + self.sysinv_client.get_license.side_effect = [ + LICENSE_VALID_RESPONSE, + ALTERNATE_LICENSE_RESPONSE, + ] + + # A license install should return a success + self.sysinv_client.install_license.return_value = LICENSE_VALID_RESPONSE + + # invoke the strategy state operation on the orch thread + self.worker.perform_state_action(self.strategy_step) + + # verify the license install was invoked + self.sysinv_client.install_license.assert_called() + + # Verify it successfully moves to the next step + self.assert_step_updated( + 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.""" + + # Only makes one query: to system controller + self.sysinv_client.get_license.return_value = MISSING_LICENSE_RESPONSE + + # invoke the strategy state operation on the orch thread + self.worker.perform_state_action(self.strategy_step) + + # 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, self.on_success_state + ) diff --git a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_lock_controller.py b/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_lock_controller.py deleted file mode 100644 index ecd845798..000000000 --- a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_lock_controller.py +++ /dev/null @@ -1,181 +0,0 @@ -# -# Copyright (c) 2023-2024 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -import itertools -import mock - -from dcmanager.common import consts -from dcmanager.orchestrator.states import lock_host - -from dcmanager.tests.unit.orchestrator.states.fakes import FakeController -from dcmanager.tests.unit.orchestrator.states.fakes import FakeSystem -from dcmanager.tests.unit.orchestrator.states.software.test_base import \ - TestSoftwareOrchestrator - - -@mock.patch("dcmanager.orchestrator.states.lock_host.DEFAULT_MAX_QUERIES", 3) -@mock.patch("dcmanager.orchestrator.states.lock_host.DEFAULT_SLEEP_DURATION", 1) -class TestSwLockSimplexStage(TestSoftwareOrchestrator): - - state = consts.STRATEGY_STATE_SW_LOCK_CONTROLLER - - def setUp(self): - super(TestSwLockSimplexStage, self).setUp() - - # next state after a successful lock is deploy host - self.on_success_state = consts.STRATEGY_STATE_SW_DEPLOY_HOST - - # Add the subcloud being processed by this unit test - self.subcloud = self.setup_subcloud() - - # Add the strategy_step state being processed by this unit test - self.strategy_step = self.setup_strategy_step(self.subcloud.id, self.state) - - # Add mock API endpoints for sysinv client calls invoked by this state - self.sysinv_client.get_host = mock.MagicMock() - self.sysinv_client.lock_host = mock.MagicMock() - self.sysinv_client.get_system = mock.MagicMock() - system_values = FakeSystem() - system_values.system_mode = consts.SYSTEM_MODE_SIMPLEX - self.sysinv_client.get_system.return_value = system_values - self.setup_fake_controllers('controller-0') - - def setup_fake_controllers(self, host_name): - self.CONTROLLER_UNLOCKED = FakeController( - hostname=host_name, administrative=consts.ADMIN_UNLOCKED - ) - self.CONTROLLER_LOCKED = FakeController(hostname=host_name, - administrative=consts.ADMIN_LOCKED) - self.CONTROLLER_LOCKING = FakeController( - hostname=host_name, - administrative=consts.ADMIN_UNLOCKED, - ihost_action='lock', - task='Locking' - ) - self.CONTROLLER_LOCKING_FAILED = \ - FakeController(hostname=host_name, - administrative=consts.ADMIN_UNLOCKED, - ihost_action='force-swact', - task='Swacting') - - def test_lock_success(self): - """Test the lock command returns a success""" - - # mock the controller host queries - # first query is the starting state - # query 2,3 are are during the lock phase - # query 4 : the host is now locked - self.sysinv_client.get_host.side_effect = [self.CONTROLLER_UNLOCKED, - self.CONTROLLER_LOCKING, - self.CONTROLLER_LOCKING, - self.CONTROLLER_LOCKED] - - # mock the API call as failed on the subcloud - self.sysinv_client.lock_host.return_value = self.CONTROLLER_LOCKING - - # invoke the strategy state operation on the orch thread - self.worker.perform_state_action(self.strategy_step) - - # verify the lock command was actually attempted - self.sysinv_client.lock_host.assert_called() - - # verify that the API moved to the next state on success - self.assert_step_updated(self.strategy_step.subcloud_id, - self.on_success_state) - - def test_lock_skipped_when_already_locked(self): - """Test the lock command skips if host is already locked""" - - # mock the controller host query as being already locked - self.sysinv_client.get_host.return_value = self.CONTROLLER_LOCKED - - # invoke the strategy state operation on the orch thread - self.worker.perform_state_action(self.strategy_step) - - # verify the lock command was never attempted - self.sysinv_client.lock_host.assert_not_called() - - # verify that the state moves to the next state - self.assert_step_updated(self.strategy_step.subcloud_id, - self.on_success_state) - - def test_lock_attempt_timeout(self): - """Test lock invoked and fails if timeout before host becomes locked""" - - # mock the get_host queries - # first query is the starting state - # all remaining queries, the host returns 'locking' - self.sysinv_client.get_host.side_effect = itertools.chain( - [self.CONTROLLER_UNLOCKED, ], - itertools.repeat(self.CONTROLLER_LOCKING)) - - # mock the API call as successful on the subcloud - self.sysinv_client.lock_host.return_value = self.CONTROLLER_LOCKING - - # invoke the strategy state operation on the orch thread - self.worker.perform_state_action(self.strategy_step) - - # verify the lock command was actually attempted - self.sysinv_client.lock_host.assert_called() - - # verify the query was invoked: 1 + max_attempts times - self.assertEqual(lock_host.DEFAULT_MAX_QUERIES + 1, - self.sysinv_client.get_host.call_count) - - # verify that state failed due to subcloud never finishing the lock - self.assert_step_updated(self.strategy_step.subcloud_id, - consts.STRATEGY_STATE_FAILED) - - def test_lock_failure(self): - """Test the lock command returns a failure""" - - # mock the controller get_host query - self.sysinv_client.get_host.return_value = self.CONTROLLER_UNLOCKED - - # mock the API call as failed on the subcloud - self.sysinv_client.lock_host.return_value = self.CONTROLLER_LOCKING_FAILED - - # invoke the strategy state operation on the orch thread - self.worker.perform_state_action(self.strategy_step) - - # verify the lock command was actually attempted - self.sysinv_client.lock_host.assert_called() - - # verify that the API error for the lock leads to a failed state - self.assert_step_updated(self.strategy_step.subcloud_id, - consts.STRATEGY_STATE_FAILED) - - def test_lock_fails_when_host_query_fails(self): - """Test the lock command fails when it cannot get the controllers""" - - # mock the get_host query is empty and raises an exception - self.sysinv_client.get_host.side_effect = \ - Exception("Unable to find host controller-0") - - # invoke the strategy state operation on the orch thread - self.worker.perform_state_action(self.strategy_step) - - # verify the lock command was never attempted - self.sysinv_client.lock_host.assert_not_called() - - # verify that the state moves to the next state - self.assert_step_updated(self.strategy_step.subcloud_id, - consts.STRATEGY_STATE_FAILED) - - -class TestSwLockDuplexStage(TestSwLockSimplexStage): - - def setUp(self): - self.state = consts.STRATEGY_STATE_SW_LOCK_CONTROLLER - super(TestSwLockDuplexStage, self).setUp() - system_values = FakeSystem() - system_values.system_mode = consts.SYSTEM_MODE_DUPLEX - self.sysinv_client.get_system.return_value = system_values - # next state after a successful lock is deploy host - self.on_success_state = consts.STRATEGY_STATE_SW_DEPLOY_HOST - - # Add mock API endpoints for sysinv client calls invoked by this state - self.setup_fake_controllers('controller-1') diff --git a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_pre_check.py b/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_pre_check.py index 41bb42a64..e9f91dffd 100644 --- a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_pre_check.py +++ b/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_pre_check.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2023 Wind River Systems, Inc. +# Copyright (c) 2023-2024 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -11,7 +11,7 @@ from dcmanager.tests.unit.orchestrator.states.software.test_base import \ class TestPreCheckState(TestSoftwareOrchestrator): def setUp(self): - super(TestPreCheckState, self).setUp() + super().setUp() self.on_success_state = consts.STRATEGY_STATE_SW_INSTALL_LICENSE diff --git a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_swact_controller.py b/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_swact_controller.py deleted file mode 100644 index 292d36323..000000000 --- a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_swact_controller.py +++ /dev/null @@ -1,148 +0,0 @@ -# -# Copyright (c) 2023 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -import itertools - -import mock - -from dcmanager.common import consts -from dcmanager.orchestrator.states import swact_host -from dcmanager.tests.unit.orchestrator.states.fakes import FakeController -from dcmanager.tests.unit.orchestrator.states.software.test_base import \ - TestSoftwareOrchestrator - - -@mock.patch("dcmanager.orchestrator.states.swact_host.DEFAULT_SWACT_SLEEP", 1) -@mock.patch("dcmanager.orchestrator.states.swact_host.DEFAULT_MAX_QUERIES", 3) -@mock.patch("dcmanager.orchestrator.states.swact_host.DEFAULT_SLEEP_DURATION", 1) -class TestSwactController0State(TestSoftwareOrchestrator): - state = consts.STRATEGY_STATE_SW_SWACT_CONTROLLER_0 - - def setUp(self): - super(TestSwactController0State, self).setUp() - - # Add the subcloud being processed by this unit test - self.subcloud = self.setup_subcloud() - - # Add the strategy_step state being processed by this unit test - self.strategy_step = self.setup_strategy_step(self.subcloud.id, self.state) - - self.on_success_state = consts.STRATEGY_STATE_SW_CREATE_VIM_STRATEGY - - # Add mock API endpoints for sysinv client calls invoked by this state - self.sysinv_client.get_host = mock.MagicMock() - self.sysinv_client.swact_host = mock.MagicMock() - - # In order to swact to controller-1, we run "system host-swact controller-0" - self.setup_fake_controllers("controller-0") - - def setup_fake_controllers(self, host_name): - self.CONTROLLER_ACTIVE = FakeController(hostname=host_name) - self.CONTROLLER_STANDBY = FakeController( - hostname=host_name, capabilities={"Personality": "Controller-Standby"} - ) - self.CONTROLLER_SWACTING = FakeController( - hostname=host_name, task="Swacting" - ) - - def test_swact_controller_success(self): - """Test swact controller when the API call succeeds.""" - - # mock the controller host queries - # first query is the starting state - # query 2 is during the ongoing swact phase - # query 3 is after successful host swact - self.sysinv_client.get_host.side_effect = [ - self.CONTROLLER_STANDBY, - self.CONTROLLER_STANDBY, - self.CONTROLLER_ACTIVE, - ] - - # mock the API call as failed on the subcloud - self.sysinv_client.swact_host.return_value = self.CONTROLLER_SWACTING - - self.worker.perform_state_action(self.strategy_step) - - # verify the swact command was actually attempted - self.sysinv_client.swact_host.assert_called() - - # 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_swact_skipped_when_already_active(self): - """Test the swact command skips if host is already active controller""" - # mock the controller host query as being already Controller-Active - self.sysinv_client.get_host.return_value = self.CONTROLLER_ACTIVE - - # invoke the strategy state operation on the orch thread - self.worker.perform_state_action(self.strategy_step) - - # verify the swact command was never attempted - self.sysinv_client.swact_host.assert_not_called() - - # verify that the state moves to the next state - self.assert_step_updated( - self.strategy_step.subcloud_id, self.on_success_state - ) - - def test_swact_attempt_timeout(self): - """Test swact invoked and fails if timeout""" - # mock the get_host queries - # all remaining queries, the host returns 'Controller-Standby' - self.sysinv_client.get_host.side_effect = itertools.chain( - itertools.repeat(self.CONTROLLER_STANDBY) - ) - - # mock the API call as successful on the subcloud - self.sysinv_client.swact_host.return_value = self.CONTROLLER_SWACTING - - # invoke the strategy state operation on the orch thread - self.worker.perform_state_action(self.strategy_step) - - # verify the swact command was actually attempted - self.sysinv_client.swact_host.assert_called() - - # verify the query was invoked: 1 + max_attempts times - self.assertEqual( - swact_host.DEFAULT_MAX_QUERIES + 2, - self.sysinv_client.get_host.call_count, - ) - - # verify that state failed due to subcloud never finishing the swact - self.assert_step_updated( - self.strategy_step.subcloud_id, consts.STRATEGY_STATE_FAILED - ) - - def test_swact_fails_when_host_query_fails(self): - """Test the swact command fails when it cannot get the controllers""" - - # mock the get_host query is empty and raises an exception - self.sysinv_client.get_host.side_effect = Exception("Unable to find host") - - # invoke the strategy state operation on the orch thread - self.worker.perform_state_action(self.strategy_step) - - # verify the swact command was never attempted - self.sysinv_client.swact_host.assert_not_called() - - # verify that the state moves to the next state - self.assert_step_updated( - self.strategy_step.subcloud_id, consts.STRATEGY_STATE_FAILED - ) - - -class TestSwactController1State(TestSwactController0State): - def setUp(self): - self.state = consts.STRATEGY_STATE_SW_SWACT_CONTROLLER_1 - super(TestSwactController1State, self).setUp() - - # next state after a successful swact controller-1 is deploy activate - self.on_success_state = consts.STRATEGY_STATE_SW_DEPLOY_ACTIVATE - - # In order to swact to controller-0, we run "system host-swact controller-1" - self.setup_fake_controllers("controller-1") diff --git a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_unlock_controller.py b/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_unlock_controller.py deleted file mode 100644 index 323b89269..000000000 --- a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_unlock_controller.py +++ /dev/null @@ -1,32 +0,0 @@ -# -# Copyright (c) 2023 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -from dcmanager.common import consts -from dcmanager.tests.unit.orchestrator.states.software.test_base import \ - TestSoftwareOrchestrator - - -class TestUnlockControllerState(TestSoftwareOrchestrator): - def setUp(self): - super(TestUnlockControllerState, self).setUp() - - self.on_success_state = consts.STRATEGY_STATE_SW_DEPLOY_ACTIVATE - - # Add the subcloud being processed by this unit test - self.subcloud = self.setup_subcloud() - - # Add the strategy_step state being processed by this unit test - self.strategy_step = self.setup_strategy_step( - self.subcloud.id, consts.STRATEGY_STATE_SW_UNLOCK_CONTROLLER) - - def test_unlock_controller_success(self): - """Test unlock controller when the API call succeeds.""" - - self.worker.perform_state_action(self.strategy_step) - - # On success, the state should transition to the next state - self.assert_step_updated(self.strategy_step.subcloud_id, - self.on_success_state) diff --git a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_upload.py b/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_upload.py deleted file mode 100644 index 2d50f75f9..000000000 --- a/distributedcloud/dcmanager/tests/unit/orchestrator/states/software/test_upload.py +++ /dev/null @@ -1,205 +0,0 @@ -# -# Copyright (c) 2023-2024 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -import os - -import mock - -from oslo_config import cfg - -from dcmanager.common import consts -from dcmanager.orchestrator.states.software.upload import UploadState -from dcmanager.tests.unit.orchestrator.states.software.test_base \ - import TestSoftwareOrchestrator - -REGION_ONE_RELEASES = {"DC_20.12.1": {"sw_version": "20.12.1", - "state": "deployed"}, - "DC_20.12.2": {"sw_version": "20.12.2", - "state": "deployed"}, - "DC_20.12.3": {"sw_version": "20.12.3", - "state": "committed"}, - "DC_20.12.4": {"sw_version": "20.12.4", - "state": "committed"}} - - -REGION_ONE_RELEASES_2 = {"DC_20.12.1": {"sw_version": "20.12.1", - "state": "deployed"}, - "DC_22.12.0": {"sw_version": "22.12.0", - "state": "deployed"}, - "DC_20.12.2": {"sw_version": "20.12.2", - "state": "deployed"}} - -REGION_ONE_RELEASES_3 = {"DC_22.12.1": {"sw_version": "22.12.1", - "state": "deployed"}, - "DC_22.12.0": {"sw_version": "22.12.0", - "state": "deployed"}} - -SUBCLOUD_RELEASES = {"DC_20.12.1": {"sw_version": "20.12.1", - "state": "deployed"}, - "DC_20.12.2": {"sw_version": "20.12.2", - "state": "deployed"}} - - -@mock.patch("dcmanager.orchestrator.states.software.upload." - "DEFAULT_MAX_QUERIES", 3) -@mock.patch("dcmanager.orchestrator.states.software.upload" - ".DEFAULT_SLEEP_DURATION", 1) -class TestUploadState(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(TestUploadState, self).setUp() - self.on_success_state = consts.STRATEGY_STATE_SW_DEPLOY_PRE_CHECK - - # Add the subcloud being processed by this unit test - self.subcloud = self.setup_subcloud() - - # Add the strategy_step state being processed by this unit test - self.strategy_step = self.setup_strategy_step( - self.subcloud.id, consts.STRATEGY_STATE_SW_UPLOAD) - - # Add mock API endpoints for software client calls - # invoked by this state - self.software_client.query = mock.MagicMock() - self.software_client.upload = mock.MagicMock() - self._read_from_cache = mock.MagicMock() - - @mock.patch.object(UploadState, '_read_from_cache') - @mock.patch.object(os.path, 'isfile') - def test_software_upload_strategy_success(self, mock_is_file, - mock_read_from_cache): - """Test software upload when the API call succeeds.""" - mock_read_from_cache.return_value = REGION_ONE_RELEASES - mock_is_file.return_value = True - - 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) - - self.software_client.upload.assert_called_once_with([ - consts.RELEASE_VAULT_DIR + '/20.12/DC_20.12.3.patch', - consts.RELEASE_VAULT_DIR + '/20.12/DC_20.12.4.patch' - ]) - - # 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(UploadState, '_read_from_cache') - def test_software_upload_strategy_no_operation_required(self, - mock_read_from_cache): - """Test software upload when no software 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.upload.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) - - @mock.patch.object(UploadState, '_read_from_cache') - @mock.patch.object(os, 'listdir') - @mock.patch.object(os.path, 'isdir') - def test_software_upload_strategy_missing_sig(self, mock_is_dir, mock_listdir, - mock_read_from_cache): - """Test software upload when release is missing signature""" - mock_read_from_cache.return_value = REGION_ONE_RELEASES_2 - mock_is_dir.return_value = True - mock_listdir.return_value = ["DC_22.12.0.iso"] - 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) - - self.software_client.upload.assert_not_called() - - # On success, the state should transition to the next state - self.assert_step_updated(self.strategy_step.subcloud_id, - consts.STRATEGY_STATE_FAILED) - - @mock.patch.object(UploadState, '_read_from_cache') - @mock.patch.object(os, 'listdir') - @mock.patch.object(os.path, 'isdir') - def test_software_upload_strategy_success_load(self, mock_is_dir, mock_listdir, - mock_read_from_cache): - """Test software upload when the API call succeeds.""" - mock_read_from_cache.return_value = REGION_ONE_RELEASES_2 - mock_is_dir.return_value = True - mock_listdir.return_value = ["DC_22.12.0.iso", "DC_22.12.0.sig"] - self.software_client.query.side_effect = [ - SUBCLOUD_RELEASES, REGION_ONE_RELEASES_2] - - # invoke the strategy state operation on the orch thread - self.worker.perform_state_action(self.strategy_step) - - self.software_client.upload.assert_called_once_with([ - consts.RELEASE_VAULT_DIR + '/22.12/DC_22.12.0.iso', - consts.RELEASE_VAULT_DIR + '/22.12/DC_22.12.0.sig' - ]) - - # 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(UploadState, '_read_from_cache') - @mock.patch.object(os, 'listdir') - @mock.patch.object(os.path, 'isdir') - @mock.patch.object(os.path, 'isfile') - def test_software_upload_prepatched_load(self, mock_isfile, - mock_is_dir, mock_listdir, - mock_read_from_cache): - """Test software upload when release is a prepatched iso.""" - mock_read_from_cache.return_value = REGION_ONE_RELEASES_3 - mock_is_dir.return_value = True - mock_isfile.return_value = False - mock_listdir.return_value = ["DC_22.12.0.iso", "DC_22.12.0.sig"] - self.software_client.query.side_effect = [{}, REGION_ONE_RELEASES_3] - - # invoke the strategy state operation on the orch thread - self.worker.perform_state_action(self.strategy_step) - - self.software_client.upload.assert_called_once_with([ - consts.RELEASE_VAULT_DIR + '/22.12/DC_22.12.0.iso', - consts.RELEASE_VAULT_DIR + '/22.12/DC_22.12.0.sig' - ]) - - # 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(UploadState, '_read_from_cache') - @mock.patch.object(os, 'listdir') - @mock.patch.object(os.path, 'isdir') - @mock.patch.object(os.path, 'isfile') - def test_software_upload_patch_and_load(self, mock_isfile, - mock_is_dir, mock_listdir, - mock_read_from_cache): - """Test software upload when both patch and load is uploaded.""" - mock_read_from_cache.return_value = REGION_ONE_RELEASES_3 - mock_is_dir.return_value = True - mock_isfile.return_value = True - mock_listdir.return_value = ["DC_22.12.0.iso", "DC_22.12.0.sig"] - self.software_client.query.side_effect = [{}, REGION_ONE_RELEASES_3] - - # invoke the strategy state operation on the orch thread - self.worker.perform_state_action(self.strategy_step) - - self.software_client.upload.assert_called_once_with([ - consts.RELEASE_VAULT_DIR + '/22.12/DC_22.12.1.patch', - consts.RELEASE_VAULT_DIR + '/22.12/DC_22.12.0.iso', - consts.RELEASE_VAULT_DIR + '/22.12/DC_22.12.0.sig' - ]) - - # On success, the state should transition to the next state - self.assert_step_updated(self.strategy_step.subcloud_id, - self.on_success_state)