diff --git a/experimental/sgl-router/src/config/cli.rs b/experimental/sgl-router/src/config/cli.rs index 879ff838f..cc30b7188 100644 --- a/experimental/sgl-router/src/config/cli.rs +++ b/experimental/sgl-router/src/config/cli.rs @@ -11,12 +11,13 @@ use std::num::NonZeroU32; use crate::config::sampling::{parse_sampling_overrides, ConflictPolicy, SamplingOverrides}; use crate::config::{ - default_cb_cool_down, default_proxy_request_timeout_secs, default_stale_request_timeout_secs, - resolve_mode, ActiveLoadConfig, AffinityConfig, AffinityMode, CacheAwareConfig, - CachePrefixProvider, CircuitBreakerConfig, Config, DecodePolicyKind, DiscoveryBackend, - EligibilityConfig, FilterKind, FusedTerm, K8sDiscoveryConfig, KvIndexerEndpointConfig, - LogFormat, ModelConfig, ObservabilityConfig, PolicyKind, ProxyConfig, ServerConfig, - SessionAffinityMode, StaticUrlsDiscoveryConfig, StickyConfig, StickyFallbackKind, DEFAULT_FUSE, + default_cb_cool_down, default_host, default_port, default_proxy_request_timeout_secs, + default_shutdown_drain_secs, default_stale_request_timeout_secs, resolve_mode, + ActiveLoadConfig, AffinityConfig, AffinityMode, CacheAwareConfig, CachePrefixProvider, + CircuitBreakerConfig, Config, DecodePolicyKind, DiscoveryBackend, EligibilityConfig, + FilterKind, FusedTerm, K8sDiscoveryConfig, KvIndexerEndpointConfig, LogFormat, ModelConfig, + ObservabilityConfig, PolicyKind, ProxyConfig, ServerConfig, SessionAffinityMode, + StaticUrlsDiscoveryConfig, StickyConfig, StickyFallbackKind, DEFAULT_FUSE, }; const DEFAULT_KV_INDEXER_QUERY_TIMEOUT_MS: u64 = 100; @@ -36,11 +37,30 @@ const DEFAULT_KV_INDEXER_QUERY_MAX_INFLIGHT: usize = sgl_kv_indexer::DEFAULT_QUE pub struct Cli { // ---- server ---- /// Address to bind the HTTP server to. - #[arg(long, default_value = "127.0.0.1")] + #[arg(long, default_value_t = default_host())] pub host: String, /// Port to bind the HTTP server to. - #[arg(long, default_value_t = 30000)] + #[arg(long, default_value_t = default_port())] pub port: u16, + /// Seconds to keep serving after SIGTERM, with `/readyz` returning 503, + /// before the server stops accepting — so the endpoint removal reaches + /// kube-proxy first. Leave room under terminationGracePeriodSeconds for the + /// in-flight drain that follows. If you rely on a readiness probe (rather + /// than pod deletion) to deregister, size this above your + /// failureThreshold * periodSeconds. The default equals the k8s default + /// terminationGracePeriodSeconds, so on a pod that has not raised its grace + /// period startup warns until you do — and declare it with + /// --termination-grace-secs so the check uses the real budget. + /// 0 disables the pause. + #[arg(long, default_value_t = default_shutdown_drain_secs())] + pub shutdown_drain_secs: u64, + /// The pod's terminationGracePeriodSeconds, if you have raised it from the + /// k8s default of 30. Only used to check --shutdown-drain-secs leaves room + /// for the in-flight drain at startup: the router cannot read its own pod + /// spec, so without this it warns against the default and a deliberately + /// long drain has no way to say it is safe. + #[arg(long)] + pub termination_grace_secs: Option, // ---- model (exactly one) ---- /// Model id this router serves (the OpenAI `model` field). @@ -633,6 +653,8 @@ impl Cli { server: ServerConfig { host: self.host, port: self.port, + shutdown_drain_secs: self.shutdown_drain_secs, + termination_grace_secs: self.termination_grace_secs, }, observability: ObservabilityConfig { log_level: self.log_level, @@ -784,6 +806,70 @@ mod tests { assert_eq!(c.model.id, "qwen3-0.6b"); assert_eq!(c.proxy.request_timeout_secs, 300); assert_eq!(c.active_load.stale_request_timeout_secs, 600); + assert_eq!(c.server.shutdown_drain_secs, 30); + } + + /// Several values, not just the default: a clamp or a rescale in the + /// mapping satisfies any single-value assertion. + #[test] + fn shutdown_drain_secs_maps_into_config() { + for secs in ["0", "17", "1800"] { + let c = into_config_owned(with_model(&[ + "--worker-urls", + "http://10.0.0.1:30000", + "--shutdown-drain-secs", + secs, + ])) + .unwrap(); + let expected: u64 = secs.parse().unwrap(); + assert_eq!( + c.server.shutdown_drain_secs, expected, + "--shutdown-drain-secs {secs} must map through unchanged", + ); + assert_eq!( + c.server.shutdown_drain(), + std::time::Duration::from_secs(expected), + "the Duration accessor must agree with the configured seconds", + ); + } + } + + /// The ceiling is enforced on the CLI path, not only on a hand-built + /// `Config`: a drain carrying a fat-fingered extra digit must fail at + /// startup rather than turn every later termination into a SIGKILL. + #[test] + fn shutdown_drain_secs_past_the_ceiling_is_rejected() { + let error = into_config_owned(with_model(&[ + "--worker-urls", + "http://10.0.0.1:30000", + "--shutdown-drain-secs", + "18000", + ])) + .expect_err("a drain past the ceiling must not start") + .to_string(); + assert!( + error.contains("shutdown_drain_secs"), + "the error must name the flag to fix: {error}" + ); + } + + /// `--termination-grace-secs` exists only to feed the startup advisory, so + /// the one thing that matters is that it reaches the config — and that + /// omitting it stays `None` (assume the k8s default) rather than + /// defaulting to a number that would silently become the compared budget. + #[test] + fn termination_grace_secs_maps_into_config_and_defaults_to_none() { + let c = into_config_owned(with_model(&["--worker-urls", "http://10.0.0.1:30000"])).unwrap(); + assert_eq!(c.server.termination_grace_secs, None); + + let c = into_config_owned(with_model(&[ + "--worker-urls", + "http://10.0.0.1:30000", + "--termination-grace-secs", + "120", + ])) + .unwrap(); + assert_eq!(c.server.termination_grace_secs, Some(120)); } /// With `--tokenizer-path` omitted, the tokenizer source defaults to the diff --git a/experimental/sgl-router/src/config/mod.rs b/experimental/sgl-router/src/config/mod.rs index 4a3241a2c..fea8f1c2c 100644 --- a/experimental/sgl-router/src/config/mod.rs +++ b/experimental/sgl-router/src/config/mod.rs @@ -7,6 +7,61 @@ pub use types::*; use anyhow::{anyhow, Result}; +/// The k8s default `terminationGracePeriodSeconds`, assumed when the operator +/// has not declared the pod's real one. A `shutdown_drain_secs` at or above the +/// grace period leaves no time for the in-flight drain, so the pod is SIGKILLed +/// before it finishes — the opposite of what the drain is for. +pub const K8S_DEFAULT_GRACE_SECS: u64 = 30; + +/// Ceiling on `shutdown_drain_secs`, enforced by [`Config::validate`]. Sized +/// for the workload rather than for the k8s default grace period: a single +/// streaming completion can hold the router for many minutes, so a deployment +/// that does not want terminations cutting one off runs a +/// `terminationGracePeriodSeconds` in the tens of minutes and a drain to match. +/// Deciding whether a particular drain fits a particular grace period is +/// [`shutdown_drain_advisory`]'s job — advice, because the operator can raise +/// the budget. This constant is the separate, harder gate: it rejects a value +/// that is not a drain at all, an extra digit or seconds confused with +/// milliseconds, which no grace period could ever service. +pub const MAX_SHUTDOWN_DRAIN_SECS: u64 = 1800; + +/// A `shutdown_drain_secs` that leaves no room under the grace period for the +/// in-flight drain that follows the pause. Carries the compared values as +/// fields so the caller logs a static message with structured data rather than +/// interpolating the numbers into the message text. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ShutdownDrainAdvisory { + pub shutdown_drain_secs: u64, + /// The budget the drain was compared against. + pub termination_grace_secs: u64, + /// Whether that budget came from the operator or from + /// [`K8S_DEFAULT_GRACE_SECS`]. An assumed budget makes the advisory a + /// guess; a declared one makes it a fact. + pub grace_declared: bool, +} + +/// Advisory (not a hard error: the drain may well be right and the grace period +/// raised to match) for a drain that leaves no room for the in-flight drain. +/// The bound is `>=`, not `>`: a drain of exactly the grace period already +/// consumes all of it. +/// +/// `termination_grace_secs` is the pod's real `terminationGracePeriodSeconds` +/// when the operator declared it. The router cannot read its own pod spec, so +/// `None` falls back to the k8s default — which is why declaring the real value +/// is the way to silence this on a deployment that raised the grace period +/// deliberately, rather than lowering a drain that was correct. +pub fn shutdown_drain_advisory( + shutdown_drain_secs: u64, + termination_grace_secs: Option, +) -> Option { + let grace = termination_grace_secs.unwrap_or(K8S_DEFAULT_GRACE_SECS); + (shutdown_drain_secs >= grace).then_some(ShutdownDrainAdvisory { + shutdown_drain_secs, + termination_grace_secs: grace, + grace_declared: termination_grace_secs.is_some(), + }) +} + impl Config { /// Check invariants the type system and `clap` don't already enforce. /// Called by [`cli::Cli::into_config`] after assembling the `Config` @@ -21,6 +76,16 @@ impl Config { validate_bucket_config(bucket_config)?; } self.model.sampling_overrides.validate()?; + if self.server.shutdown_drain_secs > MAX_SHUTDOWN_DRAIN_SECS { + return Err(anyhow!( + "shutdown_drain_secs must be at most {MAX_SHUTDOWN_DRAIN_SECS} (got {}); \ + past the ceiling a value is a typo rather than a drain, and the pod \ + would be SIGKILLed long before the pause elapsed. A long but deliberate \ + drain is fine — declare --termination-grace-secs so startup can check \ + it against the pod's real budget", + self.server.shutdown_drain_secs, + )); + } match &self.discovery { DiscoveryBackend::StaticUrls(s) => { if s.urls.is_empty() { @@ -209,10 +274,7 @@ mod tests { /// the `cli` module tests; the k8s selector grammar in `types`. fn cfg(model_id: &str, urls: &[&str]) -> Config { Config { - server: ServerConfig { - host: "127.0.0.1".into(), - port: 30000, - }, + server: ServerConfig::default(), observability: ObservabilityConfig::default(), model: ModelConfig { id: model_id.into(), @@ -483,4 +545,112 @@ mod tests { .to_string(); assert!(error.contains("ttft_p95_at_capcity_ms"), "got: {error}"); } + + #[test] + fn shutdown_drain_advisory_is_silent_below_the_k8s_default_grace() { + // Anything strictly under the k8s default terminationGracePeriodSeconds + // (30 s) still leaves room for the in-flight drain, so it is safe + // without operator action. + assert!(shutdown_drain_advisory(29, None).is_none()); + assert!(shutdown_drain_advisory(0, None).is_none()); + } + + /// The default drain is exactly the assumed grace period, so out of the box + /// it leaves nothing for the in-flight drain and says so on every startup. + /// Asserted rather than left implicit because it is the one case an + /// operator meets without choosing it: a later edit to either constant that + /// silenced the warning would be changing the default deployment's + /// behaviour, and should have to say so here. + #[test] + fn the_default_drain_warns_until_the_grace_period_is_raised() { + let advisory = shutdown_drain_advisory(default_shutdown_drain_secs(), None) + .expect("the default drain must warn against the assumed k8s grace period"); + assert_eq!(advisory.termination_grace_secs, K8S_DEFAULT_GRACE_SECS); + assert!( + !advisory.grace_declared, + "an assumed budget must not be reported as declared", + ); + // Raising the pod's grace period past the drain is what silences it — + // the action the warning asks for has to actually work. + assert!( + shutdown_drain_advisory( + default_shutdown_drain_secs(), + Some(K8S_DEFAULT_GRACE_SECS * 2), + ) + .is_none(), + "a grace period declared with room for the in-flight drain must silence it", + ); + } + + #[test] + fn shutdown_drain_advisory_warns_once_the_drain_consumes_the_whole_grace() { + // A drain of exactly the 30 s k8s default leaves zero seconds for the + // in-flight drain, so the pod is SIGKILLed mid-drain — the boundary + // itself must warn, not just values past it. The ceiling is in the list + // because it is startable: `validate` accepts it, so the advisory is + // the only thing left to say it does not fit the default grace period. + for drain in [K8S_DEFAULT_GRACE_SECS, 120, MAX_SHUTDOWN_DRAIN_SECS] { + let advisory = shutdown_drain_advisory(drain, None) + .unwrap_or_else(|| panic!("{drain}s must warn")); + assert_eq!(advisory.shutdown_drain_secs, drain); + assert_eq!(advisory.termination_grace_secs, K8S_DEFAULT_GRACE_SECS); + assert!( + !advisory.grace_declared, + "an undeclared grace period must be reported as assumed, not as fact", + ); + } + } + + /// The advisory's whole purpose is to be silenceable by declaring the real + /// budget: a 60 s drain under a 120 s grace period is a correct + /// configuration, and warning about it trains operators to ignore the line. + #[test] + fn shutdown_drain_advisory_respects_a_declared_grace_period() { + assert!( + shutdown_drain_advisory(60, Some(120)).is_none(), + "a drain with room under the declared grace period must not warn", + ); + let advisory = shutdown_drain_advisory(60, Some(60)) + .expect("a drain consuming the whole declared grace period must warn"); + assert_eq!(advisory.termination_grace_secs, 60); + assert!( + advisory.grace_declared, + "a declared grace period must be reported as declared", + ); + // ...and declaring a *shorter* budget than the k8s default must be able + // to warn about a drain the default would have waved through. + assert!( + shutdown_drain_advisory(10, Some(10)).is_some(), + "a short declared grace period must still be compared against", + ); + // The configuration the ceiling was raised for: a completion streaming + // for minutes wants a drain of minutes, under a grace period declared + // to match. That is correct, not merely tolerated, so it must be silent. + assert!( + shutdown_drain_advisory(MAX_SHUTDOWN_DRAIN_SECS, Some(3600)).is_none(), + "a long drain under a grace period declared to cover it must not warn", + ); + } + + /// `validate` is the hard gate the advisory deliberately is not: past the + /// ceiling the value can only be a typo, and starting on it would make + /// every later termination a SIGKILL. + #[test] + fn validate_rejects_a_shutdown_drain_past_the_ceiling() { + let mut config = cfg("qwen3-0.6b", &["http://10.0.0.1:30000"]); + config.server.shutdown_drain_secs = MAX_SHUTDOWN_DRAIN_SECS; + config + .validate() + .expect("the ceiling itself must remain startable"); + + config.server.shutdown_drain_secs = MAX_SHUTDOWN_DRAIN_SECS + 1; + let error = config + .validate() + .expect_err("a drain past the ceiling must fail startup") + .to_string(); + assert!( + error.contains("shutdown_drain_secs"), + "the error must name the flag to fix: {error}" + ); + } } diff --git a/experimental/sgl-router/src/config/types.rs b/experimental/sgl-router/src/config/types.rs index e7d30f01e..9ce039c86 100644 --- a/experimental/sgl-router/src/config/types.rs +++ b/experimental/sgl-router/src/config/types.rs @@ -248,6 +248,65 @@ impl std::fmt::Display for StickyFallbackKind { pub struct ServerConfig { pub host: String, pub port: u16, + /// Seconds to keep serving after SIGTERM — with `/readyz` flipped to 503 — + /// before the HTTP server stops accepting. The default covers both + /// deregistration paths: endpoint removal reaching kube-proxy after the + /// pod's `deletionTimestamp` is stamped, and a probe-driven load balancer, + /// which cannot act until `failureThreshold * periodSeconds` of `/readyz` + /// failures have accumulated. + /// + /// Note that it equals the k8s default `terminationGracePeriodSeconds`, so + /// a pod that has not raised its grace period is left with nothing for the + /// in-flight drain that follows the pause, and + /// [`shutdown_drain_advisory`](crate::config::shutdown_drain_advisory) + /// warns at every startup. That is the intended reading rather than a + /// misconfigured default: a router whose completions stream for minutes + /// cannot terminate cleanly inside 30 s at all, and the grace period is the + /// thing to raise. 0 disables the pause. + pub shutdown_drain_secs: u64, + /// The pod's actual `terminationGracePeriodSeconds`, when the operator + /// declares it. The router cannot read its own pod spec, so without this + /// the startup advisory can only compare the drain against the k8s + /// default — and warns, wrongly, about a deployment that raised the grace + /// period on purpose. `None` means "assume the default". + pub termination_grace_secs: Option, +} + +impl ServerConfig { + /// [`Self::shutdown_drain_secs`] as a `Duration`. Keeps the seconds-to- + /// `Duration` conversion in the library, where a test can pin it, rather + /// than in `main.rs` where a `from_secs`/`from_millis` slip would silently + /// shorten every drain by a factor of 1000. + pub fn shutdown_drain(&self) -> std::time::Duration { + std::time::Duration::from_secs(self.shutdown_drain_secs) + } +} + +pub fn default_host() -> String { + "127.0.0.1".into() +} + +pub fn default_port() -> u16 { + 30000 +} + +pub fn default_shutdown_drain_secs() -> u64 { + 30 +} + +/// Exists so test fixtures can spell out only the fields they care about +/// (`tests/` is a separate crate, so a `#[cfg(test)]` constructor cannot reach +/// the integration fixtures). Keep `Cli::into_config` exhaustive so adding a +/// field still forces a decision on the production path. +impl Default for ServerConfig { + fn default() -> Self { + Self { + host: default_host(), + port: default_port(), + shutdown_drain_secs: default_shutdown_drain_secs(), + termination_grace_secs: None, + } + } } #[derive(Debug, Clone)] diff --git a/experimental/sgl-router/src/policies/factory.rs b/experimental/sgl-router/src/policies/factory.rs index 855189ee0..d78c3bb76 100644 --- a/experimental/sgl-router/src/policies/factory.rs +++ b/experimental/sgl-router/src/policies/factory.rs @@ -331,6 +331,7 @@ mod tests { server: ServerConfig { host: "0".into(), port: 0, + ..Default::default() }, observability: Default::default(), model: ModelConfig { diff --git a/experimental/sgl-router/src/server/app_context.rs b/experimental/sgl-router/src/server/app_context.rs index cdfe184b6..f860acfeb 100644 --- a/experimental/sgl-router/src/server/app_context.rs +++ b/experimental/sgl-router/src/server/app_context.rs @@ -124,6 +124,7 @@ impl AppContext { server: crate::config::ServerConfig { host: "x".into(), port: 0, + ..Default::default() }, observability: Default::default(), model: crate::config::ModelConfig { diff --git a/experimental/sgl-router/src/server/routes/tokenize.rs b/experimental/sgl-router/src/server/routes/tokenize.rs index 89b8ab835..9a27f8f72 100644 --- a/experimental/sgl-router/src/server/routes/tokenize.rs +++ b/experimental/sgl-router/src/server/routes/tokenize.rs @@ -112,6 +112,7 @@ mod tests { server: crate::config::ServerConfig { host: "x".into(), port: 0, + ..Default::default() }, observability: Default::default(), model: crate::config::ModelConfig { diff --git a/experimental/sgl-router/src/tokenizer/mod.rs b/experimental/sgl-router/src/tokenizer/mod.rs index 2cbd198d6..f9f80044c 100644 --- a/experimental/sgl-router/src/tokenizer/mod.rs +++ b/experimental/sgl-router/src/tokenizer/mod.rs @@ -226,6 +226,7 @@ mod tests { server: crate::config::ServerConfig { host: "0".into(), port: 0, + ..Default::default() }, observability: Default::default(), model: crate::config::ModelConfig { diff --git a/experimental/sgl-router/src/workers/manager.rs b/experimental/sgl-router/src/workers/manager.rs index 3de03a638..4643e6bf8 100644 --- a/experimental/sgl-router/src/workers/manager.rs +++ b/experimental/sgl-router/src/workers/manager.rs @@ -585,6 +585,7 @@ mod tests { server: ServerConfig { host: "0".into(), port: 0, + ..Default::default() }, observability: Default::default(), model: ModelConfig { diff --git a/experimental/sgl-router/tests/component/discovery/static_urls.rs b/experimental/sgl-router/tests/component/discovery/static_urls.rs index 110519487..d6dbfb780 100644 --- a/experimental/sgl-router/tests/component/discovery/static_urls.rs +++ b/experimental/sgl-router/tests/component/discovery/static_urls.rs @@ -124,6 +124,7 @@ async fn static_urls_pd_role_resolved_end_to_end() { server: ServerConfig { host: "127.0.0.1".into(), port: 0, + ..Default::default() }, observability: ObservabilityConfig::default(), model: sgl_router::config::ModelConfig { diff --git a/experimental/sgl-router/tests/proxy/bucket_routing.rs b/experimental/sgl-router/tests/proxy/bucket_routing.rs index a5b0fbf07..9f555c7d8 100644 --- a/experimental/sgl-router/tests/proxy/bucket_routing.rs +++ b/experimental/sgl-router/tests/proxy/bucket_routing.rs @@ -55,6 +55,7 @@ fn build_app_context( server: ServerConfig { host: "0".into(), port: 0, + ..Default::default() }, observability: ObservabilityConfig::default(), model: ModelConfig { diff --git a/experimental/sgl-router/tests/proxy/chat_routing.rs b/experimental/sgl-router/tests/proxy/chat_routing.rs index 1c87765a7..9ecf68fcc 100644 --- a/experimental/sgl-router/tests/proxy/chat_routing.rs +++ b/experimental/sgl-router/tests/proxy/chat_routing.rs @@ -28,6 +28,7 @@ fn config_for(_worker_url: &str) -> Config { server: ServerConfig { host: "0".into(), port: 0, + ..Default::default() }, observability: ObservabilityConfig::default(), model: ModelConfig { diff --git a/experimental/sgl-router/tests/proxy/common/cache_aware_fixture.rs b/experimental/sgl-router/tests/proxy/common/cache_aware_fixture.rs index aba8e4e84..67feee682 100644 --- a/experimental/sgl-router/tests/proxy/common/cache_aware_fixture.rs +++ b/experimental/sgl-router/tests/proxy/common/cache_aware_fixture.rs @@ -20,6 +20,7 @@ pub fn config() -> Config { server: ServerConfig { host: "0".into(), port: 0, + ..Default::default() }, observability: ObservabilityConfig::default(), model: ModelConfig { diff --git a/experimental/sgl-router/tests/proxy/failover.rs b/experimental/sgl-router/tests/proxy/failover.rs index 0e236b1cf..6e4696ee9 100644 --- a/experimental/sgl-router/tests/proxy/failover.rs +++ b/experimental/sgl-router/tests/proxy/failover.rs @@ -29,6 +29,7 @@ async fn failover_when_one_worker_dies() { server: ServerConfig { host: "0".into(), port: 0, + ..Default::default() }, observability: Default::default(), model: ModelConfig { diff --git a/experimental/sgl-router/tests/proxy/graceful_shutdown.rs b/experimental/sgl-router/tests/proxy/graceful_shutdown.rs index 650f23302..10e48bff3 100644 --- a/experimental/sgl-router/tests/proxy/graceful_shutdown.rs +++ b/experimental/sgl-router/tests/proxy/graceful_shutdown.rs @@ -38,6 +38,7 @@ fn build_ctx_with_worker(worker_url: &str) -> Arc { server: ServerConfig { host: "127.0.0.1".into(), port: 0, + ..Default::default() }, observability: ObservabilityConfig::default(), model: ModelConfig { diff --git a/experimental/sgl-router/tests/proxy/header_forwarding.rs b/experimental/sgl-router/tests/proxy/header_forwarding.rs index 21a31a389..374817603 100644 --- a/experimental/sgl-router/tests/proxy/header_forwarding.rs +++ b/experimental/sgl-router/tests/proxy/header_forwarding.rs @@ -25,6 +25,7 @@ async fn forwards_whitelisted_headers_strips_others() { server: ServerConfig { host: "0".into(), port: 0, + ..Default::default() }, observability: ObservabilityConfig::default(), model: ModelConfig { diff --git a/experimental/sgl-router/tests/proxy/pd_bootstrap_injection.rs b/experimental/sgl-router/tests/proxy/pd_bootstrap_injection.rs index 9d1917526..aa2ab1542 100644 --- a/experimental/sgl-router/tests/proxy/pd_bootstrap_injection.rs +++ b/experimental/sgl-router/tests/proxy/pd_bootstrap_injection.rs @@ -40,6 +40,7 @@ fn config() -> Config { server: ServerConfig { host: "0".into(), port: 0, + ..Default::default() }, observability: ObservabilityConfig::default(), model: ModelConfig { diff --git a/experimental/sgl-router/tests/proxy/pd_pool_isolation.rs b/experimental/sgl-router/tests/proxy/pd_pool_isolation.rs index 1b1de43bd..460f9923f 100644 --- a/experimental/sgl-router/tests/proxy/pd_pool_isolation.rs +++ b/experimental/sgl-router/tests/proxy/pd_pool_isolation.rs @@ -39,6 +39,7 @@ fn config() -> Config { server: ServerConfig { host: "0".into(), port: 0, + ..Default::default() }, observability: ObservabilityConfig::default(), model: ModelConfig { diff --git a/experimental/sgl-router/tests/proxy/pd_protocol_binding.rs b/experimental/sgl-router/tests/proxy/pd_protocol_binding.rs index 6b1bbb2d1..f5eccbceb 100644 --- a/experimental/sgl-router/tests/proxy/pd_protocol_binding.rs +++ b/experimental/sgl-router/tests/proxy/pd_protocol_binding.rs @@ -127,6 +127,7 @@ fn config() -> Config { server: ServerConfig { host: "0".into(), port: 0, + ..Default::default() }, observability: ObservabilityConfig::default(), model: ModelConfig { diff --git a/experimental/sgl-router/tests/proxy/roundrobin_input_ids.rs b/experimental/sgl-router/tests/proxy/roundrobin_input_ids.rs index a0c83359d..4c970b2bd 100644 --- a/experimental/sgl-router/tests/proxy/roundrobin_input_ids.rs +++ b/experimental/sgl-router/tests/proxy/roundrobin_input_ids.rs @@ -36,6 +36,7 @@ fn config() -> Config { server: ServerConfig { host: "0".into(), port: 0, + ..Default::default() }, observability: ObservabilityConfig::default(), model: ModelConfig { diff --git a/experimental/sgl-router/tests/proxy/shared_prefill_admission.rs b/experimental/sgl-router/tests/proxy/shared_prefill_admission.rs index c996a536b..3eabdcb62 100644 --- a/experimental/sgl-router/tests/proxy/shared_prefill_admission.rs +++ b/experimental/sgl-router/tests/proxy/shared_prefill_admission.rs @@ -143,6 +143,7 @@ fn config(policy: PolicyKind) -> Config { server: ServerConfig { host: "0".into(), port: 0, + ..Default::default() }, observability: ObservabilityConfig::default(), model: ModelConfig { diff --git a/experimental/sgl-router/tests/proxy/sticky_input_ids.rs b/experimental/sgl-router/tests/proxy/sticky_input_ids.rs index ca876ebd0..237a0f5fe 100644 --- a/experimental/sgl-router/tests/proxy/sticky_input_ids.rs +++ b/experimental/sgl-router/tests/proxy/sticky_input_ids.rs @@ -48,6 +48,7 @@ fn config() -> Config { server: ServerConfig { host: "0".into(), port: 0, + ..Default::default() }, observability: ObservabilityConfig::default(), model: ModelConfig { diff --git a/experimental/sgl-router/tests/proxy/sticky_routing.rs b/experimental/sgl-router/tests/proxy/sticky_routing.rs index 5fcb79349..02fd1fcbc 100644 --- a/experimental/sgl-router/tests/proxy/sticky_routing.rs +++ b/experimental/sgl-router/tests/proxy/sticky_routing.rs @@ -36,6 +36,7 @@ fn build_sticky_ctx(header_name: &str, worker_urls: &[String]) -> Arc Config { server: ServerConfig { host: "0".into(), port: 0, + ..Default::default() }, observability: ObservabilityConfig::default(), model: ModelConfig {