Merge "Patch activate script to restart service"

This commit is contained in:
Zuul
2025-09-26 14:26:51 +00:00
committed by Gerrit Code Review
4 changed files with 318 additions and 0 deletions

View File

@@ -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/<relevant-script> 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
```

View File

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

View File

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

View File

@@ -1108,6 +1108,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