Use unavailable sentinel when VM guest agent disk query fails

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/<allocated>)" 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.
This commit is contained in:
rcourtman 2026-07-13 09:41:11 +01:00
parent 06a6028b29
commit e1720ca219
2 changed files with 58 additions and 0 deletions

View file

@ -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)
}
}

View file

@ -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
}