carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package main
2
3import (
4 "bytes"
5 "errors"
6 "os/exec"
7 "strings"
8 "testing"
9 "time"
10)
11
12func TestSystemVerbAcceptsTheComponentForm(t *testing.T) {
13 cases := []struct {
14 in []string
15 verb, comp string
16 rest int
17 }{
18 {[]string{"update", "--channel", "canary"}, "update", "router", 2},
19 {[]string{"router", "update"}, "update", "router", 0},
20 {[]string{"router", "status"}, "status", "router", 0},
21 {[]string{"apply", "--recover"}, "apply", "router", 1},
22 // The system units' floor gate: both file paths stay in rest.
23 {[]string{"floor-allows-prev", "/data/.moves/floor.min-release", "/opt/carlos/carlos.prev.allowed"}, "floor-allows-prev", "router", 2},
24 // antivirus is a family: its sub-verb stays in rest untouched.
25 {[]string{"antivirus", "status"}, "antivirus", "router", 1},
26 {[]string{"antivirus", "enable", "--console", "x"}, "antivirus", "router", 3},
27 {[]string{"antivirus"}, "antivirus", "router", 0},
28 {[]string{"files", "seed", "--host", "b.example"}, "files", "router", 3},
29 // layout is a family too: its sub-verb and flags stay in rest.
30 {[]string{"layout", "migrate", "--box", "x"}, "layout", "router", 3},
31 {[]string{"layout", "revert", "--force", "--yes"}, "layout", "router", 3},
32 {[]string{"layout", "status"}, "layout", "router", 1},
33 // claims is a family: its command and flags stay in rest.
34 {[]string{"claims", "inspect", "--bucket", "b"}, "claims", "router", 3},
35 // Region HA joining (spec 2 §5): host-env is a family; join and
36 // install-channel take no arguments.
37 {[]string{"host-env", "write", "--deployment", "flagship", "--region", "ie"}, "host-env", "router", 5},
38 {[]string{"install-channel"}, "install-channel", "router", 0},
39 {[]string{"join"}, "join", "router", 0},
40 {[]string{}, "", "router", 0},
41 {[]string{"router"}, "", "router", 0},
42 }
43 for _, c := range cases {
44 verb, rest, comp := systemVerb(c.in)
45 if verb != c.verb || comp != c.comp || len(rest) != c.rest {
46 t.Errorf("systemVerb(%v) = %q %v %q", c.in, verb, rest, comp)
47 }
48 }
49 if verb, _, _ := systemVerb([]string{"console", "update"}); verb != "" {
50 t.Errorf("an unknown component must not be a verb: got %q", verb)
51 }
52}
53
54// TestSystemUnknownVerbMessageNamesTheVerbNotTheComponent holds the one thing
55// the message must never do: report `carlos system router console` as an
56// unknown verb "router". systemVerb has already eaten the component by then,
57// so the message has to read what is LEFT, not what arrived.
58func TestSystemUnknownVerbMessageNamesTheVerbNotTheComponent(t *testing.T) {
59 cases := []struct {
60 in []string
61 want string
62 }{
63 {[]string{"console"}, `system: unknown verb "console"`},
64 {[]string{"router", "console"}, `system: unknown verb "console"`},
65 {[]string{"router"}, "system: no verb given"},
66 {[]string{}, ""},
67 }
68 for _, c := range cases {
69 _, rest, _ := systemVerb(c.in)
70 if got := systemUnknownVerbMessage(c.in, rest); got != c.want {
71 t.Errorf("carlos system %v: message = %q, want %q", c.in, got, c.want)
72 }
73 }
74}
75
76func TestPrintSystemStatusSaysWhatEachBoxIsDoing(t *testing.T) {
77 resp := systemStatusResponse{
78 Pointers: map[string]*systemPointerRow{"canary": {Version: "v2", Spread: "5m0s"}, "stable": nil},
79 Boxes: []systemBoxRow{
80 {Label: "ie", Platform: "linux-arm64", Channel: "canary", Release: "v2", Target: "v2"},
81 {Label: "us", Platform: "linux-arm64", Channel: "canary", Release: "v1", Target: "v2",
82 System: &systemStateRow{Version: "v2", Outcome: "rolled-back", Reason: "gate: carlos-edge is failed"}},
83 {Label: "au", Platform: "", Channel: "stable", Release: "v1", Target: "", Stale: true},
84 // Two separate facts, so two separate boxes: "au" is stale and
85 // nothing it last said can be trusted, including its blank
86 // platform; "old" is answering, on a build that predates the
87 // platform field. A box that is both must read as UNREACHABLE.
88 {Label: "old", Platform: "", Channel: "stable", Release: "v0", Target: "v1"},
89 },
90 }
91 var b strings.Builder
92 printSystemStatus(&b, resp, "")
93 out := b.String()
94 for _, want := range []string{"canary v2", "stable (no pointer)", "ie ", "current", "us ", "rolled-back", "gate: carlos-edge is failed", "au ", "UNREACHABLE", "pre-upgrade"} {
95 if !strings.Contains(out, want) {
96 t.Errorf("status output lacks %q:\n%s", want, out)
97 }
98 }
99 var only strings.Builder
100 printSystemStatus(&only, resp, "stable")
101 if strings.Contains(only.String(), "ie ") {
102 t.Error("--channel stable still listed a canary box")
103 }
104}
105
106// TestPrintSystemStatusShowsWhatItCouldNotRead is the other half: a pointer
107// this console cannot verify and a fleet it could not reach are the two
108// answers an operator must never read as "fine". Neither may render as the
109// thing it is not — a bad pointer as "never promoted", a short box list as
110// the whole fleet.
111func TestPrintSystemStatusShowsWhatItCouldNotRead(t *testing.T) {
112 resp := systemStatusResponse{
113 Pointers: map[string]*systemPointerRow{
114 "canary": {Error: "signature does not verify"},
115 "stable": {Version: "v1", Spread: "5m0s"},
116 },
117 Boxes: []systemBoxRow{{Label: "ie", Platform: "linux-arm64", Channel: "stable", Release: "v1", Target: "v1"}},
118 Partial: true,
119 }
120 var b strings.Builder
121 printSystemStatus(&b, resp, "")
122 out := b.String()
123 for _, want := range []string{"canary (pointer does not verify: signature does not verify)", "fleet boxes could not all be read"} {
124 if !strings.Contains(out, want) {
125 t.Errorf("status output lacks %q:\n%s", want, out)
126 }
127 }
128 if strings.Contains(out, "no pointer") {
129 t.Errorf("an unverifiable pointer must not read as an absent one:\n%s", out)
130 }
131
132 var quiet strings.Builder
133 printSystemStatus(&quiet, systemStatusResponse{Boxes: resp.Boxes}, "")
134 if strings.Contains(quiet.String(), "could not all be read") {
135 t.Errorf("a complete fleet warned anyway:\n%s", quiet.String())
136 }
137}
138
139func TestSystemLadderWarning(t *testing.T) {
140 onCanary := map[string]*systemPointerRow{"canary": {Version: "v9"}}
141 never := map[string]*systemPointerRow{}
142 older := map[string]*systemPointerRow{"canary": {Version: "v8"}}
143 const want = "warning: v9 has not been on canary; promoting straight to stable"
144 for _, c := range []struct {
145 name string
146 pointers map[string]*systemPointerRow
147 channel, version string
148 warn string
149 }{
150 {"the version soaked on canary", onCanary, "stable", "v9", ""},
151 {"canary has never been promoted", never, "stable", "v9", want},
152 {"canary is on a different version", older, "stable", "v9", want},
153 // Promoting TO canary is the rung below; nothing to skip.
154 {"promoting to canary", never, "canary", "v9", ""},
155 // No --version: the tag is "the latest public release", which only
156 // the server resolves, so there is nothing to compare and a warning
157 // would be a guess.
158 {"the latest public release", never, "stable", "", ""},
159 } {
160 t.Run(c.name, func(t *testing.T) {
161 if got := systemLadderWarning(c.pointers, c.channel, c.version); got != c.warn {
162 t.Errorf("systemLadderWarning = %q, want %q", got, c.warn)
163 }
164 })
165 }
166 // A nil map is what an unreadable status body leaves behind: it warns
167 // rather than panicking.
168 if got := systemLadderWarning(nil, "stable", "v9"); got != want {
169 t.Errorf("nil pointers = %q, want %q", got, want)
170 }
171}
172
173// Wiring and policy are two facts, and the output keeps them apart. An
174// operator debugging "nothing is scanning" needs to know which half is
175// missing; one "healthy" line would send half of them to the wrong place.
176func TestPrintSystemAntivirusSeparatesWiringFromPolicy(t *testing.T) {
177 fresh := "ClamAV 1.4.1/27500/" + time.Now().Format(time.ANSIC)
178
179 // Not wired at all.
180 var b bytes.Buffer
181 printSystemAntivirus(&b, apiSystemAntivirusReply{})
182 if out := b.String(); !strings.Contains(out, "no engine is wired") ||
183 !strings.Contains(out, "CARLOS_CLAMD_ADDR") {
184 t.Fatalf("unwired output does not name the wiring:\n%s", out)
185 }
186
187 // Wired, switched off. Names the COMMAND, not the variable — this half is
188 // restartless and an operator sent to a unit file would be sent wrong.
189 var b2 bytes.Buffer
190 printSystemAntivirus(&b2, apiSystemAntivirusReply{
191 Wired: true, EngineWired: "clamav", Signature: fresh, Enabled: false,
192 })
193 out2 := b2.String()
194 if !strings.Contains(out2, "scanning is OFF") ||
195 !strings.Contains(out2, "carlos system antivirus enable") {
196 t.Fatalf("disabled output does not name the command:\n%s", out2)
197 }
198 if strings.Contains(out2, "CARLOS_CLAMD_ADDR") {
199 t.Fatalf("a wired-but-off deployment was told to fix its wiring:\n%s", out2)
200 }
201
202 // Wired and on.
203 var b3 bytes.Buffer
204 printSystemAntivirus(&b3, apiSystemAntivirusReply{
205 Wired: true, EngineWired: "clamav", Signature: fresh,
206 Enabled: true, By: "op@example.com", At: "2026-09-02T08:00:00Z",
207 })
208 out3 := b3.String()
209 if !strings.Contains(out3, "scanning is ON") || !strings.Contains(out3, "op@example.com") {
210 t.Fatalf("enabled output:\n%s", out3)
211 }
212 // And it must not imply anything is being scanned yet.
213 if !strings.Contains(out3, "scans nothing") {
214 t.Fatalf("enabled output implies coverage it does not have:\n%s", out3)
215 }
216}
217
218// An engine that is wired but not answering is its own state, distinct from
219// both "no engine" and "switched off".
220func TestPrintSystemAntivirusReportsUnreachableEngine(t *testing.T) {
221 var b bytes.Buffer
222 printSystemAntivirus(&b, apiSystemAntivirusReply{
223 Wired: true, EngineError: "dial tcp scanner:3310: connection refused", Enabled: true,
224 })
225 out := b.String()
226 if !strings.Contains(out, "not answering") {
227 t.Fatalf("an unreachable engine was not reported:\n%s", out)
228 }
229 // The policy half still reports, because it is still true.
230 if !strings.Contains(out, "scanning is ON") {
231 t.Fatalf("an engine failure hid the recorded policy:\n%s", out)
232 }
233}
234
235// A record the console could not read is not "off". Reporting it as off would
236// be a guess, on the one command an operator runs to find out what is true.
237func TestPrintSystemAntivirusReportsUnreadableRecord(t *testing.T) {
238 var b bytes.Buffer
239 printSystemAntivirus(&b, apiSystemAntivirusReply{
240 Wired: true, EngineWired: "clamav", RecordError: "schema 99 is not 1",
241 })
242 out := b.String()
243 if !strings.Contains(out, "could not be read") {
244 t.Fatalf("an unreadable record was not reported:\n%s", out)
245 }
246 if strings.Contains(out, "scanning is OFF") {
247 t.Fatalf("an unreadable record was reported as off:\n%s", out)
248 }
249}
250
251// Stale definitions are called out here too, not only in store scan status: a
252// deployment-wide view is where an operator would notice freshclam had died.
253func TestPrintSystemAntivirusWarnsOnStaleSignatures(t *testing.T) {
254 old := "ClamAV 1.4.1/27000/" + time.Now().Add(-90*24*time.Hour).Format(time.ANSIC)
255 var b bytes.Buffer
256 printSystemAntivirus(&b, apiSystemAntivirusReply{
257 Wired: true, EngineWired: "clamav", Signature: old, Enabled: true,
258 MaxSignatureAgeSeconds: int64((168 * time.Hour) / time.Second),
259 })
260 if !strings.Contains(b.String(), "days old") {
261 t.Fatalf("stale definitions were not called out:\n%s", b.String())
262 }
263}
264
265func TestPrintSystemLayoutStatus(t *testing.T) {
266 var b strings.Builder
267 printSystemLayoutStatus(&b, systemLayoutResponse{Mode: "none"})
268 for _, want := range []string{"no layout intent recorded; boxes use the nested layout by default once their isolation is ready", "CARLOS_LAYOUT_DEFAULT=flat", "carlos system layout revert", "carlos status --layout"} {
269 if !strings.Contains(b.String(), want) {
270 t.Errorf("none lacks %q: %q", want, b.String())
271 }
272 }
273 b.Reset()
274 printSystemLayoutStatus(&b, systemLayoutResponse{Mode: "nested", Box: "kass-1", RequestedAt: "2026-10-06T10:00:00Z", Force: true})
275 for _, want := range []string{"intent: nested for box kass-1", "2026-10-06T10:00:00Z, force", "carlos status --layout"} {
276 if !strings.Contains(b.String(), want) {
277 t.Errorf("output lacks %q:\n%s", want, b.String())
278 }
279 }
280 b.Reset()
281 printSystemLayoutStatus(&b, systemLayoutResponse{Mode: "flat", RequestedAt: "2026-10-06T10:00:00Z"})
282 if !strings.Contains(b.String(), "intent: flat for every box (recorded 2026-10-06T10:00:00Z)") {
283 t.Errorf("fleet: %q", b.String())
284 }
285}
286
287func TestSystemFilesSeedCommandEntryRefusesBeforeEffects(t *testing.T) {
288 bin := buildCarlos(t)
289 for _, flags := range []string{"", "1"} {
290 cmd := exec.Command(bin, "system", "files", "seed", "--host", "seed.example")
291 cmd.Dir = t.TempDir()
292 cmd.Env = carlosTestEnv(t, "CARLOS_HOST_CLAIM="+flags, "CARLOS_HOST_CLAIM_ORIGINS="+flags, "CARLOS_MOVES_PHASE1=", "CARLOS_DEPLOYMENT_BUCKET=", "CARLOS_DEPLOYMENT_DIR="+t.TempDir(), "AWS_EC2_METADATA_DISABLED=true")
293 out, err := cmd.CombinedOutput()
294 var exit *exec.ExitError
295 if !errors.As(err, &exit) || exit.ExitCode() != 1 || !strings.Contains(string(out), "Files seed failed:") {
296 t.Fatalf("dispatch: %v %s", err, out)
297 }
298 }
299}
300
301// Issue #316: status says when bootstrap/carlos is not stable's build, and is
302// quiet when it is, when there is nothing to compare, and under --channel
303// canary.
304func TestPrintSystemStatusSaysWhenBootstrapLags(t *testing.T) {
305 stable := map[string]*systemPointerRow{"stable": {Version: "v0.28.0"}}
306 behind := "bootstrap/carlos is not stable's build (v0.28.0), so a new box would boot a different one. Copy bootstrap/router/v0.28.0/carlos-linux-arm64 over it."
307 for _, tc := range []struct {
308 name, channel, want string
309 row *systemBootstrapRow
310 }{
311 {"behind", "", behind, &systemBootstrapRow{Version: "v0.28.0", Key: "bootstrap/router/v0.28.0/carlos-linux-arm64", State: "behind"}},
312 {"behind, stable only", "stable", behind, &systemBootstrapRow{Version: "v0.28.0", Key: "bootstrap/router/v0.28.0/carlos-linux-arm64", State: "behind"}},
313 {"unreadable", "", "bootstrap/carlos could not be read, so this can't say what a new box would boot: access denied",
314 &systemBootstrapRow{Version: "v0.28.0", State: "unreadable", Error: "access denied"}},
315 {"current", "", "", &systemBootstrapRow{Version: "v0.28.0", State: "current"}},
316 {"nothing to compare", "", "", nil},
317 {"canary only", "canary", "", &systemBootstrapRow{Version: "v0.28.0", State: "behind"}},
318 } {
319 t.Run(tc.name, func(t *testing.T) {
320 var b strings.Builder
321 printSystemStatus(&b, systemStatusResponse{Pointers: stable, Bootstrap: tc.row}, tc.channel)
322 out := b.String()
323 if tc.want == "" {
324 if strings.Contains(out, "bootstrap/carlos") {
325 t.Fatalf("output mentions bootstrap/carlos:\n%s", out)
326 }
327 return
328 }
329 if !strings.Contains(out, tc.want) {
330 t.Fatalf("output lacks %q:\n%s", tc.want, out)
331 }
332 })
333 }
334}
335