xds_server/lib.rs
1//! # xds-server
2//!
3//! xDS gRPC server implementation for control planes.
4//!
5//! This crate provides the gRPC server layer for xDS:
6//!
7//! - [`XdsServer`] - Main server type wrapping all xDS services
8//! - [`XdsServerBuilder`] - Builder for configuring the server
9//! - State-of-the-World (SotW) protocol support
10//! - Delta xDS protocol support (incremental updates)
11//! - Health checking via gRPC health protocol
12//! - Prometheus metrics for observability
13//! - Graceful shutdown with connection draining
14//! - Connection tracking and limits
15//!
16//! ## Example
17//!
18//! ```rust,no_run
19//! use xds_server::XdsServerBuilder;
20//! use xds_cache::ShardedCache;
21//! use std::sync::Arc;
22//!
23//! # async fn run() -> Result<(), Box<dyn std::error::Error>> {
24//! let cache = Arc::new(ShardedCache::new());
25//! let server = XdsServerBuilder::new()
26//! .cache(cache)
27//! .enable_sotw()
28//! .enable_delta()
29//! .enable_health_check()
30//! .enable_metrics()
31//! .build()?;
32//!
33//! // Use with tonic - includes health and metrics
34//! server.serve("[::]:18000".parse()?).await?;
35//! # Ok(())
36//! # }
37//! ```
38//!
39//! ## Production Features
40//!
41//! ### Health Checking
42//!
43//! The server implements the gRPC health checking protocol:
44//!
45//! ```rust,no_run
46//! # use xds_server::XdsServerBuilder;
47//! # use xds_cache::ShardedCache;
48//! # use std::sync::Arc;
49//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
50//! # let cache = Arc::new(ShardedCache::new());
51//! let _server = XdsServerBuilder::new()
52//! .cache(cache)
53//! .enable_health_check()
54//! .build()?;
55//!
56//! // Health status is managed automatically
57//! // Available at grpc.health.v1.Health/Check
58//! # Ok(()) }
59//! ```
60//!
61//! ### Metrics
62//!
63//! Prometheus metrics are exposed for monitoring:
64//!
65//! ```rust,no_run
66//! # use xds_server::XdsServerBuilder;
67//! # use xds_cache::ShardedCache;
68//! # use std::sync::Arc;
69//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
70//! # let cache = Arc::new(ShardedCache::new());
71//! let _server = XdsServerBuilder::new()
72//! .cache(cache)
73//! .enable_metrics()
74//! .build()?;
75//! # Ok(()) }
76//! ```
77//!
78//! ### Graceful Shutdown
79//!
80//! The server supports graceful shutdown with connection draining:
81//!
82//! ```rust,no_run
83//! # use xds_server::XdsServerBuilder;
84//! # use xds_cache::ShardedCache;
85//! # use std::sync::Arc;
86//! use std::time::Duration;
87//!
88//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
89//! # let cache = Arc::new(ShardedCache::new());
90//! let _server = XdsServerBuilder::new()
91//! .cache(cache)
92//! .graceful_shutdown(Duration::from_secs(30))
93//! .build()?;
94//!
95//! // Server will drain connections on SIGTERM/SIGINT
96//! # Ok(()) }
97//! ```
98
99#![cfg_attr(docsrs, feature(doc_cfg))]
100#![deny(unsafe_code)]
101#![warn(missing_docs)]
102// Tests are allowed to use .unwrap() / .expect() / panic! freely; the
103// production-only warnings still apply.
104#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used, clippy::panic))]
105
106mod builder;
107mod config;
108pub mod connections;
109mod delta;
110pub mod health;
111pub mod metrics;
112pub mod reflection;
113pub mod shutdown;
114mod sotw;
115mod stream;
116pub mod streaming;
117pub mod utils;
118
119#[cfg(test)]
120mod protocol_tests;
121
122// Re-export service modules
123pub mod services;
124
125pub use builder::XdsServerBuilder;
126pub use config::ServerConfig;
127pub use connections::{ConnectionGuard, ConnectionLimits, ConnectionTracker};
128pub use health::{HealthConfig, HealthService};
129pub use metrics::XdsMetrics;
130#[cfg(feature = "reflection")]
131pub use reflection::{ReflectionConfig, ReflectionService};
132pub use shutdown::{ShutdownConfig, ShutdownController};
133pub use stream::{StreamContext, StreamId};
134
135#[cfg(feature = "tls")]
136#[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
137pub use tonic::transport::{Identity as TlsIdentity, ServerTlsConfig};
138
139use std::net::SocketAddr;
140use std::sync::Arc;
141
142use tokio::sync::oneshot;
143use tonic::transport::Server;
144use tracing::info;
145use xds_cache::ShardedCache;
146use xds_core::ResourceRegistry;
147
148use crate::health::HealthService as HealthSvc;
149use crate::services::ServiceState;
150
151/// The main xDS server.
152///
153/// This server wraps the cache and provides gRPC services for xDS.
154/// It includes production-ready features like health checking,
155/// metrics, graceful shutdown, and connection tracking.
156#[derive(Debug)]
157pub struct XdsServer {
158 /// Shared cache.
159 cache: Arc<ShardedCache>,
160 /// Resource registry.
161 registry: Arc<ResourceRegistry>,
162 /// Server configuration.
163 config: ServerConfig,
164 /// Metrics collector.
165 metrics: Option<XdsMetrics>,
166 /// Shutdown controller.
167 shutdown: ShutdownController,
168 /// Connection tracker.
169 connections: Option<ConnectionTracker>,
170 /// Optional TLS configuration applied to the gRPC transport.
171 #[cfg(feature = "tls")]
172 tls_config: Option<tonic::transport::ServerTlsConfig>,
173}
174
175impl XdsServer {
176 /// Create a new builder for configuring the server.
177 #[must_use = "builder is unused unless `.build()` is called"]
178 pub fn builder() -> XdsServerBuilder {
179 XdsServerBuilder::new()
180 }
181
182 /// Get a reference to the cache.
183 #[inline]
184 pub fn cache(&self) -> &Arc<ShardedCache> {
185 &self.cache
186 }
187
188 /// Get a reference to the resource registry.
189 #[inline]
190 pub fn registry(&self) -> &Arc<ResourceRegistry> {
191 &self.registry
192 }
193
194 /// Get the server configuration.
195 #[inline]
196 pub fn config(&self) -> &ServerConfig {
197 &self.config
198 }
199
200 /// Get the metrics instance, if enabled.
201 #[inline]
202 pub fn metrics(&self) -> Option<&XdsMetrics> {
203 self.metrics.as_ref()
204 }
205
206 /// Get the shutdown controller.
207 #[inline]
208 pub fn shutdown_controller(&self) -> &ShutdownController {
209 &self.shutdown
210 }
211
212 /// Get the connection tracker, if enabled.
213 #[inline]
214 pub fn connections(&self) -> Option<&ConnectionTracker> {
215 self.connections.as_ref()
216 }
217
218 /// Create the service state for all discovery services.
219 pub fn service_state(&self) -> ServiceState {
220 ServiceState::new(
221 Arc::clone(&self.cache),
222 Arc::clone(&self.registry),
223 self.config.clone(),
224 )
225 }
226
227 /// Build the router with all xDS services configured.
228 ///
229 /// This is an internal helper to reduce duplication between serve methods.
230 async fn build_router(
231 &self,
232 ) -> Result<(tonic::transport::server::Router, Option<HealthSvc>), tonic::transport::Error>
233 {
234 let state = self.service_state();
235 let (ads, cds, lds, rds, eds, sds) = state.create_services();
236
237 // Build the server with configured options
238 let mut builder = Server::builder();
239
240 if let Some(interval) = self.config.keepalive_interval {
241 builder = builder.http2_keepalive_interval(Some(interval));
242 }
243 if let Some(timeout) = self.config.keepalive_timeout {
244 builder = builder.http2_keepalive_timeout(Some(timeout));
245 }
246 if let Some(max_streams) = self.config.max_concurrent_streams {
247 builder = builder.concurrency_limit_per_connection(max_streams as usize);
248 }
249
250 #[cfg(feature = "tls")]
251 if let Some(ref tls) = self.tls_config {
252 builder = builder.tls_config(tls.clone())?;
253 }
254
255 // Add all xDS services
256 let mut router = builder
257 .add_service(ads.into_service())
258 .add_service(cds.into_service())
259 .add_service(lds.into_service())
260 .add_service(rds.into_service())
261 .add_service(eds.into_service())
262 .add_service(sds.into_service());
263
264 // Add health service if enabled
265 let health = if self.config.enable_health {
266 let (health, health_svc) = HealthSvc::new();
267 router = router.add_service(health_svc);
268 health.set_all_serving().await;
269 Some(health)
270 } else {
271 None
272 };
273
274 Ok((router, health))
275 }
276
277 /// Start the server and listen on the given address.
278 ///
279 /// This method will:
280 /// 1. Set up health checking (if enabled)
281 /// 2. Register all xDS services
282 /// 3. Handle graceful shutdown on SIGTERM/SIGINT
283 ///
284 /// # Example
285 ///
286 /// ```rust,no_run
287 /// # use xds_server::XdsServerBuilder;
288 /// # use xds_cache::ShardedCache;
289 /// # use std::sync::Arc;
290 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
291 /// # let cache = Arc::new(ShardedCache::new());
292 /// let server = XdsServerBuilder::new()
293 /// .cache(cache)
294 /// .build()?;
295 ///
296 /// server.serve("[::]:18000".parse()?).await?;
297 /// # Ok(()) }
298 /// ```
299 pub async fn serve(self, addr: SocketAddr) -> Result<(), tonic::transport::Error> {
300 info!(addr = %addr, "starting xDS server");
301
302 let (router, health) = self.build_router().await?;
303 let grace_period = self.config.grace_period;
304
305 let serve_future = router.serve_with_shutdown(addr, async move {
306 // Wait for shutdown signal
307 shutdown::wait_for_signal().await;
308
309 // Mark services as not serving (for load balancer drain)
310 if let Some(ref health) = health {
311 health.set_all_not_serving().await;
312 }
313
314 // Wait for grace period
315 info!(grace_period = ?grace_period, "draining connections");
316 tokio::time::sleep(grace_period).await;
317 });
318
319 info!(addr = %addr, "xDS server listening");
320 serve_future.await
321 }
322
323 /// Start the server with a custom shutdown signal.
324 ///
325 /// This allows you to control shutdown programmatically.
326 pub async fn serve_with_shutdown(
327 self,
328 addr: SocketAddr,
329 shutdown_rx: oneshot::Receiver<()>,
330 ) -> Result<(), tonic::transport::Error> {
331 info!(addr = %addr, "starting xDS server with custom shutdown");
332
333 let (router, health) = self.build_router().await?;
334 let grace_period = self.config.grace_period;
335
336 let serve_future = router.serve_with_shutdown(addr, async move {
337 let _ = shutdown_rx.await;
338
339 if let Some(ref health) = health {
340 health.set_all_not_serving().await;
341 }
342
343 info!(grace_period = ?grace_period, "draining connections");
344 tokio::time::sleep(grace_period).await;
345 });
346
347 info!(addr = %addr, "xDS server listening");
348 serve_future.await
349 }
350}