feat(#1930): move otel stats export to host-level services.hyperhive.otel

This commit is contained in:
damocles 2026-06-23 20:42:15 +02:00 committed by mara
commit 838cc9af9a
3 changed files with 303 additions and 38 deletions

View file

@ -656,6 +656,48 @@ fn peer_ca_sources() -> Vec<String> {
.collect()
}
/// Hive-wide OTEL config injected into every agent's build, read off
/// hive-c0re's own unit env (set from `services.hyperhive.otel.*` in
/// `nix/modules/hive-c0re.nix`). A present, non-empty
/// `HYPERHIVE_OTEL_ENDPOINT` is the enable signal — the host module
/// asserts the endpoint is set whenever `otel.enable` is true, so
/// "endpoint present" == "OTEL on". The optional fields map to the
/// matching host options and are only carried when set.
struct OtelConfig {
endpoint: String,
protocol: String,
extra_resource_attributes: Option<String>,
headers_credential: Option<String>,
}
/// Read the hive-wide OTEL config from env, or `None` when OTEL is off.
/// Mirrors `hive_ca_source` — host state surfaced to the meta renderer
/// so it can bake build-time `hyperhive.otel.*` config into each agent
/// (the per-agent options harness-base.nix consumes). Returns `None`
/// when the endpoint signal is absent so the renderer emits no
/// `hyperhive.otel.*` lines and agents keep the disabled default.
fn otel_config() -> Option<OtelConfig> {
let endpoint = std::env::var("HYPERHIVE_OTEL_ENDPOINT")
.ok()
.filter(|v| !v.is_empty())?;
let protocol = std::env::var("HYPERHIVE_OTEL_PROTOCOL")
.ok()
.filter(|v| !v.is_empty())
.unwrap_or_else(|| "http/protobuf".to_owned());
let extra_resource_attributes = std::env::var("HYPERHIVE_OTEL_EXTRA_RESOURCE_ATTRIBUTES")
.ok()
.filter(|v| !v.is_empty());
let headers_credential = std::env::var("HYPERHIVE_OTEL_HEADERS_CREDENTIAL")
.ok()
.filter(|v| !v.is_empty());
Some(OtelConfig {
endpoint,
protocol,
extra_resource_attributes,
headers_credential,
})
}
/// The ordered set of CA certs embedded next to the meta flake, as
/// `(filename, host_source_path)`. The self-signed hive CA (when active)
/// is `hive-ca.pem`; each peer CA is `peer-ca-<N>.pem` in declaration
@ -907,6 +949,40 @@ where
ca_refs.join(" ")
);
}
// Hive-wide OTEL stats export (`services.hyperhive.otel.*`): inject the
// build-time `hyperhive.otel.*` config harness-base.nix consumes (its
// otelEnv + otelExecStart wrapper + LoadCredential). Host-driven, so
// the same config lands on every agent; emitted only when enabled.
// Mirrors the CA-cert injection above — host state -> build-time agent
// module config.
if let Some(otel) = otel_config() {
let esc = |s: &str| s.replace('\\', "\\\\").replace('"', "\\\"");
out.push_str(" hyperhive.otel.enable = true;\n");
let _ = writeln!(
out,
" hyperhive.otel.endpoint = \"{}\";",
esc(&otel.endpoint)
);
let _ = writeln!(
out,
" hyperhive.otel.protocol = \"{}\";",
esc(&otel.protocol)
);
if let Some(attrs) = &otel.extra_resource_attributes {
let _ = writeln!(
out,
" hyperhive.otel.extraResourceAttributes = \"{}\";",
esc(attrs)
);
}
if let Some(cred) = &otel.headers_credential {
let _ = writeln!(
out,
" hyperhive.otel.headersCredential = \"{}\";",
esc(cred)
);
}
}
out.push_str(
r#" # The harness service inside the container runs as a
# non-root unix user named after the agent (`damocles`,
@ -1445,4 +1521,89 @@ mod tests {
"no certificateFiles reference without any CA signal:\n{without_ca}"
);
}
#[test]
fn render_flake_injects_otel_when_signalled() {
// services.hyperhive.otel.* -> HYPERHIVE_OTEL_* on hive-c0re's unit
// -> injected as build-time hyperhive.otel.* into every agent. With
// no endpoint signal, no hyperhive.otel lines are emitted (agents
// keep the harness-base disabled default).
//
// SAFETY: single-threaded mutation of process env vars no other
// test asserts on; restored before returning.
let render = || {
render_flake(
"github:example/hyperhive",
"path:/nix/store/aaaa-nixpkgs-source",
"path:/nix/store/bbbb-nixpkgs-unstable-source",
8000,
"she/her",
&std::collections::HashMap::new(),
&[sample_spec("alice", false, 9001)],
)
};
unsafe {
std::env::remove_var("HYPERHIVE_OTEL_EXTRA_RESOURCE_ATTRIBUTES");
std::env::remove_var("HYPERHIVE_OTEL_HEADERS_CREDENTIAL");
std::env::set_var("HYPERHIVE_OTEL_ENDPOINT", "https://c.example/otel");
std::env::set_var("HYPERHIVE_OTEL_PROTOCOL", "grpc");
}
let on_minimal = render();
unsafe {
std::env::set_var(
"HYPERHIVE_OTEL_EXTRA_RESOURCE_ATTRIBUTES",
"deployment.environment=prod",
);
std::env::set_var(
"HYPERHIVE_OTEL_HEADERS_CREDENTIAL",
"/run/secrets/otel-headers",
);
}
let on_full = render();
unsafe {
std::env::remove_var("HYPERHIVE_OTEL_ENDPOINT");
std::env::remove_var("HYPERHIVE_OTEL_PROTOCOL");
std::env::remove_var("HYPERHIVE_OTEL_EXTRA_RESOURCE_ATTRIBUTES");
std::env::remove_var("HYPERHIVE_OTEL_HEADERS_CREDENTIAL");
}
let off = render();
assert!(
on_minimal.contains("hyperhive.otel.enable = true;"),
"otel enable must be injected:\n{on_minimal}"
);
assert!(
on_minimal.contains("hyperhive.otel.endpoint = \"https://c.example/otel\";"),
"otel endpoint must be injected:\n{on_minimal}"
);
assert!(
on_minimal.contains("hyperhive.otel.protocol = \"grpc\";"),
"otel protocol must be injected:\n{on_minimal}"
);
// Optional fields absent when unset.
assert!(
!on_minimal.contains("hyperhive.otel.extraResourceAttributes"),
"extraResourceAttributes must not appear when unset:\n{on_minimal}"
);
assert!(
!on_minimal.contains("hyperhive.otel.headersCredential"),
"headersCredential must not appear when unset:\n{on_minimal}"
);
assert!(
on_full.contains(
"hyperhive.otel.extraResourceAttributes = \"deployment.environment=prod\";"
),
"extraResourceAttributes must be injected when set:\n{on_full}"
);
assert!(
on_full.contains("hyperhive.otel.headersCredential = \"/run/secrets/otel-headers\";"),
"headersCredential must be injected when set:\n{on_full}"
);
assert!(
!off.contains("hyperhive.otel"),
"no otel lines when disabled:\n{off}"
);
}
}