systemd: Introduce common templates

We already use templating to generate sockets, which are all
based off libvirtd's. Push the idea further, and extend it to
cover services as well.

This is more challenging, as the various modular daemons each have
their own needs in terms of what system services needs to be
available before they can be started, which other components of
libvirt they depend on, and so on.

In order to make this sort of per-service tweaks possible, we
introduce a Python script that can merge two systemd units
together. The script is aware of the semantics of systemd's unit
definition format, so it can intelligently merge sections
together.

This generic systemd unit merging mechanism will also supersede
the extremely ad-hoc @deps@ variable, which is currently used in
a single scenario.

Signed-off-by: Andrea Bolognani <abologna@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
This commit is contained in:
Andrea Bolognani 2023-09-19 19:28:55 +02:00
parent 80ee76218c
commit e86390c9b4
9 changed files with 204 additions and 0 deletions

94
scripts/merge-systemd-units.py Executable file
View File

@ -0,0 +1,94 @@
#!/usr/bin/env python3
# Copyright (C) 2023 Red Hat, Inc.
# SPDX-License-Identifier: LGPL-2.1-or-later
import sys
SECTIONS = [
"[Unit]",
"[Service]",
"[Socket]",
"[Install]",
]
def parse_unit(unit_path):
unit = {}
current_section = "[Invalid]"
with open(unit_path) as f:
for line in f:
line = line.strip()
if line == "":
continue
if line[0] == "[" and line[-1] == "]":
if line not in SECTIONS:
print("Unknown section {}".format(line))
sys.exit(1)
current_section = line
continue
if current_section not in unit:
unit[current_section] = []
unit[current_section].append(line)
if "[Invalid]" in unit:
print("Contents found outside of any section")
sys.exit(1)
return unit
def format_unit(unit):
lines = []
for section in SECTIONS:
if section not in unit:
continue
lines.append(section)
for line in unit[section]:
lines.append(line)
lines.append("")
return "\n".join(lines)
def merge_units(base, extra):
merged = {}
for section in SECTIONS:
if section in extra and section not in base:
print("Section {} in extra but not in base".format(section))
sys.exit(1)
if section not in base:
continue
merged[section] = base[section]
if section not in extra:
continue
merged[section].extend(extra[section])
return merged
if len(sys.argv) < 2:
print("usage: {} BASE EXTRA".format(sys.argv[0]))
sys.exit(1)
base = parse_unit(sys.argv[1])
extra = parse_unit(sys.argv[2])
merged = merge_units(base, extra)
sys.stdout.write(format_unit(merged))

View File

