carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package provision
2
3import (
4 "context"
5 "errors"
6 "testing"
7
8 "github.com/carlosframework/platform/internal/registry"
9)
10
11// Issue #213: a box mints an instance's record only if it can provide that
12// instance's config. ReconcileConfig.ConfigGate is the seam; the host agent
13// wires it to the same guard the activator asks before taking a lease.
14
15type gateLog struct {
16 asked []registry.Route
17 refuse map[string]error // host -> refusal
18}
19
20func (g *gateLog) gate(_ context.Context, rt registry.Route) error {
21 g.asked = append(g.asked, rt)
22 return g.refuse[rt.Host]
23}
24
25func TestReconcileDoesNotMintWhatTheGateRefuses(t *testing.T) {
26 s, sqid := setup(t)
27 if err := CreateRecord(ctx, s, acmeRecord(sqid)); err != nil {
28 t.Fatal(err)
29 }
30 reg := regWith(t)
31 g := &gateLog{refuse: map[string]error{"acme.go.tito.io": errors.New("no key")}}
32 cfg := ReconcileConfig{Region: "ie", Pools: []string{"tito"}, DataDir: "/data", ConfigGate: g.gate}
33
34 if err := Reconcile(ctx, s, reg, cfg); err != nil {
35 t.Fatal(err)
36 }
37 noRoute(t, reg, "acme.go.tito.io", "a refused record was minted")
38 if len(g.asked) != 1 {
39 t.Fatalf("gate asked %d times, want 1", len(g.asked))
40 }
41 // The gate sees the route that WOULD be minted: the activator's own gate
42 // reads App, Account, Environment off it.
43 if got := g.asked[0]; got.App != "tito" || got.Account != sqid || got.Kind != "instance" || got.Socket == "" {
44 t.Errorf("gate saw %+v, want the would-be minted socket-backed instance route", got)
45 }
46
47 // Retried next pass: once the box can provide the config, it mints.
48 delete(g.refuse, "acme.go.tito.io")
49 if err := Reconcile(ctx, s, reg, cfg); err != nil {
50 t.Fatal(err)
51 }
52 hasRoute(t, reg, "acme.go.tito.io", "not minted once the gate allowed it")
53}
54
55// A nil gate is today's behaviour exactly.
56func TestReconcileNilGateMintsAsBefore(t *testing.T) {
57 s, sqid := setup(t)
58 if err := CreateRecord(ctx, s, acmeRecord(sqid)); err != nil {
59 t.Fatal(err)
60 }
61 reg := regWith(t)
62 if err := Reconcile(ctx, s, reg, ReconcileConfig{Region: "ie", Pools: []string{"tito"}, DataDir: "/data"}); err != nil {
63 t.Fatal(err)
64 }
65 hasRoute(t, reg, "acme.go.tito.io", "nil gate stopped a mint")
66}
67
68// The gate is asked about every minted shape and decides itself which need
69// config; a refusal of one record leaves the others minted, and an existing
70// route is never unminted by a refusal (the wake gate is what protects it).
71func TestReconcileGateRefusesOnlyTheRecordItNames(t *testing.T) {
72 s, sqid := setup(t)
73 a := acmeRecord(sqid)
74 b := acmeRecord(sqid)
75 b.Host, b.Slug = "beta.go.tito.io", "beta"
76 for _, r := range []Record{a, b} {
77 if err := CreateRecord(ctx, s, r); err != nil {
78 t.Fatal(err)
79 }
80 }
81 reg := regWith(t)
82 g := &gateLog{refuse: map[string]error{"beta.go.tito.io": errors.New("cannot open")}}
83 cfg := ReconcileConfig{Region: "ie", Pools: []string{"tito"}, DataDir: "/data", ConfigGate: g.gate}
84 if err := Reconcile(ctx, s, reg, cfg); err != nil {
85 t.Fatal(err)
86 }
87 hasRoute(t, reg, "acme.go.tito.io", "an allowed record was held back by its neighbour's refusal")
88 noRoute(t, reg, "beta.go.tito.io", "a refused record was minted")
89
90 // Already minted, then refused: the route stays (removal is only for a
91 // retired record).
92 g.refuse["acme.go.tito.io"] = errors.New("now cannot open")
93 if err := Reconcile(ctx, s, reg, cfg); err != nil {
94 t.Fatal(err)
95 }
96 hasRoute(t, reg, "acme.go.tito.io", "a refusal unminted an existing route")
97}
98
99func TestSidecarReconcileDoesNotMintWhatTheGateRefuses(t *testing.T) {
100 f := sidecarSetup(t)
101 f.putManifest(t, "eleven", SidecarManifest{
102 Schema: 1, App: "eleven", Args: []string{"sidecar", "run"}, Channel: "stable"})
103 f.putRecord(t, SidecarRecord{Schema: 1, Name: "bot", App: "eleven", Channel: "stable"})
104 host := f.host("bot", "eleven")
105 g := &gateLog{refuse: map[string]error{host: errors.New("no key")}}
106
107 f.reconcile(t, ReconcileConfig{ConfigGate: g.gate})
108 noRoute(t, f.reg, host, "a refused sidecar was minted")
109 if len(g.asked) != 1 || g.asked[0].Kind != "sidecar" || g.asked[0].App != "eleven" {
110 t.Fatalf("gate asked %+v, want the would-be sidecar route", g.asked)
111 }
112
113 delete(g.refuse, host)
114 f.reconcile(t, ReconcileConfig{ConfigGate: g.gate})
115 hasRoute(t, f.reg, host, "sidecar not minted once the gate allowed it")
116}
117