carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3// These tests share the package-level plans cache, so they stay serial.
4
5import (
6 "context"
7 "errors"
8 "slices"
9 "sync"
10 "testing"
11 "time"
12
13 "github.com/carlosframework/platform/internal/plans"
14 "github.com/carlosframework/platform/internal/registry"
15 "github.com/carlosframework/platform/internal/release"
16)
17
18// plansCountingStore records every Get and can fail chosen keys.
19type plansCountingStore struct {
20 release.Store
21 mu sync.Mutex
22 gets []string
23 fail map[string]bool
24}
25
26func (c *plansCountingStore) Get(ctx context.Context, key string) ([]byte, string, error) {
27 c.mu.Lock()
28 c.gets = append(c.gets, key)
29 failing := c.fail[key]
30 c.mu.Unlock()
31 if failing {
32 return nil, "", errors.New("bucket unavailable")
33 }
34 return c.Store.Get(ctx, key)
35}
36
37func (c *plansCountingStore) reset() {
38 c.mu.Lock()
39 c.gets, c.fail = nil, map[string]bool{}
40 c.mu.Unlock()
41}
42
43func plansWorld(t *testing.T) (*plansCountingStore, *registry.Registry) {
44 t.Helper()
45 t.Cleanup(func() { planLimits.Store(nil) })
46 s := &plansCountingStore{Store: release.NewDirStore(t.TempDir()), fail: map[string]bool{}}
47 reg := edgesTestRegistry(t)
48 for _, rt := range []registry.Route{
49 {Host: "a.example", Kind: "instance", Account: "bab", App: "web", Status: "ready"},
50 {Host: "b.example", Kind: "instance", Account: "bdk", App: "shop", Status: "ready"},
51 {Host: "status.example", Kind: "status", Status: "ready"},
52 } {
53 if err := reg.Upsert(rt); err != nil {
54 t.Fatal(err)
55 }
56 }
57 return s, reg
58}
59
60func putMarker(t *testing.T, s release.Store, enabled bool) {
61 t.Helper()
62 ctx := context.Background()
63 _, etag, err := plans.GetMarker(ctx, s)
64 if err != nil {
65 t.Fatal(err)
66 }
67 if _, err := plans.PutMarker(ctx, s, &plans.Marker{Enabled: enabled, CatalogID: "v1@0123456789ab"}, etag); err != nil {
68 t.Fatal(err)
69 }
70}
71
72func putAccountLimits(t *testing.T, s release.Store, sqid string, idle int) {
73 t.Helper()
74 ctx := context.Background()
75 _, etag, err := plans.GetLimits(ctx, s, sqid)
76 if err != nil {
77 t.Fatal(err)
78 }
79 r := &plans.Record{Account: sqid, CatalogID: "v1@0123456789ab", Plan: "free", Revision: 1, Limits: plans.Limits{IdleSeconds: idle}}
80 if _, err := plans.PutLimits(ctx, s, r, etag); err != nil {
81 t.Fatal(err)
82 }
83}
84
85func plansNoEnv(string) string { return "" }
86
87func TestPlanLimitsSwitch(t *testing.T) {
88 for v, want := range map[string]bool{"": true, "on": true, "off": false, "OFF": false, "false": false, "0": false, "no": false, " off ": false} {
89 if got := planLimitsSwitchOn(func(string) string { return v }); got != want {
90 t.Errorf("CARLOS_PLAN_LIMITS=%q: on = %v, want %v", v, got, want)
91 }
92 }
93}
94
95func TestPlansPassOffReadsOnlyTheMarker(t *testing.T) {
96 s, reg := plansWorld(t)
97 putAccountLimits(t, s, "bab", 90)
98 s.reset()
99 plansPass(context.Background(), s, reg, plansNoEnv)
100 if !slices.Equal(s.gets, []string{plans.MarkerKey()}) {
101 t.Fatalf("reads %v, want only the marker", s.gets)
102 }
103 if _, ok := planLimitsFor("bab"); ok {
104 t.Fatal("limits applied with no marker")
105 }
106}
107
108func TestPlansPassBoxSwitchOffReadsNothing(t *testing.T) {
109 s, reg := plansWorld(t)
110 putMarker(t, s, true)
111 putAccountLimits(t, s, "bab", 90)
112 plansPass(context.Background(), s, reg, plansNoEnv)
113 if _, ok := planLimitsFor("bab"); !ok {
114 t.Fatal("setup: limits not loaded")
115 }
116 s.reset()
117 plansPass(context.Background(), s, reg, func(string) string { return "off" })
118 if len(s.gets) != 0 {
119 t.Fatalf("switched-off box read %v", s.gets)
120 }
121 if _, ok := planLimitsFor("bab"); ok {
122 t.Fatal("switched-off box kept limits")
123 }
124}
125
126func TestPlansPassLoadsServedAccounts(t *testing.T) {
127 s, reg := plansWorld(t)
128 putMarker(t, s, true)
129 putAccountLimits(t, s, "bab", 90)
130 putAccountLimits(t, s, "bdk", 900)
131 putAccountLimits(t, s, "zzz", 60)
132 s.reset()
133 plansPass(context.Background(), s, reg, plansNoEnv)
134 if l, ok := planLimitsFor("bab"); !ok || l.IdleSeconds != 90 {
135 t.Fatalf("bab: %+v, %v", l, ok)
136 }
137 if l, ok := planLimitsFor("bdk"); !ok || l.IdleSeconds != 900 {
138 t.Fatalf("bdk: %+v, %v", l, ok)
139 }
140 if slices.Contains(s.gets, plans.LimitsKey("zzz")) {
141 t.Fatal("read the limits of an account with no routes on this box")
142 }
143}
144
145func TestPlansPassAbsentRecordMeansNoLimits(t *testing.T) {
146 s, reg := plansWorld(t)
147 putMarker(t, s, true)
148 putAccountLimits(t, s, "bab", 90)
149 plansPass(context.Background(), s, reg, plansNoEnv)
150 if _, ok := planLimitsFor("bdk"); ok {
151 t.Fatal("an account with no record got limits")
152 }
153}
154
155func TestPlansPassKeepsLastGoodOnErrors(t *testing.T) {
156 s, reg := plansWorld(t)
157 putMarker(t, s, true)
158 putAccountLimits(t, s, "bab", 90)
159 putAccountLimits(t, s, "bdk", 900)
160 plansPass(context.Background(), s, reg, plansNoEnv)
161
162 s.fail[plans.MarkerKey()] = true
163 plansPass(context.Background(), s, reg, plansNoEnv)
164 if l, ok := planLimitsFor("bab"); !ok || l.IdleSeconds != 90 {
165 t.Fatalf("a marker read error dropped bab: %+v, %v", l, ok)
166 }
167
168 s.reset()
169 putAccountLimits(t, s, "bdk", 600)
170 s.fail[plans.LimitsKey("bab")] = true
171 plansPass(context.Background(), s, reg, plansNoEnv)
172 if l, ok := planLimitsFor("bab"); !ok || l.IdleSeconds != 90 {
173 t.Fatalf("a record read error dropped bab: %+v, %v", l, ok)
174 }
175 if l, _ := planLimitsFor("bdk"); l.IdleSeconds != 600 {
176 t.Fatalf("bdk not refreshed: %+v", l)
177 }
178}
179
180func TestPlansPassClearsWhenDisabled(t *testing.T) {
181 s, reg := plansWorld(t)
182 putMarker(t, s, true)
183 putAccountLimits(t, s, "bab", 90)
184 plansPass(context.Background(), s, reg, plansNoEnv)
185 putMarker(t, s, false)
186 plansPass(context.Background(), s, reg, plansNoEnv)
187 if _, ok := planLimitsFor("bab"); ok {
188 t.Fatal("limits kept after plans were disabled")
189 }
190}
191
192// blockingLimitsStore blocks reads of one limits key until released, and
193// says when a read has reached it.
194type blockingLimitsStore struct {
195 release.Store
196 key string
197 reached chan struct{}
198 release chan struct{}
199 once sync.Once
200}
201
202func (b *blockingLimitsStore) Get(ctx context.Context, key string) ([]byte, string, error) {
203 if key == b.key {
204 b.once.Do(func() { close(b.reached); <-b.release })
205 }
206 return b.Store.Get(ctx, key)
207}
208
209// Astra plans-2 finding 2: the full pass and the five-minute pass both run
210// plansPass. A pass that read an enabled marker and stalled must not install
211// its snapshot after a newer pass saw plans disabled.
212func TestPlansPassesDoNotRestoreDisabledLimits(t *testing.T) {
213 s, reg := plansWorld(t)
214 putMarker(t, s, true)
215 putAccountLimits(t, s, "bab", 90)
216 b := &blockingLimitsStore{Store: s.Store, key: plans.LimitsKey("bab"), reached: make(chan struct{}), release: make(chan struct{})}
217
218 aDone := make(chan struct{})
219 go func() { plansPass(context.Background(), b, reg, plansNoEnv); close(aDone) }()
220 <-b.reached // pass A read the enabled marker and is stalled on bab's record
221
222 putMarker(t, s, false)
223 bDone := make(chan struct{})
224 go func() { plansPass(context.Background(), s.Store, reg, plansNoEnv); close(bDone) }()
225 select { // without ordering, pass B finishes now and clears the cache
226 case <-bDone:
227 case <-time.After(200 * time.Millisecond):
228 }
229 close(b.release)
230 <-aDone
231 <-bDone
232 if _, ok := planLimitsFor("bab"); ok {
233 t.Fatal("a stalled pass restored limits after plans were disabled")
234 }
235}
236