use std::net::{Ipv4Addr, TcpListener};
use olai_stack_topology::Plan;
use serde::Serialize;
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct BusyPort {
pub port: u16,
pub owner: String,
}
pub fn probe_host_ports(plan: &Plan) -> Vec<BusyPort> {
plan.host_ports()
.into_iter()
.filter(|(port, _)| !is_free(*port))
.map(|(port, owner)| BusyPort { port, owner })
.collect()
}
pub fn is_free(port: u16) -> bool {
TcpListener::bind((Ipv4Addr::UNSPECIFIED, port)).is_ok()
}
#[cfg(test)]
mod tests {
use super::*;
use olai_stack_topology::{PlanCtx, Selection, baseline_catalog};
#[test]
fn a_held_port_is_busy() {
let held = TcpListener::bind((Ipv4Addr::UNSPECIFIED, 0)).unwrap();
let port = held.local_addr().unwrap().port();
assert!(!is_free(port));
drop(held);
assert!(is_free(port));
}
#[test]
fn the_probe_reports_the_plans_busy_ports() {
let held = TcpListener::bind((Ipv4Addr::UNSPECIFIED, 0)).unwrap();
let port = held.local_addr().unwrap().port();
let ctx = PlanCtx {
gateway_host_port: port,
..PlanCtx::default()
};
let plan = baseline_catalog()
.plan(&Selection::modules(["envoy", "rustfs"]), &ctx)
.unwrap();
let busy = probe_host_ports(&plan);
assert!(busy.iter().any(|b| b.port == port), "{busy:?}");
}
}