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 <jessica.castelino@windriver.com>
Co-Signed-off-by: sshathee <shunmugam.shatheesh@windriver.com>
Signed-off-by: sshathee <shunmugam.shatheesh@windriver.com>
This commit is contained in:
Jessica Castelino 2024-01-27 00:19:34 +00:00 committed by sshathee
parent c5a7d1d336
commit 727714b39d
6 changed files with 115 additions and 81 deletions

View File

@ -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 <<EOF >>$SCRIPTLOG
############################################################
`date "+%FT%T.%3N"`: Running $NUM_SCRIPTS in-service patch scripts:
`date "+%FT%T.%3N"`: Running $NUM_SCRIPTS install patch scripts:
$SCRIPTS

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -52,7 +52,8 @@ metadata2 = """<?xml version="1.0" ?>
<status>DEV</status>
<unremovable>N</unremovable>
<reboot_required>N</reboot_required>
<restart_script>23.09_NRR_INSVC_example-cgcs-patch-restart</restart_script>
<pre_install>pre-install.sh</pre_install>
<post_install>post-install.sh</post_install>
<contents>
<ostree>
<number_of_commits>1</number_of_commits>
@ -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)