carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package host
4
5import (
6 "context"
7 "errors"
8 "io"
9 "os"
10 "path/filepath"
11 "strings"
12 "testing"
13
14 "github.com/carlosframework/platform/internal/hostclaim"
15 "github.com/carlosframework/platform/internal/hostfiles"
16 "github.com/carlosframework/platform/internal/hostwrite"
17 "github.com/carlosframework/platform/internal/lease"
18 "github.com/carlosframework/platform/internal/lease/leasetest"
19 "github.com/carlosframework/platform/internal/registry"
20 "github.com/carlosframework/platform/internal/release"
21)
22
23func filesSeedPrivateFixture(t *testing.T) (*filesSeedFixture, *movesFiles, *filesSeedOperation, ClaimLaunchInputs) {
24 f := newFilesSeedFixture(t)
25 selected, err := f.inspect()
26 if err != nil {
27 t.Fatal(err)
28 }
29 reg, err := registry.Open(filepath.Join(t.TempDir(), "registry.db"))
30 if err != nil {
31 t.Fatal(err)
32 }
33 t.Cleanup(func() { reg.Close() })
34 if err := reg.Upsert(f.route); err != nil {
35 t.Fatal(err)
36 }
37 if err := reg.SetNextDue(filesHost, 777); err != nil {
38 t.Fatal(err)
39 }
40 f.actor.Cloud.Region = "eu-west-1"
41 source := &movesFilesSource{DataDir: f.files.DataDir, Routes: testClaimRoutes{rows: map[string]registry.Route{filesHost: f.route}}, newFiles: func(context.Context, string, string) (release.Store, error) { return f.files.Files, nil }}
42 private, op, err := newFilesSeedProvider(t.Context(), source, f.claims, f.data, f.actor, reg, selected)
43 if err != nil {
44 t.Fatal(err)
45 }
46 if strings.HasSuffix(t.Name(), "/origins-off") {
47 t.Setenv("CARLOS_HOST_CLAIM_ORIGINS", "")
48 }
49 f.admit("seed")
50 if err := op.admitted(t.Context(), f.observed); err != nil {
51 t.Fatal(err)
52 }
53 in := ClaimLaunchInputs{Identity: hostwrite.Identity{Config: "config", Release: "release"}, DBPath: f.route.DBPath, Admission: hostclaim.Admission{Box: f.actor.Box, Region: f.actor.Region, Mode: "cold", NewGeneration: "seed"}}
54 return f, private, op, in
55}
56
57func TestFilesSeedMaterializeUsesRealAdmittedPrefix(t *testing.T) {
58 f, private, op, in := filesSeedPrivateFixture(t)
59 path := filepath.Join(filepath.Dir(f.route.DBPath), filesHost, "tenant.txt")
60 if err := os.WriteFile(path, []byte("tenant bytes"), 0600); err != nil {
61 t.Fatal(err)
62 }
63 owners := plan9D5Fixture(t).owners
64 owners.Sources.Files = private
65 work := &filesFixture{t: t}
66 if err := owners.Materialize(t.Context(), f.observed, in, work); err != nil {
67 t.Fatal(err)
68 }
69 prefix := movesFilesPrefix(f.observed.Claim.Holder.Location, "seed")
70 m, found, err := hostfiles.Load(t.Context(), f.files.Files, prefix)
71 if err != nil || !found || m.Entries["tenant.txt"].Object == "" || len(m.Origins) != 1 || m.Origins[0] != prefix || op.stats.Published != 1 {
72 t.Fatal(m, err, op.stats)
73 }
74 ws, found, err := hostfiles.LoadWake(t.Context(), f.files.Files, prefix, m, m.Origins)
75 if err != nil || !found || ws.NextDue != 777 {
76 t.Fatal(ws, err)
77 }
78 env, err := hostfiles.ReadPlatform(t.Context(), f.files.Files, prefix, m, hostfiles.EnvItem, m.Origins)
79 if err != nil || string(env) != "KEY=value\n" {
80 t.Fatal(string(env), err)
81 }
82 version := f.data.LatestID("data", prefix+"manifest.json")
83 if err := private.Establish(t.Context(), f.observed, in, work); !errors.Is(err, hostfiles.ErrManifestExists) {
84 t.Fatal("armed replay", err)
85 }
86 if f.data.LatestID("data", prefix+"manifest.json") != version || f.files.Seed != nil {
87 t.Fatal("replay or ordinary provider changed")
88 }
89 // An ordinary successor carries the committed manifest without a seed hook.
90 f.admit("next")
91 in.Admission.NewGeneration = "next"
92 if err := f.files.Establish(t.Context(), f.observed, in, work); err != nil {
93 t.Fatal("ordinary successor", err)
94 }
95 if f.files.Seed != nil {
96 t.Fatal("seed escaped into ordinary provider")
97 }
98}
99
100type filesSeedReadCounter struct {
101 lease.VersionedStore
102 reads int
103}
104
105func (s *filesSeedReadCounter) OpenVersion(ctx context.Context, bucket, key, version string) (io.ReadCloser, error) {
106 if strings.Contains(key, "/objects/") {
107 s.reads++
108 }
109 return s.VersionedStore.OpenVersion(ctx, bucket, key, version)
110}
111
112type filesSeedConflictStore struct {
113 release.Store
114 data *leasetest.VersionedBucket
115 poison bool
116 injected bool
117}
118
119func (s *filesSeedConflictStore) Put(ctx context.Context, key string, body []byte, etag string) (string, error) {
120 if !s.injected && strings.Contains(key, "/objects/") {
121 s.injected = true
122 actual := body
123 if s.poison {
124 actual = []byte("poisoned bytes")
125 }
126 s.data.Put("data", key, actual)
127 }
128 return s.Store.Put(ctx, key, body, etag)
129}
130func TestFilesSeedCreateConflictChecksBytesBeforeCommit(t *testing.T) {
131 for _, kind := range []string{"created", "same", "poisoned"} {
132 t.Run(kind, func(t *testing.T) {
133 f, private, _, in := filesSeedPrivateFixture(t)
134 counter := &filesSeedReadCounter{VersionedStore: f.data}
135 private.Data = counter
136 if kind != "created" {
137 private.Files = &filesSeedConflictStore{Store: private.Files, data: f.data, poison: kind == "poisoned"}
138 }
139 err := private.Establish(t.Context(), f.observed, in, &filesFixture{t: t})
140 prefix := movesFilesPrefix(f.observed.Claim.Holder.Location, "seed")
141 if kind == "poisoned" {
142 if !errors.Is(err, hostfiles.ErrSuperseded) || f.data.LatestID("data", prefix+"manifest.json") != "" {
143 t.Fatal("poison committed", err)
144 }
145 } else if err != nil {
146 t.Fatal(err)
147 }
148 want := 1
149 if kind == "created" {
150 want = 0
151 }
152 if counter.reads != want {
153 t.Fatal("object read count", counter.reads, want)
154 }
155 })
156 }
157}
158
159func TestFilesSeedRefusesMissingOriginAndWrongTuple(t *testing.T) {
160 for _, fault := range []string{"generation", "empty", "prepared", "source-gone", "origins-off"} {
161 t.Run(fault, func(t *testing.T) {
162 f, private, _, in := filesSeedPrivateFixture(t)
163 switch fault {
164 case "generation":
165 in.Admission.NewGeneration = "foreign"
166 case "empty":
167 in.Empty = true
168 case "prepared":
169 in.Prepared = true
170 case "source-gone":
171 if err := os.Remove(filepath.Join(filepath.Dir(f.route.DBPath), filesHost)); err != nil {
172 t.Fatal(err)
173 }
174 case "origins-off":
175 if f.observed.Claim.Origins != nil {
176 t.Fatal("fixture recorded origins")
177 }
178
179 }
180 if err := private.Establish(t.Context(), f.observed, in, &filesFixture{t: t}); err == nil {
181 t.Fatal("accepted", fault)
182 }
183 if f.data.LatestID("data", movesFilesPrefix(f.observed.Claim.Holder.Location, in.Admission.NewGeneration)+"manifest.json") != "" {
184 t.Fatal("refusal committed")
185 }
186 })
187 }
188}
189