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 linux
2
3package host
4
5import (
6 "errors"
7 "fmt"
8 "os"
9 "reflect"
10 "testing"
11
12 "github.com/carlosframework/platform/internal/hostbox"
13)
14
15// Put the historical records on a different boot-local device number without
16// changing any persistent proof or anything in the actual filesystem.
17func servingRenumberHistory(t *testing.T, f *servingParkDiskFixture, r *servingParkRecord) {
18 t.Helper()
19 old := r.Source.Parent.Device + 123
20 for _, d := range []*servingArtifactDirectory{&r.Source.Parent, &r.Source.Directory, &r.Authorization.Directory, &r.Namespace, &r.Event} {
21 if d.Inode != 0 {
22 d.Device = old
23 }
24 }
25 for i := range r.Source.Files {
26 if r.Source.Files[i].Present {
27 r.Source.Files[i].Device = old
28 }
29 }
30 if err := f.ledger.save(*r); err != nil {
31 t.Fatal(err)
32 }
33 p, err := f.fs.historicalNamespaceParent()
34 if err != nil {
35 t.Fatal(err)
36 }
37 p.Parent.Device = old
38 if err := f.ledger.write("namespace-parent.json", p); err != nil {
39 t.Fatal(err)
40 }
41 var n servingParkNamespace
42 body, err := f.disk.Read("namespace.json")
43 if err != nil || servingParkDecode(body, &n) != nil {
44 t.Fatal(err)
45 }
46 n.Parent.Device, n.Root.Device = old, old
47 if err := f.ledger.write("namespace.json", n); err != nil {
48 t.Fatal(err)
49 }
50 if r.Authorization.Legacy {
51 c, err := f.ledger.legacyConsumption(r.Authorization.Successor)
52 if err != nil {
53 t.Fatal(err)
54 }
55 if c == nil {
56 return
57 }
58 c.Source, c.Authorization = r.Source, r.Authorization
59 if err := f.ledger.write(servingLegacyKey("legacy", r.Authorization.Successor), c); err != nil {
60 t.Fatal(err)
61 }
62 }
63}
64
65func TestServingParkRebootDeviceRenumber(t *testing.T) {
66 for _, phase := range []string{"captured", "prepared", "rename-reply", "parked", "complete", "unlink-reply", "partial-unlink", "rmdir-reply", "closed"} {
67 t.Run(phase, func(t *testing.T) {
68 f, r := servingRebootRecordFixture(t, phase)
69 servingRenumberHistory(t, f, &r)
70 before := servingBootSnapshot(t, f.disk.dir)
71 got, err := f.fs.inspectRebootRecord(t.Context(), r.Authorization.ID)
72 if err != nil || got.Topology.CurrentParent.Device == r.Source.Parent.Device || !reflect.DeepEqual(got.Record, r) {
73 t.Fatal("renumbered-device inspection", got, err)
74 }
75 if !reflect.DeepEqual(before, servingBootSnapshot(t, f.disk.dir)) {
76 t.Fatal("inspection rewrote history")
77 }
78 if err := f.fs.CheckParked(&r); err == nil {
79 t.Fatal("inspection granted old-boot authority")
80 }
81 if phase == "captured" || phase == "prepared" || phase == "rename-reply" {
82 return
83 }
84 var active *servingParkRebootAuthority
85 if !r.Complete {
86 a := r.activeAuthorization()
87 active = &servingParkRebootAuthority{ClaimETag: "fixture", Actor: hostbox.Actor{Box: a.Successor.Box, Region: a.Successor.Region, Enrollment: "fixture", Revision: 1, Binding: "1", Eligible: true}, Directory: got.Source, Tags: a.TagsFrom}
88 }
89 if err := f.ledger.prepareRebootLocked(t.Context(), r, active); err != nil {
90 t.Fatal("prepare", err)
91 }
92 if err := f.ledger.commitRebootLocked(t.Context(), r, active); err != nil {
93 t.Fatal("commit", err)
94 }
95 current, err := f.ledger.read(r.Authorization.ID)
96 if err != nil || current.Source.Parent.Device != got.Topology.CurrentParent.Device {
97 t.Fatal("current device", current, err)
98 }
99 if err := f.ledger.save(current); err != nil {
100 t.Fatal("reverse binding", err)
101 }
102 raw, err := f.ledger.readHistorical(r.Authorization.ID)
103 if err != nil || !reflect.DeepEqual(raw, r) {
104 t.Fatal("round trip changed history", raw, err)
105 }
106 if r.Parked && !r.Deleting {
107 if err := f.fs.CheckParked(&current); !r.Complete && err != nil {
108 t.Fatal("bound park", err)
109 }
110 if r.Complete {
111 f.now = current.ReapAfter
112 if err := f.ledger.reapLocked(t.Context(), r.Authorization.ID); err != nil {
113 t.Fatal("reap", err)
114 }
115 if _, err := os.Stat(f.fs.data + "/instances/.serving-parked/" + r.Authorization.ID); !os.IsNotExist(err) {
116 t.Fatal("event survived reap", err)
117 }
118 }
119 }
120 })
121 }
122}
123
124func TestServingParkRebootDeviceRenumberRefusesReplacement(t *testing.T) {
125 for _, fault := range []string{"filesystem", "root-handle", "source-handle", "event-handle", "file-handle", "inode", "same-boot"} {
126 t.Run(fault, func(t *testing.T) {
127 f, r := servingRebootRecordFixture(t, "parked")
128 servingRenumberHistory(t, f, &r)
129 var n servingParkNamespace
130 body, err := f.disk.Read("namespace.json")
131 if err != nil || servingParkDecode(body, &n) != nil {
132 t.Fatal(err)
133 }
134 switch fault {
135 case "filesystem":
136 n.Topology.Parent.Filesystem[0]++
137 n.Topology.Root.Filesystem = n.Topology.Parent.Filesystem
138 case "root-handle":
139 n.Topology.Root.Handle = "aa"
140 case "source-handle":
141 r.Source.Persistent.Handle = "aa"
142 case "event-handle":
143 r.EventPersistent.Handle = "aa"
144 case "file-handle":
145 r.Source.Files[0].Persistent.Handle = "aa"
146 case "inode":
147 r.Source.Directory.Inode++
148 r.Authorization.Directory = r.Source.Directory
149 case "same-boot":
150 f.fs.boot = func() (string, error) { return r.Source.BootID, nil }
151 }
152 if err := f.ledger.save(r); err != nil {
153 t.Fatal(err)
154 }
155 if err := f.ledger.write("namespace.json", n); err != nil {
156 t.Fatal(err)
157 }
158 before := servingBootSnapshot(t, f.disk.dir)
159 if _, err := f.fs.inspectRebootRecord(t.Context(), r.Authorization.ID); err == nil {
160 t.Fatal("renumbering accepted replaced identity")
161 }
162 if !reflect.DeepEqual(before, servingBootSnapshot(t, f.disk.dir)) {
163 t.Fatal("refusal changed authority")
164 }
165 })
166 }
167}
168
169func TestServingParkRebootDeviceRenumberLostWrite(t *testing.T) {
170 for _, before := range []bool{true, false} {
171 for at := 1; at <= 3; at++ {
172 f, r := servingRebootRecordFixture(t, "complete")
173 servingRenumberHistory(t, f, &r)
174 fault := &servingParkFaultStore{Store: f.disk, failAt: at, before: before}
175 f.ledger.state, f.fs.state = fault, fault
176 root := &servingParkRoot{ledger: f.ledger}
177 err := root.commitServingReboot(t.Context(), r.Authorization.ID)
178 if !errors.Is(err, os.ErrPermission) {
179 t.Fatalf("%v/%d: missing write fault: %v", before, at, err)
180 }
181 f.ledger.state, f.fs.state = f.disk, f.disk
182 if err := root.commitServingReboot(t.Context(), r.Authorization.ID); err != nil {
183 t.Fatal("commit replay", err)
184 }
185 if err := f.fs.recoverRecordedNamespace(t.Context()); err != nil {
186 t.Fatal("namespace recovery", err)
187 }
188 current, err := f.ledger.read(r.Authorization.ID)
189 if err != nil {
190 t.Fatal(err)
191 }
192 f.now = current.ReapAfter
193 if err := f.ledger.reapLocked(t.Context(), r.Authorization.ID); err != nil {
194 t.Fatal("reap after lost reply", err)
195 }
196 raw, err := f.ledger.readHistorical(r.Authorization.ID)
197 if err != nil || raw.Source.BootID != r.Source.BootID || raw.Source.Parent != r.Source.Parent || !raw.Closed {
198 t.Fatal("history after recovery", raw, err)
199 }
200 }
201 }
202}
203
204func TestServingParkRetirementAfterDeviceRenumber(t *testing.T) {
205 for _, renumberAgain := range []bool{false, true} {
206 for _, replacement := range []bool{false, true} {
207 t.Run(fmt.Sprint(renumberAgain, "/replacement-", replacement), func(t *testing.T) {
208 f, old := servingRebootRecordFixture(t, "complete")
209 servingRenumberHistory(t, f, &old) // A/D1 -> B/D2.
210 root := &servingParkRoot{ledger: f.ledger}
211 if err := root.commitServingReboot(t.Context(), old.Authorization.ID); err != nil {
212 t.Fatal(err)
213 }
214 f.put("", "boot B served database")
215 f.put(".gen", "G2")
216 a, stops := f.evidence(2, false, "started")
217 park, err := f.park(a, stops)
218 if err != nil {
219 t.Fatal("B retirement and replacement", err)
220 }
221 f.complete(a)
222 park, err = f.ledger.readHistorical(a.ID)
223 if err != nil {
224 t.Fatal(err)
225 }
226 if renumberAgain {
227 // C's real device is D3; B's immutable record used D2.
228 b := servingParkBootBinding{Version: 1, ID: "retirement-" + a.ID, BeforeBoot: park.Source.BootID, BootID: park.Source.BootID, BeforeParent: park.Source.Parent, Parent: park.Source.Parent}
229 b.Parent.Device += 456
230 if err := b.record(&park, false); err != nil {
231 t.Fatal(err)
232 }
233 if err := f.ledger.save(park); err != nil {
234 t.Fatal(err)
235 }
236 }
237 f.fs.boot = func() (string, error) { return "33333333-3333-4333-8333-333333333333", nil }
238 if err := root.commitServingReboot(t.Context(), a.ID); err != nil {
239 t.Fatal("C recovery", err)
240 }
241 if replacement {
242 f.put("", "boot C served database")
243 f.put(".gen", "G3")
244 next, stops := f.evidence(3, false, "started")
245 if _, err := f.park(next, stops); err != nil {
246 t.Fatal("C replacement", err)
247 }
248 f.complete(next)
249 nextPark, err := f.ledger.read(next.ID)
250 if err != nil {
251 t.Fatal(err)
252 }
253 f.now = nextPark.ReapAfter
254 if err := f.ledger.reapLocked(t.Context(), next.ID); err != nil {
255 t.Fatal("C replacement reaping", err)
256 }
257 }
258 f.now = park.ReapAfter
259 if err := f.ledger.reapLocked(t.Context(), a.ID); err != nil {
260 t.Fatal("C reap", err)
261 }
262 if _, err := os.Stat(f.path(a, "")); !os.IsNotExist(err) {
263 t.Fatal("park survived", err)
264 }
265 })
266 }
267 }
268
269}
270
271func TestServingParkLaterRetirementRequiresParentProof(t *testing.T) {
272 for _, fault := range []string{"missing", "handle", "filesystem"} {
273 t.Run(fault, func(t *testing.T) {
274 f, r := servingRebootRecordFixture(t, "complete")
275 servingRenumberHistory(t, f, &r)
276 root := &servingParkRoot{ledger: f.ledger}
277 if err := root.commitServingReboot(t.Context(), r.Authorization.ID); err != nil {
278 t.Fatal(err)
279 }
280 f.put("", "later served database")
281 f.put(".gen", "G2")
282 a, stops := f.evidence(2, false, "started")
283 if _, err := f.park(a, stops); err != nil {
284 t.Fatal(err)
285 }
286 f.complete(a)
287 later, err := f.ledger.readHistorical(a.ID)
288 if err != nil {
289 t.Fatal(err)
290 }
291 switch fault {
292 case "missing":
293 later.Source.ParentPersistent = servingParkPersistentDirectory{}
294 case "handle":
295 later.Source.ParentPersistent.Handle = "aa"
296 case "filesystem":
297 later.Source.ParentPersistent.Filesystem[0]++
298 }
299 if err := f.ledger.write("retirement-"+a.ID+".json", later); err != nil {
300 t.Fatal(err)
301 }
302 f.fs.boot = func() (string, error) { return "33333333-3333-4333-8333-333333333333", nil }
303 before := servingBootSnapshot(t, f.disk.dir)
304 if err := root.commitServingReboot(t.Context(), a.ID); err == nil {
305 t.Fatal("unbound historical parent accepted")
306 }
307 if !reflect.DeepEqual(before, servingBootSnapshot(t, f.disk.dir)) {
308 t.Fatal("refusal rewrote authority")
309 }
310 f.retained(a, "later served database", later.ReapAfter)
311 })
312 }
313}
314