book/operation/howto/define-services
How to define services and service classes
Synit services are started in response
to run-service
assertions. These, in turn, are eventually asserted by the service
dependency tracker in response to require-service
assertions, once any declared dependencies have been started.
So to implement a service, respond to run-service
records mentioning the service’s name.
There are a number of concepts involved in service definitions:
Service name. A unique identifier for a service instance.
Service implementation. Code that responds to
run-servicerequests for a service instance to start running, implementing the service’s ongoing behaviour.Service class. A parameterized collection of services sharing a common parameterized implementation.
A service may be an instance of a service class (a parameterized family of services) or may be a simple service that is the only instance of its class. Service dependencies can be statically-declared or dynamically-computed.
A service’s implementation may be external, running as a subprocess managed
by syndicate-server; internal, backed by code that is part
of the syndicate-server process itself; or user-defined,
implemented via user-supplied code written in the configuration language or as other actor
programs connected somehow to the system bus.
An external service may involve a long-running process (a “daemon”;
what s6-rc
calls a “longrun”), or may involve a short-lived activity that, at
startup or shutdown, modifies aspects of overall system state outside
the purview of the supervision tree (what s6-rc
calls a “one-shot”).
Service names
Every service is identified with its name. A service name can be any Preserves value. A simple symbol may suffice, but records and dictionaries are often useful in giving structure to service names.
Here are a few example service names:
<config-watcher "/foo/bar" $.>1<daemon docker><milestone network><qmi-wwan "/dev/cdc-wdm0"><udhcpc "eth0">
The first two invoke service behaviours that are built-in to syndicate-server;
the last three are user-defined service names.
Defining a simple external service
As an example of a simple external service, take the
ntpd daemon. The following assertions placed in the
configuration file /etc/syndicate/services/ntpd.pr cause
ntpd to be run as part of the Synit
services layer.
First, we choose the service name: <daemon ntpd>.
The name is a daemon record, marking it as a supervised external service. Having chosen a name, and
chosen to use the external service supervision mechanism to run the
service, we make our first assertion, which defines the program to be
launched:
<daemon ntpd "ntpd -d -n -p pool.ntp.org">
Next, we mark the service as depending on the presence of another
assertion, <default-route ipv4>. This assertion is
managed by the networking
core.
<depends-on <daemon ntpd> <default-route ipv4>>
These two assertions are, together, the total of the definition of the service.
However, without a final require-service assertion, the
service will not be activated. By requiring the service, we connect
the service definition into the system dependency tree, enabling actual
loading and activation of the service.
<require-service <daemon ntpd>>
Defining a service class
The following stanza (actually part of the networking core) waits
for run-service assertions matching a family of
service names,
<daemon <udhcpcifname>>.
When it sees one, it computes the specification for the corresponding
command-line, on the fly, substituting the value of the ifname
binding in the correct places (once in the service name and once in the
command-line specification).
? <run-service <daemon <udhcpc ?ifname>>> [
<daemon
<udhcpc $ifname>
["udhcpc" "-i" $ifname "-fR" "-s" "/usr/lib/synit/udhcpc.script"]
>
]
This suffices to define the service. To instantiate it, we may either manually provide assertions mentioning the interfaces we care about,
<require-service <daemon <udhcpc "eth0">>>
<require-service <daemon <udhcpc "wlan0">>>
or, as actually implemented in the networking core (in
network.pr lines 13–15
and 42–47),
we may respond to assertions placed in the dataspace by a
daemon, interface-monitor, whose role is to reflect AF_NETLINK events into
assertions:
? <configure-interface ?ifname <dhcp>> [
<require-service <daemon <udhcpc $ifname>>>
]
Here, when an assertion of the form
<configure-interfaceifname<dhcp>>
appears in the dataspace, we react by asserting a
require-service record that in turn eventually triggers
assertion of a matching run-service, which then in turn
results in invocation of the udhcpc command-line we
specified above.
Defining
non-daemon services; reacting to user settings
Only service names of the form
<daemonname> are backed by external service supervisor code. Other
service name schemes have other implementations. In particualr,
user-defined service name schemes are possible and useful.
For example, in the configuration relating to setup
of mobile data interfaces, service names of the form
<qmi-wwandevicePath> are
defined:
? <user-setting <mobile-data-enabled>> [
? <user-setting <mobile-data-apn ?apn>> [
? <run-service <qmi-wwan ?dev>> [
<require-service <daemon <qmi-wwan-manager $dev $apn>>>
$log ! <log "-" { line: "starting wwan manager", dev: $dev, apn: $apn }>
]
]
]
Reading this inside-out,
run-serviceforqmi-wwanservice names is defined to require a<daemon <qmi-wwan-managerdeviceName APN>>service, defined elsewhere; in addition, when arun-serviceassertion appears, a log message is produced.the stanza reacting to
run-serviceis only active when some<user-setting <mobile-data-apnAPN>>assertion exists.the stanza querying the
mobile-data-apnuser setting is itself only active when<user-setting <mobile-data-enabled>>has been asserted.
In sum, this means that even if a qmi-wwan service is
requested and activated, nothing will happen until the user enables
mobile data and selects an APN. If the
user later disables mobile data, the qmi-wwan
implementation will automatically be retracted, and the corresponding
qmi-wwan-manager service terminated.
Copyright © 2021–2023 Tony Garnock-Jones, CC BY 4.0
This first service name example is interesting because it includes an embedded capability reference using the
$.syntax from the scripting language to denote the active scripting language environment dictionary.↩︎
