From b3d206781ba8f9c6bedfeff8e3334b82d452720b Mon Sep 17 00:00:00 2001 From: Hugo Brito Date: Mon, 22 Jul 2024 20:26:27 -0300 Subject: [PATCH] Improve DC VIM strategy create/apply error handling This commit updates subcloud's error_description with the error returned by the software API during VIM strategy create and apply. - Created two custom exceptions for handling these errors. - Clean up error_description in strategy creation. Note: This also updated the timeout values of software API. Test Plan: PASS - Apply a sw-deploy-strategy and force an error in the deploy precheck command. - Apply should fail in the `create VIM strategy` state - dcmanager subcloud errors should be updated PASS - Apply a sw-deploy-strategy and force an error in the deploy start command. - Apply should fail in `apply VIM strategy` state - dcmanager subcloud errors should be updated PASS - Create a dcmanager sw-deploy-strategy with subcloud errors. - Strategy created and subcloud errors should be `No errors present`. Story: 2010676 Task: 50644 Change-Id: Ib0b0b586d90093088a6af96e5d630e3fe04fd3f7 Signed-off-by: Hugo Brito --- .../dccommon/drivers/openstack/software_v1.py | 8 +- .../dcmanager/common/exceptions.py | 14 +++ .../states/applying_vim_strategy.py | 94 ++++++++++++------- .../states/creating_vim_strategy.py | 67 ++++++++----- .../states/software/finish_strategy.py | 27 ++---- .../orchestrator/sw_update_manager.py | 8 ++ .../dcmanager/tests/unit/fakes.py | 11 +++ .../test_apply_vim_software_strategy.py | 68 +++++++++++--- .../test_create_vim_software_strategy.py | 36 ++++++- 9 files changed, 240 insertions(+), 93 deletions(-) diff --git a/distributedcloud/dccommon/drivers/openstack/software_v1.py b/distributedcloud/dccommon/drivers/openstack/software_v1.py index 492d649d4..64c838ea7 100644 --- a/distributedcloud/dccommon/drivers/openstack/software_v1.py +++ b/distributedcloud/dccommon/drivers/openstack/software_v1.py @@ -23,6 +23,8 @@ REMOVING = "removing" UNAVAILABLE = "unavailable" REST_DEFAULT_TIMEOUT = 900 +REST_SHOW_TIMEOUT = 150 +REST_DELETE_TIMEOUT = 300 class SoftwareClient(base.DriverBase): @@ -58,13 +60,13 @@ class SoftwareClient(base.DriverBase): response = requests.get(url, headers=self.headers, timeout=timeout) return self._handle_response(response, operation="List") - def show(self, release, timeout=REST_DEFAULT_TIMEOUT): + def show(self, release, timeout=REST_SHOW_TIMEOUT): """Show release""" url = self.endpoint + f"/release/{release}" response = requests.get(url, headers=self.headers, timeout=timeout) return self._handle_response(response, operation="Show") - def delete(self, releases, timeout=REST_DEFAULT_TIMEOUT): + def delete(self, releases, timeout=REST_DELETE_TIMEOUT): """Delete release""" release_str = "/".join(releases) url = self.endpoint + f"/release/{release_str}" @@ -77,7 +79,7 @@ class SoftwareClient(base.DriverBase): response = requests.post(url, headers=self.headers, timeout=timeout) return self._handle_response(response, operation="Deploy precheck") - def deploy_delete(self, timeout=REST_DEFAULT_TIMEOUT): + def deploy_delete(self, timeout=REST_DELETE_TIMEOUT): """Deploy delete""" url = self.endpoint + "/deploy" response = requests.delete(url, headers=self.headers, timeout=timeout) diff --git a/distributedcloud/dcmanager/common/exceptions.py b/distributedcloud/dcmanager/common/exceptions.py index 51f661a29..129608136 100644 --- a/distributedcloud/dcmanager/common/exceptions.py +++ b/distributedcloud/dcmanager/common/exceptions.py @@ -262,6 +262,20 @@ class SoftwarePreCheckFailedException(DCManagerException): message = _("Subcloud %(subcloud)s software deploy precheck failed: %(details)s") +class CreateVIMStrategyFailedException(DCManagerException): + message = _( + "Subcloud %(subcloud)s create VIM %(name)s strategy " + "failed. State: %(state)s Details: %(details)s" + ) + + +class ApplyVIMStrategyFailedException(DCManagerException): + message = _( + "Subcloud %(subcloud)s apply VIM %(name)s strategy " + "failed. State: %(state)s Details: %(details)s" + ) + + class SoftwareListFailedException(DCManagerException): message = _("Subcloud %(subcloud)s software list failed: %(details)s") diff --git a/distributedcloud/dcmanager/orchestrator/states/applying_vim_strategy.py b/distributedcloud/dcmanager/orchestrator/states/applying_vim_strategy.py index b690c83ec..cd7124304 100644 --- a/distributedcloud/dcmanager/orchestrator/states/applying_vim_strategy.py +++ b/distributedcloud/dcmanager/orchestrator/states/applying_vim_strategy.py @@ -6,7 +6,7 @@ import time from dccommon.drivers.openstack import vim -from dcmanager.common.exceptions import StrategyStoppedException +from dcmanager.common import exceptions from dcmanager.db import api as db_api from dcmanager.orchestrator.states.base import BaseState @@ -64,7 +64,13 @@ class ApplyingVIMStrategyState(BaseState): # Do not raise the default exception if there is no strategy # because the default exception is unclear: ie: "Get strategy failed" if subcloud_strategy is None: - raise Exception("(%s) VIM Strategy not found." % self.strategy_name) + message = "VIM Strategy not found." + raise exceptions.ApplyVIMStrategyFailedException( + subcloud=strategy_step.subcloud.name, + name=self.strategy_name, + state=subcloud_strategy.state, + details=message, + ) # We have a VIM strategy, but need to check if it is ready to apply elif subcloud_strategy.state == vim.STATE_READY_TO_APPLY: @@ -87,20 +93,20 @@ class ApplyingVIMStrategyState(BaseState): vim.STATE_APPLY_FAILED, vim.STATE_APPLY_TIMEOUT, ]: - # Explicit known failure states - raise Exception( - "(%s) VIM strategy apply failed. %s. %s" - % ( - self.strategy_name, - subcloud_strategy.state, - subcloud_strategy.apply_phase.reason, - ) + message = "VIM strategy apply failed: " + raise exceptions.ApplyVIMStrategyFailedException( + subcloud=strategy_step.subcloud.name, + name=self.strategy_name, + state=subcloud_strategy.state, + details=message + subcloud_strategy.apply_phase.reason, ) else: - # Other states are bad - raise Exception( - "(%s) VIM strategy apply failed. Unexpected State: %s." - % (self.strategy_name, subcloud_strategy.state) + message = "VIM strategy unexpected apply state." + raise exceptions.ApplyVIMStrategyFailedException( + subcloud=strategy_step.subcloud.name, + name=self.strategy_name, + state=subcloud_strategy.state, + details=message, ) # wait for new strategy to apply or the existing strategy to complete. @@ -117,14 +123,19 @@ class ApplyingVIMStrategyState(BaseState): # which would allow the longer 60 second sleep to be broken into # multiple smaller sleep calls + error_message = None # If event handler stop has been triggered, fail the state if self.stopped(): - raise StrategyStoppedException() + raise exceptions.StrategyStoppedException() # break out of the loop if the max number of attempts is reached wait_count += 1 if wait_count >= self.wait_attempts: - raise Exception( - "Timeout applying (%s) vim strategy." % self.strategy_name + message = "Timeout applying vim strategy." + raise exceptions.ApplyVIMStrategyFailedException( + subcloud=strategy_step.subcloud.name, + name=self.strategy_name, + state=subcloud_strategy.state, + details=message, ) # every loop we wait, even the first one time.sleep(self.wait_interval) @@ -143,9 +154,12 @@ class ApplyingVIMStrategyState(BaseState): get_fail_count += 1 if get_fail_count >= self.max_failed_queries: # We have waited too long. - raise Exception( - "Timeout during recovery of apply (%s) Vim strategy." - % self.strategy_name + message = "Timeout during recovery of VIM strategy." + raise exceptions.ApplyVIMStrategyFailedException( + subcloud=strategy_step.subcloud.name, + name=self.strategy_name, + state=subcloud_strategy.state, + details=message, ) self.debug_log( strategy_step, @@ -156,8 +170,12 @@ class ApplyingVIMStrategyState(BaseState): # If an external actor has deleted the strategy, the only option # is to fail this state. if subcloud_strategy is None: - raise Exception( - "(%s) VIM Strategy no longer exists." % self.strategy_name + message = "VIM Strategy no longer exists." + raise exceptions.ApplyVIMStrategyFailedException( + subcloud=strategy_step.subcloud.name, + name=self.strategy_name, + state=subcloud_strategy.state, + details=message, ) elif subcloud_strategy.state == vim.STATE_APPLYING: @@ -186,22 +204,26 @@ class ApplyingVIMStrategyState(BaseState): vim.STATE_APPLY_FAILED, vim.STATE_APPLY_TIMEOUT, ]: - # Explicit known failure states - raise Exception( - "(%s) Vim strategy apply failed. %s. %s" - % ( - self.strategy_name, - subcloud_strategy.state, - subcloud_strategy.apply_phase.reason, - ) - ) + error_message = "VIM strategy apply failed: " else: - # Other states are bad - raise Exception( - "(%s) Vim strategy apply failed. Unexpected State: %s." - % (self.strategy_name, subcloud_strategy.state) + error_message = "VIM strategy unexpected apply state." + + if error_message: + apply_error = subcloud_strategy.apply_phase.response + # If response is None, use the reason + if not apply_error: + apply_error = subcloud_strategy.apply_phase.reason + db_api.subcloud_update( + self.context, + strategy_step.subcloud_id, + error_description=apply_error, + ) + raise exceptions.ApplyVIMStrategyFailedException( + subcloud=strategy_step.subcloud.name, + name=self.strategy_name, + state=subcloud_strategy.state, + details=error_message + apply_error, ) - # end of loop # Success, state machine can proceed to the next state return self.next_state diff --git a/distributedcloud/dcmanager/orchestrator/states/creating_vim_strategy.py b/distributedcloud/dcmanager/orchestrator/states/creating_vim_strategy.py index 6cb5270a4..5e3d1212e 100644 --- a/distributedcloud/dcmanager/orchestrator/states/creating_vim_strategy.py +++ b/distributedcloud/dcmanager/orchestrator/states/creating_vim_strategy.py @@ -8,8 +8,9 @@ import time from dccommon.drivers.openstack import vim from dcmanager.common import consts -from dcmanager.common.exceptions import StrategyStoppedException +from dcmanager.common import exceptions from dcmanager.common import utils +from dcmanager.db import api as db_api from dcmanager.orchestrator.states.base import BaseState # Max time: 30 minutes = 180 queries x 10 seconds between @@ -59,8 +60,12 @@ class CreatingVIMStrategyState(BaseState): # a successful API call to create MUST set the state be 'building' if subcloud_strategy.state != vim.STATE_BUILDING: - raise Exception( - "Unexpected VIM strategy build state: %s" % subcloud_strategy.state + message = "Unexpected VIM strategy build state." + raise exceptions.CreateVIMStrategyFailedException( + subcloud=strategy_step.subcloud.name, + name=self.strategy_name, + state=subcloud_strategy.state, + details=message, ) return subcloud_strategy @@ -91,9 +96,9 @@ class CreatingVIMStrategyState(BaseState): strategy_step, "VIM strategy exists with state: %s" % subcloud_strategy.state, ) - # if a strategy exists in any type of failed state or aborted - # state it should be deleted. - # applied state should also be deleted from previous success runs. + # if a strategy exists in any type of failed state or aborted state it + # should be deleted. Applied state should also be deleted from previous + # success runs. if subcloud_strategy.state in [ vim.STATE_BUILDING, vim.STATE_APPLYING, @@ -101,12 +106,16 @@ class CreatingVIMStrategyState(BaseState): ]: # Can't delete a strategy in these states message = ( - "Failed to create a VIM strategy for %s. " - "There already is an existing strategy in %s state" - % (region, subcloud_strategy.state) + "Failed to create a VIM strategy. There already is an existing " + "strategy in this state." ) self.warn_log(strategy_step, message) - raise Exception(message) + raise exceptions.CreateVIMStrategyFailedException( + subcloud=strategy_step.subcloud.name, + name=self.strategy_name, + state=subcloud_strategy.state, + details=message, + ) # if strategy exists in any other type of state, delete and create self.info_log(strategy_step, "Deleting existing VIM strategy") @@ -120,12 +129,17 @@ class CreatingVIMStrategyState(BaseState): # Loop until the strategy is done building Repeatedly query the API counter = 0 while True: + error_message = None # If event handler stop has been triggered, fail the state if self.stopped(): - raise StrategyStoppedException() + raise exceptions.StrategyStoppedException() if counter >= self.max_queries: - raise Exception( - "Timeout building vim strategy. state: %s" % subcloud_strategy.state + details = "Timeout building VIM strategy." + raise exceptions.CreateVIMStrategyFailedException( + subcloud=strategy_step.subcloud.name, + name=self.strategy_name, + state=subcloud_strategy.state, + details=details, ) counter += 1 time.sleep(self.sleep_duration) @@ -151,17 +165,26 @@ class CreatingVIMStrategyState(BaseState): # This is the expected state while creating the strategy pass elif subcloud_strategy.state == vim.STATE_BUILD_FAILED: - raise Exception( - "VIM strategy build failed: %s. %s." - % (subcloud_strategy.state, subcloud_strategy.build_phase.reason) - ) + error_message = "VIM strategy build failed: " elif subcloud_strategy.state == vim.STATE_BUILD_TIMEOUT: - raise Exception( - "VIM strategy build timed out: %s." % subcloud_strategy.state - ) + error_message = "VIM strategy build timed out: " else: - raise Exception( - "VIM strategy unexpected build state: %s" % subcloud_strategy.state + error_message = "VIM strategy unexpected build state." + if error_message: + build_error = subcloud_strategy.build_phase.response + # If response is None, use the reason + if not build_error: + build_error = subcloud_strategy.build_phase.reason + db_api.subcloud_update( + self.context, + strategy_step.subcloud_id, + error_description=build_error, + ) + raise exceptions.CreateVIMStrategyFailedException( + subcloud=strategy_step.subcloud.name, + name=self.strategy_name, + state=subcloud_strategy.state, + details=error_message + build_error, ) # Success, state machine can proceed to the next state diff --git a/distributedcloud/dcmanager/orchestrator/states/software/finish_strategy.py b/distributedcloud/dcmanager/orchestrator/states/software/finish_strategy.py index f04470dc0..c1033a4db 100644 --- a/distributedcloud/dcmanager/orchestrator/states/software/finish_strategy.py +++ b/distributedcloud/dcmanager/orchestrator/states/software/finish_strategy.py @@ -7,7 +7,6 @@ from dccommon.drivers.openstack import software_v1 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_RELEASE_USM_CACHE_TYPE, @@ -28,8 +27,6 @@ class FinishStrategyState(BaseState): self.info_log(strategy_step, "Finishing software strategy") - subcloud = db_api.subcloud_get(self.context, strategy_step.subcloud.id) - regionone_deployed_releases = self._read_from_cache( REGION_ONE_RELEASE_USM_CACHE_TYPE, state=software_v1.DEPLOYED ) @@ -46,7 +43,7 @@ class FinishStrategyState(BaseState): message = "Cannot retrieve subcloud releases. Please see logs for details." self.exception_log(strategy_step, message) raise exceptions.SoftwareListFailedException( - subcloud=subcloud.name, + subcloud=strategy_step.subcloud.name, details=message, ) @@ -72,7 +69,7 @@ class FinishStrategyState(BaseState): if releases_to_delete: self._handle_release_delete( - strategy_step, software_client, subcloud, releases_to_delete + strategy_step, software_client, releases_to_delete ) if self.stopped(): @@ -80,14 +77,13 @@ class FinishStrategyState(BaseState): if releases_to_commit: self._handle_deploy_commit( - strategy_step, software_client, subcloud, releases_to_commit + strategy_step, software_client, releases_to_commit ) if releases_to_deploy_delete: self._handle_deploy_delete( strategy_step, software_client, - subcloud, releases_to_deploy_delete, regionone_deployed_releases, ) @@ -95,7 +91,7 @@ class FinishStrategyState(BaseState): return self.next_state def _handle_release_delete( - self, strategy_step, software_client, subcloud, releases_to_delete + self, strategy_step, software_client, releases_to_delete ): self.info_log(strategy_step, f"Deleting releases {releases_to_delete}") try: @@ -106,13 +102,11 @@ class FinishStrategyState(BaseState): ) self.exception_log(strategy_step, message) raise exceptions.SoftwareDeleteFailedException( - subcloud=subcloud.name, + subcloud=strategy_step.subcloud.name, details=message, ) - def _handle_deploy_commit( - self, strategy_step, software_client, subcloud, releases_to_commit - ): + def _handle_deploy_commit(self, strategy_step, software_client, releases_to_commit): raise NotImplementedError() # If there are releases in deploying state and it's deployed in the regionone, @@ -121,7 +115,6 @@ class FinishStrategyState(BaseState): self, strategy_step, software_client, - subcloud, releases_to_deploy_delete, regionone_deployed_releases, ): @@ -131,12 +124,12 @@ class FinishStrategyState(BaseState): for release_regionone in regionone_deployed_releases ): message = ( - f"There is a deploying release on subcloud {subcloud.name} " - "that is not deployed in System Controller. Aborting." + f"Deploying release found on subcloud {strategy_step.subcloud.name} " + "and is not deployed in System Controller. Aborting." ) self.error_log(strategy_step, message) raise exceptions.SoftwareDeployDeleteFailedException( - subcloud=subcloud.name, + subcloud=strategy_step.subcloud.name, details=message, ) self.info_log( @@ -151,6 +144,6 @@ class FinishStrategyState(BaseState): ) self.exception_log(strategy_step, message) raise exceptions.SoftwareDeployDeleteFailedException( - subcloud=subcloud.name, + subcloud=strategy_step.subcloud.name, details=message, ) diff --git a/distributedcloud/dcmanager/orchestrator/sw_update_manager.py b/distributedcloud/dcmanager/orchestrator/sw_update_manager.py index 047465ec9..83ed1bd1f 100644 --- a/distributedcloud/dcmanager/orchestrator/sw_update_manager.py +++ b/distributedcloud/dcmanager/orchestrator/sw_update_manager.py @@ -462,6 +462,14 @@ class SwUpdateManager(manager.Manager): state=consts.STRATEGY_STATE_INITIAL, details="", ) + # Clear the error_description field for all subclouds that will + # perform orchestration. + update_form = {"error_description": consts.ERROR_DESC_EMPTY} + db_api.subcloud_bulk_update_by_ids( + context, + [subcloud.id for subcloud, _ in valid_subclouds], + update_form, + ) LOG.info( f"Finished creating software update strategy of type {payload['type']}." diff --git a/distributedcloud/dcmanager/tests/unit/fakes.py b/distributedcloud/dcmanager/tests/unit/fakes.py index 3d3446a35..3819209fd 100644 --- a/distributedcloud/dcmanager/tests/unit/fakes.py +++ b/distributedcloud/dcmanager/tests/unit/fakes.py @@ -65,6 +65,17 @@ class FakeVimStrategy(object): self.abort_phase = abort_phase +class FakeVimStrategyPhase(object): + """Represents a VIM StrategyPhase object defined in: + + starlingx/nfv/nfv-client/nfv_client/openstack/sw_update.py + """ + + def __init__(self, response=None, reason=None): + self.response = response + self.reason = reason + + class SwUpdateStrategy(object): def __init__(self, id, data): self.id = id 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 7493f2d85..2fcefc0e6 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 @@ -8,14 +8,20 @@ import mock from dccommon.drivers.openstack import vim from dcmanager.common import consts +from dcmanager.common import exceptions from dcmanager.tests.unit.common import fake_strategy -from dcmanager.tests.unit.fakes import FakeVimStrategy -from dcmanager.tests.unit.orchestrator.states.software.test_base import \ - TestSoftwareOrchestrator +from dcmanager.tests.unit import fakes +from dcmanager.tests.unit.orchestrator.states.software.test_base import ( + TestSoftwareOrchestrator, +) -STRATEGY_READY_TO_APPLY = FakeVimStrategy(state=vim.STATE_READY_TO_APPLY) -STRATEGY_APPLYING = FakeVimStrategy(state=vim.STATE_APPLYING) -STRATEGY_APPLIED = FakeVimStrategy(state=vim.STATE_APPLIED) +STRATEGY_READY_TO_APPLY = fakes.FakeVimStrategy(state=vim.STATE_READY_TO_APPLY) +STRATEGY_APPLYING = fakes.FakeVimStrategy(state=vim.STATE_APPLYING) +STRATEGY_APPLIED = fakes.FakeVimStrategy(state=vim.STATE_APPLIED) +APPLY_PHASE_ERROR = fakes.FakeVimStrategyPhase(response="Deploy Start Failed") +STRATEGY_APPLY_FAILED = fakes.FakeVimStrategy( + state=vim.STATE_APPLY_FAILED, apply_phase=APPLY_PHASE_ERROR +) RELEASE_ID = "starlingx-9.0.1" @@ -36,7 +42,8 @@ class TestApplyVIMSoftwareStrategyState(TestSoftwareOrchestrator): # 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_APPLY_VIM_STRATEGY) + self.subcloud.id, consts.STRATEGY_STATE_SW_APPLY_VIM_STRATEGY + ) # Mock the API calls made by the state self.vim_client.get_strategy = mock.MagicMock() @@ -52,9 +59,7 @@ class TestApplyVIMSoftwareStrategyState(TestSoftwareOrchestrator): "DEFAULT_MAX_WAIT_ATTEMPTS", 3, ) - @mock.patch( - "dcmanager.orchestrator.states.applying_vim_strategy.WAIT_INTERVAL", 1 - ) + @mock.patch("dcmanager.orchestrator.states.applying_vim_strategy.WAIT_INTERVAL", 1) @mock.patch( "dcmanager.orchestrator.states.applying_vim_strategy." "ApplyingVIMStrategyState.__init__.__defaults__", @@ -80,5 +85,44 @@ class TestApplyVIMSoftwareStrategyState(TestSoftwareOrchestrator): self.vim_client.apply_strategy.assert_called_with(strategy_name="sw-upgrade") # 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) + + @mock.patch( + "dcmanager.orchestrator.states.applying_vim_strategy." + "DEFAULT_MAX_FAILED_QUERIES", + 3, + ) + @mock.patch( + "dcmanager.orchestrator.states.applying_vim_strategy." + "DEFAULT_MAX_WAIT_ATTEMPTS", + 3, + ) + @mock.patch("dcmanager.orchestrator.states.applying_vim_strategy.WAIT_INTERVAL", 1) + @mock.patch( + "dcmanager.orchestrator.states.applying_vim_strategy." + "ApplyingVIMStrategyState.__init__.__defaults__", + (3, 1), + ) + @mock.patch.object(exceptions, "ApplyVIMStrategyFailedException") + def test_apply_vim_software_strategy_apply_failed(self, mock_exception): + """Test apply vim software strategy apply failed""" + + self.vim_client.get_strategy.side_effect = [ + STRATEGY_READY_TO_APPLY, + STRATEGY_APPLYING, + STRATEGY_APPLY_FAILED, + ] + + # API calls acts as expected + self.vim_client.apply_strategy.return_value = STRATEGY_APPLYING + + self.worker.perform_state_action(self.strategy_step) + + # Assert ApplyVIMStrategyFailedException is called with the correct parameters + expected_message = f"VIM strategy apply failed: {APPLY_PHASE_ERROR.response}" + mock_exception.assert_called_once_with( + subcloud=self.subcloud.name, + name=vim.STRATEGY_NAME_SW_USM, + state=vim.STATE_APPLY_FAILED, + details=expected_message, + ) 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 32736be2a..a1535313e 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 @@ -8,14 +8,21 @@ import mock from dccommon.drivers.openstack import vim from dcmanager.common import consts +from dcmanager.common import exceptions from dcmanager.tests.unit.common import fake_strategy -from dcmanager.tests.unit.fakes import FakeVimStrategy +from dcmanager.tests.unit import fakes from dcmanager.tests.unit.orchestrator.states.software.test_base import ( TestSoftwareOrchestrator, ) -STRATEGY_BUILDING = FakeVimStrategy(state=vim.STATE_BUILDING) -STRATEGY_DONE_BUILDING = FakeVimStrategy(state=vim.STATE_READY_TO_APPLY) +STRATEGY_BUILDING = fakes.FakeVimStrategy(state=vim.STATE_BUILDING) +BUILD_PHASE_ERROR = fakes.FakeVimStrategyPhase( + response="Installed license is valid: [FAIL]" +) +STRATEGY_BUILDING_FAILED = fakes.FakeVimStrategy( + state=vim.STATE_BUILD_FAILED, build_phase=BUILD_PHASE_ERROR +) +STRATEGY_DONE_BUILDING = fakes.FakeVimStrategy(state=vim.STATE_READY_TO_APPLY) RELEASE_ID = "starlingx-9.0.1" @@ -72,3 +79,26 @@ class TestCreateVIMSoftwareStrategyState(TestSoftwareOrchestrator): # 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(exceptions, "CreateVIMStrategyFailedException") + def test_create_vim_software_strategy_build_failed(self, mock_exception): + """Test create vim software strategy build failed""" + + self.vim_client.get_strategy.side_effect = [ + None, + STRATEGY_BUILDING_FAILED, + ] + + # API calls acts as expected + self.vim_client.create_strategy.return_value = STRATEGY_BUILDING + + self.worker.perform_state_action(self.strategy_step) + + # Assert ApplyVIMStrategyFailedException is called with the correct parameters + expected_message = f"VIM strategy build failed: {BUILD_PHASE_ERROR.response}" + mock_exception.assert_called_once_with( + subcloud=self.subcloud.name, + name=vim.STRATEGY_NAME_SW_USM, + state=vim.STATE_BUILD_FAILED, + details=expected_message, + )