From d0d637225236b65c75bf08d94c7ec6d6bcac9a2f Mon Sep 17 00:00:00 2001 From: Lindley Vieira Date: Tue, 9 Sep 2025 12:58:16 -0400 Subject: [PATCH] Patch activate script to restart service Add a patch activate script to restart the software-controller and VIM services if a restart flag is detected in the system. Also, add a fix to detect this flag in 24.09.300 in case the patch 24.09.400 is removed. Test-Plan SX/DX: PASS: Apply an inservice patch with these scripts and check if the service was restarted PASS: Remove the inservice patch with these changes with success Closes-Bug: 2122607 Change-Id: I80c0ac47b48ed1b32b0a91f75ff77613c07eede1 Signed-off-by: Lindley Vieira --- patch-scripts/activate-scripts/README.md | 72 +++++++ .../examples/01-restart-services.py | 196 ++++++++++++++++++ .../start-scripts/24.09.400/pre-start.sh | 39 ++++ software/software/software_controller.py | 11 + 4 files changed, 318 insertions(+) create mode 100644 patch-scripts/activate-scripts/README.md create mode 100644 patch-scripts/activate-scripts/examples/01-restart-services.py create mode 100644 patch-scripts/start-scripts/24.09.400/pre-start.sh diff --git a/patch-scripts/activate-scripts/README.md b/patch-scripts/activate-scripts/README.md new file mode 100644 index 00000000..7e13646e --- /dev/null +++ b/patch-scripts/activate-scripts/README.md @@ -0,0 +1,72 @@ +# Activate Scripts Management + +This repository manages **activate** scripts used in the patch deployment process. +They run for each patch during `software deploy activate` + +## Folder Structure + +``` +activate-scripts/ +├── 24.09.400/ +│ └── 01-restart-services.sh +├── examples/ +│ └── ... +└── ... +``` + +- `boilerplate/`: + Contains the **default scripts**. These are the standard versions used for most software releases. + +- `MM.mm.pp/`: + Contains **version-specific scripts** to run in an specific release, copy the scripts from the examples folder and modify them if needed. + +--- + +## Usage + +### Default Case + +If there is no specific folder for a given release: +- This patch will not have activation scripts. +- No need to create a version-specific directory. + +### When a Script is Needed + +If a patch requires an activation script, search in the examples folder and copy the related : + +1. **Create a version folder** (e.g., `24.09.400/`): + ```bash + mkdir activate-scripts/24.09.400 + ``` + +2. **Copy the relevant scripts from examples folder**: + ```bash + cp activate-scripts/examples/ activate-scripts/24.09.400/ + ``` + +3. **Edit the scripts** in `24.09.400/` if needed. + +4. **Create new scripts** in `24.09.400/` and `examples/` if needed. +Scripts names always follow the formmat `DD-name.extension` + +> The scripts run in DD order +> Always check the examples folder to ensure consistency. + +--- + +## Tips + +- **Include comments** in versioned scripts, noting what the change is doing. +- Use scripts in the examples folder. +- The activate scripts runs in order of the first 2-digits at the script name. + +--- + +## License + +Include the license in all scripts + +``` +Copyright (c) 2025 Wind River Systems, Inc. +SPDX-License-Identifier: Apache-2.0 +``` diff --git a/patch-scripts/activate-scripts/examples/01-restart-services.py b/patch-scripts/activate-scripts/examples/01-restart-services.py new file mode 100644 index 00000000..cd7bc68b --- /dev/null +++ b/patch-scripts/activate-scripts/examples/01-restart-services.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python +# Copyright (c) 2025 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# +# This script checks if there is a software-controller restart flag, +# if it exists: +# - Create a flag detected by software-controller on startup +# and set its state to activate-done +# - Restarts the software-controller-daemon +# + +import logging +import os +import re +import shutil +import subprocess +import sys + +from software.states import DEPLOY_STATES +from software.utilities.update_deploy_state import update_deploy_state +from software.utilities.utils import configure_logging + +LOG = logging.getLogger('main_logger') + +RESTART_SERVICES_FLAG = "/run/software/.activate.restart-services.flag" +ACTIVATE_DONE_FLAG = "/run/software/.activate-done.flag" + + +def apply_fix_to_set_activate_done_state(): + """ + This is a live patch-back of a function to report the activate-done state after + the software-controller service is restarted. Only applies when it is removing + the patch 24.09.400, when applying, it will stop and log 'Already patched". + """ + LOG.info("Applying activate done fix in software_controller.py") + + FILE = "/ostree/1/usr/lib/python3/dist-packages/software/software_controller.py" + LINE_REGEX = r'^\s*self\.register_deploy_state_change_listeners\(\)\s*$' + TO_ADD_LINE = "_detect_flag_and_set_to_activate_done" + + try: + with open(FILE, "r", encoding="utf-8") as f: + original_content = f.read() + except FileNotFoundError: + LOG.error(f"Error: File not found: {FILE}") + return + + # Idempotency check: if function reference already present, assume patched + if TO_ADD_LINE in original_content: + LOG.info("Already patched") + return + + # Ensure target line exists + line_re = re.compile(LINE_REGEX) + lines = original_content.splitlines() + match_index = None + for i, line in enumerate(lines): + if line_re.match(line): + match_index = i + break + + if match_index is None: + LOG.error("Error: Could not find line to replace") + return + + # Build replacement block (mirrors the sed-replaced text and indentation) + replacement_block = ( + " self.register_deploy_state_change_listeners()\n" + "\n" + " self._detect_flag_and_set_to_activate_done()\n" + "\n" + " def _detect_flag_and_set_to_activate_done(self):\n" + " ACTIVATE_DONE_FLAG = \"/run/software/.activate-done.flag\"\n" + "\n" + " if os.path.isfile(ACTIVATE_DONE_FLAG):\n" + " os.remove(ACTIVATE_DONE_FLAG)\n" + " deploy_state = DeployState.get_instance()\n" + " deploy_state.activate_done()" + ) + + # Prepare new content, replacing only the first match + new_lines = lines[:match_index] + [replacement_block] + lines[match_index + 1:] + new_content = "\n".join(new_lines) + + # Backup before writing + backup_path = f"{FILE}.bak" + try: + shutil.copy2(FILE, backup_path) + except Exception as e: + LOG.error(f"Error: Unable to create backup at {backup_path}: {e}") + return + + # Write patched content + try: + with open(FILE, "w", encoding="utf-8", newline="\n") as f: + f.write(new_content) + except Exception as e: + LOG.error(f"Error: Failed to write patched file: {e}") + # Attempt restore if write failed after backup + try: + shutil.move(backup_path, FILE) + except Exception as e2: + LOG.error(f"Error: Failed to restore backup: {e2}") + return + + # Post-check: confirm the function reference now exists + try: + with open(FILE, "r", encoding="utf-8") as f: + after = f.read() + except Exception as e: + LOG.error(f"Error: Failed to read back file for verification: {e}") + # Attempt restore + try: + shutil.move(backup_path, FILE) + except Exception as e2: + LOG.error(f"Error: Failed to restore backup: {e2}") + return + + if TO_ADD_LINE not in after: + LOG.error("Error: Patch did not apply correctly. Restoring backup.") + try: + shutil.move(backup_path, FILE) + except Exception as e2: + LOG.error(f"Error: Failed to restore backup: {e2}") + return + + # Remove backup on success + os.remove(backup_path) + + +def restart_vim_services(): + services = ["vim", "vim-api", "vim-webserver"] + for service in services: + command = ["sudo", "sm-restart-safe", "service", service] + try: + result = subprocess.run(command, + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True) + print(f"Successfully restarted {service}:\n{result.stdout}") + except subprocess.CalledProcessError as e: + print(f"Error restarting {service}:\n{e.stderr}") + + +def main(): + action = None + from_release = None + to_release = None + + arg = 1 + while arg < len(sys.argv): + if arg == 1: + from_release = sys.argv[arg] + elif arg == 2: + to_release = sys.argv[arg] + elif arg == 3: + action = sys.argv[arg] + elif arg == 4: + # Optional port + # port = sys.argv[arg] + pass + else: + print("Invalid option %s." % sys.argv[arg]) + return 1 + arg += 1 + configure_logging() + LOG.info( + "%s invoked from_release = %s to_release = %s action = %s" + % (sys.argv[0], from_release, to_release, action) + ) + + try: + if os.path.isfile(RESTART_SERVICES_FLAG): + restart_vim_services() + apply_fix_to_set_activate_done_state() + + open(ACTIVATE_DONE_FLAG, 'a').close() + os.remove(RESTART_SERVICES_FLAG) + + # Restart software-controller service + subprocess.run(["pmon-restart", "software-controller-daemon"], check=True) + except Exception as e: + LOG.error(f"Activate script to restart services failed: {e}") + try: + os.remove(ACTIVATE_DONE_FLAG) + open(RESTART_SERVICES_FLAG, 'a').close() + update_deploy_state("deploy-activate", deploy_state=DEPLOY_STATES.ACTIVATE_FAILED.value) + except Exception as e: + LOG.error(f"Activate script to restart services failed twice: {e}") + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/patch-scripts/start-scripts/24.09.400/pre-start.sh b/patch-scripts/start-scripts/24.09.400/pre-start.sh new file mode 100644 index 00000000..da91029b --- /dev/null +++ b/patch-scripts/start-scripts/24.09.400/pre-start.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# +# Copyright (c) 2025 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +operation="apply" + +if [[ "$1" == --operation=* ]]; then + operation="${1#*=}" +fi + +echo "### Start of pre-start script ###" + +patch="24.09.400" +patch_migration_script_dir="/etc/update.d" +activate_script_name="01-restart-services.py" +extra_script="/opt/software/rel-${patch}/extra/${activate_script_name}" + +if [[ "$operation" == "apply" ]]; then + echo "Running script while applying patch" + # Put commands to run during apply here +else + echo "Running script while removing patch" + # Put commands to run during remove here +fi + +# WA to upload the activate script +echo "Copying activate script" +if [[ -f "$extra_script" ]]; then + cp "$extra_script" "$patch_migration_script_dir" + chmod +x "${patch_migration_script_dir}/${activate_script_name}" + echo "Copied ${activate_script_name} to ${patch_migration_script_dir}" +else + echo "Error: ${extra_script} not found" +fi + +echo "### End of pre-start script ###" diff --git a/software/software/software_controller.py b/software/software/software_controller.py index e900cb54..8afd27ac 100644 --- a/software/software/software_controller.py +++ b/software/software/software_controller.py @@ -1074,6 +1074,17 @@ class PatchController(PatchService): self.register_deploy_state_change_listeners() + # TODO(lvieira) solution for 24.09 upgrade enabler + self._detect_flag_and_set_to_activate_done() + + def _detect_flag_and_set_to_activate_done(self): + ACTIVATE_DONE_FLAG = "/run/software/.activate-done.flag" + + if os.path.isfile(ACTIVATE_DONE_FLAG): + os.remove(ACTIVATE_DONE_FLAG) + deploy_state = DeployState.get_instance() + deploy_state.activate_done() + def _state_changed_sync(self, *args): # pylint: disable=unused-argument if is_simplex(): # ensure the in-sync state for SX