carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package appconfig
2
3import (
4 "context"
5 "encoding/json"
6 "sync"
7 "testing"
8 "time"
9
10 "github.com/carlosframework/platform/internal/release"
11)
12
13// gatedStore holds every Get until want of them are in flight at once, or
14// a deadline passes. A loader that reads its layers one after another
15// waits out the deadline on each; one that reads them together does not.
16type gatedStore struct {
17 release.Store
18 want int
19 mu sync.Mutex
20 in int
21 open chan struct{}
22}
23
24func (g *gatedStore) Get(ctx context.Context, key string) ([]byte, string, error) {
25 g.mu.Lock()
26 g.in++
27 if g.in == g.want {
28 close(g.open)
29 }
30 g.mu.Unlock()
31 select {
32 case <-g.open:
33 case <-time.After(300 * time.Millisecond):
34 }
35 return g.Store.Get(ctx, key)
36}
37
38func putManifest(t *testing.T, s release.Store, key string, m *Manifest) {
39 t.Helper()
40 raw, err := json.Marshal(m)
41 if err != nil {
42 t.Fatal(err)
43 }
44 if _, err := s.Put(context.Background(), key, raw, ""); err != nil {
45 t.Fatal(err)
46 }
47}
48
49// A wake's config admission reads three layers (the app's default bundle,
50// its environment, the host's overlay). They are independent objects, so
51// they are read together: one round trip, not three, before the lease.
52func TestLoadMergedHostVersionsReadsItsLayersTogether(t *testing.T) {
53 dir := release.NewDirStore(t.TempDir())
54 putManifest(t, dir, Key("bab", "shop"), &Manifest{Schema: 1, App: "shop", Vars: map[string]Var{"A": {Kind: "env", Value: "default"}, "B": {Kind: "env", Value: "default"}}})
55 putManifest(t, dir, EnvironmentKey("bab", "shop", "prod"), &Manifest{Schema: 1, App: "shop", Vars: map[string]Var{"B": {Kind: "env", Value: "env"}, "C": {Kind: "env", Value: "env"}}})
56 putManifest(t, dir, InstanceConfigKey("bab", "shop", "one.example.com"), &Manifest{Schema: 1, App: "shop", Vars: map[string]Var{"C": {Kind: "env", Value: "host"}}})
57 g := &gatedStore{Store: dir, want: 3, open: make(chan struct{})}
58 start := time.Now()
59 m, v, err := LoadMergedHostVersions(context.Background(), g, "bab", "shop", "prod", "one.example.com")
60 if err != nil {
61 t.Fatal(err)
62 }
63 if took := time.Since(start); took > 250*time.Millisecond {
64 t.Errorf("took %s: the three layers were not read together", took)
65 }
66 if m.Vars["A"].Value != "default" || m.Vars["B"].Value != "env" || m.Vars["C"].Value != "host" {
67 t.Errorf("merge = %+v, want default < environment < host", m.Vars)
68 }
69 if v.Default == "" || v.Environment == "" || v.Overlay == "" {
70 t.Errorf("versions = %+v, want each layer's etag", v)
71 }
72}
73
74// Reading together changes no answer: an environment the route is bound to
75// but nobody created is still the error, whatever the other reads found.
76func TestLoadMergedHostVersionsStillRefusesAMissingEnvironment(t *testing.T) {
77 dir := release.NewDirStore(t.TempDir())
78 putManifest(t, dir, Key("bab", "shop"), &Manifest{Schema: 1, App: "shop", Vars: map[string]Var{}})
79 putManifest(t, dir, InstanceConfigKey("bab", "shop", "one.example.com"), &Manifest{Schema: 1, App: "shop", Vars: map[string]Var{}})
80 if _, _, err := LoadMergedHostVersions(context.Background(), dir, "bab", "shop", "prod", "one.example.com"); err == nil {
81 t.Fatal("a missing environment was accepted")
82 }
83}
84