Merge "Patch activate script to restart service"
This commit is contained in:
72
patch-scripts/activate-scripts/README.md
Normal file
72
patch-scripts/activate-scripts/README.md
Normal 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
|
||||
```
|
196
patch-scripts/activate-scripts/examples/01-restart-services.py
Normal file
196
patch-scripts/activate-scripts/examples/01-restart-services.py
Normal 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())
|
39
patch-scripts/start-scripts/24.09.400/pre-start.sh
Normal file
39
patch-scripts/start-scripts/24.09.400/pre-start.sh
Normal 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 ###"
|
@@ -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
|
||||
|
Reference in New Issue
Block a user