v0.112.0 — E-2: GET /disks flags the backup-target drive

Additive backup_target field, true for the drive backing the PRIMARY tier.

The controller cannot work this out itself: settings.StoragePath.BackupTarget is
customer INTENT, and on the two boxes migrated by hand in E-1 that intent was
never recorded -- intent is empty while the drive really IS the target. Without
this flag the absent-target alarm could not name the drive on exactly the boxes
that currently have one.

omitempty + false on an older agent, so an old controller degrades to the generic
disconnect alarm rather than a wrong one.

Test asserts the target IS flagged AND the non-target is NOT, as a pair -- a
blanket true would satisfy a naive one-sided check.
This commit is contained in:
2026-07-29 08:20:27 +02:00
parent 38176ada9d
commit 958e54f6a6
3 changed files with 65 additions and 0 deletions
@@ -2,6 +2,7 @@ package localapi
import (
"context"
"encoding/json"
"io"
"log/slog"
"net/http"
@@ -106,3 +107,41 @@ func TestEjectStillAllowedOnANonTargetDrive(t *testing.T) {
"it must block exactly the target, not harden the whole eject path; got: %s", rr.Body.String())
}
}
// E-2 — GET /disks must FLAG the backup-target drive, because the controller cannot work it out.
//
// The controller's own StoragePath.BackupTarget is customer INTENT, and on a box migrated by hand
// (E-1, both demo boxes) nobody ever assigned it — intent is empty while the drive really is the
// target. Without this flag the absent-target alarm could not name the drive on exactly the boxes
// that have one, which is the only place it currently matters.
func TestDisksFlagsTheBackupTargetDrive(t *testing.T) {
h := backupTargetServer(t)
rr := do(t, h, http.MethodGet, "/disks", "A", "")
if rr.Code != http.StatusOK {
t.Fatalf("GET /disks = %d, body %s", rr.Code, rr.Body.String())
}
body := rr.Body.String()
// The target must be flagged and the non-target must not be — asserted as a pair, since a
// blanket true would satisfy a naive "is it flagged?" check.
var got struct {
Data struct {
Disks []struct {
Name string `json:"name"`
BackupTarget bool `json:"backup_target"`
} `json:"disks"`
} `json:"data"`
}
if err := json.Unmarshal([]byte(body), &got); err != nil {
t.Fatalf("decode: %v (body %s)", err, body)
}
seen := map[string]bool{}
for _, d := range got.Data.Disks {
seen[d.Name] = d.BackupTarget
}
if !seen["felhom-backup"] {
t.Errorf("felhom-backup is the primary tier's storage but backup_target is false; body: %s", body)
}
if seen["spare-drive"] {
t.Errorf("spare-drive is NOT the target but was flagged — a blanket true is not a signal; body: %s", body)
}
}