Skip to content

Commit

Permalink
manager: files watchdog: watch parent directory for changes
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
alesmrazek committed Nov 29, 2024
1 parent d4d13ef commit 343ec9d
Showing 1 changed file with 62 additions and 67 deletions.
129 changes: 62 additions & 67 deletions python/knot_resolver/manager/files/watchdog.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -29,86 +27,83 @@ 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

_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()
Expand All @@ -124,11 +119,11 @@ async def _init_tls_cert_watchdog(config: KresConfig) -> None:
_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()


Expand Down

0 comments on commit 343ec9d

Please sign in to comment.