1use std::sync::Arc;
22use tokio::sync::Mutex;
23
24use tonic_health::server::HealthReporter;
25
26#[derive(Clone)]
31pub struct HealthService {
32 reporter: Arc<Mutex<HealthReporter>>,
33}
34
35impl std::fmt::Debug for HealthService {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 f.debug_struct("HealthService").finish()
38 }
39}
40
41impl HealthService {
42 pub fn new() -> (Self, tonic_health::pb::health_server::HealthServer<impl tonic_health::pb::health_server::Health>) {
48 let (reporter, service) = tonic_health::server::health_reporter();
49 let wrapper = Self {
50 reporter: Arc::new(Mutex::new(reporter)),
51 };
52 (wrapper, service)
53 }
54
55 pub async fn set_serving(&self, service_name: &str) {
57 let mut reporter = self.reporter.lock().await;
58 reporter
59 .set_serving::<tonic_health::pb::health_server::HealthServer<
60 tonic_health::server::HealthService,
61 >>()
62 .await;
63 reporter
65 .set_service_status(service_name, tonic_health::ServingStatus::Serving)
66 .await;
67 }
68
69 pub async fn set_not_serving(&self, service_name: &str) {
71 let mut reporter = self.reporter.lock().await;
72 reporter
73 .set_service_status(service_name, tonic_health::ServingStatus::NotServing)
74 .await;
75 }
76
77 pub async fn set_unknown(&self, service_name: &str) {
79 let mut reporter = self.reporter.lock().await;
80 reporter
81 .set_service_status(service_name, tonic_health::ServingStatus::Unknown)
82 .await;
83 }
84
85 pub async fn set_all_serving(&self) {
87 self.set_all_status(tonic_health::ServingStatus::Serving).await;
88 }
89
90 pub async fn set_all_not_serving(&self) {
92 self.set_all_status(tonic_health::ServingStatus::NotServing).await;
93 }
94
95 async fn set_all_status(&self, status: tonic_health::ServingStatus) {
97 let mut reporter = self.reporter.lock().await;
98
99 if status == tonic_health::ServingStatus::Serving {
101 reporter
102 .set_serving::<tonic_health::pb::health_server::HealthServer<
103 tonic_health::server::HealthService,
104 >>()
105 .await;
106 }
107
108 for service in Self::xds_service_names() {
110 reporter.set_service_status(service, status).await;
111 }
112 }
113
114 #[inline]
119 pub const fn xds_service_names() -> &'static [&'static str] {
120 &[
121 "envoy.service.discovery.v3.AggregatedDiscoveryService",
122 "envoy.service.cluster.v3.ClusterDiscoveryService",
123 "envoy.service.listener.v3.ListenerDiscoveryService",
124 "envoy.service.route.v3.RouteDiscoveryService",
125 "envoy.service.endpoint.v3.EndpointDiscoveryService",
126 "envoy.service.secret.v3.SecretDiscoveryService",
127 "xds",
128 ]
129 }
130}
131
132#[derive(Debug, Clone)]
134pub struct HealthConfig {
135 pub enabled: bool,
137 pub initial_serving: bool,
139}
140
141impl Default for HealthConfig {
142 fn default() -> Self {
143 Self {
144 enabled: true,
145 initial_serving: true,
146 }
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[tokio::test]
155 async fn health_service_creation() {
156 let (health, _server) = HealthService::new();
157 health.set_all_serving().await;
159 }
160
161 #[tokio::test]
162 async fn health_service_status_transitions() {
163 let (health, _server) = HealthService::new();
164
165 health.set_serving("test-service").await;
166 health.set_not_serving("test-service").await;
167 health.set_unknown("test-service").await;
168 }
170
171 #[test]
172 fn health_config_defaults() {
173 let config = HealthConfig::default();
174 assert!(config.enabled);
175 assert!(config.initial_serving);
176 }
177}