mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 03:20:11 +00:00
Fix reload-driven PVE host linking consistency (#1269)
This commit is contained in:
parent
0a7b93a842
commit
42a84fc5ca
2 changed files with 125 additions and 0 deletions
|
|
@ -1534,9 +1534,69 @@ func (s *State) UpdateNodesForInstance(instanceName string, nodes []Node) {
|
|||
})
|
||||
|
||||
s.Nodes = newNodes
|
||||
s.reconcileHostNodeLinksLocked()
|
||||
s.LastUpdate = time.Now()
|
||||
}
|
||||
|
||||
// reconcileHostNodeLinksLocked backfills host-side LinkedNodeID values from the
|
||||
// authoritative node-side LinkedHostAgentID links after node refreshes. This is
|
||||
// especially important after reloads/auto-registration, where nodes are rebuilt
|
||||
// from config and can recover their linked host agent by hostname before the host
|
||||
// agent has sent another report.
|
||||
func (s *State) reconcileHostNodeLinksLocked() {
|
||||
if len(s.Hosts) == 0 || len(s.Nodes) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
validNodeIDs := make(map[string]struct{}, len(s.Nodes))
|
||||
uniqueNodeForHost := make(map[string]string)
|
||||
ambiguousHosts := make(map[string]struct{})
|
||||
for _, node := range s.Nodes {
|
||||
nodeID := strings.TrimSpace(node.ID)
|
||||
if nodeID == "" {
|
||||
continue
|
||||
}
|
||||
validNodeIDs[nodeID] = struct{}{}
|
||||
|
||||
hostID := strings.TrimSpace(node.LinkedHostAgentID)
|
||||
if hostID == "" {
|
||||
continue
|
||||
}
|
||||
if _, ambiguous := ambiguousHosts[hostID]; ambiguous {
|
||||
continue
|
||||
}
|
||||
if existingNodeID, ok := uniqueNodeForHost[hostID]; ok && existingNodeID != nodeID {
|
||||
delete(uniqueNodeForHost, hostID)
|
||||
ambiguousHosts[hostID] = struct{}{}
|
||||
continue
|
||||
}
|
||||
uniqueNodeForHost[hostID] = nodeID
|
||||
}
|
||||
|
||||
for i := range s.Hosts {
|
||||
currentNodeID := strings.TrimSpace(s.Hosts[i].LinkedNodeID)
|
||||
candidateNodeID, hasCandidate := uniqueNodeForHost[s.Hosts[i].ID]
|
||||
|
||||
switch {
|
||||
case currentNodeID != "":
|
||||
if _, ok := validNodeIDs[currentNodeID]; !ok {
|
||||
if hasCandidate {
|
||||
s.Hosts[i].LinkedNodeID = candidateNodeID
|
||||
} else {
|
||||
s.Hosts[i].LinkedNodeID = ""
|
||||
}
|
||||
}
|
||||
case hasCandidate:
|
||||
s.Hosts[i].LinkedNodeID = candidateNodeID
|
||||
}
|
||||
|
||||
if s.Hosts[i].LinkedNodeID != "" {
|
||||
s.Hosts[i].LinkedVMID = ""
|
||||
s.Hosts[i].LinkedContainerID = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateVMs updates the VMs in the state
|
||||
func (s *State) UpdateVMs(vms []VM) {
|
||||
s.mu.Lock()
|
||||
|
|
|
|||
|
|
@ -186,6 +186,71 @@ func TestStateLinkHostAgentToNode(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestStateUpdateNodesForInstanceBackfillsHostLinkedNodeID(t *testing.T) {
|
||||
state := &State{
|
||||
Hosts: []Host{
|
||||
{ID: "host-1", Hostname: "pve01.local"},
|
||||
},
|
||||
}
|
||||
|
||||
state.UpdateNodesForInstance("cluster-a", []Node{
|
||||
{ID: "node-1", Instance: "cluster-a", Name: "pve01"},
|
||||
})
|
||||
|
||||
if len(state.Nodes) != 1 {
|
||||
t.Fatalf("expected 1 node, got %d", len(state.Nodes))
|
||||
}
|
||||
if state.Nodes[0].LinkedHostAgentID != "host-1" {
|
||||
t.Fatalf("LinkedHostAgentID = %q, want host-1", state.Nodes[0].LinkedHostAgentID)
|
||||
}
|
||||
if state.Hosts[0].LinkedNodeID != "node-1" {
|
||||
t.Fatalf("LinkedNodeID = %q, want node-1", state.Hosts[0].LinkedNodeID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateUpdateNodesForInstanceRepairsStaleHostLinkedNodeID(t *testing.T) {
|
||||
state := &State{
|
||||
Hosts: []Host{
|
||||
{ID: "host-1", Hostname: "pve01.local", LinkedNodeID: "node-old"},
|
||||
},
|
||||
Nodes: []Node{
|
||||
{ID: "node-old", Instance: "cluster-a", Name: "pve01", LinkedHostAgentID: "host-1"},
|
||||
},
|
||||
}
|
||||
|
||||
state.UpdateNodesForInstance("cluster-a", []Node{
|
||||
{ID: "node-new", Instance: "cluster-a", Name: "pve01"},
|
||||
})
|
||||
|
||||
if len(state.Nodes) != 1 {
|
||||
t.Fatalf("expected 1 node, got %d", len(state.Nodes))
|
||||
}
|
||||
if state.Nodes[0].LinkedHostAgentID != "host-1" {
|
||||
t.Fatalf("LinkedHostAgentID = %q, want host-1", state.Nodes[0].LinkedHostAgentID)
|
||||
}
|
||||
if state.Hosts[0].LinkedNodeID != "node-new" {
|
||||
t.Fatalf("LinkedNodeID = %q, want node-new", state.Hosts[0].LinkedNodeID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateUpdateNodesForInstanceDoesNotBackfillAmbiguousHostLink(t *testing.T) {
|
||||
state := &State{
|
||||
Hosts: []Host{
|
||||
{ID: "host-1", Hostname: "pve01.local"},
|
||||
},
|
||||
Nodes: []Node{
|
||||
{ID: "node-1", Instance: "cluster-a", Name: "pve01", LinkedHostAgentID: "host-1"},
|
||||
{ID: "node-2", Instance: "cluster-b", Name: "pve01", LinkedHostAgentID: "host-1"},
|
||||
},
|
||||
}
|
||||
|
||||
state.UpdateNodesForInstance("cluster-c", nil)
|
||||
|
||||
if state.Hosts[0].LinkedNodeID != "" {
|
||||
t.Fatalf("LinkedNodeID = %q, want empty", state.Hosts[0].LinkedNodeID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateSnapshotPreservesEmptyTemplateInventoryReadiness(t *testing.T) {
|
||||
state := &State{}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue