]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
manager: supervisord plugin for creating PIPE to stdout
authorVasek Sraier <git@vakabus.cz>
Tue, 12 Jul 2022 13:46:27 +0000 (15:46 +0200)
committerVasek Sraier <git@vakabus.cz>
Sun, 31 Jul 2022 15:26:39 +0000 (17:26 +0200)
manager/knot_resolver_manager/kresd_controller/supervisord/plugin/stdout_pipe_log.py [new file with mode: 0644]
manager/knot_resolver_manager/kresd_controller/supervisord/supervisord.conf.j2

diff --git a/manager/knot_resolver_manager/kresd_controller/supervisord/plugin/stdout_pipe_log.py b/manager/knot_resolver_manager/kresd_controller/supervisord/plugin/stdout_pipe_log.py
new file mode 100644 (file)
index 0000000..542bdde
--- /dev/null
@@ -0,0 +1,52 @@
+# type: ignore
+# pylint: disable=protected-access
+"""
+Plugin which creates a new fd at `NEW_STDOUT_FD` and a thread copying data from there to actual stdout.
+Why would we want this? Because when running under systemd, stdout FD is a socket and we can't open it
+by calling `open("/proc/self/fd/1")`. We can do this with pipes though. So in order to transparently pass
+stdout from manager to stdout of supervisord, we are configuring manager to use /proc/self/fd/42001 as its
+logfile. Then we are routing the data to the actual supervisord's stdout.
+
+Performance should not be a problem as this is not a performance critical component.
+"""
+import os
+import sys
+from threading import Thread
+from typing import Any
+
+from supervisor.supervisord import Supervisor
+
+# when changing this, change it in supervisord.conf.j2 as well
+NEW_STDOUT_FD = 42
+
+
+class SplicingThread(Thread):
+    def __init__(self, source_fd: int, target_fd: int) -> None:
+        super().__init__(daemon=True, name=f"FD-splice-{source_fd}->{target_fd}")
+        self.source_fd = source_fd
+        self.dest_fd = target_fd
+
+    def run(self) -> None:
+        if sys.version_info.major >= 3 and sys.version_info.minor >= 10:
+            while True:
+                os.splice(self.source_fd, self.dest_fd, 1024)  # type: ignore[attr-defined]
+        else:
+            while True:
+                buf = os.read(self.source_fd, 1024)
+                os.write(self.dest_fd, buf)
+
+
+def make_rpcinterface(_supervisord: Supervisor, **_config: Any) -> Any:  # pylint: disable=useless-return
+    # create pipe
+    (r, w) = os.pipe()
+    os.dup2(w, NEW_STDOUT_FD)
+    os.close(w)
+
+    # start splicing
+    t = SplicingThread(r, sys.stdout.fileno())
+    t.start()
+
+    # this method is called by supervisord when loading the plugin,
+    # it should return XML-RPC object, which we don't care about
+    # That's why why are returning just None
+    return None
index c77913eaa534c85e100b2e0026282abd44b854ee..1171dd1c99442e55633992ee39309e7ce8ca2a6b 100644 (file)
@@ -28,10 +28,13 @@ supervisor.rpcinterface_factory = knot_resolver_manager.kresd_controller.supervi
 [rpcinterface:notify]
 supervisor.rpcinterface_factory = knot_resolver_manager.kresd_controller.supervisord.plugin:make_rpcinterface
 
+[rpcinterface:stdout_pipe]
+supervisor.rpcinterface_factory = knot_resolver_manager.kresd_controller.supervisord.plugin.stdout_pipe_log:make_rpcinterface
+
 
 [program:manager]
 redirect_stderr=false
-stdout_logfile=/proc/self/fd/1
+stdout_logfile=/proc/self/fd/42  # handled by stdout_pipe plugin
 stdout_logfile_maxbytes=0
 directory={{ manager.workdir }}
 command={{ manager.command }}