mirror of
https://github.com/telemt/telemt.git
synced 2026-11-01 05:12:02 +03:00
WEB Knobs in API
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
@@ -256,6 +256,46 @@ fn reload_applies_hot_change_on_first_observed_snapshot() {
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn candidate_watcher_waits_for_activation_and_reconciles_disk() {
|
||||
let initial_tag = "10101010101010101010101010101010";
|
||||
let disk_tag = "20202020202020202020202020202020";
|
||||
let path = temp_config_path("telemt_hot_reload_activation_gate");
|
||||
write_reload_config(&path, Some(initial_tag), None);
|
||||
let initial = Arc::new(ProxyConfig::load(&path).unwrap());
|
||||
write_reload_config(&path, Some(disk_tag), None);
|
||||
let cancellation = tokio_util::sync::CancellationToken::new();
|
||||
let (activation_tx, activation_rx) = watch::channel(false);
|
||||
let (mut config_rx, _log_rx, watcher) = spawn_config_watcher(
|
||||
path.clone(),
|
||||
initial,
|
||||
None,
|
||||
None,
|
||||
cancellation.clone(),
|
||||
Some(activation_rx),
|
||||
);
|
||||
let watcher = tokio::spawn(watcher);
|
||||
|
||||
tokio::task::yield_now().await;
|
||||
assert_eq!(
|
||||
config_rx.borrow().general.ad_tag.as_deref(),
|
||||
Some(initial_tag)
|
||||
);
|
||||
activation_tx.send_replace(true);
|
||||
tokio::time::timeout(Duration::from_secs(2), config_rx.changed())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
config_rx.borrow_and_update().general.ad_tag.as_deref(),
|
||||
Some(disk_tag)
|
||||
);
|
||||
|
||||
cancellation.cancel();
|
||||
watcher.await.unwrap();
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reload_keeps_hot_apply_when_non_hot_fields_change() {
|
||||
let initial_tag = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
||||
|
||||
@@ -213,20 +213,47 @@ pub fn spawn_config_watcher(
|
||||
detected_ip_v4: Option<IpAddr>,
|
||||
detected_ip_v6: Option<IpAddr>,
|
||||
cancellation: tokio_util::sync::CancellationToken,
|
||||
) -> (watch::Receiver<Arc<ProxyConfig>>, watch::Receiver<LogLevel>) {
|
||||
mut activation: Option<watch::Receiver<bool>>,
|
||||
) -> (
|
||||
watch::Receiver<Arc<ProxyConfig>>,
|
||||
watch::Receiver<LogLevel>,
|
||||
impl std::future::Future<Output = ()> + Send + 'static,
|
||||
) {
|
||||
let initial_level = initial.general.log_level.clone();
|
||||
let (config_tx, config_rx) = watch::channel(initial);
|
||||
let (log_tx, log_rx) = watch::channel(initial_level);
|
||||
|
||||
let config_path = normalize_watch_path(&config_path);
|
||||
let initial_loaded = ProxyConfig::load_with_metadata(&config_path).ok();
|
||||
let initial_manifest = initial_loaded
|
||||
.as_ref()
|
||||
.map(|loaded| WatchManifest::from_source_files(&loaded.source_files))
|
||||
.unwrap_or_else(|| WatchManifest::from_source_files(std::slice::from_ref(&config_path)));
|
||||
let initial_snapshot_hash = initial_loaded.as_ref().map(|loaded| loaded.rendered_hash);
|
||||
|
||||
tokio::spawn(async move {
|
||||
let task = async move {
|
||||
if let Some(activation) = activation.as_mut() {
|
||||
loop {
|
||||
if *activation.borrow_and_update() {
|
||||
break;
|
||||
}
|
||||
tokio::select! {
|
||||
result = activation.changed() => {
|
||||
if result.is_err() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ = cancellation.cancelled() => return,
|
||||
}
|
||||
}
|
||||
}
|
||||
let initial_loaded = ProxyConfig::load_with_metadata(&config_path).ok();
|
||||
let initial_manifest = initial_loaded
|
||||
.as_ref()
|
||||
.map(|loaded| WatchManifest::from_source_files(&loaded.source_files))
|
||||
.unwrap_or_else(|| {
|
||||
WatchManifest::from_source_files(std::slice::from_ref(&config_path))
|
||||
});
|
||||
let initial_matches_disk = initial_loaded
|
||||
.as_ref()
|
||||
.is_some_and(|loaded| config_equal(config_tx.borrow().as_ref(), &loaded.config));
|
||||
let initial_snapshot_hash = initial_loaded
|
||||
.as_ref()
|
||||
.filter(|_| initial_matches_disk)
|
||||
.map(|loaded| loaded.rendered_hash);
|
||||
let (notify_tx, mut notify_rx) = mpsc::channel::<()>(4);
|
||||
let manifest_state = Arc::new(StdRwLock::new(WatchManifest::default()));
|
||||
let mut reload_state = ReloadState::new(initial_snapshot_hash);
|
||||
@@ -304,6 +331,9 @@ pub fn spawn_config_watcher(
|
||||
if poll_watcher.is_some() {
|
||||
info!("config watcher: poll watcher active (Docker/NFS safe)");
|
||||
}
|
||||
if initial_loaded.is_some() && !initial_matches_disk {
|
||||
let _ = notify_tx.try_send(());
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
let mut sighup = {
|
||||
@@ -364,7 +394,7 @@ pub fn spawn_config_watcher(
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
(config_rx, log_rx)
|
||||
(config_rx, log_rx, task)
|
||||
}
|
||||
|
||||
@@ -381,6 +381,11 @@ fn validate_vhosts(config: &mut ProxyConfig) -> Result<()> {
|
||||
validate_decoy(vhost_idx, &vhost.decoy)?;
|
||||
let mut profiles = HashSet::with_capacity(vhost.profiles.len());
|
||||
for (profile_idx, profile) in vhost.profiles.iter().enumerate() {
|
||||
if profile.user.is_empty() || profile.user.len() > 64 {
|
||||
return config_error(&format!(
|
||||
"web.vhosts[{vhost_idx}].profiles[{profile_idx}].user must contain 1..64 bytes"
|
||||
));
|
||||
}
|
||||
if !config.access.users.contains_key(&profile.user) {
|
||||
return config_error(&format!(
|
||||
"web.vhosts[{vhost_idx}].profiles[{profile_idx}].user references unknown access user `{}`",
|
||||
|
||||
@@ -56,6 +56,17 @@ fn web_config_builds_canonical_runtime_snapshot() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn web_profile_user_labels_are_bounded_for_runtime_status() {
|
||||
let user = "a".repeat(65);
|
||||
let invalid = WEB_CONFIG.replace("alice", &user);
|
||||
|
||||
assert!(
|
||||
load_config_error_from_temp_toml(&invalid)
|
||||
.contains("web.vhosts[0].profiles[0].user must contain 1..64 bytes")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn web_carriers_missing_or_false_disable_negotiation() {
|
||||
let missing = load_config_from_temp_toml(WEB_CONFIG);
|
||||
|
||||
Reference in New Issue
Block a user