mod circuit_breaker;
mod instance;
mod lb;
mod outlier;
mod registry;
use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
pub use instance::UpstreamInstance;
pub use lb::{HashInput, HashKeySource, Pick};
pub use registry::UpstreamRegistry;
use crate::maglev::MaglevTable;
use crate::manifest::{HealthConfig, LbAlgorithm};
use lb::LbState;
fn now_millis() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
}
#[derive(Debug)]
pub struct Endpoints {
pub instances: Vec<Arc<UpstreamInstance>>,
pub(in crate::upstream) lb: LbState,
}
impl Endpoints {
pub(super) fn build(
instances: Vec<Arc<UpstreamInstance>>,
algorithm: LbAlgorithm,
maglev_table_size: usize,
) -> Self {
let lb = match algorithm {
LbAlgorithm::RoundRobin => LbState::RoundRobin,
LbAlgorithm::LeastRequest => LbState::LeastRequest,
LbAlgorithm::Maglev => {
let entries: Vec<(&str, u32)> = instances
.iter()
.map(|i| (i.address(), i.weight()))
.collect();
LbState::Maglev(MaglevTable::build(&entries, maglev_table_size))
}
};
Self { instances, lb }
}
}
#[derive(Debug)]
pub struct UpstreamGroup {
pub name: String,
pub health: HealthConfig,
endpoints: arc_swap::ArcSwap<Endpoints>,
configured: Vec<(String, u32)>,
resolve_interval: Duration,
lb_algorithm: LbAlgorithm,
maglev_table_size: usize,
request_timeout: Duration,
overall_timeout: Duration,
max_retries: u64,
rr: AtomicUsize,
max_requests: usize,
in_flight: AtomicUsize,
outlier_consecutive: u32,
outlier_base_ejection: Duration,
outlier_max_ejection_percent: u32,
outlier_decision: std::sync::Mutex<()>,
hash_key: Option<HashKeySource>,
tls_manifest: Option<crate::manifest::UpstreamTls>,
tls_client: Option<Arc<rustls::ClientConfig>>,
tls_sni: Option<rustls::pki_types::ServerName<'static>>,
}
impl UpstreamGroup {
pub fn request_timeout(&self) -> Duration {
self.request_timeout
}
pub fn overall_timeout(&self) -> Duration {
self.overall_timeout
}
pub fn max_retries(&self) -> u64 {
self.max_retries
}
pub fn hash_key_source(&self) -> Option<&HashKeySource> {
self.hash_key.as_ref()
}
pub fn scheme(&self) -> &'static str {
if self.tls_client.is_some() {
"https"
} else {
"http"
}
}
pub fn tls_client_config(&self) -> Option<&Arc<rustls::ClientConfig>> {
self.tls_client.as_ref()
}
pub fn tls_sni(&self) -> Option<&rustls::pki_types::ServerName<'static>> {
self.tls_sni.as_ref()
}
pub fn endpoints(&self) -> Arc<Endpoints> {
self.endpoints.load_full()
}
pub fn resolve_interval(&self) -> Option<Duration> {
(!self.resolve_interval.is_zero()).then_some(self.resolve_interval)
}
pub fn configured_addresses(&self) -> &[(String, u32)] {
&self.configured
}
pub fn update_endpoints(&self, resolved: &[(String, u32)]) -> bool {
if resolved.is_empty() {
tracing::warn!(
upstream = %self.name,
"update_endpoints called with an empty set; keeping the current endpoints"
);
return false;
}
let current = self.endpoints.load();
let unchanged = current.instances.len() == resolved.len()
&& current
.instances
.iter()
.zip(resolved)
.all(|(inst, (addr, weight))| inst.address() == addr && inst.weight() == *weight);
if unchanged {
return false;
}
let instances: Vec<Arc<UpstreamInstance>> = resolved
.iter()
.map(|(addr, weight)| {
current
.instances
.iter()
.find(|i| i.address() == addr && i.weight() == *weight)
.cloned()
.unwrap_or_else(|| {
Arc::new(UpstreamInstance::new(addr.clone(), *weight, &self.health))
})
})
.collect();
self.endpoints.store(Arc::new(Endpoints::build(
instances,
self.lb_algorithm,
self.maglev_table_size,
)));
true
}
}
impl crate::Control {
pub fn upstream_groups(&self) -> Vec<Arc<UpstreamGroup>> {
self.upstreams.groups()
}
}