1

I'm trying to launch a meta-service remotely through a socket. Requirements:

  1. The socket should start the meta-service when a connection is established
  2. The meta-service should start all child (Wants=) services upon start
  3. When the connection is closed, the meta-service should stop
  4. When the meta-service stops, it should stop all child (ConsistsOf=) services

I'm not expecting multiple connections, so my requirements are undefined if multiple connections are made.

Here's one attempt:

# simple.socket
[Unit]
Description=Socket

[Socket]
ListenStream=11111
Accept=no

# simple.service
[Unit]
Description=Meta-service
Wants=simple-child.service # Will be a full tree of dependencies

[Service]
ExecStart=-cat -  # cat will fail to start because it doesn't accept connections
StandardInput=socket

# simple-child.service
[Unit]
Description=Child1
PartOf=simple.service # Puts a ConsistsOf= relationship in simple.service

[Service]
ExecStart=sleep infinity # Business goes here

The problem here is that when Accept=no, the ExecStart= is responsible for handling the incoming connections. cat - doesn't call accept() and so simple.service will fail to start. Is there another basic tool I could use in ExecStart= which would accept() the connections, but close when the a connection is stopped? It could be the first or last connection, I'm agnostic to that. That would be the easiest solution which also solves the rest of the question. Is there an example of a C-application which uses accept() so I could figure out how systemd passes sockfd for our first argument accept(int sockfd, ...)? Then I could write something myself. I tried running this, but I kept getting failures when using bind().

Here's another attempt using Accept=yes:

# simple.socket
[Unit]
Description=Socket

[Socket]
ListenStream=11111
Accept=yes

# simple@.service  # Note the template here
[Unit]
Description=Meta-service
Wants=simple-child.service

[Service]
ExecStart=-cat - # now cat will work!
StandardInput=socket

# simple-child.service
[Unit]
Description=Child1
PartOf=simple@.service # This fails to connect to the instance

[Service]
ExecStart=sleep infinity # Business goes here

In this case, everything starts great. When the connection is closed, simple@XYZ.service stops nicely, but simple-child.service keeps running. That's because PartOf=simple@.service does not refer to the correct instance. I'd REALLY prefer to avoid templating simple-child.service, but let's try it:

# simple.socket
[Unit]
Description=Socket

[Socket]
ListenStream=11111
Accept=yes

# simple@.service
[Unit]
Description=Meta-service
Wants=simple-child@%i.service  % Starts simple-child as a template

[Service]
ExecStart=-cat -
StandardInput=socket

# simple-child@.service # Newly templated
[Unit]
Description=Child1
PartOf=simple@%i.service  # Using %i 

[Service]
ExecStart=sleep infinity # Business goes here

In this case simple@.service is templated as simple@6-127.0.0.1:11111-127.0.0.1:49276.service, but %i is only 6. It spawns simple-child@6.service which is only PartOf=simple@6.service and so it fails to stop when simple@6-127.0.0.1:11111-127.0.0.1:49276.service stops.

0

I have a solution, but it's in python which is not ideal for dependency reasons.

# simple.socket
[Unit]
Description=Socket

[Socket]
ListenStream=11111
Accept=no

# simple.service
[Unit]
Description=Meta-service
Wants=simple-child.service

[Service]
ExecStart=/usr/local/bin/listen.py

# simple-child.service
[Unit]
Description=Child1
PartOf=simple.service

[Service]
ExecStart=sleep infinity

And the python script is:

#!/usr/bin/env python3

from socketserver import TCPServer, StreamRequestHandler
import socket

class Handler(StreamRequestHandler):
    def handle(self):
        while True:
            c = self.rfile.read(1)
            if len(c) == 0:
                exit()

class Server(TCPServer):

    SYSTEMD_FIRST_SOCKET_FD = 3

    def __init__(self, server_address, handler_cls):
        TCPServer.__init__(self, server_address, handler_cls, bind_and_activate=False)
        self.socket = socket.fromfd(self.SYSTEMD_FIRST_SOCKET_FD, self.address_family, self.socket_type)

if __name__ == "__main__":
    HOST, PORT = "12.34.56.78", 12345 # This gets overridden by systemd
    server = Server((HOST, PORT), Handler)
    server.serve_forever()

Now when the connection is established, simple.service and simple-child.service both launch. When the connection is killed simple.service stops, but somehow simple-child.service doesn't stop despite the PartOf= relationship. Adding ExecStop=systemctl stop simple-child.service to simple.service does work, but that's not a great solution.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.