]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
manager: files watchdog: watch parent directory for changes
authorAleš Mrázek <ales.mrazek@nic.cz>
Thu, 28 Nov 2024 16:44:58 +0000 (17:44 +0100)
committerVladimír Čunát <vladimir.cunat@nic.cz>
Tue, 3 Dec 2024 07:53:33 +0000 (08:53 +0100)
Watching the whole parent directory is better because we can see file deletion and creation and there is no need to reinitiate the watchdog when a file is replaced.

python/knot_resolver/manager/files/watchdog.py

index d365542c7697b9faf4e494b5b0e09bc680453e56..64547192e1d60460c54577b0cc94c449e6e5d49a 100644 (file)
@@ -1,10 +1,8 @@
 import importlib
 import logging
-import os
-import time
 from pathlib import Path
 from threading import Timer
-from typing import List, Optional, Union
+from typing import List, Optional
 
 from knot_resolver.controller.registered_workers import command_registered_workers
 from knot_resolver.datamodel import KresConfig
@@ -29,10 +27,7 @@ def tls_cert_paths(config: KresConfig) -> List[str]:
 
 if _watchdog:
     from watchdog.events import (
-        DirDeletedEvent,
-        DirModifiedEvent,
-        FileDeletedEvent,
-        FileModifiedEvent,
+        FileSystemEvent,
         FileSystemEventHandler,
     )
     from watchdog.observers import Observer
@@ -40,75 +35,75 @@ if _watchdog:
     _tls_cert_watchdog: Optional["TLSCertWatchDog"] = None
 
     class TLSCertEventHandler(FileSystemEventHandler):
-        def __init__(self, cmd: str, delay: int = 5) -> None:
-            self._delay = delay
-            self._timer: Optional[Timer] = None
+        def __init__(self, files: List[Path], cmd: str) -> None:
+            self._files = files
             self._cmd = cmd
+            self._timer: Optional[Timer] = None
 
-        def _reload_cmd(self) -> None:
-            logger.info("Reloading TLS certificate files for the all workers")
-            if compat.asyncio.is_event_loop_running():
-                compat.asyncio.create_task(command_registered_workers(self._cmd))
-            else:
-                compat.asyncio.run(command_registered_workers(self._cmd))
-
-        def on_deleted(self, event: Union[DirDeletedEvent, FileDeletedEvent]) -> None:
-            path = str(event.src_path)
-            logger.info(f"Stopped watching '{path}', because it was deleted")
-
-            # do not send command when the file was deleted
-            if self._timer and self._timer.is_alive():
-                self._timer.cancel()
-                self._timer.join()
-
-            if _tls_cert_watchdog:
-                _tls_cert_watchdog.reschedule()
-
-        def on_modified(self, event: Union[DirModifiedEvent, FileModifiedEvent]) -> None:
-            path = str(event.src_path)
-            logger.info(f"TLS certificate file '{path}' has been modified")
+        def _reload(self) -> None:
+            def command() -> None:
+                if compat.asyncio.is_event_loop_running():
+                    compat.asyncio.create_task(command_registered_workers(self._cmd))
+                else:
+                    compat.asyncio.run(command_registered_workers(self._cmd))
+                logger.info("Reloading of TLS certificate files has finished")
 
-            # skipping if command was already triggered
+            # skipping if reload was already triggered
             if self._timer and self._timer.is_alive():
-                logger.info(f"Skipping '{path}', reload file already triggered")
+                logger.info("Skipping TLS certificate files reloading, reload command was already triggered")
                 return
-            # start a new timer
-            self._timer = Timer(self._delay, self._reload_cmd)
-            self._timer.start()
+            # start a 5sec timer
             logger.info("Delayed reload of TLS certificate files has started")
+            self._timer = Timer(5, command)
+            self._timer.start()
+
+        def on_created(self, event: FileSystemEvent) -> None:
+            src_path = Path(str(event.src_path))
+            if src_path in self._files:
+                logger.info(f"Watched file '{src_path}' has been created")
+                self._reload()
+
+        def on_deleted(self, event: FileSystemEvent) -> None:
+            src_path = Path(str(event.src_path))
+            if src_path in self._files:
+                logger.warning(f"Watched file '{src_path}' has been deleted")
+                if self._timer:
+                    self._timer.cancel()
+            for file in self._files:
+                if file.parent == src_path:
+                    logger.warning(f"Watched directory '{src_path}' has been deleted")
+                    if self._timer:
+                        self._timer.cancel()
+
+        def on_modified(self, event: FileSystemEvent) -> None:
+            src_path = Path(str(event.src_path))
+            if src_path in self._files:
+                logger.info(f"Watched file '{src_path}' has been modified")
+                self._reload()
 
     class TLSCertWatchDog:
         def __init__(self, cert_file: Path, key_file: Path) -> None:
             self._observer = Observer()
-            self._cert_file = cert_file
-            self._key_file = key_file
-            self._cmd = f"net.tls('{cert_file}', '{key_file}')"
-
-        def schedule(self) -> None:
-            event_handler = TLSCertEventHandler(self._cmd)
-            logger.info("Schedule watching of TLS certificate files")
-            self._observer.schedule(
-                event_handler,
-                str(self._cert_file),
-                recursive=False,
-            )
-            self._observer.schedule(
-                event_handler,
-                str(self._key_file),
-                recursive=False,
-            )
 
-        def reschedule(self) -> None:
-            self._observer.unschedule_all()
+            cmd = f"net.tls('{cert_file}', '{key_file}')"
+
+            cert_files: List[Path] = []
+            cert_files.append(cert_file)
+            cert_files.append(key_file)
+
+            cert_dirs: List[Path] = []
+            cert_dirs.append(cert_file.parent)
+            if cert_file.parent != key_file.parent:
+                cert_dirs.append(key_file.parent)
 
-            # wait for files creation
-            while not (os.path.exists(self._cert_file) and os.path.exists(self._key_file)):
-                if os.path.exists(self._cert_file):
-                    logger.error(f"Cannot start watching TLS cert file, '{self._cert_file}' is missing.")
-                if os.path.exists(self._key_file):
-                    logger.error(f"Cannot start watching TLS cert key file, '{self._key_file}' is missing.")
-                time.sleep(1)
-            self.schedule()
+            event_handler = TLSCertEventHandler(cert_files, cmd)
+            for d in cert_dirs:
+                self._observer.schedule(
+                    event_handler,
+                    str(d),
+                    recursive=False,
+                )
+                logger.info(f"Directory '{d}' scheduled for watching")
 
         def start(self) -> None:
             self._observer.start()
@@ -124,11 +119,11 @@ if _watchdog:
             _tls_cert_watchdog.stop()
 
         if config.network.tls.cert_file and config.network.tls.key_file:
-            logger.info("Starting TLS certificate files WatchDog")
+            logger.info("Initializing TLS certificate files WatchDog")
             _tls_cert_watchdog = TLSCertWatchDog(
-                config.network.tls.cert_file.to_path(), config.network.tls.key_file.to_path()
+                config.network.tls.cert_file.to_path(),
+                config.network.tls.key_file.to_path(),
             )
-            _tls_cert_watchdog.schedule()
             _tls_cert_watchdog.start()