carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package lease
2
3import (
4 "bytes"
5 "context"
6 "errors"
7 "io"
8 "log/slog"
9 "path/filepath"
10 "testing"
11 "time"
12
13 "github.com/benbjohnson/litestream"
14 lss3 "github.com/benbjohnson/litestream/s3"
15 "github.com/superfly/ltx"
16)
17
18type generationFake struct {
19 calls int
20 ctx context.Context
21 files []*ltx.FileInfo
22}
23
24func (f *generationFake) Type() string { return "fake" }
25func (f *generationFake) SetLogger(*slog.Logger) {}
26func (f *generationFake) Init(ctx context.Context) error { f.calls++; f.ctx = ctx; return nil }
27func (f *generationFake) LTXFiles(ctx context.Context, _ int, _ ltx.TXID, _ bool) (ltx.FileIterator, error) {
28 f.calls++
29 f.ctx = ctx
30 return ltx.NewFileInfoSliceIterator(f.files), nil
31}
32func (f *generationFake) OpenLTXFile(ctx context.Context, _ int, _ ltx.TXID, _ ltx.TXID, _ int64, _ int64) (io.ReadCloser, error) {
33 f.calls++
34 f.ctx = ctx
35 return io.NopCloser(bytes.NewReader(nil)), nil
36}
37func (f *generationFake) WriteLTXFile(ctx context.Context, _ int, min, max ltx.TXID, _ io.Reader) (*ltx.FileInfo, error) {
38 f.calls++
39 f.ctx = ctx
40 return &ltx.FileInfo{MinTXID: min, MaxTXID: max}, nil
41}
42func (f *generationFake) DeleteLTXFiles(ctx context.Context, _ []*ltx.FileInfo) error {
43 f.calls++
44 f.ctx = ctx
45 return nil
46}
47func (f *generationFake) DeleteAll(context.Context) error { f.calls++; return nil }
48
49func TestBindGenerationIsImmutableAndCanonical(t *testing.T) {
50 leaser := lss3.NewLeaser()
51 leaser.Path = "instances/shop.example/inc"
52 f := &generationFake{}
53 h := &Held{leaser: leaser, replicaClient: f}
54 check := func(context.Context) error { return nil }
55 if err := h.BindGeneration("instances/shop.example/inc/g-generation1", check); err != nil {
56 t.Fatal(err)
57 }
58 if err := h.BindGeneration("instances/shop.example/inc/g-generation2", check); err != ErrGenerationBinding {
59 t.Fatalf("rebind=%v", err)
60 }
61 for _, bad := range []string{"instances/shop.example/inc/g-a/child", "instances/shop.example/inc/x/../g-a", "instances/shop.example/other/g-a", "instances/shop.example/inc/g-"} {
62 h2 := &Held{leaser: leaser, replicaClient: &generationFake{}}
63 if err := h2.BindGeneration(bad, check); err != ErrGenerationBinding {
64 t.Errorf("BindGeneration(%q)=%v", bad, err)
65 }
66 }
67}
68
69func TestGenerationClientValidatesBeforeStorage(t *testing.T) {
70 f := &generationFake{}
71 c := &generationClient{client: f, check: func(context.Context) error { return nil }}
72 if _, err := c.WriteLTXFile(t.Context(), -1, 1, 1, bytes.NewReader(nil)); err != ErrGenerationBinding {
73 t.Fatal(err)
74 }
75 if _, err := c.OpenLTXFile(t.Context(), 0, 2, 1, 0, 0); err != ErrGenerationBinding {
76 t.Fatal(err)
77 }
78 if err := c.DeleteLTXFiles(t.Context(), []*ltx.FileInfo{nil}); err != ErrGenerationBinding {
79 t.Fatal(err)
80 }
81 if err := c.DeleteAll(t.Context()); err == nil {
82 t.Fatal("DeleteAll accepted")
83 }
84 if f.calls != 0 {
85 t.Fatalf("invalid requests reached storage: %d", f.calls)
86 }
87}
88
89func TestGenerationClientBoundsAndRetainsStreamContext(t *testing.T) {
90 checks := 0
91 f := &generationFake{}
92 c := &generationClient{client: f, check: func(context.Context) error { checks++; return nil }}
93 r, err := c.OpenLTXFile(t.Context(), litestream.SnapshotLevel, 1, 1, 0, 0)
94 if err != nil {
95 t.Fatal(err)
96 }
97 deadline, ok := f.ctx.Deadline()
98 if !ok || time.Until(deadline) > generationRequestLimit || time.Until(deadline) < generationRequestLimit-time.Second {
99 t.Fatalf("deadline=%v ok=%v", deadline, ok)
100 }
101 select {
102 case <-f.ctx.Done():
103 t.Fatal("stream context canceled before Close")
104 default:
105 }
106 if err := r.Close(); err != nil {
107 t.Fatal(err)
108 }
109 select {
110 case <-f.ctx.Done():
111 case <-time.After(time.Second):
112 t.Fatal("stream Close did not cancel request context")
113 }
114 if checks != 1 {
115 t.Fatalf("authority checks=%d", checks)
116 }
117}
118
119// GenerationTestBinding is a test-only bridge for the external package to
120// exercise hostbox.GenerationPrefix without a lease -> hostbox import cycle.
121func GenerationTestBinding(location, prefix string) error {
122 l := lss3.NewLeaser()
123 l.Path = location
124 h := &Held{leaser: l, replicaClient: &generationFake{}}
125 return h.BindGeneration(prefix, func(context.Context) error { return nil })
126}
127
128func TestGenerationClientRetentionRefusedButUploadsContinue(t *testing.T) {
129 f := &generationFake{}
130 c := &generationClient{client: f, check: func(context.Context) error { return nil }}
131 for _, level := range []int{0, 1, litestream.SnapshotLevel} {
132 if err := c.DeleteLTXFiles(t.Context(), []*ltx.FileInfo{{Level: level, MinTXID: 1, MaxTXID: 2}}); !errors.Is(err, ErrGenerationRetention) {
133 t.Fatalf("level %d delete: %v", level, err)
134 }
135 }
136 if f.calls != 0 {
137 t.Fatal("bare-key deletion reached backing client")
138 }
139 if _, err := c.WriteLTXFile(t.Context(), 0, 3, 3, bytes.NewReader(nil)); err != nil {
140 t.Fatal(err)
141 }
142 if f.calls != 1 {
143 t.Fatal("replication stopped after refusal")
144 }
145}
146
147func TestGenerationClientDisablesRemoteRetentionInRealStore(t *testing.T) {
148 for _, bound := range []bool{false, true} {
149 l := lss3.NewLeaser()
150 l.Path = "instances/x.example/inc"
151 h := &Held{leaser: l, replicaClient: &generationFake{}}
152 if bound {
153 if err := h.BindGeneration(l.Path+"/g-one/", func(context.Context) error { return nil }); err != nil {
154 t.Fatal(err)
155 }
156 }
157 _, db, err := h.newStore(filepath.Join(t.TempDir(), "db"), time.Second)
158 if err != nil {
159 t.Fatal(err)
160 }
161 if db.RetentionEnabled == bound {
162 t.Fatalf("bound=%v retention=%v", bound, db.RetentionEnabled)
163 }
164 }
165}
166
167func TestGenerationClientRealRetentionMonitorsDoNotDelete(t *testing.T) {
168 l := lss3.NewLeaser()
169 l.Path = "instances/x.example/inc"
170 f := &generationFake{files: []*ltx.FileInfo{{Level: 1, MinTXID: 1, MaxTXID: 2, CreatedAt: time.Unix(1, 0)}, {Level: 1, MinTXID: 3, MaxTXID: 4, CreatedAt: time.Unix(2, 0)}}}
171 h := &Held{leaser: l, replicaClient: f}
172 if err := h.BindGeneration(l.Path+"/g-one/", func(context.Context) error { return nil }); err != nil {
173 t.Fatal(err)
174 }
175 store, db, err := h.newStore(filepath.Join(t.TempDir(), "db"), time.Second)
176 if err != nil {
177 t.Fatal(err)
178 }
179 if store.RetentionEnabled || db.RetentionEnabled {
180 t.Fatal("remote retention enabled")
181 }
182 db.MonitorInterval = 0
183 db.Replica.MonitorEnabled = false
184 if err := db.Open(); err != nil {
185 t.Fatal(err)
186 }
187 defer db.Close(t.Context())
188 // These execute the real DB and compactor retention code. A deletion
189 // attempt would get ErrGenerationRetention and fail the call.
190 if err := db.EnforceRetentionByTXID(t.Context(), 1, 10); err != nil {
191 t.Fatal(err)
192 }
193 if _, err := db.EnforceSnapshotRetention(t.Context(), time.Now()); err != nil {
194 t.Fatal(err)
195 }
196 if err := db.EnforceL0RetentionByTime(t.Context()); err != nil {
197 t.Fatal(err)
198 }
199 if _, err := db.Replica.Client.WriteLTXFile(t.Context(), 0, 5, 5, bytes.NewReader(nil)); err != nil {
200 t.Fatal(err)
201 }
202}
203
204// BindRoot gives the root layout the same per-request authority a bound
205// generation has (moves Task 5 fix round 1, ruling 2): every replica request
206// below the lease's own path first passes check, so an authority withdrawn
207// between two requests refuses the second before it reaches storage. It is
208// as immutable as BindGeneration, and the two exclude each other.
209func TestBindRootChecksEveryRequest(t *testing.T) {
210 leaser := lss3.NewLeaser()
211 leaser.Path = "instances/shop.example"
212 f := &generationFake{}
213 h := &Held{leaser: leaser, replicaClient: f}
214 withdrawn := errors.New("the claim moved")
215 calls := 0
216 check := func(context.Context) error {
217 calls++
218 if calls > 1 {
219 return withdrawn
220 }
221 return nil
222 }
223 if err := h.BindRoot(check); err != nil {
224 t.Fatal(err)
225 }
226 if err := h.replicaClient.Init(t.Context()); err != nil {
227 t.Fatalf("first request: %v", err)
228 }
229 if _, err := h.replicaClient.LTXFiles(t.Context(), 0, 0, false); !errors.Is(err, withdrawn) {
230 t.Fatalf("second request after the authority was withdrawn: %v", err)
231 }
232 if f.calls != 1 {
233 t.Fatalf("storage saw %d requests, want the first only", f.calls)
234 }
235 if err := h.BindRoot(check); err != ErrGenerationBinding {
236 t.Fatalf("rebind=%v", err)
237 }
238 if err := h.BindGeneration("instances/shop.example/g-a", check); err != ErrGenerationBinding {
239 t.Fatalf("a generation over the root binding=%v", err)
240 }
241 h2 := &Held{leaser: leaser, replicaClient: &generationFake{}}
242 if err := h2.BindGeneration("instances/shop.example/g-a", check); err != nil {
243 t.Fatal(err)
244 }
245 if err := h2.BindRoot(check); err != ErrGenerationBinding {
246 t.Fatalf("the root over a generation binding=%v", err)
247 }
248 if err := (&Held{leaser: leaser}).BindRoot(nil); err != ErrGenerationBinding {
249 t.Fatalf("nil check=%v", err)
250 }
251}
252