use std::time::Duration;
use super::circuit::{DownstreamSpec, ProbeKind};
const PROBE_TIMEOUT: Duration = Duration::from_secs(3);
pub async fn probe_downstream(spec: &DownstreamSpec) -> Option<bool> {
let target = spec.target.as_deref()?;
match spec.probe {
ProbeKind::Passive => None,
ProbeKind::Http => Some(probe_http(target).await),
ProbeKind::Tcp => Some(probe_tcp(target).await),
ProbeKind::Nats => Some(probe_tcp(&strip_nats_scheme(target)).await),
}
}
async fn probe_http(url: &str) -> bool {
let client = match reqwest::Client::builder().timeout(PROBE_TIMEOUT).build() {
Ok(c) => c,
Err(_) => return false,
};
match client.get(url).send().await {
Ok(resp) => !resp.status().is_server_error(),
Err(_) => false,
}
}
async fn probe_tcp(host_port: &str) -> bool {
let addr = host_port.trim();
matches!(
tokio::time::timeout(PROBE_TIMEOUT, tokio::net::TcpStream::connect(addr)).await,
Ok(Ok(_stream))
)
}
fn strip_nats_scheme(url: &str) -> String {
url.strip_prefix("nats://")
.or_else(|| url.strip_prefix("tls://"))
.unwrap_or(url)
.trim_end_matches('/')
.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn passive_probe_returns_none() {
let spec = DownstreamSpec {
name: "x".into(),
probe: ProbeKind::Passive,
target: None,
};
assert_eq!(probe_downstream(&spec).await, None);
}
#[tokio::test]
async fn missing_target_returns_none() {
let spec = DownstreamSpec {
name: "x".into(),
probe: ProbeKind::Http,
target: None,
};
assert_eq!(probe_downstream(&spec).await, None);
}
#[tokio::test]
async fn tcp_probe_dead_port_is_down() {
let spec = DownstreamSpec {
name: "x".into(),
probe: ProbeKind::Tcp,
target: Some("127.0.0.1:1".into()),
};
assert_eq!(probe_downstream(&spec).await, Some(false));
}
#[tokio::test]
async fn http_probe_unreachable_is_down() {
let spec = DownstreamSpec {
name: "x".into(),
probe: ProbeKind::Http,
target: Some("http://127.0.0.1:1/health".into()),
};
assert_eq!(probe_downstream(&spec).await, Some(false));
}
#[test]
fn strip_scheme_variants() {
assert_eq!(strip_nats_scheme("nats://nats:4222"), "nats:4222");
assert_eq!(strip_nats_scheme("tls://nats:4222/"), "nats:4222");
assert_eq!(strip_nats_scheme("nats:4222"), "nats:4222");
}
}