Apply agent LXC filesystem data on the cluster/resources poll path

The host agent's node-local pct df inventory was only applied by the
per-node container poll fallback. Cluster instances are served by the
efficient cluster/resources path, which never called the enrichment, so
installs on that path showed rootfs-only container filesystems (and,
since the config-mount restoration, config-listed mounts with unknown
usage) no matter how healthy the linked agent was. Reported on #1477
after the reporter installed agents specifically to get per-mount usage.

Reproduced end to end against a live PVE cluster: the agent shipped the
full inventory, the server linked the agent and populated the
filesystem cache every report, and the poll path never read it. With
the enrichment applied after metadata enrichment, mirroring the
per-node path's ordering, the same rig surfaces real per-mount usage.

Refs #1477

Contract-Neutral: behavioral fix: cluster/resources poll path now applies agent pct df enrichment like the per-node path (#1477), no public contract delta
This commit is contained in:
rcourtman
2026-08-12 14:45:38 +01:00
parent b8d61b9e12
commit d6cf090737
2 changed files with 73 additions and 0 deletions
@@ -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.
@@ -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)
}
}