diff --git a/src/container.rs b/src/container.rs index a27e2df5..13a4164e 100644 --- a/src/container.rs +++ b/src/container.rs @@ -137,11 +137,12 @@ impl Container { pub fn spawn(&self, cmd: &[&str]) -> Result { let mut command = Command::new("docker"); + command.stdout(Stdio::piped()).stderr(Stdio::piped()); command.args(["exec", "-t", &self.inner.id]).args(cmd); let inner = command.spawn()?; Ok(Child { - inner, + inner: Some(inner), _container: self.inner.clone(), }) } @@ -164,13 +165,22 @@ struct Inner { // runs inside of, to prevent the scenario of the container being destroyed _before_ // the child is killed pub struct Child { - inner: process::Child, + inner: Option, _container: Arc, } +impl Child { + pub fn wait(mut self) -> Result { + let output = self.inner.take().expect("unreachable").wait_with_output()?; + output.try_into() + } +} + impl Drop for Child { fn drop(&mut self) { - let _ = self.inner.kill(); + if let Some(mut inner) = self.inner.take() { + let _ = inner.kill(); + } } } diff --git a/src/name_server.rs b/src/name_server.rs index cb56a620..97c45967 100644 --- a/src/name_server.rs +++ b/src/name_server.rs @@ -152,7 +152,7 @@ impl<'a> NameServer<'a, Stopped> { Ok(NameServer { container, zone_file, - state: Running { _child: child }, + state: Running { child }, }) } } @@ -188,7 +188,7 @@ impl<'a> NameServer<'a, Signed> { Ok(NameServer { container, zone_file, - state: Running { _child: child }, + state: Running { child }, }) } @@ -209,6 +209,31 @@ impl<'a> NameServer<'a, Signed> { } } +impl<'a> NameServer<'a, Running> { + /// gracefully terminates the name server collecting all logs + pub fn terminate(self) -> Result { + let pidfile = "/run/nsd/nsd.pid"; + // if `terminate` is called right after `start` NSD may not have had the chance to create + // the PID file so if it doesn't exist wait for a bit before invoking `kill` + let kill = format!( + "test -f {pidfile} || sleep 1 +kill -TERM $(cat {pidfile})" + ); + self.container.status_ok(&["sh", "-c", &kill])?; + let output = self.state.child.wait()?; + + if !output.status.success() { + return Err("could not terminate the `unbound` process".into()); + } + + assert!( + output.stderr.is_empty(), + "stderr should be returned if not empty" + ); + Ok(output.stdout) + } +} + impl<'a, S> NameServer<'a, S> { pub fn ipv4_addr(&self) -> Ipv4Addr { self.container.ipv4_addr() @@ -238,7 +263,7 @@ pub struct Signed { } pub struct Running { - _child: Child, + child: Child, } fn primary_ns(ns_count: usize) -> FQDN<'static> { @@ -345,4 +370,14 @@ mod tests { Ok(()) } + + #[test] + fn terminate_works() -> Result<()> { + let ns = NameServer::new(FQDN::ROOT)?.start()?; + let logs = ns.terminate()?; + + assert!(logs.contains("nsd starting")); + + Ok(()) + } } diff --git a/src/recursive_resolver.rs b/src/recursive_resolver.rs index 2b282973..6713dcb9 100644 --- a/src/recursive_resolver.rs +++ b/src/recursive_resolver.rs @@ -7,7 +7,7 @@ use crate::Result; pub struct RecursiveResolver { container: Container, - _child: Child, + child: Child, } impl RecursiveResolver { @@ -37,15 +37,33 @@ impl RecursiveResolver { let child = container.spawn(&["unbound", "-d"])?; - Ok(Self { - _child: child, - container, - }) + Ok(Self { child, container }) } pub fn ipv4_addr(&self) -> Ipv4Addr { self.container.ipv4_addr() } + + /// gracefully terminates the name server collecting all logs + pub fn terminate(self) -> Result { + let pidfile = "/run/unbound.pid"; + let kill = format!( + "test -f {pidfile} || sleep 1 +kill -TERM $(cat {pidfile})" + ); + self.container.status_ok(&["sh", "-c", &kill])?; + let output = self.child.wait()?; + + if !output.status.success() { + return Err("could not terminate the `unbound` process".into()); + } + + assert!( + output.stderr.is_empty(), + "stderr should be returned if not empty" + ); + Ok(output.stdout) + } } fn unbound_conf(use_dnssec: bool) -> String { @@ -229,4 +247,15 @@ mod tests { Ok(()) } + + #[test] + fn terminate_works() -> Result<()> { + let resolver = RecursiveResolver::start(&[], &[])?; + let logs = resolver.terminate()?; + + eprintln!("{logs}"); + assert!(logs.contains("start of service")); + + Ok(()) + } }