From: Aleš Mrázek Date: Mon, 25 Nov 2024 11:51:38 +0000 (+0100) Subject: manager: files watchdog: watchdog created specifically for TLS certificate files X-Git-Tag: v6.0.10~10^2~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7afb10f703a47b81d1b5b163dcbe622e0817cd28;p=thirdparty%2Fknot-resolver.git manager: files watchdog: watchdog created specifically for TLS certificate files - on_modified: the command is delayed to avoid sending too many - on_deleted: files watching is stopped rescheduled (replaced file) --- diff --git a/python/knot_resolver/manager/files/watchdog.py b/python/knot_resolver/manager/files/watchdog.py index 9cb644a68..d365542c7 100644 --- a/python/knot_resolver/manager/files/watchdog.py +++ b/python/knot_resolver/manager/files/watchdog.py @@ -1,6 +1,9 @@ import importlib import logging +import os +import time from pathlib import Path +from threading import Timer from typing import List, Optional, Union from knot_resolver.controller.registered_workers import command_registered_workers @@ -16,73 +19,120 @@ if importlib.util.find_spec("watchdog"): logger = logging.getLogger(__name__) -def files_to_watch(config: KresConfig) -> List[Path]: +def tls_cert_paths(config: KresConfig) -> List[str]: files: List[Optional[File]] = [ config.network.tls.cert_file, config.network.tls.key_file, ] - return [file.to_path() for file in files if file is not None] + return [str(file) for file in files if file is not None] if _watchdog: from watchdog.events import ( + DirDeletedEvent, DirModifiedEvent, + FileDeletedEvent, FileModifiedEvent, FileSystemEventHandler, ) from watchdog.observers import Observer - _files_watchdog: Optional["FilesWatchDog"] = None + _tls_cert_watchdog: Optional["TLSCertWatchDog"] = None - class CertificatesEventHandler(FileSystemEventHandler): + class TLSCertEventHandler(FileSystemEventHandler): + def __init__(self, cmd: str, delay: int = 5) -> None: + self._delay = delay + self._timer: Optional[Timer] = None + self._cmd = cmd - def __init__(self, config: KresConfig) -> None: - self._config = config - self._command = f"net.tls('{config.network.tls.cert_file}', '{config.network.tls.key_file}')" + 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_any_event(self, event: FileSystemEvent) -> None: - # pass + def on_deleted(self, event: Union[DirDeletedEvent, FileDeletedEvent]) -> None: + path = str(event.src_path) + logger.info(f"Stopped watching '{path}', because it was deleted") - # def on_created(self, event: Union[DirCreatedEvent, FileCreatedEvent]) -> None: - # pass + # do not send command when the file was deleted + if self._timer and self._timer.is_alive(): + self._timer.cancel() + self._timer.join() - # def on_deleted(self, event: Union[DirDeletedEvent, FileDeletedEvent]) -> None: - # pass + if _tls_cert_watchdog: + _tls_cert_watchdog.reschedule() def on_modified(self, event: Union[DirModifiedEvent, FileModifiedEvent]) -> None: - if compat.asyncio.is_event_loop_running(): - compat.asyncio.create_task(command_registered_workers(self._command)) - else: - compat.asyncio.run(command_registered_workers(self._command)) - - # def on_closed(self, event: FileClosedEvent) -> None: - # pass - - class FilesWatchDog: - def __init__(self, config: KresConfig, files: List[Path]) -> None: + path = str(event.src_path) + logger.info(f"TLS certificate file '{path}' has been modified") + + # skipping if command was already triggered + if self._timer and self._timer.is_alive(): + logger.info(f"Skipping '{path}', reload file already triggered") + return + # start a new timer + self._timer = Timer(self._delay, self._reload_cmd) + self._timer.start() + logger.info("Delayed reload of TLS certificate files has started") + + class TLSCertWatchDog: + def __init__(self, cert_file: Path, key_file: Path) -> None: self._observer = Observer() - for file in files: - self._observer.schedule(CertificatesEventHandler(config), str(file), recursive=False) - logger.info(f"Watching '{file}. file") + 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() + + # 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() def start(self) -> None: - if self._observer: - self._observer.start() + self._observer.start() def stop(self) -> None: - if self._observer: - self._observer.stop() - self._observer.join() + self._observer.stop() + self._observer.join() + + @only_on_real_changes_update(tls_cert_paths) + async def _init_tls_cert_watchdog(config: KresConfig) -> None: + global _tls_cert_watchdog + if _tls_cert_watchdog: + _tls_cert_watchdog.stop() - @only_on_real_changes_update(files_to_watch) - async def _init_files_watchdog(config: KresConfig) -> None: - global _files_watchdog - if _files_watchdog is None: - logger.info("Starting files WatchDog") - _files_watchdog = FilesWatchDog(config, files_to_watch(config)) - _files_watchdog.start() + if config.network.tls.cert_file and config.network.tls.key_file: + logger.info("Starting TLS certificate files WatchDog") + _tls_cert_watchdog = TLSCertWatchDog( + config.network.tls.cert_file.to_path(), config.network.tls.key_file.to_path() + ) + _tls_cert_watchdog.schedule() + _tls_cert_watchdog.start() async def init_files_watchdog(config_store: ConfigStore) -> None: if _watchdog: - await config_store.register_on_change_callback(_init_files_watchdog) + # watchdog for TLS certificate files + await config_store.register_on_change_callback(_init_tls_cert_watchdog)