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 "bytes"
7 "errors"
8 "os"
9 "path/filepath"
10 "reflect"
11 "strings"
12 "testing"
13
14 "github.com/carlosframework/platform/internal/hostbox"
15 "golang.org/x/sys/unix"
16)
17
18const servingOtherBoot = "01234567-89ab-4cde-8fab-0123456789ab"
19
20func TestServingParkBootIdentity(t *testing.T) {
21 f := newServingParkFS("/data", nil)
22 id, err := f.currentBoot()
23 if err != nil || id == "" || f.checkBoot(id) != nil {
24 t.Fatal("kernel boot identity", id, err)
25 }
26 for _, id := range []string{"", "not-a-boot", strings.Repeat("0", 36), "00000000-0000-0000-0000-000000000000", strings.ToUpper(servingOtherBoot), servingOtherBoot + "\n", "01234567-89ab-4cde-8fab-0123456789ag"} {
27 t.Run(id, func(t *testing.T) {
28 f.boot = func() (string, error) { return id, nil }
29 if _, err := f.currentBoot(); !errors.Is(err, hostbox.ErrChanged) {
30 t.Fatal("malformed boot accepted", err)
31 }
32 })
33 }
34 f.boot = func() (string, error) { return "", unix.EIO }
35 if _, err := f.currentBoot(); !errors.Is(err, unix.EIO) {
36 t.Fatal("boot read failure hidden", err)
37 }
38 f.boot = nil
39 if _, err := f.currentBoot(); !errors.Is(err, hostbox.ErrUnsupported) {
40 t.Fatal("missing boot dependency accepted", err)
41 }
42}
43
44// Change only the boot observation. Device, inode and mount number stay equal,
45// reproducing the equality that a recycled mount ID can produce after reboot.
46func TestServingParkBootReuseRefusesEffects(t *testing.T) {
47 for _, fault := range []string{"reboot", "missing-boot", "read-error", "malformed"} {
48 t.Run(fault, func(t *testing.T) {
49 f := newServingParkDiskFixture(t)
50 a, receipts := f.evidence(1, true, "missing")
51 f.put("", "last writer")
52 r, err := f.park(a, receipts)
53 if err != nil {
54 t.Fatal(err)
55 }
56 f.complete(a)
57 before, err := f.disk.Read("namespace.json")
58 if err != nil {
59 t.Fatal(err)
60 }
61 var n servingParkNamespace
62 if servingParkDecode(before, &n) != nil || n.BootID == "" || n.BootID == servingOtherBoot {
63 t.Fatal("fixture did not capture the current boot")
64 }
65 parent, id, err := f.fs.parent()
66 if err != nil {
67 t.Fatal(err)
68 }
69 parent.Close()
70 if id != r.Source.Parent {
71 t.Fatal("test changed more than the boot")
72 }
73 switch fault {
74 case "reboot":
75 f.fs.boot = func() (string, error) { return servingOtherBoot, nil }
76 case "missing-boot":
77 n.BootID = ""
78 if err := f.ledger.write("namespace.json", n); err != nil {
79 t.Fatal(err)
80 }
81 case "read-error":
82 f.fs.boot = func() (string, error) { return "", unix.EIO }
83 case "malformed":
84 f.fs.boot = func() (string, error) { return "invalid", nil }
85 }
86 before, err = f.disk.Read("namespace.json")
87 if err != nil {
88 t.Fatal(err)
89 }
90 writes := 0
91 save := func() error { writes++; return nil }
92 for name, call := range map[string]func(*servingParkRecord) error{
93 "inspect": f.fs.CheckParked,
94 "prepare": func(r *servingParkRecord) error { return f.fs.Prepare(r, save) },
95 "move": func(r *servingParkRecord) error { return f.fs.Move(r, save) },
96 "delete": func(r *servingParkRecord) error { return f.fs.Delete(r, save) },
97 } {
98 copy := r
99 if err := call(&copy); err == nil || !reflect.DeepEqual(r, copy) || writes != 0 {
100 t.Fatal(name, "reused boot authority", err, writes)
101 }
102 }
103 after, err := f.disk.Read("namespace.json")
104 if err != nil || !bytes.Equal(before, after) {
105 t.Fatal("refusal rewrote namespace", err)
106 }
107 body, err := os.ReadFile(f.path(a, ""))
108 if err != nil || string(body) != "last writer" {
109 t.Fatal("refusal changed database", err)
110 }
111 })
112 }
113}
114
115func TestServingNamespaceBootRecoveryRefusesAdoption(t *testing.T) {
116 for _, boundary := range []string{"parent", "namespace", "quarantine", "old-parent", "old-namespace", "old-quarantine"} {
117 t.Run(boundary, func(t *testing.T) {
118 f := newServingRootFixture(t, true)
119 fs := f.p.ledger.fs.(*servingParkFS)
120 path := filepath.Join(fs.data, "instances", ".serving-parked")
121 if strings.Contains(boundary, "quarantine") {
122 if err := os.Mkdir(path, 0700); err != nil {
123 t.Fatal(err)
124 }
125 if err := os.WriteFile(filepath.Join(path, "unknown"), []byte("retain"), 0600); err != nil {
126 t.Fatal(err)
127 }
128 }
129 a, err := f.park()
130 if err != nil {
131 t.Fatal(err)
132 }
133 anchor, err := fs.namespaceParent()
134 if err != nil || anchor.BootID == "" {
135 t.Fatal("missing boot anchor", err)
136 }
137 name := "namespace-parent.json"
138 switch boundary {
139 case "namespace", "old-namespace":
140 name = "namespace.json"
141 case "quarantine", "old-quarantine":
142 name = "namespace-quarantine.json"
143 }
144 if strings.HasPrefix(boundary, "old-") {
145 body, err := f.disk.Read(name)
146 if err != nil {
147 t.Fatal(err)
148 }
149 body = bytes.ReplaceAll(body, []byte(`"BootID":"`+anchor.BootID+`",`), nil)
150 if err := f.disk.Write(name, body); err != nil {
151 t.Fatal(err)
152 }
153 } else {
154 fs.boot = func() (string, error) { return servingOtherBoot, nil }
155 }
156 before := servingBootSnapshot(t, f.disk.dir)
157 claim := f.f.read()
158 if err := fs.recoverRecordedNamespace(t.Context()); err == nil {
159 t.Fatal("startup accepted missing or changed boot")
160 }
161 if _, err := fs.recoverNamespace(t.Context(), anchor.Parent); err == nil {
162 t.Fatal("quarantine accepted missing or changed boot")
163 }
164 if err := fs.recordNamespaceParent(t.Context(), a.Successor, a.Directory); err == nil {
165 // A valid existing anchor need not examine the namespace, but it
166 // must never overwrite the earlier boot or infer a missing one.
167 if boundary != "old-namespace" && boundary != "old-quarantine" {
168 t.Fatal("authenticated parent adopted old boot")
169 }
170 }
171 if after := servingBootSnapshot(t, f.disk.dir); !reflect.DeepEqual(before, after) || !reflect.DeepEqual(claim, f.f.read()) {
172 t.Fatal("recovery refusal changed private evidence or claim")
173 }
174 })
175 }
176}
177
178func servingBootSnapshot(t *testing.T, root string) map[string]string {
179 t.Helper()
180 names, err := os.ReadDir(root)
181 if err != nil {
182 t.Fatal(err)
183 }
184 out := make(map[string]string)
185 for _, name := range names {
186 if name.IsDir() {
187 continue
188 }
189 body, err := os.ReadFile(filepath.Join(root, name.Name()))
190 if err != nil {
191 t.Fatal(err)
192 }
193 out[name.Name()] = string(body)
194 }
195 return out
196}
197