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 "context"
7 "errors"
8 "net"
9 "os"
10 "path/filepath"
11 "reflect"
12 "testing"
13
14 "github.com/carlosframework/platform/internal/hostbox"
15 "github.com/carlosframework/platform/internal/provision"
16 "golang.org/x/sys/unix"
17)
18
19func TestServingNamespaceAuthenticatedParentAndRecovery(t *testing.T) {
20 f := newServingRootFixture(t, true)
21 fs := f.p.ledger.fs.(*servingParkFS)
22 path := filepath.Join(fs.data, "instances", ".serving-parked")
23 if err := os.Mkdir(path, 0700); err != nil {
24 t.Fatal(err)
25 }
26 if err := os.WriteFile(filepath.Join(path, "unknown"), []byte("keep"), 0600); err != nil {
27 t.Fatal(err)
28 }
29 before := f.f.read()
30 a, err := f.park()
31 if err != nil {
32 t.Fatal(err)
33 }
34 anchor, err := fs.namespaceParent()
35 if err != nil {
36 t.Fatal(err)
37 }
38 r, err := f.p.ledger.read(a.ID)
39 if err != nil || anchor.Parent != r.Source.Parent {
40 t.Fatal("parent was not captured from the root installation", err)
41 }
42 var q servingParkQuarantine
43 body, err := f.disk.Read("namespace-quarantine.json")
44 if err != nil || servingParkDecode(body, &q) != nil || !q.Complete {
45 t.Fatal("authenticated recovery", err)
46 }
47 body, err = os.ReadFile(filepath.Join(filepath.Dir(path), q.name(), "unknown"))
48 if err != nil || string(body) != "keep" {
49 t.Fatal("unknown bytes changed", err)
50 }
51 if !reflect.DeepEqual(f.f.read(), before) {
52 t.Fatal("namespace recovery changed the claim")
53 }
54}
55
56func TestServingNamespaceParentRequiresIndependentInstallation(t *testing.T) {
57 for _, fault := range []string{"peer", "census", "installation", "inventory", "receipt", "source", "mode"} {
58 t.Run(fault, func(t *testing.T) {
59 f := newServingRootFixture(t, true)
60 fs := f.p.ledger.fs.(*servingParkFS)
61 path := filepath.Join(fs.data, "instances", ".serving-parked")
62 if err := os.Mkdir(path, 0700); err != nil {
63 t.Fatal(err)
64 }
65 switch fault {
66 case "peer":
67 f.p.root.Stop.Authenticate = func(context.Context, net.Conn) (StopPeer, error) { return StopPeer{}, hostbox.ErrChanged }
68 case "census":
69 f.p.root.RequireNewWork = func(context.Context) error { return hostbox.ErrChanged }
70 case "installation":
71 f.actor.Enrollment = "changed"
72 case "inventory":
73 f.actor.Binding = "8"
74 case "receipt":
75 f.mutateRoot(func(m map[string]any) { m["State"].(map[string]any)["fence_journal"] = []any{} })
76 case "source":
77 if err := os.Rename(filepath.Dir(f.db), filepath.Dir(f.db)+"-saved"); err != nil {
78 t.Fatal(err)
79 }
80 if err := os.Mkdir(filepath.Dir(f.db), 0700); err != nil {
81 t.Fatal(err)
82 }
83 case "mode":
84 if err := os.Chmod(filepath.Dir(path), 0777); err != nil {
85 t.Fatal(err)
86 }
87 }
88 if _, err := f.park(); err == nil {
89 t.Fatal("unverified recovery accepted")
90 }
91 for _, name := range []string{"namespace-parent.json", "namespace.json", "namespace-quarantine.json"} {
92 if _, err := f.disk.Read(name); !errors.Is(err, os.ErrNotExist) {
93 t.Fatal("unverified recovery wrote", name, err)
94 }
95 }
96 if _, err := os.Stat(path); err != nil {
97 t.Fatal("obstruction moved without authority", err)
98 }
99 })
100 }
101}
102
103func TestServingNamespaceStartupUsesRecordedParent(t *testing.T) {
104 for _, boundary := range []string{"parent", "quarantine-rename", "fresh-before", "fresh", "complete"} {
105 t.Run(boundary, func(t *testing.T) {
106 f := newServingRootFixture(t, true)
107 fs := f.p.ledger.fs.(*servingParkFS)
108 path := filepath.Join(fs.data, "instances", ".serving-parked")
109 if err := os.Mkdir(path, 0700); err != nil {
110 t.Fatal(err)
111 }
112 if err := os.WriteFile(filepath.Join(path, "unknown"), []byte("retained"), 0600); err != nil {
113 t.Fatal(err)
114 }
115 fs.state = quarantineWriteFault{Store: f.disk, before: func(name string, body []byte) error {
116 var q servingParkQuarantine
117 if boundary == "fresh-before" && name == "namespace-quarantine.json" && servingParkDecode(body, &q) == nil && q.Fresh.Inode != 0 {
118 return unix.EIO
119 }
120 return nil
121 }, after: func(name string, body []byte) error {
122 var q servingParkQuarantine
123 if boundary == "parent" && name == "namespace-parent.json" {
124 return unix.EIO
125 }
126 if name == "namespace-quarantine.json" && servingParkDecode(body, &q) == nil && (boundary == "fresh" && q.Fresh.Inode != 0 || boundary == "complete" && q.Complete) {
127 return unix.EIO
128 }
129 return nil
130 }}
131 if boundary == "quarantine-rename" {
132 fs.rename = func(a int, b string, c int, d string, flags uint) error {
133 if err := unix.Renameat2(a, b, c, d, flags); err != nil {
134 return err
135 }
136 return unix.EIO
137 }
138 }
139 if _, err := f.park(); !errors.Is(err, unix.EIO) {
140 t.Fatal("missing interrupted boundary", err)
141 }
142 before := f.f.read()
143 restarted := *f.p
144 ledger := *f.p.ledger
145 restarted.ledger = &ledger
146 freshFS := newServingParkFS(fs.data, f.disk)
147 freshFS.owner, freshFS.mount = fs.owner, fs.mount
148 ledger.fs = freshFS
149 restarted.root.Stop.Authenticate = func(context.Context, net.Conn) (StopPeer, error) { panic("startup used a peer") }
150 // Namespace recovery needs only the independently recorded parent,
151 // not a surviving source or a freshly selected host installation.
152 if err := os.Rename(filepath.Dir(f.db), filepath.Dir(f.db)+"-saved"); err != nil {
153 t.Fatal(err)
154 }
155 page, err := restarted.recoverServingParks(t.Context(), "", servingParkRecoveryLimit)
156 if err != nil || len(page.Results) != 0 || page.More {
157 t.Fatalf("startup recovery: %+v %v", page, err)
158 }
159 var q servingParkQuarantine
160 body, err := f.disk.Read("namespace-quarantine.json")
161 if err != nil || servingParkDecode(body, &q) != nil || !q.Complete {
162 t.Fatal("startup did not finish quarantine", err)
163 }
164 body, err = os.ReadFile(filepath.Join(filepath.Dir(path), q.name(), "unknown"))
165 if err != nil || string(body) != "retained" || !reflect.DeepEqual(before, f.f.read()) {
166 t.Fatal("startup changed retained bytes or claim", err)
167 }
168 beforeRecord, err := f.disk.Read("namespace-quarantine.json")
169 if err != nil {
170 t.Fatal(err)
171 }
172 if page, err = restarted.recoverServingParks(t.Context(), "", servingParkRecoveryLimit); err != nil || len(page.Results) != 0 {
173 t.Fatal("startup replay", page, err)
174 }
175 afterRecord, err := f.disk.Read("namespace-quarantine.json")
176 if err != nil || string(beforeRecord) != string(afterRecord) {
177 t.Fatal("completed startup rewrote quarantine", err)
178 }
179 })
180 }
181}
182
183func TestServingNamespaceStartupCannotCaptureParent(t *testing.T) {
184 f, _, path := quarantineFixture(t)
185 if err := f.ledger.withNamespace(t.Context(), func() error { return f.fs.recoverRecordedNamespace(t.Context()) }); err != nil {
186 t.Fatal(err)
187 }
188 if _, err := os.Stat(filepath.Join(path, "unknown")); err != nil {
189 t.Fatal("startup moved unrecorded obstruction", err)
190 }
191 for _, name := range []string{"namespace-parent.json", "namespace.json", "namespace-quarantine.json"} {
192 if _, err := f.disk.Read(name); !errors.Is(err, os.ErrNotExist) {
193 t.Fatal("startup created authority", name, err)
194 }
195 }
196}
197
198func TestServingNamespaceParentCannotBeReplaced(t *testing.T) {
199 for _, fault := range []string{"parent", "corrupt-anchor", "corrupt-namespace", "mount", "mode"} {
200 t.Run(fault, func(t *testing.T) {
201 f := newServingRootFixture(t, true)
202 a, err := f.park()
203 if err != nil {
204 t.Fatal(err)
205 }
206 fs := f.p.ledger.fs.(*servingParkFS)
207 before, err := f.disk.Read("namespace-parent.json")
208 if err != nil {
209 t.Fatal(err)
210 }
211 switch fault {
212 case "parent":
213 path := filepath.Join(fs.data, "instances")
214 if err := os.Rename(path, path+"-saved"); err != nil {
215 t.Fatal(err)
216 }
217 if err := os.Mkdir(path, 0700); err != nil {
218 t.Fatal(err)
219 }
220 // Even moving the genuine inventoried child into the replacement
221 // parent does not authorize refreshing the recorded parent.
222 if err := os.Rename(filepath.Join(path+"-saved", provision.InstanceID(f.current.Host)), filepath.Dir(f.db)); err != nil {
223 t.Fatal(err)
224 }
225 case "corrupt-anchor":
226 if err := f.disk.Write("namespace-parent.json", []byte("{}")); err != nil {
227 t.Fatal(err)
228 }
229 before = []byte("{}")
230 case "corrupt-namespace":
231 if err := f.disk.Write("namespace.json", []byte("{}")); err != nil {
232 t.Fatal(err)
233 }
234 case "mount":
235 fs.mount = func(string) (bool, error) { return false, nil }
236 case "mode":
237 if err := os.Chmod(filepath.Join(fs.data, "instances"), 0777); err != nil {
238 t.Fatal(err)
239 }
240 }
241 page, err := f.p.recoverServingParks(t.Context(), "", servingParkRecoveryLimit)
242 if err != nil || len(page.Results) == 0 || page.Results[0].ID != "namespace" || page.Results[0].Err == nil {
243 t.Fatal("startup accepted unsafe parent", page, err)
244 }
245 if fault == "parent" {
246 if err := f.p.ledger.withNamespace(t.Context(), func() error {
247 return fs.recordNamespaceParent(t.Context(), f.current, a.Directory)
248 }); err == nil {
249 t.Fatal("parent recorder accepted a moved genuine source")
250 }
251 }
252 if _, err := f.park(); err == nil {
253 t.Fatal("authenticated request refreshed unsafe parent")
254 }
255 after, err := f.disk.Read("namespace-parent.json")
256 if err != nil || string(before) != string(after) {
257 t.Fatal("parent authority changed", err)
258 }
259 if _, err := f.disk.Read("namespace-quarantine.json"); !errors.Is(err, os.ErrNotExist) {
260 t.Fatal("unsafe parent authorized quarantine", err)
261 }
262 })
263 }
264}
265
266func TestServingNamespaceParentUpgradePreservesEarlierEvidence(t *testing.T) {
267 for _, fault := range []string{"", "parent", "corrupt-namespace", "corrupt-quarantine"} {
268 t.Run(fault, func(t *testing.T) {
269 f, parent, _ := quarantineFixture(t)
270 f.put("", "current database")
271 a, _ := f.evidence(10, true, "missing")
272 if _, err := quarantineRecover(t, f, parent); err != nil {
273 t.Fatal(err)
274 }
275 // These are checkpoint-14 namespace records: no parent anchor yet.
276 switch fault {
277 case "parent":
278 body, err := f.disk.Read("namespace.json")
279 if err != nil {
280 t.Fatal(err)
281 }
282 var n servingParkNamespace
283 if err := servingParkDecode(body, &n); err != nil {
284 t.Fatal(err)
285 }
286 n.Parent.Inode++
287 if err := f.ledger.write("namespace.json", n); err != nil {
288 t.Fatal(err)
289 }
290 case "corrupt-namespace":
291 if err := f.disk.Write("namespace.json", []byte("{}")); err != nil {
292 t.Fatal(err)
293 }
294 case "corrupt-quarantine":
295 if err := f.disk.Write("namespace-quarantine.json", []byte("{}")); err != nil {
296 t.Fatal(err)
297 }
298 }
299 err := f.ledger.withNamespace(t.Context(), func() error { return f.fs.recordNamespaceParent(t.Context(), a.Successor, a.Directory) })
300 if (err != nil) != (fault != "") {
301 t.Fatal("parent upgrade", err)
302 }
303 if fault != "" {
304 if _, err := f.disk.Read("namespace-parent.json"); !errors.Is(err, os.ErrNotExist) {
305 t.Fatal("upgrade blessed changed private evidence", err)
306 }
307 return
308 }
309 before, err := f.disk.Read("namespace-parent.json")
310 if err != nil {
311 t.Fatal(err)
312 }
313 // A failed write seam proves an established anchor is read-only.
314 f.fs.state = quarantineWriteFault{Store: f.disk, before: func(string, []byte) error { return unix.EIO }}
315 if err := f.ledger.withNamespace(t.Context(), func() error {
316 if err := f.fs.recordNamespaceParent(t.Context(), a.Successor, a.Directory); err != nil {
317 return err
318 }
319 return f.fs.recoverRecordedNamespace(t.Context())
320 }); err != nil {
321 t.Fatal("completed recovery wrote state", err)
322 }
323 after, err := f.disk.Read("namespace-parent.json")
324 if err != nil || string(before) != string(after) {
325 t.Fatal("parent replay changed record", err)
326 }
327 })
328 }
329}
330