carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "os"
6 "strings"
7 "testing"
8
9 "github.com/carlosframework/platform/internal/hostbox"
10 "github.com/carlosframework/platform/internal/release"
11 "github.com/carlosframework/platform/internal/rootstate"
12)
13
14func TestPlan14GenerationRetirementRootBinding(t *testing.T) {
15 for _, attack := range []string{"none", "open-writers", "unsettled", "controller", "inputs", "invocation", "executable", "installation", "kernel", "no-kernel", "no-journal"} {
16 t.Run(attack, func(t *testing.T) {
17 f, disk, _ := generationLaunchFixture(t)
18 if _, err := f.root.Serve(t.Context(), f.peer, f.req); err != nil {
19 t.Fatal(err)
20 }
21 r := readGenerationLaunch(t, disk)
22 if attack != "unsettled" {
23 if err := f.root.stop(t.Context(), f.peer, *f.req.Reservation); err != nil {
24 t.Fatal(err)
25 }
26 }
27 if attack != "open-writers" {
28 if err := f.journal.CloseWriters(t.Context(), f.target); err != nil {
29 t.Fatal(err)
30 }
31 }
32 b := r.Binding
33 switch attack {
34 case "controller":
35 b.ControllerPID++
36 case "inputs":
37 b.Inputs = "changed"
38 case "invocation":
39 b.Invocation = "changed"
40 case "executable":
41 b.Executable.Inode++
42 case "installation":
43 b.Installation = "changed"
44 }
45 if b != r.Binding {
46 r.Binding = b
47 }
48 calls := 0
49 verifier := generationRetirement{guard: f.root.Guard, journal: f.journal, kernel: func(context.Context, release.GenerationReservation) (string, error) {
50 calls++
51 if attack == "kernel" {
52 return "", os.ErrPermission
53 }
54 return r.Binding.Digest, nil
55 }}
56 if attack == "no-kernel" {
57 verifier.kernel = nil
58 }
59 if attack == "no-journal" {
60 verifier.journal = nil
61 }
62 err := verifier.closed(t.Context(), r)
63 if err == nil {
64 _, err = verifier.verify(t.Context(), r)
65 }
66 if attack == "none" {
67 if err != nil || calls != 1 {
68 t.Fatalf("positive: %v, kernel %d", err, calls)
69 }
70 } else if err == nil {
71 t.Fatal("unverified retirement accepted")
72 }
73 })
74 }
75}
76
77func TestPlan14GenerationRetirementAfterLostStarted(t *testing.T) {
78 f, disk, _ := generationLaunchFixture(t)
79 permit, err := f.root.generations.reserve(t.Context(), f.root, f.peer, f.req)
80 if err != nil {
81 t.Fatal(err)
82 }
83 if err := f.journal.IntendUnit(t.Context(), *f.req.Reservation); err != nil {
84 t.Fatal(err)
85 }
86 if err := f.journal.SettleUnit(t.Context(), f.target, f.req.Reservation.ID); err != nil {
87 t.Fatal(err)
88 }
89 if err := f.journal.CloseWriters(t.Context(), f.target); err != nil {
90 t.Fatal(err)
91 }
92 verifier := generationRetirement{guard: f.root.Guard, journal: f.journal, kernel: func(context.Context, release.GenerationReservation) (string, error) {
93 return permit.reservation.Binding.Digest, nil
94 }}
95 if err := verifier.retire(t.Context(), &release.RootGenerations{Store: disk}, permit.reservation); err != nil {
96 t.Fatal(err)
97 }
98 got, err := permit.ledger.Read(permit.reservation.Binding)
99 if err != nil || got.Retirement == "" {
100 t.Fatalf("pending not retired: %+v %v", got, err)
101 }
102 if err := f.journal.IntendUnit(t.Context(), *f.req.Reservation); err == nil {
103 t.Fatal("closed launch replayed")
104 }
105 if _, err := permit.ledger.Reserve(permit.reservation.Binding); err == nil {
106 t.Fatal("retired generation replayed")
107 }
108}
109
110func TestPlan14GenerationRetirementKeepsActivePin(t *testing.T) {
111 for _, mode := range []string{"running", "running-wrong-controller", "running-wrong-inputs", "running-wrong-process", "settled-open-writers", "closed-unsettled", "closed", "wrong-controller", "wrong-inputs", "kernel"} {
112 t.Run(mode, func(t *testing.T) {
113 f, disk, _ := generationLaunchFixture(t)
114 if _, err := f.root.Serve(t.Context(), f.peer, f.req); err != nil {
115 t.Fatal(err)
116 }
117 r := readGenerationLaunch(t, disk)
118 if !strings.HasPrefix(mode, "running") && mode != "closed-unsettled" {
119 if err := f.root.stop(t.Context(), f.peer, *f.req.Reservation); err != nil {
120 t.Fatal(err)
121 }
122 }
123 if !strings.HasPrefix(mode, "running") && mode != "settled-open-writers" {
124 if err := f.journal.CloseWriters(t.Context(), f.target); err != nil {
125 t.Fatal(err)
126 }
127 }
128 if mode == "wrong-controller" || mode == "running-wrong-controller" {
129 r.Binding.ControllerPID++
130 }
131 if mode == "wrong-inputs" || mode == "running-wrong-inputs" {
132 r.Binding.Inputs = "other"
133 }
134 if mode == "running-wrong-process" {
135 r.Process.PID++
136 }
137 calls := 0
138 v := generationRetirement{guard: f.root.Guard, journal: f.journal, kernel: func(context.Context, release.GenerationReservation) (string, error) {
139 calls++
140 if mode == "kernel" {
141 return "", os.ErrPermission
142 }
143 return r.Binding.Digest, nil
144 }}
145 err := v.retire(t.Context(), &release.RootGenerations{Store: disk}, r)
146 got := readGenerationLaunch(t, disk)
147 switch mode {
148 case "running", "settled-open-writers", "closed-unsettled":
149 if err != nil || calls != 0 || got != r {
150 t.Fatalf("active pin changed or blocked the pass: %v calls=%d got=%+v", err, calls, got)
151 }
152 case "closed":
153 if err != nil || calls != 1 || got.Retirement == "" {
154 t.Fatalf("closed pin not retired: %v calls=%d got=%+v", err, calls, got)
155 }
156 default:
157 if err == nil || got.Retirement != "" {
158 t.Fatalf("uncertain pin released: %v %+v", err, got)
159 }
160 }
161 })
162 }
163}
164
165func TestPlan14GenerationRetirementCensusGate(t *testing.T) {
166 for _, flag := range []string{"", "1"} {
167 for _, mode := range []string{"legacy", "census", "unreadable"} {
168 t.Run(flag+"/"+mode, func(t *testing.T) {
169 t.Setenv("CARLOS_HOST_CLAIM", flag)
170 f, disk, _ := generationLaunchFixture(t)
171 if _, err := f.root.Serve(t.Context(), f.peer, f.req); err != nil {
172 t.Fatal(err)
173 }
174 r := readGenerationLaunch(t, disk)
175 if err := f.root.stop(t.Context(), f.peer, *f.req.Reservation); err != nil {
176 t.Fatal(err)
177 }
178 if err := f.journal.CloseWriters(t.Context(), f.target); err != nil {
179 t.Fatal(err)
180 }
181 opened, retired := 0, 0
182 source := productionRootGenerationPins().(generationPinSource)
183 source.legacy = func() (bool, error) {
184 if mode == "unreadable" {
185 return false, os.ErrPermission
186 }
187 return mode == "legacy", nil
188 }
189 source.open = func() (rootstate.Store, error) { opened++; return disk, nil }
190 source.retire = func(ctx context.Context, g *release.RootGenerations) error {
191 retired++
192 verifier := generationRetirement{guard: f.root.Guard, journal: f.journal, kernel: func(context.Context, release.GenerationReservation) (string, error) { return r.Binding.Digest, nil }}
193 return verifier.retire(ctx, g, r)
194 }
195 _, err := source.StagedPins(t.Context(), f.root.generations.optDir)
196 got := readGenerationLaunch(t, disk)
197 switch mode {
198 case "legacy":
199 if err != nil || opened != 0 || retired != 0 || got != r {
200 t.Fatalf("claims-off effects: %v %d %d %+v", err, opened, retired, got)
201 }
202 case "unreadable":
203 if err == nil || opened != 0 || retired != 0 || got != r {
204 t.Fatal("unknown census chose a path")
205 }
206 case "census":
207 if err == nil || opened != 1 || retired != 1 || got.Retirement == "" {
208 t.Fatalf("retirement substituted for completeness: %v %d %d %+v", err, opened, retired, got)
209 }
210 }
211 })
212 }
213 }
214}
215
216func TestPlan14GenerationRetirementContinuesPastActivePin(t *testing.T) {
217 f, disk, _ := generationLaunchFixture(t)
218 if _, err := f.root.Serve(t.Context(), f.peer, f.req); err != nil {
219 t.Fatal(err)
220 }
221 running := readGenerationLaunch(t, disk)
222 target := f.target
223 target.ControllerID = "closed-controller"
224 prior, err := f.journal.Read(t.Context(), f.target)
225 if err != nil {
226 t.Fatal(err)
227 }
228 prior.Target, prior.Reservations = target, nil
229 prior.Files, prior.ProducerIDs, prior.NoState = nil, nil, true
230 if err := f.journal.Prelaunch(t.Context(), prior); err != nil {
231 t.Fatal(err)
232 }
233 b := running.Binding
234 b.Target = generationOwner(target)
235 b.Attempt, b.Invocation = "second:main", "second-invocation"
236 g := &release.RootGenerations{Store: disk}
237 second, err := g.Reserve(b)
238 if err != nil {
239 t.Fatal(err)
240 }
241 unit := *f.req.Reservation
242 unit.Target, unit.ID, unit.InvocationID = target, b.Attempt, b.Invocation
243 if err := f.journal.IntendUnit(t.Context(), unit); err != nil {
244 t.Fatal(err)
245 }
246 if err := f.journal.SettleUnit(t.Context(), target, unit.ID); err != nil {
247 t.Fatal(err)
248 }
249 if err := f.journal.CloseWriters(t.Context(), target); err != nil {
250 t.Fatal(err)
251 }
252 calls := 0
253 v := generationRetirement{guard: f.root.Guard, journal: f.journal, kernel: func(_ context.Context, r release.GenerationReservation) (string, error) {
254 calls++
255 if r.Binding != second.Binding {
256 t.Fatal("active reservation reached kernel retirement")
257 }
258 return r.Binding.Digest, nil
259 }}
260 if err := v.retireAll(t.Context(), g, []release.GenerationReservation{running, second}); err != nil {
261 t.Fatal(err)
262 }
263 got, err := g.Read(running.Binding)
264 if err != nil || got != running {
265 t.Fatalf("active pin changed: %+v %v", got, err)
266 }
267 got, err = g.Read(second.Binding)
268 if err != nil || calls != 1 || got.Retirement == "" {
269 t.Fatalf("later closed pin not retired: %+v %v calls=%d", got, err, calls)
270 }
271 launch, err := f.journal.Read(t.Context(), f.target)
272 if err != nil || launch.WritersClosed || launch.Reservations[0].State != hostbox.UnitStarted {
273 t.Fatalf("active launch changed: %+v %v", launch, err)
274 }
275}
276
277func TestPlan14GenerationRetirementKeepsPrelaunchPin(t *testing.T) {
278 f, disk, _ := generationLaunchFixture(t)
279 permit, err := f.root.generations.reserve(t.Context(), f.root, f.peer, f.req)
280 if err != nil {
281 t.Fatal(err)
282 }
283 r := permit.reservation
284 v := generationRetirement{guard: f.root.Guard, journal: f.journal, kernel: func(context.Context, release.GenerationReservation) (string, error) {
285 t.Fatal("prelaunch reservation reached kernel retirement")
286 return "", os.ErrPermission
287 }}
288 if err := v.retire(t.Context(), &release.RootGenerations{Store: disk}, r); err != nil {
289 t.Fatal(err)
290 }
291 if got := readGenerationLaunch(t, disk); got != r {
292 t.Fatalf("prelaunch pin changed: %+v", got)
293 }
294 if err := v.closed(t.Context(), r); err == nil {
295 t.Fatal("prelaunch mistaken for closed authority")
296 }
297}
298