//go:build linux package host import ( "os" "path/filepath" "testing" "github.com/carlosframework/platform/internal/instancectl" "golang.org/x/sys/unix" ) func TestServingParkRebootVolumeIdentity(t *testing.T) { for _, serial := range []string{"vol0123456789abcdef0", "vol11111111111111111", ""} { t.Run("serial-"+serial, func(t *testing.T) { f, r := servingRebootRecordFixture(t, "complete") servingRenumberHistory(t, f, &r) f.fs.volume = func(uint64) string { return serial } root := &servingParkRoot{ledger: f.ledger} if err := root.commitServingReboot(t.Context(), r.Authorization.ID); err != nil { t.Fatal("retention recovery", err) } current, err := f.ledger.read(r.Authorization.ID) if err != nil { t.Fatal(err) } if current.Source.BootID != servingOtherBoot { t.Fatal("serving authority not rebound") } b, err := servingParkReadBinding(f.disk, "retirement-"+r.Authorization.ID) if err != nil || b == nil { t.Fatal(err) } if (b.Volume != "") != (serial == "vol0123456789abcdef0") { t.Fatal("wrong destructive binding", b) } if serial != "vol0123456789abcdef0" { if err := f.ledger.maintainParks(t.Context(), func() error { return instancectl.ErrDiskFull }); err == nil { t.Fatal("pressure deleted retained volume") } f.retained(r.Authorization, "last database", r.ReapAfter) } f.now = r.ReapAfter err = f.ledger.reapLocked(t.Context(), r.Authorization.ID) if serial == "vol0123456789abcdef0" { if err != nil { t.Fatal("same volume reap", err) } if _, err := os.Stat(f.path(r.Authorization, "")); !os.IsNotExist(err) { t.Fatal("park survived", err) } } else { if err == nil { t.Fatal("destructive clone/unreadable rebind") } f.retained(r.Authorization, "last database", r.ReapAfter) // A later readable serial does not silently re-enroll a retained park. f.fs.volume = func(uint64) string { return "vol0123456789abcdef0" } f.fs.boot = func() (string, error) { return "33333333-3333-4333-8333-333333333333", nil } if err := root.commitServingReboot(t.Context(), r.Authorization.ID); err != nil { t.Fatal(err) } b, err := servingParkReadBinding(f.disk, "retirement-"+r.Authorization.ID) if err != nil || b == nil || b.Volume != "" { t.Fatal("automatic re-enrollment", b, err) } if err := f.ledger.reapLocked(t.Context(), r.Authorization.ID); err == nil { t.Fatal("retained park gained deletion authority") } } }) } } func TestServingParkKernelVolumeResolution(t *testing.T) { sys := t.TempDir() disk := filepath.Join(sys, "devices", "nvme0n1") part := filepath.Join(disk, "nvme0n1p1") if err := os.MkdirAll(filepath.Join(disk, "device"), 0700); err != nil { t.Fatal(err) } if err := os.Mkdir(part, 0700); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(part, "partition"), []byte("1\n"), 0600); err != nil { t.Fatal(err) } links := filepath.Join(sys, "dev", "block") if err := os.MkdirAll(links, 0700); err != nil { t.Fatal(err) } for name, target := range map[string]string{"259:0": disk, "259:1": part} { if err := os.Symlink(target, filepath.Join(links, name)); err != nil { t.Fatal(err) } } serial := filepath.Join(disk, "device", "serial") for _, raw := range []string{"vol-0123456789abcdef0\n", " vol0123456789abcdef0 ", "bad", "vol../escape", "vol", "vol0123456789abcdefg"} { if err := os.WriteFile(serial, []byte(raw), 0600); err != nil { t.Fatal(err) } for _, minor := range []uint32{0, 1} { got := servingParkVolumeAt(sys, unix.Mkdev(259, minor)) want := "" if raw == "vol-0123456789abcdef0\n" || raw == " vol0123456789abcdef0 " { want = "vol0123456789abcdef0" } if got != want { t.Fatal(raw, minor, got, want) } } } if err := os.Remove(serial); err != nil { t.Fatal(err) } if servingParkVolumeAt(sys, unix.Mkdev(259, 1)) != "" || servingParkVolumeAt(sys, unix.Mkdev(7, 0)) != "" { t.Fatal("unreadable disk/loop supplied identity") } }