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 "crypto/ed25519"
6 "errors"
7 "os"
8 "testing"
9)
10
11// storeCall is one call a request made through hookStore.
12type storeCall struct {
13 seq int
14 op, key string
15 ifMatch string // put only
16}
17
18// hookStore runs before(call) ahead of every call it forwards, and after()
19// once a get returns, so a test can make another actor's writes land at an
20// exact point inside a request. The other actor writes to inner directly:
21// the same handle, which is where G2 holds for a directory store (spec
22// §7.2). seq counts only the calls made through hookStore.
23type hookStore struct {
24 inner Store
25 before func(c storeCall)
26 after func(c storeCall, body []byte, etag string, err error)
27 seq int
28}
29
30func (h *hookStore) call(op, key, ifMatch string) storeCall {
31 h.seq++
32 c := storeCall{seq: h.seq, op: op, key: key, ifMatch: ifMatch}
33 if h.before != nil {
34 h.before(c)
35 }
36 return c
37}
38
39func (h *hookStore) Get(ctx context.Context, key string) ([]byte, string, error) {
40 c := h.call("get", key, "")
41 body, etag, err := h.inner.Get(ctx, key)
42 if h.after != nil {
43 h.after(c, body, etag, err)
44 }
45 return body, etag, err
46}
47
48func (h *hookStore) Put(ctx context.Context, key string, body []byte, ifMatch string) (string, error) {
49 h.call("put", key, ifMatch)
50 return h.inner.Put(ctx, key, body, ifMatch)
51}
52
53func (h *hookStore) List(ctx context.Context, prefix string) ([]string, error) {
54 h.call("list", prefix, "")
55 return h.inner.List(ctx, prefix)
56}
57
58func (h *hookStore) ListDirs(ctx context.Context, prefix string) ([]string, error) {
59 h.call("listdirs", prefix, "")
60 return h.inner.ListDirs(ctx, prefix)
61}
62
63func (h *hookStore) Delete(ctx context.Context, key string) error {
64 h.call("delete", key, "")
65 return h.inner.Delete(ctx, key)
66}
67
68// onNth runs fn once, just before the nth (1-based) call matching op and key.
69func onNth(op, key string, n int, fn func()) func(storeCall) {
70 seen := 0
71 return func(c storeCall) {
72 if c.op == op && c.key == key {
73 seen++
74 if seen == n {
75 fn()
76 }
77 }
78 }
79}
80
81// noPipelineOnly is R2's rule for a non-canary channel: refuse if the app
82// has a pipeline.
83func noPipelineOnly(p *Pipeline) error {
84 if p != nil {
85 return errors.New("pipelined")
86 }
87 return nil
88}
89
90var edgeKey = ChannelKey("bab", "myapp", "edge")
91
92func setupEdge(t *testing.T) (context.Context, Store, ed25519.PrivateKey, ed25519.PublicKey) {
93 t.Helper()
94 ctx := context.Background()
95 inner := NewDirStore(t.TempDir())
96 priv, pub := testKeys(t)
97 for _, v := range []string{"v1", "v2", "v3", "v4"} {
98 shipRelease(t, inner, "bab", "myapp", v, "linux-arm64", []byte("bin "+v))
99 }
100 return ctx, inner, priv, pub
101}
102
103func installPipeline(t *testing.T, ctx context.Context, s Store, priv ed25519.PrivateKey, names ...string) {
104 t.Helper()
105 var chans []PipelineChannel
106 for _, n := range names {
107 chans = append(chans, PipelineChannel{Name: n})
108 }
109 if _, err := SetPipeline(ctx, s, priv, "bab", "myapp", chans, "owner"); err != nil {
110 t.Fatalf("SetPipeline(%v): %v", names, err)
111 }
112}
113
114func ownerPromote(t *testing.T, ctx context.Context, s Store, priv ed25519.PrivateKey, v string) {
115 t.Helper()
116 if _, err := PromoteOnLadder(ctx, s, priv, "bab", "myapp", "edge", v, PromoteOptions{Hotfix: true}); err != nil {
117 t.Fatalf("owner promote %s: %v", v, err)
118 }
119}
120
121func edgeVersion(t *testing.T, ctx context.Context, s Store, pub ed25519.PublicKey) string {
122 t.Helper()
123 p, err := Channel(ctx, s, pub, "bab", "myapp", "edge")
124 if errors.Is(err, os.ErrNotExist) {
125 return ""
126 }
127 if err != nil {
128 t.Fatal(err)
129 }
130 return p.Version
131}
132
133func wantAuthorizationError(t *testing.T, err error) {
134 t.Helper()
135 var ae *AuthorizationError
136 if !errors.As(err, &ae) {
137 t.Fatalf("err = %v; want *AuthorizationError", err)
138 }
139}
140
141func TestAuthorizeSeesAPipelineInstalledBeforeTheRequest(t *testing.T) {
142 ctx, inner, priv, pub := setupEdge(t)
143 installPipeline(t, ctx, inner, priv, "edge", "stable")
144 _, err := RootSigner(priv).PromoteOnLadder(ctx, inner, "bab", "myapp", "edge", "v1",
145 PromoteOptions{Authorize: noPipelineOnly})
146 wantAuthorizationError(t, err)
147 if got := edgeVersion(t, ctx, inner, pub); got != "" {
148 t.Fatalf("edge = %q after a refusal", got)
149 }
150}
151
152// The refusal is the hook's, not the ladder's order error, even when the
153// pipeline would make the ladder refuse too (spec §7.2 point 1, codex R3-3).
154func TestAuthorizeRefusesBeforeTheLadderDoes(t *testing.T) {
155 ctx, inner, priv, _ := setupEdge(t)
156 h := &hookStore{inner: inner}
157 h.before = onNth("get", PipelineKey("bab", "myapp"), 1, func() {
158 installPipeline(t, ctx, inner, priv, "preview")
159 })
160 _, err := RootSigner(priv).PromoteOnLadder(ctx, h, "bab", "myapp", "edge", "v1",
161 PromoteOptions{Authorize: noPipelineOnly})
162 wantAuthorizationError(t, err)
163}
164
165// The ladder's read saw no pipeline; one installed before the authoritative
166// read (after the pointer read) is still seen.
167func TestAuthorizeSeesAPipelineInstalledBeforeTheAuthoritativeRead(t *testing.T) {
168 ctx, inner, priv, pub := setupEdge(t)
169 h := &hookStore{inner: inner}
170 h.before = onNth("get", PipelineKey("bab", "myapp"), 2, func() {
171 installPipeline(t, ctx, inner, priv, "edge", "stable")
172 })
173 _, err := RootSigner(priv).PromoteOnLadder(ctx, h, "bab", "myapp", "edge", "v1",
174 PromoteOptions{Authorize: noPipelineOnly})
175 wantAuthorizationError(t, err)
176 if got := edgeVersion(t, ctx, inner, pub); got != "" {
177 t.Fatalf("edge = %q after a refusal", got)
178 }
179 // Astra I1 #1: the authoritative callback refused, so nothing may vouch
180 // for v1 either.
181 if _, err := ReadPromotionRecord(ctx, inner, pub, "bab", "myapp", "v1"); !errors.Is(err, ErrNoPromotionRecord) {
182 t.Fatalf("a refused promotion left a record: %v", err)
183 }
184}
185
186// Astra's round 2 counterexample: an owner promotes under a new pipeline
187// after the deploy request read the pointer. The deploy write must lose.
188func TestOwnerPromoteAfterInstallDefeatsTheDeployWrite(t *testing.T) {
189 ctx, inner, priv, pub := setupEdge(t)
190 ownerPromote(t, ctx, inner, priv, "v1")
191 h := &hookStore{inner: inner}
192 h.before = onNth("put", edgeKey, 1, func() {
193 installPipeline(t, ctx, inner, priv, "edge", "stable")
194 ownerPromote(t, ctx, inner, priv, "v3")
195 })
196 _, err := RootSigner(priv).PromoteOnLadder(ctx, h, "bab", "myapp", "edge", "v2",
197 PromoteOptions{Authorize: noPipelineOnly})
198 if !errors.Is(err, ErrCASConflict) {
199 t.Fatalf("err = %v; want ErrCASConflict", err)
200 }
201 if got := edgeVersion(t, ctx, inner, pub); got != "v3" {
202 t.Fatalf("edge = %q; want the owner's v3", got)
203 }
204}
205
206// R5, accepted: a pipeline installed after the authorization read, and the
207// pointer written and restored to identical bytes, before the write. G2
208// holds (the bytes are what was read), so the write lands.
209func TestRestoredBytesLandAsTheAcceptedInFlightCase(t *testing.T) {
210 ctx, inner, priv, pub := setupEdge(t)
211 ownerPromote(t, ctx, inner, priv, "v1")
212 ownerPromote(t, ctx, inner, priv, "v2")
213 h := &hookStore{inner: inner}
214 h.before = onNth("put", edgeKey, 1, func() {
215 orig, _, err := inner.Get(ctx, edgeKey)
216 if err != nil {
217 t.Fatal(err)
218 }
219 installPipeline(t, ctx, inner, priv, "edge", "stable")
220 ownerPromote(t, ctx, inner, priv, "v4")
221 _, cur, err := inner.Get(ctx, edgeKey)
222 if err != nil {
223 t.Fatal(err)
224 }
225 if _, err := inner.Put(ctx, edgeKey, orig, cur); err != nil {
226 t.Fatal(err)
227 }
228 })
229 if _, err := RootSigner(priv).PromoteOnLadder(ctx, h, "bab", "myapp", "edge", "v3",
230 PromoteOptions{Authorize: noPipelineOnly}); err != nil {
231 t.Fatalf("err = %v; R5 accepts this write (spec §16)", err)
232 }
233 if got := edgeVersion(t, ctx, inner, pub); got != "v3" {
234 t.Fatalf("edge = %q; want v3", got)
235 }
236}
237
238// R5, accepted: a first promote onto an empty channel, with a pipeline
239// installed after the authorization read.
240func TestFirstPromoteOntoEmptyChannelLandsAsTheAcceptedInFlightCase(t *testing.T) {
241 ctx, inner, priv, pub := setupEdge(t)
242 h := &hookStore{inner: inner}
243 h.before = onNth("put", edgeKey, 1, func() {
244 installPipeline(t, ctx, inner, priv, "edge", "stable")
245 })
246 if _, err := RootSigner(priv).PromoteOnLadder(ctx, h, "bab", "myapp", "edge", "v1",
247 PromoteOptions{Authorize: noPipelineOnly}); err != nil {
248 t.Fatalf("err = %v; R5 accepts this write (spec §16)", err)
249 }
250 if got := edgeVersion(t, ctx, inner, pub); got != "v1" {
251 t.Fatalf("edge = %q; want v1", got)
252 }
253}
254
255func TestRepeatPromoteRunsTheHook(t *testing.T) {
256 ctx, inner, priv, _ := setupEdge(t)
257 ownerPromote(t, ctx, inner, priv, "v1")
258 installPipeline(t, ctx, inner, priv, "edge", "stable")
259 res, err := RootSigner(priv).PromoteOnLadder(ctx, inner, "bab", "myapp", "edge", "v1",
260 PromoteOptions{Authorize: noPipelineOnly})
261 wantAuthorizationError(t, err)
262 if res.NoOp {
263 t.Fatal("a refused repeat reported NoOp")
264 }
265}
266
267func TestRepeatPromoteWithNoPipelineIsANoOp(t *testing.T) {
268 ctx, inner, priv, _ := setupEdge(t)
269 ownerPromote(t, ctx, inner, priv, "v1")
270 var seen []bool
271 hook := func(p *Pipeline) error { seen = append(seen, p != nil); return noPipelineOnly(p) }
272 res, err := RootSigner(priv).PromoteOnLadder(ctx, inner, "bab", "myapp", "edge", "v1",
273 PromoteOptions{Authorize: hook})
274 if err != nil || !res.NoOp {
275 t.Fatalf("= %+v, %v; want a no-op", res, err)
276 }
277 if len(seen) != 1 || seen[0] {
278 t.Fatalf("hook calls = %v; want one, with no pipeline", seen)
279 }
280}
281
282// A caller with no hook makes no extra pipeline reads (spec §7.2).
283func TestNoHookNoExtraReads(t *testing.T) {
284 ctx, inner, priv, _ := setupEdge(t)
285 count := func(run func(Store) error) int {
286 n := 0
287 h := &hookStore{inner: inner, before: func(c storeCall) {
288 if c.op == "get" && c.key == PipelineKey("bab", "myapp") {
289 n++
290 }
291 }}
292 if err := run(h); err != nil {
293 t.Fatal(err)
294 }
295 return n
296 }
297 fresh := count(func(s Store) error {
298 _, err := RootSigner(priv).PromoteOnLadder(ctx, s, "bab", "myapp", "edge", "v1", PromoteOptions{})
299 return err
300 })
301 repeat := count(func(s Store) error {
302 _, err := RootSigner(priv).PromoteOnLadder(ctx, s, "bab", "myapp", "edge", "v1", PromoteOptions{})
303 return err
304 })
305 ownerPromote(t, ctx, inner, priv, "v2")
306 rollback := count(func(s Store) error { return RootSigner(priv).Rollback(ctx, s, "bab", "myapp", "edge") })
307 if fresh != 1 || repeat != 0 || rollback != 0 {
308 t.Fatalf("pipeline reads: fresh %d, repeat %d, rollback %d; want 1, 0, 0", fresh, repeat, rollback)
309 }
310}
311
312func TestRollbackAuthorized(t *testing.T) {
313 ctx, inner, priv, pub := setupEdge(t)
314 ownerPromote(t, ctx, inner, priv, "v1")
315 ownerPromote(t, ctx, inner, priv, "v2")
316 installPipeline(t, ctx, inner, priv, "edge", "stable")
317 err := RootSigner(priv).RollbackAuthorized(ctx, inner, "bab", "myapp", "edge", noPipelineOnly)
318 wantAuthorizationError(t, err)
319 if got := edgeVersion(t, ctx, inner, pub); got != "v2" {
320 t.Fatalf("edge = %q after a refused rollback", got)
321 }
322 if err := inner.Delete(ctx, PipelineKey("bab", "myapp")); err != nil {
323 t.Fatal(err)
324 }
325 h := &hookStore{inner: inner}
326 h.before = onNth("put", edgeKey, 1, func() { ownerPromote(t, ctx, inner, priv, "v3") })
327 err = RootSigner(priv).RollbackAuthorized(ctx, h, "bab", "myapp", "edge", noPipelineOnly)
328 if !errors.Is(err, ErrCASConflict) {
329 t.Fatalf("err = %v; want ErrCASConflict", err)
330 }
331 if got := edgeVersion(t, ctx, inner, pub); got != "v3" {
332 t.Fatalf("edge = %q; want the owner's v3", got)
333 }
334}
335