olai_stack_runtime/
ports.rs1use std::net::{Ipv4Addr, TcpListener};
4
5use olai_stack_topology::Plan;
6use serde::Serialize;
7
8#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
10pub struct BusyPort {
11 pub port: u16,
13 pub owner: String,
15}
16
17pub fn probe_host_ports(plan: &Plan) -> Vec<BusyPort> {
21 plan.host_ports()
22 .into_iter()
23 .filter(|(port, _)| !is_free(*port))
24 .map(|(port, owner)| BusyPort { port, owner })
25 .collect()
26}
27
28pub fn is_free(port: u16) -> bool {
30 TcpListener::bind((Ipv4Addr::UNSPECIFIED, port)).is_ok()
31}
32
33#[cfg(test)]
34mod tests {
35 use super::*;
36 use olai_stack_topology::{PlanCtx, Selection, baseline_catalog};
37
38 #[test]
39 fn a_held_port_is_busy() {
40 let held = TcpListener::bind((Ipv4Addr::UNSPECIFIED, 0)).unwrap();
41 let port = held.local_addr().unwrap().port();
42 assert!(!is_free(port));
43 drop(held);
44 assert!(is_free(port));
45 }
46
47 #[test]
48 fn the_probe_reports_the_plans_busy_ports() {
49 let held = TcpListener::bind((Ipv4Addr::UNSPECIFIED, 0)).unwrap();
51 let port = held.local_addr().unwrap().port();
52 let ctx = PlanCtx {
53 gateway_host_port: port,
54 ..PlanCtx::default()
55 };
56 let plan = baseline_catalog()
57 .plan(&Selection::modules(["envoy", "rustfs"]), &ctx)
58 .unwrap();
59 let busy = probe_host_ports(&plan);
60 assert!(busy.iter().any(|b| b.port == port), "{busy:?}");
61 }
62}