From d90a677af424f433098c7ed107aa0fadc0b9666e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Mr=C3=A1zek?= Date: Mon, 25 Nov 2024 12:51:38 +0100 Subject: [PATCH] manager: files watchdog: watchdog created specifically for TLS certificate --- .../knot_resolver/manager/files/watchdog.py | 102 +++++++++++------- 1 file changed, 66 insertions(+), 36 deletions(-) diff --git a/python/knot_resolver/manager/files/watchdog.py b/python/knot_resolver/manager/files/watchdog.py index 9cb644a68..91c5430c0 100644 --- a/python/knot_resolver/manager/files/watchdog.py +++ b/python/knot_resolver/manager/files/watchdog.py @@ -1,7 +1,7 @@ import importlib import logging -from pathlib import Path -from typing import List, Optional, Union +from threading import Timer +from typing import Dict, List, Optional, Tuple, Type, Union from knot_resolver.controller.registered_workers import command_registered_workers from knot_resolver.datamodel import KresConfig @@ -16,54 +16,82 @@ 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): - - def __init__(self, config: KresConfig) -> None: + class TLSCertEventHandler(FileSystemEventHandler): + def __init__(self, config: KresConfig, delay: int) -> None: self._config = config - self._command = f"net.tls('{config.network.tls.cert_file}', '{config.network.tls.key_file}')" - - # def on_any_event(self, event: FileSystemEvent) -> None: - # pass + self._delay = delay + self._cmd = f"net.tls('{config.network.tls.cert_file}', '{config.network.tls.key_file}')" + self._cmd_triggered: bool = False - # def on_created(self, event: Union[DirCreatedEvent, FileCreatedEvent]) -> None: - # pass - - # def on_deleted(self, event: Union[DirDeletedEvent, FileDeletedEvent]) -> None: - # pass - - def on_modified(self, event: Union[DirModifiedEvent, FileModifiedEvent]) -> None: + def trigger_cmd(self) -> None: + logger.info("Commanding workers to reload TLS certificate") if compat.asyncio.is_event_loop_running(): - compat.asyncio.create_task(command_registered_workers(self._command)) + compat.asyncio.create_task(command_registered_workers(self._cmd)) else: - compat.asyncio.run(command_registered_workers(self._command)) + compat.asyncio.run(command_registered_workers(self._cmd)) + self._cmd_triggered = False - # def on_closed(self, event: FileClosedEvent) -> None: - # pass + def on_deleted(self, event: Union[DirDeletedEvent, FileDeletedEvent]) -> None: + pass + # path = str(event.src_path) + # logger.info(f"Stopped watching '{path}', because it was replaced or deleted") + # if _tls_cert_watchdog: + # logger.info(f"Trying to reschedule watching '{path}'") + # _tls_cert_watchdog.reschedule(path) - class FilesWatchDog: - def __init__(self, config: KresConfig, files: List[Path]) -> None: + def on_modified(self, event: Union[DirModifiedEvent, FileModifiedEvent]) -> None: + # skipping if command was already triggered + if not self._cmd_triggered: + logger.info(f"Reload TLS certificate will start in {self._delay} seconds") + timer = Timer(self._delay, self.trigger_cmd) + self._cmd_triggered = True + timer.start() + + class TLSCertWatchDog: + def __init__(self, config: KresConfig) -> None: self._observer = Observer() - for file in files: - self._observer.schedule(CertificatesEventHandler(config), str(file), recursive=False) - logger.info(f"Watching '{file}. file") + self._config = config + + def schedule(self) -> None: + event_handler = TLSCertEventHandler(self._config, delay=5) + + if self._observer and self._config.network.tls.cert_file: + self._observer.schedule( + event_handler, + str(self._config.network.tls.cert_file), + event_filter=[FileModifiedEvent, DirDeletedEvent], + ) + if self._observer and self._config.network.tls.key_file: + self._observer.schedule( + event_handler, + str(self._config.network.tls.key_file), + event_filter=[FileModifiedEvent, DirDeletedEvent], + ) + + def reschedule(self, path: str) -> None: + if self._observer: + self._observer.unschedule_all() + self.schedule() def start(self) -> None: if self._observer: @@ -74,15 +102,17 @@ def stop(self) -> None: self._observer.stop() self._observer.join() - @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() + @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 is None: + logger.info("Starting TLS certificate WatchDog") + _tls_cert_watchdog = TLSCertWatchDog(config) + _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)