fix(#2245): drop TCP fallback from gateway nginx upstream
Agents run in private netns (always-on isolation). The TCP fallback to 127.0.0.1:<port> was unreachable from the gateway's host netns regardless of whether the per-agent socket marker existed. Remove the conditional entirely: always use the unix socket path. If the socket is not yet bound, nginx returns 502 which is already handled by the error_page 502 503 504 = /__hive_agent_unreachable directive in every location block. Also removes the unused lifecycle::agent_web_port call and the now-misleading '.bound state' mention from the render doc comment.
This commit is contained in:
parent
4947037454
commit
0456206e52
1 changed files with 25 additions and 26 deletions
|
|
@ -1,7 +1,7 @@
|
|||
//! Runtime nginx include-file generator for the gateway's per-agent
|
||||
//! `/agent/<name>/` location blocks. Writes
|
||||
//! `/var/lib/hyperhive/gateway/agents.conf` on every topology change.
|
||||
//! UDS vs TCP upstream selection, reload trigger (`systemd-run
|
||||
//! UDS upstream selection, reload trigger (`systemd-run
|
||||
//! --machine=hive-gateway`), and idempotency:
|
||||
//! `docs/gateway.md::Per-agent unix-socket upstream`.
|
||||
|
||||
|
|
@ -14,7 +14,6 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
|||
use crate::priv_client;
|
||||
|
||||
use crate::agent_sockets;
|
||||
use crate::lifecycle;
|
||||
|
||||
/// Set when `write` publishes a new agents.conf; cleared when
|
||||
/// `reload_gateway_nginx` submits the reload command successfully.
|
||||
|
|
@ -74,12 +73,11 @@ const PROXY_HEADER_BLOCK: &str = " proxy_http_version 1.1;
|
|||
/// When `frontend_dir` is `None` (legacy) each agent gets a single
|
||||
/// `location /agent/<name>/` proxy block.
|
||||
///
|
||||
/// Output is deterministic for the same (sorted) set of names +
|
||||
/// `.bound` state: no timestamps, no UUIDs. `BTreeMap` would give
|
||||
/// alphabetical order; we rely on the caller to pass sorted names if
|
||||
/// they care about diff stability, but the gateway treats the blocks
|
||||
/// as unordered by nginx's longest-prefix-match rules so ordering only
|
||||
/// affects human readability.
|
||||
/// Output is deterministic for the same (sorted) set of names:
|
||||
/// no timestamps, no UUIDs. `BTreeMap` would give alphabetical order;
|
||||
/// we rely on the caller to pass sorted names if they care about diff
|
||||
/// stability, but the gateway treats the blocks as unordered by nginx's
|
||||
/// longest-prefix-match rules so ordering only affects human readability.
|
||||
fn render(names: &[String], frontend_dir: Option<&str>) -> String {
|
||||
let mut out = String::from(
|
||||
"# Generated by hive-c0re \u{2014} do not edit.\
|
||||
|
|
@ -87,24 +85,20 @@ fn render(names: &[String], frontend_dir: Option<&str>) -> String {
|
|||
\n# Reload triggered by hive-c0re via systemd-run --machine=hive-gateway.\n",
|
||||
);
|
||||
for name in names {
|
||||
let port = lifecycle::agent_web_port(name);
|
||||
// Two upstream forms because named locations (split mode's
|
||||
// `@<name>_dynamic`) forbid a URI part on `proxy_pass`. The
|
||||
// legacy prefix-location path keeps the trailing `/` so nginx
|
||||
// strips `/agent/<name>/` automatically; the named-location
|
||||
// path strips the prefix via `rewrite` and uses a bare upstream.
|
||||
let (upstream_prefix, upstream_bare) = if agent_sockets::ready_marker_for(name).exists() {
|
||||
let sock = agent_sockets::socket_path_for(name).display().to_string();
|
||||
(
|
||||
format!("http://unix:{sock}:/"),
|
||||
format!("http://unix:{sock}:"),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
format!("http://127.0.0.1:{port}/"),
|
||||
format!("http://127.0.0.1:{port}"),
|
||||
)
|
||||
};
|
||||
//
|
||||
// Always use the unix socket path. The old TCP fallback
|
||||
// (`127.0.0.1:<port>`) was removed: agents run in private netns
|
||||
// (always-on isolation) so the host loopback is unreachable from
|
||||
// the gateway's netns. When the socket is not yet bound, nginx
|
||||
// returns 502 which is handled by the error_page directive below.
|
||||
let sock = agent_sockets::socket_path_for(name).display().to_string();
|
||||
let upstream_prefix = format!("http://unix:{sock}:/");
|
||||
let upstream_bare = format!("http://unix:{sock}:");
|
||||
|
||||
if let Some(frontend) = frontend_dir {
|
||||
// Split mode: try to serve files from the nix-store dist first;
|
||||
|
|
@ -303,14 +297,19 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn render_tcp_upstream_when_no_bound_marker() {
|
||||
// No .bound file on disk → falls back to TCP loopback.
|
||||
fn render_uds_upstream_unconditional() {
|
||||
// TCP fallback removed — always UDS regardless of socket marker.
|
||||
let names = vec!["iris".to_owned()];
|
||||
let body = render(&names, None);
|
||||
let port = lifecycle::agent_web_port("iris");
|
||||
// UDS form present
|
||||
assert!(
|
||||
body.contains(&format!("proxy_pass http://127.0.0.1:{port}/")),
|
||||
"expected TCP upstream for iris, got:\n{body}"
|
||||
body.contains("proxy_pass http://unix:"),
|
||||
"expected UDS upstream for iris, got:\n{body}"
|
||||
);
|
||||
// No TCP loopback fallback
|
||||
assert!(
|
||||
!body.contains("127.0.0.1"),
|
||||
"unexpected TCP loopback in output:\n{body}"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue