refactor: remove service id altogether
Deploy bin / deploy (push) Successful in 2s
Deploy agebd python package / deploy (push) Successful in 4s

This commit is contained in:
Benjamin Labrecque
2026-07-08 16:47:42 +02:00
parent 3d921e69c1
commit debac0cf5e
34 changed files with 76 additions and 92 deletions
@@ -0,0 +1,230 @@
"""
General Infos
- This application is intended to be run as a service on the SLS beam dynamics server for high level applications sls-vserv-bd-hla01
- Control of this service is handled via EPICS from the associated IOC AGEBD-CPCL-MASTER hosted on sls-vserv-bd-01
- Monitoring of this service is handled via EPICS from the generic IOC AGEBD-CPCL-ALH hosted on sls-vserv-bd-01
- Supervision of this service is handled via EPICS -> MASTER-SERVICE -> SYSTEMD -> This Service (wip)
Specific Infos
- This service will provide the possibility to stop, start, restart and change the target version of all available SLS beam dynamics services
Usage: screen -S MASTER bash -c 'source /opt/gfa/python ; python AGEBD-SERVICE-MASTER.py'
"""
# TODO: remove -- test cicd 9
import html
import os
import subprocess
from datetime import datetime
from pathlib import Path
from time import sleep
import typer
import yaml
from agebd.enums import LogLevel
from agebd.pv import PVLink as PV
from agebd.runner import CallbackRunner
from agebd.service.base import BaseService
from agebd.service.pvs import BasePVs
from agebd.utils import get_pv_class, init_logging, printgetversion
__version__ = printgetversion(__file__)
PV = get_pv_class()
HERE_DIR = Path(__file__).parent
CONFIG_DIR = HERE_DIR / ".." / "config"
HLA_NAMES_PATH = CONFIG_DIR / "hla_names.yml"
def read_hla_names():
with open(HLA_NAMES_PATH, "r") as file:
config = yaml.safe_load(file)
# Access your list of apps directly
apps_list = config.get("hla_apps", [])
return apps_list
class PVs(BasePVs):
# output to master panel
logbook = PV("AGEBD-MASTER:LOG")
logclear = PV("AGEBD-MASTER:LOG-CLEAR.VAL")
systemdreq = PV("AGEBD-MASTER:GET-SYSTEMD-STATUS")
def __init__(self, service_name: str, pv_factory):
super().__init__(service_name, pv_factory)
PV = pv_factory
HLAnames = read_hla_names()
self.HLAs = []
for hla in HLAnames:
for todo in ["-START", "-RESTART", "-LOGS"]:
pv1 = "AGEBD-MASTER:" + hla + todo
pv2 = "AGEBD-MASTER:" + hla + todo + "-REQ"
self.HLAs.append(["AGEBD-SERVICE-" + hla, PV(pv1), PV(pv2)])
# prepare sum of all start PVs on IOC for callback
self.CallbackPV = PV("AGEBD-MASTER:CALLBACK-ANY-REQ.VAL")
class Service(BaseService[PVs]):
def __init__(
self, name: str, pvs: PVs, version: str = __version__, sleep_interval: float = 0.1
):
super().__init__(name, pvs, version, sleep_interval)
self.systemd_services = [HLA[0] for HLA in pvs.HLAs[::2]]
self.logs = []
self.log_head = ["Master Service Log, " + str(datetime.now())[:-7] + "<br>"]
def update(self):
# update header
self.log_head = ["Master Service Log, " + str(datetime.now())[:-7] + "<br>"]
# when callback fired, write elog entry
if self.CallbackFired:
# Confirm run of callback
self.CallbackFired = 0
# clear logs
self.logs = []
self.logs.append('<table border="1" style="border-collapse: collapse;">')
for HLA in self.pvs.HLAs[::-1]:
# Check if request is confirmed by IOC
if HLA[1].get():
# Stop request
HLA[2].put(0)
# Get the service name
systemd_service = HLA[0]
if "RESTART" in HLA[1].pvname:
# Check the status of the service
self.systemctl_status(systemd_service, lines=20)
# Reload the unit files
subprocess.run(["systemctl", "--user", "daemon-reload"])
# Restart the service
subprocess.run(["systemctl", "--user", "restart", systemd_service])
# Check the status of the service
self.systemctl_status(systemd_service, lines=20)
elif "START" in HLA[1].pvname:
# Check the status of the service
self.systemctl_status(systemd_service, lines=20)
# Start the service
subprocess.run(["systemctl", "--user", "start", systemd_service])
# Check the status of the service
self.systemctl_status(systemd_service, lines=20)
elif "LOGS" in HLA[1].pvname:
# Check the status of the service
self.systemctl_status(systemd_service, lines=100)
elif self.pvs.logclear.get():
self.logs = []
self.pvs.logclear.put(0)
elif self.pvs.systemdreq.get():
self.logs = []
self.logs.append(
'<table cellpadding="15px" border="1" style="border-collapse: collapse;">'
)
self.pvs.systemdreq.put(0)
for systemd_service in self.systemd_services:
self.systemctl_status(systemd_service)
self.logs.append("</table>")
else:
# wait for callback(s) to fire
sleep(0.2)
# Nchars = len(''.join(self.log_head + [' Chars <br>'] + self.logs))
# logentry = self.log_head + ['{:.0f} Chars <br>'.format(Nchars)] + self.logs
logentry = self.log_head + self.logs
self.pvs.logbook.put("".join(logentry))
def systemctl_status(self, systemd_service, lines=0):
self.logs.append("<tr><td>")
# Check the status of the service
os.environ["SYSTEMD_COLORS"] = "1"
command = ["systemctl", "--user", "--lines=0", "status", systemd_service]
output = (
subprocess.run(command, capture_output=True, text=True)
.stdout.encode("ascii", errors="xmlcharrefreplace")
.decode()
)
output = output.replace("\x1b[0;1;39m", "<span>") # ANSI Default
output = output.replace(
"\x1b[0;1;32m", '<span style="color:#00aa00; font-size:14.0pt; ">'
) # ANSI Green
output = output.replace(
"\x1b[0;1;31m", '<span style="color:#800000; font-size:14.0pt; ">'
) # ANSI Red
output = output.replace("\x1b[0m", "</span>")
output = output.replace("\x1b]8;;\x07", "</a>") # end of hyperlink
output = output.replace("\x1b]8;;", '<a href="') # start of hyperlink
output = output.replace(
"\x07/sls/bd/bin/systemd/AGEBD-SERVICE-", '">/sls/bd/bin/systemd/AGEBD-SERVICE-'
)
status = "<br>".join(output.splitlines())
self.logs.append(status)
self.logs.append("<br><br>")
if lines > 0:
os.environ["SYSTEMD_COLORS"] = "0"
command = [
"journalctl",
"--user",
"--lines={:.0f}".format(lines),
"--no-hostname",
"--no-pager",
"--all",
"--unit={:}".format(systemd_service),
]
output = (
subprocess.run(command, capture_output=True, text=True)
.stdout.encode("ascii", errors="xmlcharrefreplace")
.decode()
)
output = html.escape(output)
status = "<br>".join(output.splitlines()[::-1][:-1])
print(command)
self.logs.append(status)
self.logs.append("</td></tr>")
def main(
log_level: LogLevel = typer.Option(
LogLevel.INFO,
"--log-level",
"-l",
# Looks at the OS env variable first. If empty, falls back to "INFO"
envvar="AGEBD_LOG_LEVEL",
# help="Set log level: DEBUG, INFO, WARNING, ERROR, CRITICAL",
case_sensitive=False,
),
):
init_logging(log_level)
service_name = "MASTER"
pvs = PVs(service_name=service_name, pv_factory=PV)
service = Service(name=service_name, pvs=pvs)
runner = CallbackRunner(service=service)
runner.start()
if __name__ == "__main__":
typer.run(main)