Keep parent node identity out of Proxmox child aliases

Guests and storage copy their node's NodeIdentity, NodeName and
NodeAliases into ProxmoxData so search and filters agree after a node
rename. canonicalAliases then listed those values as aliases of the
child itself, so every guest answered to the node's alert resource ID
(for example homelab-minipc). The workloads table matches alerts against
canonical aliases, so a single node temperature or memory alert painted
every guest on that node yellow and showed up in each guest's Needs
Attention panel. The shared alias also made the node's own alias lookup
ambiguous in the registry index.

Only the node resource now lists its node identity as canonical aliases.
Children keep the inherited fields in ProxmoxData, which is what search,
storage and backup consumers already read.
This commit is contained in:
rcourtman
2026-10-06 11:03:01 +01:00
parent 25f4131720
commit 108b113383
3 changed files with 53 additions and 7 deletions
@@ -684,7 +684,11 @@ but registry IDs, merge keys, parent links, metrics targets, source-native
guest IDs, external URLs, discovery targets, and actions must not. Children
such as VMs, containers, and storage inherit the parent's presentation and
native aliases so Overview, search, API/websocket/mobile, storage, backups,
and filters agree. A linked agent may enrich the canonical node but cannot
and filters agree. That inheritance stays in the child's `ProxmoxData`: only
the node itself lists its node identity, native name, and native-name aliases
as canonical aliases, because a child carrying them would answer to the node's
alert resource ID and every guest would show each node alert as its own.
A linked agent may enrich the canonical node but cannot
replace a configured override with its hostname; same-name cluster identity
remains provider scoped even after a native rename.
Physical-disk resources own cross-source disk identity. When Proxmox inventory
@@ -111,12 +111,18 @@ func canonicalAliases(resource Resource, primaryID, platformID, hostname string)
values = append(values, "agent:"+agentID)
}
if resource.Proxmox != nil {
values = append(
values,
strings.TrimSpace(resource.Proxmox.NodeIdentity),
strings.TrimSpace(resource.Proxmox.NodeName),
)
values = append(values, resource.Proxmox.NodeAliases...)
// Only the node owns its node identity. Guests and storage inherit
// the parent's NodeIdentity and NodeAliases for presentation and
// search, but as aliases they would make every child answer to the
// node's alert resource ID, so one node alert painted every guest.
if CanonicalResourceType(resource.Type) == ResourceTypeAgent {
values = append(
values,
strings.TrimSpace(resource.Proxmox.NodeIdentity),
strings.TrimSpace(resource.Proxmox.NodeName),
)
values = append(values, resource.Proxmox.NodeAliases...)
}
// PVE disk alerts predate the unified resource registry and retain a
// path-shaped resource reference. Resolve it to this exact physical
// disk so a UI mute reaches both new and restored occurrences (#2112).
@@ -176,6 +176,42 @@ func TestRefreshCanonicalIdentityKeepsProxmoxPresentationSeparateFromNativeAlias
}
}
func TestRefreshCanonicalIdentityKeepsParentNodeIdentityOffProxmoxChildren(t *testing.T) {
parentNode := &ProxmoxData{
Instance: "homelab",
NodeIdentity: "homelab-minipc",
NodeName: "minipc",
NodeAliases: []string{"minipc-old"},
}
for _, resourceType := range []ResourceType{ResourceTypeVM, ResourceTypeSystemContainer, ResourceTypeStorage} {
resource := Resource{
ID: string(resourceType) + "-1",
Type: resourceType,
Name: "cloudflared",
Proxmox: &ProxmoxData{
SourceID: "homelab:minipc:104",
Instance: parentNode.Instance,
NodeIdentity: parentNode.NodeIdentity,
NodeName: parentNode.NodeName,
NodeAliases: append([]string(nil), parentNode.NodeAliases...),
},
MetricsTarget: &MetricsTarget{ResourceType: string(resourceType), ResourceID: "homelab:minipc:104"},
}
RefreshCanonicalIdentity(&resource)
if resource.Canonical == nil {
t.Fatalf("%s: canonical identity missing", resourceType)
}
for _, alias := range resource.Canonical.Aliases {
if alias == "homelab-minipc" || alias == "minipc-old" {
t.Fatalf("%s carries parent node identity %q as an alias, so node alerts attach to it: %v",
resourceType, alias, resource.Canonical.Aliases)
}
}
}
}
func TestProxmoxDiskAlertAliasIsScopedAndNotPrimaryIdentity(t *testing.T) {
resource := Resource{
ID: "physical-disk-1", Type: ResourceTypePhysicalDisk,