From 727714b39df091ad8ef0dd620f2953d15eabddc0 Mon Sep 17 00:00:00 2001 From: Jessica Castelino Date: Sat, 27 Jan 2024 00:19:34 +0000 Subject: [PATCH] Support install scripts for in-service patch This commit adds support for 1) Post-install scripts for In-Service patches 2) Pre-install scripts for In-Service patches Test Plan: [PENDING] Install scripts run before and after install [PENDING] Scripts are optional Story: 2010676 Task: 49480 Change-Id: I6729798a59ac61c7eae395d8a3a3b425fe4e6f72 Signed-off-by: Jessica Castelino Co-Signed-off-by: sshathee Signed-off-by: sshathee --- software/service-files/run-software-scripts | 15 ++- software/service-files/software-functions | 3 +- software/software/software_agent.py | 21 +++- software/software/software_controller.py | 110 ++++++++++-------- software/software/software_functions.py | 20 ++-- .../software/tests/test_software_function.py | 27 +++-- 6 files changed, 115 insertions(+), 81 deletions(-) diff --git a/software/service-files/run-software-scripts b/software/service-files/run-software-scripts index eff3aa28..ea279fce 100644 --- a/software/service-files/run-software-scripts +++ b/software/service-files/run-software-scripts @@ -1,13 +1,20 @@ #!/bin/bash # -# Copyright (c) 2023 Wind River Systems, Inc. +# Copyright (c) 2023-2024 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # . /etc/software/software-functions -declare SCRIPTS=$(find $PATCH_SCRIPTDIR -type f -executable | sort) +if [ -z "$1" ] +then + loginfo "No input parameter provided to identify script type." + exit 0 +fi + +declare DIR="${PATCH_SCRIPTDIR}/${1}" +declare SCRIPTS=$(find $DIR -type f -executable | sort) declare -i NUM_SCRIPTS=$(echo "$SCRIPTS" | wc -l) if [ $NUM_SCRIPTS -eq 0 ] @@ -18,10 +25,10 @@ fi loginfo "Running $NUM_SCRIPTS in-service patch scripts" -declare SCRIPTLOG=/var/log/software-insvc.log +declare SCRIPTLOG=/var/log/software-script.log cat <>$SCRIPTLOG ############################################################ -`date "+%FT%T.%3N"`: Running $NUM_SCRIPTS in-service patch scripts: +`date "+%FT%T.%3N"`: Running $NUM_SCRIPTS install patch scripts: $SCRIPTS diff --git a/software/service-files/software-functions b/software/service-files/software-functions index 888fadbe..0d3b961b 100644 --- a/software/service-files/software-functions +++ b/software/service-files/software-functions @@ -12,7 +12,8 @@ # Source platform.conf, for nodetype and subfunctions . /etc/platform/platform.conf -declare PATCH_SCRIPTDIR=/run/software/software-scripts +declare PRE_INSTALL_SCRIPTDIR=/run/software/software-scripts/preinstall +declare POST_INSTALL_SCRIPTDIR=/run/software/software-scripts/postinstall declare PATCH_FLAGDIR=/run/software/software-flags declare -i PATCH_STATUS_OK=0 declare -i PATCH_STATUS_FAILED=1 diff --git a/software/software/software_agent.py b/software/software/software_agent.py index 46549a4e..c4dc00d7 100644 --- a/software/software/software_agent.py +++ b/software/software/software_agent.py @@ -45,7 +45,7 @@ insvc_software_scripts = "/run/software/software-scripts" insvc_software_flags = "/run/software/software-flags" insvc_software_restart_agent = "/run/software/.restart.software-agent" -run_insvc_software_scripts_cmd = "/usr/sbin/run-software-scripts" +run_install_software_scripts_cmd = "/usr/sbin/run-software-scripts" pa = None @@ -68,7 +68,7 @@ def clearflag(fname): LOG.exception("Failed to clear %s flag", fname) -def pull_restart_scripts_from_controller(): +def pull_install_scripts_from_controller(): # If the rsync fails, it raises an exception to # the caller "handle_install()" and fails the # host-install request for this host. @@ -576,6 +576,11 @@ class PatchAgent(PatchService): os.path.exists(ostree_pull_completed_deployment_pending_file) or \ os.path.exists(mount_pending_file): try: + LOG.info("Running pre-install patch-scripts") + pull_install_scripts_from_controller() + subprocess.check_output([run_install_software_scripts_cmd, "preinstall"], + stderr=subprocess.STDOUT) + # Pull changes from remote to the sysroot ostree # The remote value is configured inside # "/sysroot/ostree/repo/config" file @@ -585,6 +590,10 @@ class PatchAgent(PatchService): LOG.exception("Failed to pull changes and create deployment" "during host-install.") success = False + except subprocess.CalledProcessError as e: + LOG.exception("Failed to execute pre-install scripts.") + LOG.error("Command output: %s", e.output) + success = False try: # Create a new deployment once the changes are pulled @@ -619,15 +628,15 @@ class PatchAgent(PatchService): setflag(mount_pending_file) ostree_utils.mount_new_deployment(deployment_dir) clearflag(mount_pending_file) - LOG.info("Running in-service patch-scripts") - pull_restart_scripts_from_controller() - subprocess.check_output(run_insvc_software_scripts_cmd, stderr=subprocess.STDOUT) + LOG.info("Running post-install patch-scripts") + subprocess.check_output([run_install_software_scripts_cmd, "postinstall"], + stderr=subprocess.STDOUT) # Clear the node_is_patched flag, since we've handled it in-service clearflag(node_is_patched_file) self.node_is_patched = False except subprocess.CalledProcessError as e: - LOG.exception("In-Service patch installation failed") + LOG.exception("Failed to execute post-install scripts.") LOG.error("Command output: %s", e.output) success = False diff --git a/software/software/software_controller.py b/software/software/software_controller.py index 70b38295..02d817e0 100644 --- a/software/software/software_controller.py +++ b/software/software/software_controller.py @@ -1075,22 +1075,27 @@ class PatchController(PatchService): ostree_tar_filename = "%s/%s-software.tar" % (ostree_tar_dir, patch_id) return ostree_tar_filename - def delete_restart_script(self, patch_id): + def delete_install_script(self, patch_id): ''' - Deletes the restart script (if any) associated with the patch + Deletes the install scripts associated with the patch :param patch_id: The patch ID ''' - release = self.release_collection.get_release_by_id(patch_id) - restart_script = release.restart_script - if not restart_script: - return - - restart_script_path = "%s/%s" % (root_scripts_dir, restart_script) + pre_install_filename = self.release_data.metadata[patch_id].get("pre_install") + post_install_filename = self.release_data.metadata[patch_id].get("post_install") + pre_install_path = None + post_install_path = None + if pre_install_filename: + pre_install_path = "%s/%s" % (root_scripts_dir, pre_install_filename) + if post_install_filename: + post_install_path = "%s/%s" % (root_scripts_dir, post_install_filename) try: - # Delete the metadata - os.remove(restart_script_path) + # Delete the install scripts + if pre_install_path: + os.remove(pre_install_path) + if post_install_path: + os.remove(post_install_path) except OSError: - msg = "Failed to remove restart script for %s" % patch_id + msg = "Failed to remove the install script for %s" % patch_id LOG.exception(msg) raise SoftwareError(msg) @@ -1625,7 +1630,7 @@ class PatchController(PatchService): LOG.exception(msg) raise MetadataFail(msg) - self.delete_restart_script(release_id) + self.delete_install_script(release_id) reload_release_data() msg = "%s has been deleted" % release_id LOG.info(msg) @@ -1994,17 +1999,23 @@ class PatchController(PatchService): results["error"] += errormsg return results + for patch_id in commit_list: - release = self.release_collection.get_release_by_id(patch_id) # Fetch file paths that need to be cleaned up to # free patch storage disk space - if release.restart_script: - restart_script_path = "%s/%s" % \ - (root_scripts_dir, - release.restart_script) - if os.path.exists(restart_script_path): - cleanup_files.add(restart_script_path) - patch_sw_version = release.sw_release + pre_install_filename = self.release_data.metadata[patch_id].get("pre_install") + post_install_filename = self.release_data.metadata[patch_id].get("post_install") + + if pre_install_filename: + pre_install_script_path = "%s/%s" % (root_scripts_dir, pre_install_filename) + post_install_script_path = "%s/%s" % (root_scripts_dir, post_install_filename) + if os.path.exists(pre_install_script_path): + cleanup_files.add(pre_install_script_path) + if os.path.exists(post_install_script_path): + cleanup_files.add(post_install_script_path) + + patch_sw_version = utils.get_major_release_version( + self.release_data.metadata[patch_id]["sw_version"]) abs_ostree_tar_dir = package_dir[patch_sw_version] software_tar_path = "%s/%s-software.tar" % (abs_ostree_tar_dir, patch_id) if os.path.exists(software_tar_path): @@ -2076,40 +2087,39 @@ class PatchController(PatchService): break return rc - def copy_restart_scripts(self): + def copy_install_scripts(self): applying_states = [states.DEPLOYING, states.REMOVING] - for release in self.release_collection.iterate_releases(): - if release.restart_script: - if release.state in applying_states: - try: - restart_script_name = release.restart_script - restart_script_path = "%s/%s" \ - % (root_scripts_dir, restart_script_name) - dest_path = constants.PATCH_SCRIPTS_STAGING_DIR - dest_script_file = "%s/%s" \ - % (constants.PATCH_SCRIPTS_STAGING_DIR, restart_script_name) + for patch_id in self.release_data.metadata: + pre_install = self.release_data.metadata[patch_id].get("pre_install") + post_install = self.release_data.metadata[patch_id].get("post_install") + folder = ["preinstall", "postinstall"] + if self.release_data.metadata[patch_id]["state"] in applying_states: + try: + for i, file in enumerate(file for file in (pre_install, post_install) if file): + script_path = "%s/%s" % (root_scripts_dir, file) + dest_path = constants.PATCH_SCRIPTS_STAGING_DIR + "/" + folder[i] + dest_script_file = "%s/%s" % (dest_path, file) if not os.path.exists(dest_path): os.makedirs(dest_path, 0o700) - shutil.copyfile(restart_script_path, dest_script_file) + shutil.copyfile(script_path, dest_script_file) os.chmod(dest_script_file, 0o700) - msg = "Creating restart script for %s" % release.id + msg = "Creating install script %s for %s" % (file, patch_id) LOG.info(msg) - except shutil.Error: - msg = "Failed to copy the restart script for %s" % release.id - LOG.exception(msg) - raise SoftwareError(msg) - else: - try: - restart_script_name = release.restart_script - restart_script_path = "%s/%s" \ - % (constants.PATCH_SCRIPTS_STAGING_DIR, restart_script_name) - if os.path.exists(restart_script_path): - os.remove(restart_script_path) - msg = "Removing restart script for %s" % release.id - LOG.info(msg) - except shutil.Error: - msg = "Failed to delete the restart script for %s" % release.id - LOG.exception(msg) + except shutil.Error: + msg = "Failed to copy the install script %s for %s" % (file, patch_id) + LOG.exception(msg) + raise SoftwareError(msg) + else: + try: + for i, file in enumerate(file for file in (pre_install, post_install) if file): + script_path = "%s/%s/%s" % (constants.PATCH_SCRIPTS_STAGING_DIR, folder[i], file) + if os.path.exists(script_path): + os.remove(script_path) + msg = "Removing install script %s for %s" % (file, patch_id) + LOG.info(msg) + except shutil.Error: + msg = "Failed to delete the install script %s for %s" % (file, patch_id) + LOG.exception(msg) def _update_state_to_peer(self): state_update_msg = SoftwareMessageDeployStateUpdate() @@ -2731,7 +2741,7 @@ class PatchController(PatchService): if self.allow_insvc_patching: LOG.info("Allowing in-service patching") force = True - self.copy_restart_scripts() + self.copy_install_scripts() # Check if there is a major release deployment in progress # and set agent request parameters accordingly diff --git a/software/software/software_functions.py b/software/software/software_functions.py index b3629496..5d502594 100644 --- a/software/software/software_functions.py +++ b/software/software/software_functions.py @@ -347,7 +347,8 @@ class ReleaseData(object): "summary", "description", "install_instructions", - "restart_script", + "pre_install", + "post_install", "warnings", "apply_active_release_only", "commit"]: @@ -880,13 +881,16 @@ class PatchFile(object): v = "%s/%s-software.tar" % (abs_ostree_tar_dir, patch_id) LOG.info("software.tar %s" % v) - # restart_script may not exist in metadata. - if thispatch.metadata[patch_id].get("restart_script"): - if not os.path.exists(root_scripts_dir): - os.makedirs(root_scripts_dir) - restart_script_name = os.path.join(tmpdir, thispatch.metadata[patch_id]["restart_script"]) - if os.path.isfile(restart_script_name): - shutil.move(restart_script_name, os.path.join(root_scripts_dir, restart_script_name)) + if not os.path.exists(root_scripts_dir): + os.makedirs(root_scripts_dir) + if thispatch.metadata[patch_id].get("pre_install"): + pre_install_script_name = thispatch.metadata[patch_id]["pre_install"] + shutil.move(pre_install_script_name, + "%s/%s" % (root_scripts_dir, pre_install_script_name)) + if thispatch.metadata[patch_id].get("post_install"): + post_install_script_name = thispatch.metadata[patch_id]["post_install"] + shutil.move(post_install_script_name, + "%s/%s" % (root_scripts_dir, post_install_script_name)) except tarfile.TarError as te: msg = "Extract software failed %s" % str(te) diff --git a/software/software/tests/test_software_function.py b/software/software/tests/test_software_function.py index c976a9d2..f209c042 100644 --- a/software/software/tests/test_software_function.py +++ b/software/software/tests/test_software_function.py @@ -52,7 +52,8 @@ metadata2 = """ DEV N N - 23.09_NRR_INSVC_example-cgcs-patch-restart + pre-install.sh + post-install.sh 1 @@ -81,7 +82,8 @@ expected_values = [ "warnings": "Sample warning2", "status": "DEV", "unremovable": "N", - "restart_script": "23.09_NRR_INSVC_example-cgcs-patch-restart", + "pre_install": "pre-install.sh", + "post_install": "post-install.sh", "commit_id": "0b53576092a189133d56eac49ae858c1218f480a4a859eaca2b47f2604a4e0e7", "checksum": "2f742b1b719f19b302c306604659ccf4aa61a1fdb7742ac79b009c79af18c79b", }, @@ -95,7 +97,8 @@ expected_values = [ "warnings": "Sample warning", "status": "TST", "unremovable": "Y", - "restart_script": None, + "pre_install": "pre-install.sh", + "post_install": "post-install.sh", "commit_id": "38453dcb1aeb5bb9394ed02c0e6b8f2f913d00a827c89faf98cb63dff503b8e2", "checksum": "2f742b1b719f19b302c306604659ccf4aa61a1fdb7742ac79b009c79af18c79b", } @@ -137,10 +140,10 @@ class TestSoftwareFunction(unittest.TestCase): self.assertEqual(val["warnings"], r.warnings) self.assertEqual(val["status"], r.status) self.assertEqual(val["unremovable"] == 'Y', r.unremovable) - if val["restart_script"] is None: - self.assertIsNone(r.restart_script) + if val["pre_install"] is None: + self.assertIsNone(r.pre_install) else: - self.assertEqual(val["restart_script"], r.restart_script) + self.assertEqual(val["pre_install"], r.pre_install) self.assertEqual(val["commit_id"], r.commit_id) self.assertEqual(val["checksum"], r.commit_checksum) @@ -166,10 +169,10 @@ class TestSoftwareFunction(unittest.TestCase): self.assertEqual(val["warnings"], r.warnings) self.assertEqual(val["status"], r.status) self.assertEqual(val["unremovable"] == 'Y', r.unremovable) - if val["restart_script"] is None: - self.assertIsNone(r.restart_script) + if val["pre_install"] is None: + self.assertIsNone(r.pre_install) else: - self.assertEqual(val["restart_script"], r.restart_script) + self.assertEqual(val["pre_install"], r.pre_install) self.assertEqual(val["commit_id"], r.commit_id) self.assertEqual(val["checksum"], r.commit_checksum) @@ -185,10 +188,10 @@ class TestSoftwareFunction(unittest.TestCase): self.assertEqual(val["warnings"], r.warnings) self.assertEqual(val["status"], r.status) self.assertEqual(val["unremovable"] == 'Y', r.unremovable) - if val["restart_script"] is None: - self.assertIsNone(r.restart_script) + if val["pre_install"] is None: + self.assertIsNone(r.pre_install) else: - self.assertEqual(val["restart_script"], r.restart_script) + self.assertEqual(val["pre_install"], r.pre_install) self.assertEqual(val["commit_id"], r.commit_id) self.assertEqual(val["checksum"], r.commit_checksum)