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
3import (
4 "context"
5 "errors"
6 "path/filepath"
7 "testing"
8 "time"
9
10 "github.com/carlosframework/platform/internal/hostbox"
11 "github.com/carlosframework/platform/internal/hostwrite"
12 "github.com/carlosframework/platform/internal/lease"
13 "github.com/carlosframework/platform/internal/lease/leasetest"
14 "github.com/carlosframework/platform/internal/registry"
15 "github.com/carlosframework/platform/internal/release"
16)
17
18// fakeFilesSource is a C8 files source that records its opens and closes.
19type fakeFilesSource struct {
20 opens, closes int
21 actor hostbox.Actor
22 err error
23 files ClaimFilesManifest
24}
25
26func (s *fakeFilesSource) Open(_ context.Context, _ release.Store, _ lease.VersionedStore, actor hostbox.Actor) (ClaimFilesManifest, error) {
27 s.opens++
28 s.actor = actor
29 return s.files, s.err
30}
31
32func (s *fakeFilesSource) Close(context.Context) error { s.closes++; return nil }
33
34func withClaimFilesSource(t *testing.T, s ClaimProductionFilesSource) {
35 t.Helper()
36 claimFilesSources.Lock()
37 prev := claimFilesSources.Source
38 claimFilesSources.Source = s
39 claimFilesSources.Unlock()
40 t.Cleanup(func() {
41 claimFilesSources.Lock()
42 claimFilesSources.Source = prev
43 claimFilesSources.Unlock()
44 })
45}
46
47// The C8 provider registers once at process start, under
48// CARLOS_HOST_CLAIM alone (platform-1e, 2026-10-12): a nil source and a
49// second registration are refused, and neither can replace the first.
50func TestRegisterProductionClaimFilesIsOneShot(t *testing.T) {
51 withClaimFilesSource(t, nil)
52 if err := RegisterProductionClaimFiles(nil); !errors.Is(err, hostwrite.ErrUnavailable) {
53 t.Fatalf("nil source: %v", err)
54 }
55 first := &fakeFilesSource{files: &movesFiles{}}
56 if err := RegisterProductionClaimFiles(first); err != nil {
57 t.Fatal(err)
58 }
59 if err := RegisterProductionClaimFiles(&fakeFilesSource{}); !errors.Is(err, hostwrite.ErrUnavailable) {
60 t.Fatalf("second registration: %v", err)
61 }
62 if productionClaimFilesSource() != ClaimProductionFilesSource(first) {
63 t.Fatal("the registered source is not the first")
64 }
65}
66
67// openClaimFilesManifest binds the registered source's provider into the
68// serving sources with the claim on and moves off or on; with the claim
69// off, or nothing registered, the bindings are untouched and nothing is
70// opened. A provider the moves source already bound is a wiring error,
71// never silently replaced; an Open error or a nil provider refuses.
72func TestOpenClaimFilesManifestBindsUnderTheClaimAlone(t *testing.T) {
73 store := release.NewDirStore(t.TempDir())
74 data := leasetest.NewVersionedBucket()
75 actor := hostbox.Actor{Box: "box-1", Region: "ie", Bucket: "data", HomeRegion: "ie", Enrollment: "enr-1"}
76 claimOn := func(k string) string {
77 if k == "CARLOS_HOST_CLAIM" {
78 return "1"
79 }
80 return ""
81 }
82 bothOn := func(k string) string {
83 if k == "CARLOS_HOST_CLAIM" || k == "CARLOS_MOVES_PHASE1" {
84 return "1"
85 }
86 return ""
87 }
88 off := func(string) string { return "" }
89
90 pass := func(context.Context) error { return nil }
91 provider := &movesFiles{}
92 for name, getenv := range map[string]func(string) string{"claim alone": claimOn, "both": bothOn} {
93 src := &fakeFilesSource{files: provider}
94 var out ClaimOwnerBindings
95 closeFn, err := openClaimFilesManifest(context.Background(), getenv, src, store, data, actor, &out, pass)
96 if err != nil || closeFn == nil {
97 t.Fatalf("%s: %v %v", name, err, closeFn != nil)
98 }
99 if out.Serving.Files != ClaimFilesManifest(provider) || src.opens != 1 || src.actor != actor {
100 t.Fatalf("%s: bound %v, opens %d, actor %+v", name, out.Serving.Files, src.opens, src.actor)
101 }
102 if err := closeFn(context.Background()); err != nil || src.closes != 1 {
103 t.Fatalf("%s: close %v %d", name, err, src.closes)
104 }
105 }
106
107 // Claim off: untouched, not opened.
108 src := &fakeFilesSource{files: provider}
109 var out ClaimOwnerBindings
110 closeFn, err := openClaimFilesManifest(context.Background(), off, src, store, data, actor, &out, pass)
111 if err != nil || closeFn != nil || out.Serving.Files != nil || src.opens != 0 {
112 t.Fatalf("claim off: %v %v %v %d", err, closeFn != nil, out.Serving.Files, src.opens)
113 }
114 // Nothing registered: untouched.
115 if closeFn, err := openClaimFilesManifest(context.Background(), claimOn, nil, store, data, actor, &out, pass); err != nil || closeFn != nil || out.Serving.Files != nil {
116 t.Fatalf("nothing registered: %v %v %v", err, closeFn != nil, out.Serving.Files)
117 }
118 // Already bound by the moves source: refused, not replaced.
119 already := &movesFiles{Box: "other"}
120 out = ClaimOwnerBindings{Serving: ClaimServingSources{Files: already}}
121 src = &fakeFilesSource{files: provider}
122 if _, err := openClaimFilesManifest(context.Background(), claimOn, src, store, data, actor, &out, pass); !errors.Is(err, hostwrite.ErrUnavailable) || out.Serving.Files != ClaimFilesManifest(already) || src.opens != 0 {
123 t.Fatalf("two providers: %v %v %d", err, out.Serving.Files, src.opens)
124 }
125 // An Open error refuses; a nil provider refuses.
126 out = ClaimOwnerBindings{}
127 src = &fakeFilesSource{err: errors.New("no bucket")}
128 if _, err := openClaimFilesManifest(context.Background(), claimOn, src, store, data, actor, &out, pass); err == nil || out.Serving.Files != nil {
129 t.Fatalf("open error: %v %v", err, out.Serving.Files)
130 }
131 src = &fakeFilesSource{}
132 if _, err := openClaimFilesManifest(context.Background(), claimOn, src, store, data, actor, &out, pass); !errors.Is(err, hostwrite.ErrUnavailable) || out.Serving.Files != nil {
133 t.Fatalf("nil provider: %v %v", err, out.Serving.Files)
134 }
135}
136
137// C8 reads the claim's data bucket as the serving and moves owners do
138// (whole-branch fixes, item 5): through hostbox.RequestStore, so every
139// version listing and exact-version read it makes is bounded by the
140// claim's request limit and preceded by the box's identity check, a
141// refused identity issuing no bucket call. An establishment under a
142// refused identity refuses with hostbox.ErrChanged and writes nothing.
143func TestClaimFilesReadTheBucketThroughTheRequestStore(t *testing.T) {
144 inner := &mwBucket{VersionedBucket: leasetest.NewVersionedBucket()}
145 inner.AddBucket("data")
146 var refuse error
147 checks := 0
148 check := func(context.Context) error { checks++; return refuse }
149 src := &movesFilesSource{DataDir: t.TempDir(), Routes: testClaimRoutes{}, newFiles: func(context.Context, string, string) (release.Store, error) {
150 return casBucketStore{b: inner.VersionedBucket, bucket: "data"}, nil
151 }}
152 actor := hostbox.Actor{Box: "uk-box", Region: "uk", Bucket: "data", HomeRegion: "ie", Cloud: hostbox.CloudIdentity{Region: "eu-west-2"}}
153 var out ClaimOwnerBindings
154 out.Data = inner
155 closeFn, err := openClaimFilesManifest(t.Context(), func(k string) string { return "1" }, src, release.NewDirStore(t.TempDir()), inner, actor, &out, check)
156 if err != nil || closeFn == nil {
157 t.Fatalf("open: %v", err)
158 }
159 f, ok := out.Serving.Files.(*movesFiles)
160 if !ok {
161 t.Fatalf("%T", out.Serving.Files)
162 }
163 if _, ok := f.Data.(hostbox.RequestStore); !ok {
164 t.Fatalf("C8's bucket is %T, not the claim's request store", f.Data)
165 }
166 before := time.Now()
167 if _, err := f.Data.ListVersions(t.Context(), "data", "instances/"); err != nil {
168 t.Fatal(err)
169 }
170 if inner.lists != 1 || checks != 1 || inner.deadlines[0].IsZero() || inner.deadlines[0].After(time.Now().Add(hostbox.RequestLimit)) || inner.deadlines[0].Before(before.Add(hostbox.RequestLimit-time.Minute)) {
171 t.Fatalf("the request's deadline %v (lists %d, checks %d), want the request limit", inner.deadlines, inner.lists, checks)
172 }
173 refuse = hostbox.ErrChanged
174 if _, err := f.Data.ListVersions(t.Context(), "data", "instances/"); !errors.Is(err, hostbox.ErrChanged) || inner.lists != 1 {
175 t.Fatalf("a refused identity: %v, %d bucket calls", err, inner.lists)
176 }
177 if _, err := f.Data.GetVersion(t.Context(), "data", "k", "v"); !errors.Is(err, hostbox.ErrChanged) {
178 t.Fatalf("a refused identity's read: %v", err)
179 }
180 // An establishment under a refused identity: the chain's first listing
181 // refuses, nothing is written.
182 x := &filesFixture{t: t, bucket: inner.VersionedBucket, files: casBucketStore{b: inner.VersionedBucket, bucket: "data"}, dir: src.DataDir}
183 x.f = f
184 f.EnvFile = flatClaimEnvFile(x.dir)
185 x.writeEnv("K=1\n")
186 o := servingObserved("g2", "g1:uk:@root:uk", "g2:uk:g1:uk")
187 if err := f.Establish(t.Context(), o, launchInputs("g2"), x); !errors.Is(err, hostbox.ErrChanged) {
188 t.Fatalf("establish under a refused identity: %v", err)
189 }
190 if _, found := x.manifest("g2"); found {
191 t.Fatal("a manifest was written under a refused identity")
192 }
193 if inner.lists != 1 {
194 t.Fatalf("%d bucket calls, want none from the refused establishment", inner.lists)
195 }
196}
197
198// The production source opens a movesFiles over the verified actor: the
199// data bucket by exact version, the files store for that bucket in its
200// cloud region, this box's engine region and the home alias, and the
201// host's files directory and env file under the data directory. No pin
202// reader is wired on this branch, so Evidence is nil.
203func TestProductionFilesSourceOpensOverTheActor(t *testing.T) {
204 data := leasetest.NewVersionedBucket()
205 var gotBucket, gotRegion string
206 src := &movesFilesSource{DataDir: "/data", Routes: testClaimRoutes{}, newFiles: func(_ context.Context, bucket, region string) (release.Store, error) {
207 gotBucket, gotRegion = bucket, region
208 return release.NewDirStore(t.TempDir()), nil
209 }}
210 actor := hostbox.Actor{Box: "box-1", Region: "", HomeRegion: "ie", Bucket: "data-ie", Cloud: hostbox.CloudIdentity{Region: "eu-west-1"}}
211 got, err := src.Open(context.Background(), release.NewDirStore(t.TempDir()), data, actor)
212 if err != nil {
213 t.Fatal(err)
214 }
215 f, ok := got.(*movesFiles)
216 if !ok {
217 t.Fatalf("%T", got)
218 }
219 if gotBucket != "data-ie" || gotRegion != "eu-west-1" || f.Bucket != "data-ie" || f.Box != "box-1" || f.HomeRegion != "ie" || f.Region != "@home" || f.Data == nil || f.Files == nil || f.Evidence != nil {
220 t.Fatalf("provider: %+v (%s %s)", f, gotBucket, gotRegion)
221 }
222 if f.EnvFile == nil || f.DataDir != "/data" {
223 t.Fatalf("env file resolver set %v, data %s", f.EnvFile != nil, f.DataDir)
224 }
225 // An actor without a bucket, box or region cannot be opened, nor a
226 // source without the box's registry to find env files by.
227 if _, err := src.Open(context.Background(), release.NewDirStore(t.TempDir()), data, hostbox.Actor{Box: "box-1"}); !errors.Is(err, hostwrite.ErrUnavailable) {
228 t.Fatalf("bare actor: %v", err)
229 }
230 noRoutes := *src
231 noRoutes.Routes = nil
232 if _, err := noRoutes.Open(context.Background(), release.NewDirStore(t.TempDir()), data, actor); !errors.Is(err, hostwrite.ErrUnavailable) {
233 t.Fatalf("no registry: %v", err)
234 }
235 // Registration happens only with the claim on, and needs the registry.
236 reg, err := registry.Open(filepath.Join(t.TempDir(), "registry.db"))
237 if err != nil {
238 t.Fatal(err)
239 }
240 t.Cleanup(func() { reg.Close() })
241 withClaimFilesSource(t, nil)
242 if err := registerProductionClaimFiles(func(string) string { return "" }, "/data", reg); err != nil || productionClaimFilesSource() != nil {
243 t.Fatalf("claim off registered: %v %v", err, productionClaimFilesSource())
244 }
245 if err := registerProductionClaimFiles(func(k string) string { return "1" }, "/data", nil); !errors.Is(err, hostwrite.ErrUnavailable) || productionClaimFilesSource() != nil {
246 t.Fatalf("claim on without a registry: %v", err)
247 }
248 if err := registerProductionClaimFiles(func(k string) string { return "1" }, "/data", reg); err != nil || productionClaimFilesSource() == nil {
249 t.Fatalf("claim on: %v", err)
250 }
251}
252