carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "errors"
6 "net"
7 "os"
8 "path/filepath"
9 "strings"
10 "testing"
11 "time"
12
13 "github.com/carlosframework/platform/internal/registry"
14 "github.com/carlosframework/platform/internal/release"
15)
16
17func TestParseShowReadsActiveEnterTimestamp(t *testing.T) {
18 p := parseShow("ActiveState=active\nActiveEnterTimestamp=Mon 2026-10-06 09:30:30 UTC\n")
19 if want := time.Date(2026, 10, 6, 9, 30, 30, 0, time.UTC); !p.ActiveEnterTimestamp.Equal(want) {
20 t.Fatalf("ActiveEnterTimestamp = %v, want %v", p.ActiveEnterTimestamp, want)
21 }
22 if !parseShow("ActiveEnterTimestamp=\n").ActiveEnterTimestamp.IsZero() {
23 t.Fatal("an empty timestamp is not zero")
24 }
25 if !parseShow("ActiveEnterTimestamp=n/a\n").ActiveEnterTimestamp.IsZero() {
26 t.Fatal("n/a is not zero")
27 }
28}
29
30const observedHost = "platform.example.com"
31
32// observeFixture: the route's live link names versions/v1/console, and by
33// default the unit runs that very file, bound, active for 20 minutes, ready.
34func observeFixture(t *testing.T) (proofObserver, registry.Route, string, time.Time) {
35 t.Helper()
36 opt := t.TempDir()
37 liveTo(t, opt, observedHost, "v1")
38 staged := filepath.Join(opt, "versions", "v1", "console")
39 now := time.Date(2026, 10, 6, 10, 0, 0, 0, time.UTC)
40 o := proofObserver{
41 sys: &fakeUnits{shows: []unitProps{{ActiveState: "active", InvocationID: "i1", MainPID: 7,
42 ExecPath: release.LiveLink(opt, observedHost), ActiveEnterTimestamp: now.Add(-20 * time.Minute)}}},
43 procExe: func(int) string { return staged },
44 optDir: opt,
45 now: func() time.Time { return now },
46 ready: func(registry.Route) func() error { return func() error { return nil } },
47 }
48 rt := registry.Route{Host: observedHost, App: "console", Unit: "console.service"}
49 o.resolve = proofFixtureResolver(rt)
50 return o, rt, staged, now
51}
52
53func TestProofObserveAcceptsABakedBuild(t *testing.T) {
54 o, rt, staged, now := observeFixture(t)
55 b, err := o.observe(rt, 15*time.Minute)
56 if err != nil {
57 t.Fatalf("observe: %v", err)
58 }
59 want, _ := release.FileSHA256(staged)
60 if b.SHA256 != want || b.Version != "v1" || !b.ActiveSince.Equal(now.Add(-20*time.Minute)) {
61 t.Fatalf("build = %+v", b)
62 }
63}
64
65// Spec §7: the mint conditions, each false in turn.
66func TestProofObserveConditionsEachFalse(t *testing.T) {
67 for name, c := range map[string]struct {
68 edit func(t *testing.T, o *proofObserver, now time.Time)
69 want string
70 }{
71 "binding": {func(t *testing.T, o *proofObserver, now time.Time) {
72 o.sys.(*fakeUnits).shows[0].ExecPath = release.LiveLink(o.optDir, "console.example.com")
73 }, "binding"},
74 "not active": {func(t *testing.T, o *proofObserver, now time.Time) {
75 o.sys.(*fakeUnits).shows[0].ActiveState = "failed"
76 }, "failed"},
77 "too young": {func(t *testing.T, o *proofObserver, now time.Time) {
78 o.sys.(*fakeUnits).shows[0].ActiveEnterTimestamp = now.Add(-5 * time.Minute)
79 }, "needs 15m"},
80 "restarted during the check": {func(t *testing.T, o *proofObserver, now time.Time) {
81 f := o.sys.(*fakeUnits)
82 second := f.shows[0]
83 second.InvocationID = "i2"
84 f.shows = append(f.shows, second)
85 }, "restarted"},
86 "exe and live link differ": {func(t *testing.T, o *proofObserver, now time.Time) {
87 other := filepath.Join(t.TempDir(), "console")
88 if err := os.WriteFile(other, []byte("a different build"), 0o755); err != nil {
89 t.Fatal(err)
90 }
91 o.procExe = func(int) string { return other }
92 }, "is not the live link's build"},
93 "not ready": {func(t *testing.T, o *proofObserver, now time.Time) {
94 o.ready = func(registry.Route) func() error { return func() error { return errors.New("status 503") } }
95 }, "not ready"},
96 } {
97 t.Run(name, func(t *testing.T) {
98 o, rt, _, now := observeFixture(t)
99 c.edit(t, &o, now)
100 if _, err := o.observe(rt, 15*time.Minute); err == nil || !strings.Contains(err.Error(), c.want) {
101 t.Fatalf("observe = %v, want an error containing %q", err, c.want)
102 }
103 })
104 }
105}
106
107// D1's settle passing is the hook that records a healthy build, hashed from
108// the executable that passed.
109func TestSettledRestartRecordsTheRunningExe(t *testing.T) {
110 opt, bake := t.TempDir(), t.TempDir()
111 liveTo(t, opt, observedHost, "new")
112 staged, err := filepath.EvalSymlinks(filepath.Join(opt, "versions", "new", "console"))
113 if err != nil {
114 t.Fatal(err)
115 }
116 ln, err := net.Listen("tcp", "127.0.0.1:0")
117 if err != nil {
118 t.Fatal(err)
119 }
120 t.Cleanup(func() { ln.Close() })
121 rt := registry.Route{Host: observedHost, App: "console", Unit: "console.service", Addr: ln.Addr().String()}
122 f := &fakeUnits{shows: []unitProps{
123 {ActiveState: "active", InvocationID: "old", ExecPath: filepath.Join(opt, "live", observedHost)},
124 {ActiveState: "active", InvocationID: "new", MainPID: 7, ExecPath: filepath.Join(opt, "live", observedHost)},
125 }, exe: map[int]string{7: staged}}
126 settled := proofFixtureSettled(f, func(int) string { return staged }, opt, bake, time.Now, rt)
127 if err := agentCheckedUnitRestart(f, opt, func(string) string { return "" }, settled)(rt); err != nil {
128 t.Fatalf("restart: %v", err)
129 }
130 sha, _ := release.FileSHA256(staged)
131 h := release.ReadHealthy(bake, "bab", "console")
132 if len(h) != 1 || h[0].SHA256 != sha || h[0].How != "settle" || h[0].Version != "new" {
133 t.Fatalf("history = %+v, want the settled build", h)
134 }
135}
136
137// Ruling 8: the settled hook records only what observe would. Its own Show
138// must name the live link as the unit's executable, and the bytes it hashes
139// must be the live link's; otherwise a process restarted, or not yet exec'd,
140// in the moment after the settle could enter the history.
141func TestRecordSettledRecordsNothingForAnotherExe(t *testing.T) {
142 for name, c := range map[string]struct {
143 execPath func(opt string) string
144 procExe func(t *testing.T, opt string) string
145 }{
146 "Show names another executable": {
147 execPath: func(opt string) string { return release.LiveLink(opt, "console.example.com") },
148 procExe: func(t *testing.T, opt string) string { return filepath.Join(opt, "versions", "new", "console") },
149 },
150 "the running bytes are not the live link's": {
151 execPath: func(opt string) string { return release.LiveLink(opt, observedHost) },
152 procExe: func(t *testing.T, opt string) string {
153 other := filepath.Join(t.TempDir(), "console")
154 if err := os.WriteFile(other, []byte("a different build"), 0o755); err != nil {
155 t.Fatal(err)
156 }
157 return other
158 },
159 },
160 } {
161 t.Run(name, func(t *testing.T) {
162 opt, bake := t.TempDir(), t.TempDir()
163 liveTo(t, opt, observedHost, "new")
164 exe := c.procExe(t, opt)
165 rt := registry.Route{Host: observedHost, App: "console", Unit: "console.service"}
166 f := &fakeUnits{shows: []unitProps{{ActiveState: "active", InvocationID: "new", MainPID: 7,
167 ExecPath: c.execPath(opt)}}}
168 proofFixtureSettled(f, func(int) string { return exe }, opt, bake, time.Now, rt)(rt)
169 if h := release.ReadHealthy(bake, "bab", "console"); len(h) != 0 {
170 t.Fatalf("recorded %+v for a process that is not the live link's build", h)
171 }
172 })
173 }
174 // The control: the same call with both equalities holding records.
175 opt, bake := t.TempDir(), t.TempDir()
176 liveTo(t, opt, observedHost, "new")
177 rt := registry.Route{Host: observedHost, App: "console", Unit: "console.service"}
178 f := &fakeUnits{shows: []unitProps{{ActiveState: "active", InvocationID: "new", MainPID: 7,
179 ExecPath: release.LiveLink(opt, observedHost)}}}
180 staged := filepath.Join(opt, "versions", "new", "console")
181 proofFixtureSettled(f, func(int) string { return staged }, opt, bake, time.Now, rt)(rt)
182 if h := release.ReadHealthy(bake, "bab", "console"); len(h) != 1 || h[0].How != "settle" {
183 t.Fatalf("history = %+v, want the settled build", h)
184 }
185}
186
187func TestUnsettledRestartRecordsNothing(t *testing.T) {
188 opt, bake := t.TempDir(), t.TempDir()
189 liveTo(t, opt, observedHost, "new")
190 rt := registry.Route{Host: observedHost, App: "console", Unit: "console.service"}
191 f := &fakeUnits{shows: []unitProps{{ActiveState: "active", InvocationID: "old",
192 ExecPath: filepath.Join(opt, "live", "console.example.com")}}}
193 settled := proofFixtureSettled(f, func(int) string { return "/nonexistent" }, opt, bake, time.Now, rt)
194 if err := agentCheckedUnitRestart(f, opt, func(string) string { return "" }, settled)(rt); err != nil {
195 t.Fatalf("restart: %v", err)
196 }
197 if h := release.ReadHealthy(bake, "bab", "console"); len(h) != 0 {
198 t.Fatalf("a restart that was not checked recorded %+v", h)
199 }
200}
201
202// F2: a unit an operator stopped during the settle is not judged, and its
203// build is not recorded as healthy, even though its executable is readable.
204func TestStoppedDuringSettleRecordsNothing(t *testing.T) {
205 opt, bake := t.TempDir(), t.TempDir()
206 liveTo(t, opt, observedHost, "new")
207 staged := filepath.Join(opt, "versions", "new", "console")
208 rt := registry.Route{Host: observedHost, App: "console", Unit: "console.service"}
209 f := &fakeUnits{shows: []unitProps{
210 {ActiveState: "active", SubState: "running", InvocationID: "old", MainPID: 6,
211 ExecPath: filepath.Join(opt, "live", observedHost)},
212 {ActiveState: "inactive", SubState: "dead", InvocationID: "old"},
213 }}
214 settled := proofFixtureSettled(f, func(int) string { return staged }, opt, bake, time.Now, rt)
215 if err := agentCheckedUnitRestart(f, opt, func(string) string { return "" }, settled)(rt); err != nil {
216 t.Fatalf("restart: %v", err)
217 }
218 if h := release.ReadHealthy(bake, "bab", "console"); len(h) != 0 {
219 t.Fatalf("a stopped unit recorded %+v", h)
220 }
221}
222
223// A settle that fails records nothing either.
224func TestFailedSettleRecordsNothing(t *testing.T) {
225 opt, bake := t.TempDir(), t.TempDir()
226 liveTo(t, opt, observedHost, "new")
227 staged := filepath.Join(opt, "versions", "new", "console")
228 rt := registry.Route{Host: observedHost, App: "console", Unit: "console.service"}
229 f := &fakeUnits{shows: []unitProps{
230 {ActiveState: "active", SubState: "running", InvocationID: "old", MainPID: 6,
231 ExecPath: filepath.Join(opt, "live", observedHost)},
232 {ActiveState: "failed", SubState: "failed", InvocationID: "new"},
233 }}
234 settled := proofFixtureSettled(f, func(int) string { return staged }, opt, bake, time.Now, rt)
235 if err := agentCheckedUnitRestart(f, opt, func(string) string { return "" }, settled)(rt); err == nil {
236 t.Fatal("a failed unit settled")
237 }
238 if h := release.ReadHealthy(bake, "bab", "console"); len(h) != 0 {
239 t.Fatalf("a failed build recorded %+v", h)
240 }
241}
242
243func proofFixtureResolver(rt registry.Route) proofBindingResolver {
244 rt.Account = release.RouteAccount(rt)
245 return func(_ context.Context, candidate registry.Route) (proofBinding, error) {
246 if candidate.Host != rt.Host || candidate.App != rt.App || candidate.Unit != rt.Unit {
247 return proofBinding{}, os.ErrPermission
248 }
249 return proofBinding{Route: rt, Source: "test-installed-binding", Network: true}, nil
250 }
251}
252
253func proofFixtureSettled(sys unitSystem, procExe func(int) string, opt, history string, now func() time.Time, rt registry.Route) func(registry.Route) error {
254 if f, ok := sys.(*fakeUnits); ok {
255 for i := range f.shows {
256 if f.shows[i].ActiveEnterTimestamp.IsZero() {
257 f.shows[i].ActiveEnterTimestamp = now().Add(-time.Minute)
258 }
259 }
260 }
261 o := proofObserver{sys: sys, procExe: procExe, optDir: opt, now: now, resolve: proofFixtureResolver(rt),
262 ready: func(registry.Route) func() error { return func() error { return nil } }}
263 return o.recordSettled(history)
264}
265