diff --git a/internal/monitoring/monitor_pve_guest_lxc.go b/internal/monitoring/monitor_pve_guest_lxc.go index a827708ae..7cbb4c379 100644 --- a/internal/monitoring/monitor_pve_guest_lxc.go +++ b/internal/monitoring/monitor_pve_guest_lxc.go @@ -179,6 +179,12 @@ func (m *Monitor) buildContainerFromClusterResource( } m.enrichContainerMetadata(ctx, client, instanceName, res.Node, &container, statusSnapshot) + // The per-node fallback path applies node-local pct df data after metadata + // enrichment (monitor_polling_containers.go); the efficient cluster/resources + // path must do the same or installs served by it never surface the host + // agent's per-mount usage and fall back to config-listed mounts with + // unknown usage (#1477). + m.enrichContainerWithAgentLXCFilesystems(instanceName, res.Node, &container, sampleTime) // For non-running containers, zero out resource usage metrics to prevent false alerts. // Proxmox may report stale or residual metrics for stopped containers. diff --git a/internal/monitoring/monitor_pve_guest_lxc_test.go b/internal/monitoring/monitor_pve_guest_lxc_test.go index 88c55a2f6..4b1e82bb6 100644 --- a/internal/monitoring/monitor_pve_guest_lxc_test.go +++ b/internal/monitoring/monitor_pve_guest_lxc_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/rcourtman/pulse-go-rewrite/internal/models" + agentshost "github.com/rcourtman/pulse-go-rewrite/pkg/agents/host" "github.com/rcourtman/pulse-go-rewrite/pkg/proxmox" ) @@ -393,3 +394,69 @@ func TestIssue1477ConfigOnlyMountsSurviveIntoContainerDisks(t *testing.T) { t.Fatalf("config-only mount must keep device and mp key, got %+v", archive) } } + +// The efficient cluster/resources path must apply the host agent's node-local +// pct df data the same way the per-node fallback path does. Before the #1477 +// fix it skipped that enrichment entirely, so cluster-served installs never +// surfaced per-mount usage regardless of a healthy linked agent. +func TestBuildContainerFromClusterResource_AppliesAgentLXCFilesystems(t *testing.T) { + monitor := newTestMonitor(t) + monitor.state.UpdateNodesForInstance("cluster-a", []models.Node{{ + ID: "cluster-a-pve-a", + Name: "pve-a", + Instance: "cluster-a", + Status: "online", + }}) + + now := time.Now() + monitor.applyAgentLXCFilesystems("cluster-a-pve-a", "agent-1", &agentshost.ProxmoxLXCInventory{ + Containers: []agentshost.ProxmoxLXCContainer{{ + VMID: 202, + Name: "cache-ct", + Disks: []agentshost.Disk{ + {Device: "local:202/vm-202-disk-0.raw", Mountpoint: "/", Type: "rootfs", TotalBytes: 32 << 30, UsedBytes: 8 << 30, FreeBytes: 24 << 30, Usage: 25}, + {Device: "/mnt/tank/cache", Mountpoint: "/data", Type: "mp0", TotalBytes: 100 << 30, UsedBytes: 50 << 30, FreeBytes: 50 << 30, Usage: 50}, + }, + }}, + CollectedAt: now.UTC(), + }, now, 30) + + client := &stubPVEClientLXCStatus{ + containerStatus: &proxmox.Container{Status: "running"}, + } + resource := proxmox.ClusterResource{ + Type: "lxc", + Node: "pve-a", + Name: "cache-ct", + Status: "running", + VMID: 202, + MaxCPU: 2, + MaxMem: 4096, + Mem: 2048, + MaxDisk: 32 << 30, + Disk: 8 << 30, + } + + container, _, _, _, ok := monitor.buildContainerFromClusterResource( + context.Background(), + "cluster-a", + resource, + client, + map[int]bool{}, + ) + if !ok { + t.Fatal("expected container to be built") + } + if len(container.Disks) != 2 { + t.Fatalf("expected 2 disks from agent pct df enrichment, got %d: %+v", len(container.Disks), container.Disks) + } + var dataMount *models.Disk + for i := range container.Disks { + if container.Disks[i].Mountpoint == "/data" { + dataMount = &container.Disks[i] + } + } + if dataMount == nil || dataMount.Total != 100<<30 || dataMount.Used != 50<<30 { + t.Fatalf("expected /data mount with real usage from agent data, got %+v", container.Disks) + } +}