use std::sync::Arc;
use tokio::sync::Mutex;
use tonic_health::server::HealthReporter;
#[derive(Clone)]
pub struct HealthService {
reporter: Arc<Mutex<HealthReporter>>,
}
impl std::fmt::Debug for HealthService {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HealthService").finish()
}
}
impl HealthService {
pub fn new() -> (Self, tonic_health::pb::health_server::HealthServer<impl tonic_health::pb::health_server::Health>) {
let (reporter, service) = tonic_health::server::health_reporter();
let wrapper = Self {
reporter: Arc::new(Mutex::new(reporter)),
};
(wrapper, service)
}
pub async fn set_serving(&self, service_name: &str) {
let mut reporter = self.reporter.lock().await;
reporter
.set_serving::<tonic_health::pb::health_server::HealthServer<
tonic_health::server::HealthService,
>>()
.await;
reporter
.set_service_status(service_name, tonic_health::ServingStatus::Serving)
.await;
}
pub async fn set_not_serving(&self, service_name: &str) {
let mut reporter = self.reporter.lock().await;
reporter
.set_service_status(service_name, tonic_health::ServingStatus::NotServing)
.await;
}
pub async fn set_unknown(&self, service_name: &str) {
let mut reporter = self.reporter.lock().await;
reporter
.set_service_status(service_name, tonic_health::ServingStatus::Unknown)
.await;
}
pub async fn set_all_serving(&self) {
self.set_all_status(tonic_health::ServingStatus::Serving).await;
}
pub async fn set_all_not_serving(&self) {
self.set_all_status(tonic_health::ServingStatus::NotServing).await;
}
async fn set_all_status(&self, status: tonic_health::ServingStatus) {
let mut reporter = self.reporter.lock().await;
if status == tonic_health::ServingStatus::Serving {
reporter
.set_serving::<tonic_health::pb::health_server::HealthServer<
tonic_health::server::HealthService,
>>()
.await;
}
for service in Self::xds_service_names() {
reporter.set_service_status(service, status).await;
}
}
#[inline]
pub const fn xds_service_names() -> &'static [&'static str] {
&[
"envoy.service.discovery.v3.AggregatedDiscoveryService",
"envoy.service.cluster.v3.ClusterDiscoveryService",
"envoy.service.listener.v3.ListenerDiscoveryService",
"envoy.service.route.v3.RouteDiscoveryService",
"envoy.service.endpoint.v3.EndpointDiscoveryService",
"envoy.service.secret.v3.SecretDiscoveryService",
"xds",
]
}
}
#[derive(Debug, Clone)]
pub struct HealthConfig {
pub enabled: bool,
pub initial_serving: bool,
}
impl Default for HealthConfig {
fn default() -> Self {
Self {
enabled: true,
initial_serving: true,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn health_service_creation() {
let (health, _server) = HealthService::new();
health.set_all_serving().await;
}
#[tokio::test]
async fn health_service_status_transitions() {
let (health, _server) = HealthService::new();
health.set_serving("test-service").await;
health.set_not_serving("test-service").await;
health.set_unknown("test-service").await;
}
#[test]
fn health_config_defaults() {
let config = HealthConfig::default();
assert!(config.enabled);
assert!(config.initial_serving);
}
}