mirror of
https://github.com/telemt/telemt.git
synced 2026-04-18 11:04:09 +03:00
ME Pool Hardswap
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
use std::net::IpAddr;
|
||||
use std::collections::HashMap;
|
||||
use ipnetwork::IpNetwork;
|
||||
use serde::Deserialize;
|
||||
@@ -172,11 +171,23 @@ pub(crate) fn default_proxy_config_reload_secs() -> u64 {
|
||||
}
|
||||
|
||||
pub(crate) fn default_update_every_secs() -> u64 {
|
||||
2 * 60 * 60
|
||||
12 * 60 * 60
|
||||
}
|
||||
|
||||
pub(crate) fn default_me_reinit_drain_timeout_secs() -> u64 {
|
||||
300
|
||||
120
|
||||
}
|
||||
|
||||
pub(crate) fn default_me_pool_drain_ttl_secs() -> u64 {
|
||||
90
|
||||
}
|
||||
|
||||
pub(crate) fn default_me_pool_min_fresh_ratio() -> f32 {
|
||||
0.8
|
||||
}
|
||||
|
||||
pub(crate) fn default_hardswap() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub(crate) fn default_ntp_check() -> bool {
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
//! | `general` | `me_keepalive_*` | Passed on next connection |
|
||||
//! | `general` | `desync_all_full` | Applied immediately |
|
||||
//! | `general` | `update_every` | Applied to ME updater immediately |
|
||||
//! | `general` | `hardswap` | Applied on next ME map update |
|
||||
//! | `general` | `me_pool_drain_ttl_secs` | Applied on next ME map update |
|
||||
//! | `general` | `me_pool_min_fresh_ratio` | Applied on next ME map update |
|
||||
//! | `general` | `me_reinit_drain_timeout_secs`| Applied on next ME map update |
|
||||
//! | `access` | All user/quota fields | Effective immediately |
|
||||
//!
|
||||
@@ -39,6 +42,9 @@ pub struct HotFields {
|
||||
pub middle_proxy_pool_size: usize,
|
||||
pub desync_all_full: bool,
|
||||
pub update_every_secs: u64,
|
||||
pub hardswap: bool,
|
||||
pub me_pool_drain_ttl_secs: u64,
|
||||
pub me_pool_min_fresh_ratio: f32,
|
||||
pub me_reinit_drain_timeout_secs: u64,
|
||||
pub me_keepalive_enabled: bool,
|
||||
pub me_keepalive_interval_secs: u64,
|
||||
@@ -55,6 +61,9 @@ impl HotFields {
|
||||
middle_proxy_pool_size: cfg.general.middle_proxy_pool_size,
|
||||
desync_all_full: cfg.general.desync_all_full,
|
||||
update_every_secs: cfg.general.effective_update_every_secs(),
|
||||
hardswap: cfg.general.hardswap,
|
||||
me_pool_drain_ttl_secs: cfg.general.me_pool_drain_ttl_secs,
|
||||
me_pool_min_fresh_ratio: cfg.general.me_pool_min_fresh_ratio,
|
||||
me_reinit_drain_timeout_secs: cfg.general.me_reinit_drain_timeout_secs,
|
||||
me_keepalive_enabled: cfg.general.me_keepalive_enabled,
|
||||
me_keepalive_interval_secs: cfg.general.me_keepalive_interval_secs,
|
||||
@@ -198,6 +207,27 @@ fn log_changes(
|
||||
);
|
||||
}
|
||||
|
||||
if old_hot.hardswap != new_hot.hardswap {
|
||||
info!(
|
||||
"config reload: hardswap: {} → {}",
|
||||
old_hot.hardswap, new_hot.hardswap,
|
||||
);
|
||||
}
|
||||
|
||||
if old_hot.me_pool_drain_ttl_secs != new_hot.me_pool_drain_ttl_secs {
|
||||
info!(
|
||||
"config reload: me_pool_drain_ttl_secs: {}s → {}s",
|
||||
old_hot.me_pool_drain_ttl_secs, new_hot.me_pool_drain_ttl_secs,
|
||||
);
|
||||
}
|
||||
|
||||
if (old_hot.me_pool_min_fresh_ratio - new_hot.me_pool_min_fresh_ratio).abs() > f32::EPSILON {
|
||||
info!(
|
||||
"config reload: me_pool_min_fresh_ratio: {:.3} → {:.3}",
|
||||
old_hot.me_pool_min_fresh_ratio, new_hot.me_pool_min_fresh_ratio,
|
||||
);
|
||||
}
|
||||
|
||||
if old_hot.me_reinit_drain_timeout_secs != new_hot.me_reinit_drain_timeout_secs {
|
||||
info!(
|
||||
"config reload: me_reinit_drain_timeout_secs: {}s → {}s",
|
||||
|
||||
@@ -145,6 +145,24 @@ impl ProxyConfig {
|
||||
}
|
||||
}
|
||||
|
||||
if !(0.0..=1.0).contains(&config.general.me_pool_min_fresh_ratio) {
|
||||
return Err(ProxyError::Config(
|
||||
"general.me_pool_min_fresh_ratio must be within [0.0, 1.0]".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if config.general.effective_me_pool_force_close_secs() > 0
|
||||
&& config.general.effective_me_pool_force_close_secs()
|
||||
< config.general.me_pool_drain_ttl_secs
|
||||
{
|
||||
warn!(
|
||||
me_pool_drain_ttl_secs = config.general.me_pool_drain_ttl_secs,
|
||||
me_reinit_drain_timeout_secs = config.general.effective_me_pool_force_close_secs(),
|
||||
"force-close timeout is lower than drain TTL; bumping force-close timeout to TTL"
|
||||
);
|
||||
config.general.me_reinit_drain_timeout_secs = config.general.me_pool_drain_ttl_secs;
|
||||
}
|
||||
|
||||
// Validate secrets.
|
||||
for (user, secret) in &config.access.users {
|
||||
if !secret.chars().all(|c| c.is_ascii_hexdigit()) || secret.len() != 32 {
|
||||
@@ -439,4 +457,45 @@ mod tests {
|
||||
assert!(err.contains("general.update_every must be > 0"));
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn me_pool_min_fresh_ratio_out_of_range_is_rejected() {
|
||||
let toml = r#"
|
||||
[general]
|
||||
me_pool_min_fresh_ratio = 1.5
|
||||
|
||||
[censorship]
|
||||
tls_domain = "example.com"
|
||||
|
||||
[access.users]
|
||||
user = "00000000000000000000000000000000"
|
||||
"#;
|
||||
let dir = std::env::temp_dir();
|
||||
let path = dir.join("telemt_me_pool_min_ratio_invalid_test.toml");
|
||||
std::fs::write(&path, toml).unwrap();
|
||||
let err = ProxyConfig::load(&path).unwrap_err().to_string();
|
||||
assert!(err.contains("general.me_pool_min_fresh_ratio must be within [0.0, 1.0]"));
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn force_close_bumped_when_below_drain_ttl() {
|
||||
let toml = r#"
|
||||
[general]
|
||||
me_pool_drain_ttl_secs = 90
|
||||
me_reinit_drain_timeout_secs = 30
|
||||
|
||||
[censorship]
|
||||
tls_domain = "example.com"
|
||||
|
||||
[access.users]
|
||||
user = "00000000000000000000000000000000"
|
||||
"#;
|
||||
let dir = std::env::temp_dir();
|
||||
let path = dir.join("telemt_force_close_bump_test.toml");
|
||||
std::fs::write(&path, toml).unwrap();
|
||||
let cfg = ProxyConfig::load(&path).unwrap();
|
||||
assert_eq!(cfg.general.me_reinit_drain_timeout_secs, 90);
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,6 +206,11 @@ pub struct GeneralConfig {
|
||||
#[serde(default = "default_desync_all_full")]
|
||||
pub desync_all_full: bool,
|
||||
|
||||
/// Enable C-like hard-swap for ME pool generations.
|
||||
/// When true, Telemt prewarms a new generation and switches once full coverage is reached.
|
||||
#[serde(default = "default_hardswap")]
|
||||
pub hardswap: bool,
|
||||
|
||||
/// Enable staggered warmup of extra ME writers.
|
||||
#[serde(default = "default_true")]
|
||||
pub me_warmup_stagger_enabled: bool,
|
||||
@@ -262,6 +267,16 @@ pub struct GeneralConfig {
|
||||
#[serde(default)]
|
||||
pub update_every: Option<u64>,
|
||||
|
||||
/// Drain-TTL in seconds for stale ME writers after endpoint map changes.
|
||||
/// During TTL, stale writers may be used only as fallback for new bindings.
|
||||
#[serde(default = "default_me_pool_drain_ttl_secs")]
|
||||
pub me_pool_drain_ttl_secs: u64,
|
||||
|
||||
/// Minimum desired-DC coverage ratio required before draining stale writers.
|
||||
/// Range: 0.0..=1.0.
|
||||
#[serde(default = "default_me_pool_min_fresh_ratio")]
|
||||
pub me_pool_min_fresh_ratio: f32,
|
||||
|
||||
/// Drain timeout in seconds for stale ME writers after endpoint map changes.
|
||||
/// Set to 0 to keep stale writers draining indefinitely (no force-close).
|
||||
#[serde(default = "default_me_reinit_drain_timeout_secs")]
|
||||
@@ -328,8 +343,11 @@ impl Default for GeneralConfig {
|
||||
crypto_pending_buffer: default_crypto_pending_buffer(),
|
||||
max_client_frame: default_max_client_frame(),
|
||||
desync_all_full: default_desync_all_full(),
|
||||
hardswap: default_hardswap(),
|
||||
fast_mode_min_tls_record: default_fast_mode_min_tls_record(),
|
||||
update_every: Some(default_update_every_secs()),
|
||||
me_pool_drain_ttl_secs: default_me_pool_drain_ttl_secs(),
|
||||
me_pool_min_fresh_ratio: default_me_pool_min_fresh_ratio(),
|
||||
me_reinit_drain_timeout_secs: default_me_reinit_drain_timeout_secs(),
|
||||
proxy_secret_auto_reload_secs: default_proxy_secret_reload_secs(),
|
||||
proxy_config_auto_reload_secs: default_proxy_config_reload_secs(),
|
||||
@@ -348,6 +366,12 @@ impl GeneralConfig {
|
||||
self.update_every
|
||||
.unwrap_or_else(|| self.proxy_secret_auto_reload_secs.min(self.proxy_config_auto_reload_secs))
|
||||
}
|
||||
|
||||
/// Resolve force-close timeout for stale writers.
|
||||
/// `me_reinit_drain_timeout_secs` remains backward-compatible alias.
|
||||
pub fn effective_me_pool_force_close_secs(&self) -> u64 {
|
||||
self.me_reinit_drain_timeout_secs
|
||||
}
|
||||
}
|
||||
|
||||
/// `[general.links]` — proxy link generation settings.
|
||||
|
||||
Reference in New Issue
Block a user