mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-25 13:10:03 +00:00
Record a relinked node/agent row as a manual-link fold
Main replaced the registry's linkMergedIDs with ManualLinkFold records on the holder row (#2721), which canonical-ID succession, reference resolution and report-merge all read. The relink of a split node and its agent still wrote the removed map, so the branch no longer built against main. When a relink's absent side is one of a joined node+agent row's own IDs, applyManualLinks now records it as a fold on that row, with the side's own source. Pin succession then keeps the link instead of re-keying it onto the joined ID, and a later report-merge of the joined row names the relinked pair, which replaces the link.
This commit is contained in:
@@ -7066,10 +7066,11 @@ another pair than the exclusions do, as a relink of the two rows a
|
||||
report-merge left does (a registry now keeps each exclusion's `CreatedAt`);
|
||||
a tie keeps the pair apart. Once a relink rejoins them, the declared link
|
||||
folds one of the rows it names into the other's ID, and `applyManualLinks`
|
||||
records that ID with the IDs manual links fold (it recognises the node's own
|
||||
IDs from the joined row's Proxmox facet, and the agent's source-specific
|
||||
ID), so pin succession keeps the link instead of re-keying it onto the
|
||||
joined ID and handing the decision back to the older exclusions. The paths
|
||||
records that as a `ManualLinkFold` on the joined row (it recognises the
|
||||
node's own IDs from the row's Proxmox facet, and the agent's source-specific
|
||||
ID), so pin succession treats the folded ID as observed and keeps the link
|
||||
instead of re-keying it onto the joined ID and handing the decision back to
|
||||
the older exclusions, and report-merge of the joined row names that pair. The paths
|
||||
that join rows after ingest apply the same decision: `applyManualLinks`
|
||||
skips a link between a node row and an agent row split after it, and the
|
||||
presentation filter (`ListForPresentation` and the broadcast) keeps such
|
||||
|
||||
@@ -202,22 +202,23 @@ func pinBelongsToAgent(pin ResourceIdentityPin, agent ResourceIdentity) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// joinedPairOwnsIDLocked reports whether id is an ID one side of a joined
|
||||
// Proxmox node+agent row holds on its own: the node's (derived from the
|
||||
// row's Proxmox facet, not its merged identity, whose first hostname can be
|
||||
// the agent's) or the agent's source-specific ID, which an agent reporting
|
||||
// no machine key holds when split.
|
||||
func (rr *ResourceRegistry) joinedPairOwnsIDLocked(joined *Resource, id string) bool {
|
||||
// joinedPairSideLocked reports whether id is an ID one side of a joined
|
||||
// Proxmox node+agent row holds on its own, and that side's source: the
|
||||
// node's own IDs (derived from the row's Proxmox facet, not its merged
|
||||
// identity, whose first hostname can be the agent's) or the agent's
|
||||
// source-specific ID, which an agent reporting no machine key holds when
|
||||
// split.
|
||||
func (rr *ResourceRegistry) joinedPairSideLocked(joined *Resource, id string) (DataSource, bool) {
|
||||
if joined == nil || joined.Proxmox == nil || joined.Agent == nil || CanonicalResourceType(joined.Type) != ResourceTypeAgent {
|
||||
return false
|
||||
return "", false
|
||||
}
|
||||
id = CanonicalResourceID(id)
|
||||
if agentID := normalizeSourceID(joined.Agent.AgentID); agentID != "" && id == rr.sourceSpecificID(ResourceTypeAgent, SourceAgent, agentID) {
|
||||
return true
|
||||
return SourceAgent, true
|
||||
}
|
||||
sourceID := normalizeSourceID(joined.Proxmox.SourceID)
|
||||
if sourceID == "" {
|
||||
return false
|
||||
return "", false
|
||||
}
|
||||
_, identity := resourceFromProxmoxNode(models.Node{
|
||||
ID: sourceID,
|
||||
@@ -226,7 +227,7 @@ func (rr *ResourceRegistry) joinedPairOwnsIDLocked(joined *Resource, id string)
|
||||
Instance: joined.Proxmox.Instance,
|
||||
ClusterName: joined.Proxmox.ClusterName,
|
||||
}, nil)
|
||||
return slices.Contains(rr.proxmoxNodeOwnIDs(sourceID, identity), id)
|
||||
return SourceProxmox, slices.Contains(rr.proxmoxNodeOwnIDs(sourceID, identity), id)
|
||||
}
|
||||
|
||||
// nodeAgentRowsSplit reports whether two listed rows are a Proxmox node and
|
||||
|
||||
@@ -4950,16 +4950,17 @@ func (rr *ResourceRegistry) applyManualLinks(thresholds map[DataSource]time.Dura
|
||||
otherID = link.ResourceA
|
||||
}
|
||||
other := rr.resources[otherID]
|
||||
if other == nil && otherID != primaryID && rr.joinedPairOwnsIDLocked(primary, otherID) {
|
||||
// A relink of a split node and its agent names the two rows,
|
||||
// and the declared link folds one of them into the other's ID
|
||||
// once the relink rejoins them. Treat the ID as folded by this
|
||||
// link so pin succession keeps the link rather than re-keying
|
||||
// it onto the joined ID, which would let the split decide again.
|
||||
if rr.linkMergedIDs == nil {
|
||||
rr.linkMergedIDs = make(map[string]struct{})
|
||||
if other == nil && otherID != primaryID {
|
||||
if source, ok := rr.joinedPairSideLocked(primary, otherID); ok {
|
||||
// A relink of a split node and its agent names the two rows,
|
||||
// and the declared link folds one of them into the other's
|
||||
// ID once the relink rejoins them. Record the fold so pin
|
||||
// succession keeps the link rather than re-keying it onto
|
||||
// the joined ID, which would let the split decide again,
|
||||
// and so report-merge of the joined row names this pair.
|
||||
recordManualLinkFold(primary, primaryID, &Resource{Sources: []DataSource{source}}, otherID)
|
||||
rr.indexLinkFoldsLocked(primary)
|
||||
}
|
||||
rr.linkMergedIDs[otherID] = struct{}{}
|
||||
}
|
||||
if other == nil || otherID == primaryID {
|
||||
continue
|
||||
|
||||
@@ -350,6 +350,19 @@ func TestOperatorSplitOverridesProxmoxNodeAgentLink(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertViews("after relink", 2, nodeAgentRows{joined: merged.joined})
|
||||
// The joined row records the relinked row it took in, so a
|
||||
// report-merge of it names the relink's own pair.
|
||||
folded := want.node
|
||||
if folded == merged.joined {
|
||||
folded = want.agent
|
||||
}
|
||||
recorded := false
|
||||
for _, fold := range adapter.currentRegistry().ManualLinkFolds(merged.joined) {
|
||||
recorded = recorded || (fold.HolderID == merged.joined && fold.FoldedID == folded)
|
||||
}
|
||||
if !recorded {
|
||||
t.Fatalf("joined %s records no fold of relinked %s: %+v", merged.joined, folded, adapter.currentRegistry().ManualLinkFolds(merged.joined))
|
||||
}
|
||||
// A rebuild that meets the agent first orders the joined row's
|
||||
// hostnames the agent's way; persisting its pins must not
|
||||
// re-key the relink onto the joined ID either.
|
||||
|
||||
Reference in New Issue
Block a user