carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "context"
5 "errors"
6 "math"
7 "sync"
8 "sync/atomic"
9 "testing"
10 "time"
11)
12
13type countedWorkerStore struct {
14 WorkerControlStore
15 puts atomic.Int64
16}
17
18func (s *countedWorkerStore) Put(ctx context.Context, key string, body []byte, etag string) (string, error) {
19 s.puts.Add(1)
20 return s.WorkerControlStore.Put(ctx, key, body, etag)
21}
22
23func TestWorkerAssignmentPublicationAndDisableShareCAS(t *testing.T) {
24 for _, publicationFirst := range []bool{false, true} {
25 t.Run(map[bool]string{false: "disable-first", true: "publication-first"}[publicationFirst], func(t *testing.T) {
26 priv, pub := testKeys(t)
27 sg := RootSigner(priv)
28 store := NewDirStore(t.TempDir())
29 ctx := t.Context()
30 initial := workerTestAssignment()
31 if _, err := CreateWorkerAssignment(ctx, store, sg, initial); err != nil {
32 t.Fatal(err)
33 }
34 publisher, err := ReadWorkerAssignment(ctx, store, pub, initial.Identity)
35 if err != nil {
36 t.Fatal(err)
37 }
38 revoker, err := ReadWorkerAssignment(ctx, store, pub, initial.Identity)
39 if err != nil {
40 t.Fatal(err)
41 }
42 published := initial
43 published.Revision++
44 ref := workerTestRef(7)
45 published.Preflight = &ref
46 disabled := initial
47 disabled.Revision++
48 disabled.Disabled = true
49 first, second := publisher, revoker
50 firstAfter, secondAfter := published, disabled
51 if !publicationFirst {
52 first, second = revoker, publisher
53 firstAfter, secondAfter = disabled, published
54 }
55 if _, err := AdvanceWorkerAssignment(ctx, store, sg, first, firstAfter); err != nil {
56 t.Fatal(err)
57 }
58 if _, err := AdvanceWorkerAssignment(ctx, store, sg, second, secondAfter); !errors.Is(err, ErrCASConflict) {
59 t.Fatalf("stale competing update: %v", err)
60 }
61 latest, err := ReadWorkerAssignment(ctx, store, pub, initial.Identity)
62 if err != nil {
63 t.Fatal(err)
64 }
65 state, err := latest.State(pub)
66 if err != nil {
67 t.Fatal(err)
68 }
69 if publicationFirst {
70 if state.Preflight == nil || *state.Preflight != ref {
71 t.Fatal("accepted reference lost")
72 }
73 state.Disabled = true
74 state.Revision++
75 if _, err := AdvanceWorkerAssignment(ctx, store, sg, latest, state); err != nil {
76 t.Fatal(err)
77 }
78 } else if !state.Disabled || state.Preflight != nil {
79 t.Fatal("publication accepted after disable")
80 }
81 // A different console can continue after the first console stops.
82 recovered, err := ReadWorkerAssignment(ctx, store, pub, initial.Identity)
83 if err != nil {
84 t.Fatal(err)
85 }
86 state, err = recovered.State(pub)
87 if err != nil {
88 t.Fatal(err)
89 }
90 if !state.Disabled || (state.Preflight != nil) != publicationFirst {
91 t.Fatal("revocation did not preserve accepted history")
92 }
93 retry := state
94 retry.Disabled = false
95 retry.Revision++
96 retry.Preflight = &ref
97 if _, err := AdvanceWorkerAssignment(ctx, store, sg, recovered, retry); !errors.Is(err, ErrWorkerControl) {
98 t.Fatal("disabled assignment revived")
99 }
100 })
101 }
102}
103
104func TestWorkerAssignmentConcurrentConsoleUpdatesHaveOneWinner(t *testing.T) {
105 priv, pub := testKeys(t)
106 sg := RootSigner(priv)
107 store := NewDirStore(t.TempDir())
108 ctx := t.Context()
109 initial := workerTestAssignment()
110 if _, err := CreateWorkerAssignment(ctx, store, sg, initial); err != nil {
111 t.Fatal(err)
112 }
113 snapshots := make([]WorkerAssignmentSnapshot, 2)
114 for i := range snapshots {
115 var err error
116 snapshots[i], err = ReadWorkerAssignment(ctx, store, pub, initial.Identity)
117 if err != nil {
118 t.Fatal(err)
119 }
120 }
121 ready := make(chan struct{})
122 results := make(chan error, 2)
123 var wg sync.WaitGroup
124 for i := range snapshots {
125 wg.Add(1)
126 go func(i int) {
127 defer wg.Done()
128 <-ready
129 after := initial
130 after.Revision++
131 ref := workerTestRef(byte(i + 7))
132 after.Preflight = &ref
133 _, err := AdvanceWorkerAssignment(ctx, store, sg, snapshots[i], after)
134 results <- err
135 }(i)
136 }
137 close(ready)
138 wg.Wait()
139 close(results)
140 wins, conflicts := 0, 0
141 for err := range results {
142 if err == nil {
143 wins++
144 } else if errors.Is(err, ErrCASConflict) {
145 conflicts++
146 } else {
147 t.Fatal(err)
148 }
149 }
150 if wins != 1 || conflicts != 1 {
151 t.Fatalf("wins=%d conflicts=%d", wins, conflicts)
152 }
153}
154
155func TestWorkerAssignmentPreservesAcceptedExecutionOnRevocation(t *testing.T) {
156 priv, pub := testKeys(t)
157 sg := RootSigner(priv)
158 store := &countedWorkerStore{WorkerControlStore: NewDirStore(t.TempDir())}
159 ctx := t.Context()
160 state := workerTestAssignment()
161 snapshot, err := CreateWorkerAssignment(ctx, store, sg, state)
162 if err != nil {
163 t.Fatal(err)
164 }
165 for step := 0; step < 4; step++ {
166 ref := workerTestRef(byte(step + 10))
167 switch step {
168 case 0:
169 state.Preflight = &ref
170 case 1:
171 state.Compatibility = &ref
172 case 2:
173 state.Execution = &ref
174 case 3:
175 state.Resume = &ref
176 }
177 state.Revision++
178 snapshot, err = AdvanceWorkerAssignment(ctx, store, sg, snapshot, state)
179 if err != nil {
180 t.Fatal(err)
181 }
182 }
183 state.Disabled = true
184 state.Revision++
185 snapshot, err = AdvanceWorkerAssignment(ctx, store, sg, snapshot, state)
186 if err != nil {
187 t.Fatal(err)
188 }
189 got, err := snapshot.State(pub)
190 if err != nil {
191 t.Fatal(err)
192 }
193 if !sameWorkerReference(got.Execution, state.Execution) || !sameWorkerReference(got.Resume, state.Resume) {
194 t.Fatal("accepted outcome lost during revocation")
195 }
196 // Accessors return independent decoded values, not mutable snapshot state.
197 got.Execution.ID = workerTestRef(20).ID
198 again, err := snapshot.State(pub)
199 if err != nil || *again.Execution != *state.Execution {
200 t.Fatal("caller mutated retained signed snapshot")
201 }
202 puts := store.puts.Load()
203 if _, err := AdvanceWorkerAssignment(ctx, store, sg, snapshot, state); !errors.Is(err, ErrWorkerControl) {
204 t.Fatal("identical retry rewrote state")
205 }
206 if store.puts.Load() != puts {
207 t.Fatal("no-op retry reached storage")
208 }
209 if _, err := CreateWorkerAssignment(ctx, store, sg, workerTestAssignment()); !errors.Is(err, ErrCASConflict) {
210 t.Fatal("create retry reset accepted state")
211 }
212}
213
214func TestWorkerAssignmentInvalidTransitionNeverWrites(t *testing.T) {
215 priv, pub := testKeys(t)
216 sg := RootSigner(priv)
217 ctx := t.Context()
218 for name, change := range map[string]func(*WorkerAssignmentState){
219 "same-revision": func(s *WorkerAssignmentState) { s.Revision-- },
220 "skip-revision": func(s *WorkerAssignmentState) { s.Revision++ },
221 "identity": func(s *WorkerAssignmentState) { s.Identity.Box = "other-box" },
222 "expiry": func(s *WorkerAssignmentState) { s.Expires++ },
223 "replace-preflight": func(s *WorkerAssignmentState) { r := workerTestRef(20); s.Preflight = &r },
224 "remove-preflight": func(s *WorkerAssignmentState) { s.Preflight = nil },
225 "two-new-references": func(s *WorkerAssignmentState) { r := workerTestRef(20); s.Compatibility = &r },
226 "publish-and-disable": func(s *WorkerAssignmentState) { s.Disabled = true },
227 "resume-before-execution": func(s *WorkerAssignmentState) { s.Resume = s.Execution; s.Execution = nil },
228 } {
229 t.Run(name, func(t *testing.T) {
230 store := &countedWorkerStore{WorkerControlStore: NewDirStore(t.TempDir())}
231 state := workerTestAssignment()
232 snapshot, err := CreateWorkerAssignment(ctx, store, sg, state)
233 if err != nil {
234 t.Fatal(err)
235 }
236 preflight := workerTestRef(10)
237 state.Preflight = &preflight
238 state.Revision++
239 snapshot, err = AdvanceWorkerAssignment(ctx, store, sg, snapshot, state)
240 if err != nil {
241 t.Fatal(err)
242 }
243 after := state
244 after.Revision++
245 execution := workerTestRef(11)
246 after.Execution = &execution
247 change(&after)
248 puts := store.puts.Load()
249 if _, err := AdvanceWorkerAssignment(ctx, store, sg, snapshot, after); !errors.Is(err, ErrWorkerControl) {
250 t.Fatal("invalid transition accepted")
251 }
252 if store.puts.Load() != puts {
253 t.Fatal("invalid transition reached storage")
254 }
255 loaded, err := ReadWorkerAssignment(ctx, store, pub, state.Identity)
256 if err != nil {
257 t.Fatal(err)
258 }
259 got, err := loaded.State(pub)
260 if err != nil || got.Revision != state.Revision || got.Execution != nil {
261 t.Fatal("invalid transition changed state")
262 }
263 })
264 }
265 state := workerTestAssignment()
266 state.Revision = math.MaxInt64
267 after := state
268 after.Revision = math.MinInt64
269 after.Disabled = true
270 if validWorkerAssignmentAdvance(state, after) {
271 t.Fatal("revision overflow accepted")
272 }
273}
274
275func TestWorkerAssignmentWrongScopeAndSignerRefuse(t *testing.T) {
276 priv, pub := testKeys(t)
277 sg := RootSigner(priv)
278 ctx := t.Context()
279 store := &countedWorkerStore{WorkerControlStore: NewDirStore(t.TempDir())}
280 state := workerTestAssignment()
281 snapshot, err := CreateWorkerAssignment(ctx, store, sg, state)
282 if err != nil {
283 t.Fatal(err)
284 }
285 wrong := state.Identity
286 wrong.Host = "other.example.com"
287 if _, err := ReadWorkerAssignment(ctx, store, pub, wrong); !errors.Is(err, ErrWorkerControl) {
288 t.Fatal("wrong scope accepted")
289 }
290 otherPriv, _ := testKeys(t)
291 _, _, scoped := scopedFixture(t, []string{"canary"}, time.Hour)
292 after := state
293 after.Revision++
294 after.Disabled = true
295 puts := store.puts.Load()
296 for _, signer := range []Signer{{}, RootSigner(otherPriv), scoped} {
297 if _, err := AdvanceWorkerAssignment(ctx, store, signer, snapshot, after); !errors.Is(err, ErrWorkerControl) {
298 t.Fatal("wrong signer accepted")
299 }
300 }
301 if _, err := AdvanceWorkerAssignment(ctx, store, sg, WorkerAssignmentSnapshot{}, after); !errors.Is(err, ErrWorkerControl) {
302 t.Fatal("constructed empty snapshot accepted")
303 }
304 if store.puts.Load() != puts {
305 t.Fatal("unauthorized transition wrote storage")
306 }
307}
308
309func TestWorkerAssignmentCancelledOperationDoesNotWrite(t *testing.T) {
310 priv, pub := testKeys(t)
311 sg := RootSigner(priv)
312 store := &countedWorkerStore{WorkerControlStore: NewDirStore(t.TempDir())}
313 state := workerTestAssignment()
314 snapshot, err := CreateWorkerAssignment(t.Context(), store, sg, state)
315 if err != nil {
316 t.Fatal(err)
317 }
318 ctx, cancel := context.WithCancel(t.Context())
319 cancel()
320 after := state
321 after.Revision++
322 after.Disabled = true
323 puts := store.puts.Load()
324 if _, err := AdvanceWorkerAssignment(ctx, store, sg, snapshot, after); !errors.Is(err, ErrWorkerControl) {
325 t.Fatal("cancelled write accepted")
326 }
327 if _, err := CreateWorkerAssignment(ctx, store, sg, state); !errors.Is(err, ErrWorkerControl) {
328 t.Fatal("cancelled create accepted")
329 }
330 if _, err := ReadWorkerAssignment(ctx, store, pub, state.Identity); !errors.Is(err, ErrWorkerControl) {
331 t.Fatal("cancelled read accepted")
332 }
333 if store.puts.Load() != puts {
334 t.Fatal("cancelled operation reached storage")
335 }
336}
337