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-service
requests 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 <udhcpc
ifname>>
. 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-interface
ifname <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 <daemon
name>
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-wwan
devicePath>
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-service
forqmi-wwan
service names is defined to require a<daemon <qmi-wwan-manager
deviceName APN>>
service, defined elsewhere; in addition, when arun-service
assertion appears, a log message is produced. -
the stanza reacting to
run-service
is only active when some<user-setting <mobile-data-apn
APN>>
assertion exists. -
the stanza querying the
mobile-data-apn
user 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.
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.