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 activator
4
5import (
6 "context"
7 "errors"
8 "os"
9 "path/filepath"
10 "slices"
11 "sync/atomic"
12 "testing"
13
14 "github.com/carlosframework/platform/internal/instancectl"
15 "github.com/carlosframework/platform/internal/registry"
16)
17
18// Astra's review of zdd-owed-items. P1: the pending record is a bucket
19// write, and the lease, a fence or the attempt can change while it runs, so
20// the exec is decided again after it. P2: a nested seed writes its
21// obligation before its record, so an obligation that fails records
22// nothing.
23
24// P1, the exec gate: a gate that passes before the write and refuses after
25// it (the attempt superseded during the PUT) starts nothing, at both seams
26// that carry a gate.
27func TestAnExecGateRefusingDuringTheRecordStartsNothing(t *testing.T) {
28 for _, seam := range []string{"runExecGate", "launchGate"} {
29 t.Run(seam, func(t *testing.T) {
30 var superseded atomic.Bool
31 superseding := errors.New("attempt superseded")
32 gate := func() error {
33 if superseded.Load() {
34 return superseding
35 }
36 return nil
37 }
38 writes := 0
39 write := func() error { writes++; superseded.Store(true); return nil }
40 ctx := withPendingRecord(withExecGate(context.Background(), gate), "v1", write, nil)
41 var err error
42 if seam == "runExecGate" {
43 err = runExecGate(ctx)
44 } else {
45 err = (&ExecBackend{}).launchGate(ctx, "one.example.com")()
46 }
47 if !errors.Is(err, superseding) {
48 t.Fatalf("%s = %v, want the gate's refusal after the record", seam, err)
49 }
50 if writes != 1 {
51 t.Fatalf("%d writes, want 1", writes)
52 }
53 })
54 }
55}
56
57// P1, the fence: a maintenance fence raised while a cold wake's record is
58// being written starts nothing, at the seed and at the spawn.
59func TestAFenceRaisedDuringTheRecordStartsNothing(t *testing.T) {
60 t.Parallel()
61 for _, seeded := range []bool{false, true} {
62 name := "spawn"
63 if seeded {
64 name = "seed"
65 }
66 t.Run(name, func(t *testing.T) {
67 a, b := identityActivator(t, "v1")
68 b.restoreFresh = seeded
69 rt := identityRoute(t, "one.example.com")
70 if seeded {
71 rt.Seed = `["seed"]`
72 }
73 l := &execLog{}
74 a.BeforeExec = func(ctx context.Context, host, build string) error {
75 a.mu.Lock()
76 if a.fences == nil {
77 a.fences = map[string]uint64{}
78 }
79 a.fences[host] = 7 // a maintenance fence lands mid-write
80 a.mu.Unlock()
81 return l.hook(ctx, host, build)
82 }
83 _, err := a.beginRequest(context.Background(), rt)
84 if !errors.Is(err, ErrFenced) {
85 t.Fatalf("wake = %v, want ErrFenced", err)
86 }
87 if b.startCount() != 0 || b.seedCount() != 0 {
88 t.Fatalf("started %d, seeded %d after a fence raised during the record", b.startCount(), b.seedCount())
89 }
90 if calls, _, _ := l.seen(); !slices.Equal(calls, []string{"one.example.com@v1"}) {
91 t.Fatalf("records %v, want the one write that ran", calls)
92 }
93 })
94 }
95}
96
97// P2: a nested seed whose obligation cannot be written records nothing and
98// asks root for nothing; a nested seed whose record fails discharges the
99// obligation it wrote, keeps the database, and asks root for nothing.
100func TestANestedSeedWritesItsObligationBeforeItsRecord(t *testing.T) {
101 seedOnce := func(t *testing.T, plant func(rt registry.Route), fail bool) (*fakeControl, registry.Route, []string, error) {
102 t.Helper()
103 ctl := &fakeControl{seed: instancectl.SeedResponse{InvocationID: "s1", Result: "success", Status: "0", Code: "exited"}}
104 b, rt := nestedBackend(t, ctl, &fakeUnit{})
105 b.Control = identityControl{ctl}
106 rt.Seed = `["seed"]`
107 stageVersion(t, b, rt.Host, "vA", "exit 0\n")
108 id, err := b.LiveIdentity(context.Background(), rt)
109 if err != nil {
110 t.Fatal(err)
111 }
112 if err := os.MkdirAll(filepath.Dir(rt.DBPath), 0o755); err != nil {
113 t.Fatal(err)
114 }
115 plant(rt)
116 var records []string
117 write := func() error {
118 records = append(records, id.Version)
119 if fail {
120 return errBuildStateUnwritten
121 }
122 return nil
123 }
124 ctx := withPendingRecord(context.Background(), id.Version, write, nil)
125 err = b.SeedExe(ctx, rt, id)
126 return ctl, rt, records, err
127 }
128
129 t.Run("the obligation fails", func(t *testing.T) {
130 ctl, _, recs, err := seedOnce(t, func(rt registry.Route) {
131 // An earlier seed's unanswered obligation refuses this one.
132 if err := os.WriteFile(rt.DBPath+seedUnprovenSuffix, []byte("{}"), 0o600); err != nil {
133 t.Fatal(err)
134 }
135 }, false)
136 if !errors.Is(err, ErrSeedUnproven) {
137 t.Fatalf("SeedExe = %v, want the obligation's refusal", err)
138 }
139 if len(recs) != 0 {
140 t.Fatalf("records %v, want none: no seed ran", recs)
141 }
142 if ctl.joined() != "" {
143 t.Fatalf("root was asked:\n%s", ctl.joined())
144 }
145 })
146
147 t.Run("the record fails", func(t *testing.T) {
148 ctl, rt, recs, err := seedOnce(t, func(rt registry.Route) {
149 if err := os.WriteFile(rt.DBPath, []byte("data"), 0o600); err != nil {
150 t.Fatal(err)
151 }
152 }, true)
153 if !errors.Is(err, errBuildStateUnwritten) {
154 t.Fatalf("SeedExe = %v, want the record's refusal", err)
155 }
156 if len(recs) != 1 {
157 t.Fatalf("records %v, want one attempt", recs)
158 }
159 if _, err := os.Stat(rt.DBPath + seedUnprovenSuffix); !errors.Is(err, os.ErrNotExist) {
160 t.Fatalf("the obligation stayed after a refused record: %v", err)
161 }
162 if raw, err := os.ReadFile(rt.DBPath); err != nil || string(raw) != "data" {
163 t.Fatalf("the database was touched: %q, %v", raw, err)
164 }
165 if ctl.joined() != "" {
166 t.Fatalf("root was asked:\n%s", ctl.joined())
167 }
168 })
169}
170