Watch
0
0
Fork
You've already forked hyperhive
0

every hive runs its otel collector, and agents export to it by default

This commit is contained in:
atlas 2026-10-18 23:17:28 +02:00 • committed by mara
commit 13daf4c7dc
28 changed files with 245 additions and 303 deletions

View file

@ -561,42 +561,17 @@ fn hive_ca_source() -> Option<String> {
Some(path)
}
/// Hive-wide OTEL config injected into every agent's build, read off
/// hive-c0re's own unit env (set from `services.hyperhive.otel.*`,
/// `services.hyperhive.swarm.hiveOtel.*` and
/// `services.hyperhive.deploy.hive-controller.otel.*`). 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>,
}
/// 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 `services.hyperhive.agent.otel.*` config into each agent
/// (the per-agent options the harness modules consume). Returns `None`
/// when the endpoint signal is absent so the renderer emits no
/// `services.hyperhive.agent.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")
/// Extra `OTEL_RESOURCE_ATTRIBUTES` entries for every agent, read off
/// hive-c0re's own unit env
/// (`services.hyperhive.deploy.hive-controller.otel.extraResourceAttributes`).
/// `None` when unset or empty. This is the only OTEL value the renderer
/// writes into the flake: agents export to the hive collector by default and
/// find it by name, so there is no enable flag, endpoint or protocol to
/// carry.
fn otel_extra_resource_attributes() -> Option<String> {
std::env::var("HYPERHIVE_OTEL_EXTRA_RESOURCE_ATTRIBUTES")
.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());
Some(OtelConfig {
endpoint,
protocol,
extra_resource_attributes,
})
}
/// The ordered set of CA certs embedded next to the meta flake, as
@ -907,36 +882,20 @@ fn render_flake(
ca_refs.join(" ")
);
}
// Hive-wide OTEL stats export (`services.hyperhive.otel.*`): inject the
// build-time `services.hyperhive.agent.otel.*` config the harness modules consume (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() {
// Extra OTEL resource labels (`deploy.hive-controller.otel.*`) are the one
// OTEL value rendered here; enablement and the collector endpoint are
// agent-module defaults.
if let Some(attrs) = otel_extra_resource_attributes() {
let esc = |s: &str| s.replace('\\', "\\\\").replace('"', "\\\"");
out.push_str(" services.hyperhive.agent.otel.enable = true;\n");
let _ = writeln!(
out,
" services.hyperhive.agent.otel.endpoint = \"{}\";",
esc(&otel.endpoint)
" services.hyperhive.agent.otel.extraResourceAttributes = \"{}\";",
esc(&attrs)
);
let _ = writeln!(
out,
" services.hyperhive.agent.otel.protocol = \"{}\";",
esc(&otel.protocol)
);
if let Some(attrs) = &otel.extra_resource_attributes {
let _ = writeln!(
out,
" services.hyperhive.agent.otel.extraResourceAttributes = \"{}\";",
esc(attrs)
);
}
}
// Agent-facing service URLs (`services.hyperhive.agent.forge.url`, `services.hyperhive.agent.matrix.url`):
// emit the host's real values as build-time agent config, the same
// host-state -> agent-module shape as the otel block above.
// host-state -> agent-module shape as the CA block above.
//
// These options exist already, but until now *nothing set them*, so every
// agent silently fell back to their `localhost:<port>` defaults while the
@ -2140,16 +2099,16 @@ mod tests {
}
#[test]
fn render_flake_injects_otel_when_signalled() {
// services.hyperhive.otel.* -> HYPERHIVE_OTEL_* on hive-c0re's unit
// -> injected as build-time services.hyperhive.agent.otel.* into every agent. With
// no endpoint signal, no services.hyperhive.agent.otel lines are emitted (agents
// keep the the harness modules disabled default).
fn render_flake_carries_no_otel_enablement() {
// The agent module defaults otel on and finds the collector by name, so
// the flake carries no enable, endpoint or protocol line however the
// host's OTEL env is set. Only the per-hive resource labels are
// rendered, and only when set.
//
// Serialised against every other env-mutating test in the crate.
// `stats::otel_metrics::tests` perturbs HYPERHIVE_OTEL_ENDPOINT too,
// and lands in this same test binary — "no other test asserts on
// these" was true of this module and false of the process.
// `stats::otel_metrics::tests` perturbs OTEL env too, and lands in this
// same test binary — "no other test asserts on these" was true of this
// module and false of the process.
let _env = crate::test_env::lock();
// SAFETY: serialised by the guard above; restored before returning.
let render = || {
@ -2165,53 +2124,49 @@ mod tests {
std::env::set_var("HYPERHIVE_OTEL_ENDPOINT", "https://c.example/otel");
std::env::set_var("HYPERHIVE_OTEL_PROTOCOL", "grpc");
}
let on_minimal = render();
let plain = render();
unsafe {
std::env::set_var(
"HYPERHIVE_OTEL_EXTRA_RESOURCE_ATTRIBUTES",
"deployment.environment=prod",
);
}
let on_full = render();
let labelled = 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");
}
let off = render();
// Presence control: the render is the real flake, carrying the pin.
assert!(
on_minimal.contains("services.hyperhive.agent.otel.enable = true;"),
"otel enable must be injected:\n{on_minimal}"
plain.contains("rev=0123abcd"),
"the hyperhive pin line must be rendered:\n{plain}"
);
for (name, flake) in [("plain", &plain), ("labelled", &labelled)] {
for needle in [
"agent.otel.enable",
"agent.otel.endpoint",
"agent.otel.protocol",
"c.example",
] {
assert!(
!flake.contains(needle),
"{name}: `{needle}` must not be rendered:\n{flake}"
);
}
}
assert!(
!plain.contains("agent.otel.extraResourceAttributes"),
"extraResourceAttributes must not appear when unset:\n{plain}"
);
assert!(
on_minimal
.contains("services.hyperhive.agent.otel.endpoint = \"https://c.example/otel\";"),
"otel endpoint must be injected:\n{on_minimal}"
);
assert!(
on_minimal.contains("services.hyperhive.agent.otel.protocol = \"grpc\";"),
"otel protocol must be injected:\n{on_minimal}"
);
// Optional fields absent when unset.
assert!(
!on_minimal.contains("services.hyperhive.agent.otel.extraResourceAttributes"),
"extraResourceAttributes must not appear when unset:\n{on_minimal}"
);
assert!(
on_full.contains(
labelled.contains(
"services.hyperhive.agent.otel.extraResourceAttributes = \"deployment.environment=prod\";"
),
"extraResourceAttributes must be injected when set:\n{on_full}"
);
assert!(
!off.contains("services.hyperhive.agent.otel"),
"no otel lines when disabled:\n{off}"
"extraResourceAttributes must be rendered when set:\n{labelled}"
);
}
#[test]
fn render_flake_injects_github_disable_only_when_signalled() {
// services.hyperhive.swarm.github.enable = false -> HYPERHIVE_GITHUB_DISABLED