231 lines
7.9 KiB
Python
231 lines
7.9 KiB
Python
"""
|
|
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)
|