Impl-2b: resolve a raw candidate's fs-UUID via /disks/candidates (enroll fix)
runStorageInit/runStorageAttach resolved the fs UUID only via agent.Disks(),
which does NOT include a raw (unenrolled, non-PVE-storage) device — so a raw
candidate could be offered but never enrolled ("no fs identifier"). New
resolveEnrollUUID falls back to the raw-device scan (/disks/candidates), which
reports each free disk's durable_id (uuid:<fs-uuid>). Both enroll paths use it;
legacy re-attach (drive in /disks) still works. Test + red-proof.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,7 @@ import (
|
||||
// without a live agent). *agentapi.Client satisfies it.
|
||||
type diskAgent interface {
|
||||
Disks(ctx context.Context) (agentapi.DisksResponse, error)
|
||||
ListCandidates(ctx context.Context) (agentapi.CandidatesResult, error)
|
||||
FormatDisk(ctx context.Context, device, fstype string, confirmed bool, durableID string) (agentapi.FormatResult, error)
|
||||
AssignDisk(ctx context.Context, uuid, where, fstype, options string) error
|
||||
EjectDisk(ctx context.Context, where string) (agentapi.EjectResult, error)
|
||||
@@ -63,6 +64,31 @@ func fsUUIDForDevice(disks agentapi.DisksResponse, device string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// resolveEnrollUUID resolves a device's filesystem UUID for enrollment. A RAW candidate (Impl-2b) is
|
||||
// NOT in the /disks list (it has no PVE storage / isn't enrolled), so fsUUIDForDevice can't see it —
|
||||
// fall back to the raw-device scan (/disks/candidates), which reports each free disk's durable_id
|
||||
// (uuid:<fs-uuid>). Matches the whole disk OR its FS-bearing node (mount_source, e.g. /dev/sdd1).
|
||||
// Returns "" only if the device truly has no resolvable fs UUID.
|
||||
func resolveEnrollUUID(ctx context.Context, agent diskAgent, device string) string {
|
||||
if disks, err := agent.Disks(ctx); err == nil {
|
||||
if u := fsUUIDForDevice(disks, device); u != "" {
|
||||
return u
|
||||
}
|
||||
}
|
||||
cands, err := agent.ListCandidates(ctx)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, c := range append(append([]agentapi.DiskCandidate{}, cands.Initialize...), cands.Attach...) {
|
||||
if c.Device == device || c.MountSource == device {
|
||||
if rest, ok := strings.CutPrefix(c.DurableID, "uuid:"); ok {
|
||||
return rest
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// storageInitResult is the outcome of an init attempt (JSON-rendered to the wizard).
|
||||
type storageInitResult struct {
|
||||
Registered bool `json:"registered"`
|
||||
@@ -107,12 +133,9 @@ func (s *Server) runStorageInit(ctx context.Context, agent diskAgent, device, fs
|
||||
if !fr.Formatted {
|
||||
return storageInitResult{}, fmt.Errorf("az eszköz nem lett megformázva (%s)", fr.Reason)
|
||||
}
|
||||
// 2. Resolve the NEW fs UUID (re-list disks; the device's storage now carries a fresh UUID).
|
||||
disks, err := agent.Disks(ctx)
|
||||
if err != nil {
|
||||
return storageInitResult{}, fmt.Errorf("formázás kész, de a meghajtólista nem olvasható: %w", err)
|
||||
}
|
||||
uuid := fsUUIDForDevice(disks, device)
|
||||
// 2. Resolve the NEW fs UUID. A freshly-formatted RAW device isn't in /disks (not enrolled yet), so
|
||||
// resolve via the raw-device scan too (Impl-2b) — the device now appears there with its new durable_id.
|
||||
uuid := resolveEnrollUUID(ctx, agent, device)
|
||||
if uuid == "" {
|
||||
return storageInitResult{}, fmt.Errorf("formázás kész, de az új fájlrendszer-azonosító nem feloldható — frissítsen és használja a Csatolás funkciót")
|
||||
}
|
||||
@@ -145,11 +168,9 @@ func (s *Server) attachIntoGuest(ctx context.Context, agent diskAgent, where str
|
||||
// runStorageAttach mounts an existing-filesystem device (non-destructive — never touches the gate)
|
||||
// and registers it. The UUID is resolved server-side from the device.
|
||||
func (s *Server) runStorageAttach(ctx context.Context, agent diskAgent, device, fstype, where, label string, setDefault bool) (storageInitResult, error) {
|
||||
disks, err := agent.Disks(ctx)
|
||||
if err != nil {
|
||||
return storageInitResult{}, fmt.Errorf("a meghajtólista nem olvasható: %w", err)
|
||||
}
|
||||
uuid := fsUUIDForDevice(disks, device)
|
||||
// Resolve the fs UUID. Works for a legacy re-attach (drive in /disks) AND a raw candidate (Impl-2b:
|
||||
// not in /disks — resolved via the raw-device scan's durable_id).
|
||||
uuid := resolveEnrollUUID(ctx, agent, device)
|
||||
if uuid == "" {
|
||||
return storageInitResult{}, fmt.Errorf("a kiválasztott meghajtóhoz nem található fájlrendszer-azonosító (csak fájlrendszerrel rendelkező meghajtó csatolható)")
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ type mockAgent struct {
|
||||
decommissionCalls []string
|
||||
guestRebootCalls int
|
||||
guestRebootErr error
|
||||
candidates agentapi.CandidatesResult
|
||||
}
|
||||
|
||||
type assignCall struct{ uuid, where, fstype string }
|
||||
@@ -54,6 +55,10 @@ func (m *mockAgent) Disks(context.Context) (agentapi.DisksResponse, error) {
|
||||
m.disksCalls++
|
||||
return m.disks, nil
|
||||
}
|
||||
|
||||
func (m *mockAgent) ListCandidates(context.Context) (agentapi.CandidatesResult, error) {
|
||||
return m.candidates, nil
|
||||
}
|
||||
func (m *mockAgent) FormatDisk(_ context.Context, device, fstype string, confirmed bool, durableID string) (agentapi.FormatResult, error) {
|
||||
m.formatCalls = append(m.formatCalls, formatCall{device, fstype, durableID, confirmed})
|
||||
return m.formatRes, m.formatErr
|
||||
@@ -219,6 +224,31 @@ func TestRunStorageAttach_Success(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Impl-2b: a RAW candidate (not in /disks — no PVE storage) attaches by resolving its fs UUID from the
|
||||
// raw-device scan's durable_id. RED-PROOF: with the old fsUUIDForDevice(Disks)-only resolution this
|
||||
// fails ("no fs identifier"), since the raw device is absent from /disks.
|
||||
func TestRunStorageAttach_RawCandidate(t *testing.T) {
|
||||
s := testServer(t)
|
||||
agent := &mockAgent{
|
||||
disks: agentapi.DisksResponse{Disks: []agentapi.DiskInfo{}}, // raw device NOT in /disks
|
||||
candidates: agentapi.CandidatesResult{
|
||||
Attach: []agentapi.DiskCandidate{
|
||||
{Device: "/dev/sdd", FSType: "ext4", Mountable: true, MountSource: "/dev/sdd", DurableID: "uuid:RAW-99"},
|
||||
},
|
||||
},
|
||||
}
|
||||
res, err := s.runStorageAttach(context.Background(), agent, "/dev/sdd", "ext4", "/mnt/teszt", "Teszt", false)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !res.Registered {
|
||||
t.Fatal("expected registered")
|
||||
}
|
||||
if len(agent.assignCalls) != 1 || agent.assignCalls[0].uuid != "RAW-99" {
|
||||
t.Fatalf("raw candidate must assign by the scan-derived fs UUID: %+v", agent.assignCalls)
|
||||
}
|
||||
}
|
||||
|
||||
// handleStorageRegister (the "Regisztrálás" action for an already-mounted, unregistered drive) must
|
||||
// register the STABLE intermediary path /mnt/felhom-drives/<name> — the same path runStorageInit/
|
||||
// runStorageAttach register — NOT the raw /mnt/<name> it receives. Registering the raw path made the
|
||||
|
||||
Reference in New Issue
Block a user