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 "testing"
5
6 "github.com/carlosframework/platform/internal/registry"
7 "github.com/carlosframework/platform/internal/release"
8)
9
10// updatesFixture is reconcile_test.go's own setup (an account, the "tito"
11// app's instances manifest, a DirStore, a temp registry) with the three
12// moves these tests make: write the app's manifest with a given update
13// policy, run a pass, read a row.
14type updatesFixture struct {
15 t *testing.T
16 s *release.DirStore
17 sqid string
18 reg *registry.Registry
19 cfg ReconcileConfig
20}
21
22func newUpdatesFixture(t *testing.T) *updatesFixture {
23 t.Helper()
24 s, sqid := setup(t)
25 return &updatesFixture{t: t, s: s, sqid: sqid, reg: regWith(t),
26 cfg: ReconcileConfig{Region: "ie", Pools: []string{"tito"}, DataDir: "/data"}}
27}
28
29// putManifest replaces the app's instances manifest with one carrying p.
30func (fx *updatesFixture) putManifest(p *UpdatePolicy) {
31 fx.t.Helper()
32 m := Manifest{Schema: 1, App: "tito", Domain: "go.tito.io", Pool: "tito", Seed: []string{"seed"}, Updates: p}
33 putReplace(fx.t, fx.s, ManifestKey(fx.sqid, "tito"), mustJSON(fx.t, m))
34}
35
36// corruptManifest leaves unparseable bytes at the manifest's key: a read
37// failure that is not absence.
38func (fx *updatesFixture) corruptManifest() {
39 fx.t.Helper()
40 putReplace(fx.t, fx.s, ManifestKey(fx.sqid, "tito"), []byte("{"))
41}
42
43func (fx *updatesFixture) putRecord(rec Record) {
44 fx.t.Helper()
45 if err := CreateRecord(ctx, fx.s, rec); err != nil {
46 fx.t.Fatal(err)
47 }
48}
49
50func (fx *updatesFixture) run() {
51 fx.t.Helper()
52 if err := Reconcile(ctx, fx.s, fx.reg, fx.cfg); err != nil {
53 fx.t.Fatal(err)
54 }
55}
56
57func (fx *updatesFixture) row(host string) registry.Route { return *mustGet(fx.t, fx.reg, host) }
58
59func (fx *updatesFixture) hasRow(host string) bool {
60 fx.t.Helper()
61 rt, err := fx.reg.Get(host)
62 if err != nil {
63 fx.t.Fatal(err)
64 }
65 return rt != nil
66}
67
68func TestReconcileCarriesSettingsToTheRow(t *testing.T) {
69 fx := newUpdatesFixture(t)
70 fx.putManifest(&UpdatePolicy{DefaultUpdates: release.UpdatesManual})
71 fx.putRecord(acmeRecord(fx.sqid))
72 fx.run()
73 rt := fx.row("acme.go.tito.io")
74 u, err := release.ParseUpdateSettings(rt.UpdateSettings)
75 if err != nil || u.Updates != release.UpdatesManual {
76 t.Fatalf("minted row settings %q: %+v %v", rt.UpdateSettings, u, err)
77 }
78 // The default changes: the row follows on the next pass.
79 fx.putManifest(nil)
80 fx.run()
81 if rt = fx.row("acme.go.tito.io"); rt.UpdateSettings != "" {
82 t.Fatalf("after the policy was removed: %q", rt.UpdateSettings)
83 }
84}
85
86func TestAnUntouchedFleetConvergesNothing(t *testing.T) {
87 fx := newUpdatesFixture(t)
88 fx.putRecord(acmeRecord(fx.sqid))
89 fx.run()
90 if fx.row("acme.go.tito.io").UpdateSettings != "" {
91 t.Fatal("a record with no settings wrote the column")
92 }
93 fx.run()
94 if fx.row("acme.go.tito.io").UpdateSettings != "" {
95 t.Fatal("a second pass wrote the column")
96 }
97}
98
99// Review Focus 2.
100func TestUnreadablePolicyLeavesTheColumnAlone(t *testing.T) {
101 fx := newUpdatesFixture(t)
102 fx.putManifest(&UpdatePolicy{DefaultUpdates: release.UpdatesManual})
103 fx.putRecord(acmeRecord(fx.sqid))
104 fx.run()
105 fx.corruptManifest()
106 fx.run()
107 if u, _ := release.ParseUpdateSettings(fx.row("acme.go.tito.io").UpdateSettings); u.Updates != release.UpdatesManual {
108 t.Fatal("an unreadable policy reset the instance to auto")
109 }
110}
111
112// Astra I1 #7: an instance that inherits manual from its app, minted (on a
113// fresh box, or after a move) while the app's manifest cannot be read, must
114// not be minted as auto: it would adopt the head instead of recovering
115// last_executed. The mint waits until the policy reads; a genuinely absent
116// manifest is known (no policy) and mints at once.
117func TestMintWaitsForAReadablePolicy(t *testing.T) {
118 fx := newUpdatesFixture(t)
119 fx.putManifest(&UpdatePolicy{DefaultUpdates: release.UpdatesManual})
120 fx.corruptManifest()
121 fx.putRecord(acmeRecord(fx.sqid))
122 fx.run()
123 if fx.hasRow("acme.go.tito.io") {
124 t.Fatal("minted while the app's update policy was unreadable")
125 }
126 fx.putManifest(&UpdatePolicy{DefaultUpdates: release.UpdatesManual})
127 fx.run()
128 if u, _ := release.ParseUpdateSettings(fx.row("acme.go.tito.io").UpdateSettings); u.Updates != release.UpdatesManual {
129 t.Fatalf("minted with %q once the policy read", fx.row("acme.go.tito.io").UpdateSettings)
130 }
131}
132
133// Ruling R5: the moves held mint is a mint like any other. It waits while the
134// policy is unreadable, then lands held with the inherited settings.
135func TestHeldMintWaitsForAReadablePolicy(t *testing.T) {
136 fx := newUpdatesFixture(t)
137 fx.cfg.Region = "uk"
138 fx.cfg.Hold = func(Record) (string, string, bool) { return "landing", "mv_acme", true }
139 fx.putManifest(&UpdatePolicy{DefaultUpdates: release.UpdatesManual})
140 fx.corruptManifest()
141 fx.putRecord(movingRecord(fx.sqid))
142 fx.run()
143 if fx.hasRow("acme.go.tito.io") {
144 t.Fatal("held mint landed while the app's update policy was unreadable")
145 }
146 fx.putManifest(&UpdatePolicy{DefaultUpdates: release.UpdatesManual})
147 fx.run()
148 rt := fx.row("acme.go.tito.io")
149 if rt.Status != "landing" || rt.HoldOp != "mv_acme" {
150 t.Fatalf("row is %q held by %q, want landing by mv_acme", rt.Status, rt.HoldOp)
151 }
152 if u, _ := release.ParseUpdateSettings(rt.UpdateSettings); u.Updates != release.UpdatesManual {
153 t.Fatalf("held mint carries %q once the policy read", rt.UpdateSettings)
154 }
155}
156