package storage import ( "os" "path/filepath" "runtime" "testing" ) // fakeDevDisk builds a temp /dev/disk tree: a real "device" file + by-id/by-uuid symlinks to it. // Returns the disk root + the device path. (filepath.EvalSymlinks needs real targets, so the // "device" is a regular file standing in for a block device.) Skips on Windows where creating a // symlink needs a privilege — the durable-device code is Linux-only (the agent runs on the PVE // host), so these run on the build server / demo host. func fakeDevDisk(t *testing.T, links map[string]string, uuids map[string]string) (root, device string) { t.Helper() if runtime.GOOS == "windows" { t.Skip("durable-device symlink tests run on Linux (the agent's OS); symlink creation needs privilege on Windows") } base := t.TempDir() device = filepath.Join(base, "sdb") if err := os.WriteFile(device, []byte("x"), 0o600); err != nil { t.Fatal(err) } root = filepath.Join(base, "disk") mk := func(sub, name, target string) { dir := filepath.Join(root, sub) os.MkdirAll(dir, 0o755) if err := os.Symlink(target, filepath.Join(dir, name)); err != nil { t.Fatal(err) } } for name := range links { mk("by-id", name, device) } for uuid := range uuids { mk("by-uuid", uuid, device) } return root, device } func withDevDiskRoot(t *testing.T, root string) { t.Helper() old := devDiskRoot devDiskRoot = root t.Cleanup(func() { devDiskRoot = old }) } // DeviceDurableID prefers a wwn- by-id link over ata-/serial links and over a uuid. func TestDeviceDurableID_PrefersWWN(t *testing.T) { root, device := fakeDevDisk(t, map[string]string{"wwn-0x5000c500abcd": "", "ata-Samsung_SSD_850_S1": "", "scsi-35000c500abcd": ""}, map[string]string{"1111-2222": ""}) withDevDiskRoot(t, root) id, err := DeviceDurableID(device) if err != nil { t.Fatalf("DeviceDurableID: %v", err) } if id != "byid:wwn-0x5000c500abcd" { t.Errorf("durable id = %q, want the wwn link", id) } } // With no by-id link, it falls back to a filesystem UUID. func TestDeviceDurableID_FallsBackToUUID(t *testing.T) { root, device := fakeDevDisk(t, map[string]string{}, map[string]string{"abcd-ef01": ""}) withDevDiskRoot(t, root) id, err := DeviceDurableID(device) if err != nil { t.Fatalf("DeviceDurableID: %v", err) } if id != "byuuid:abcd-ef01" { t.Errorf("durable id = %q, want byuuid:abcd-ef01", id) } } // A device with no durable identity at all → error (cannot be wipe-bound). func TestDeviceDurableID_NoneErrors(t *testing.T) { root, device := fakeDevDisk(t, map[string]string{}, map[string]string{}) withDevDiskRoot(t, root) if _, err := DeviceDurableID(device); err == nil { t.Fatal("a device with no wwn/serial/uuid must error (no durable id)") } } // ResolveDurableDevice round-trips a by-id id back to the canonical device path. func TestResolveDurableDevice_RoundTrip(t *testing.T) { root, device := fakeDevDisk(t, map[string]string{"wwn-0xabc": ""}, map[string]string{}) withDevDiskRoot(t, root) got, err := ResolveDurableDevice("byid:wwn-0xabc") if err != nil { t.Fatalf("ResolveDurableDevice: %v", err) } want, _ := filepath.EvalSymlinks(device) if got != want { t.Errorf("resolved %q, want %q", got, want) } } // A path-only / unknown-scheme id is REFUSED (the anti-retarget invariant). func TestResolveDurableDevice_RefusesPathOnly(t *testing.T) { withDevDiskRoot(t, t.TempDir()) for _, bad := range []string{"/dev/sdb", "sdb", "uuid-no-scheme", "byid:../../etc/passwd", "byuuid:../x"} { if _, err := ResolveDurableDevice(bad); err == nil { t.Errorf("ResolveDurableDevice(%q) succeeded, want refusal", bad) } } } // A durable id whose link is gone → error (device removed/replaced). func TestResolveDurableDevice_MissingErrors(t *testing.T) { withDevDiskRoot(t, t.TempDir()) // empty: no by-id dir if _, err := ResolveDurableDevice("byid:wwn-0xgone"); err == nil { t.Fatal("an absent durable id must error") } }