mirror of
https://github.com/pi-hole/FTL.git
synced 2026-07-07 15:55:09 +01:00
3952f05555
Issue #2818 reports FTL terminating cleanly (code 0) after ~15-16 hours of uptime, with the only captured log lines being: Thread webserver (7) is idle, terminating it. All threads joined ########## FTL terminated after 15h 50m 45s (code 0)! ########## A thorough audit of all paths that can set the global `killed` flag confirms there is no internal code path leading to spontaneous termination - every shutdown is either test-mode (`pihole-FTL test`), a `die()`/longjmp from dnsmasq, or an external SIGTERM mapped to dnsmasq's EVENT_TERM via SIGUSR6. The "Thread webserver (7) is idle" message is a red herring: the webserver cert-renewal thread sleeps for 24 hours between checks, so it is essentially always cancellable when shutdown begins. The reason the reporter could not diagnose the cause is that the relevant information was either logged at DEBUG level (and therefore invisible) or emitted far above the final termination message (and therefore lost when the log was truncated). This commit closes both gaps so the next occurrence will be diagnosable from a tail of FTL.log alone: - signals.c: persist the SIGTERM sender info ("name (PID, user UID)") into a static buffer in SIGTERM_handler, exposed via get_term_source(). The buffer is sized 256 bytes and only ever read after being written by the signal handler, so no locking is needed. - daemon.c: in cleanup(), re-log the stored SIGTERM source as "Terminated by ..." right before the final "FTL terminated after" banner. Even when the log is truncated to the last few lines, the termination source will now be visible alongside the final message. - main.c: promote the "Shutting down (exit code, jmpret)" log from log_debug to log_info so the entry into the cleanup path is always recorded, not just under DEBUG_ANY. - webserver/webserver.c: split the webserver thread's 24-hour cert-check sleep into 24 x 1-hour sleeps with intermediate `killed` checks. This is a robustness improvement, not a bug fix: the thread is no longer marked as cancellable for 24 hours straight, so the misleading "is idle" log on shutdown happens at most an hour after the last cert check rather than at any point in a 24-hour window. - test/test_final.bats: add two regression tests that run after the existing "FTL terminates with message" test: * Verify "INFO: Shutting down (exit code" is present (catches accidental regression of the log_debug -> log_info change). * Verify "INFO: Terminated by" is present AND that the three key messages appear in order: Shutting down -> Terminated by -> FTL terminated after. After this change, a SIGTERM-triggered shutdown produces: INFO: Asked to terminate by "systemd" (PID 1, user root UID 0) ... INFO: Shutting down (exit code 0, jmpret 0) INFO: Finished final database update INFO: Waiting for threads to join INFO: Thread webserver (7) is idle, terminating it. INFO: All threads joined INFO: Terminated by "systemd" (PID 1, user root UID 0) INFO: ########## FTL terminated after 15h 50m 45s (code 0)! ########## Signed-off-by: Dominik <dl6er@dl6er.de>
119 lines
5.5 KiB
Bash
119 lines
5.5 KiB
Bash
#!./test/libs/bats/bin/bats
|
|
# Final log validation and FTL termination tests.
|
|
# This file runs AFTER both test_suite.bats and the pytest API tests
|
|
# to catch any unexpected log messages produced during the entire run.
|
|
|
|
@test "No WARNING messages in FTL.log (besides known warnings)" {
|
|
run bash -c 'grep "WARNING:" /var/log/pihole/FTL.log | grep -v -E "CAP_NET_ADMIN|CAP_NET_RAW|CAP_SYS_NICE|CAP_IPC_LOCK|CAP_CHOWN|CAP_NET_BIND_SERVICE|CAP_SYS_TIME|FTLCONF_|(Negative DS reply without NS record received for ftl)|(nameserver 127.0.0.1 refused to do a recursive query)|API: Config item is invalid|API: Config item validation failed|API: Not found|API: Config items set via environment variables|API: Rate-limiting login attempts|API: You need to specify both|API: No request body data|API: Invalid request"'
|
|
printf "%s\n" "${lines[@]}"
|
|
[[ "${lines[@]}" == "" ]]
|
|
}
|
|
|
|
@test "No ERROR messages in FTL.log (besides known/intended errors)" {
|
|
run bash -c 'grep "ERROR: " /var/log/pihole/FTL.log'
|
|
printf "%s\n" "${lines[@]}"
|
|
run bash -c 'grep "ERROR: " /var/log/pihole/FTL.log | grep -c -v -E "(index\.html)|(Failed to create shared memory object)|(FTLCONF_debug_api is not a boolean)|(FTLCONF_files_pcap)|(Failed to set|adjust time during NTP sync: Insufficient permissions)|(nlrequest error)|(Failed to read ARP cache)"'
|
|
printf "count: %s\n" "${lines[@]}"
|
|
[[ ${lines[0]} == "0" ]]
|
|
}
|
|
|
|
@test "No CRIT messages in FTL.log (besides error due to starting FTL more than once)" {
|
|
run bash -c 'grep "CRIT:" /var/log/pihole/FTL.log | grep -v "CRIT: pihole-FTL is already running"'
|
|
printf "%s\n" "${lines[@]}"
|
|
[[ "${lines[@]}" == "" ]]
|
|
}
|
|
|
|
@test "No \"DB not available\" messages in FTL.log" {
|
|
run bash -c 'grep -c "database not available" /var/log/pihole/FTL.log'
|
|
printf "%s\n" "${lines[@]}"
|
|
[[ ${lines[0]} == "0" ]]
|
|
}
|
|
|
|
@test "Expected number of config file rotations" {
|
|
# BATS: 1x pihole.toml write (dns.reply.host API PATCH)
|
|
# BATS: 2x pihole.toml writes (CLI password set/remove processes)
|
|
# pytest: 3x pihole.toml writes (password, app_pwhash, serve_all via API)
|
|
# pytest: 2x pihole.toml writes (dns/hosts config array PUT + DELETE)
|
|
# pytest: 2x pihole.toml writes (dns/blocking disable + enable)
|
|
# pytest: 4x pihole.toml writes (config PATCH round-trips: bool + int, change + restore each)
|
|
# pytest: 2x pihole.toml writes (auth stress test password set + remove)
|
|
run bash -c 'grep -c "INFO: Config file written to /etc/pihole/pihole.toml" /var/log/pihole/FTL.log'
|
|
printf "pihole.toml write count: %s\n" "${lines[0]}"
|
|
# On RISCV64, pytest is skipped (too slow), so only BATS writes occur
|
|
if [[ "${CI_ARCH}" == "linux/riscv64" ]]; then
|
|
[[ ${lines[0]} == "1" ]]
|
|
else
|
|
[[ ${lines[0]} == "16" ]]
|
|
fi
|
|
# CLI password set/remove trigger inotify reload but result in
|
|
# "pihole.toml unchanged" as the in-memory config already matches
|
|
run bash -c 'grep -c "pihole.toml unchanged" /var/log/pihole/FTL.log'
|
|
printf "pihole.toml unchanged count: %s\n" "${lines[0]}"
|
|
[[ ${lines[0]} -ge 2 ]]
|
|
run bash -c 'grep -c "DEBUG_CONFIG: Config file written to /etc/pihole/dnsmasq.conf" /var/log/pihole/FTL.log'
|
|
printf "dnsmasq.conf write count: %s\n" "${lines[0]}"
|
|
[[ ${lines[0]} == "1" ]]
|
|
run bash -c 'grep -c "DEBUG_CONFIG: HOSTS file written to /etc/pihole/hosts/custom.list" /var/log/pihole/FTL.log'
|
|
printf "custom.list write count: %s\n" "${lines[0]}"
|
|
# On RISCV64, pytest is skipped, so only BATS writes occur (3x)
|
|
# Otherwise, pytest dns/hosts config array PUT + DELETE add 2 more (5x)
|
|
if [[ "${CI_ARCH}" == "linux/riscv64" ]]; then
|
|
[[ ${lines[0]} == "3" ]]
|
|
else
|
|
[[ ${lines[0]} == "5" ]]
|
|
fi
|
|
}
|
|
|
|
@test "Query with ID 0 has been saved to the database" {
|
|
# FTL exports queries from in-memory DB to disk after a configurable
|
|
# delay (default 30s). Poll up to 60s for the export to complete.
|
|
for i in $(seq 1 30); do
|
|
run bash -c './pihole-FTL sqlite3 /etc/pihole/pihole-FTL.db "SELECT COUNT(*) FROM queries WHERE id=0;"'
|
|
if [[ ${lines[0]} == "1" ]]; then
|
|
break
|
|
fi
|
|
sleep 2
|
|
done
|
|
printf "%s\n" "${lines[@]}"
|
|
[[ ${lines[0]} == "1" ]]
|
|
}
|
|
|
|
@test "FTL terminates with message" {
|
|
logsize_before=$(stat -c%s /var/log/pihole/FTL.log)
|
|
# Kill pihole-FTL after having completed all tests
|
|
pid=$(cat /run/pihole-FTL.pid)
|
|
printf "Killing pihole-FTL with PID %s\n" "$pid"
|
|
|
|
run bash -c "kill $pid"
|
|
printf "%s\n" "${lines[@]}"
|
|
[[ $status == 0 ]]
|
|
|
|
# Wait until pihole-FTL has terminated
|
|
run bash -c "./pihole-FTL wait-for '########## FTL terminated after' /var/log/pihole/FTL.log 30 $logsize_before"
|
|
printf "%s\n" "${lines[@]}"
|
|
[[ $status == 0 ]]
|
|
}
|
|
|
|
@test "Shutdown reason logged at INFO level (#2818)" {
|
|
# Verify the shutdown path now logs at INFO level instead of DEBUG-only
|
|
run bash -c 'grep "INFO: Shutting down (exit code" /var/log/pihole/FTL.log'
|
|
printf "%s\n" "${lines[@]}"
|
|
[[ $status == 0 ]]
|
|
}
|
|
|
|
@test "SIGTERM source re-logged near final termination message (#2818)" {
|
|
# Verify the SIGTERM sender is re-logged during cleanup so it appears
|
|
# near the "FTL terminated" message even in truncated logs
|
|
run bash -c 'grep "INFO: Terminated by" /var/log/pihole/FTL.log'
|
|
printf "%s\n" "${lines[@]}"
|
|
[[ $status == 0 ]]
|
|
|
|
# Verify ordering: "Terminated by" must appear AFTER "Shutting down" and
|
|
# BEFORE the final "FTL terminated" message
|
|
run bash -c 'grep -n "Shutting down (exit code\|Terminated by\|FTL terminated after" /var/log/pihole/FTL.log | tail -3'
|
|
printf "ordering: %s\n" "${lines[@]}"
|
|
[[ "${lines[0]}" == *"Shutting down (exit code"* ]]
|
|
[[ "${lines[1]}" == *"Terminated by"* ]]
|
|
[[ "${lines[2]}" == *"FTL terminated after"* ]]
|
|
}
|