From 108b113383c52af7aad8a52213edfcaf06ce0943 Mon Sep 17 00:00:00 2001 From: rcourtman <8825017+rcourtman@users.noreply.github.com> Date: Tue, 6 Oct 2026 11:03:01 +0100 Subject: [PATCH] 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. --- .../internal/subsystems/unified-resources.md | 6 +++- .../unifiedresources/canonical_identity.go | 18 ++++++---- .../canonical_identity_test.go | 36 +++++++++++++++++++ 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/unified-resources.md b/docs/release-control/v6/internal/subsystems/unified-resources.md index 31e4da09fa..434aa7392d 100644 --- a/docs/release-control/v6/internal/subsystems/unified-resources.md +++ b/docs/release-control/v6/internal/subsystems/unified-resources.md @@ -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 diff --git a/internal/unifiedresources/canonical_identity.go b/internal/unifiedresources/canonical_identity.go index 838def2176..cdafee8cd2 100644 --- a/internal/unifiedresources/canonical_identity.go +++ b/internal/unifiedresources/canonical_identity.go @@ -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). diff --git a/internal/unifiedresources/canonical_identity_test.go b/internal/unifiedresources/canonical_identity_test.go index 3418d614e1..ad7f95816d 100644 --- a/internal/unifiedresources/canonical_identity_test.go +++ b/internal/unifiedresources/canonical_identity_test.go @@ -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,