Check hickory-dns is fully started

When starting `hickory-dns` there is no easy way to check the start
sequence has finished & its fully ready to accept connections. Other
tools, e.g. unbound, are designed as services, they will correctly
manage their `pidfile`. They also could be queried by the `servicectl`
inside the Docker container.
This commit is contained in:
Sebastian Ziebell 2024-05-14 14:17:44 +02:00 committed by Андрей Листочкин (Andrei Listochkin)
parent fe3961ffe1
commit 2e46421927
2 changed files with 35 additions and 18 deletions

View File

@ -55,6 +55,11 @@ impl Implementation {
matches!(self, Self::Bind) matches!(self, Self::Bind)
} }
#[must_use]
pub fn is_hickory(&self) -> bool {
matches!(self, Self::Hickory(_))
}
pub(crate) fn format_config(&self, config: Config) -> String { pub(crate) fn format_config(&self, config: Config) -> String {
match config { match config {
Config::Resolver { Config::Resolver {

View File

@ -1,4 +1,5 @@
use core::fmt::Write; use core::fmt::Write;
use std::io::{BufRead, BufReader};
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use crate::container::{Child, Container, Network}; use crate::container::{Child, Container, Network};
@ -38,23 +39,26 @@ impl Resolver {
self.container.ipv4_addr() self.container.ipv4_addr()
} }
/// gracefully terminates the name server collecting all logs /// Gracefully terminates the name server collecting all logs
pub fn terminate(self) -> Result<String> { pub fn terminate(self) -> Result<String> {
let pidfile = self.implementation.pidfile(Role::Resolver); let Resolver {
implementation,
container,
child,
} = self;
let pidfile = implementation.pidfile(Role::Resolver);
let kill = format!( let kill = format!(
"test -f {pidfile} || sleep 1 "test -f {pidfile} || sleep 1
kill -TERM $(cat {pidfile})" kill -TERM $(cat {pidfile})"
); );
self.container.status_ok(&["sh", "-c", &kill])?; container.status_ok(&["sh", "-c", &kill])?;
let output = self.child.wait()?; let output = child.wait()?;
// the hickory-dns binary does not do signal handling so it won't shut down gracefully; we // the hickory-dns binary does not do signal handling so it won't shut down gracefully; we
// will still get some logs so we'll ignore the fact that it fails to shut down ... // will still get some logs so we'll ignore the fact that it fails to shut down ...
let is_hickory = matches!(self.implementation, Implementation::Hickory(_)); if !implementation.is_hickory() && !output.status.success() {
if !is_hickory && !output.status.success() { return Err(format!("could not terminate the `{}` process", implementation).into());
return Err(
format!("could not terminate the `{}` process", self.implementation).into(),
);
} }
assert!( assert!(
@ -115,7 +119,21 @@ impl ResolverSettings {
container.cp(path, &contents)?; container.cp(path, &contents)?;
} }
let child = container.spawn(implementation.cmd_args(config.role()))?; let mut child = container.spawn(implementation.cmd_args(config.role()))?;
// For HickoryDNS we need to wait until its start sequence finished. Only then the server is able
// to accept connections. The start sequence logs are consumed here.
if implementation.is_hickory() {
let stdout = child.stdout()?;
let lines = BufReader::new(stdout).lines();
for line in lines {
let line = line?;
if line.contains("Server starting up") {
break;
}
}
}
Ok(Resolver { Ok(Resolver {
child, child,
@ -195,14 +213,8 @@ mod tests {
)))?; )))?;
let logs = resolver.terminate()?; let logs = resolver.terminate()?;
eprintln!("{logs}"); // Hickory-DNS start sequence log has been consumed in `ResolverSettings.start`.
let mut found = false; assert!(logs.is_empty());
for line in logs.lines() {
if line.contains("Hickory DNS") && line.contains("starting") {
found = true;
}
}
assert!(found);
Ok(()) Ok(())
} }