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 "reflect"
6 "testing"
7
8 "github.com/carlosframework/platform/internal/registry"
9)
10
11// settings-5 task 8 (ruling R26): a hostname that moves to another tenant on
12// one box meets the old tenant's consumed file, keyed by hostname only. A
13// file tagged for another tenant is absent to root's decision and to the
14// next file root writes; an untagged file from before the tag is read as
15// today, so its completions and resolutions still count for the tenant that
16// has the route.
17
18// writeForeignConsumed is tenant A's record for host: a manual decision, a
19// resolved request, a failed completion watermark and a baking head.
20func writeForeignConsumed(t *testing.T, opt, host string) {
21 t.Helper()
22 a := Consumed{
23 Host: host, Account: "cdf", App: "km",
24 Resolved: &ResolvedRequest{ID: 7, Target: "v0"}, Completed: 6, CompletedFailed: true,
25 Desired: "v0", Reason: string(ReasonManual), Detail: "v0", Baking: "v9",
26 Settings: SettingsDigest(UpdateSettings{Updates: UpdatesManual, Channel: "canary/x"}.Column()),
27 }
28 if err := WriteConsumed(opt, a); err != nil {
29 t.Fatal(err)
30 }
31}
32
33func TestAForeignConsumedFileIsAbsentForTheNewTenant(t *testing.T) {
34 s := NewDirStore(t.TempDir())
35 ctx := context.Background()
36 priv, pub := testKeys(t)
37 for _, ta := range [][2]string{{"cdf", "km"}, {"dee", "blog"}} {
38 shipRelease(t, s, ta[0], ta[1], "v1", adoptPlatform, []byte("v1"))
39 if err := Promote(ctx, s, priv, ta[0], ta[1], "canary/x", "v1"); err != nil {
40 t.Fatal(err)
41 }
42 }
43
44 t.Run("default settings: a fresh rule 4 decision with B's identity", func(t *testing.T) {
45 opt, reg := t.TempDir(), testRegistry(t)
46 const host = "a.example.com"
47 if err := reg.Upsert(registry.Route{Host: host, Kind: "instance", Account: "dee", App: "blog", Channel: "canary/x", Hibernate: true}); err != nil {
48 t.Fatal(err)
49 }
50 writeForeignConsumed(t, opt, host)
51 passWithSettings(t, reg, s, pub, opt)
52 if v, _ := LiveVersion(opt, host); v != "v1" {
53 t.Fatalf("live %q, want B's head v1 (today's adoption)", v)
54 }
55 c, err := ReadConsumed(opt, host)
56 if err != nil || c == nil {
57 t.Fatalf("consumed = %+v, %v", c, err)
58 }
59 want := Consumed{Schema: 1, Host: host, Account: "dee", App: "blog",
60 Desired: "v1", Reason: string(ReasonAdopt), Detail: "v1", Settings: SettingsDigest(""), At: c.At}
61 if !reflect.DeepEqual(*c, want) {
62 t.Fatalf("B's consumed file:\n got %+v\nwant %+v (B's identity, none of A's bookkeeping)", *c, want)
63 }
64 })
65
66 t.Run("an install request: A's watermark never swallows B's id", func(t *testing.T) {
67 opt, reg := t.TempDir(), testRegistry(t)
68 const host = "b.example.com"
69 if err := reg.Upsert(registry.Route{Host: host, Kind: "instance", Account: "dee", App: "blog", Channel: "canary/x", Hibernate: true}); err != nil {
70 t.Fatal(err)
71 }
72 if err := reg.SetUpdateSettings(host, UpdateSettings{Updates: UpdatesManual, Channel: "canary/x", InstallRequest: 3}.Column()); err != nil {
73 t.Fatal(err)
74 }
75 if err := reg.SetBuildState(host, BuildStateCopy{Known: true, Replica: true, State: &BuildState{LastExecuted: "v0"}}.Column()); err != nil {
76 t.Fatal(err)
77 }
78 writeForeignConsumed(t, opt, host)
79 passWithSettings(t, reg, s, pub, opt)
80 if v, _ := LiveVersion(opt, host); v != "v1" {
81 t.Fatalf("live %q, want v1: B's request 3 resolved onto B's head", v)
82 }
83 c, _ := ReadConsumed(opt, host)
84 if c == nil || c.Resolved == nil || *c.Resolved != (ResolvedRequest{ID: 3, Target: "v1"}) ||
85 c.Completed != 0 || c.CompletedFailed || c.Account != "dee" || c.App != "blog" {
86 t.Fatalf("B's consumed file = %+v (resolved %+v)", c, c.Resolved)
87 }
88 })
89}
90
91// An untagged file (root wrote it before ruling R22) is the route's own
92// record: read as today, it keeps a failed completion and a resolution that
93// only it holds, and root's next write tags it with the route's tenant.
94func TestAnUntaggedConsumedFileKeepsItsBookkeepingForTheSameTenant(t *testing.T) {
95 s := NewDirStore(t.TempDir())
96 ctx := context.Background()
97 priv, pub := testKeys(t)
98 for _, v := range []string{"v1", "v2", "v3"} {
99 shipRelease(t, s, "bab", "km", v, adoptPlatform, []byte(v))
100 if err := Promote(ctx, s, priv, "bab", "km", "canary/x", v); err != nil {
101 t.Fatal(err)
102 }
103 }
104 manual5 := UpdateSettings{Updates: UpdatesManual, Channel: "canary/x", InstallRequest: 5}
105 state := BuildStateCopy{Known: true, Replica: true, State: &BuildState{LastExecuted: "v1", Completed: 4}}
106
107 t.Run("a failed completion only the file holds is not re-resolved", func(t *testing.T) {
108 opt, reg := t.TempDir(), testRegistry(t)
109 const host = "a.example.com"
110 settingsRoute(t, reg, host, "km", "canary/x", manual5, state)
111 legacy := Consumed{Host: host, Resolved: &ResolvedRequest{ID: 5, Target: "vbad"}, Completed: 5, CompletedFailed: true,
112 Desired: "v1", Reason: string(ReasonManual), Detail: "v1"}
113 if err := WriteConsumed(opt, legacy); err != nil {
114 t.Fatal(err)
115 }
116 passWithSettings(t, reg, s, pub, opt)
117 if v, _ := LiveVersion(opt, host); v != "v1" {
118 t.Fatalf("live %q, want v1: request 5 is completed (failed), the instance is manual", v)
119 }
120 c, _ := ReadConsumed(opt, host)
121 if c == nil || c.Completed != 5 || !c.CompletedFailed || c.Resolved == nil || *c.Resolved != (ResolvedRequest{ID: 5, Target: "vbad"}) {
122 t.Fatalf("consumed = %+v (resolved %+v): the file's completion was lost", c, c.Resolved)
123 }
124 if c.Account != OpsAccount || c.App != "km" || c.Reason != string(ReasonManual) {
125 t.Fatalf("consumed names %q/%q, reason %q", c.Account, c.App, c.Reason)
126 }
127 })
128
129 t.Run("a resolution only the file holds is followed", func(t *testing.T) {
130 opt, reg := t.TempDir(), testRegistry(t)
131 const host = "b.example.com"
132 settingsRoute(t, reg, host, "km", "canary/x", manual5, state)
133 legacy := Consumed{Host: host, Resolved: &ResolvedRequest{ID: 5, Target: "v2"}, Completed: 4,
134 Desired: "v2", Reason: string(ReasonRequest), Detail: "v2"}
135 if err := WriteConsumed(opt, legacy); err != nil {
136 t.Fatal(err)
137 }
138 passWithSettings(t, reg, s, pub, opt)
139 if v, _ := LiveVersion(opt, host); v != "v2" {
140 t.Fatalf("live %q, want v2: request 5 was resolved to v2, not re-resolved onto the head", v)
141 }
142 c, _ := ReadConsumed(opt, host)
143 if c == nil || c.Resolved == nil || *c.Resolved != (ResolvedRequest{ID: 5, Target: "v2"}) {
144 t.Fatalf("consumed = %+v: the resolution was lost", c)
145 }
146 })
147}
148
149// NextConsumed itself never copies another tenant's bookkeeping, whatever
150// its caller read: a tagged foreign file starts the next record empty, and
151// an untagged file is carried as today.
152func TestNextConsumedStartsEmptyOverAForeignFile(t *testing.T) {
153 const host = "a.example.com"
154 route := registry.Route{Host: host, Kind: "instance", Account: "dee", App: "blog"}
155 known := BuildStateCopy{Known: true, Replica: true}
156 dec := TargetDecision{Version: "v1", Reason: ReasonAdopt, Detail: "v1"}
157 bookkeeping := Consumed{Host: host, Resolved: &ResolvedRequest{ID: 7, Target: "v0"}, Completed: 6, CompletedFailed: true, Baking: "v9"}
158
159 foreign := bookkeeping
160 foreign.Account, foreign.App = "cdf", "km"
161 got := NextConsumed(TargetInput{Route: route, Build: known, Consumed: &foreign}, dec)
162 want := Consumed{Host: host, Account: "dee", App: "blog", Desired: "v1", Reason: "adopt", Detail: "v1", Settings: SettingsDigest("")}
163 if !reflect.DeepEqual(got, want) {
164 t.Fatalf("over A's file:\n got %+v\nwant %+v", got, want)
165 }
166
167 sameAccount := bookkeeping
168 sameAccount.Account, sameAccount.App = "dee", "km"
169 if got := NextConsumed(TargetInput{Route: route, Build: known, Consumed: &sameAccount}, dec); got.Completed != 0 || got.Resolved != nil {
170 t.Fatalf("another app on the same account passed its bookkeeping on: %+v", got)
171 }
172
173 legacy := bookkeeping
174 got = NextConsumed(TargetInput{Route: route, Build: known, Consumed: &legacy}, dec)
175 if got.Completed != 6 || !got.CompletedFailed || got.Resolved == nil || *got.Resolved != *legacy.Resolved {
176 t.Fatalf("an untagged file lost its bookkeeping: %+v", got)
177 }
178 if got.Account != "dee" || got.App != "blog" {
179 t.Fatalf("the untagged file was not tagged with the route's tenant: %q/%q", got.Account, got.App)
180 }
181}
182
183// ForeignTo is the rule every reader shares: tagged, and for another tenant.
184func TestConsumedForeignTo(t *testing.T) {
185 rt := registry.Route{Host: "a.example.com", Account: "dee", App: "blog"}
186 cases := []struct {
187 name string
188 c *Consumed
189 want bool
190 }{
191 {"no file", nil, false},
192 {"untagged", &Consumed{}, false},
193 {"the route's tenant", &Consumed{Account: "dee", App: "blog"}, false},
194 {"another app", &Consumed{Account: "dee", App: "km"}, true},
195 {"another account", &Consumed{Account: "cdf", App: "blog"}, true},
196 {"half tagged, another account", &Consumed{Account: "cdf"}, true},
197 {"half tagged, the route's app", &Consumed{App: "blog"}, true},
198 }
199 for _, c := range cases {
200 if got := c.c.ForeignTo(rt); got != c.want {
201 t.Errorf("%s: ForeignTo = %v, want %v", c.name, got, c.want)
202 }
203 }
204 // A route with no valid account is the ops account's, as root tags it.
205 ops := registry.Route{Host: "a.example.com", App: "km"}
206 if (&Consumed{Account: OpsAccount, App: "km"}).ForeignTo(ops) {
207 t.Error("the ops account's own file is foreign to its route")
208 }
209}
210
211// The registry's Upsert compares tenants by registry.EffectiveAccount (Astra
212// r4 finding 2). It must be RouteAccount on the account, or the registry
213// keeps one tenant's settings columns for a route root reads as another's,
214// or drops them for one root reads as the same.
215func TestRegistryEffectiveAccountIsRouteAccount(t *testing.T) {
216 for _, a := range []string{"", OpsAccount, "bes", "cdf", "ab", "ABC", "ab1", "abcdefghijklmnopq", "abcdefghijklmnop"} {
217 if got, want := registry.EffectiveAccount(a), RouteAccount(registry.Route{Account: a}); got != want {
218 t.Fatalf("account %q: registry.EffectiveAccount %q, RouteAccount %q", a, got, want)
219 }
220 }
221}
222