Skip to content

Commit 3db9d05

Browse files
committed
manager: files: watchdog: added RPZ files
Separate timer for each command.
1 parent 03f29dc commit 3db9d05

File tree

3 files changed

+167
-19
lines changed

3 files changed

+167
-19
lines changed

python/knot_resolver/manager/files/watchdog.py

+40-18
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
logger = logging.getLogger(__name__)
1313

14+
FilesToWatch = Dict[Path, Optional[str]]
15+
1416

1517
def tls_cert_files_config(config: KresConfig) -> List[Any]:
1618
return [
@@ -20,9 +22,6 @@ def tls_cert_files_config(config: KresConfig) -> List[Any]:
2022
]
2123

2224

23-
FilesToWatch = Dict[Path, str]
24-
25-
2625
if WATCHDOG_LIB:
2726
from watchdog.events import (
2827
FileSystemEvent,
@@ -33,48 +32,65 @@ def tls_cert_files_config(config: KresConfig) -> List[Any]:
3332
class FilesWatchdogEventHandler(FileSystemEventHandler):
3433
def __init__(self, files: FilesToWatch) -> None:
3534
self._files = files
36-
self._timer: Optional[Timer] = None
35+
self._policy_timer: Optional[Timer] = None
36+
self._timers: Dict[str, Timer] = {}
37+
38+
def _trigger(self, cmd: Optional[str]) -> None:
39+
def policy_reload() -> None:
40+
logger.info("Policy rules reloaded")
41+
42+
if not cmd:
43+
# skipping if reload was already triggered
44+
if self._policy_timer and self._policy_timer.is_alive():
45+
logger.info("Skipping reloading policy rules, it was already triggered")
46+
return
47+
# start a 5sec timer
48+
logger.info("Delayed policy rules reload has started")
49+
self._policy_timer = Timer(5, policy_reload)
50+
self._policy_timer.start()
51+
return
3752

38-
def _reload(self, cmd: str) -> None:
3953
def command() -> None:
4054
if compat.asyncio.is_event_loop_running():
4155
compat.asyncio.create_task(command_registered_workers(cmd))
4256
else:
4357
compat.asyncio.run(command_registered_workers(cmd))
44-
logger.info("Reloading of TLS certificate files has finished")
58+
logger.info(f"Sending '{cmd}' command to reload watched files has finished")
4559

46-
# skipping if reload was already triggered
47-
if self._timer and self._timer.is_alive():
48-
logger.info("Skipping TLS certificate files reloading, reload command was already triggered")
60+
# skipping if command was already triggered
61+
if cmd in self._timers and self._timers[cmd].is_alive():
62+
logger.info(f"Skipping sending '{cmd}' command, it was already triggered")
4963
return
5064
# start a 5sec timer
51-
logger.info("Delayed reload of TLS certificate files has started")
52-
self._timer = Timer(5, command)
53-
self._timer.start()
65+
logger.info(f"Delayed send of '{cmd}' command has started")
66+
self._timers[cmd] = Timer(5, command)
67+
self._timers[cmd].start()
5468

5569
def on_created(self, event: FileSystemEvent) -> None:
5670
src_path = Path(str(event.src_path))
5771
if src_path in self._files.keys():
5872
logger.info(f"Watched file '{src_path}' has been created")
59-
self._reload(self._files[src_path])
73+
self._trigger(self._files[src_path])
6074

6175
def on_deleted(self, event: FileSystemEvent) -> None:
6276
src_path = Path(str(event.src_path))
6377
if src_path in self._files.keys():
6478
logger.warning(f"Watched file '{src_path}' has been deleted")
65-
if self._timer:
66-
self._timer.cancel()
79+
cmd = self._files[src_path]
80+
if cmd in self._timers:
81+
self._timers[cmd].cancel()
6782
for file in self._files.keys():
6883
if file.parent == src_path:
6984
logger.warning(f"Watched directory '{src_path}' has been deleted")
70-
if self._timer:
71-
self._timer.cancel()
85+
cmd = self._files[file]
86+
if cmd in self._timers:
87+
self._timers[cmd].cancel()
7288

7389
def on_modified(self, event: FileSystemEvent) -> None:
7490
src_path = Path(str(event.src_path))
7591
if src_path in self._files.keys():
7692
logger.info(f"Watched file '{src_path}' has been modified")
77-
self._reload(self._files[src_path])
93+
self._trigger(self._files[src_path])
7894

7995
_files_watchdog: Optional["FilesWatchdog"] = None
8096

@@ -119,6 +135,12 @@ async def _init_files_watchdog(config: KresConfig) -> None:
119135
files_to_watch[config.network.tls.cert_file.to_path()] = net_tls
120136
files_to_watch[config.network.tls.key_file.to_path()] = net_tls
121137

138+
# local-data.rpz
139+
if config.local_data.rpz:
140+
for rpz in config.local_data.rpz:
141+
if rpz.watchdog:
142+
files_to_watch[rpz.file.to_path()] = None
143+
122144
if files_to_watch:
123145
logger.info("Initializing files watchdog")
124146
_files_watchdog = FilesWatchdog(files_to_watch)
+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#!/usr/bin/env bash
2+
3+
set -e
4+
5+
gitroot=$(git rev-parse --show-toplevel)
6+
rpz_file=$gitroot/example.rpz
7+
rpz_conf="[{ 'file': '$rpz_file' }]"
8+
9+
rpz_conf=$(cat <<EOF
10+
{ "rpz": [
11+
{ "file": "$rpz_file" }
12+
]}
13+
EOF
14+
)
15+
16+
rpz_example=$(cat <<EOF
17+
\$ORIGIN RPZ.EXAMPLE.ORG.
18+
ok.example.com CNAME rpz-passthru.
19+
EOF
20+
)
21+
22+
# create example RPZ
23+
echo "$rpz_example" >> $rpz_file
24+
25+
# configure RPZ file
26+
kresctl config set -p /local-data "$rpz_conf"
27+
if [ "$?" -ne "0" ]; then
28+
echo "Could not set RPZ."
29+
exit 1
30+
fi
31+
32+
function count_errors(){
33+
echo "$(journalctl -u knot-resolver.service | grep -c error)"
34+
}
35+
36+
function count_reloads(){
37+
echo "$(journalctl -u knot-resolver.service | grep -c "Policy rules reloaded")"
38+
}
39+
40+
# test that files watchdog is turned off
41+
# {{
42+
43+
err_count=$(count_errors)
44+
rel_count=$(count_reloads)
45+
sleep 6
46+
47+
if [ $(count_errors) -ne $err_count ] || [ $(count_reloads) -ne $rel_count ]; then
48+
echo "RPZ file watchdog is running (should not) or other errors occurred."
49+
exit 1
50+
fi
51+
52+
# }}
53+
54+
# configure RPZ file and turn on watchdog
55+
kresctl config set -p /local-data/rpz/0/watchdog true
56+
if [ "$?" -ne "0" ]; then
57+
echo "Could not turn on RPZ file watchdog."
58+
exit 1
59+
fi
60+
61+
# test modification
62+
# {{
63+
64+
rel_count=$(count_reloads)
65+
66+
# modify certificate files with '-', it will trigger reload
67+
rel_count=$(count_reloads)
68+
echo "32.1.2.0.192.rpz-client-ip CNAME rpz-passthru." >> $rpz_file
69+
70+
# wait for files reload to finish
71+
sleep 10
72+
73+
if [ $(count_errors) -ne $err_count ] || [ $(count_reloads) -eq $rel_count ]; then
74+
echo "Could not reload modified RPZ file."
75+
exit 1
76+
fi
77+
78+
# }}
79+
80+
# test replacement
81+
# {{
82+
83+
rel_count=$(count_reloads)
84+
85+
# copy RPZ file
86+
cp $rpz_file $rpz_file.new
87+
88+
# edit new files
89+
echo "48.zz.101.db8.2001.rpz-client-ip CNAME rpz-passthru." >> $rpz_file.new
90+
91+
# replace files
92+
mv -f $rpz_file.new $rpz_file
93+
94+
# wait for files reload to finish
95+
sleep 10
96+
97+
if [ $(count_errors) -ne $err_count ] || [ $(count_reloads) -eq $rel_count ]; then
98+
echo "Could not reload replaced RPZ file."
99+
exit 1
100+
fi
101+
102+
# }}
103+
104+
# test recovery from deletion and creation
105+
# {{
106+
107+
rel_count=$(count_reloads)
108+
109+
# backup rpz file
110+
cp $rpz_file $rpz_file.backup
111+
112+
# delete RPZ file
113+
rm $rpz_file
114+
115+
# create cert files
116+
mv $rpz_file.backup $rpz_file
117+
118+
# wait for files reload to finish
119+
sleep 10
120+
121+
if [ $(count_errors) -ne $err_count ] || [ $(count_reloads) -eq $rel_count ]; then
122+
echo "Could not reload created RPZ file."
123+
exit 1
124+
fi
125+
126+
# }}

tests/packaging/interactive/watchdog.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ function count_errors(){
2626
}
2727

2828
function count_reloads(){
29-
echo "$(journalctl -u knot-resolver.service | grep -c "Reloading of TLS certificate files has finished")"
29+
echo "$(journalctl -u knot-resolver.service | grep -c "to reload watched files has finished")"
3030
}
3131

3232
# test that files watchdog is turned off

0 commit comments

Comments
 (0)