//go:build linux package host import ( "context" "errors" "os" "path/filepath" "reflect" "strings" "testing" "github.com/carlosframework/platform/internal/hostbox" "golang.org/x/sys/unix" ) func servingTopologyFixture(t *testing.T) (*servingRootFixture, servingParkNamespace) { t.Helper() f := newServingRootFixture(t, true) if _, err := f.park(); err != nil { t.Fatal(err) } body, err := f.disk.Read("namespace.json") var n servingParkNamespace if err != nil || servingParkDecode(body, &n) != nil || n.Topology == nil { t.Fatal("persistent topology was not captured", err) } return f, n } func TestServingParkPersistentDirectory(t *testing.T) { d, err := os.Open(t.TempDir()) if err != nil { t.Fatal(err) } defer d.Close() if err := d.Chmod(0700); err != nil { t.Fatal(err) } p, err := servingParkPersistentDir(d) if err != nil || !p.valid() { var st unix.Stat_t var fs unix.Statfs_t _ = unix.Fstat(int(d.Fd()), &st) _ = unix.Fstatfs(int(d.Fd()), &fs) h, m, e := unix.NameToHandleAt(int(d.Fd()), "", unix.AT_EMPTY_PATH) id, ie := servingParkDirID(d) t.Fatal("kernel persistent directory", p, err, "mode", st.Mode, "fsid", fs.Fsid, "handle", h.Type(), h.Bytes(), m, e, "statx", id, ie) } other, err := servingParkPersistentDir(d) if err != nil || other != p { t.Fatal("same descriptor changed identity", err) } for _, fault := range []string{"empty", "oversize", "uppercase", "filesystem", "type", "file", "writable"} { t.Run(fault, func(t *testing.T) { bad := p switch fault { case "empty": bad.Handle = "" case "oversize": bad.Handle = strings.Repeat("aa", 129) case "uppercase": bad.Handle = "AA" case "filesystem": bad.Filesystem = [2]int32{} case "type": bad.Type = 0 case "file": bad.Mode = unix.S_IFREG | 0700 case "writable": bad.Mode |= 0020 } if bad.valid() { t.Fatal("invalid persistent identity accepted") } }) } if _, err := servingParkPersistentDir(nil); !errors.Is(err, hostbox.ErrUnsupported) { t.Fatal("nil descriptor accepted", err) } } func TestServingParkRebootTopologyReadOnly(t *testing.T) { f, n := servingTopologyFixture(t) fs := f.p.ledger.fs.(*servingParkFS) fs.boot = func() (string, error) { return servingOtherBoot, nil } before := servingBootSnapshot(t, f.disk.dir) claim := f.f.read() var got servingParkRebootObservation err := f.p.ledger.withNamespace(t.Context(), func() error { var err error got, err = fs.inspectRebootTopology(t.Context()) return err }) if err != nil || got.BootID != servingOtherBoot || !reflect.DeepEqual(got.Previous, n) || got.CurrentParent != n.Parent || got.CurrentRoot != n.Root { t.Fatal("read-only topology observation", got, err) } if after := servingBootSnapshot(t, f.disk.dir); !reflect.DeepEqual(before, after) || !reflect.DeepEqual(claim, f.f.read()) { t.Fatal("inspection changed private authority or claim") } // Recognizing the root grants no authority over its events. The original // namespace still belongs to the old boot, even after successful inspection. if err := fs.recoverRecordedNamespace(t.Context()); err == nil { t.Fatal("observation enabled namespace recovery") } if _, err := f.park(); err == nil { t.Fatal("observation enabled parking") } if after := servingBootSnapshot(t, f.disk.dir); !reflect.DeepEqual(before, after) { t.Fatal("failed recovery rewrote retirement history") } } func TestServingParkRebootTopologyRefusesReplacements(t *testing.T) { for _, fault := range []string{"old-format", "malformed-boot", "same-boot", "parent-boot", "parent-record", "root-record", "filesystem", "parent-handle", "root-handle", "type", "owner", "mode", "root", "parent", "root-symlink", "mount", "read-error", "unsupported", "missing-reader", "quarantine", "corrupt-quarantine", "cancel", "root-race", "parent-race", "boot-race", "mode-race", "cancel-race"} { t.Run(fault, func(t *testing.T) { f, n := servingTopologyFixture(t) fs := f.p.ledger.fs.(*servingParkFS) fs.boot = func() (string, error) { return servingOtherBoot, nil } path := filepath.Join(fs.data, "instances", ".serving-parked") ctx, cancel := context.WithCancel(t.Context()) defer cancel() switch fault { case "old-format": n.Topology = nil case "malformed-boot": n.BootID = "invalid" p, err := fs.namespaceParent() if err != nil { t.Fatal(err) } p.BootID = n.BootID if err := f.p.ledger.write("namespace-parent.json", p); err != nil { t.Fatal(err) } case "same-boot": fs.boot = func() (string, error) { return n.BootID, nil } case "parent-boot": n.BootID = "11234567-89ab-4cde-8fab-0123456789ab" case "parent-record": n.Parent.Inode++ case "root-record": n.Root.Inode++ case "filesystem": n.Topology.Parent.Filesystem[0]++ n.Topology.Root.Filesystem = n.Topology.Parent.Filesystem case "parent-handle": n.Topology.Parent.Handle = "aa" case "root-handle": n.Topology.Root.Handle = "aa" case "type": n.Topology.Root.Type++ case "owner": n.Topology.Parent.Owner++ case "mode": n.Topology.Parent.Mode ^= 0100 case "root", "parent", "root-symlink": target := path if fault == "parent" { target = filepath.Dir(path) } if err := os.Rename(target, target+"-old"); err != nil { t.Fatal(err) } if fault == "root-symlink" { if err := os.Symlink(target+"-old", target); err != nil { t.Fatal(err) } } else if err := os.Mkdir(target, 0700); err != nil { t.Fatal(err) } case "mount": fs.mount = func(string) (bool, error) { return false, nil } case "read-error", "unsupported": fs.handle = func(*os.File) (servingParkPersistentDirectory, error) { if fault == "unsupported" { return servingParkPersistentDirectory{}, unix.EOPNOTSUPP } return servingParkPersistentDirectory{}, unix.EIO } case "missing-reader": fs.handle = nil case "quarantine", "corrupt-quarantine": body := []byte("{}") if fault == "corrupt-quarantine" { body = []byte("broken") } if err := f.disk.Write("namespace-quarantine.json", body); err != nil { t.Fatal(err) } case "cancel": cancel() case "root-race", "parent-race", "boot-race", "mode-race", "cancel-race": calls := 0 fs.handle = func(d *os.File) (servingParkPersistentDirectory, error) { p, err := servingParkPersistentDir(d) calls++ if calls == 2 { if fault == "cancel-race" { cancel() } else if fault == "mode-race" { if err := os.Chmod(filepath.Dir(path), 0777); err != nil { t.Fatal(err) } } else if fault == "boot-race" { fs.boot = func() (string, error) { return n.BootID, nil } } else { target := path if fault == "parent-race" { target = filepath.Dir(path) } if err := os.Rename(target, target+"-old"); err != nil { t.Fatal(err) } if err := os.Mkdir(target, 0700); err != nil { t.Fatal(err) } } } return p, err } } if err := f.p.ledger.write("namespace.json", n); err != nil { t.Fatal(err) } before := servingBootSnapshot(t, f.disk.dir) if got, err := fs.inspectRebootTopology(ctx); err == nil || !reflect.DeepEqual(got, servingParkRebootObservation{}) { t.Fatal("unsafe topology accepted", got, err) } if after := servingBootSnapshot(t, f.disk.dir); !reflect.DeepEqual(before, after) { t.Fatal("refusal changed authority") } }) } } func TestServingParkTopologyCaptureFailureRecovery(t *testing.T) { for _, boundary := range []string{"handle", "namespace-before", "namespace-after"} { t.Run(boundary, func(t *testing.T) { f := newServingRootFixture(t, true) fs := f.p.ledger.fs.(*servingParkFS) if boundary == "handle" { fs.handle = func(*os.File) (servingParkPersistentDirectory, error) { return servingParkPersistentDirectory{}, unix.EIO } } else { fail := func(name string, body []byte) error { if name == "namespace.json" { return unix.EIO } return nil } store := quarantineWriteFault{Store: f.disk} if boundary == "namespace-before" { store.before = fail } else { store.after = fail } fs.state = store } if _, err := f.park(); !errors.Is(err, unix.EIO) { t.Fatal("missing capture boundary", err) } if body, err := os.ReadFile(f.db); err != nil || len(body) == 0 { t.Fatal("capture failure changed source", err) } fs.handle, fs.state = servingParkPersistentDir, f.disk if _, err := f.park(); err != nil { t.Fatal("capture recovery", err) } body, err := f.disk.Read("namespace.json") var n servingParkNamespace if err != nil || servingParkDecode(body, &n) != nil || n.Topology == nil { t.Fatal("recovery lost topology", err) } }) } } func TestServingParkTopologyNeverUpgradesOldNamespace(t *testing.T) { f, n := servingTopologyFixture(t) fs := f.p.ledger.fs.(*servingParkFS) n.Topology = nil if err := f.p.ledger.write("namespace.json", n); err != nil { t.Fatal(err) } before := servingBootSnapshot(t, f.disk.dir) root, same, err := fs.namespace() if err != nil { t.Fatal("same-boot old namespace became unreadable", err) } root.Close() if same.Topology != nil { t.Fatal("old namespace gained inferred topology") } if err := fs.recoverRecordedNamespace(t.Context()); err != nil { t.Fatal("same-boot old namespace recovery", err) } fs.boot = func() (string, error) { return servingOtherBoot, nil } if _, err := fs.inspectRebootTopology(t.Context()); err == nil { t.Fatal("old namespace gained cross-boot proof") } if after := servingBootSnapshot(t, f.disk.dir); !reflect.DeepEqual(before, after) { t.Fatal("old namespace was rewritten") } }