Skip to content

Commit 366a4a7

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

File tree

3 files changed

+184
-21
lines changed

3 files changed

+184
-21
lines changed

python/knot_resolver/manager/files/watchdog.py

+59-20
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,20 @@
1111

1212
logger = logging.getLogger(__name__)
1313

14+
FilesToWatch = Dict[Path, Optional[str]]
1415

15-
def tls_cert_files_config(config: KresConfig) -> List[Any]:
16+
_config_store: Optional[ConfigStore] = None
17+
18+
19+
def watched_files_config(config: KresConfig) -> List[Any]:
1620
return [
1721
config.network.tls.files_watchdog,
1822
config.network.tls.cert_file,
1923
config.network.tls.key_file,
24+
config.local_data.rpz,
2025
]
2126

2227

23-
FilesToWatch = Dict[Path, str]
24-
25-
2628
if WATCHDOG_LIB:
2729
from watchdog.events import (
2830
FileSystemEvent,
@@ -33,48 +35,76 @@ def tls_cert_files_config(config: KresConfig) -> List[Any]:
3335
class FilesWatchdogEventHandler(FileSystemEventHandler):
3436
def __init__(self, files: FilesToWatch) -> None:
3537
self._files = files
36-
self._timer: Optional[Timer] = None
38+
self._policy_timer: Optional[Timer] = None
39+
self._timers: Dict[str, Timer] = {}
40+
41+
def _trigger(self, cmd: Optional[str]) -> None:
42+
def policy_reload() -> None:
43+
assert _config_store is not None
44+
if compat.asyncio.is_event_loop_running():
45+
compat.asyncio.create_task(_config_store.renew())
46+
else:
47+
compat.asyncio.run(_config_store.renew())
48+
logger.info("Reloading policy rules has finished")
49+
50+
if not cmd:
51+
# skipping if reload was already triggered
52+
if self._policy_timer and self._policy_timer.is_alive():
53+
logger.info("Skipping reloading policy rules, it was already triggered")
54+
return
55+
# start a 5sec timer
56+
logger.info("Delayed policy rules reload has started")
57+
self._policy_timer = Timer(5, policy_reload)
58+
self._policy_timer.start()
59+
return
3760

38-
def _reload(self, cmd: str) -> None:
3961
def command() -> None:
4062
if compat.asyncio.is_event_loop_running():
4163
compat.asyncio.create_task(command_registered_workers(cmd))
4264
else:
4365
compat.asyncio.run(command_registered_workers(cmd))
44-
logger.info("Reloading of TLS certificate files has finished")
66+
logger.info(f"Sending '{cmd}' command to reload watched files has finished")
4567

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")
68+
# skipping if command was already triggered
69+
if cmd in self._timers and self._timers[cmd].is_alive():
70+
logger.info(f"Skipping sending '{cmd}' command, it was already triggered")
4971
return
5072
# 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()
73+
logger.info(f"Delayed send of '{cmd}' command has started")
74+
self._timers[cmd] = Timer(5, command)
75+
self._timers[cmd].start()
5476

5577
def on_created(self, event: FileSystemEvent) -> None:
5678
src_path = Path(str(event.src_path))
5779
if src_path in self._files.keys():
5880
logger.info(f"Watched file '{src_path}' has been created")
59-
self._reload(self._files[src_path])
81+
self._trigger(self._files[src_path])
6082

6183
def on_deleted(self, event: FileSystemEvent) -> None:
6284
src_path = Path(str(event.src_path))
6385
if src_path in self._files.keys():
6486
logger.warning(f"Watched file '{src_path}' has been deleted")
65-
if self._timer:
66-
self._timer.cancel()
87+
cmd = self._files[src_path]
88+
if cmd in self._timers:
89+
self._timers[cmd].cancel()
6790
for file in self._files.keys():
6891
if file.parent == src_path:
6992
logger.warning(f"Watched directory '{src_path}' has been deleted")
70-
if self._timer:
71-
self._timer.cancel()
93+
cmd = self._files[file]
94+
if cmd in self._timers:
95+
self._timers[cmd].cancel()
96+
97+
def on_moved(self, event: FileSystemEvent) -> None:
98+
src_path = Path(str(event.src_path))
99+
if src_path in self._files.keys():
100+
logger.info(f"Watched file '{src_path}' has been moved")
101+
self._trigger(self._files[src_path])
72102

73103
def on_modified(self, event: FileSystemEvent) -> None:
74104
src_path = Path(str(event.src_path))
75105
if src_path in self._files.keys():
76106
logger.info(f"Watched file '{src_path}' has been modified")
77-
self._reload(self._files[src_path])
107+
self._trigger(self._files[src_path])
78108

79109
_files_watchdog: Optional["FilesWatchdog"] = None
80110

@@ -104,7 +134,7 @@ def stop(self) -> None:
104134
self._observer.join()
105135

106136

107-
@only_on_real_changes_update(tls_cert_files_config)
137+
@only_on_real_changes_update(watched_files_config)
108138
async def _init_files_watchdog(config: KresConfig) -> None:
109139
if WATCHDOG_LIB:
110140
global _files_watchdog
@@ -119,12 +149,21 @@ async def _init_files_watchdog(config: KresConfig) -> None:
119149
files_to_watch[config.network.tls.cert_file.to_path()] = net_tls
120150
files_to_watch[config.network.tls.key_file.to_path()] = net_tls
121151

152+
# local-data.rpz
153+
if config.local_data.rpz:
154+
for rpz in config.local_data.rpz:
155+
if rpz.watchdog:
156+
files_to_watch[rpz.file.to_path()] = None
157+
122158
if files_to_watch:
123159
logger.info("Initializing files watchdog")
124160
_files_watchdog = FilesWatchdog(files_to_watch)
125161
_files_watchdog.start()
126162

127163

128164
async def init_files_watchdog(config_store: ConfigStore) -> None:
165+
global _config_store
166+
_config_store = config_store
167+
129168
# register files watchdog callback
130169
await config_store.register_on_change_callback(_init_files_watchdog)
+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#!/usr/bin/env bash
2+
3+
set -e
4+
5+
gitroot=$(git rev-parse --show-toplevel)
6+
rpz_file=$gitroot/example.rpz
7+
8+
rpz_example=$(cat <<EOF
9+
\$ORIGIN RPZ.EXAMPLE.ORG.
10+
ok.example.com CNAME rpz-passthru.
11+
EOF
12+
)
13+
14+
# create example RPZ
15+
echo "$rpz_example" >> $rpz_file
16+
17+
rpz_conf=$(cat <<EOF
18+
local-data:
19+
rpz:
20+
- file: $rpz_file
21+
watchdog: false
22+
EOF
23+
)
24+
25+
# configure RPZ file
26+
echo "$rpz_conf" >> /etc/knot-resolver/config.yaml
27+
kresctl reload
28+
if [ "$?" -ne "0" ]; then
29+
echo "Could not set RPZ."
30+
exit 1
31+
fi
32+
33+
function count_errors(){
34+
echo "$(journalctl -u knot-resolver.service | grep -c error)"
35+
}
36+
37+
function count_reloads(){
38+
echo "$(journalctl -u knot-resolver.service | grep -c "Reloading policy rules has finished")"
39+
}
40+
41+
# test that RPZ watchdog is turned off
42+
# {{
43+
44+
err_count=$(count_errors)
45+
rel_count=$(count_reloads)
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+
# modify RPZ file, it will trigger reload
65+
rel_count=$(count_reloads)
66+
echo "32.1.2.0.192.rpz-client-ip CNAME rpz-passthru." >> $rpz_file
67+
68+
# wait for files reload to finish
69+
sleep 10
70+
71+
if [ $(count_errors) -ne $err_count ] || [ $(count_reloads) -eq $rel_count ]; then
72+
echo "Could not reload modified RPZ file."
73+
exit 1
74+
fi
75+
76+
# }}
77+
78+
# test replacement
79+
# {{
80+
81+
rel_count=$(count_reloads)
82+
83+
# copy RPZ file
84+
cp $rpz_file $rpz_file.new
85+
86+
# edit new files
87+
echo "48.zz.101.db8.2001.rpz-client-ip CNAME rpz-passthru." >> $rpz_file.new
88+
89+
# replace files
90+
cp -f $rpz_file.new $rpz_file
91+
92+
# wait for files reload to finish
93+
sleep 6
94+
95+
if [ $(count_errors) -ne $err_count ] || [ $(count_reloads) -eq $rel_count ]; then
96+
echo "Could not reload replaced RPZ file."
97+
exit 1
98+
fi
99+
100+
# }}
101+
102+
# test recovery from deletion and creation
103+
# {{
104+
105+
rel_count=$(count_reloads)
106+
107+
# backup rpz file
108+
cp $rpz_file $rpz_file.backup
109+
110+
# delete RPZ file
111+
rm $rpz_file
112+
113+
# create cert files
114+
mv $rpz_file.backup $rpz_file
115+
116+
# wait for files reload to finish
117+
sleep 10
118+
119+
if [ $(count_errors) -ne $err_count ] || [ $(count_reloads) -eq $rel_count ]; then
120+
echo "Could not reload created RPZ file."
121+
exit 1
122+
fi
123+
124+
# }}

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)