Execute software deploy delete in Software DC orchestration

This commit implements the execution of `software deploy delete`
within the finish_strategy state. Additionally, it removes outdated
committed release code and introduces custom exceptions to handle
state errors effectively.

Test Plan (patch only):
PASS - Apply a sw-deploy-strategy
- Release in deploying state after apply_vim_strategy state
- Release changed to deployed state in finish_strategy state
  - software deploy delete executed

Story: 2010676
Task: 50543

Change-Id: If40f0ec7298270fe4a2a7306c3132eba3050e62c
Signed-off-by: Hugo Brito <hugo.brito@windriver.com>
This commit is contained in:
Hugo Brito
2024-07-10 17:05:30 -03:00
committed by Hugo Nicodemos
parent 82db04c240
commit 8fbdbec0d4
8 changed files with 175 additions and 71 deletions

View File

@@ -18,6 +18,7 @@ ABORTING = "aborting"
AVAILABLE = "available"
COMMITTED = "committed"
DEPLOYED = "deployed"
DEPLOYING = "deploying"
REMOVING = "removing"
UNAVAILABLE = "unavailable"
@@ -70,6 +71,12 @@ 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):
"""Deploy delete"""
url = self.endpoint + "/deploy"
response = requests.delete(url, headers=self.headers, timeout=timeout)
return self._handle_response(response, operation="Deploy delete")
def commit_patch(self, releases, timeout=REST_DEFAULT_TIMEOUT):
"""Commit patch"""
release_str = "/".join(releases)
@@ -86,5 +93,5 @@ class SoftwareClient(base.DriverBase):
if isinstance(data, dict) and data.get("error"):
message = f"{operation} failed with error: {data.get('error')}"
LOG.error(message)
raise Exception(message)
raise exceptions.SoftwareDataException(endpoint=response.url, error=message)
return data

View File

@@ -121,6 +121,10 @@ class ApiException(DCCommonException):
message = _("%(endpoint)s failed with status code: %(rc)d")
class SoftwareDataException(DCCommonException):
message = _("%(endpoint)s failed with data error: %(error)s")
class SystemPeerNotFound(NotFound):
message = _("System Peer %(system_peer)s not found")

View File

