use std::sync::Arc;
use std::time::Duration;
use xds_cache::ShardedCache;
use xds_core::{ResourceRegistry, XdsError, XdsResult};
use crate::config::{CompressionConfig, ServerConfig};
use crate::connections::{ConnectionLimits, ConnectionTracker};
use crate::metrics::XdsMetrics;
use crate::shutdown::ShutdownController;
use crate::XdsServer;
#[must_use = "builder is unused unless `.build()` is called"]
#[derive(Debug, Default)]
pub struct XdsServerBuilder {
cache: Option<Arc<ShardedCache>>,
registry: Option<Arc<ResourceRegistry>>,
enable_sotw: bool,
enable_delta: bool,
max_concurrent_streams: Option<u32>,
keepalive_interval: Option<Duration>,
keepalive_timeout: Option<Duration>,
max_request_size: Option<usize>,
compression: Option<CompressionConfig>,
enable_health: bool,
enable_metrics: bool,
enable_connection_tracking: bool,
grace_period: Option<Duration>,
connection_limits: Option<ConnectionLimits>,
#[cfg(feature = "tls")]
tls_config: Option<tonic::transport::ServerTlsConfig>,
}
impl XdsServerBuilder {
pub fn new() -> Self {
Self {
enable_sotw: true, enable_health: true, enable_metrics: true, enable_connection_tracking: true, ..Default::default()
}
}
pub fn cache(mut self, cache: Arc<ShardedCache>) -> Self {
self.cache = Some(cache);
self
}
pub fn registry(mut self, registry: Arc<ResourceRegistry>) -> Self {
self.registry = Some(registry);
self
}
pub fn enable_sotw(mut self) -> Self {
self.enable_sotw = true;
self
}
pub fn disable_sotw(mut self) -> Self {
self.enable_sotw = false;
self
}
pub fn enable_delta(mut self) -> Self {
self.enable_delta = true;
self
}
pub fn disable_delta(mut self) -> Self {
self.enable_delta = false;
self
}
pub fn max_concurrent_streams(mut self, max: u32) -> Self {
self.max_concurrent_streams = Some(max);
self
}
pub fn keepalive_interval(mut self, interval: Duration) -> Self {
self.keepalive_interval = Some(interval);
self
}
pub fn keepalive_timeout(mut self, timeout: Duration) -> Self {
self.keepalive_timeout = Some(timeout);
self
}
pub fn max_request_size(mut self, size: usize) -> Self {
self.max_request_size = Some(size);
self
}
pub fn enable_compression(mut self) -> Self {
self.compression = Some(CompressionConfig::gzip());
self
}
pub fn compression(mut self, config: CompressionConfig) -> Self {
self.compression = Some(config);
self
}
pub fn enable_health_check(mut self) -> Self {
self.enable_health = true;
self
}
pub fn disable_health_check(mut self) -> Self {
self.enable_health = false;
self
}
pub fn enable_metrics(mut self) -> Self {
self.enable_metrics = true;
self
}
pub fn disable_metrics(mut self) -> Self {
self.enable_metrics = false;
self
}
pub fn enable_connection_tracking(mut self) -> Self {
self.enable_connection_tracking = true;
self
}
pub fn disable_connection_tracking(mut self) -> Self {
self.enable_connection_tracking = false;
self
}
pub fn graceful_shutdown(mut self, grace_period: Duration) -> Self {
self.grace_period = Some(grace_period);
self
}
pub fn connection_limits(mut self, limits: ConnectionLimits) -> Self {
self.connection_limits = Some(limits);
self
}
pub fn max_connections(mut self, max: u64) -> Self {
let limits = self.connection_limits.get_or_insert_with(ConnectionLimits::default);
limits.max_connections = max;
self
}
pub fn max_connections_per_ip(mut self, max: u64) -> Self {
let limits = self.connection_limits.get_or_insert_with(ConnectionLimits::default);
limits.max_per_ip = max;
self
}
#[cfg(feature = "tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
pub fn tls_config(mut self, config: tonic::transport::ServerTlsConfig) -> Self {
self.tls_config = Some(config);
self
}
pub fn build(self) -> XdsResult<XdsServer> {
let cache = self
.cache
.ok_or_else(|| XdsError::Configuration("cache is required".into()))?;
if !self.enable_sotw && !self.enable_delta {
return Err(XdsError::Configuration(
"at least one protocol (SotW or Delta) must be enabled".into(),
));
}
let registry = self
.registry
.unwrap_or_else(|| Arc::new(ResourceRegistry::new()));
let config = ServerConfig {
enable_sotw: self.enable_sotw,
enable_delta: self.enable_delta,
max_concurrent_streams: self.max_concurrent_streams.or(Some(100)),
keepalive_interval: self.keepalive_interval.or(Some(Duration::from_secs(30))),
keepalive_timeout: self.keepalive_timeout.or(Some(Duration::from_secs(10))),
max_request_size: self.max_request_size.unwrap_or(4 * 1024 * 1024),
compression: self.compression.unwrap_or_default(),
grace_period: self.grace_period.unwrap_or(Duration::from_secs(30)),
enable_health: self.enable_health,
enable_metrics: self.enable_metrics,
enable_connection_tracking: self.enable_connection_tracking,
};
let metrics = if self.enable_metrics {
Some(XdsMetrics::new())
} else {
None
};
let connections = if self.enable_connection_tracking {
let limits = self.connection_limits.unwrap_or_default();
Some(ConnectionTracker::new(limits))
} else {
None
};
let shutdown = ShutdownController::new();
Ok(XdsServer {
cache,
registry,
config,
metrics,
shutdown,
connections,
#[cfg(feature = "tls")]
tls_config: self.tls_config,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builder_requires_cache() {
let result = XdsServerBuilder::new().build();
assert!(result.is_err());
}
#[test]
fn builder_requires_protocol() {
let cache = Arc::new(ShardedCache::new());
let result = XdsServerBuilder::new()
.cache(cache)
.disable_sotw()
.disable_delta()
.build();
assert!(result.is_err());
}
#[test]
fn builder_success() {
let cache = Arc::new(ShardedCache::new());
let server = XdsServerBuilder::new()
.cache(cache)
.enable_sotw()
.enable_delta()
.max_concurrent_streams(200)
.build()
.expect("server should build successfully");
assert!(server.config().enable_sotw);
assert!(server.config().enable_delta);
assert_eq!(server.config().max_concurrent_streams, Some(200));
}
#[cfg(feature = "tls")]
#[test]
fn builder_accepts_tls_config() {
use tonic::transport::ServerTlsConfig;
let cache = Arc::new(ShardedCache::new());
let tls = ServerTlsConfig::new();
let server = XdsServerBuilder::new()
.cache(cache)
.tls_config(tls)
.build()
.expect("server should build with TLS config");
assert!(server.config().enable_sotw);
}
}