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.
This commit is contained in:
@@ -1,5 +1,26 @@
|
||||
## Changelog
|
||||
|
||||
### v0.184.1 — E-2b keying fix: the backup-target branch was unreachable (2026-07-29)
|
||||
|
||||
**Caught before deploy by tracing, not by a failure — and the 0.184.0 image is therefore 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 the map 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-proofed: reverting to 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"*.
|
||||
|
||||
### v0.184.0 — E-2b + Part 5: the drive-absent alarm that was never wired (2026-07-29) — MinAgent 0.112.0
|
||||
|
||||
**`NotifyStorageDisconnected` and `NotifyStorageReconnected` were defined and called from NOWHERE.**
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -600,8 +600,17 @@ func (s *Server) gateWhere(w http.ResponseWriter, r *http.Request) (string, bool
|
||||
// intent was never recorded while the drive really is the target. An older agent omits the field, so
|
||||
// every entry is false and we degrade to the generic disconnect alarm — never a wrong one.
|
||||
func driveTargetByPath(disks []agentapi.DiskInfo) map[string]bool {
|
||||
out := make(map[string]bool, len(disks))
|
||||
out := make(map[string]bool, 2*len(disks))
|
||||
for _, d := range disks {
|
||||
// BOTH keyings, mirroring planDriveGates (which registers present[] under GuestPath AND
|
||||
// MountPath). The gate's a.Path is the REGISTERED StoragePath, and for an external drive that
|
||||
// is the GUEST path /mnt/felhom-drives/<name> — not the agent's host /mnt/<name>. Keying this
|
||||
// map on MountPath alone made the backup-target branch unreachable: every absent drive,
|
||||
// including the target, fell through to the generic storage_disconnected. Caught before
|
||||
// deploy by tracing a.Path back to its source rather than assuming it matched.
|
||||
if d.GuestPath != "" {
|
||||
out[d.GuestPath] = d.BackupTarget
|
||||
}
|
||||
if d.MountPath != "" {
|
||||
out[d.MountPath] = d.BackupTarget
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user