carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "context"
5 "errors"
6 "os"
7 "reflect"
8 "testing"
9
10 "github.com/carlosframework/platform/internal/registry"
11)
12
13func addSidecar(t *testing.T, reg *registry.Registry, host, app, channel string, hibernate bool) {
14 t.Helper()
15 if err := reg.Upsert(registry.Route{
16 Host: host, Kind: "sidecar", App: app, Channel: channel, Hibernate: hibernate,
17 Args: `["sidecar","run"]`, Status: "ready",
18 }); err != nil {
19 t.Fatalf("upsert %s: %v", host, err)
20 }
21}
22
23func TestSidecarAdoptableIsASecondPredicateNotAWiderOne(t *testing.T) {
24 base := registry.Route{Host: "bot.example.com", Kind: "sidecar", App: "myapp", Status: "ready"}
25 for _, c := range []struct {
26 name string
27 mut func(*registry.Route)
28 sidecar, instance bool
29 }{
30 {"ready sidecar", func(*registry.Route) {}, true, false},
31 {"status empty", func(r *registry.Route) { r.Status = "" }, true, false},
32 {"no app", func(r *registry.Route) { r.App = "" }, false, false},
33 {"platform", func(r *registry.Route) { r.Platform = true }, false, false},
34 {"creating", func(r *registry.Route) { r.Status = "creating" }, false, false},
35 {"an instance", func(r *registry.Route) { r.Kind = "instance" }, false, true},
36 {"static", func(r *registry.Route) { r.Kind = "static" }, false, false},
37 } {
38 rt := base
39 c.mut(&rt)
40 if got := SidecarAdoptable(rt); got != c.sidecar {
41 t.Errorf("%s: SidecarAdoptable = %v, want %v", c.name, got, c.sidecar)
42 }
43 if got := Adoptable(rt); got != c.instance {
44 t.Errorf("%s: Adoptable = %v, want %v (Adoptable is not widened)", c.name, got, c.instance)
45 }
46 }
47 unit := base
48 unit.Backing = registry.BackingUnit
49 if CapabilityRoute(unit) {
50 t.Error("CapabilityRoute admitted a sidecar: no capability drop-ins are ever written for one")
51 }
52}
53
54// Spec §7.9: sidecars go through the same selection, staging, repoint and
55// stamp, and the pass never restarts them; RestartHibernating changes nothing
56// for them.
57func TestPassAdoptsASidecarWithoutRestartingIt(t *testing.T) {
58 s := NewDirStore(t.TempDir())
59 ctx := context.Background()
60 priv, pub := testKeys(t)
61 opt := t.TempDir()
62 reg := testRegistry(t)
63 shipRelease(t, s, "bab", "myapp", "v1", adoptPlatform, []byte("bin"))
64 if err := Promote(ctx, s, priv, "bab", "myapp", "canary/x", "v1"); err != nil {
65 t.Fatal(err)
66 }
67 addRoute(t, reg, "web.example.com", "myapp", "canary/x", false)
68 addSidecar(t, reg, "bot.example.com", "myapp", "canary/x", false)
69 addSidecar(t, reg, "nap.example.com", "myapp", "canary/x", true)
70 var restarted []string
71 restart := func(h string) error { restarted = append(restarted, h); return nil }
72 if err := Pass(ctx, reg, s, pub, opt, restart, RestartHibernating(restart)); err != nil {
73 t.Fatalf("Pass: %v", err)
74 }
75 for _, h := range []string{"web.example.com", "bot.example.com", "nap.example.com"} {
76 if v, _ := LiveVersion(opt, h); v != "v1" {
77 t.Errorf("%s live = %q, want v1", h, v)
78 }
79 if rt, _ := reg.Get(h); rt == nil || rt.Version != "v1" {
80 t.Errorf("%s row = %+v, want Version v1 (root's stamp)", h, rt)
81 }
82 }
83 if !reflect.DeepEqual(restarted, []string{"web.example.com"}) {
84 t.Fatalf("restarted = %v; the pass must never restart a sidecar", restarted)
85 }
86}
87
88// Sidecars go after every instance, so a build an instance poisons this pass
89// never reaches a sidecar.
90func TestSidecarIsNotMovedOntoABuildAnInstancePoisoned(t *testing.T) {
91 s := NewDirStore(t.TempDir())
92 ctx := context.Background()
93 priv, pub := testKeys(t)
94 opt := t.TempDir()
95 reg := testRegistry(t)
96 shipRelease(t, s, "bab", "myapp", "v1", adoptPlatform, []byte("good"))
97 shipRelease(t, s, "bab", "myapp", "v2", adoptPlatform, []byte("bad"))
98 if err := Promote(ctx, s, priv, "bab", "myapp", "canary/x", "v1"); err != nil {
99 t.Fatal(err)
100 }
101 addRoute(t, reg, "web.example.com", "myapp", "canary/x", false)
102 addSidecar(t, reg, "bot.example.com", "myapp", "canary/x", false)
103 if err := Pass(ctx, reg, s, pub, opt, func(string) error { return nil }); err != nil {
104 t.Fatal(err)
105 }
106 if err := Promote(ctx, s, priv, "bab", "myapp", "canary/x", "v2"); err != nil {
107 t.Fatal(err)
108 }
109 restart := func(h string) error {
110 if h == "web.example.com" {
111 return errors.New("did not come up")
112 }
113 return nil
114 }
115 if err := Pass(ctx, reg, s, pub, opt, restart); err != nil {
116 t.Fatal(err)
117 }
118 if v, _ := LiveVersion(opt, "bot.example.com"); v != "v1" {
119 t.Fatalf("sidecar live = %q, want v1: a build that failed an instance this pass must not reach it", v)
120 }
121}
122
123// Spec §7.9: sidecars take the same bake holds.
124func TestSidecarWaitsOutItsBakeWindow(t *testing.T) {
125 s := NewDirStore(t.TempDir())
126 ctx := context.Background()
127 priv, pub := testKeys(t)
128 opt := t.TempDir()
129 reg := testRegistry(t)
130 shipRelease(t, s, "bab", "myapp", "v1", adoptPlatform, []byte("bin"))
131 if err := Promote(ctx, s, priv, "bab", "myapp", "stable", "v1"); err != nil {
132 t.Fatal(err)
133 }
134 if err := SetProduction(ctx, s, priv, "bab", "myapp", true, "test"); err != nil {
135 t.Fatal(err)
136 }
137 addSidecar(t, reg, "bot.example.com", "myapp", "stable", false)
138 if err := Pass(ctx, reg, s, pub, opt, func(string) error { return nil }); err != nil {
139 t.Fatal(err)
140 }
141 if v, _ := LiveVersion(opt, "bot.example.com"); v != "" {
142 t.Fatalf("sidecar live = %q, want unmoved inside stable's bake window", v)
143 }
144}
145
146// Spec §7.9: cold materialization of a new sidecar uses the same leg.
147func TestMaterializeHostMaterializesANewSidecar(t *testing.T) {
148 s := NewDirStore(t.TempDir())
149 ctx := context.Background()
150 priv, pub := testKeys(t)
151 opt := t.TempDir()
152 reg := testRegistry(t)
153 shipRelease(t, s, "bab", "myapp", "v1", adoptPlatform, []byte("bin"))
154 if err := Promote(ctx, s, priv, "bab", "myapp", "canary/x", "v1"); err != nil {
155 t.Fatal(err)
156 }
157 addSidecar(t, reg, "bot.example.com", "myapp", "canary/x", true)
158 if err := MaterializeHost(ctx, reg, s, pub, opt, t.TempDir(), "bot.example.com"); err != nil {
159 t.Fatalf("MaterializeHost: %v", err)
160 }
161 if _, err := os.Stat(LiveLink(opt, "bot.example.com")); err != nil {
162 t.Fatalf("live link does not resolve: %v", err)
163 }
164}
165
166// Unchanged behaviour: what happens to instances does not depend on whether
167// the box also carries a sidecar.
168func TestInstanceOutcomesDoNotDependOnTheSidecarLeg(t *testing.T) {
169 type outcome struct {
170 restarted []string
171 live, row string
172 }
173 run := func(withSidecar bool) outcome {
174 s := NewDirStore(t.TempDir())
175 ctx := context.Background()
176 priv, pub := testKeys(t)
177 opt := t.TempDir()
178 reg := testRegistry(t)
179 shipRelease(t, s, "bab", "myapp", "v1", adoptPlatform, []byte("bin"))
180 if err := Promote(ctx, s, priv, "bab", "myapp", "canary/x", "v1"); err != nil {
181 t.Fatal(err)
182 }
183 addRoute(t, reg, "web.example.com", "myapp", "canary/x", false)
184 addRoute(t, reg, "nap.example.com", "myapp", "canary/x", true)
185 if withSidecar {
186 addSidecar(t, reg, "bot.example.com", "myapp", "canary/x", false)
187 }
188 var o outcome
189 restart := func(h string) error { o.restarted = append(o.restarted, h); return nil }
190 if err := Pass(ctx, reg, s, pub, opt, restart, RestartHibernating(restart)); err != nil {
191 t.Fatal(err)
192 }
193 o.live, _ = LiveVersion(opt, "web.example.com")
194 if rt, _ := reg.Get("nap.example.com"); rt != nil {
195 o.row = rt.Version
196 }
197 return o
198 }
199 without, with := run(false), run(true)
200 if !reflect.DeepEqual(without, with) {
201 t.Fatalf("instance outcomes changed when a sidecar was present:\nwithout %+v\nwith %+v", without, with)
202 }
203}
204