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 "os"
9 "reflect"
10 "strings"
11 "testing"
12
13 "golang.org/x/sys/unix"
14)
15
16func TestServingParkRebootCommitResumes(t *testing.T) {
17 for _, phase := range []string{"rename-reply", "parked", "retagged", "complete", "source-gone"} {
18 t.Run(phase, func(t *testing.T) {
19 f, original := servingRebootPreparationFixture(t, phase)
20 id := original.Authorization.ID
21 before, err := f.disk.Read("retirement-" + id + ".json")
22 if err != nil {
23 t.Fatal(err)
24 }
25 if err := f.p.commitServingReboot(t.Context(), id); err != nil {
26 t.Fatal(err)
27 }
28 after, _ := f.disk.Read("retirement-" + id + ".json")
29 if string(before) != string(after) {
30 t.Fatal("commit rewrote historical retirement")
31 }
32 fs := f.p.ledger.fs.(*servingParkFS)
33 if err := fs.recoverRecordedNamespace(t.Context()); err != nil {
34 t.Fatal(err)
35 }
36 r, err := f.p.ledger.read(id)
37 if err != nil || r.Source.BootID != servingOtherBoot {
38 t.Fatal("current authority", err)
39 }
40 if r.ParkedAt != original.ParkedAt || r.ReapAfter != original.ReapAfter {
41 t.Fatal("retention changed")
42 }
43 if r.Complete {
44 f.f.now = r.ReapAfter
45 if err := f.p.ledger.reapLocked(t.Context(), id); err != nil {
46 t.Fatal("reap", err)
47 }
48 } else {
49 f.tags(f.current.Stamp)
50 if err := f.p.recoverServingPark(t.Context(), r); err != nil {
51 t.Fatal("resume", err)
52 }
53 }
54 raw, err := f.p.ledger.readHistorical(id)
55 if err != nil || raw.Source.BootID != original.Source.BootID || raw.Authorization != original.Authorization {
56 t.Fatal("history changed", err)
57 }
58 })
59 }
60}
61
62func TestServingParkRebootCommitLostWrite(t *testing.T) {
63 for _, before := range []bool{true, false} {
64 for at := 1; at <= 3; at++ {
65 f, r := servingRebootPreparationFixture(t, "complete")
66 fault := &servingParkFaultStore{Store: f.disk, failAt: at, before: before}
67 f.p.ledger.state = fault
68 fs := f.p.ledger.fs.(*servingParkFS)
69 fs.state = fault
70 err := f.p.commitServingReboot(t.Context(), r.Authorization.ID)
71 if !errors.Is(err, os.ErrPermission) {
72 t.Fatalf("%v/%d: %v", before, at, err)
73 }
74 f.p.ledger.state = f.disk
75 fs.state = f.disk
76 if err := f.p.commitServingReboot(t.Context(), r.Authorization.ID); err != nil {
77 t.Fatal("retry", err)
78 }
79 current, err := f.p.ledger.read(r.Authorization.ID)
80 if err != nil {
81 t.Fatal(err)
82 }
83 if err := fs.CheckParked(&current); err != nil {
84 t.Fatal(err)
85 }
86 }
87 }
88}
89
90func TestServingParkRebootCommitRefusesChangedEvidence(t *testing.T) {
91 for _, fault := range []string{"file", "slot", "claim", "census"} {
92 t.Run(fault, func(t *testing.T) {
93 f, r := servingRebootPreparationFixture(t, "parked")
94 if err := f.p.prepareServingReboot(t.Context(), r.Authorization.ID); err != nil {
95 t.Fatal(err)
96 }
97 switch fault {
98 case "file":
99 fs := f.p.ledger.fs.(*servingParkFS)
100 fs.handle = func(*os.File) (servingParkPersistentDirectory, error) {
101 return servingParkPersistentDirectory{}, unix.EIO
102 }
103 case "slot":
104 s, _ := f.p.ledger.slot(r.Authorization)
105 s.ID = ""
106 if err := f.p.ledger.write("slot-"+servingParkKey(s.Box, s.Host)+".json", s); err != nil {
107 t.Fatal(err)
108 }
109 case "claim":
110 f.tags(f.current.Stamp)
111 case "census":
112 f.p.root.RequireNewWork = func(_ context.Context) error { return unix.EPERM }
113 }
114 before := servingBootSnapshot(t, f.disk.dir)
115 if err := f.p.commitServingReboot(t.Context(), r.Authorization.ID); err == nil {
116 t.Fatal("accepted changed evidence")
117 }
118 if !reflect.DeepEqual(before, servingBootSnapshot(t, f.disk.dir)) {
119 t.Fatal("refusal wrote authority")
120 }
121 })
122 }
123}
124
125func TestServingParkRebootCommitSecondBoot(t *testing.T) {
126 f, r := servingRebootPreparationFixture(t, "complete")
127 id := r.Authorization.ID
128 if err := f.p.commitServingReboot(t.Context(), id); err != nil {
129 t.Fatal(err)
130 }
131 fs := f.p.ledger.fs.(*servingParkFS)
132 nextBoot := "11234567-89ab-4cde-8fab-0123456789ab"
133 fs.boot = func() (string, error) { return nextBoot, nil }
134 if err := f.p.commitServingReboot(t.Context(), id); err != nil {
135 t.Fatal("second boot", err)
136 }
137 current, err := f.p.ledger.read(id)
138 if err != nil || current.Source.BootID != nextBoot {
139 t.Fatal(err)
140 }
141 if err := fs.CheckParked(&current); err != nil {
142 t.Fatal(err)
143 }
144 historical, err := f.p.ledger.readHistorical(id)
145 if err != nil || !reflect.DeepEqual(historical, r) {
146 t.Fatal("history changed", err)
147 }
148}
149
150func TestServingParkRebootStartupCommits(t *testing.T) {
151 f, r := servingRebootPreparationFixture(t, "retagged")
152 page, err := f.p.recoverServingParks(t.Context(), "", servingParkRecoveryLimit)
153 if err != nil {
154 t.Fatal(err)
155 }
156 for _, result := range page.Results {
157 if result.Err != nil {
158 t.Fatal(result)
159 }
160 }
161 current, err := f.p.ledger.read(r.Authorization.ID)
162 if err != nil || !current.Complete || current.Source.BootID != servingOtherBoot {
163 t.Fatal(current, err)
164 }
165}
166
167func TestServingParkRebootBindingCollectionCrash(t *testing.T) {
168 f, old := servingRebootPreparationFixture(t, "complete")
169 id := old.Authorization.ID
170 if err := f.p.commitServingReboot(t.Context(), id); err != nil {
171 t.Fatal(err)
172 }
173 current, err := f.p.ledger.read(id)
174 if err != nil {
175 t.Fatal(err)
176 }
177 f.f.now = current.ReapAfter
178 if err := f.p.ledger.reapLocked(t.Context(), id); err != nil {
179 t.Fatal(err)
180 }
181 current, err = f.p.ledger.read(id)
182 if err != nil {
183 t.Fatal(err)
184 }
185 slot, err := f.p.ledger.slot(current.Authorization)
186 if err != nil {
187 t.Fatal(err)
188 }
189 // The fixture advances the slot reference; no payload is deleted by this.
190 slot.ID = ""
191 slot.Pending = ""
192 slot.Last = strings.Repeat("b", 64)
193 collect := func() error {
194 return f.p.ledger.withNamespace(t.Context(), func() error { return f.p.ledger.collectClosedLocked(t.Context(), &current, slot) })
195 }
196 if err := collect(); err != nil {
197 t.Fatal(err)
198 }
199 current, err = f.p.ledger.read(id)
200 if err != nil {
201 t.Fatal(err)
202 }
203 f.f.now = current.CollectAfter
204 fault := &servingCollectionFaultStore{Store: f.disk}
205 fault.remove = func(name string) error {
206 if err := f.disk.Remove(name); err != nil {
207 return err
208 }
209 if name == "retirement-"+id+".json" {
210 return unix.EIO
211 }
212 return nil
213 }
214 f.p.ledger.state = fault
215 if err := collect(); !errors.Is(err, unix.EIO) {
216 t.Fatal(err)
217 }
218 f.p.ledger.state = f.disk
219 if err := f.p.ledger.maintainParks(t.Context(), func() error { return nil }); err != nil {
220 t.Fatal(err)
221 }
222 for _, name := range []string{"current-retirement-" + id + ".json", servingRebootPreparationName(id, servingOtherBoot)} {
223 if _, err := f.disk.Read(name); !errors.Is(err, os.ErrNotExist) {
224 t.Fatal("reboot adjunct survived", name, err)
225 }
226 }
227 if _, err := f.disk.Read("current-namespace.json"); err != nil {
228 t.Fatal("collected namespace binding", err)
229 }
230}
231