package provision import ( "context" "errors" "testing" "github.com/carlosframework/platform/internal/registry" ) // Issue #213: a box mints an instance's record only if it can provide that // instance's config. ReconcileConfig.ConfigGate is the seam; the host agent // wires it to the same guard the activator asks before taking a lease. type gateLog struct { asked []registry.Route refuse map[string]error // host -> refusal } func (g *gateLog) gate(_ context.Context, rt registry.Route) error { g.asked = append(g.asked, rt) return g.refuse[rt.Host] } func TestReconcileDoesNotMintWhatTheGateRefuses(t *testing.T) { s, sqid := setup(t) if err := CreateRecord(ctx, s, acmeRecord(sqid)); err != nil { t.Fatal(err) } reg := regWith(t) g := &gateLog{refuse: map[string]error{"acme.go.tito.io": errors.New("no key")}} cfg := ReconcileConfig{Region: "ie", Pools: []string{"tito"}, DataDir: "/data", ConfigGate: g.gate} if err := Reconcile(ctx, s, reg, cfg); err != nil { t.Fatal(err) } noRoute(t, reg, "acme.go.tito.io", "a refused record was minted") if len(g.asked) != 1 { t.Fatalf("gate asked %d times, want 1", len(g.asked)) } // The gate sees the route that WOULD be minted: the activator's own gate // reads App, Account, Environment off it. if got := g.asked[0]; got.App != "tito" || got.Account != sqid || got.Kind != "instance" || got.Socket == "" { t.Errorf("gate saw %+v, want the would-be minted socket-backed instance route", got) } // Retried next pass: once the box can provide the config, it mints. delete(g.refuse, "acme.go.tito.io") if err := Reconcile(ctx, s, reg, cfg); err != nil { t.Fatal(err) } hasRoute(t, reg, "acme.go.tito.io", "not minted once the gate allowed it") } // A nil gate is today's behaviour exactly. func TestReconcileNilGateMintsAsBefore(t *testing.T) { s, sqid := setup(t) if err := CreateRecord(ctx, s, acmeRecord(sqid)); err != nil { t.Fatal(err) } reg := regWith(t) if err := Reconcile(ctx, s, reg, ReconcileConfig{Region: "ie", Pools: []string{"tito"}, DataDir: "/data"}); err != nil { t.Fatal(err) } hasRoute(t, reg, "acme.go.tito.io", "nil gate stopped a mint") } // The gate is asked about every minted shape and decides itself which need // config; a refusal of one record leaves the others minted, and an existing // route is never unminted by a refusal (the wake gate is what protects it). func TestReconcileGateRefusesOnlyTheRecordItNames(t *testing.T) { s, sqid := setup(t) a := acmeRecord(sqid) b := acmeRecord(sqid) b.Host, b.Slug = "beta.go.tito.io", "beta" for _, r := range []Record{a, b} { if err := CreateRecord(ctx, s, r); err != nil { t.Fatal(err) } } reg := regWith(t) g := &gateLog{refuse: map[string]error{"beta.go.tito.io": errors.New("cannot open")}} cfg := ReconcileConfig{Region: "ie", Pools: []string{"tito"}, DataDir: "/data", ConfigGate: g.gate} if err := Reconcile(ctx, s, reg, cfg); err != nil { t.Fatal(err) } hasRoute(t, reg, "acme.go.tito.io", "an allowed record was held back by its neighbour's refusal") noRoute(t, reg, "beta.go.tito.io", "a refused record was minted") // Already minted, then refused: the route stays (removal is only for a // retired record). g.refuse["acme.go.tito.io"] = errors.New("now cannot open") if err := Reconcile(ctx, s, reg, cfg); err != nil { t.Fatal(err) } hasRoute(t, reg, "acme.go.tito.io", "a refusal unminted an existing route") } func TestSidecarReconcileDoesNotMintWhatTheGateRefuses(t *testing.T) { f := sidecarSetup(t) f.putManifest(t, "eleven", SidecarManifest{ Schema: 1, App: "eleven", Args: []string{"sidecar", "run"}, Channel: "stable"}) f.putRecord(t, SidecarRecord{Schema: 1, Name: "bot", App: "eleven", Channel: "stable"}) host := f.host("bot", "eleven") g := &gateLog{refuse: map[string]error{host: errors.New("no key")}} f.reconcile(t, ReconcileConfig{ConfigGate: g.gate}) noRoute(t, f.reg, host, "a refused sidecar was minted") if len(g.asked) != 1 || g.asked[0].Kind != "sidecar" || g.asked[0].App != "eleven" { t.Fatalf("gate asked %+v, want the would-be sidecar route", g.asked) } delete(g.refuse, host) f.reconcile(t, ReconcileConfig{ConfigGate: g.gate}) hasRoute(t, f.reg, host, "sidecar not minted once the gate allowed it") }