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 "crypto/sha256"
6 "encoding/hex"
7 "encoding/json"
8 "errors"
9 "testing"
10 "time"
11
12 "github.com/carlosframework/platform/internal/genselect"
13 "github.com/carlosframework/platform/internal/hostbox"
14 "github.com/carlosframework/platform/internal/hostclaim"
15 "github.com/carlosframework/platform/internal/hostfiles"
16 "github.com/carlosframework/platform/internal/hostguard"
17 "github.com/carlosframework/platform/internal/hostwrite"
18 "github.com/carlosframework/platform/internal/moves"
19 "github.com/carlosframework/platform/internal/provision"
20 "github.com/superfly/ltx"
21)
22
23// fenceWork is an admitted spawn selection whose fence can be closed: once
24// shut, every check refuses. It counts the checks it answered.
25type fenceWork struct {
26 shut *bool
27 checks *int
28}
29
30func newFenceWork() fenceWork { return fenceWork{shut: new(bool), checks: new(int)} }
31
32func (w fenceWork) With(_ context.Context, _ hostwrite.Identity, fn func() error) error {
33 *w.checks++
34 if *w.shut {
35 return hostbox.ErrChanged
36 }
37 return fn()
38}
39
40func (f *poFixture) markerKey() string { return "instances/" + poHost + "/generation.json" }
41
42func (f *poFixture) currentMarker() (genselect.Marker, bool) {
43 f.t.Helper()
44 body, ok := f.bucket.Current("data", f.markerKey())
45 var m genselect.Marker
46 if ok && json.Unmarshal(body, &m) != nil {
47 f.t.Fatalf("marker %s", body)
48 }
49 return m, ok
50}
51
52// selectAndAdmit runs Select and the admission CAS for the claim as it is.
53func (f *poFixture) selectAndAdmit() (hostclaim.Observed, ClaimLaunchInputs) {
54 f.t.Helper()
55 o := f.read()
56 in, err := f.owners.Select(f.t.Context(), f.route, o, poProbe)
57 if err != nil {
58 f.t.Fatal(err)
59 }
60 return f.admit(o, in), in
61}
62
63// Finding 1: AdmissionEvidence, immediately before the admission's CAS,
64// refuses a landing past its deadline, a record attempt outside what T11
65// admitted, an install the record does not vouch for, and a baseline whose
66// pin is not the build's.
67func TestMovesProbeOwnersAdmissionEvidenceChecksTheLanding(t *testing.T) {
68 past := time.Date(2026, 10, 9, 11, 59, 0, 0, time.UTC)
69 for name, tc := range map[string]struct {
70 rec func(*provision.Record)
71 plan func(*genselect.Plan)
72 }{
73 "past the cut deadline": {rec: func(r *provision.Record) { r.Move.CutDeadline = &past }},
74 "past the landing deadline": {rec: func(r *provision.Record) { r.Move.LandingDeadline = &past }},
75 "no deadline": {rec: func(r *provision.Record) { r.Move.CutDeadline = nil }},
76 "no attempt counted": {rec: func(r *provision.Record) { r.Move.Landing.Attempts = 0 }},
77 "beyond the admitted": {rec: func(r *provision.Record) { r.Move.Landing.Attempts = 4 }},
78 "another installed db": {rec: func(r *provision.Record) {
79 r.Move.Landing.InstalledDBSHA256 = "sha256:" + hex.EncodeToString(make([]byte, 32))
80 }},
81 "no installed db": {rec: func(r *provision.Record) { r.Move.Landing.InstalledDBSHA256 = "" }},
82 "another pin version": {plan: func(p *genselect.Plan) {
83 pin := *p.Pin
84 pin.SnapshotVersion = "other"
85 p.Pin = &pin
86 }},
87 "no pin": {plan: func(p *genselect.Plan) { p.Pin = nil }},
88 } {
89 t.Run(name, func(t *testing.T) {
90 f := newPOFixture(t)
91 ctx := t.Context()
92 o := f.read()
93 in, err := f.owners.Select(ctx, f.route, o, poProbe)
94 if err != nil {
95 t.Fatal(err)
96 }
97 if err := f.owners.AdmissionEvidence(ctx, o, in.Admission, in.Baseline); err != nil {
98 t.Fatalf("the unchanged landing: %v", err)
99 }
100 plan := *in.Baseline
101 if tc.rec != nil {
102 tc.rec(f.rec)
103 }
104 if tc.plan != nil {
105 tc.plan(&plan)
106 }
107 if err := f.owners.AdmissionEvidence(ctx, o, in.Admission, &plan); err == nil {
108 t.Fatal("admitted")
109 }
110 })
111 }
112}
113
114// Finding 2: the prepared marker is written only under the admitted
115// writer's live fence, checked by the store before every request attempt,
116// and carries the install manifest's digest, verified against install.json.
117func TestMovesProbeOwnersPreparedMarkerIsFenced(t *testing.T) {
118 f := newPOFixture(t)
119 ctx := t.Context()
120 admitted, in := f.selectAndAdmit()
121 // Group D's prepared Publish with no fence held for the session.
122 if err := f.owners.Publish(ctx, admitted, in); err == nil {
123 t.Fatal("an unfenced prepared marker was published")
124 }
125 if _, ok := f.currentMarker(); ok {
126 t.Fatal("a marker was written without the fence")
127 }
128 fence := newFenceWork()
129 if err := f.owners.Materialize(ctx, admitted, in, fence); err != nil {
130 t.Fatal(err)
131 }
132 m, ok := f.currentMarker()
133 install, _ := f.bucket.Current("data", moves.InstallManifestKey(moves.GenerationPrefix("instances/"+poHost, poOp)))
134 sum := sha256.Sum256(install)
135 if !ok || m.Op != poOp || m.ManifestSHA256 != hex.EncodeToString(sum[:]) || m.SnapshotTXID != 42 {
136 t.Fatalf("marker %+v", m)
137 }
138 // Group D's call now replays the marker this session wrote.
139 written := f.bucket.LatestID("data", f.markerKey())
140 if err := f.owners.Publish(ctx, admitted, in); err != nil || f.bucket.LatestID("data", f.markerKey()) != written {
141 t.Fatalf("replay: %v", err)
142 }
143 if err := f.owners.PublicationEvidence(ctx, admitted); err != nil {
144 t.Fatal(err)
145 }
146 // Another writer replaced it and the session's fence has closed: nothing
147 // is written over it.
148 f.bucket.Put("data", f.markerKey(), mustJSON(t, genselect.Marker{Op: "other", Incarnation: f.inc, Host: poHost, Box: "uk-box", InstalledAt: time.Unix(3, 0).UTC()}))
149 other := f.bucket.LatestID("data", f.markerKey())
150 *fence.shut = true
151 if err := f.owners.Publish(ctx, admitted, in); err == nil || f.bucket.LatestID("data", f.markerKey()) != other {
152 t.Fatalf("a marker written after the fence closed: %v", err)
153 }
154}
155
156// Finding 2: an install.json that is not the journalled manifest is not
157// published, and the probe does not start.
158func TestMovesProbeOwnersPreparedMarkerNeedsTheInstallManifest(t *testing.T) {
159 f := newPOFixture(t)
160 other := f.manifest
161 other.BuiltAt = time.Unix(9, 0).UTC()
162 f.bucket.Put("data", moves.InstallManifestKey(moves.GenerationPrefix("instances/"+poHost, poOp)), mustJSON(t, other))
163 admitted, in := f.selectAndAdmit()
164 if err := f.owners.Materialize(t.Context(), admitted, in, newFenceWork()); err == nil {
165 t.Fatal("published over an install.json the journal does not name")
166 }
167 if _, ok := f.currentMarker(); ok {
168 t.Fatal("a marker was written")
169 }
170}
171
172// Finding 3: the env pin is the report of the launch Select selected: a
173// launch environment that changed is refused though the pin and every other
174// input are unchanged; the probe-only CARLOS_MOVE_PROBE is not part of it.
175func TestMovesProbeOwnersEnvPinIsTheSelectedLaunch(t *testing.T) {
176 f := newPOFixture(t)
177 if _, err := f.owners.Select(t.Context(), f.route, f.read(), poProbe); err != nil {
178 t.Fatalf("the pinned launch: %v", err)
179 }
180 f.launch.Env = []string{"A=2", "CARLOS_MOVE_PROBE=0"}
181 _, err := f.owners.Select(t.Context(), f.route, f.read(), poProbe)
182 var changed *ProbeInputsChanged
183 if !errors.As(err, &changed) || changed.Reason != provision.RollbackConfigChanged {
184 t.Fatalf("a changed launch environment: %v", err)
185 }
186 f.launch.Env = []string{"A=1"}
187 if _, err := f.owners.Select(t.Context(), f.route, f.read(), poProbe); err != nil {
188 t.Fatalf("without the probe-only variable: %v", err)
189 }
190}
191
192// policyWork is a held selection that asks the real MovesClaimPolicy, as
193// hostbox.WorkSource does, before it runs anything.
194type policyWork struct {
195 f *poFixture
196 action string
197}
198
199func (w policyWork) With(ctx context.Context, id hostwrite.Identity, fn func() error) error {
200 o := w.f.read()
201 c := o.Claim
202 s := hostbox.Selection{Host: poHost, Action: w.action, Box: "uk-box", Region: "uk", Captured: hostguard.Captured{Incarnation: c.Holder.Incarnation, Stamp: c.Holder.Stamp},
203 Kind: c.Op.Kind, Op: c.Op.ID, Phase: c.Op.Phase, Generation: c.Holder.Generation, Session: c.Holder.Session}
204 if err := MovesClaimPolicy(func(context.Context, string, string, string) (*provision.Record, error) { return w.f.rec, nil })(ctx, c, s, id); err != nil {
205 return err
206 }
207 return fn()
208}
209
210// Finding 4: a render whose etags are not the pins is ProbeInputsChanged
211// (the landing's RollbackConfigChanged), decided before any held work is
212// selected or written, through the real policy, which would otherwise
213// refuse the write with its own error.
214func TestMovesProbeOwnersChangedConfigThroughThePolicy(t *testing.T) {
215 f := newPOFixture(t)
216 ctx := t.Context()
217 f.owners.Works = func(_ context.Context, _, action string, _ hostguard.Captured) (hostwrite.Work, error) {
218 f.order = append(f.order, "select:"+action)
219 return policyWork{f: f, action: action}, nil
220 }
221 admitted, in := f.selectAndAdmit()
222 if err := f.owners.Materialize(ctx, admitted, in, newFenceWork()); err != nil {
223 t.Fatalf("the pinned render through the policy: %v", err)
224 }
225 f.order = nil
226 f.renderOverlay = "e-changed"
227 err := f.owners.Materialize(ctx, admitted, in, newFenceWork())
228 var changed *ProbeInputsChanged
229 var refusal *MovesPolicyRefusal
230 if !errors.As(err, &changed) || changed.Reason != provision.RollbackConfigChanged || errors.As(err, &refusal) {
231 t.Fatalf("a changed render: %v", err)
232 }
233 for _, step := range f.order {
234 if step == "select:held-config" || step == "select:held-release" {
235 t.Fatalf("a held work was selected for a changed render: %v", f.order)
236 }
237 }
238}
239
240// Finding 8, ruled order: admit, publish the verified prepared marker under
241// the writer's fence (superseding the earlier stay's marker by If-Match),
242// then the start check, then the spawn. Fix round 2: the earlier stay is
243// really in the admission history, a cold admission on this box and region
244// that does not descend from the move's admission; it is still an earlier
245// stay, superseded, never taken for a restart of this landing.
246func TestMovesProbeOwnersSupersedeTheEarlierStaysMarker(t *testing.T) {
247 f := newPOFixture(t)
248 ctx := t.Context()
249 c := f.poFlipClaim()
250 c.Holder.Admissions = []hostclaim.AdmissionRecord{
251 {Session: 5, Box: "uk-box", Generation: "earlier", Predecessor: "", Region: "uk", Mode: "cold"},
252 {Session: 15, Box: "ie-box", Generation: "src", Predecessor: "earlier", Region: "ie", PredecessorRegion: "uk", Mode: "cold"},
253 }
254 if err := c.Validate(); err != nil {
255 t.Fatal(err)
256 }
257 f.putClaim(c)
258 f.bucket.Put("data", f.markerKey(), mustJSON(t, genselect.Marker{Op: "earlier", Incarnation: f.inc, Host: poHost, Box: "uk-box", InstalledAt: time.Unix(1, 0).UTC(), Pin: genselect.Pin{SnapshotTXID: 3}}))
259 admitted, in := f.selectAndAdmit()
260 if err := f.owners.Materialize(ctx, admitted, in, newFenceWork()); err != nil {
261 t.Fatalf("the first probe over an earlier stay's marker: %v", err)
262 }
263 if m, _ := f.currentMarker(); m.Op != poOp {
264 t.Fatalf("the earlier stay's marker was not superseded: %+v", m)
265 }
266 if err := f.owners.Publish(ctx, admitted, in); err != nil {
267 t.Fatalf("Group D's prepared publication: %v", err)
268 }
269 if err := f.owners.PublicationEvidence(ctx, admitted); err != nil {
270 t.Fatalf("publication evidence: %v", err)
271 }
272}
273
274// Fix round 2: a genuine restart after a crash is still this landing's. The
275// first attempt published the move id; the restarted attempt fresh1
276// published its own marker after its root and crashed. The next attempt's
277// baseline is the certified remote from fresh1, whose chain reaches the move
278// id, and fresh1's marker is kept, not superseded by the prepared one. Since
279// Task 7's fix round 1 fresh1's publication refreshed .files-gen to fresh1,
280// so that attempt passes Task 4's files rule too.
281func TestMovesProbeOwnersRecogniseARestartAfterACrash(t *testing.T) {
282 f := newPOFixture(t)
283 ctx := t.Context()
284 first, in := f.selectAndAdmit()
285 if err := f.owners.Materialize(ctx, first, in, newFenceWork()); err != nil {
286 t.Fatal(err)
287 }
288 again, err := f.owners.Select(ctx, f.route, first, poProbe)
289 if err != nil {
290 t.Fatal(err)
291 }
292 restarted := f.admit(first, again)
293 fresh := again.Admission.NewGeneration
294 f.restore(fresh)
295 if err := f.owners.Materialize(ctx, restarted, again, newFenceWork()); err != nil {
296 t.Fatal(err)
297 }
298 f.bucket.Put("data", "instances/"+poHost+"/g-"+fresh+"/0009/"+ltx.FormatFilename(1, 44), f.ltxHeader(1, 44))
299 pub := ClaimPublication{Sync: func(context.Context) error { return nil }, Check: func(context.Context) error { return nil }}
300 if _, err := f.owners.PublishFenced(ctx, restarted, again, pub); err != nil {
301 t.Fatal(err)
302 }
303 // The crash: the next record attempt admits again over fresh1.
304 next, err := f.owners.Select(ctx, f.route, restarted, poProbe)
305 if err != nil {
306 t.Fatal(err)
307 }
308 if next.Baseline.Generation != fresh || next.Baseline.Marker == nil || next.Baseline.Marker.Op != fresh || next.Baseline.MaxTXID != 44 {
309 t.Fatalf("the restart's baseline is not the certified remote of %s: %+v", fresh, next.Baseline)
310 }
311 admitted := f.admit(restarted, next)
312 f.restore(admitted.Claim.Holder.Generation)
313 written := f.bucket.LatestID("data", f.markerKey())
314 err = f.owners.Materialize(ctx, admitted, next, newFenceWork())
315 if m, _ := f.currentMarker(); m.Op != fresh || f.bucket.LatestID("data", f.markerKey()) != written {
316 t.Fatalf("the restart's marker was superseded: %+v", m)
317 }
318 if err != nil {
319 t.Fatalf("the second restart: %v", err)
320 }
321}
322
323// Fix round 2, the predicate itself: a generation is this landing's only
324// with positive proof, the chain of cold admissions on this box and region
325// back from it reaching the move id.
326func TestMovesProbeOwnersLandingMarkerNeedsDescent(t *testing.T) {
327 f := newPOFixture(t)
328 v := probeLanding{op: poOp, manifest: f.manifest, holder: &hostclaim.Holder{Incarnation: f.inc, Admissions: []hostclaim.AdmissionRecord{
329 {Session: 5, Box: "uk-box", Generation: "earlier", Region: "uk", Mode: "cold"},
330 {Session: 15, Box: "ie-box", Generation: "src", Predecessor: "earlier", Region: "ie", Mode: "cold"},
331 {Session: 20, Box: "uk-box", Generation: poOp, Predecessor: "src", Region: "uk", Mode: "cold", Coverage: "sealed"},
332 {Session: 21, Box: "uk-box", Generation: "f1", Predecessor: poOp, Region: "uk", Mode: "cold"},
333 {Session: 22, Box: "uk-box", Generation: "f2", Predecessor: "f1", Region: "uk", Mode: "cold"},
334 {Session: 23, Box: "uk-box", Generation: "orphan", Predecessor: "gone", Region: "uk", Mode: "cold"},
335 {Session: 24, Box: "other-box", Generation: "f3", Predecessor: "f2", Region: "uk", Mode: "cold"},
336 }}}
337 marker := func(op string) *genselect.Marker {
338 return &genselect.Marker{Op: op, Incarnation: f.inc, Host: poHost, Box: "uk-box"}
339 }
340 for op, want := range map[string]bool{"f1": true, "f2": true, "earlier": false, "src": false, "orphan": false, "f3": false, "unknown": false} {
341 if got := f.owners.landingMarker(marker(op), poHost, v, ""); got != want {
342 t.Errorf("%s: %v, want %v", op, got, want)
343 }
344 }
345}
346
347// Finding 8: a history trimmed so that the admitted generation's ancestry
348// back to the published marker cannot be proved is refused by the start
349// check, after the publication and before any spawn. StartCheck is not
350// weakened.
351func TestMovesProbeOwnersTrimmedHistoryIsRefused(t *testing.T) {
352 f := newPOFixture(t)
353 ctx := t.Context()
354 first, in := f.selectAndAdmit()
355 if err := f.owners.Materialize(ctx, first, in, newFenceWork()); err != nil {
356 t.Fatal(err)
357 }
358 // Two restarted attempts: fresh1 from the move id, fresh2 from fresh1.
359 again, err := f.owners.Select(ctx, f.route, first, poProbe)
360 if err != nil {
361 t.Fatal(err)
362 }
363 second := f.admit(first, again)
364 third, err := f.owners.Select(ctx, f.route, second, poProbe)
365 if err != nil {
366 t.Fatal(err)
367 }
368 admitted := f.admit(second, third)
369 // Trim the history to the last admission only (fresh2 from fresh1).
370 c := *admitted.Claim
371 h := *c.Holder
372 h.Admissions = h.Admissions[len(h.Admissions)-1:]
373 c.Holder = &h
374 f.putClaim(&c)
375 f.writeLocal(poOp, h.Generation, f.inc)
376 trimmed := f.read()
377 before := f.bucket.LatestID("data", f.markerKey())
378 // C8 refuses the unprovable history first (item 1: the establishment
379 // runs before the start check, and a chain it cannot prove is
380 // ErrNoEvidence); behind it the start check stands unweakened.
381 err = f.owners.Materialize(ctx, trimmed, third, newFenceWork())
382 if !errors.Is(err, hostfiles.ErrNoEvidence) {
383 t.Fatalf("a trimmed history through C8: %v", err)
384 }
385 f.owners.Files = noopFiles{}
386 err = f.owners.Materialize(ctx, trimmed, third, newFenceWork())
387 if !errors.Is(err, moves.ErrStartMarker) {
388 t.Fatalf("a trimmed history: %v", err)
389 }
390 if f.bucket.LatestID("data", f.markerKey()) != before {
391 t.Fatal("the verified marker was replaced")
392 }
393}
394