fix(swarm-otel): reserve the swarm tier's component names from hive names
The collector names components `<kind>/<owner>` — a hive name for the per-hive pipelines, the literal `swarm` for the swarm tier's own. Both land in one attrset via `//`, so a hive named `swarm` replaced the swarm tier's parts and lost its own: its receiver kept accepting pushes into a pipeline that routed nowhere, and its samples lost the `hive` stamp that makes attribution unforgeable. Zero failed assertions. The reserved name is now bound once and interpolated at each swarm-tier use, so the guard checks the same string the config emits rather than a copy of it. A second swarm-tier pipeline joins the list and inherits the check without touching the assertion.
This commit is contained in:
parent
8178b0b55c
commit
4de4878e74
1 changed files with 47 additions and 3 deletions
|
|
@ -43,6 +43,21 @@ let
|
||||||
|
|
||||||
autheliaCfg = hyperhiveCfg.swarm.authelia;
|
autheliaCfg = hyperhiveCfg.swarm.authelia;
|
||||||
|
|
||||||
|
# The collector names its components `<kind>/<owner>`, where `<owner>` is a
|
||||||
|
# hive name for the per-hive pipelines and this literal for the swarm tier's
|
||||||
|
# own. The two share one namespace and are merged with `//`, so a hive named
|
||||||
|
# this would silently REPLACE the swarm tier's parts — and lose its own
|
||||||
|
# pipeline in the process, while its receiver keeps accepting pushes.
|
||||||
|
#
|
||||||
|
# Bound once and interpolated at every swarm-tier use below, so the assertion
|
||||||
|
# that reserves it is checking the same string the config emits. A literal
|
||||||
|
# repeated at each site would let the guard and the config drift apart, which
|
||||||
|
# is the failure this guard exists to prevent.
|
||||||
|
swarmTierName = "swarm";
|
||||||
|
# Every `<owner>` this module claims for itself. One entry today; a second
|
||||||
|
# swarm-tier pipeline would be added here and inherit the check for free.
|
||||||
|
reservedOwners = [ swarmTierName ];
|
||||||
|
|
||||||
# `attrNames` is sorted, so this is a function of the hive SET and not of
|
# `attrNames` is sorted, so this is a function of the hive SET and not of
|
||||||
# the order anyone wrote it in.
|
# the order anyone wrote it in.
|
||||||
#
|
#
|
||||||
|
|
@ -286,6 +301,33 @@ in
|
||||||
List the swarm's hives.
|
List the swarm's hives.
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
# A hive whose name is one this module claims for itself collides in
|
||||||
|
# the collector's component namespace, and `//` resolves it silently:
|
||||||
|
# the swarm tier's parts win, that hive's `metrics/<name>` pipeline
|
||||||
|
# disappears, and its `resource/<name>` stamp goes with it — so the
|
||||||
|
# hive still connects and pushes into nothing, unlabelled.
|
||||||
|
#
|
||||||
|
# Checked rather than documented for the same reason the port range
|
||||||
|
# is: the names are all known at evaluation time, and the runtime
|
||||||
|
# symptom is a hive that looks healthy and reports no metrics, with
|
||||||
|
# nothing in any log naming the cause.
|
||||||
|
assertion = !lib.any (h: lib.elem h reservedOwners) (lib.attrNames hyperhiveCfg.swarm.hives);
|
||||||
|
message = ''
|
||||||
|
services.hyperhive.swarm.hives contains ${
|
||||||
|
lib.concatMapStringsSep ", " (h: "'${h}'") (
|
||||||
|
lib.filter (h: lib.elem h reservedOwners) (lib.attrNames hyperhiveCfg.swarm.hives)
|
||||||
|
)
|
||||||
|
}, which the swarm collector reserves for its own pipelines.
|
||||||
|
|
||||||
|
The collector names components `<kind>/<owner>` and uses the hive
|
||||||
|
name as the owner, so such a hive would silently replace the
|
||||||
|
swarm tier's parts and lose its own — it would keep accepting
|
||||||
|
pushes into a pipeline that routes nowhere.
|
||||||
|
|
||||||
|
Rename the hive. Reserved: ${lib.concatMapStringsSep ", " (n: "'${n}'") reservedOwners}.
|
||||||
|
'';
|
||||||
|
}
|
||||||
{
|
{
|
||||||
# A hive proves who it is with a token this provider mints, so
|
# A hive proves who it is with a token this provider mints, so
|
||||||
# there is no version of this collector that runs without one.
|
# there is no version of this collector that runs without one.
|
||||||
|
|
@ -512,9 +554,9 @@ in
|
||||||
# not have. Keeping it out of them makes the absence
|
# not have. Keeping it out of them makes the absence
|
||||||
# structural rather than something to remember to strip.
|
# structural rather than something to remember to strip.
|
||||||
// lib.optionalAttrs (cfg.scrapeTargets != { }) {
|
// lib.optionalAttrs (cfg.scrapeTargets != { }) {
|
||||||
"metrics/swarm" = {
|
"metrics/${swarmTierName}" = {
|
||||||
receivers = [ "prometheus" ];
|
receivers = [ "prometheus" ];
|
||||||
processors = [ "resource/swarm" ];
|
processors = [ "resource/${swarmTierName}" ];
|
||||||
exporters = exporterNames;
|
exporters = exporterNames;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -602,8 +644,10 @@ in
|
||||||
# an invented one (a sentinel, the local hive's name) would be
|
# an invented one (a sentinel, the local hive's name) would be
|
||||||
# queried as though it meant something.
|
# queried as though it meant something.
|
||||||
// lib.optionalAttrs (cfg.scrapeTargets != { }) {
|
// lib.optionalAttrs (cfg.scrapeTargets != { }) {
|
||||||
"resource/swarm".attributes = [
|
"resource/${swarmTierName}".attributes = [
|
||||||
{
|
{
|
||||||
|
# The metric LABEL, a different namespace from the
|
||||||
|
# component name above — deliberately not interpolated.
|
||||||
key = "swarm";
|
key = "swarm";
|
||||||
value = swarmDisplayName;
|
value = swarmDisplayName;
|
||||||
action = "upsert";
|
action = "upsert";
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue