//! gRPC health module for imks. //! //! Provides: //! - imks's own gRPC health server (for external health checks) //! - Health check client for probing appks health (5s interval) use std::net::SocketAddr; use std::time::Duration; use tonic_health::pb::HealthCheckRequest; use tonic_health::pb::health_check_response::ServingStatus; use tonic_health::pb::health_client::HealthClient; use crate::{ImksError, ImksResult}; /// Start imks's own gRPC health server on the given address. /// /// Reports `SERVING` for the overall server status (empty service name). pub async fn start_health_server(addr: SocketAddr) -> ImksResult<()> { let (reporter, health_service) = tonic_health::server::health_reporter(); // Empty service name = overall server health. // reporter is an owned handle — the server will remain SERVING // indefinitely unless a caller updates the status. reporter .set_service_status("", tonic_health::ServingStatus::Serving) .await; tracing::info!(%addr, "imks gRPC health server started"); tonic::transport::Server::builder() .add_service(health_service) .serve(addr) .await .map_err(|e| ImksError::Internal(format!("Health gRPC server: {e:?}")))?; Ok(()) } /// Check appks health by opening a short-lived gRPC connection and calling `Check`. /// /// Uses a 3-second connect timeout to fail fast when appks is unreachable. pub async fn check_appks_health(addr: &str) -> ImksResult { let endpoint = tonic::transport::Endpoint::from_shared(addr.to_string()) .map_err(|e| ImksError::Internal(format!("health endpoint: {e}")))?; // Short-lived connection for health probe only let channel = endpoint .connect_timeout(Duration::from_secs(3)) .connect() .await .map_err(ImksError::GrpcTransport)?; let mut client = HealthClient::new(channel); let resp = client .check(HealthCheckRequest { service: "".to_string(), }) .await .map_err(ImksError::GrpcStatus)?; Ok(resp.into_inner().status == ServingStatus::Serving as i32) }