use crate::core::types::Machine;
pub struct ControlMasterGuard {
started: Vec<Machine>,
}
impl ControlMasterGuard {
pub fn open(machines: &[Machine], verbose: bool) -> Self {
let opened: Vec<bool> = std::thread::scope(|s| {
let handles: Vec<_> = machines
.iter()
.map(|m| s.spawn(move || super::ssh::start_control_master(m)))
.collect();
handles
.into_iter()
.zip(machines)
.map(|(h, m)| match h.join() {
Ok(Ok(started)) => started,
Ok(Err(e)) => {
if verbose {
eprintln!("warning: SSH multiplexing failed for {}: {e}", m.hostname);
}
false
}
Err(_) => false,
})
.collect()
});
let started = machines
.iter()
.zip(opened)
.filter(|(_, opened)| *opened)
.map(|(m, _)| m.clone())
.collect();
Self { started }
}
pub fn opened(&self) -> usize {
self.started.len()
}
}
impl Drop for ControlMasterGuard {
fn drop(&mut self) {
for m in &self.started {
let _ = super::ssh::stop_control_master(m);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn machine(addr: &str) -> Machine {
Machine {
hostname: "h".to_string(),
addr: addr.to_string(),
user: "root".to_string(),
arch: "x86_64".to_string(),
ssh_key: None,
roles: vec![],
transport: None,
container: None,
pepita: None,
cost: 0,
allowed_operators: vec![],
}
}
#[test]
fn empty_guard_opens_nothing() {
let g = ControlMasterGuard::open(&[], false);
assert_eq!(g.opened(), 0);
}
#[test]
fn unreachable_machine_is_not_adopted() {
let g = ControlMasterGuard::open(&[machine("forjar-e02.invalid")], false);
assert_eq!(g.opened(), 0);
}
}