From 5bd85dd5ae505f1f191b2acdab2b9077fb5ffe3c Mon Sep 17 00:00:00 2001 From: rcourtman Date: Thu, 26 Mar 2026 23:25:08 +0000 Subject: [PATCH] Preserve cached guest metadata when VM status calls fail (#1319) --- .../monitoring/proxmox_vm_cluster_resource.go | 2 +- .../proxmox_vm_cluster_resource_test.go | 73 +++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/internal/monitoring/proxmox_vm_cluster_resource.go b/internal/monitoring/proxmox_vm_cluster_resource.go index fdf569ccb..803d74689 100644 --- a/internal/monitoring/proxmox_vm_cluster_resource.go +++ b/internal/monitoring/proxmox_vm_cluster_resource.go @@ -370,7 +370,7 @@ func (m *Monitor) pollEfficientQEMUResource( } } - if res.Status == "running" && detailedStatus != nil && detailedStatus.Agent.Value > 0 { + if res.Status == "running" { m.runGuestAgentVMWork(ctx, instanceName, res.Node, res.Name, res.VMID, func(agentCtx context.Context) { guestIPs, guestIfaces, guestOSName, guestOSVersion, guestAgentVersion := m.fetchGuestAgentMetadata(agentCtx, client, instanceName, res.Node, res.Name, res.VMID, detailedStatus) if len(guestIPs) > 0 { diff --git a/internal/monitoring/proxmox_vm_cluster_resource_test.go b/internal/monitoring/proxmox_vm_cluster_resource_test.go index 3678c80a9..a96796e07 100644 --- a/internal/monitoring/proxmox_vm_cluster_resource_test.go +++ b/internal/monitoring/proxmox_vm_cluster_resource_test.go @@ -32,6 +32,11 @@ type rotatingGuestAgentClusterClient struct { fsDelay time.Duration } +type transientStatusFailureClusterClient struct { + stubPVEClient + resources []proxmox.ClusterResource +} + func (c *slowGuestAgentClusterClient) GetClusterResources(ctx context.Context, resourceType string) ([]proxmox.ClusterResource, error) { return c.resources, nil } @@ -126,6 +131,14 @@ func (c *rotatingGuestAgentClusterClient) GetVMAgentVersion(ctx context.Context, return "", nil } +func (c *transientStatusFailureClusterClient) GetClusterResources(ctx context.Context, resourceType string) ([]proxmox.ClusterResource, error) { + return c.resources, nil +} + +func (c *transientStatusFailureClusterClient) GetVMStatus(ctx context.Context, node string, vmid int) (*proxmox.VMStatus, error) { + return nil, context.DeadlineExceeded +} + func TestGuestAgentFSInfoBudgetHonorsConfiguredTimeouts(t *testing.T) { t.Parallel() @@ -261,6 +274,66 @@ func TestPollVMsAndContainersEfficientRotatesGuestAgentPriorityAcrossPolls(t *te } } +func TestPollVMsAndContainersEfficientPreservesCachedGuestMetadataWhenStatusUnavailable(t *testing.T) { + t.Setenv("PULSE_DATA_DIR", t.TempDir()) + + client := &transientStatusFailureClusterClient{ + resources: []proxmox.ClusterResource{ + {Type: "qemu", Node: "node1", VMID: 100, Name: "vm100", Status: "running", MaxMem: 8 * 1024, Mem: 4 * 1024, MaxDisk: 100 * 1024 * 1024 * 1024, MaxCPU: 4}, + }, + } + + mon := newTestPVEMonitor("pve1") + defer mon.alertManager.Stop() + defer mon.notificationMgr.Stop() + + mon.rateTracker = NewRateTracker() + mon.guestMetadataCache = map[string]guestMetadataCacheEntry{ + guestMetadataCacheKey("pve1", "node1", 100): { + ipAddresses: []string{"192.168.1.50"}, + networkInterfaces: []models.GuestNetworkInterface{ + {Name: "Ethernet0", MAC: "00:11:22:33:44:55", Addresses: []string{"192.168.1.50"}}, + }, + osName: "Windows", + osVersion: "Server 2022", + agentVersion: "8.2.0", + fetchedAt: time.Now(), + }, + } + mon.guestMetadataLimiter = make(map[string]time.Time) + mon.vmRRDMemCache = make(map[string]rrdMemCacheEntry) + mon.vmAgentMemCache = make(map[string]agentMemCacheEntry) + mon.guestAgentFSInfoTimeout = 250 * time.Millisecond + mon.guestAgentNetworkTimeout = 250 * time.Millisecond + mon.guestAgentOSInfoTimeout = 250 * time.Millisecond + mon.guestAgentVersionTimeout = 250 * time.Millisecond + mon.guestAgentRetries = 0 + mon.guestAgentWorkSlots = make(chan struct{}, 1) + + if ok := mon.pollVMsAndContainersEfficient(context.Background(), "pve1", "", false, client, map[string]string{"node1": "online"}); !ok { + t.Fatal("pollVMsAndContainersEfficient() returned false") + } + + state := mon.state.GetSnapshot() + if len(state.VMs) != 1 { + t.Fatalf("expected 1 VM, got %d", len(state.VMs)) + } + + vm := state.VMs[0] + if len(vm.IPAddresses) != 1 || vm.IPAddresses[0] != "192.168.1.50" { + t.Fatalf("expected cached IPs to be preserved, got %#v", vm.IPAddresses) + } + if len(vm.NetworkInterfaces) != 1 || vm.NetworkInterfaces[0].Name != "Ethernet0" { + t.Fatalf("expected cached interfaces to be preserved, got %#v", vm.NetworkInterfaces) + } + if vm.OSName != "Windows" || vm.OSVersion != "Server 2022" { + t.Fatalf("expected cached OS info to be preserved, got %q %q", vm.OSName, vm.OSVersion) + } + if vm.AgentVersion != "8.2.0" { + t.Fatalf("expected cached agent version to be preserved, got %q", vm.AgentVersion) + } +} + func TestPollVMsAndContainersEfficientCarriesForwardPreviousIndividualDisks(t *testing.T) { t.Setenv("PULSE_DATA_DIR", t.TempDir())