v0.56.0: resolve a raw enrolled drive's durable-id from the mount table

durableIDForMount was Observe-only, so a raw (non-PVE-storage) drive's enroll
intent + guest-bind went unrecorded ("durable-id unresolved") — it mounted+bound
but wasn't intent-tracked (RegistryKnownTargets skips intent==new). Fall back to
resolving the mount's device fs-UUID (HostReader.Mounts + ResolveUUID) →
uuid:<fs-uuid>, same scheme as Observe. Test + red-proof. Residual:
ReassertGuestBinds still Observe-based (raw guest-bind not re-asserted post-reboot).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 18:29:22 +02:00
parent 4f8d2fcb57
commit e593fb277a
4 changed files with 84 additions and 7 deletions
+21 -6
View File
@@ -883,13 +883,28 @@ func (s *Server) ReassertGuestBinds(ctx context.Context) {
// durableIDForMount resolves the durable-id of the storage mounted at `where` (from the agent's own
// storage view) — the key the intent store records enroll/eject against. "" if not resolvable.
func (s *Server) durableIDForMount(ctx context.Context, where string) string {
targets, err := s.storage.Observe(ctx)
if err != nil {
return ""
// Preferred: the storage view (PVE storages) already carries the derived durable-id.
if targets, err := s.storage.Observe(ctx); err == nil {
for _, t := range targets {
if t.MountPath == where {
return t.DurableID
}
}
}
for _, t := range targets {
if t.MountPath == where {
return t.DurableID
// Fallback (Impl-2b): a RAW enrolled drive is NOT a PVE storage, so it never appears in Observe —
// which left its enroll/eject intent + guest-bind unrecorded ("durable-id unresolved"). Resolve the
// fs-UUID directly from the mount table: the device mounted at `where` → its by-uuid identity. This
// is the SAME scheme Observe derives ("uuid:<fs-uuid>"), so intent keys stay consistent.
if s.host != nil {
if mounts, err := s.host.Mounts(); err == nil {
for _, m := range mounts {
if m.MountPoint == where {
if uuid, ok := s.host.ResolveUUID(m.Device); ok && uuid != "" {
return "uuid:" + uuid
}
break
}
}
}
}
return ""
@@ -0,0 +1,43 @@
package localapi
import (
"context"
"testing"
"gitea.dooplex.hu/admin/felhom-agent/internal/storage"
)
// uuidHostReader is a HostReader that resolves specific device fs-UUIDs (fakeHostReader always fails).
type uuidHostReader struct {
mounts []storage.Mount
uuids map[string]string
}
func (u uuidHostReader) Mounts() ([]storage.Mount, error) { return u.mounts, nil }
func (u uuidHostReader) ResolveUUID(dev string) (string, bool) {
v, ok := u.uuids[dev]
return v, ok
}
func (u uuidHostReader) DeviceExists(string) bool { return true }
func (u uuidHostReader) Rotational(string) (bool, bool) { return false, false }
func (u uuidHostReader) Removable(string) (bool, bool) { return false, false }
// Impl-2b: a RAW enrolled drive is not a PVE storage (absent from Observe), so its durable-id must be
// resolved from the mount table's device fs-UUID. RED-PROOF: with the Observe-only resolution (before
// the host fallback) this returns "" → intent + guest-bind go unrecorded.
func TestDurableIDForMount_RawFallback(t *testing.T) {
s := &Server{
storage: fakeStorage{}, // Observe empty — the raw drive isn't a PVE storage
host: uuidHostReader{
mounts: []storage.Mount{{Device: "/dev/sdd", MountPoint: "/mnt/teszt"}},
uuids: map[string]string{"/dev/sdd": "RAW-UUID"},
},
}
if got := s.durableIDForMount(context.Background(), "/mnt/teszt"); got != "uuid:RAW-UUID" {
t.Fatalf("raw mount durable-id: got %q want uuid:RAW-UUID", got)
}
// An unknown mount still resolves to "" (no false positive).
if got := s.durableIDForMount(context.Background(), "/mnt/nope"); got != "" {
t.Fatalf("unknown mount: got %q want empty", got)
}
}