From cad378bac575bb5e9d8dfeb96b418673ff8bb190 Mon Sep 17 00:00:00 2001 From: Luis Eduardo Bonatti Date: Thu, 25 Apr 2024 20:13:09 -0300 Subject: [PATCH] Delete deployment This commit adds the function to delete a deployment. There's some validations in order to be able to delete it some of them is: The deploy host state should be one of: [pending, failed] The deploy state should be one of: [start-done, start-failed, abort-done] All nodes are running from release software. In case of delete a major release the data generated during the deploy should also be deleted which include /sysroot/upgrade/ostree_repo, /sysroot/upgrade/sysroot and the folders [config, fluxcd, helm, nfv/vim, sysinv, puppet, deploy] under /opt/platform//. Also delete the /var/lib/postgres/. Note: The function is not called by any endpoint at this point as the New USM REST API is in development to avoid conflicts and rework. Test Plan: PASS: Fold]ers deleted on /opt/platform// with start-done and start-failed deploy state. PASS: Cleanup of staging data. PASS: Failed to attempt deletion with a deployed host N+1 release Story: 2010676 Task: 49979 Change-Id: I1789172edc730e6c94fa6bec7f5881c0bdfd7eab Signed-off-by: Luis Eduardo Bonatti --- software/software/constants.py | 13 ++++++ software/software/db/api.py | 7 ++++ software/software/software_controller.py | 52 +++++++++++++++++++++++- software/software/software_entities.py | 7 ++++ 4 files changed, 78 insertions(+), 1 deletion(-) diff --git a/software/software/constants.py b/software/software/constants.py index d85f6ca8..2d929f05 100644 --- a/software/software/constants.py +++ b/software/software/constants.py @@ -34,6 +34,7 @@ RC_UNHEALTHY = 3 DEPLOY_PRECHECK_SCRIPT = "deploy-precheck" DEPLOY_START_SCRIPT = "software-deploy-start" +DEPLOY_CLEANUP_SCRIPT = "deploy-cleanup" SEMANTICS_DIR = "%s/semantics" % SOFTWARE_STORAGE_DIR @@ -56,6 +57,18 @@ DEBIAN_RELEASE = "bullseye" STARLINGX_RELEASE = SW_VERSION PATCH_SCRIPTS_STAGING_DIR = "/var/www/pages/updates/software-scripts" SYSROOT_OSTREE = "/sysroot/ostree/repo" +STAGING_DIR = "/sysroot/upgrade" +ROOT_DIR = "%s/sysroot" % STAGING_DIR +POSTGRES_PATH = "/var/lib/postgresql" +PLATFORM_PATH = "/opt/platform" +CONFIG = "config" +FLUXCD = "fluxcd" +HELM = "helm" +VIM = "nfv/vim" +SYSINV = "sysinv" +PUPPET = "puppet" +DEPLOY = "deploy" +CLEANUP_FOLDERS_NAME = [CONFIG, FLUXCD, HELM, VIM, SYSINV, PUPPET, DEPLOY] LOOPBACK_INTERFACE_NAME = "lo" diff --git a/software/software/db/api.py b/software/software/db/api.py index 316f1fcb..0035d2c4 100644 --- a/software/software/db/api.py +++ b/software/software/db/api.py @@ -86,6 +86,13 @@ class SoftwareAPI: finally: self.end_update() + def get_all_current_hosts_states(self): + self.begin_update() + try: + return self.deploy_host_handler.query_all_current_states() + finally: + self.end_update() + def update_deploy_host(self, hostname, state): self.begin_update() try: diff --git a/software/software/software_controller.py b/software/software/software_controller.py index e96c9f86..63db46bb 100644 --- a/software/software/software_controller.py +++ b/software/software/software_controller.py @@ -27,7 +27,6 @@ from wsgiref import simple_server from fm_api import fm_api from fm_api import constants as fm_constants - from oslo_config import cfg as oslo_cfg import software.apt_utils as apt_utils @@ -79,6 +78,7 @@ from software.deploy_state import DeployState from software.release_verify import verify_files import software.config as cfg import software.utils as utils +from software.sysinv_utils import get_ihost_list from software.sysinv_utils import get_k8s_ver from software.sysinv_utils import is_system_controller @@ -2651,6 +2651,56 @@ class PatchController(PatchService): # a deployment) return True + @require_deploy_state([DEPLOY_STATES.START_DONE, DEPLOY_STATES.START_FAILED, DEPLOY_STATES.ABORT_DONE], + "Deploy must be in the following states to be able to delete: %s, %s, %s" % ( + DEPLOY_STATES.START_DONE.value, DEPLOY_STATES.START_FAILED.value, + DEPLOY_STATES.ABORT_DONE.value)) + def software_deploy_delete_api(self) -> dict: + """ + Delete deployment and the data generated during the deploy. + + :return: dict of info, warning and error messages + """ + msg_info = "" + msg_warning = "" + msg_error = "" + deploy = self.db_api_instance.get_deploy_all()[0] + to_release = deploy.get("to_release") + from_release = deploy.get("from_release") + major_from_release = utils.get_major_release_version(from_release) + for host in get_ihost_list(): + # TODO(lbonatti) change it to sw_version once load is deprecated + if host.software_load != major_from_release: + raise SoftwareServiceError(f"Delete not allow because {host.hostname} is not running from release" + f" {major_from_release}") + + hosts_states = self.db_api_instance.get_all_current_hosts_states() + if states.DEPLOY_HOST_STATES.DEPLOYED in hosts_states or states.DEPLOY_HOST_STATES.DEPLOYING in hosts_states: + raise SoftwareServiceError(f"There's host already {states.DEPLOY_HOST_STATES.DEPLOYED.value} " + f"or in {states.DEPLOY_HOST_STATES.DEPLOYING.value} process") + major_release = utils.get_major_release_version(to_release) + major_to_release = constants.MAJOR_RELEASE % major_release + if to_release == major_to_release: + try: + cmd_path = utils.get_software_deploy_script(to_release, constants.DEPLOY_CLEANUP_SCRIPT) + if (os.path.exists(f"{constants.STAGING_DIR}/{constants.OSTREE_REPO}") and + os.path.exists(constants.ROOT_DIR)): + subprocess.check_output([cmd_path, f"{constants.STAGING_DIR}/{constants.OSTREE_REPO}", + constants.ROOT_DIR, "all"], stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as e: + msg_error = "Failed to delete deploy." + LOG.error("%s: %s" % (msg_error, e)) + return dict(info=msg_info, warning=msg_warning, error=msg_error) + + for folder in constants.CLEANUP_FOLDERS_NAME: + path = os.path.join(constants.PLATFORM_PATH, folder, major_release, "") + shutil.rmtree(path, ignore_errors=True) + shutil.rmtree(f"{constants.POSTGRES_PATH}/{major_release}") + msg_info += "Deploy deleted with success" + self.db_api_instance.delete_deploy_host_all() + self.db_api_instance.delete_deploy() + return dict(info=msg_info, warning=msg_warning, error=msg_error) + @require_deploy_state([DEPLOY_STATES.ACTIVATE_DONE], "Must complete deploy activate before completing the deployment") def software_deploy_complete_api(self) -> dict: diff --git a/software/software/software_entities.py b/software/software/software_entities.py index 974d66bd..229f7ae3 100644 --- a/software/software/software_entities.py +++ b/software/software/software_entities.py @@ -367,6 +367,13 @@ class DeployHostHandler(DeployHosts): data = get_software_filesystem_data() return data.get("deploy_host", []) + def query_all_current_states(self): + data = get_software_filesystem_data() + states = [] + for host in data.get("deploy_host", []): + states.append(host.get("state")) + return states + def update(self, hostname, state: DEPLOY_HOST_STATES): super().update(hostname, state) deploy = self.query(hostname)