xref: /tonic/tonic-health/src/server.rs (revision 171f4ddb)
1 //! Contains all healthcheck based server utilities.
2 
3 use crate::pb::health_server::{Health, HealthServer};
4 use crate::pb::{HealthCheckRequest, HealthCheckResponse};
5 use crate::ServingStatus;
6 use std::collections::HashMap;
7 use std::fmt;
8 use std::sync::Arc;
9 use tokio::sync::{watch, RwLock};
10 use tokio_stream::Stream;
11 use tonic::{server::NamedService, Request, Response, Status};
12 
13 /// Creates a `HealthReporter` and a linked `HealthServer` pair. Together,
14 /// these types can be used to serve the gRPC Health Checking service.
15 ///
16 /// A `HealthReporter` is used to update the state of gRPC services.
17 ///
18 /// A `HealthServer` is a Tonic gRPC server for the `grpc.health.v1.Health`,
19 /// which can be added to a Tonic runtime using `add_service` on the runtime
20 /// builder.
health_reporter() -> (HealthReporter, HealthServer<impl Health>)21 pub fn health_reporter() -> (HealthReporter, HealthServer<impl Health>) {
22     let reporter = HealthReporter::new();
23     let service = HealthService::new(reporter.statuses.clone());
24     let server = HealthServer::new(service);
25 
26     (reporter, server)
27 }
28 
29 type StatusPair = (watch::Sender<ServingStatus>, watch::Receiver<ServingStatus>);
30 
31 /// A handle providing methods to update the health status of gRPC services. A
32 /// `HealthReporter` is connected to a `HealthServer` which serves the statuses
33 /// over the `grpc.health.v1.Health` service.
34 #[derive(Clone, Debug)]
35 pub struct HealthReporter {
36     statuses: Arc<RwLock<HashMap<String, StatusPair>>>,
37 }
38 
39 impl HealthReporter {
new() -> Self40     fn new() -> Self {
41         // According to the gRPC Health Check specification, the empty service "" corresponds to the overall server health
42         let server_status = ("".to_string(), watch::channel(ServingStatus::Serving));
43 
44         let statuses = Arc::new(RwLock::new(HashMap::from([server_status])));
45 
46         HealthReporter { statuses }
47     }
48 
49     /// Sets the status of the service implemented by `S` to `Serving`. This notifies any watchers
50     /// if there is a change in status.
set_serving<S>(&mut self) where S: NamedService,51     pub async fn set_serving<S>(&mut self)
52     where
53         S: NamedService,
54     {
55         let service_name = <S as NamedService>::NAME;
56         self.set_service_status(service_name, ServingStatus::Serving)
57             .await;
58     }
59 
60     /// Sets the status of the service implemented by `S` to `NotServing`. This notifies any watchers
61     /// if there is a change in status.
set_not_serving<S>(&mut self) where S: NamedService,62     pub async fn set_not_serving<S>(&mut self)
63     where
64         S: NamedService,
65     {
66         let service_name = <S as NamedService>::NAME;
67         self.set_service_status(service_name, ServingStatus::NotServing)
68             .await;
69     }
70 
71     /// Sets the status of the service with `service_name` to `status`. This notifies any watchers
72     /// if there is a change in status.
set_service_status<S>(&mut self, service_name: S, status: ServingStatus) where S: AsRef<str>,73     pub async fn set_service_status<S>(&mut self, service_name: S, status: ServingStatus)
74     where
75         S: AsRef<str>,
76     {
77         let service_name = service_name.as_ref();
78         let mut writer = self.statuses.write().await;
79         match writer.get(service_name) {
80             Some((tx, _)) => {
81                 // We only ever hand out clones of the receiver, so the originally-created
82                 // receiver should always be present, only being dropped when clearing the
83                 // service status. Consequently, `tx.send` should not fail, making use
84                 // of `expect` here safe.
85                 tx.send(status).expect("channel should not be closed");
86             }
87             None => {
88                 writer.insert(service_name.to_string(), watch::channel(status));
89             }
90         };
91     }
92 
93     /// Clear the status of the given service.
clear_service_status(&mut self, service_name: &str)94     pub async fn clear_service_status(&mut self, service_name: &str) {
95         let mut writer = self.statuses.write().await;
96         let _ = writer.remove(service_name);
97     }
98 }
99 
100 /// A service providing implementations of gRPC health checking protocol.
101 #[derive(Debug)]
102 pub struct HealthService {
103     statuses: Arc<RwLock<HashMap<String, StatusPair>>>,
104 }
105 
106 impl HealthService {
new(services: Arc<RwLock<HashMap<String, StatusPair>>>) -> Self107     fn new(services: Arc<RwLock<HashMap<String, StatusPair>>>) -> Self {
108         HealthService { statuses: services }
109     }
110 
service_health(&self, service_name: &str) -> Option<ServingStatus>111     async fn service_health(&self, service_name: &str) -> Option<ServingStatus> {
112         let reader = self.statuses.read().await;
113         reader.get(service_name).map(|p| *p.1.borrow())
114     }
115 }
116 
117 #[tonic::async_trait]
118 impl Health for HealthService {
check( &self, request: Request<HealthCheckRequest>, ) -> Result<Response<HealthCheckResponse>, Status>119     async fn check(
120         &self,
121         request: Request<HealthCheckRequest>,
122     ) -> Result<Response<HealthCheckResponse>, Status> {
123         let service_name = request.get_ref().service.as_str();
124         let Some(status) = self.service_health(service_name).await else {
125             return Err(Status::not_found("service not registered"));
126         };
127 
128         Ok(Response::new(HealthCheckResponse::new(status)))
129     }
130 
131     type WatchStream = WatchStream;
132 
watch( &self, request: Request<HealthCheckRequest>, ) -> Result<Response<Self::WatchStream>, Status>133     async fn watch(
134         &self,
135         request: Request<HealthCheckRequest>,
136     ) -> Result<Response<Self::WatchStream>, Status> {
137         let service_name = request.get_ref().service.as_str();
138         let status_rx = match self.statuses.read().await.get(service_name) {
139             Some((_tx, rx)) => rx.clone(),
140             None => return Err(Status::not_found("service not registered")),
141         };
142 
143         Ok(Response::new(WatchStream::new(status_rx)))
144     }
145 }
146 
147 /// A watch stream for the health service.
148 pub struct WatchStream {
149     inner: tokio_stream::wrappers::WatchStream<ServingStatus>,
150 }
151 
152 impl WatchStream {
new(status_rx: watch::Receiver<ServingStatus>) -> Self153     fn new(status_rx: watch::Receiver<ServingStatus>) -> Self {
154         let inner = tokio_stream::wrappers::WatchStream::new(status_rx);
155         Self { inner }
156     }
157 }
158 
159 impl Stream for WatchStream {
160     type Item = Result<HealthCheckResponse, Status>;
161 
poll_next( mut self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll<Option<Self::Item>>162     fn poll_next(
163         mut self: std::pin::Pin<&mut Self>,
164         cx: &mut std::task::Context<'_>,
165     ) -> std::task::Poll<Option<Self::Item>> {
166         std::pin::Pin::new(&mut self.inner)
167             .poll_next(cx)
168             .map(|opt| opt.map(|status| Ok(HealthCheckResponse::new(status))))
169     }
170 }
171 
172 impl fmt::Debug for WatchStream {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result173     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174         f.debug_struct("WatchStream").finish()
175     }
176 }
177 
178 impl HealthCheckResponse {
new(status: ServingStatus) -> Self179     fn new(status: ServingStatus) -> Self {
180         let status = crate::pb::health_check_response::ServingStatus::from(status) as i32;
181         Self { status }
182     }
183 }
184 
185 #[cfg(test)]
186 mod tests {
187     use crate::pb::health_server::Health;
188     use crate::pb::HealthCheckRequest;
189     use crate::server::{HealthReporter, HealthService};
190     use crate::ServingStatus;
191     use tokio::sync::watch;
192     use tokio_stream::StreamExt;
193     use tonic::{Code, Request, Status};
194 
assert_serving_status(wire: i32, expected: ServingStatus)195     fn assert_serving_status(wire: i32, expected: ServingStatus) {
196         let expected = crate::pb::health_check_response::ServingStatus::from(expected) as i32;
197         assert_eq!(wire, expected);
198     }
199 
assert_grpc_status(wire: Option<Status>, expected: Code)200     fn assert_grpc_status(wire: Option<Status>, expected: Code) {
201         let wire = wire.expect("status is not None").code();
202         assert_eq!(wire, expected);
203     }
204 
make_test_service() -> (HealthReporter, HealthService)205     async fn make_test_service() -> (HealthReporter, HealthService) {
206         let health_reporter = HealthReporter::new();
207 
208         // insert test value
209         {
210             let mut statuses = health_reporter.statuses.write().await;
211             statuses.insert(
212                 "TestService".to_string(),
213                 watch::channel(ServingStatus::Unknown),
214             );
215         }
216 
217         let health_service = HealthService::new(health_reporter.statuses.clone());
218         (health_reporter, health_service)
219     }
220 
221     #[tokio::test]
test_service_check()222     async fn test_service_check() {
223         let (mut reporter, service) = make_test_service().await;
224 
225         // Overall server health
226         let resp = service
227             .check(Request::new(HealthCheckRequest {
228                 service: "".to_string(),
229             }))
230             .await;
231         assert!(resp.is_ok());
232         let resp = resp.unwrap().into_inner();
233         assert_serving_status(resp.status, ServingStatus::Serving);
234 
235         // Unregistered service
236         let resp = service
237             .check(Request::new(HealthCheckRequest {
238                 service: "Unregistered".to_string(),
239             }))
240             .await;
241         assert!(resp.is_err());
242         assert_grpc_status(resp.err(), Code::NotFound);
243 
244         // Registered service - initial state
245         let resp = service
246             .check(Request::new(HealthCheckRequest {
247                 service: "TestService".to_string(),
248             }))
249             .await;
250         assert!(resp.is_ok());
251         let resp = resp.unwrap().into_inner();
252         assert_serving_status(resp.status, ServingStatus::Unknown);
253 
254         // Registered service - updated state
255         reporter
256             .set_service_status("TestService", ServingStatus::Serving)
257             .await;
258         let resp = service
259             .check(Request::new(HealthCheckRequest {
260                 service: "TestService".to_string(),
261             }))
262             .await;
263         assert!(resp.is_ok());
264         let resp = resp.unwrap().into_inner();
265         assert_serving_status(resp.status, ServingStatus::Serving);
266     }
267 
268     #[tokio::test]
test_service_watch()269     async fn test_service_watch() {
270         let (mut reporter, service) = make_test_service().await;
271 
272         // Overall server health
273         let resp = service
274             .watch(Request::new(HealthCheckRequest {
275                 service: "".to_string(),
276             }))
277             .await;
278         assert!(resp.is_ok());
279         let mut resp = resp.unwrap().into_inner();
280         let item = resp
281             .next()
282             .await
283             .expect("streamed response is Some")
284             .expect("response is ok");
285         assert_serving_status(item.status, ServingStatus::Serving);
286 
287         // Unregistered service
288         let resp = service
289             .watch(Request::new(HealthCheckRequest {
290                 service: "Unregistered".to_string(),
291             }))
292             .await;
293         assert!(resp.is_err());
294         assert_grpc_status(resp.err(), Code::NotFound);
295 
296         // Registered service
297         let resp = service
298             .watch(Request::new(HealthCheckRequest {
299                 service: "TestService".to_string(),
300             }))
301             .await;
302         assert!(resp.is_ok());
303         let mut resp = resp.unwrap().into_inner();
304 
305         // Registered service - initial state
306         let item = resp
307             .next()
308             .await
309             .expect("streamed response is Some")
310             .expect("response is ok");
311         assert_serving_status(item.status, ServingStatus::Unknown);
312 
313         // Registered service - updated state
314         reporter
315             .set_service_status("TestService", ServingStatus::NotServing)
316             .await;
317 
318         let item = resp
319             .next()
320             .await
321             .expect("streamed response is Some")
322             .expect("response is ok");
323         assert_serving_status(item.status, ServingStatus::NotServing);
324 
325         // Registered service - updated state
326         reporter
327             .set_service_status("TestService", ServingStatus::Serving)
328             .await;
329         let item = resp
330             .next()
331             .await
332             .expect("streamed response is Some")
333             .expect("response is ok");
334         assert_serving_status(item.status, ServingStatus::Serving);
335 
336         // De-registered service
337         reporter.clear_service_status("TestService").await;
338         let item = resp.next().await;
339         assert!(item.is_none());
340     }
341 }
342