Incorporate feedback

* add new type `Network` that holds `Arc`
* adjust network name to use `CARGO_PKG_NAME` env var, process id and
  counter
* remove function to remove network
* clone Network in container
* refactor Network tests
This commit is contained in:
Sebastian Ziebell 2024-02-16 13:49:58 +01:00
parent 2289567998
commit a4ca3d6423
No known key found for this signature in database
GPG Key ID: 7CFCF2A8E3AE3A09
2 changed files with 82 additions and 86 deletions

View File

@ -67,6 +67,7 @@ impl Container {
id,
name,
ipv4_addr,
_network: network.clone(),
};
Ok(Self {
inner: Arc::new(inner),
@ -172,6 +173,7 @@ struct Inner {
id: String,
// TODO probably also want the IPv6 address
ipv4_addr: Ipv4Addr,
_network: Network,
}
/// NOTE unlike `std::process::Child`, the drop implementation of this type will `kill` the

View File

@ -1,25 +1,57 @@
use std::{
process::{Command, ExitStatus, Stdio},
sync::atomic::{self, AtomicUsize},
process::{self, Command, Stdio},
sync::{
atomic::{self, AtomicUsize},
Arc,
},
};
use crate::Result;
const NETWORK_NAME: &str = "dnssec-network";
/// Represents a network in which to put containers into.
pub struct Network {
#[derive(Clone)]
pub struct Network(Arc<NetworkInner>);
impl Network {
/// Returns the name of the network.
pub fn name(&self) -> &str {
self.0.name.as_str()
}
/// Returns the subnet mask
pub fn netmask(&self) -> &str {
&self.0.config.subnet
}
}
struct NetworkInner {
name: String,
config: NetworkConfig,
}
impl Network {
pub fn new() -> Result<Self> {
let id = network_count();
let network_name = format!("{NETWORK_NAME}-{id}");
let pid = process::id();
let network_name = env!("CARGO_PKG_NAME");
Ok(Self(Arc::new(NetworkInner::new(pid, network_name)?)))
}
}
// A network can exist, for example when a test panics
let _ = remove_network(network_name.as_str())?;
/// This ensure the Docker network is deleted after the test runner process ends.
impl Drop for NetworkInner {
fn drop(&mut self) {
let _ = Command::new("docker")
.args(["network", "rm", "--force", self.name.as_str()])
.stdout(Stdio::null())
.stderr(Stdio::null())
.status();
}
}
impl NetworkInner {
pub fn new(pid: u32, network_name: &str) -> Result<Self> {
let count = network_count();
let network_name = format!("{network_name}-{pid}-{count}");
let mut command = Command::new("docker");
command
@ -28,13 +60,13 @@ impl Network {
.arg(&network_name);
// create network
let output = command.output().unwrap();
let output = command.output()?;
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"--- STDOUT ---\n{stdout}\n--- STDERR ---\n{stderr}"
);
if !output.status.success() {
return Err(format!("--- STDOUT ---\n{stdout}\n--- STDERR ---\n{stderr}").into());
}
// inspect & parse network details
let config = get_network_config(&network_name)?;
@ -44,16 +76,6 @@ impl Network {
config,
})
}
/// Returns the name of the network.
pub fn name(&self) -> &str {
self.name.as_str()
}
/// Returns the subnet mask
pub fn netmask(&self) -> &str {
&self.config.subnet
}
}
/// Collects all important configs.
@ -83,64 +105,6 @@ fn get_network_config(network_name: &str) -> Result<NetworkConfig> {
Ok(NetworkConfig { subnet })
}
/// This ensure the Docker network is deleted after the test runner process ends.
impl Drop for Network {
fn drop(&mut self) {
let _ = remove_network(&self.name);
}
}
/// Removes the given network.
fn remove_network(network_name: &str) -> Result<ExitStatus> {
// Disconnects all attached containers
for container_id in get_attached_containers(network_name)? {
let mut command = Command::new("docker");
let _ = command
.args(["network", "disconnect", "--force"])
.args([network_name, container_id.as_str()])
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()?;
}
// Remove the network
let mut command = Command::new("docker");
command
.args(["network", "rm", "--force", network_name])
.stdout(Stdio::null())
.stderr(Stdio::null());
Ok(command.status()?)
}
/// Finds the list of connected containers
fn get_attached_containers(network_name: &str) -> Result<Vec<String>> {
let mut command = Command::new("docker");
command.args([
"network",
"inspect",
network_name,
"-f",
r#"{{ range $k, $v := .Containers }}{{ printf "%s\n" $k }}{{ end }}"#,
]);
let output = command.output()?;
let container_ids = match output.status.success() {
true => {
let container_ids = std::str::from_utf8(&output.stdout)?
.trim()
.to_string()
.lines()
.filter(|line| !line.trim().is_empty())
.map(|line| line.to_string())
.collect::<Vec<_>>();
container_ids
}
false => vec![],
};
Ok(container_ids)
}
fn network_count() -> usize {
static COUNT: AtomicUsize = AtomicUsize::new(1);
@ -153,6 +117,35 @@ mod tests {
use super::*;
/// Finds the list of connected containers
fn get_attached_containers(network_name: &str) -> Result<Vec<String>> {
let mut command = Command::new("docker");
command.args([
"network",
"inspect",
network_name,
"-f",
r#"{{ range $k, $v := .Containers }}{{ printf "%s\n" $k }}{{ end }}"#,
]);
let output = command.output()?;
let container_ids = match output.status.success() {
true => {
let container_ids = std::str::from_utf8(&output.stdout)?
.trim()
.to_string()
.lines()
.filter(|line| !line.trim().is_empty())
.map(|line| line.to_string())
.collect::<Vec<_>>();
container_ids
}
false => vec![],
};
Ok(container_ids)
}
#[test]
fn create_works() -> Result<()> {
assert!(Network::new().is_ok());
@ -170,17 +163,18 @@ mod tests {
#[test]
fn remove_network_works() -> Result<()> {
let network = Network::new().expect("Failed to create network");
let network_name = network.name().to_string();
let nameserver = NameServer::new(FQDN::ROOT, &network)?;
let container_ids = get_attached_containers(network.name())?;
assert_eq!(1, container_ids.len());
assert_eq!(&[nameserver.container_id().to_string()], &container_ids[..]);
drop(network);
drop(nameserver);
let container_ids = get_attached_containers(&network_name)?;
assert!(container_ids.is_empty());
let container_ids = get_attached_containers(network.name())?;
assert_eq!(0, container_ids.len());
drop(network);
Ok(())
}