From 853d935e08b2e7a0b28326bbb5e72eea91e00670 Mon Sep 17 00:00:00 2001 From: Alex Plotnick Date: Sat, 15 Aug 2026 06:45:22 +0000 Subject: [PATCH] Prefer the home sled Unrouted requests go to the sled hosting the proxy when the targets know it; the sticky default remains the fallback. Co-Authored-By: Claude Mythos 5 --- server/src/proxy.rs | 46 +++++++++++++++++++++++++++++++--- tests/src/integration_tests.rs | 16 +++++++++--- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/server/src/proxy.rs b/server/src/proxy.rs index cec610d..bb63cb8 100644 --- a/server/src/proxy.rs +++ b/server/src/proxy.rs @@ -7,8 +7,9 @@ //! Terminates client connections and routes each request to a sled, //! answering only `/version` itself. //! A request that names a target goes to the first sled the target -//! resolves to. Anything else goes to a sticky default, because -//! identities are cached on the sled that authenticated them. +//! resolves to. Anything else goes to the sled hosting the proxy +//! when known, else a sticky default, because identities are cached +//! on the sled that authenticated them. //! Requests are forwarded untouched: bound request signatures cover //! the exact request line, so the proxy may never rewrite one. @@ -93,11 +94,13 @@ pub struct ProxyServer { impl ProxyServer { /// Start listening at `local_addr`, routing to `targets`. + /// Unrouted requests prefer `home`, the sled hosting the proxy. pub async fn start( log: &Logger, local_addr: SocketAddr, tls: Option, targets: watch::Receiver, + home: Option, shutdown: CancellationToken, ) -> io::Result { let listener = TcpListener::bind(local_addr).await?; @@ -105,6 +108,7 @@ impl ProxyServer { let acceptor = tls.map(|config| TlsAcceptor::from(Arc::new(config))); let router = Arc::new(Router { targets, + home, default: Mutex::new(None), }); spawn(listen( @@ -133,11 +137,12 @@ impl ProxyServer { /// Pick a sled for each request. struct Router { targets: watch::Receiver, + home: Option, default: Mutex>, } impl Router { - fn route(&self, request: &Request) -> Result>> { + fn route(&self, request: &Request) -> Result>> { let targets = self.targets.borrow(); match named_target(request) { Some(Ok(target)) => targets.resolve(&target).ok_or_else(|| { @@ -151,6 +156,11 @@ impl Router { format!("unable to parse target `{bad}`"), ))), None => { + if let Some(home) = self.home.as_ref() + && let Some(addr) = targets.sleds.get(home) + { + return Ok(*addr); + } let mut default = self.default.lock().unwrap(); if let Some(baseboard) = default.as_ref() && let Some(addr) = targets.sleds.get(baseboard) @@ -367,6 +377,36 @@ mod test { s.parse().unwrap() } + /// Unrouted requests go to the home sled while the targets know + /// it, and otherwise to a sticky default. + #[test] + fn home_preference() { + let sled = |serial: &str| BaseboardId { + part_number: "913".to_string(), + serial_number: serial.to_string(), + }; + let addr = |port| SocketAddr::from(([127, 0, 0, 1], port)); + let mut targets = Targets::default(); + targets.sleds.insert(sled("away"), addr(1)); + targets.sleds.insert(sled("home"), addr(2)); + let (tx, rx) = watch::channel(targets); + let router = Router { + targets: rx, + home: Some(sled("home")), + default: Mutex::new(None), + }; + let get = request("/versions"); + assert_eq!(router.route(&get).unwrap(), addr(2)); + tx.send_modify(|t| { + t.sleds.remove(&sled("home")); + }); + assert_eq!(router.route(&get).unwrap(), addr(1)); + tx.send_modify(|t| { + t.sleds.insert(sled("home"), addr(2)); + }); + assert_eq!(router.route(&get).unwrap(), addr(2)); + } + /// The proxy answers `GET /version` itself unless `via` routes it. /// A `*` target means the handling server, which is the proxy. #[test] diff --git a/tests/src/integration_tests.rs b/tests/src/integration_tests.rs index 434c991..247a21c 100644 --- a/tests/src/integration_tests.rs +++ b/tests/src/integration_tests.rs @@ -164,9 +164,16 @@ async fn client_proxy_server() { cubbies: Cubbies::from([(14, test_baseboard_id())]), }); let shutdown_proxy = CancellationToken::new(); - let proxy = ProxyServer::start(&log, local_addr(), None, rx_targets, shutdown_proxy.clone()) - .await - .expect("can't start proxy server"); + let proxy = ProxyServer::start( + &log, + local_addr(), + None, + rx_targets, + None, + shutdown_proxy.clone(), + ) + .await + .expect("can't start proxy server"); let proxy_addr = proxy.local_addr(); assert_ne!(server_addr, proxy_addr); @@ -316,6 +323,7 @@ async fn client_tls_proxy_server() { local_addr(), Some(tls), rx_targets, + None, shutdown_proxy.clone(), ) .await @@ -396,6 +404,7 @@ async fn client_tls_proxy_server() { local_addr(), Some(delegated), rx_delegated, + None, shutdown_proxy.clone(), ) .await @@ -451,6 +460,7 @@ async fn client_tls_proxy_server() { local_addr(), Some(forged_tls), rx_forged, + None, shutdown_proxy.clone(), ) .await