@@ -250,6 +250,26 @@ class PreCheckFailedException(DCManagerException):
message = _("Subcloud %(subcloud)s upgrade precheck failed: %(details)s")
class SoftwarePreCheckFailedException(DCManagerException):
message = _("Subcloud %(subcloud)s software deploy precheck failed: %(details)s")
class SoftwareListFailedException(DCManagerException):
message = _("Subcloud %(subcloud)s software list failed: %(details)s")
class SoftwareDeleteFailedException(DCManagerException):
message = _("Subcloud %(subcloud)s sofware delete failed: %(details)s")
class SoftwareDeployCommitFailedException(DCManagerException):
message = _("Subcloud %(subcloud)s sofware deploy commit failed: %(details)s")
class SoftwareDeployDeleteFailedException(DCManagerException):
message = _("Subcloud %(subcloud)s sofware deploy delete failed: %(details)s")
class PrestagePreCheckFailedException(DCManagerException):
"""PrestagePreCheckFailedException

View File

@@ -44,9 +44,11 @@ class CreatingVIMStrategyState(BaseState):
extra_args = utils.get_sw_update_strategy_extra_args(self.context)
release_id = extra_args.get(consts.EXTRA_ARGS_RELEASE_ID)
opts_dict["release_id"] = release_id
# Create rollback = False since DC orchestration do not support rollback
opts_dict["rollback"] = False
# Call the API to build the VIM strategy
# release will be sent as a **kwargs value for sw-deploy strategy
# release and rollback will be sent as a **kwargs value for sw-deploy strategy
subcloud_strategy = self.get_vim_client(region).create_strategy(
self.strategy_name,
opts_dict['storage-apply-type'],
@@ -54,7 +56,9 @@ class CreatingVIMStrategyState(BaseState):
opts_dict['max-parallel-workers'],
opts_dict['default-instance-action'],
opts_dict['alarm-restriction-type'],
release=opts_dict.get('release_id'),)
release=opts_dict.get('release_id'),
rollback=opts_dict.get('rollback'),
)
# a successful API call to create MUST set the state be 'building'
if subcloud_strategy.state != vim.STATE_BUILDING:

View File

@@ -3,12 +3,15 @@
#
# SPDX-License-Identifier: Apache-2.0
#
from dccommon.drivers.openstack import software_v1
from dcmanager.common import consts
from dcmanager.common.exceptions import StrategyStoppedException
from dcmanager.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
from dcmanager.orchestrator.states.software.cache.cache_specifications import (
REGION_ONE_RELEASE_USM_CACHE_TYPE,
)
class FinishStrategyState(BaseState):
@@ -25,68 +28,129 @@ class FinishStrategyState(BaseState):
self.info_log(strategy_step, "Finishing software strategy")
regionone_committed_releases = self._read_from_cache(
REGION_ONE_RELEASE_USM_CACHE_TYPE,
state=software_v1.COMMITTED
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
)
self.debug_log(
strategy_step,
f"regionone_committed_releases: {regionone_committed_releases}"
f"regionone_deployed_releases: {regionone_deployed_releases}",
)
try:
software_client = self.get_software_client(self.region_name)
subcloud_releases = software_client.list()
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)
raise exceptions.SoftwareListFailedException(
subcloud=subcloud.name,
details=message,
)
self.debug_log(strategy_step,
f"Releases for subcloud: {subcloud_releases}")
self.debug_log(strategy_step, f"Releases for subcloud: {subcloud_releases}")
releases_to_commit = list()
releases_to_delete = list()
# For this subcloud, determine which releases should be committed and
# which should be deleted.
# For this subcloud, determine which releases should be committed,
# which should be deleted and which should finish the deploy.
releases_to_delete = [
release["release_id"] for release in subcloud_releases
release["release_id"]
for release in subcloud_releases
if release["state"] in (software_v1.AVAILABLE, software_v1.UNAVAILABLE)
]
releases_to_commit = [
release["release_id"] for release in subcloud_releases
if release["state"] == software_v1.DEPLOYED
and any(
release["release_id"] == release_regionone["release_id"]
for release_regionone in regionone_committed_releases
)
# TODO(nicodemos): Update releases_to_commit and handle it after
# `software commit` is implemented
releases_to_commit = []
releases_to_deploy_delete = [
release["release_id"]
for release in subcloud_releases
if release["state"] == software_v1.DEPLOYING
]
if releases_to_delete:
self.info_log(strategy_step, f"Deleting releases {releases_to_delete}")
try:
software_client.delete(releases_to_delete)
except Exception:
message = ("Cannot delete releases from subcloud. Please see "
"logs for details.")
self.exception_log(strategy_step, message)
raise Exception(message)
self._handle_release_delete(
strategy_step, software_client, subcloud, releases_to_delete
)
if self.stopped():
raise StrategyStoppedException()
raise exceptions.StrategyStoppedException()
if releases_to_commit:
self.info_log(strategy_step,
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.")
self.exception_log(strategy_step, message)
raise Exception(message)
self._handle_deploy_commit(
strategy_step, software_client, subcloud, releases_to_commit
)
if releases_to_deploy_delete:
self._handle_deploy_delete(
strategy_step,
software_client,
subcloud,
releases_to_deploy_delete,
regionone_deployed_releases,
)
return self.next_state
def _handle_release_delete(
self, strategy_step, software_client, subcloud, releases_to_delete
):
self.info_log(strategy_step, f"Deleting releases {releases_to_delete}")
try:
software_client.delete(releases_to_delete)
except Exception:
message = (
"Cannot delete releases from subcloud. Please see logs for details."
)
self.exception_log(strategy_step, message)
raise exceptions.SoftwareDeleteFailedException(
subcloud=subcloud.name,
details=message,
)
def _handle_deploy_commit(
self, strategy_step, software_client, subcloud, releases_to_commit
):
raise NotImplementedError()
# If there are releases in deploying state and it's deployed in the regionone,
# they should be finished executing the deploy delete operation.
def _handle_deploy_delete(
self,
strategy_step,
software_client,
subcloud,
releases_to_deploy_delete,
regionone_deployed_releases,
):
if not any(
release_id == release_regionone["release_id"]
for release_id in releases_to_deploy_delete
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."
)
self.error_log(strategy_step, message)
raise exceptions.SoftwareDeployDeleteFailedException(
subcloud=subcloud.name,
details=message,
)
self.info_log(
strategy_step,
f"Finishing releases {releases_to_deploy_delete} to subcloud",
)
try:
software_client.deploy_delete()
except Exception:
message = (
"Cannot finish deploy delete on subcloud. Please see logs for details."
)
self.exception_log(strategy_step, message)
raise exceptions.SoftwareDeployDeleteFailedException(
subcloud=subcloud.name,
details=message,
)

View File

@@ -58,7 +58,7 @@ class PreCheckState(BaseState):
except Exception:
details = f"Get current strategy failed on subcloud: {subcloud.name}"
self.error_log(strategy_step, details)
raise exceptions.PreCheckFailedException(
raise exceptions.SoftwarePreCheckFailedException(
subcloud=subcloud.name,
details=details,
)
@@ -78,7 +78,7 @@ class PreCheckState(BaseState):
f"subcloud {subcloud.name}. Aborting."
)
self.error_log(strategy_step, details)
raise exceptions.PreCheckFailedException(
raise exceptions.SoftwarePreCheckFailedException(
subcloud=subcloud.name,
details=details,
)
@@ -102,7 +102,10 @@ class PreCheckState(BaseState):
except Exception as exc:
message = f"Cannot retrieve release list : {exc}."
self.exception_log(strategy_step, message)
raise Exception(message)
raise exceptions.SoftwareListFailedException(
subcloud=subcloud.name,
details=message,
)
self._check_prestaged_data(
strategy_step,
@@ -129,7 +132,7 @@ class PreCheckState(BaseState):
f"subcloud: {subcloud_name}"
)
self.error_log(strategy_step, details)
raise exceptions.PreCheckFailedException(
raise exceptions.SoftwarePreCheckFailedException(
subcloud=subcloud_name,
details=details,
)
@@ -140,7 +143,7 @@ class PreCheckState(BaseState):
f"{strategy_state}. Aborting."
)
self.error_log(strategy_step, details)
raise exceptions.PreCheckFailedException(
raise exceptions.SoftwarePreCheckFailedException(
subcloud=subcloud_name,
details=details,
)
@@ -163,7 +166,7 @@ class PreCheckState(BaseState):
if not (is_available_in_subcloud and regionone_deployed_release):
details = f"Release {release_id} is not prestaged. Aborting."
self.error_log(strategy_step, details)
raise exceptions.PreCheckFailedException(
raise exceptions.SoftwarePreCheckFailedException(
subcloud=subcloud_name,
details=details,
)

View File

@@ -10,8 +10,9 @@ from dccommon.drivers.openstack import vim
from dcmanager.common import consts
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.orchestrator.states.software.test_base import (
TestSoftwareOrchestrator,
)
STRATEGY_BUILDING = FakeVimStrategy(state=vim.STATE_BUILDING)
STRATEGY_DONE_BUILDING = FakeVimStrategy(state=vim.STATE_READY_TO_APPLY)
@@ -66,6 +67,7 @@ class TestCreateVIMSoftwareStrategyState(TestSoftwareOrchestrator):
"migrate",
"relaxed",
release=RELEASE_ID,
rollback=False,
)
# On success, the state should transition to the next state

View File

@@ -17,22 +17,22 @@ from dcmanager.tests.unit.orchestrator.states.software.test_base import \
REGION_ONE_RELEASES = [
{
"release_id": "starlingx-9.0.0",
"state": "committed",
"state": "deployed",
"sw_version": "9.0.0",
},
{
"release_id": "starlingx-9.0.1",
"state": "committed",
"state": "deployed",
"sw_version": "9.0.1",
},
{
"release_id": "starlingx-9.0.2",
"state": "committed",
"state": "deployed",
"sw_version": "9.0.2",
},
{
"release_id": "starlingx-9.0.3",
"state": "committed",
"state": "deployed",
"sw_version": "9.0.3",
},
]
@@ -40,12 +40,12 @@ REGION_ONE_RELEASES = [
SUBCLOUD_RELEASES = [
{
"release_id": "starlingx-9.0.0",
"state": "committed",
"state": "deployed",
"sw_version": "9.0.0",
},
{
"release_id": "starlingx-9.0.1",
"state": "committed",
"state": "deployed",
"sw_version": "9.0.1",
},
{
@@ -53,16 +53,16 @@ SUBCLOUD_RELEASES = [
"state": "deployed",
"sw_version": "9.0.2",
},
{
"release_id": "starlingx-9.0.2",
"state": "deploying",
"sw_version": "9.0.3",
},
{
"release_id": "starlingx-9.0.4",
"state": "available",
"sw_version": "9.0.4",
},
{
"release_id": "starlingx-9.0.5",
"state": "deployed",
"sw_version": "9.0.5",
},
]
@@ -86,6 +86,7 @@ class TestFinishStrategyState(TestSoftwareOrchestrator):
self.software_client.list = mock.MagicMock()
self.software_client.delete = mock.MagicMock()
self.software_client.commit_patch = mock.MagicMock()
self.software_client.deploy_delete = mock.MagicMock()
self._read_from_cache = mock.MagicMock()
def test_finish_strategy_success(self):
@@ -101,9 +102,8 @@ class TestFinishStrategyState(TestSoftwareOrchestrator):
call_args, _ = self.software_client.delete.call_args_list[0]
self.assertItemsEqual(["starlingx-9.0.4"], call_args[0])
self.software_client.commit_patch.assert_called_once_with(
["starlingx-9.0.2"]
)
self.software_client.commit_patch.assert_not_called()
self.software_client.deploy_delete.assert_called_once()
# On success, the state should transition to the next state
self.assert_step_updated(self.strategy_step.subcloud_id,
@@ -169,8 +169,8 @@ class TestFinishStrategyState(TestSoftwareOrchestrator):
self.strategy_step.subcloud_id, consts.STRATEGY_STATE_FAILED
)
def test_finish_strategy_fails_when_commit_patch_exception(self):
"""Test finish strategy fails when software client commit_patch
def test_finish_strategy_fails_when_deploy_delete_exception(self):
"""Test finish strategy fails when software client deploy_delete
raises exception
"""
@@ -178,7 +178,7 @@ class TestFinishStrategyState(TestSoftwareOrchestrator):
self.mock_read_from_cache.return_value = REGION_ONE_RELEASES
self.software_client.list.side_effect = [SUBCLOUD_RELEASES]
self.software_client.commit_patch.side_effect = Exception()
self.software_client.deploy_delete.side_effect = Exception()
self.worker.perform_state_action(self.strategy_step)