Skip to content
Snippets Groups Projects
Commit 87501d44 authored by Erik von Reis's avatar Erik von Reis
Browse files

fe systemd generator: initial commit.

parent 33f7a438
No related branches found
No related tags found
1 merge request!188Front end systemd generator
......@@ -43,3 +43,6 @@ modules.order
# sphinx doc files
*_build
*_static
# worktree direcotries
branch/
# Setup the various processes for the front end startup
import os
import os.path as path
class Process(object):
def __init__(self, is_in_world, start, end):
self.is_in_world = is_in_world
self.start = start
self.end = end
class FrontEndProcesses(object):
def __init__(self, target_dir):
self.target_dir = target_dir
target_name = path.join(self.target_dir, "rts-models.target")
with open(target_name, "wt") as f:
f.write("""[Unit]
Description=All models
""")
@staticmethod
def dolphin_port():
return Process(False, 'rts-dolphin-port.service', 'rts-dolphin-port.service')
def dolphin_drivers(self):
# link up from one to the next
services = ['dis_kosif.service', 'dis_ix.service', 'dis_irm.service', 'dis_sisci.service',
'dis_nodemgr.service']
self.serialize_units(services)
return Process(False, 'dis_kosif.service', 'dis_nodemgr.service')
def epics_only_models(self, models):
target_name = path.join(self.target_dir, "rts-epics-only-models.target")
with open(target_name, "wt") as f:
f.write("""[Unit]
Description=All epics only models
""")
services = [f"rts-epics@{model}.service" for model in models]
self.serialize_units(services)
for service in services:
self.link_to(service, target_name)
self.link_to("rts-epics-only-models.target", "rts-models.target")
return Process(True, "rts-epics-only-models.target", services[-1])
def iop_model(self, model):
link_path = path.join(self.target_dir, "rts-iop-model.target")
os.symlink(f"rts@{model}.target", link_path)
self.link_to("rts-iop-model.target", "rts-models.target")
return Process(True, f"rts-iop-model.target", f"rts-awgtpman@{model}.service")
def user_models(self, models):
target_name = path.join(self.target_dir, "rts-user-models.target")
with open(target_name, "wt") as f:
f.write("""[Unit]
Description=All user models
""")
for i in range(1, len(models)):
self.after(f"rts@{models[i]}.target", f"rts-epics@{models[i-1]}.service")
for model in models:
self.link_to(f"rts@{model}.target", target_name)
self.link_to("rts-user-models.target", "rts-models.target")
return Process(True, f"rts-user-models.target", f"rts-awgtpman@{models[-1]}.service")
def edcs(self, edcs):
"""
Takes a list of edc names and returns a Process object that can be used to start them all.
"""
services = [f"rts-edc_{edc}.service" for edc in edcs]
self.serialize_units(services)
return Process(True, services[0], services[-1])
def streaming(self, options):
# check transport specifier exists
ts = 'transport_specifier'
if not (ts in options and options[ts]):
raise Exception(f"option '{ts}' must be determined when reading options")
# set up port
fpath = path.join(self.target_dir, "rts-daq.network")
with open(fpath, "wt") as f:
f.write(f"""[Match]
Name={options['DAQ_ETH_DEV']}
[Link]
MTUBytes=9000
[Network]
Description=Interface for streaming data to the DAQ
Address={options['DAQ_ETH_IP']}
""")
# setup services
services = ["rts-local_dc.service", f"rts-tranpsport@{options[ts]}.service"]
self.serialize_units(services)
# setup target
targ_unit_name = "rts-transport.target"
targ_path = path.join(self.target_dir, targ_unit_name)
with open(targ_path, "wt") as f:
f.write(f"""[Unit]
Description=Start transport of data to DAQ
Wants={" ".join(services)}
""")
for service in services:
self.link_to(service, targ_unit_name)
return Process(True, services[0], services[-1])
def serialize_units(self, services):
"""
Take a list of systemd unit names and put one after the other.
"""
for i in range(1, len(services)):
self.after(services[i], services[i-1])
def create_world_target(self):
with open(path.join(self.target_dir, "rts-world.target", "wt")) as world:
world.write("""[Unit]
Description=All model and streaming services for Front End servers
[Install]
WantedBy=multi-user.target
""")
def link_to(self, unit_name, target_name):
"""
Makes systemd unit unit_name wanted by target_name
:return:
"""
wants_path = path.join(self.target_dir, f"{target_name}.wants")
os.makedirs(wants_path, exist_ok=True)
targ = path.join("..", unit_name)
link = path.join(wants_path, unit_name)
os.symlink(targ, link)
def part_of(self, unit_name, parent_name):
"""
Make unit_name a part of parent_name, so that when parent_name is stopped, unit_name is as well.
"""
unit_d = path.join(self.target_dir, f"{unit_name}.d")
os.makedirs(unit_d, exist_ok=True)
fname = path.join(unit_d, f"part of {parent_name}.conf")
with open(fname, "wt") as conf:
conf.write(f"""[Unit]
PartOf={parent_name}
""")
def after(self, after_name, before_name):
"""
Make the systemd unit after_name start after before_name is finished
"""
after_d = path.join(self.target_dir, f"{after_name}.d")
os.makedirs(after_d, exist_ok=True)
fname = path.join(after_d, f"after {before_name}.conf")
with open(fname, "wt") as conf:
conf.write(f"""[Unit]
After={before_name}
""")
#!/usr/bin/python3
# Generate systemd unit files needed for LIGO CDS front-end
# server startup sequences
from sequencer import Sequencer
from options import get_options
import sys
if __name__ == '__main__':
seq = Sequencer(get_options(), sys.argv[1])
seq.create_start_sequence()
# gather options for generating systemd units
import os
import os.path as path
def split(s):
"""Split a string on whitespace"""
return s.split()
def boolean(s):
"""Convert a string into True or False.
Return true only if first character is a 'T' of either case"""
return s[0] == "t" or s[0] == "T"
class Variable(object):
def __init__(self, convert, default=None):
self.convert = convert
self.default = default
# net every expected variable is set up here, only those that need translation from a string
# or need a default value
variables = {
'USER_MODELS': Variable(split),
'EPICS_ONLY_MODELS': Variable(split),
'EDC': Variable(split),
'IS_DOLPHIN_NODE': Variable(boolean, False),
'DAQ_STREAMING': Variable(boolean, False),
}
def get_options():
# first check /etc/advligorts/systemd_env.. files
options = {}
read_env_file("/etc/advligorts/systemd_env", options)
host_name = os.uname()[1]
read_env_file(f"/etc/advligorts/systemd_env_{host_name}", options)
# also if rtsystab is available, prefer its unit list
read_rtsystab(f"/etc/rtsystab", options, host_name)
# process the options into right type, etc, and create a few that aren't to be found in the files
for name,var in variables.items():
if name in options:
options[name] = var.convert(options[name])
else:
options[name] = var.default
if 'USER_MODELS' in options:
user_models = options['USER_MODELS']
else:
user_models = []
if 'IOP_MODEL' in options:
iop_model = options['IOP_MODEL']
else:
iop_model = ""
if 'EPICS_ONLY_MODELS' in options:
global_eo_models = options['EPICS_ONLY_MODELS']
else:
global_eo_models = []
eo_models = []
if iop_model in global_eo_models:
del options['IOP_MODEL']
eo_models.append(iop_model)
filtered_user_models = []
for model in user_models:
if model in global_eo_models:
eo_models.append(model)
else:
filtered_user_models.append(model)
options['USER_MODELS'] = filtered_user_models
options['EPICS_ONLY_MODELS'] = eo_models
options['HAS_DOLPHIN_PORT'] = path.exists('/etc/dolphin_ix_port_ctl.sh')
options['HAS_EPICS_ONLY_MODELS'] = len(eo_models) > 0
options['HAS_IOP_MODEL'] = 'IOP_MODEL' in options
options['HAS_USER_MODELS'] = len(filtered_user_models) > 0
options['HAS_EDC'] = "EDCS" in options and len(options["EDCS"]) > 0
if 'cps_xmit_args' in options:
options['transport_specifier'] = 'cps_xmit'
def read_env_file(fname, options):
try:
with open(fname, "rt") as f:
for line in f.readlines():
if line.strip()[0] == '#':
continue
# ignore anything without an equal sign
if line.find('=') < 0:
continue
pair = line.split('=')
key = pair[0].strip()
value = pair[1].strip()
options[key] = value
except IOError:
# don't do anything if file not there
pass
def read_rtsystab(fname, options, host_name):
try:
with open(fname, "rt") as f:
for line in f.readlines():
if line.strip()[0] == '#':
continue
words = [word.strip() for word in line.split()]
if len(words) > 1 and words[0] == host_name:
options["IOP_MODEL"] = words[1]
options["USER_MODELS"] = words[2:]
return
except IOError:
pass
# Set the start sequence for front ends and other diskless systems
from front_end import FrontEndProcesses
from cdsrfm import CDSRFMProcesses
import os.path as path
import os
class Sequencer(object):
def __init__(self, options, target_dir):
"""options is a dictionary of host-specific key/value pairs that will determine what
elements are added to the start sequence
target_dir is the target directory for unit files
"""
self.options = options
self.target_dir = target_dir
self.processes = FrontEndProcesses(target_dir)
def create_start_sequence(self):
if not self.options["START_MODELS"]:
return
if self.options["CDSRFM"]:
sequence = self.create_cdsrfm_start_sequence(CDSRFMProcesses(self.target_dir))
else:
sequence = self.create_frontend_start_sequence()
self.link_sequence(sequence)
def create_frontend_start_sequence(self):
sequence = []
if self.options["HAS_DOLPHIN_PORT"] and self.options["IS_DOLPHIN_NODE"]:
sequence.append(self.processes.dolphin_port())
sequence.append(self.delay(15, "wait_for_dolphin_port"))
if self.options["IS_DOLPHIN_NODE"]:
sequence.append(self.processes.dolphin_drivers())
sequence.append(self.delay(15), "wait_for_dolphin_driver")
if self.options["HAS_EPICS_ONLY_MODELS"]:
sequence.append(self.processes.epics_only_models(self.options["EPICS_ONLY_MODELS"]))
if self.options["HAS_IOP_MODEL"]:
sequence.append(self.processes.iop_model(self.options["IOP_MODEL"]))
if self.options["HAS_EDC"]:
sequence.append(self.processes.edcs(self.options["EDCS"]))
if self.options["DAQ_STREAMING"]:
sequence.append(self.processes.streaming(self.options))
if self.options["HAS_USER_MODELS"]:
sequence.append(self.processes.user_models(self.options["USER_MODELS"]))
return sequence
@staticmethod
def create_cdsrfm_start_sequence(front_end_processes, cdsrfm_processes):
sequence = [
front_end_processes.DolphinPort(),
front_end_processes.DolphinDrivers(),
cdsrfm_processes.CDSRFMEpics(),
cdsrfm_processes.CDSRFMModule()]
return sequence
def link_sequence(self, sequence):
self.processes.create_world_target()
# link the first of each process to multi-user or to the world target
for process in sequence:
if process.is_in_world:
self.processes.wanted_by(process.start, "rts-world.target")
self.processes.part_of(process.start, "rts-world.target")
else:
self.processes.link_to(process.start, "multi-user.target")
for i in range(1, len(sequence)):
self.processes.after(sequence[i].start, sequence[i-1].end)
class Delay(object):
is_in_world = False
def __init__(self, unit_name):
self.start = unit_name
self.end = unit_name
def delay(self, time_s, name):
file_name = f"{name}.service"
file_path = path.join(self.target_dir, file_name)
with open(file_path, "wt") as f:
f"""[Unit]
Description=Delay for {time_s} seconds
[Service]
ExecStartPre=/bin/sleep {time_s}
ExecStart=/usr/bin/echo 'finished waiting for {name}'
"""
return self.Delay(file_name)
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment