Files
felhom-controller/controller/internal/web/backup_target_keying_test.go
T
admin 4d6c8a6056 v0.184.1 — E-2b keying fix: the backup-target branch was unreachable
Caught before deploy by tracing a.Path back to its source, not by a failure. The
0.184.0 image is superseded and must not be shipped.

ReconcileDriveGates resolves the target as isTarget[a.Path], and a.Path is the
REGISTERED StoragePath -- for an external drive that is the GUEST path
/mnt/felhom-drives/<name>, not the agent's host MountPath (/mnt/<name>) that
/disks reports. driveTargetByPath keyed on MountPath alone, so the lookup never
matched: every absent drive, the target included, fell through to the generic
storage_disconnected.

The alarm would have looked wired, passed its own unit tests, shipped, and been
silently wrong on exactly the drive it exists for -- the same defect class E-2b
was opened to fix, one level down.

Now keyed under BOTH paths, mirroring planDriveGates which already registers
present[] under GuestPath and MountPath for the same reason.

Red-proof: MountPath-only keying fails with "the backup target is not resolvable
by its GUEST path -- the gate passes a.Path (the registered StoragePath), so the
backup-target branch would never fire".

Green gate: build + vet + test rc=0, run separately from this commit.
2026-07-29 08:30:36 +02:00

52 lines
2.2 KiB
Go

package web
import (
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/agentapi"
)
// THE BUG THIS PINS, found by tracing rather than by a failure:
//
// ReconcileDriveGates looks the target up as isTarget[a.Path], and a.Path is the REGISTERED
// StoragePath. For an external drive that is the GUEST path (/mnt/felhom-drives/<name>) — NOT the
// agent's host mount path (/mnt/<name>), which is what /disks reports as MountPath.
//
// Keying driveTargetByPath on MountPath alone therefore made the whole backup-target branch
// UNREACHABLE: every absent drive, the target included, fell through to the generic
// storage_disconnected. The alarm would have looked wired, shipped, and been silently wrong on
// exactly the drive it exists for — the same class of defect E-2b was opened to fix.
//
// planDriveGates already registers present[] under BOTH keys; this mirrors that convention.
func TestDriveTargetIsResolvableByTheGuestPathTheGateActuallyUses(t *testing.T) {
disks := []agentapi.DiskInfo{
{
Name: "felhom-backup", MountPath: "/mnt/nvme-1tb",
GuestPath: "/mnt/felhom-drives/nvme-1tb", BackupTarget: true,
},
{
Name: "spare", MountPath: "/mnt/spare",
GuestPath: "/mnt/felhom-drives/spare", BackupTarget: false,
},
}
got := driveTargetByPath(disks)
// The guest path is the one the gate hands in — this is the assertion that would have caught it.
if !got["/mnt/felhom-drives/nvme-1tb"] {
t.Error("the backup target is not resolvable by its GUEST path — the gate passes a.Path " +
"(the registered StoragePath), so the backup-target branch would never fire")
}
// The host path must keep working too: a legacy raw /mnt/<name> registration is gated on it.
if !got["/mnt/nvme-1tb"] {
t.Error("the backup target is not resolvable by its HOST path — legacy raw registrations break")
}
// A non-target must be false under both keys, or the map would flag everything.
if got["/mnt/felhom-drives/spare"] || got["/mnt/spare"] {
t.Error("a non-target drive is flagged as the backup target under one of its keys")
}
// An unknown path must be absent, not a zero-value surprise.
if got["/mnt/felhom-drives/never-seen"] {
t.Error("an unregistered path resolved as the backup target")
}
}