X-Git-Url: https://tinc-vpn.org/git/browse?p=tinc;a=blobdiff_plain;f=test%2Fintegration%2Fsystemd.py;fp=test%2Fintegration%2Fsystemd.py;h=ccf2f23be292e2ef893b06f3ad73bb8edb08af41;hp=0000000000000000000000000000000000000000;hb=c8402791b82947c49ba1d04f855dab04191607ca;hpb=66eb66ec8f872db3dc12e1d01101772918d69a4a diff --git a/test/integration/systemd.py b/test/integration/systemd.py new file mode 100755 index 00000000..ccf2f23b --- /dev/null +++ b/test/integration/systemd.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 + +"""Test systemd integration.""" + +import os +import socket +import tempfile +import time + +from testlib import check, path +from testlib.log import log +from testlib.feature import Feature +from testlib.const import MAXSOCKETS +from testlib.proc import Tinc, Script +from testlib.test import Test + + +def tincd_start_socket(foo: Tinc, pass_pid: bool) -> int: + """Start tincd as systemd socket activation does it.""" + + pid = os.fork() + if not pid: + env = {**os.environ, "LISTEN_FDS": str(MAXSOCKETS + 1)} + if pass_pid: + env["LISTEN_PID"] = str(os.getpid()) + args = [ + path.TINCD_PATH, + "-c", + foo.work_dir, + "--pidfile", + foo.pid_file, + "--logfile", + foo.sub("log"), + ] + assert not os.execve(path.TINCD_PATH, args, env) + + assert pid > 0 + + _, status = os.waitpid(pid, 0) + assert os.WIFEXITED(status) + return os.WEXITSTATUS(status) + + +def test_listen_fds(foo: Tinc) -> None: + """Test systemd socket activation.""" + + foo_log = foo.sub("log") + + log.info("foreground tincd fails with too high LISTEN_FDS") + status = tincd_start_socket(foo, pass_pid=True) + check.failure(status) + check.in_file(foo_log, "Too many listening sockets") + + foo.add_script(Script.TINC_UP) + foo.add_script(Script.TINC_DOWN) + os.remove(foo_log) + + log.info("LISTEN_FDS is ignored without LISTEN_PID") + status = tincd_start_socket(foo, pass_pid=False) + foo[Script.TINC_UP].wait() + foo.cmd("stop") + foo[Script.TINC_DOWN].wait() + check.success(status) + check.not_in_file(foo_log, "Too many listening sockets") + + +def recv_until(sock: socket.socket, want: bytes) -> None: + """Receive from a datagram socket until a specific value is found.""" + + while True: + msg = sock.recv(65000) + log.info("received %s", msg) + if msg == want: + break + + +def test_watchdog(foo: Tinc) -> None: + """Test systemd watchdog.""" + + address = tempfile.mktemp() + foo_log = foo.sub("log") + + log.info("watchdog is disabled if no env vars are passed") + foo.cmd("start", "--logfile", foo_log) + foo.cmd("stop") + check.in_file(foo_log, "Watchdog is disabled") + + log.info("watchdog is enabled by systemd env vars") + foo.add_script(Script.TINC_UP) + foo.add_script(Script.TINC_DOWN) + + with socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) as sock: + sock.bind(address) + + watchdog = 0.5 + env = {"NOTIFY_SOCKET": address, "WATCHDOG_USEC": str(int(watchdog * 1e6))} + proc = foo.tincd("-D", env=env) + recv_until(sock, b"READY=1") + + for _ in range(6): + before = time.monotonic() + recv_until(sock, b"WATCHDOG=1") + spent = time.monotonic() - before + assert spent < watchdog + + foo.cmd("stop") + recv_until(sock, b"STOPPING=1") + + check.success(proc.wait()) + + +with Test("socket activation") as context: + test_listen_fds(context.node(init=True)) + +with Test("watchdog") as context: + node = context.node(init=True) + if Feature.WATCHDOG in node.features: + test_watchdog(node)