mirror of
https://github.com/telemt/telemt.git
synced 2026-09-20 09:40:33 +03:00
WEB Knobs in API
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
@@ -14,6 +14,7 @@ use super::bind::{BoundListeners, BoundTcpListener, PreparedTcpListener, prepare
|
||||
use super::plan::{ListenerBindSpec, listener_bind_plan};
|
||||
#[cfg(unix)]
|
||||
use super::unix::UnixAcceptHandle;
|
||||
use crate::web::control::{WebRuntimeControl, WebRuntimeLifecycle};
|
||||
use crate::web::manager::{WebProcessRuntime, WebShutdownOutcome};
|
||||
use crate::web::trace::WebTraceStore;
|
||||
|
||||
@@ -22,6 +23,8 @@ pub(crate) struct ListenerManager {
|
||||
active_runtime: Arc<ArcSwap<RuntimeGeneration>>,
|
||||
slots: BTreeMap<SocketAddr, ListenerSlot>,
|
||||
web_runtime: Option<Arc<WebProcessRuntime>>,
|
||||
web_control: WebRuntimeControl,
|
||||
web_listeners: Arc<[SocketAddr]>,
|
||||
#[cfg(unix)]
|
||||
unix: Option<UnixAcceptHandle>,
|
||||
}
|
||||
@@ -46,11 +49,15 @@ impl ListenerManager {
|
||||
bound: BoundListeners,
|
||||
active_runtime: Arc<ArcSwap<RuntimeGeneration>>,
|
||||
trace: Arc<WebTraceStore>,
|
||||
web_control: WebRuntimeControl,
|
||||
) -> Self {
|
||||
let has_web = bound
|
||||
let web_listeners: Arc<[SocketAddr]> = bound
|
||||
.listeners
|
||||
.iter()
|
||||
.any(|listener| listener.spec.transport == ListenerTransport::Web);
|
||||
.filter(|listener| listener.spec.transport == ListenerTransport::Web)
|
||||
.map(|listener| listener.spec.addr)
|
||||
.collect();
|
||||
let has_web = !web_listeners.is_empty();
|
||||
let web_runtime =
|
||||
has_web.then(|| WebProcessRuntime::start_with_trace(active_runtime.clone(), trace));
|
||||
let mut slots = BTreeMap::new();
|
||||
@@ -65,10 +72,23 @@ impl ListenerManager {
|
||||
let unix = bound
|
||||
.unix_listener
|
||||
.map(|listener| UnixAcceptHandle::start(listener, active_runtime.clone()));
|
||||
web_control.publish(
|
||||
if has_web {
|
||||
WebRuntimeLifecycle::Running
|
||||
} else {
|
||||
WebRuntimeLifecycle::NoWebListener
|
||||
},
|
||||
Arc::clone(&web_listeners),
|
||||
web_runtime
|
||||
.as_ref()
|
||||
.map_or_else(std::sync::Weak::new, Arc::downgrade),
|
||||
);
|
||||
Self {
|
||||
active_runtime,
|
||||
slots,
|
||||
web_runtime,
|
||||
web_control,
|
||||
web_listeners,
|
||||
#[cfg(unix)]
|
||||
unix,
|
||||
}
|
||||
@@ -76,10 +96,18 @@ impl ListenerManager {
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn empty(active_runtime: Arc<ArcSwap<RuntimeGeneration>>) -> Self {
|
||||
let web_control = WebRuntimeControl::new();
|
||||
web_control.publish(
|
||||
WebRuntimeLifecycle::NoWebListener,
|
||||
Arc::from([]),
|
||||
std::sync::Weak::new(),
|
||||
);
|
||||
Self {
|
||||
active_runtime,
|
||||
slots: BTreeMap::new(),
|
||||
web_runtime: None,
|
||||
web_control,
|
||||
web_listeners: Arc::from([]),
|
||||
#[cfg(unix)]
|
||||
unix: None,
|
||||
}
|
||||
@@ -217,6 +245,13 @@ impl ListenerManager {
|
||||
|
||||
/// Stops every accept task and applies one deadline to the complete WEB ingress.
|
||||
pub(crate) async fn shutdown(&mut self) -> Result<(), String> {
|
||||
self.web_control.publish(
|
||||
WebRuntimeLifecycle::Draining,
|
||||
Arc::clone(&self.web_listeners),
|
||||
self.web_runtime
|
||||
.as_ref()
|
||||
.map_or_else(std::sync::Weak::new, Arc::downgrade),
|
||||
);
|
||||
if self.web_runtime.is_none() {
|
||||
let mut errors = Vec::new();
|
||||
for slot in self.slots.values_mut() {
|
||||
@@ -235,6 +270,11 @@ impl ListenerManager {
|
||||
{
|
||||
self.unix = None;
|
||||
}
|
||||
self.web_control.publish(
|
||||
WebRuntimeLifecycle::Drained,
|
||||
Arc::clone(&self.web_listeners),
|
||||
std::sync::Weak::new(),
|
||||
);
|
||||
return if errors.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -289,6 +329,15 @@ impl ListenerManager {
|
||||
{
|
||||
self.unix = None;
|
||||
}
|
||||
self.web_control.publish(
|
||||
if web_outcome == WebShutdownOutcome::DeadlineExceeded {
|
||||
WebRuntimeLifecycle::DeadlineExceeded
|
||||
} else {
|
||||
WebRuntimeLifecycle::Drained
|
||||
},
|
||||
Arc::clone(&self.web_listeners),
|
||||
std::sync::Weak::new(),
|
||||
);
|
||||
if errors.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -367,7 +416,8 @@ mod tests {
|
||||
runtime.config().web.debug.clone(),
|
||||
&runtime.config().web.limits,
|
||||
);
|
||||
let mut manager = ListenerManager::start(bound, active_runtime, trace);
|
||||
let mut manager =
|
||||
ListenerManager::start(bound, active_runtime, trace, WebRuntimeControl::new());
|
||||
let blocker = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let blocked_addr = blocker.local_addr().unwrap();
|
||||
let mut desired = ProxyConfig::default();
|
||||
@@ -394,7 +444,8 @@ mod tests {
|
||||
runtime.config().web.debug.clone(),
|
||||
&runtime.config().web.limits,
|
||||
);
|
||||
let mut manager = ListenerManager::start(bound, active_runtime, trace);
|
||||
let mut manager =
|
||||
ListenerManager::start(bound, active_runtime, trace, WebRuntimeControl::new());
|
||||
let reservation = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let new_addr = reservation.local_addr().unwrap();
|
||||
drop(reservation);
|
||||
|
||||
@@ -17,6 +17,7 @@ use crate::stats::{QuotaStore, Stats};
|
||||
use crate::synlimit_control;
|
||||
use crate::transport::UpstreamManager;
|
||||
use crate::transport::middle_proxy::MePool;
|
||||
use crate::web::control::WebRuntimeControl;
|
||||
use crate::web::trace::WebTraceStore;
|
||||
|
||||
use super::{
|
||||
@@ -106,6 +107,7 @@ pub(super) async fn run_telemt_core(
|
||||
config.access.cidr_rate_limits.clone(),
|
||||
);
|
||||
let web_trace = WebTraceStore::new(config.web.debug.clone(), &config.web.limits);
|
||||
let web_runtime_control = WebRuntimeControl::new();
|
||||
|
||||
let (detected_ips_tx, detected_ips_rx) = watch::channel((None::<IpAddr>, None::<IpAddr>));
|
||||
let initial_direct_first = config.general.use_middle_proxy && config.general.me2dc_fallback;
|
||||
@@ -157,6 +159,7 @@ pub(super) async fn run_telemt_core(
|
||||
let active_runtime_rx_api = active_runtime_rx.clone();
|
||||
let runtime_watch_rx_api = runtime_watch_rx.clone();
|
||||
let web_trace_api = web_trace.clone();
|
||||
let web_runtime_rx_api = web_runtime_control.subscribe();
|
||||
tokio::spawn(async move {
|
||||
api::serve(
|
||||
listen,
|
||||
@@ -175,6 +178,7 @@ pub(super) async fn run_telemt_core(
|
||||
active_runtime_rx_api,
|
||||
runtime_watch_rx_api,
|
||||
web_trace_api,
|
||||
web_runtime_rx_api,
|
||||
)
|
||||
.await;
|
||||
});
|
||||
@@ -318,8 +322,12 @@ pub(super) async fn run_telemt_core(
|
||||
active_runtime_tx.send_replace(Some(active_runtime.clone()));
|
||||
runtime_tasks::mark_runtime_ready(&startup_tracker).await;
|
||||
|
||||
let listener_manager =
|
||||
listeners::ListenerManager::start(bound, active_runtime.clone(), web_trace.clone());
|
||||
let listener_manager = listeners::ListenerManager::start(
|
||||
bound,
|
||||
active_runtime.clone(),
|
||||
web_trace.clone(),
|
||||
web_runtime_control,
|
||||
);
|
||||
let reload_supervisor = reload_supervisor::ReloadSupervisor::spawn(
|
||||
active_runtime.clone(),
|
||||
reload_control,
|
||||
|
||||
@@ -278,7 +278,11 @@ impl ReloadSupervisor {
|
||||
self.control
|
||||
.mark_phase(command.reload_id, ReloadPhase::Activating)
|
||||
.await;
|
||||
let new_runtime = prepared.generation;
|
||||
let PreparedRuntime {
|
||||
generation: new_runtime,
|
||||
detected_ips,
|
||||
config_watcher_activation,
|
||||
} = prepared;
|
||||
if let Err(error) = install_dns(&new_runtime.config().network.dns_overrides) {
|
||||
let message = format!("runtime DNS activation failed: {}", error);
|
||||
if command.request.failure_policy == ReloadFailurePolicy::Rollback {
|
||||
@@ -313,14 +317,16 @@ impl ReloadSupervisor {
|
||||
};
|
||||
old_runtime.stop_accepting_sessions();
|
||||
let replaced = self.active_runtime.swap(new_runtime.clone());
|
||||
self.web_trace.apply_policy(&new_runtime.config().web.debug);
|
||||
self.web_trace
|
||||
.apply_policy(new_runtime.id, &new_runtime.config().web.debug);
|
||||
config_watcher_activation.send_replace(true);
|
||||
if let Some(pending) = pending_listener_transition {
|
||||
self.listener_manager
|
||||
.lock()
|
||||
.await
|
||||
.finish_transition(pending);
|
||||
}
|
||||
self.detected_ips_tx.send_replace(prepared.detected_ips);
|
||||
self.detected_ips_tx.send_replace(detected_ips);
|
||||
self.runtime_log_filter
|
||||
.apply_reload(&new_runtime.config().general.log_level);
|
||||
self.runtime_watch_tx
|
||||
|
||||
@@ -21,6 +21,15 @@ fn runtime_log_filter() -> RuntimeLogFilter {
|
||||
RuntimeLogFilter::new(handle)
|
||||
}
|
||||
|
||||
fn prepared_runtime(generation: Arc<RuntimeGeneration>) -> PreparedRuntime {
|
||||
let (config_watcher_activation, _activation_rx) = watch::channel(false);
|
||||
PreparedRuntime {
|
||||
generation,
|
||||
detected_ips: (None, None),
|
||||
config_watcher_activation,
|
||||
}
|
||||
}
|
||||
|
||||
async fn fixture(request: ReloadRequest) -> ReloadFixture {
|
||||
let old_runtime = test_runtime_generation(1, ProxyConfig::default());
|
||||
let new_config = Arc::new(ProxyConfig::default());
|
||||
@@ -120,10 +129,7 @@ async fn revision_rollback_keeps_old_generation_and_cleans_candidate() {
|
||||
.activate_prepared(
|
||||
fixture.command,
|
||||
fixture.old_runtime.clone(),
|
||||
PreparedRuntime {
|
||||
generation: fixture.new_runtime,
|
||||
detected_ips: (None, None),
|
||||
},
|
||||
prepared_runtime(fixture.new_runtime),
|
||||
RevisionGateAction::Rollback("revision changed".to_string()),
|
||||
|_| -> Result<(), String> { panic!("DNS activation must not run on rollback") },
|
||||
)
|
||||
@@ -161,10 +167,7 @@ async fn dns_failure_policy_controls_rollback_or_keep_new() {
|
||||
.activate_prepared(
|
||||
fixture.command,
|
||||
fixture.old_runtime.clone(),
|
||||
PreparedRuntime {
|
||||
generation: fixture.new_runtime.clone(),
|
||||
detected_ips: (None, None),
|
||||
},
|
||||
prepared_runtime(fixture.new_runtime.clone()),
|
||||
RevisionGateAction::Proceed,
|
||||
|_| Err("invalid DNS entry".to_string()),
|
||||
)
|
||||
@@ -215,10 +218,7 @@ async fn drain_publishes_new_generation_before_old_sessions_finish() {
|
||||
.activate_prepared(
|
||||
fixture.command,
|
||||
old_runtime,
|
||||
PreparedRuntime {
|
||||
generation: new_runtime,
|
||||
detected_ips: (None, None),
|
||||
},
|
||||
prepared_runtime(new_runtime),
|
||||
RevisionGateAction::Proceed,
|
||||
|_| Ok(()),
|
||||
)
|
||||
@@ -271,10 +271,7 @@ async fn drain_timeout_cancels_old_sessions_and_records_one_warning() {
|
||||
.activate_prepared(
|
||||
fixture.command,
|
||||
old_runtime,
|
||||
PreparedRuntime {
|
||||
generation: new_runtime,
|
||||
detected_ips: (None, None),
|
||||
},
|
||||
prepared_runtime(new_runtime),
|
||||
RevisionGateAction::Proceed,
|
||||
|_| Ok(()),
|
||||
)
|
||||
|
||||
@@ -28,9 +28,14 @@ use super::listeners::listener_rebind_supported;
|
||||
use super::runtime_tasks::RuntimeLogFilter;
|
||||
use super::{me_startup, runtime_tasks, tls_bootstrap};
|
||||
|
||||
/// Fully prepared candidate runtime and its activation-gated config watcher.
|
||||
pub(crate) struct PreparedRuntime {
|
||||
/// Candidate generation ready for publication.
|
||||
pub(crate) generation: Arc<RuntimeGeneration>,
|
||||
/// Detected public addresses associated with the candidate.
|
||||
pub(crate) detected_ips: (Option<IpAddr>, Option<IpAddr>),
|
||||
/// Gate opened only after the candidate becomes the active generation.
|
||||
pub(crate) config_watcher_activation: watch::Sender<bool>,
|
||||
}
|
||||
|
||||
pub(crate) async fn prepare_runtime(
|
||||
@@ -171,6 +176,7 @@ pub(crate) async fn prepare_runtime(
|
||||
config.server.max_connections as usize
|
||||
};
|
||||
let max_connections = Arc::new(Semaphore::new(max_connections_limit));
|
||||
let (config_watcher_activation, config_watcher_activation_rx) = watch::channel(false);
|
||||
let watches = runtime_tasks::spawn_runtime_tasks(
|
||||
&config,
|
||||
config_path,
|
||||
@@ -190,6 +196,7 @@ pub(crate) async fn prepare_runtime(
|
||||
proxy_shared.clone(),
|
||||
me_ready_tx.clone(),
|
||||
task_scope.clone(),
|
||||
Some(config_watcher_activation_rx),
|
||||
)
|
||||
.await;
|
||||
let config_rx = watches.config_rx;
|
||||
@@ -295,6 +302,7 @@ pub(crate) async fn prepare_runtime(
|
||||
|
||||
Ok(PreparedRuntime {
|
||||
generation,
|
||||
config_watcher_activation,
|
||||
detected_ips: (
|
||||
probe.detected_ipv4.map(IpAddr::V4),
|
||||
probe.detected_ipv6.map(IpAddr::V6),
|
||||
|
||||
@@ -247,6 +247,7 @@ pub(super) async fn prepare_runtime(
|
||||
shared_state.clone(),
|
||||
me_ready_tx.clone(),
|
||||
runtime_task_scope.clone(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
let config_rx = runtime_watches.config_rx;
|
||||
|
||||
@@ -107,6 +107,7 @@ pub(crate) async fn spawn_runtime_tasks(
|
||||
shared_state: Arc<ProxySharedState>,
|
||||
me_ready_tx: watch::Sender<u64>,
|
||||
task_scope: RuntimeTaskScope,
|
||||
config_watcher_activation: Option<watch::Receiver<bool>>,
|
||||
) -> RuntimeWatches {
|
||||
let um_clone = upstream_manager.clone();
|
||||
let dc_overrides_for_health = config.dc_overrides.clone();
|
||||
@@ -151,14 +152,15 @@ pub(crate) async fn spawn_runtime_tasks(
|
||||
Some("spawn config hot-reload watcher".to_string()),
|
||||
)
|
||||
.await;
|
||||
let (config_rx, log_level_rx): (watch::Receiver<Arc<ProxyConfig>>, watch::Receiver<LogLevel>) =
|
||||
spawn_config_watcher(
|
||||
config_path.to_path_buf(),
|
||||
config.clone(),
|
||||
detected_ip_v4,
|
||||
detected_ip_v6,
|
||||
task_scope.cancellation_token(),
|
||||
);
|
||||
let (config_rx, log_level_rx, config_watcher_task) = spawn_config_watcher(
|
||||
config_path.to_path_buf(),
|
||||
config.clone(),
|
||||
detected_ip_v4,
|
||||
detected_ip_v6,
|
||||
task_scope.cancellation_token(),
|
||||
config_watcher_activation,
|
||||
);
|
||||
task_scope.spawn(config_watcher_task);
|
||||
startup_tracker
|
||||
.complete_component(
|
||||
COMPONENT_CONFIG_WATCHER_START,
|
||||
|
||||
Reference in New Issue
Block a user