Give mock mode Docker-in-LXC coverage

The Proxmox page's nested-container row cue, the drawer's nested Docker
card, and the proxmox-pve platform scoping of nested workloads had no
local reproduction at all: mock mode never generated a Docker host with
the proxmox-lxc-docker: identity prefix, so the surface could only be
seen against a live estate with node agents, exec-scoped tokens, and
the server-side inventory opt-in. That is how it shipped invisible.

The fixture now marks the first two running LXC guests as probed
Docker hosts and appends a nested Docker host for each, following the
production proxmoxGuestDockerAgentID convention of
proxmox-lxc-docker:<instance>:<node>:<vmid>. One guest nests a single
container and one nests several, so the row cue renders both its
singular and plural counts. The nested hosts mirror the shape of the
real pct exec inventory: guest-derived sizing and usage, one rootfs
disk, and no native engine inventory, host I/O rates, temperature,
machine ID, or Swarm membership.
This commit is contained in:
rcourtman
2026-08-20 11:49:12 +01:00
parent 2483dbed9c
commit cdec581d00
2 changed files with 187 additions and 4 deletions
+98
View File
@@ -641,6 +641,7 @@ func buildFixtureState(config MockConfig) models.StateSnapshot {
ensureFreshFilesystemFixture(&data)
ensureConfigOnlyMountFixture(&data)
ensureMockDockerInLXCFixture(&data, config)
// Calculate stats
data.Stats.StartTime = time.Now()
@@ -2690,6 +2691,103 @@ func generateDockerHosts(config MockConfig) []models.DockerHost {
return hosts
}
// mockProxmoxLXCDockerHostSourcePrefix mirrors the production Docker host
// identity prefix from monitoring.proxmoxGuestDockerAgentID (and the platform
// scope match in unifiedresources): hosts whose source ID carries it are
// treated as Docker runtimes discovered inside a Proxmox LXC guest.
const mockProxmoxLXCDockerHostSourcePrefix = "proxmox-lxc-docker:"
// ensureMockDockerInLXCFixture adds Proxmox-side Docker-in-LXC discovery to
// the fixture estate: the first running LXC guests are marked HasDocker (as
// the node's pct exec socket probe would) and each gains a nested Docker host
// whose identity follows the production proxmoxGuestDockerAgentID convention,
// "proxmox-lxc-docker:" + the guest's instance:node:vmid coordinates. That
// prefix is what grants the nested workloads the proxmox-pve platform scope
// and lets the Proxmox page match them back to their parent guest for the
// row cue and drawer card, so without this fixture the whole surface has no
// local reproduction.
func ensureMockDockerInLXCFixture(data *models.StateSnapshot, config MockConfig) {
if data == nil || config.DockerHostCount <= 0 {
return
}
// One single-container guest and one busier guest, so the row cue
// renders both its singular and plural counts.
nestedCounts := []int{1, 4}
now := time.Now()
nested := 0
for i := range data.Containers {
if nested >= len(nestedCounts) {
break
}
ct := &data.Containers[i]
if ct.Status != "running" || ct.IsOCI {
continue
}
ct.HasDocker = true
ct.DockerCheckedAt = now
sourceID := fmt.Sprintf("%s%s:%s:%d", mockProxmoxLXCDockerHostSourcePrefix, ct.Instance, ct.Node, ct.VMID)
containers := generateDockerContainers(ct.Name, i, config, false)
if len(containers) > nestedCounts[nested] {
containers = containers[:nestedCounts[nested]]
}
version := dockerVersions[rand.Intn(len(dockerVersions))]
kernelVersion := "6.8.12-4-pve"
for _, node := range data.Nodes {
if node.Name == ct.Node && node.KernelVersion != "" {
kernelVersion = node.KernelVersion
break
}
}
// Host info mirrors what parseProxmoxGuestDockerInventory plus
// enrichGuestDockerReportFromContainer report for a real guest: the
// guest's own sizing and usage, one rootfs disk, no machine ID, no
// agent version, no network interfaces.
host := models.DockerHost{
ID: sourceID,
AgentID: sourceID,
Hostname: ct.Name,
DisplayName: ct.Name,
OS: "linux",
KernelVersion: kernelVersion,
Architecture: "x86_64",
Runtime: "docker",
RuntimeVersion: version,
DockerVersion: version,
CPUs: ct.CPUs,
TotalMemoryBytes: ct.Memory.Total,
UptimeSeconds: ct.Uptime,
CPUUsage: clampFloat(ct.CPU*100, 0, 100),
Memory: models.Memory{
Total: ct.Memory.Total,
Used: ct.Memory.Used,
Free: ct.Memory.Free,
Usage: ct.Memory.Usage,
},
Disks: []models.Disk{{
Total: ct.Disk.Total,
Used: ct.Disk.Used,
Free: ct.Disk.Free,
Usage: ct.Disk.Usage,
Mountpoint: "/",
Type: "rootfs",
}},
Status: "online",
LastSeen: now,
IntervalSeconds: 30,
Containers: containers,
}
data.DockerHosts = append(data.DockerHosts, host)
data.ConnectionHealth[dockerConnectionPrefix+host.ID] = true
nested++
}
}
func ensureMockDockerNativeInventory(host *models.DockerHost, hostIndex int, now time.Time) {
if host == nil {
return
+89 -4
View File
@@ -18,14 +18,18 @@ func TestBuildFixtureStateIncludesDockerHosts(t *testing.T) {
data := buildFixtureState(cfg)
if len(data.DockerHosts) != cfg.DockerHostCount {
t.Fatalf("expected %d docker hosts, got %d", cfg.DockerHostCount, len(data.DockerHosts))
}
// Nested Docker-in-LXC hosts are appended on top of the configured
// agent-reported hosts and deliberately carry no native engine
// inventory, so the native-host expectations exclude them.
nativeHosts := 0
for _, host := range data.DockerHosts {
if host.ID == "" {
t.Fatalf("docker host missing id: %+v", host)
}
if strings.HasPrefix(host.ID, mockProxmoxLXCDockerHostSourcePrefix) {
continue
}
nativeHosts++
if len(host.Containers) == 0 {
t.Fatalf("docker host %s has no containers", host.Hostname)
}
@@ -42,6 +46,9 @@ func TestBuildFixtureStateIncludesDockerHosts(t *testing.T) {
t.Fatalf("docker host %s has no engine storage usage inventory: %+v", host.Hostname, host.StorageUsage)
}
}
if nativeHosts != cfg.DockerHostCount {
t.Fatalf("expected %d native docker hosts, got %d", cfg.DockerHostCount, nativeHosts)
}
}
func TestComputeGuestCountsHandlesZeroBaselines(t *testing.T) {
@@ -300,6 +307,11 @@ func TestBuildFixtureStatePopulatesDockerHostIORates(t *testing.T) {
}
for _, host := range data.DockerHosts {
if strings.HasPrefix(host.ID, mockProxmoxLXCDockerHostSourcePrefix) {
// Docker-in-LXC hosts mirror the production pct exec inventory,
// which reports no host-level I/O rates or temperature.
continue
}
if host.Status == "offline" {
if host.NetInRate != 0 || host.NetOutRate != 0 || host.DiskReadRate != 0 || host.DiskWriteRate != 0 {
t.Fatalf("offline docker host %s should have zero I/O rates", host.ID)
@@ -893,3 +905,76 @@ func TestEnsureConfigOnlyMountFixtureSeedsUnknownUsageMount(t *testing.T) {
t.Fatalf("config-only mount must keep its mp key and device, got %+v", archive)
}
}
func TestBuildFixtureStateIncludesDockerInLXCFixture(t *testing.T) {
cfg := DefaultConfig
cfg.StoppedPercent = 0 // keep every LXC running so the fixture always binds
data := buildFixtureState(cfg)
var nested []models.DockerHost
for _, host := range data.DockerHosts {
if strings.HasPrefix(host.ID, mockProxmoxLXCDockerHostSourcePrefix) {
nested = append(nested, host)
}
}
if len(nested) != 2 {
t.Fatalf("expected 2 nested Docker-in-LXC hosts, got %d", len(nested))
}
guestsBySourceID := make(map[string]models.Container, len(data.Containers))
for _, ct := range data.Containers {
key := fmt.Sprintf("%s%s:%s:%d", mockProxmoxLXCDockerHostSourcePrefix, ct.Instance, ct.Node, ct.VMID)
guestsBySourceID[key] = ct
}
containerCounts := make(map[int]bool)
for _, host := range nested {
guest, ok := guestsBySourceID[host.ID]
if !ok {
t.Fatalf("nested docker host %s has no matching LXC guest", host.ID)
}
if guest.Status != "running" {
t.Fatalf("nested docker host %s bound to non-running guest %s", host.ID, guest.ID)
}
if !guest.HasDocker || guest.DockerCheckedAt.IsZero() {
t.Fatalf("parent LXC %s not marked as probed Docker host", guest.ID)
}
if host.AgentID != host.ID {
t.Fatalf("nested docker host agent id %q must equal its source id %q", host.AgentID, host.ID)
}
if host.Hostname != guest.Name {
t.Fatalf("nested docker host hostname %q should be the guest name %q", host.Hostname, guest.Name)
}
if len(host.Containers) == 0 {
t.Fatalf("nested docker host %s has no containers", host.ID)
}
containerCounts[len(host.Containers)] = true
if len(host.Images) != 0 || len(host.Volumes) != 0 || len(host.Networks) != 0 || host.StorageUsage != nil {
t.Fatalf("nested docker host %s must not fabricate native engine inventory (production pct exec inventory has none)", host.ID)
}
if host.Swarm != nil {
t.Fatalf("nested docker host %s must not join the mock Swarm", host.ID)
}
if !data.ConnectionHealth[dockerConnectionPrefix+host.ID] {
t.Fatalf("nested docker host %s missing connection health entry", host.ID)
}
}
if !containerCounts[1] {
t.Fatalf("expected one single-container nested host so the row cue renders its singular form, got counts %v", containerCounts)
}
// Disabling Docker in the mock estate disables the nested fixture too.
cfg.DockerHostCount = 0
dataNoDocker := buildFixtureState(cfg)
for _, host := range dataNoDocker.DockerHosts {
if strings.HasPrefix(host.ID, mockProxmoxLXCDockerHostSourcePrefix) {
t.Fatalf("nested docker host %s generated despite DockerHostCount=0", host.ID)
}
}
for _, ct := range dataNoDocker.Containers {
if ct.HasDocker {
t.Fatalf("LXC %s marked HasDocker despite DockerHostCount=0", ct.ID)
}
}
}