carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1//go:build unix
2
3package hostguard
4
5import (
6 "context"
7 "errors"
8 "os"
9 "path/filepath"
10 "testing"
11 "time"
12)
13
14func TestAdmissionFencedFollowsTheDurableFence(t *testing.T) {
15 f, target, captured := fenceFixture(t)
16 if fenced, err := f.g.AdmissionFenced(target.Host, f.obs.Claim, captured); err != nil || fenced {
17 t.Fatalf("before any fence: %v %v", fenced, err)
18 }
19 if _, err := f.g.BeginFence(t.Context(), target); err != nil {
20 t.Fatal(err)
21 }
22 // Read inside the host lock, as Activate's reader and WithLocal do:
23 // no second acquisition, no deadlock.
24 err := f.g.WithLocal(t.Context(), target.Host, func() error {
25 fenced, err := f.g.AdmissionFenced(target.Host, f.obs.Claim, captured)
26 if err != nil || !fenced {
27 return errors.New("after BeginFence, under the lock: not fenced")
28 }
29 return nil
30 })
31 if err != nil {
32 t.Fatal(err)
33 }
34 // A capture whose stamp is not the active one is refused by With, so
35 // it reads closed too.
36 other := captured
37 other.Stamp++
38 if fenced, err := f.g.AdmissionFenced(target.Host, f.obs.Claim, other); err != nil || !fenced {
39 t.Fatalf("another installation: %v %v", fenced, err)
40 }
41 if _, err := f.g.AdmissionFenced("no.such.host", f.obs.Claim, captured); err == nil {
42 t.Fatal("a claim for another host was read")
43 }
44}
45
46// AdmissionFenced and With read the same record the same way: With admits
47// exactly when AdmissionFenced reads open with no error, in every state.
48func TestAdmissionFencedAgreesWithWith(t *testing.T) {
49 verified := func(context.Context, FenceTarget, []byte, time.Time) error { return nil }
50 complete := func(t *testing.T, f *guardFixture, target FenceTarget) {
51 if _, err := f.g.BeginFence(t.Context(), target); err != nil {
52 t.Fatal(err)
53 }
54 if err := f.g.CompleteFence(t.Context(), target, []byte("root receipt"), time.Unix(100, 0).UTC(), verified); err != nil {
55 t.Fatal(err)
56 }
57 }
58 retire := func(t *testing.T, f *guardFixture, target FenceTarget) {
59 complete(t, f, target)
60 if err := f.g.CleanupRetireInstallationWith(t.Context(), target, func(context.Context, FenceReceipt) error { return nil }, func() error { return nil }); err != nil {
61 t.Fatal(err)
62 }
63 }
64 for _, tc := range []struct {
65 name string
66 change func(*testing.T, *guardFixture, FenceTarget, *Captured)
67 with error // With's refusal; nil admits
68 fenced bool
69 readErr error
70 }{
71 {"open", func(*testing.T, *guardFixture, FenceTarget, *Captured) {}, nil, false, nil},
72 {"a stop root began", func(t *testing.T, f *guardFixture, target FenceTarget, _ *Captured) {
73 if _, err := f.g.BeginFence(t.Context(), target); err != nil {
74 t.Fatal(err)
75 }
76 }, ErrFenced, true, nil},
77 {"a stop root completed, the receipt only", func(t *testing.T, f *guardFixture, target FenceTarget, _ *Captured) {
78 complete(t, f, target)
79 // As CompleteColdHandover leaves it: no active fence, the journal kept.
80 r, err := readRecord(f.g.DataDir, target.Host)
81 if err != nil {
82 t.Fatal(err)
83 }
84 r.ActiveFence = nil
85 if err := writeRecord(f.g.DataDir, r); err != nil {
86 t.Fatal(err)
87 }
88 }, ErrFenced, true, nil},
89 {"fenced, then retired by cleanup", func(t *testing.T, f *guardFixture, target FenceTarget, _ *Captured) {
90 retire(t, f, target)
91 }, ErrInactive, true, nil},
92 {"fenced, retired, then captured again", func(t *testing.T, f *guardFixture, target FenceTarget, c *Captured) {
93 retire(t, f, target)
94 fresh, err := Capture(f.g.DataDir, target.Host)
95 if err != nil {
96 t.Fatal(err)
97 }
98 *c = fresh
99 }, ErrInactive, true, nil},
100 {"retired with no fence", func(t *testing.T, f *guardFixture, _ FenceTarget, _ *Captured) {
101 if err := f.g.RetireInstallation(t.Context(), f.obs.Host, allow); err != nil {
102 t.Fatal(err)
103 }
104 }, ErrInactive, true, nil},
105 {"a later installation is active", func(t *testing.T, f *guardFixture, _ FenceTarget, _ *Captured) {
106 f.obs.Claim.Holder.Stamp = 3
107 if err := f.g.Activate(t.Context(), f.obs.Host, "uk", nil); err != nil {
108 t.Fatal(err)
109 }
110 }, ErrStampMismatch, true, nil},
111 {"another incarnation on the record", func(t *testing.T, f *guardFixture, _ FenceTarget, _ *Captured) {
112 if err := writeRecord(f.g.DataDir, record{V: 1, Host: f.obs.Host, Incarnation: "other", ActiveStamp: 1}); err != nil {
113 t.Fatal(err)
114 }
115 }, ErrRecordIncarnation, true, nil},
116 {"a later session of the fenced installation", func(t *testing.T, f *guardFixture, target FenceTarget, _ *Captured) {
117 if _, err := f.g.BeginFence(t.Context(), target); err != nil {
118 t.Fatal(err)
119 }
120 f.obs.Claim.Holder.Session = 8
121 }, nil, false, nil},
122 {"a claim of another incarnation", func(_ *testing.T, f *guardFixture, _ FenceTarget, _ *Captured) {
123 f.obs.Claim.Holder.Incarnation = "other"
124 }, ErrClaimIncarnation, false, ErrClaimIncarnation},
125 {"a claim of another host", func(_ *testing.T, f *guardFixture, _ FenceTarget, _ *Captured) {
126 f.obs.Claim.Host = "else.example"
127 }, ErrMalformedClaim, false, ErrMalformedClaim},
128 {"no record", func(t *testing.T, f *guardFixture, _ FenceTarget, _ *Captured) {
129 if err := os.Remove(filepath.Join(f.g.DataDir, "hostguard", f.obs.Host+".json")); err != nil {
130 t.Fatal(err)
131 }
132 }, ErrNoRecord, false, ErrNoRecord},
133 {"a malformed record", func(t *testing.T, f *guardFixture, _ FenceTarget, _ *Captured) {
134 if err := os.WriteFile(filepath.Join(f.g.DataDir, "hostguard", f.obs.Host+".json"), []byte(`{`), 0660); err != nil {
135 t.Fatal(err)
136 }
137 }, ErrMalformedRecord, false, ErrMalformedRecord},
138 } {
139 t.Run(tc.name, func(t *testing.T) {
140 f, target, captured := fenceFixture(t)
141 tc.change(t, f, target, &captured)
142 called := false
143 withErr := f.g.With(t.Context(), f.obs.Host, captured, allow, func() error { called = true; return nil })
144 if !errors.Is(withErr, tc.with) || (withErr == nil) != called {
145 t.Fatalf("With = %v, called=%v; want %v", withErr, called, tc.with)
146 }
147 fenced, err := f.g.AdmissionFenced(f.obs.Host, f.obs.Claim, captured)
148 if fenced != tc.fenced || !errors.Is(err, tc.readErr) || (tc.readErr == nil && err != nil) {
149 t.Fatalf("AdmissionFenced = %v, %v; want %v, %v", fenced, err, tc.fenced, tc.readErr)
150 }
151 if open := !fenced && err == nil; open != (withErr == nil) {
152 t.Fatalf("disagreement: With = %v, AdmissionFenced = %v, %v", withErr, fenced, err)
153 }
154 })
155 }
156}
157