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:
parent
2289567998
commit
a4ca3d6423
@ -67,6 +67,7 @@ impl Container {
|
|||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
ipv4_addr,
|
ipv4_addr,
|
||||||
|
_network: network.clone(),
|
||||||
};
|
};
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
inner: Arc::new(inner),
|
inner: Arc::new(inner),
|
||||||
@ -172,6 +173,7 @@ struct Inner {
|
|||||||
id: String,
|
id: String,
|
||||||
// TODO probably also want the IPv6 address
|
// TODO probably also want the IPv6 address
|
||||||
ipv4_addr: Ipv4Addr,
|
ipv4_addr: Ipv4Addr,
|
||||||
|
_network: Network,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// NOTE unlike `std::process::Child`, the drop implementation of this type will `kill` the
|
/// NOTE unlike `std::process::Child`, the drop implementation of this type will `kill` the
|
||||||
|
@ -1,25 +1,57 @@
|
|||||||
use std::{
|
use std::{
|
||||||
process::{Command, ExitStatus, Stdio},
|
process::{self, Command, Stdio},
|
||||||
sync::atomic::{self, AtomicUsize},
|
sync::{
|
||||||
|
atomic::{self, AtomicUsize},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
|
||||||
const NETWORK_NAME: &str = "dnssec-network";
|
|
||||||
|
|
||||||
/// Represents a network in which to put containers into.
|
/// 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,
|
name: String,
|
||||||
config: NetworkConfig,
|
config: NetworkConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Network {
|
impl Network {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
let id = network_count();
|
let pid = process::id();
|
||||||
let network_name = format!("{NETWORK_NAME}-{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
|
/// This ensure the Docker network is deleted after the test runner process ends.
|
||||||
let _ = remove_network(network_name.as_str())?;
|
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");
|
let mut command = Command::new("docker");
|
||||||
command
|
command
|
||||||
@ -28,13 +60,13 @@ impl Network {
|
|||||||
.arg(&network_name);
|
.arg(&network_name);
|
||||||
|
|
||||||
// create network
|
// create network
|
||||||
let output = command.output().unwrap();
|
let output = command.output()?;
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
assert!(
|
|
||||||
output.status.success(),
|
if !output.status.success() {
|
||||||
"--- STDOUT ---\n{stdout}\n--- STDERR ---\n{stderr}"
|
return Err(format!("--- STDOUT ---\n{stdout}\n--- STDERR ---\n{stderr}").into());
|
||||||
);
|
}
|
||||||
|
|
||||||
// inspect & parse network details
|
// inspect & parse network details
|
||||||
let config = get_network_config(&network_name)?;
|
let config = get_network_config(&network_name)?;
|
||||||
@ -44,16 +76,6 @@ impl Network {
|
|||||||
config,
|
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.
|
/// Collects all important configs.
|
||||||
@ -83,64 +105,6 @@ fn get_network_config(network_name: &str) -> Result<NetworkConfig> {
|
|||||||
Ok(NetworkConfig { subnet })
|
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 {
|
fn network_count() -> usize {
|
||||||
static COUNT: AtomicUsize = AtomicUsize::new(1);
|
static COUNT: AtomicUsize = AtomicUsize::new(1);
|
||||||
|
|
||||||
@ -153,6 +117,35 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
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]
|
#[test]
|
||||||
fn create_works() -> Result<()> {
|
fn create_works() -> Result<()> {
|
||||||
assert!(Network::new().is_ok());
|
assert!(Network::new().is_ok());
|
||||||
@ -170,17 +163,18 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn remove_network_works() -> Result<()> {
|
fn remove_network_works() -> Result<()> {
|
||||||
let network = Network::new().expect("Failed to create network");
|
let network = Network::new().expect("Failed to create network");
|
||||||
let network_name = network.name().to_string();
|
|
||||||
let nameserver = NameServer::new(FQDN::ROOT, &network)?;
|
let nameserver = NameServer::new(FQDN::ROOT, &network)?;
|
||||||
|
|
||||||
let container_ids = get_attached_containers(network.name())?;
|
let container_ids = get_attached_containers(network.name())?;
|
||||||
assert_eq!(1, container_ids.len());
|
assert_eq!(1, container_ids.len());
|
||||||
assert_eq!(&[nameserver.container_id().to_string()], &container_ids[..]);
|
assert_eq!(&[nameserver.container_id().to_string()], &container_ids[..]);
|
||||||
|
|
||||||
drop(network);
|
drop(nameserver);
|
||||||
|
|
||||||
let container_ids = get_attached_containers(&network_name)?;
|
let container_ids = get_attached_containers(network.name())?;
|
||||||
assert!(container_ids.is_empty());
|
assert_eq!(0, container_ids.len());
|
||||||
|
|
||||||
|
drop(network);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user