carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package provision
2
3import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "errors"
8 "testing"
9
10 "github.com/carlosframework/platform/internal/release"
11)
12
13func TestPlan9B8BoxRecordSequenceAndLegacyBytes(t *testing.T) {
14 s := release.NewDirStore(t.TempDir())
15 ctx := context.Background()
16 r := acmeRecord("bdm")
17 if err := CreateRecord(ctx, s, r); err != nil {
18 t.Fatal(err)
19 }
20 before, tag, err := s.Get(ctx, RecordKey(r.Account, r.App, r.Host))
21 if err != nil {
22 t.Fatal(err)
23 }
24 if err := SetRecordRegion(ctx, s, r.Account, r.App, r.Host, r.Region); err != nil {
25 t.Fatal(err)
26 }
27 unchanged, same, err := s.Get(ctx, RecordKey(r.Account, r.App, r.Host))
28 if err != nil || string(before) != string(unchanged) || tag != same {
29 t.Fatalf("legacy bytes changed: %v", err)
30 }
31 for want := uint64(1); want <= 2; want++ {
32 got, err := SetRecordMove(WithRecordSequence(ctx), s, r.Account, r.App, r.Host, func(*Record) error { return nil })
33 if err != nil || got.RecordSeq != want {
34 t.Fatalf("sequence: %+v %v", got, err)
35 }
36 }
37 if _, err := SetRecordMove(WithRecordSequence(ctx), s, r.Account, r.App, r.Host, func(r *Record) error { r.RecordSeq = 0; return nil }); !errors.Is(err, ErrMaintenanceProjection) {
38 t.Fatalf("reset: %v", err)
39 }
40 current, etag, err := GetRecordETag(ctx, s, r.Account, r.App, r.Host)
41 if err != nil {
42 t.Fatal(err)
43 }
44 if _, err := s.Put(ctx, RecordKey(r.Account, r.App, r.Host), before, tag); !errors.Is(err, release.ErrCASConflict) {
45 t.Fatalf("old Put: %v", err)
46 }
47 current.RecordSeq = 0
48 if err := UpdateRecord(ctx, s, *current, etag); !errors.Is(err, ErrMaintenanceProjection) {
49 t.Fatalf("whole record removed sequence: %v", err)
50 }
51}
52
53func TestPlan9B8PopulatedSequenceAdvancesWithoutClaimContext(t *testing.T) {
54 s := release.NewDirStore(t.TempDir())
55 ctx := context.Background()
56 rec := acmeRecord("bdm")
57 if err := CreateRecord(WithRecordSequence(ctx), s, rec); err != nil {
58 t.Fatal(err)
59 }
60 // Every box transition, including future commit/open/close callbacks, uses
61 // SetRecordMove on the raw store. A populated record needs no flag context.
62 for want := uint64(2); want <= 5; want++ {
63 got, err := SetRecordMove(ctx, s, rec.Account, rec.App, rec.Host, func(*Record) error { return nil })
64 if err != nil || got.RecordSeq != want {
65 t.Fatalf("box: %+v %v", got, err)
66 }
67 }
68 got, tag, err := GetRecordETag(ctx, s, rec.Account, rec.App, rec.Host)
69 if err != nil {
70 t.Fatal(err)
71 }
72 got.Channel = "new-channel"
73 if err := UpdateRecord(ctx, s, *got, tag); err != nil {
74 t.Fatal(err)
75 }
76 got, err = GetRecord(ctx, s, rec.Account, rec.App, rec.Host)
77 if err != nil || got.RecordSeq != 6 {
78 t.Fatalf("whole record: %+v %v", got, err)
79 }
80 if err := SetRecordRegion(ctx, s, rec.Account, rec.App, rec.Host, rec.Region); err != nil {
81 t.Fatal(err)
82 }
83 got, err = GetRecord(ctx, s, rec.Account, rec.App, rec.Host)
84 if err != nil || got.RecordSeq != 7 {
85 t.Fatalf("placement: %+v %v", got, err)
86 }
87}
88
89func TestPlan9B8CreateBytesAndSidecarSequence(t *testing.T) {
90 for _, claim := range []bool{false, true} {
91 ctx := context.Background()
92 if claim {
93 ctx = WithRecordSequence(ctx)
94 }
95 s := release.NewDirStore(t.TempDir())
96 rec := acmeRecord("bdm")
97 side := SidecarRecord{Schema: 1, Account: rec.Account, App: rec.App, Name: "worker", Host: SidecarHost("worker", rec.App, rec.Account), CreatedAt: rec.CreatedAt}
98 if err := CreateRecord(ctx, s, rec); err != nil {
99 t.Fatal(err)
100 }
101 if err := CreateSidecarRecord(ctx, s, side); err != nil {
102 t.Fatal(err)
103 }
104 for _, tc := range []struct {
105 key string
106 value any
107 }{{RecordKey(rec.Account, rec.App, rec.Host), rec}, {SidecarRecordKey(side.Account, side.App, side.Name), side}} {
108 raw, _, err := s.Get(ctx, tc.key)
109 if err != nil {
110 t.Fatal(err)
111 }
112 _, seq, err := RecordSequence(raw)
113 if err != nil {
114 t.Fatal(err)
115 }
116 want := uint64(0)
117 if claim {
118 want = 1
119 }
120 if seq != want {
121 t.Fatalf("%s sequence=%d", tc.key, seq)
122 }
123 if !claim {
124 expected, err := json.Marshal(tc.value)
125 if err != nil || !bytes.Equal(raw, expected) || bytes.Contains(raw, []byte("record_seq")) {
126 t.Fatalf("legacy bytes %s: %s", tc.key, raw)
127 }
128 }
129 }
130 }
131}
132
133type plan9SequenceRetry struct {
134 release.Store
135 key string
136 raced bool
137}
138
139func (s *plan9SequenceRetry) Put(ctx context.Context, key string, body []byte, tag string) (string, error) {
140 if key == s.key && !s.raced {
141 s.raced = true
142 // A different box wins the first CAS. Its write advances the same counter.
143 raw, current, err := s.Store.Get(ctx, key)
144 if err != nil {
145 return "", err
146 }
147 if _, _, err = WriteRecord(context.Background(), s.Store, key, raw, raw, current); err != nil {
148 return "", err
149 }
150 }
151 return s.Store.Put(ctx, key, body, tag)
152}
153
154func TestPlan9B8BoxCASRetryRereadsSequence(t *testing.T) {
155 ctx := context.Background()
156 base := release.NewDirStore(t.TempDir())
157 rec := acmeRecord("bdm")
158 if err := CreateRecord(WithRecordSequence(ctx), base, rec); err != nil {
159 t.Fatal(err)
160 }
161 s := &plan9SequenceRetry{Store: base, key: RecordKey(rec.Account, rec.App, rec.Host)}
162 got, err := SetRecordMove(ctx, s, rec.Account, rec.App, rec.Host, func(*Record) error { return nil })
163 if err != nil || !s.raced || got.RecordSeq != 3 {
164 t.Fatalf("retry: %+v %v", got, err)
165 }
166}
167
168func TestPlan9B8WholeRecordUsesRawSequenceBase(t *testing.T) {
169 for _, field := range []string{`"record_seq":0`, `"Record_Seq":0`, `"record_seq":0,"record_seq":0`, `"record_seq":null`} {
170 t.Run(field, func(t *testing.T) {
171 ctx := context.Background()
172 s := release.NewDirStore(t.TempDir())
173 r := acmeRecord("bdm")
174 body, _ := json.Marshal(r)
175 body = append(append([]byte(nil), body[:len(body)-1]...), []byte(","+field+"}")...)
176 key := RecordKey(r.Account, r.App, r.Host)
177 tag, err := s.Put(ctx, key, body, "")
178 if err != nil {
179 t.Fatal(err)
180 }
181 err = UpdateRecord(ctx, s, r, tag)
182 if field == `"record_seq":0` {
183 got, getErr := GetRecord(ctx, s, r.Account, r.App, r.Host)
184 if err != nil || getErr != nil || got.RecordSeq != 1 {
185 t.Fatalf("present zero: %+v %v %v", got, err, getErr)
186 }
187 } else {
188 if !errors.Is(err, ErrRecordSequence) {
189 t.Fatalf("malformed base: %v", err)
190 }
191 got, _, getErr := s.Get(ctx, key)
192 if getErr != nil || !bytes.Equal(got, body) {
193 t.Fatalf("refusal wrote: %s %v", got, getErr)
194 }
195 }
196 })
197 }
198}
199
200func TestPlan9B8BoxPopulatedCounterNeedsNoContext(t *testing.T) {
201 ctx := context.Background()
202 store := release.NewDirStore(t.TempDir())
203 r := acmeRecord("bdm")
204 if err := CreateRecord(ctx, store, r); err != nil {
205 t.Fatal(err)
206 }
207 first, err := SetRecordMove(WithRecordSequence(ctx), store, r.Account, r.App, r.Host, func(*Record) error { return nil })
208 if err != nil || first.RecordSeq != 1 {
209 t.Fatalf("seed: %+v %v", first, err)
210 }
211 got, err := SetRecordMove(ctx, store, r.Account, r.App, r.Host, func(*Record) error { return nil })
212 if err != nil || got.RecordSeq != 2 {
213 t.Fatalf("unflagged populated write: %+v %v", got, err)
214 }
215}
216