]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
manager: improved logging, no exceptions on shutdown -> now it looks almost as before...
authorVasek Sraier <git@vakabus.cz>
Thu, 7 Jul 2022 14:21:46 +0000 (16:21 +0200)
committerAleš Mrázek <ales.mrazek@nic.cz>
Fri, 8 Jul 2022 13:04:38 +0000 (13:04 +0000)
manager/knot_resolver_manager/constants.py
manager/knot_resolver_manager/datamodel/config_schema.py
manager/knot_resolver_manager/exceptions.py
manager/knot_resolver_manager/kresd_controller/supervisord/__init__.py
manager/knot_resolver_manager/kresd_controller/supervisord/config_file.py
manager/knot_resolver_manager/kresd_controller/supervisord/supervisord.conf.j2
manager/knot_resolver_manager/server.py
manager/knot_resolver_manager/utils/custom_atexit.py [new file with mode: 0644]

index 4c2194c93f765b0fa9d2e45e1791fece752eb2f1..8b4b775c4ce81d119071dc72eec0df87da7c69b7 100644 (file)
@@ -3,7 +3,6 @@ from pathlib import Path
 from typing import TYPE_CHECKING, Optional
 
 from knot_resolver_manager.utils import which
-from knot_resolver_manager.utils.functional import Result
 
 if TYPE_CHECKING:
     from knot_resolver_manager.config_store import ConfigStore
index 75d733b14844d19e18beee3c9ddb2cfe44ccae45..6fcc0888b91228f8b52654d9363a4d954ea2e015 100644 (file)
@@ -23,7 +23,7 @@ from knot_resolver_manager.datamodel.rpz_schema import RPZSchema
 from knot_resolver_manager.datamodel.slice_schema import SliceSchema
 from knot_resolver_manager.datamodel.static_hints_schema import StaticHintsSchema
 from knot_resolver_manager.datamodel.stub_zone_schema import StubZoneSchema
-from knot_resolver_manager.datamodel.types.types import IDPattern, IntPositive, UncheckedPath
+from knot_resolver_manager.datamodel.types.types import IntPositive, UncheckedPath
 from knot_resolver_manager.datamodel.view_schema import ViewSchema
 from knot_resolver_manager.datamodel.webmgmt_schema import WebmgmtSchema
 from knot_resolver_manager.exceptions import DataException
