carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "bytes"
5 "context"
6 "errors"
7 "os"
8 "path/filepath"
9 "testing"
10 "time"
11
12 "github.com/carlosframework/platform/internal/hostclaim"
13 "github.com/carlosframework/platform/internal/hostguard"
14 "golang.org/x/sys/unix"
15)
16
17func TestR2LegacyLockContentionRetries(t *testing.T) {
18 if !plan14RootFixture(t) {
19 return
20 }
21 for _, scenario := range []string{"contention", "fifo", "foreign owner", "io error"} {
22 t.Run(scenario, func(t *testing.T) {
23 f := newLaunchFixture(t, servingOp())
24 g := f.root.Guard
25 store := &rollbackDisk{dir: t.TempDir()}
26 g.Root = hostguard.NewRootAuthority(store)
27 in := hostguard.RootInstallation{Host: f.target.Host, Account: "bab", App: "app", Record: "record", Incarnation: f.target.Incarnation, Stamp: f.target.Stamp, Box: f.target.Box, Enrollment: f.target.Enrollment, Migration: 1}
28 // The fixture's target carries installation identity; the claim remains
29 // the authority checked by EstablishRoot and BeginFence.
30 f.claims.set(func(c *hostclaim.Claim) {
31 c.Holder.Account = in.Account
32 c.Holder.App = in.App
33 c.Holder.Admissions = []hostclaim.AdmissionRecord{{Box: f.target.Box, Region: f.target.Region, Session: f.target.Session, Generation: c.Holder.Generation}}
34 })
35 if err := g.EstablishRoot(t.Context(), in, func(*hostclaim.Claim) error { return nil }); err != nil {
36 t.Fatal(err)
37 }
38 before, err := store.Read(in.Host + ".json")
39 if err != nil {
40 t.Fatal(err)
41 }
42 path := filepath.Join(g.LockDir, in.Host+".lock")
43 var held *os.File
44 switch scenario {
45 case "contention":
46 held, err = os.OpenFile(path, os.O_RDWR, 0660)
47 if err != nil {
48 t.Fatal(err)
49 }
50 defer held.Close()
51 if err := unix.Flock(int(held.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil {
52 t.Fatal(err)
53 }
54 defer unix.Flock(int(held.Fd()), unix.LOCK_UN)
55 case "fifo":
56 if err := os.Remove(path); err != nil {
57 t.Fatal(err)
58 }
59 if err := unix.Mkfifo(path, 0660); err != nil {
60 t.Fatal(err)
61 }
62 case "foreign owner":
63 if err := unix.Mount("/foreign", path, "", unix.MS_BIND, ""); err != nil {
64 t.Fatal(err)
65 }
66 defer unix.Unmount(path, 0)
67 case "io error":
68 g.LockDir = filepath.Join(g.LockDir, "missing")
69 }
70 target := f.target
71 target.FenceID = "fix3-fence"
72 start := time.Now()
73 _, err = g.BeginFence(t.Context(), target)
74 if err == nil {
75 t.Fatal("mutation crossed unavailable lock")
76 }
77 if time.Since(start) > time.Second {
78 t.Fatal("lock bound exceeded")
79 }
80 after, readErr := store.Read(in.Host + ".json")
81 if readErr != nil {
82 t.Fatal(readErr)
83 }
84 captured := hostguard.Captured{Incarnation: in.Incarnation, Stamp: in.Stamp}
85 _, viewErr := g.Root.View(t.Context(), in.Host, captured)
86 if scenario == "fifo" || scenario == "foreign owner" {
87 if !errors.Is(err, hostguard.ErrUnsafeFile) || !errors.Is(viewErr, hostguard.ErrFenced) || bytes.Equal(before, after) {
88 t.Fatal("hostile lock not durably quarantined", err, viewErr)
89 }
90 } else {
91 if !bytes.Equal(before, after) || viewErr != nil {
92 t.Fatal("transient lock failure changed durable state", err, viewErr)
93 }
94 }
95 if scenario == "contention" {
96 if !errors.Is(err, context.DeadlineExceeded) {
97 t.Fatal(err)
98 }
99 if err := unix.Flock(int(held.Fd()), unix.LOCK_UN); err != nil {
100 t.Fatal(err)
101 }
102 if _, err := g.BeginFence(t.Context(), target); err != nil {
103 t.Fatal("released lock did not retry", err)
104 }
105 }
106 })
107 }
108}
109