@ -19,6 +19,7 @@ scripts = [
'header-ifdef.py', 'header-ifdef.py',
'hvsupport.py', 'hvsupport.py',
'hyperv_wmi_generator.py', 'hyperv_wmi_generator.py',
'merge-systemd-units.py',
'meson-dist.py', 'meson-dist.py',
'meson-gen-authors.py', 'meson-gen-authors.py',
'meson-gen-def.py', 'meson-gen-def.py',

View File

@ -203,6 +203,8 @@ libvirtd_socket_admin_in = files('remote' / 'libvirtd-admin.socket.in')
# * sockets - array of additional sockets (optional, default [ 'main', 'ro', 'admin' ]) # * sockets - array of additional sockets (optional, default [ 'main', 'ro', 'admin' ])
# * service_in - service source file (optional, default remote/libvirtd.service.in) # * service_in - service source file (optional, default remote/libvirtd.service.in)
# * socket_$name_in - additional socket source files (optional, default remote/libvirtd.socket.in ) # * socket_$name_in - additional socket source files (optional, default remote/libvirtd.socket.in )
# * service_extra_in - unit to merge with service_in (optional, default None)
# * socket_extra_in - unit to merge with socket_$name_in (optional, default None)
# * deps - socket dependencies (optional, default '') # * deps - socket dependencies (optional, default '')
virt_daemon_units = [] virt_daemon_units = []
@ -817,6 +819,7 @@ if conf.has('WITH_LIBVIRTD')
'initconfdir': initconfdir, 'initconfdir': initconfdir,
'name': unit['name'], 'name': unit['name'],
'service': unit['service'], 'service': unit['service'],
'SERVICE': unit['service'].to_upper(),
'sockprefix': unit.get('sockprefix', unit['service']), 'sockprefix': unit.get('sockprefix', unit['service']),
'deps': unit.get('deps', ''), 'deps': unit.get('deps', ''),
'sockmode': sockmode, 'sockmode': sockmode,
@ -825,6 +828,15 @@ if conf.has('WITH_LIBVIRTD')
service_in = unit.get('service_in', service_in_default) service_in = unit.get('service_in', service_in_default)
service_out = '@0@.service'.format(unit['service']) service_out = '@0@.service'.format(unit['service'])
if 'service_extra_in' in unit
service_in = configure_file(
input: [ service_in, unit['service_extra_in'] ],
output: '@0@.in'.format(service_out),
command: [ merge_systemd_units_prog, '@INPUT0@', '@INPUT1@' ],
capture: true,
)
endif
configure_file( configure_file(
input: service_in, input: service_in,
output: service_out, output: service_out,
@ -843,6 +855,16 @@ if conf.has('WITH_LIBVIRTD')
socket_in = unit.get('socket_@0@_in'.format(socket), socket_in_default) socket_in = unit.get('socket_@0@_in'.format(socket), socket_in_default)
socket_out = '@0@-@1@.socket'.format(unit['service'], socket) socket_out = '@0@-@1@.socket'.format(unit['service'], socket)
endif endif
if 'socket_extra_in' in unit
socket_in = configure_file(
input: [ socket_in, unit['socket_extra_in'] ],
output: '@0@.in'.format(socket_out),
command: [ merge_systemd_units_prog, '@INPUT0@', '@INPUT1@' ],
capture: true,
)
endif
configure_file( configure_file(
input: socket_in, input: socket_in,
output: socket_out, output: socket_out,

13
src/virtd-admin.socket.in Normal file
View File

@ -0,0 +1,13 @@
[Unit]
Description=@name@ admin socket
Before=@service@.service
BindsTo=@service@.socket
After=@service@.socket
[Socket]
ListenStream=@runstatedir@/libvirt/@sockprefix@-admin-sock
Service=@service@.service
SocketMode=0600
[Install]
WantedBy=sockets.target

13
src/virtd-ro.socket.in Normal file
View File

@ -0,0 +1,13 @@
[Unit]
Description=@name@ local read-only socket
Before=@service@.service
BindsTo=@service@.socket
After=@service@.socket
[Socket]
ListenStream=@runstatedir@/libvirt/@sockprefix@-sock-ro
Service=@service@.service
SocketMode=0666
[Install]
WantedBy=sockets.target

12
src/virtd-tcp.socket.in Normal file
View File

@ -0,0 +1,12 @@
[Unit]
Description=@name@ non-TLS IP socket
Before=@service@.service
BindsTo=@service@.socket
After=@service@.socket
[Socket]
ListenStream=16509
Service=@service@.service
[Install]
WantedBy=sockets.target

12
src/virtd-tls.socket.in Normal file
View File

@ -0,0 +1,12 @@
[Unit]
Description=@name@ TLS IP socket
Before=@service@.service
BindsTo=@service@.socket
After=@service@.socket
[Socket]
ListenStream=16514
Service=@service@.service
[Install]
WantedBy=sockets.target

25
src/virtd.service.in Normal file
View File

@ -0,0 +1,25 @@
[Unit]
Description=@name@ daemon
Conflicts=libvirtd.service
Requires=@service@.socket
Requires=@service@-ro.socket
Requires=@service@-admin.socket
After=network.target
After=dbus.service
After=apparmor.service
Documentation=man:@service@(8)
Documentation=https://libvirt.org
[Service]
Type=notify
Environment=@SERVICE@_ARGS="--timeout 120"
EnvironmentFile=-@initconfdir@/@service@
ExecStart=@sbindir@/@service@ $@SERVICE@_ARGS
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
[Install]
WantedBy=multi-user.target
Also=@service@.socket
Also=@service@-ro.socket
Also=@service@-admin.socket

12
src/virtd.socket.in Normal file
View File

@ -0,0 +1,12 @@
[Unit]
Description=@name@ local socket
Before=@service@.service
[Socket]
ListenStream=@runstatedir@/libvirt/@sockprefix@-sock
Service=@service@.service
SocketMode=@sockmode@
RemoveOnStop=yes
[Install]
WantedBy=sockets.target