From e1720ca219b3add797553bdf3fc2d62bfdeae58f Mon Sep 17 00:00:00 2001 From: rcourtman Date: Mon, 13 Jul 2026 09:41:11 +0100 Subject: [PATCH] Use unavailable sentinel when VM guest agent disk query fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When fetchVMFSInfo fails (agent not running, timeout, permission denied, no filesystems), the builder passed the cluster/resources numbers through — and PVE always reports 0 used for QEMU — so the UI rendered a confident "0% (0 B/)" for every affected VM instead of the unavailable dash plus diskStatusReason tooltip. The agent-disabled path already used the -1 sentinel for exactly this; apply it on the error path too. The stabilizer can still replace the sentinel with the previous good sample when recent agent evidence exists. Reported by Massimo Simoni (support, 2026-07-13): screenshot showed every VM at 0% disk with only containers reporting real usage. --- .../monitoring/guest_disk_stability_test.go | 50 +++++++++++++++++++ .../monitoring/monitor_pve_guest_builders.go | 8 +++ 2 files changed, 58 insertions(+) diff --git a/internal/monitoring/guest_disk_stability_test.go b/internal/monitoring/guest_disk_stability_test.go index c61818ee1..f449e9f53 100644 --- a/internal/monitoring/guest_disk_stability_test.go +++ b/internal/monitoring/guest_disk_stability_test.go @@ -1,10 +1,13 @@ package monitoring import ( + "context" + "errors" "testing" "time" "github.com/rcourtman/pulse-go-rewrite/internal/models" + "github.com/rcourtman/pulse-go-rewrite/pkg/proxmox" ) func TestStabilizeGuestLowTrustDiskCarriesForwardPreviousSnapshot(t *testing.T) { @@ -133,3 +136,50 @@ func TestStabilizeGuestLowTrustDiskCarriesPreviouslyForwardedSnapshotWithAgentEv t.Fatalf("reason = %q, want prev-no-filesystems", reason) } } + +type failingFSInfoClient struct { + PVEClientInterface + err error +} + +func (c failingFSInfoClient) GetVMFSInfo(ctx context.Context, node string, vmid int) ([]proxmox.VMFileSystem, error) { + return nil, c.err +} + +func TestUpdateVMDisksFromGuestAgentFSInfoErrorUsesUnavailableSentinel(t *testing.T) { + // Cluster/resources reports 0 used for QEMU, so a failed agent query must + // not pass those zeros through as a confident 0% (it renders as + // "0% (0 B/120 GB)" instead of unavailable + reason in the UI). + m := &Monitor{} + res := proxmox.ClusterResource{Node: "pve1", VMID: 100, Name: "vm100", MaxDisk: 128849018880} + client := failingFSInfoClient{err: errors.New("guest agent request timeout")} + + total, used, free, usage, disks, fromAgent, reason := m.updateVMDisksFromGuestAgentFSInfo( + context.Background(), + "test-instance", + res, + client, + uint64(res.MaxDisk), + 0, + 0, + ) + + if usage != -1 { + t.Fatalf("usage = %v, want -1 sentinel", usage) + } + if reason != "agent-timeout" { + t.Fatalf("reason = %q, want agent-timeout", reason) + } + if fromAgent { + t.Fatal("fromAgent = true, want false") + } + if total != uint64(res.MaxDisk) { + t.Fatalf("total = %d, want allocated size %d", total, res.MaxDisk) + } + if used != 0 || free != total { + t.Fatalf("used/free = %d/%d, want 0/%d", used, free, total) + } + if disks != nil { + t.Fatalf("disks = %v, want nil", disks) + } +} diff --git a/internal/monitoring/monitor_pve_guest_builders.go b/internal/monitoring/monitor_pve_guest_builders.go index cca26c69c..8b2d052c5 100644 --- a/internal/monitoring/monitor_pve_guest_builders.go +++ b/internal/monitoring/monitor_pve_guest_builders.go @@ -701,6 +701,14 @@ func (m *Monitor) updateVMDisksFromGuestAgentFSInfo( fsInfo, diskStatusReason, ok := m.fetchVMFSInfo(ctx, instanceName, res, client) if !ok { + // No trustworthy usage data: cluster/resources reports 0 used for + // QEMU, so passing it through renders a confident "0%" in the UI. + // Use the -1 sentinel (same as the agent-disabled path) so the + // frontend shows unavailable + diskStatusReason instead; the + // stabilizer may still replace this with the previous good sample. + if diskTotal > 0 { + diskUsage = -1 + } return diskTotal, diskUsed, diskTotal - diskUsed, diskUsage, nil, false, diskStatusReason }