#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(unsafe_code)]
#![warn(missing_docs)]
#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used, clippy::panic))]
mod builder;
mod config;
pub mod connections;
mod delta;
pub mod health;
pub mod metrics;
pub mod reflection;
pub mod shutdown;
mod sotw;
mod stream;
pub mod streaming;
pub mod utils;
#[cfg(test)]
mod protocol_tests;
pub mod services;
pub use builder::XdsServerBuilder;
pub use config::ServerConfig;
pub use connections::{ConnectionGuard, ConnectionLimits, ConnectionTracker};
pub use health::{HealthConfig, HealthService};
pub use metrics::XdsMetrics;
#[cfg(feature = "reflection")]
pub use reflection::{ReflectionConfig, ReflectionService};
pub use shutdown::{ShutdownConfig, ShutdownController};
pub use stream::{StreamContext, StreamId};
#[cfg(feature = "tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
pub use tonic::transport::{Identity as TlsIdentity, ServerTlsConfig};
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::sync::oneshot;
use tonic::transport::Server;
use tracing::info;
use xds_cache::ShardedCache;
use xds_core::ResourceRegistry;
use crate::health::HealthService as HealthSvc;
use crate::services::ServiceState;
#[derive(Debug)]
pub struct XdsServer {
cache: Arc<ShardedCache>,
registry: Arc<ResourceRegistry>,
config: ServerConfig,
metrics: Option<XdsMetrics>,
shutdown: ShutdownController,
connections: Option<ConnectionTracker>,
#[cfg(feature = "tls")]
tls_config: Option<tonic::transport::ServerTlsConfig>,
}
impl XdsServer {
#[must_use = "builder is unused unless `.build()` is called"]
pub fn builder() -> XdsServerBuilder {
XdsServerBuilder::new()
}
#[inline]
pub fn cache(&self) -> &Arc<ShardedCache> {
&self.cache
}
#[inline]
pub fn registry(&self) -> &Arc<ResourceRegistry> {
&self.registry
}
#[inline]
pub fn config(&self) -> &ServerConfig {
&self.config
}
#[inline]
pub fn metrics(&self) -> Option<&XdsMetrics> {
self.metrics.as_ref()
}
#[inline]
pub fn shutdown_controller(&self) -> &ShutdownController {
&self.shutdown
}
#[inline]
pub fn connections(&self) -> Option<&ConnectionTracker> {
self.connections.as_ref()
}
pub fn service_state(&self) -> ServiceState {
ServiceState::new(
Arc::clone(&self.cache),
Arc::clone(&self.registry),
self.config.clone(),
)
}
async fn build_router(
&self,
) -> Result<(tonic::transport::server::Router, Option<HealthSvc>), tonic::transport::Error>
{
let state = self.service_state();
let (ads, cds, lds, rds, eds, sds) = state.create_services();
let mut builder = Server::builder();
if let Some(interval) = self.config.keepalive_interval {
builder = builder.http2_keepalive_interval(Some(interval));
}
if let Some(timeout) = self.config.keepalive_timeout {
builder = builder.http2_keepalive_timeout(Some(timeout));
}
if let Some(max_streams) = self.config.max_concurrent_streams {
builder = builder.concurrency_limit_per_connection(max_streams as usize);
}
#[cfg(feature = "tls")]
if let Some(ref tls) = self.tls_config {
builder = builder.tls_config(tls.clone())?;
}
let mut router = builder
.add_service(ads.into_service())
.add_service(cds.into_service())
.add_service(lds.into_service())
.add_service(rds.into_service())
.add_service(eds.into_service())
.add_service(sds.into_service());
let health = if self.config.enable_health {
let (health, health_svc) = HealthSvc::new();
router = router.add_service(health_svc);
health.set_all_serving().await;
Some(health)
} else {
None
};
Ok((router, health))
}
pub async fn serve(self, addr: SocketAddr) -> Result<(), tonic::transport::Error> {
info!(addr = %addr, "starting xDS server");
let (router, health) = self.build_router().await?;
let grace_period = self.config.grace_period;
let serve_future = router.serve_with_shutdown(addr, async move {
shutdown::wait_for_signal().await;
if let Some(ref health) = health {
health.set_all_not_serving().await;
}
info!(grace_period = ?grace_period, "draining connections");
tokio::time::sleep(grace_period).await;
});
info!(addr = %addr, "xDS server listening");
serve_future.await
}
pub async fn serve_with_shutdown(
self,
addr: SocketAddr,
shutdown_rx: oneshot::Receiver<()>,
) -> Result<(), tonic::transport::Error> {
info!(addr = %addr, "starting xDS server with custom shutdown");
let (router, health) = self.build_router().await?;
let grace_period = self.config.grace_period;
let serve_future = router.serve_with_shutdown(addr, async move {
let _ = shutdown_rx.await;
if let Some(ref health) = health {
health.set_all_not_serving().await;
}
info!(grace_period = ?grace_period, "draining connections");
tokio::time::sleep(grace_period).await;
});
info!(addr = %addr, "xDS server listening");
serve_future.await
}
}