use std::path::{Path, PathBuf};
use std::time::Duration;
use super::{Discoverer, HEALTH_PROBE_TIMEOUT, HealthState, ServiceStatus};
use crate::daemon::search_rpc;
use crate::services::manifest::{HealthProbe, ServiceDecl};
pub trait SearchSocketProber: Send + Sync {
fn socket(&self) -> anyhow::Result<PathBuf>;
fn search_health(&self, socket: &Path, timeout: Duration) -> HealthState;
}
pub struct RealSearchSocketProber;
impl SearchSocketProber for RealSearchSocketProber {
fn socket(&self) -> anyhow::Result<PathBuf> {
search_rpc::search_socket()
}
fn search_health(&self, socket: &Path, timeout: Duration) -> HealthState {
match search_rpc::call_blocking(
socket,
search_rpc::METHOD_HEALTH,
serde_json::json!({}),
timeout,
) {
Ok(_) => HealthState::Ok,
Err(e) => HealthState::Fail {
detail: format!("trusty-search unreachable at {}: {e:#}", socket.display()),
},
}
}
}
#[cfg(test)]
pub(crate) struct NoSearchDaemon;
#[cfg(test)]
impl SearchSocketProber for NoSearchDaemon {
fn socket(&self) -> anyhow::Result<PathBuf> {
Ok(PathBuf::from(
"/nonexistent/trusty-search/trusty-search.sock",
))
}
fn search_health(&self, _socket: &Path, _timeout: Duration) -> HealthState {
HealthState::Fail {
detail: "no trusty-search daemon in this test".into(),
}
}
}
impl Discoverer {
pub(super) fn probe_uds_search(&self, name: &str, decl: &ServiceDecl) -> ServiceStatus {
let health = match self.socket_prober.socket() {
Ok(socket) => self
.socket_prober
.search_health(&socket, HEALTH_PROBE_TIMEOUT),
Err(e) => HealthState::Fail {
detail: format!("cannot resolve the trusty-search socket: {e:#}"),
},
};
let running = health == HealthState::Ok;
let pid = self.probe_process(decl);
let version = if running {
self.probe_version(decl)
} else {
None
};
ServiceStatus {
name: name.to_string(),
declared: true,
running,
pid,
port: None,
url: None,
version,
health,
log_path: super::existing_log_path(decl),
uptime_secs: pid.and_then(|p| self.probe_uptime(p)),
}
}
pub fn port(&mut self, name: &str) -> Option<Result<u16, String>> {
let decl = self.manifest.services.get(name)?.clone();
if decl.health_probe == HealthProbe::UdsSearch {
return Some(Err(format!(
"{name} has no TCP port ({})",
self.socket_label()
)));
}
let status = self.probe_or_cached(name, &decl);
Some(
status
.port
.ok_or_else(|| format!("{name}: port unavailable (service down or no port)")),
)
}
pub fn url(&mut self, name: &str) -> Option<Result<String, String>> {
let decl = self.manifest.services.get(name)?.clone();
if decl.health_probe == HealthProbe::UdsSearch {
return Some(Err(format!(
"{name} has no TCP URL ({})",
self.socket_label()
)));
}
let status = self.probe_or_cached(name, &decl);
Some(
status
.url
.ok_or_else(|| format!("{name}: URL unavailable (service down or no port)")),
)
}
fn socket_label(&self) -> String {
match self.socket_prober.socket() {
Ok(socket) => format!("socket: {}", socket.display()),
Err(e) => format!("socket unresolved: {e:#}"),
}
}
}