Decoy Fasttrack Drafts

Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
Alexey
2026-09-05 21:18:48 +03:00
parent 9f023ff9c7
commit 106b26a5b7
28 changed files with 920 additions and 59 deletions
+5 -6
View File
@@ -15,7 +15,7 @@ use super::model::ApiFailure;
use crate::config::ProxyConfig;
use crate::config::hot_reload::classify_config_changes;
use crate::maestro::reload::{ReloadAccepted, ReloadRequest, ReloadSubmitError};
use crate::maestro::runtime_build::{deferred_process_fields, resolve_reload_config};
use crate::maestro::runtime_build::resolve_reload_config;
use serde::Serialize;
use std::path::{Path, PathBuf};
use std::sync::Arc;
@@ -206,8 +206,7 @@ async fn prepare_patch_to_path(
let revision = compute_snapshot_revision(&candidate);
let new_cfg = candidate.config;
let class = classify_config_changes(&old_cfg, &new_cfg);
let deferred_process_fields =
deferred_process_fields(&old_cfg, &new_cfg).map_err(ApiFailure::bad_request)?;
let resolved = resolve_reload_config(&old_cfg, &new_cfg).map_err(ApiFailure::bad_request)?;
Ok(PreparedConfigPatch {
owner_path,
@@ -216,9 +215,9 @@ async fn prepare_patch_to_path(
response: PatchConfigResponse {
revision,
restart_required: class.restart_required,
runtime_reload_required: class.restart_required,
process_restart_required: !deferred_process_fields.is_empty(),
deferred_process_fields,
runtime_reload_required: resolved.runtime_changed,
process_restart_required: !resolved.deferred_process_fields.is_empty(),
deferred_process_fields: resolved.deferred_process_fields,
changed: class.changed,
reload: None,
},
+18
View File
@@ -131,6 +131,24 @@ async fn patch_web_debug_is_hot_and_limits_are_process_deferred() {
);
}
#[tokio::test]
async fn patch_web_decoy_fasttrack_requires_only_process_restart() {
let (path, _directory) = temp_config("[web]\nenabled = false\n");
let patch: Json = serde_json::json!({
"web": {"decoy_fasttrack_mode": "shadow"}
});
let response = apply_patch_to_path(&path, &patch, None).await.unwrap();
assert!(response.restart_required);
assert!(!response.runtime_reload_required);
assert!(response.process_restart_required);
assert_eq!(
response.deferred_process_fields,
vec!["web.decoy_fasttrack_mode".to_string()]
);
}
#[tokio::test]
async fn invalid_web_patch_does_not_modify_the_source() {
let (path, _directory) = temp_config("[web]\nenabled = false\n");
+5 -2
View File
@@ -20,8 +20,8 @@ mod request;
// Ingress, capacity, and decoy telemetry remain separate availability planes.
mod observability;
use observability::{
WebCapacityStatus, WebCarrierNegotiationStatus, WebDecoyUpstreamStatus, WebIngressStatus,
WebLifecycleCountersStatus,
WebCapacityStatus, WebCarrierNegotiationStatus, WebDecoyFastTrackStatus,
WebDecoyUpstreamStatus, WebIngressStatus, WebLifecycleCountersStatus,
};
use request::{
CloseRequest, DrainRequest, RuntimeInstanceRequest, parse_session_query, parse_session_ref,
@@ -280,6 +280,7 @@ struct WebStatusData {
ingress: WebIngressStatus,
capacity: WebCapacityStatus,
decoy_upstream: WebDecoyUpstreamStatus,
decoy_fasttrack: WebDecoyFastTrackStatus,
carrier_negotiation: WebCarrierNegotiationStatus,
lifecycle_counters: WebLifecycleCountersStatus,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -315,6 +316,7 @@ impl WebStatusData {
let ingress = WebIngressStatus::new(&publication, runtime.is_some());
let capacity = WebCapacityStatus::new(&publication, runtime, config);
let decoy_upstream = WebDecoyUpstreamStatus::new(&publication);
let decoy_fasttrack = WebDecoyFastTrackStatus::new(&publication, config);
let carrier_negotiation = WebCarrierNegotiationStatus::new(&publication);
let lifecycle_counters = WebLifecycleCountersStatus::new(&publication, config);
Self {
@@ -332,6 +334,7 @@ impl WebStatusData {
ingress,
capacity,
decoy_upstream,
decoy_fasttrack,
carrier_negotiation,
lifecycle_counters,
operator_lifecycle,
+35 -2
View File
@@ -1,11 +1,12 @@
use serde::Serialize;
use crate::config::{ProxyConfig, WebHttpConnectionCapacityAction};
use crate::config::{ProxyConfig, WebDecoyFastTrackMode, WebHttpConnectionCapacityAction};
use crate::web::control::{WebRuntimeLifecycle, WebRuntimePublication};
use crate::web::manager::{WebCapacityResourceStatus, WebCapacitySnapshot, WebProcessRuntime};
use crate::web::telemetry::{
WebBridgeRecoveryCounter, WebCarrierFailureCounter, WebCarrierLearningCounter,
WebCarrierSelectionCounter, WebSessionCloseCounter, WebSessionLifecycleObservationCounter,
WebCarrierSelectionCounter, WebDecoyFastTrackCounter, WebSessionCloseCounter,
WebSessionLifecycleObservationCounter,
};
use crate::web::telemetry::{WebOutcomeCounter, WebRejectionCounter};
@@ -121,6 +122,27 @@ impl WebDecoyUpstreamStatus {
}
}
/// Fixed-cardinality process-lifetime decoy capability-routing counters.
#[derive(Serialize)]
pub(super) struct WebDecoyFastTrackStatus {
mode: WebDecoyFastTrackMode,
requests: Vec<WebDecoyFastTrackCounter>,
shadow_mismatches_total: u64,
}
impl WebDecoyFastTrackStatus {
/// Builds effective policy and counters without requiring the runtime manager.
pub(super) fn new(publication: &WebRuntimePublication, config: &ProxyConfig) -> Self {
Self {
mode: config.web.decoy_fasttrack_mode,
requests: publication.telemetry.decoy_fasttrack_counters(),
shadow_mismatches_total: publication
.telemetry
.decoy_fasttrack_shadow_mismatches(),
}
}
}
/// Fixed-cardinality process-lifetime carrier negotiation counters.
#[derive(Serialize)]
pub(super) struct WebCarrierNegotiationStatus {
@@ -187,6 +209,11 @@ mod tests {
serde_json::to_value(super::WebCapacityStatus::new(&publication, None, &config))
.unwrap();
let decoy = serde_json::to_value(super::WebDecoyUpstreamStatus::new(&publication)).unwrap();
let fasttrack = serde_json::to_value(super::WebDecoyFastTrackStatus::new(
&publication,
&config,
))
.unwrap();
let carrier =
serde_json::to_value(super::WebCarrierNegotiationStatus::new(&publication)).unwrap();
let lifecycle = serde_json::to_value(super::WebLifecycleCountersStatus::new(
@@ -210,6 +237,12 @@ mod tests {
decoy["outcomes"].as_array().unwrap().len(),
crate::web::telemetry::WebDecoyUpstreamOutcome::ALL.len()
);
assert_eq!(fasttrack["mode"], "off");
assert_eq!(
fasttrack["requests"].as_array().unwrap().len(),
crate::web::telemetry::WebDecoyFastTrackDisposition::ALL.len()
);
assert_eq!(fasttrack["shadow_mismatches_total"], 0);
assert_eq!(capacity["partial"][0], "runtime");
assert_eq!(
carrier["selections"].as_array().unwrap().len(),