index 0509038318039a3db176910c97f9352dcb5a9119..9dbaeb863ed9fc9255b1d12127371a9ed2dc0036 100644 (file)
@@ -8,6 +8,7 @@ class CancelStartupExecInsteadException(Exception):
     controllers such as supervisord to allow them to run as top-level
     process in a process tree.
     """
+
     def __init__(self, exec_args: List[str], *args: object) -> None:
         self.exec_args = exec_args
         super().__init__(*args)
index dcc4d0299efcee7fc01b63ecd098f40ff5d6cb76..49e53a631220e473d255b0a6c9ea83e045a1465f 100644 (file)
@@ -1,7 +1,6 @@
 import logging
-from os import execl, kill
+from os import kill
 from pathlib import Path
-from time import sleep
 from typing import Any, Dict, Iterable, NoReturn, Optional, Union, cast
 from xmlrpc.client import Fault, ServerProxy
 
@@ -38,28 +37,46 @@ async def _exec_supervisord(config: KresConfig) -> NoReturn:
     logger.debug("Writing supervisord config")
     await write_config_file(config)
     logger.debug("Execing supervisord")
-    raise CancelStartupExecInsteadException([str(which.which("supervisord")), "supervisord", "--configuration", str(supervisord_config_file(config).absolute()), "-n"])
+    raise CancelStartupExecInsteadException(
+        [
+            str(which.which("supervisord")),
+            "supervisord",
+            "--configuration",
+            str(supervisord_config_file(config).absolute()),
+        ]
+    )
 
 
 async def _reload_supervisord(config: KresConfig) -> None:
     await write_config_file(config)
-    res = await call(f'supervisorctl --configuration="{supervisord_config_file(config).absolute()}" update', shell=True)
-    if res != 0:
-        raise SubprocessControllerException(f"Supervisord reload failed with exit code {res}")
+    try:
+        supervisord = _create_supervisord_proxy(config)
+        supervisord.reloadConfig()
+    except Fault as e:
+        raise SubprocessControllerException("supervisord reload failed") from e
 
 
 @async_in_a_thread
 def _stop_supervisord(config: KresConfig) -> None:
     supervisord = _create_supervisord_proxy(config)
-    pid = supervisord.getPID()
-    supervisord.shutdown()
+    # pid = supervisord.getPID()
     try:
-        while True:
-            kill(pid, 0)
-            sleep(0.1)
-    except ProcessLookupError:
-        pass  # there is finally no supervisord process
-    supervisord_config_file(config).unlink()
+        # we might be trying to shut down supervisord at a moment, when it's waiting
+        # for us to stop. Therefore, this shutdown request for supervisord might
+        # die and it's not a problem.
+        supervisord.shutdown()
+    except Fault as e:
+        if e.faultCode == 6 and e.faultString == "SHUTDOWN_STATE":
+            # supervisord is already stopping, so it's fine
+            pass
+        else:
+            # something wrong happened, let's be loud about it
+            raise
+
+    # We could remove the configuration, but there is actually no specific need to do so.
+    # If we leave it behind, someone might find it and use it to start us from scratch again,
+    # which is perfectly fine.
+    # supervisord_config_file(config).unlink()
 
 
 async def _is_supervisord_available() -> bool:
@@ -227,9 +244,12 @@ class SupervisordSubprocessController(SubprocessController):
         self._controller_config = config
 
         if not await _is_supervisord_running(config):
-            #await _start_supervisord(config)
+            logger.info(
+                "We want supervisord to restart us when needed, we will therefore exec() it and let it start us again."
+            )
             await _exec_supervisord(config)
         else:
+            logger.info("Supervisord is already running, we will just update its config...")
             await _reload_supervisord(config)
 
     async def shutdown_controller(self) -> None:
index 346ebf5a94eea3ca76915cb505c55cdb8941dc42..bbce2bc1175e34862496eea7993a0aff73610f2d 100644 (file)
@@ -77,17 +77,17 @@ class ProcessTypeConfig:
         )
 
     @staticmethod
-    def create_manager_config(config: KresConfig) -> "ProcessTypeConfig":
+    def create_manager_config(_config: KresConfig) -> "ProcessTypeConfig":
         # read original command from /proc
-        with open("/proc/self/cmdline", 'rb') as f:
-            args = [s.decode('utf-8') for s in f.read()[:-1].split(b'\0')]
+        with open("/proc/self/cmdline", "rb") as f:
+            args = [s.decode("utf-8") for s in f.read()[:-1].split(b"\0")]
         cmd = '"' + '" "'.join(args) + '"'
 
         return ProcessTypeConfig(  # type: ignore[call-arg]
             workdir=user_constants().working_directory_on_startup,
             command=cmd,
-            environment='X-SUPERVISORD-TYPE=notify',
-            logfile=""  # this will be ignored
+            environment="X-SUPERVISORD-TYPE=notify",
+            logfile="",  # this will be ignored
         )
 
 
index c78ecbed2cbb6ba6f0fb117d70671f39eab097b2..10ffcbd81fa29fcff2f04377833e223ada76139c 100644 (file)
@@ -1,10 +1,11 @@
 [supervisord]
 pidfile = {{ config.pid_file }}
 directory = {{ config.workdir }}
-nodaemon = false
+nodaemon = true
 logfile = {{ config.logfile }}
 logfile_maxbytes = 50MB
 loglevel = info
+silent=true
 {# user=root #}
 
 [unix_http_server]
index bbca9e183895b1059877093227487ec8ed663552..f26763f73e186d2f1acb155169f2939ea7d2bb78 100644 (file)
@@ -1,5 +1,4 @@
 import asyncio
-import atexit
 import errno
 import logging
 import os
@@ -16,19 +15,25 @@ from aiohttp.web_app import Application
 from aiohttp.web_response import json_response
 from aiohttp.web_runner import AppRunner, TCPSite, UnixSite
 
+import knot_resolver_manager.utils.custom_atexit as atexit
 from knot_resolver_manager import log, statistics
 from knot_resolver_manager.compat import asyncio as asyncio_compat
 from knot_resolver_manager.config_store import ConfigStore
 from knot_resolver_manager.constants import DEFAULT_MANAGER_CONFIG_FILE, PID_FILE_NAME, init_user_constants
 from knot_resolver_manager.datamodel.config_schema import KresConfig
 from knot_resolver_manager.datamodel.management_schema import ManagementSchema
-from knot_resolver_manager.exceptions import CancelStartupExecInsteadException, DataException, KresManagerException, SchemaException
+from knot_resolver_manager.exceptions import (
+    CancelStartupExecInsteadException,
+    DataException,
+    KresManagerException,
+    SchemaException,
+)
 from knot_resolver_manager.kresd_controller import get_best_controller_implementation
 from knot_resolver_manager.utils.async_utils import readfile
 from knot_resolver_manager.utils.functional import Result
 from knot_resolver_manager.utils.parsing import ParsedTree, parse, parse_yaml
-from knot_resolver_manager.utils.types import NoneType
 from knot_resolver_manager.utils.systemd_notify import systemd_notify
+from knot_resolver_manager.utils.types import NoneType
 
 from .kres_manager import KresManager
 
@@ -391,6 +396,9 @@ async def _sigterm_while_shutting_down():
 
 
 async def start_server(config: Union[Path, ParsedTree] = DEFAULT_MANAGER_CONFIG_FILE) -> int:
+    # This function is quite long, but it describes how manager runs. So let's silence pylint
+    # pylint: disable=too-many-statements
+
     start_time = time()
     working_directory_on_startup = os.getcwd()
     manager: Optional[KresManager] = None
@@ -456,9 +464,9 @@ async def start_server(config: Union[Path, ParsedTree] = DEFAULT_MANAGER_CONFIG_
         signal.pthread_sigmask(signal.SIG_UNBLOCK, Server.all_handled_signals())
 
         # run exit functions
-        atexit._run_exitfuncs()
+        atexit.run_callbacks()
 
-        # and finally exec what we we told to exec
+        # and finally exec what we were told to exec
         os.execl(*e.exec_args)
 
     except KresManagerException as e:
diff --git a/manager/knot_resolver_manager/utils/custom_atexit.py b/manager/knot_resolver_manager/utils/custom_atexit.py
new file mode 100644 (file)
index 0000000..2fe5543
--- /dev/null
@@ -0,0 +1,20 @@
+"""
+Custom replacement for standard module `atexit`. We use `atexit` behind the scenes, we just add the option
+to invoke the exit functions manually.
+"""
+
+import atexit
+from typing import Callable, List
+
+_at_exit_functions: List[Callable[[], None]] = []
+
+
+def register(func: Callable[[], None]) -> None:
+    _at_exit_functions.append(func)
+    atexit.register(func)
+
+
+def run_callbacks() -> None:
+    for func in _at_exit_functions:
+        func()
+        atexit.unregister(func)