carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package console
2
3import (
4 "context"
5 "fmt"
6 "net/http"
7 "strings"
8 "testing"
9 "time"
10
11 "github.com/carlosframework/platform/internal/release"
12 "github.com/carlosframework/platform/internal/status"
13)
14
15// layoutDowngradeBlocked judges observed state: a target at or above the
16// floor is always allowed; below it, every box must be fresh, carry a
17// layout report, and report no nested, fenced or conflict row.
18func TestLayoutDowngradeBlockedJudgesEveryBoxsReport(t *testing.T) {
19 fx := newConsoleFixture(t)
20 ctx := context.Background()
21 now := time.Now()
22 flat := &status.LayoutReport{Mode: "flat", Rows: map[string]status.RowLayout{"a.example.com": {Layout: "flat"}, "x.example.com": {Layout: "external"}}}
23
24 seedLayoutBox(t, fx.rel, "ie", now, flat)
25 if got := fx.srv.layoutDowngradeBlocked(ctx, "v0.19.0"); got != "" {
26 t.Fatalf("all flat: %q, want allowed", got)
27 }
28
29 seedLayoutBox(t, fx.rel, "uk", now, &status.LayoutReport{Mode: "nested", Rows: map[string]status.RowLayout{
30 "b.example.com": {Layout: "nested"}, "c.example.com": {Layout: "flat"}, "d.example.com": {Layout: "fenced", Reason: "mid-move"},
31 }})
32 got := fx.srv.layoutDowngradeBlocked(ctx, "v0.19.0")
33 for _, want := range []string{"v0.19.0 predates the instance layout", "uk: b.example.com is nested", "uk: d.example.com is fenced", "carlos system layout revert"} {
34 if !strings.Contains(got, want) {
35 t.Errorf("reason %q lacks %q", got, want)
36 }
37 }
38 if strings.Contains(got, "c.example.com") || strings.Contains(got, "ie") {
39 t.Errorf("reason %q names a flat row or a flat box", got)
40 }
41 // At or above the floor nothing is judged.
42 for _, v := range []string{"v0.25.0", "v0.26.1"} {
43 if got := fx.srv.layoutDowngradeBlocked(ctx, v); got != "" {
44 t.Errorf("%s: %q, want allowed", v, got)
45 }
46 }
47
48 // A box that reports no layout at all is unknown and blocks, unless
49 // the release it reports running parses below the floor: that build
50 // has no layout pass, so its silence is flat. Today's fleet, on
51 // v0.21.0 to v0.24.2, keeps its rollback (and its named update).
52 for _, v := range []string{"v0.18.0", "v0.20.0", "v0.21.0", "v0.24.2"} {
53 seedBox(t, fx, "au", "linux-arm64", "stable", v)
54 if got := fx.srv.layoutDowngradeBlocked(ctx, "v0.19.0"); strings.Contains(got, "au ") {
55 t.Errorf("a %s box with no layout field blocked: %q", v, got)
56 }
57 }
58 // Silence at or above the floor is unknown: that build reports a
59 // layout when it has an activator, and says "unmanaged" when it has
60 // none, so no report at all means a box the console cannot vouch for.
61 seedBox(t, fx, "au", "linux-arm64", "stable", "v0.25.0")
62 if got := fx.srv.layoutDowngradeBlocked(ctx, "v0.19.0"); !strings.Contains(got, "au reports no layout field") {
63 t.Errorf("no layout field on the floor release: %q", got)
64 }
65 seedBox(t, fx, "au", "linux-arm64", "stable", "dev")
66 if got := fx.srv.layoutDowngradeBlocked(ctx, "v0.19.0"); !strings.Contains(got, "au reports no layout field") {
67 t.Errorf("no layout field on an unplaceable release: %q", got)
68 }
69 // So does a stale one, whatever its last report said.
70 seedLayoutBox(t, fx.rel, "au", now.Add(-20*time.Minute), flat)
71 if got := fx.srv.layoutDowngradeBlocked(ctx, "v0.19.0"); !strings.Contains(got, "au is stale") {
72 t.Errorf("stale: %q", got)
73 }
74}
75
76// Both system doors call the guard before a pointer moves: the rollback
77// door against the release the pointer would go back to, the update door
78// against a named version. A non-nested fleet rolls back as before.
79func TestSystemDoorsRefuseAPreLayoutMoveWhileABoxIsNested(t *testing.T) {
80 fx := newConsoleFixture(t)
81 owner := cliToken(t, fx, "paul@keymail.dev", "code-layout-guard")
82 ctx := context.Background()
83 sg := release.RootSigner(fx.priv)
84 rel := func(v string) release.SystemRelease {
85 return release.SystemRelease{Version: v, Artifacts: map[string]release.SystemArtifact{"linux-arm64": {Key: release.SystemArtifactKey(v, "linux-arm64"), SHA256: strings.Repeat("a", 64)}}}
86 }
87 if _, err := release.PromoteSystem(ctx, fx.rel, sg, "router", "canary", rel("v0.19.0"), release.SystemPromoteOptions{}); err != nil {
88 t.Fatal(err)
89 }
90 time.Sleep(1100 * time.Millisecond)
91 if _, err := release.PromoteSystem(ctx, fx.rel, sg, "router", "canary", rel("v0.25.0"), release.SystemPromoteOptions{}); err != nil {
92 t.Fatal(err)
93 }
94 seedLayoutBox(t, fx.rel, "ie", time.Now(), &status.LayoutReport{Mode: "nested", Rows: map[string]status.RowLayout{"a.example.com": {Layout: "nested"}}})
95
96 st, out := postJSON(t, fx.ts.URL+"/api/cli/system/rollback", map[string]string{"channel": "canary"}, owner)
97 if st != http.StatusConflict || !strings.Contains(str(t, out, "error"), "ie: a.example.com is nested") {
98 t.Fatalf("rollback to v0.19.0 with a nested row = %d %v, want 409 naming the row", st, out)
99 }
100 p, _, err := release.GetSystemPointer(ctx, fx.rel, fx.pub, "router", "canary")
101 if err != nil || p.Current.Version != "v0.25.0" {
102 t.Fatalf("pointer moved anyway: %+v %v", p, err)
103 }
104 st, out = postJSON(t, fx.ts.URL+"/api/cli/system/update", map[string]any{"channel": "canary", "version": "v0.19.0"}, owner)
105 if st != http.StatusConflict {
106 t.Fatalf("update to v0.19.0 with a nested row = %d %v, want 409", st, out)
107 }
108 // A version at the floor or above is not the guard's business; the
109 // request goes on to the job (and fails there, which is not what this
110 // test is about). The fixture has no SystemFeed, so the job asks the
111 // vendor feed: the version must be one that can never be published, or
112 // the job succeeds once it is (v0.26.0 was, on 2026-10-13), moves the
113 // pointer, and the rollback below lands on the wrong version.
114 if st, _ := postJSON(t, fx.ts.URL+"/api/cli/system/update", map[string]any{"channel": "canary", "version": "v99.0.0"}, owner); st == http.StatusConflict {
115 t.Error("update to v99.0.0 was refused by the layout guard")
116 }
117
118 // Every row flat: the rollback goes through.
119 seedLayoutBox(t, fx.rel, "ie", time.Now(), &status.LayoutReport{Mode: "flat", Rows: map[string]status.RowLayout{"a.example.com": {Layout: "flat"}}})
120 time.Sleep(1100 * time.Millisecond)
121 if st, out := postJSON(t, fx.ts.URL+"/api/cli/system/rollback", map[string]string{"channel": "canary"}, owner); st != http.StatusOK || str(t, out, "version") != "v0.19.0" {
122 t.Fatalf("rollback with every row flat = %d %v", st, out)
123 }
124}
125
126// `layout migrate` is refused while any box in scope runs below the floor,
127// is stale, reports no release, or (for a named box) has never reported;
128// it goes through once every box in scope runs the floor release. A
129// revert is never judged this way.
130func TestSystemLayoutMigrateRefusedBelowTheFloor(t *testing.T) {
131 fx := newConsoleFixture(t)
132 owner := cliToken(t, fx, "paul@keymail.dev", "code-layout-floor")
133 seedBox(t, fx, "ie", "linux-arm64", "stable", "v0.24.2")
134 seedBox(t, fx, "uk", "linux-arm64", "stable", "v0.25.0")
135 st, out := postJSON(t, fx.ts.URL+"/api/cli/system/layout", map[string]string{"mode": "nested"}, owner)
136 if st != http.StatusConflict || !strings.Contains(str(t, out, "error"), "ie runs v0.24.2") || strings.Contains(str(t, out, "error"), "uk") {
137 t.Fatalf("fleet migrate with a box below the floor = %d %v, want 409 naming ie only", st, out)
138 }
139 if _, found, _ := release.LoadLayoutIntents(context.Background(), fx.rel); found {
140 t.Fatal("a refused migrate recorded an intent")
141 }
142 if st, out := postJSON(t, fx.ts.URL+"/api/cli/system/layout", map[string]string{"mode": "nested", "box": "ie"}, owner); st != http.StatusConflict {
143 t.Errorf("box migrate on the low box = %d %v, want 409", st, out)
144 }
145 if st, out := postJSON(t, fx.ts.URL+"/api/cli/system/layout", map[string]string{"mode": "nested", "box": "au"}, owner); st != http.StatusConflict || !strings.Contains(str(t, out, "error"), "box au has not reported") {
146 t.Errorf("box migrate on an unknown box = %d %v, want 409 saying it has not reported", st, out)
147 }
148 if st, out := postJSON(t, fx.ts.URL+"/api/cli/system/layout", map[string]string{"mode": "nested", "box": "uk"}, owner); st != http.StatusOK {
149 t.Errorf("box migrate on the floor-release box = %d %v, want 200", st, out)
150 }
151 // A revert is not judged by the floor: the low box can always go flat.
152 if st, out := postJSON(t, fx.ts.URL+"/api/cli/system/layout", map[string]string{"mode": "flat", "box": "ie"}, owner); st != http.StatusOK {
153 t.Errorf("revert on the low box = %d %v, want 200", st, out)
154 }
155 seedBox(t, fx, "ie", "linux-arm64", "stable", "v0.25.0")
156 if st, out := postJSON(t, fx.ts.URL+"/api/cli/system/layout", map[string]string{"mode": "nested"}, owner); st != http.StatusOK {
157 t.Errorf("fleet migrate with every box on the floor release = %d %v, want 200", st, out)
158 }
159 // An unplaceable release (a dev box on a sha) blocks while it reports
160 // no layout, and passes once its layout report is there: the report
161 // is proof the build carries the pass.
162 seedBox(t, fx, "ie", "linux-arm64", "stable", "abc1234")
163 if st, out := postJSON(t, fx.ts.URL+"/api/cli/system/layout", map[string]string{"mode": "nested"}, owner); st != http.StatusConflict || !strings.Contains(str(t, out, "error"), "ie runs abc1234, which cannot be placed against v0.25.0, and reports no layout") {
164 t.Errorf("fleet migrate with an unplaceable release and no layout = %d %v, want 409", st, out)
165 }
166 b := status.CollectBox(nil, status.Probes{Release: "abc1234", Platform: "linux-arm64", SystemChannel: "stable",
167 Layout: &status.LayoutReport{Mode: "none", Rows: map[string]status.RowLayout{"a.example.com": {Layout: "flat"}}}}, time.Now())
168 b.Label = "ie"
169 if err := status.WriteBox(context.Background(), fx.rel, b); err != nil {
170 t.Fatal(err)
171 }
172 if st, out := postJSON(t, fx.ts.URL+"/api/cli/system/layout", map[string]string{"mode": "nested"}, owner); st != http.StatusOK {
173 t.Errorf("fleet migrate with a sha box that reports its layout = %d %v, want 200", st, out)
174 }
175}
176
177// Today's fleet: every box on v0.21.0 to v0.24.2, none reporting a layout
178// field (those builds have no pass). Rollback and a named update to a
179// pre-floor release both go through, because nothing on such a box can be
180// nested. A box on a sha is a build that might carry the pass, so its
181// silence still blocks.
182func TestTodaysFleetWithNoLayoutFieldKeepsRollbackAndUpdate(t *testing.T) {
183 fx := newConsoleFixture(t)
184 owner := cliToken(t, fx, "paul@keymail.dev", "code-layout-todays-fleet")
185 ctx := context.Background()
186 for i, v := range []string{"v0.21.0", "v0.22.2", "v0.24.2", "v0.24.2"} {
187 seedBox(t, fx, fmt.Sprintf("box-%d", i), "linux-arm64", "stable", v)
188 }
189 sg := release.RootSigner(fx.priv)
190 rel := func(v string) release.SystemRelease {
191 return release.SystemRelease{Version: v, Artifacts: map[string]release.SystemArtifact{"linux-arm64": {Key: release.SystemArtifactKey(v, "linux-arm64"), SHA256: strings.Repeat("a", 64)}}}
192 }
193 if _, err := release.PromoteSystem(ctx, fx.rel, sg, "router", "stable", rel("v0.23.0"), release.SystemPromoteOptions{}); err != nil {
194 t.Fatal(err)
195 }
196 time.Sleep(1100 * time.Millisecond)
197 if _, err := release.PromoteSystem(ctx, fx.rel, sg, "router", "stable", rel("v0.24.2"), release.SystemPromoteOptions{}); err != nil {
198 t.Fatal(err)
199 }
200 if got := fx.srv.layoutDowngradeBlocked(ctx, "v0.23.0"); got != "" {
201 t.Fatalf("today's fleet blocked: %q", got)
202 }
203 time.Sleep(1100 * time.Millisecond)
204 if st, out := postJSON(t, fx.ts.URL+"/api/cli/system/rollback", map[string]string{"channel": "stable"}, owner); st != http.StatusOK || str(t, out, "version") != "v0.23.0" {
205 t.Fatalf("rollback on today's fleet = %d %v, want 200 onto v0.23.0", st, out)
206 }
207 // A named pre-floor update passes the guard (and goes on to the job,
208 // which fails on this fixture's absent feed; not what this is about).
209 if st, out := postJSON(t, fx.ts.URL+"/api/cli/system/update", map[string]any{"channel": "stable", "version": "v0.24.2"}, owner); st == http.StatusConflict {
210 t.Fatalf("named update on today's fleet refused by the layout guard: %v", out)
211 }
212 // A box whose release is a sha is a build that might carry the pass,
213 // so its silence blocks.
214 seedBox(t, fx, "box-9", "linux-arm64", "stable", "abc1234")
215 if got := fx.srv.layoutDowngradeBlocked(ctx, "v0.23.0"); !strings.Contains(got, "box-9 reports no layout field") {
216 t.Fatalf("a sha box with no layout field: %q", got)
217 }
218}
219
220// A box that is nested by default (no recorded intent) is judged exactly
221// like one under an explicit intent: its nested rows block a pre-floor
222// pointer move, and a box the default has not reached (flat rows, a
223// blocked reason) does not. The guards never read the intent, so the
224// default needs no rule of its own; this pins that.
225func TestLayoutGuardsTreatNestedByDefaultLikeAnIntent(t *testing.T) {
226 fx := newConsoleFixture(t)
227 ctx := context.Background()
228 now := time.Now()
229 seedLayoutBox(t, fx.rel, "ie", now, &status.LayoutReport{Mode: "nested", Default: true,
230 Rows: map[string]status.RowLayout{"a.example.com": {Layout: "nested"}}})
231 if got := fx.srv.layoutDowngradeBlocked(ctx, "v0.19.0"); !strings.Contains(got, "ie: a.example.com is nested") {
232 t.Fatalf("nested by default did not block: %q", got)
233 }
234 seedLayoutBox(t, fx.rel, "ie", now, &status.LayoutReport{Mode: "none", DefaultBlocked: "isolation not ready: probe fixture absent",
235 Rows: map[string]status.RowLayout{"a.example.com": {Layout: "flat"}}})
236 if got := fx.srv.layoutDowngradeBlocked(ctx, "v0.19.0"); got != "" {
237 t.Fatalf("a box the default has not reached blocked: %q", got)
238 }
239}
240