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
+13
View File
@@ -143,6 +143,10 @@ type DiskInfo struct {
// HDD look available when it wasn't bound. Only meaningful for user-data drives. LEGACY (per-drive
// `pct set -mpN` model) — the intermediary model uses BoundUnderParent.
GuestAttached bool `json:"guest_attached"`
// BackupTarget (E-2) reports that this drive backs the PRIMARY whole-guest backup tier. Additive:
// an older controller ignores it. It is the agent's answer, not the controller's intent flag —
// on a hand-migrated box (E-1) intent is unset while the drive really is the target.
BackupTarget bool `json:"backup_target,omitempty"`
// GuestPath is the drive's STABLE in-guest path in the intermediary-mount model
// (/mnt/felhom-drives/<name>). This is what the controller repoints HDD_PATH to and registers as the
// storage path. Set for /mnt/<name> drives; "" otherwise. Distinct from MountPath (the RAW host PVE
@@ -177,6 +181,9 @@ func (s *Server) handleDisks(w http.ResponseWriter, r *http.Request, vmid int) {
// F9: which host mount paths are actually BOUND into THIS guest's config (guest-usable, not just
// host-present). A bind's mp= equals the guest path, which is the drive's host mount path (`where`).
boundPaths := s.guestBoundPaths(r.Context(), vmid)
// E-2: the PRIMARY tier's storage id — the whole-guest vzdump destination. "" when no tier
// carries a target (the legacy single-tier shape), which correctly flags nothing.
primaryTargetID := s.primaryTier().TargetID
out := make([]DiskInfo, 0, len(targets))
for _, t := range targets {
di := DiskInfo{
@@ -188,6 +195,12 @@ func (s *Server) handleDisks(w http.ResponseWriter, r *http.Request, vmid int) {
UsedBytes: t.UsedBytes,
UsedFraction: t.UsedFraction,
GuestAttached: t.MountPath != "" && boundPaths[t.MountPath],
// E-2: is THIS drive the whole-guest backup target? The agent is the only component that
// can answer — the controller's own StoragePath.BackupTarget is customer INTENT, and on a
// box migrated by hand (E-1) nobody ever assigned it, so intent is empty while the drive
// really is the target. Reported here so the controller can name the drive in an
// absent-target alarm and hide the destructive controls the agent would refuse anyway.
BackupTarget: t.Name == primaryTargetID,
}
// Intermediary model: the stable in-guest path + whether felhom-data is bound under the parent.
// Only user-data /mnt/<name> drives have a guest path (system/backup mounts never cross in).