carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux && rootrunner
2
3package host
4
5import (
6 "context"
7 "crypto/ed25519"
8 "crypto/sha256"
9 "encoding/hex"
10 "encoding/json"
11 "fmt"
12 "net"
13 "net/http"
14 "net/url"
15 "os"
16 "os/exec"
17 "path/filepath"
18 "strings"
19 "testing"
20 "time"
21
22 "github.com/carlosframework/platform/internal/hostbox"
23 "github.com/carlosframework/platform/internal/hostclaim"
24 execlease "github.com/carlosframework/platform/internal/hostexec"
25 "github.com/carlosframework/platform/internal/hostguard"
26 "github.com/carlosframework/platform/internal/instancectl"
27 "github.com/carlosframework/platform/internal/provision"
28 "github.com/carlosframework/platform/internal/registry"
29 "github.com/carlosframework/platform/internal/release"
30 "github.com/carlosframework/platform/internal/rollout"
31 "github.com/carlosframework/platform/internal/rootstate"
32)
33
34func plan14InstalledTenant() {
35 if os.Getenv("PLAN14_TENANT_FAIL") == "1" {
36 os.Exit(1)
37 }
38 socket := os.Getenv("PLAN14_TENANT_SOCKET")
39 if socket == "" {
40 os.Exit(2)
41 }
42 network := "unix"
43 if addr := os.Getenv("PLAN14_TENANT_ADDR"); addr != "" {
44 network, socket = "tcp", addr
45 }
46 if network == "unix" {
47 // The payload owns its socket, like an ordinary Unix HTTP server. A
48 // process killed at exit may leave that socket pathname behind.
49 if st, err := os.Lstat(socket); err == nil {
50 if st.Mode()&os.ModeSocket == 0 || os.Remove(socket) != nil {
51 os.Exit(1)
52 }
53 } else if !os.IsNotExist(err) {
54 os.Exit(1)
55 }
56 }
57 ln, err := net.Listen(network, socket)
58 if err != nil {
59 fmt.Fprintln(os.Stderr, err)
60 os.Exit(1)
61 }
62 if err := http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
63 if target := r.URL.Query().Get("dial"); target != "" {
64 conn, err := net.DialTimeout("tcp", target, 200*time.Millisecond)
65 if err != nil {
66 w.WriteHeader(http.StatusForbidden)
67 return
68 }
69 conn.Close()
70 w.WriteHeader(http.StatusNoContent)
71 return
72 }
73 w.Write([]byte("running"))
74 })); err != nil {
75 os.Exit(1)
76 }
77}
78
79func TestPlan14InstalledClaimLaunchRootRunner(t *testing.T) {
80 plan14InstalledClaimLaunch(t, "")
81}
82
83func TestPlan14NamedClaimLaunchRootRunner(t *testing.T) {
84 for _, unit := range []string{"plan14-named.service", "plan14-socket.service", "backing"} {
85 t.Run(unit, func(t *testing.T) { plan14InstalledClaimLaunch(t, unit) })
86 }
87}
88
89func TestPlan14RootRuntimeInstalledCompletion(t *testing.T) { plan14InstalledClaimLaunch(t, "", true) }
90
91func TestPlan14RootRuntimeInstalledFailure(t *testing.T) { plan14InstalledClaimLaunch(t, "", false) }
92
93// This witness permits loopback HTTP between the runner and its named TCP
94// payloads. The payloads make no remote network requests.
95func TestPlan14BoundRouteProgramRootRunner(t *testing.T) {
96 for _, unit := range []string{"tcp", "socket-program", "backing-program"} {
97 t.Run(unit, func(t *testing.T) { plan14InstalledClaimLaunch(t, unit) })
98 }
99}
100
101func TestPlan14MaterializedBoundRouteRootRunner(t *testing.T) {
102 for _, unit := range []string{"tcp", "socket-program", "backing-program"} {
103 t.Run(unit, func(t *testing.T) { plan14InstalledClaimLaunch(t, "materialized:"+unit) })
104 }
105}
106
107func TestPlan14MigratedBoundRouteRootRunner(t *testing.T) {
108 for _, unit := range []string{"tcp", "socket-program", "backing-program"} {
109 t.Run(unit, func(t *testing.T) { plan14InstalledClaimLaunch(t, "migrated:materialized:"+unit) })
110 }
111}
112
113func TestPlan14BoundNetworkEdgeRootRunner(t *testing.T) {
114 for _, unit := range []string{"tcp", "socket-program", "backing-program"} {
115 t.Run(unit, func(t *testing.T) { plan14InstalledClaimLaunch(t, "isolation:materialized:"+unit) })
116 }
117}
118
119func TestPlan14ConsoleLibraryRootRunner(t *testing.T) {
120 plan14InstalledClaimLaunch(t, "console:materialized:tcp")
121}
122
123var plan14ConsoleExecutable, plan14WoodstarExecutable []byte
124
125func TestPlan14RealConsoleRootRunnerPayload(t *testing.T) {
126 if len(plan14ConsoleExecutable) == 0 {
127 t.Fatal("build the real executable with scripts/plan14-console-root-runner.sh")
128 }
129 plan14InstalledClaimLaunch(t, "binary:console:materialized:tcp")
130}
131
132func TestPlan14RealWoodstarRootRunnerPayload(t *testing.T) {
133 if len(plan14WoodstarExecutable) == 0 {
134 t.Fatal("set PLAN14_WOODSTAR_SOURCE when running scripts/plan14-console-root-runner.sh")
135 }
136 plan14InstalledClaimLaunch(t, "woodstar:materialized:tcp")
137}
138
139func TestPlan14NamedStartupDefersReplacementRootRunner(t *testing.T) {
140 for _, mode := range []string{"tcp", "socket-program", "backing-program"} {
141 t.Run(mode, func(t *testing.T) { plan14InstalledClaimLaunch(t, "defer:migrated:materialized:"+mode) })
142 }
143}
144
145func TestPlan14OrdinaryUnitAuthorizationRootRunner(t *testing.T) {
146 plan14InstalledClaimLaunch(t, "ordinary-verified")
147}
148
149func plan14InstalledClaimLaunch(t *testing.T, boundUnit string, runtimeUpdate ...bool) {
150 verifiedOrdinary := boundUnit == "ordinary-verified"
151 if verifiedOrdinary {
152 boundUnit = ""
153 }
154 woodstar := strings.HasPrefix(boundUnit, "woodstar:")
155 boundUnit = strings.TrimPrefix(boundUnit, "woodstar:")
156 deferReplacement := strings.HasPrefix(boundUnit, "defer:")
157 boundUnit = strings.TrimPrefix(boundUnit, "defer:")
158 consoleBinary := strings.HasPrefix(boundUnit, "binary:")
159 boundUnit = strings.TrimPrefix(boundUnit, "binary:")
160 realConsole := strings.HasPrefix(boundUnit, "console:")
161 boundUnit = strings.TrimPrefix(boundUnit, "console:")
162 isolation := strings.HasPrefix(boundUnit, "isolation:")
163 boundUnit = strings.TrimPrefix(boundUnit, "isolation:")
164 startup := strings.HasPrefix(boundUnit, "startup:")
165 boundUnit = strings.TrimPrefix(boundUnit, "startup:")
166 combined := strings.HasPrefix(boundUnit, "combined:")
167 boundUnit = strings.TrimPrefix(boundUnit, "combined:")
168 migrated := strings.HasPrefix(boundUnit, "migrated:")
169 boundUnit = strings.TrimPrefix(boundUnit, "migrated:")
170 materialized := strings.HasPrefix(boundUnit, "materialized:")
171 boundUnit = strings.TrimPrefix(boundUnit, "materialized:")
172 programMode := boundUnit == "tcp" || boundUnit == "socket-program" || boundUnit == "backing-program"
173 tcpMode := boundUnit == "tcp" || boundUnit == "backing-program"
174 if boundUnit == "tcp" {
175 boundUnit = "plan14-tcp.service"
176 }
177 if boundUnit == "socket-program" {
178 boundUnit = "plan14-socket.service"
179 }
180 if boundUnit == "backing-program" {
181 boundUnit = "backing"
182 }
183 if dir := os.Getenv("PLAN14_INSTALLED_RETIRE_CHILD"); dir != "" {
184 plan14InstalledRetire(t, dir)
185 return
186 }
187 dir := generationRootRunnerDir(t)
188 t.Setenv("TMPDIR", dir)
189 uid, gid := generationRunnerRegistryUser(t)
190 ctl := func(ctx context.Context, args ...string) (string, error) {
191 out, err := exec.CommandContext(ctx, "systemctl", args...).CombinedOutput()
192 return strings.TrimSpace(string(out)), err
193 }
194 // These fixture paths must be absent. Install only this runner's units and
195 // filesystem roots; retain the deployed unit's hardening and launch entrypoint.
196 for _, p := range []string{"/data", "/opt/carlos", "/run/carlos", "/run/carlos-control", "/etc/carlos"} {
197 if err := os.Mkdir(p, 0755); err != nil {
198 t.Fatal("fixture root already exists", p, err)
199 }
200 p := p
201 t.Cleanup(func() {
202 if err := os.RemoveAll(p); err != nil {
203 t.Error(err)
204 }
205 })
206 }
207 binary, err := os.Executable()
208 if err != nil {
209 t.Fatal(err)
210 }
211 body, err := os.ReadFile(binary)
212 if err != nil {
213 t.Fatal(err)
214 }
215 if err := os.WriteFile(instancectl.SystemBinary, body, 0755); err != nil {
216 t.Fatal(err)
217 }
218 f, _, rec := generationLaunchFixture(t)
219 t.Setenv("CARLOS_DATA_DIR", "/data")
220 t.Setenv("CARLOS_OPT_DIR", "/opt/carlos")
221 host := f.target.Host
222 var endpoint string
223 if tcpMode {
224 ln, err := net.Listen("tcp", "127.0.0.1:0")
225 if err != nil {
226 t.Fatal(err)
227 }
228 endpoint = ln.Addr().String()
229 ln.Close()
230 }
231 socketEndpoint := filepath.Join(provision.InstanceRunDir(host), "app.sock")
232 if materialized && !tcpMode {
233 socketEndpoint = "/run/carlos/woodstar-test.sock"
234 }
235 program := claimUnitProgram{Schema: 1, Host: host, Args: []string{"plan14-tenant"}, Env: []string{"PLAN14_TENANT_SOCKET=" + socketEndpoint}}
236 if tcpMode {
237 program.Env = append(program.Env, "PLAN14_TENANT_ADDR="+endpoint)
238 }
239 var checkService func(*http.Client)
240 if realConsole {
241 program, checkService = plan14ConsoleProgram(t, host, endpoint)
242 if consoleBinary {
243 if len(plan14ConsoleExecutable) == 0 {
244 t.Fatal("real console executable missing")
245 }
246 program = plan14ConsoleBinaryProgram(t, program)
247 }
248 }
249 if woodstar {
250 if len(plan14WoodstarExecutable) == 0 {
251 t.Fatal("real Woodstar executable missing")
252 }
253 program, checkService = plan14WoodstarProgram(t, host, endpoint)
254 }
255 healthURL := "http://tenant/"
256 if woodstar {
257 healthURL += "healthz"
258 }
259 programRaw, _ := json.Marshal(program)
260 programSum := sha256.Sum256(programRaw)
261 if boundUnit == "backing" {
262 boundUnit = "carlos-app@" + host + ".service"
263 }
264 for _, p := range []string{provision.InstanceDir("/data", host), provision.InstanceRunDir(host), "/run/carlos-control/exe", "/run/carlos/logs"} {
265 if err := os.MkdirAll(p, 0755); err != nil {
266 t.Fatal(err)
267 }
268 }
269 for _, p := range []string{provision.InstanceDir("/data", host), provision.InstanceRunDir(host)} {
270 if err := os.Chown(p, uid, gid); err != nil {
271 t.Fatal(err)
272 }
273 }
274 if materialized || verifiedOrdinary {
275 plan14MaterializedDependencies(t, ctl)
276 }
277 if verifiedOrdinary {
278 path := "/etc/systemd/system/carlos-edge.service"
279 if _, err := os.Lstat(path); !os.IsNotExist(err) {
280 t.Fatal("existing edge", err)
281 }
282 if err := os.WriteFile(path, []byte("[Service]\nType=oneshot\nExecStart=/usr/bin/true\nRemainAfterExit=yes\n"), 0644); err != nil {
283 t.Fatal(err)
284 }
285 t.Cleanup(func() {
286 ctl(context.Background(), "stop", "carlos-edge.service")
287 os.Remove(path)
288 ctl(context.Background(), "daemon-reload")
289 })
290 }
291 template, err := unitsFS.ReadFile("units/carlos-instance@.service")
292 if err != nil {
293 t.Fatal(err)
294 }
295 var lines []string
296 for _, line := range strings.Split(string(template), "\n") {
297 key, _, _ := strings.Cut(line, "=")
298 switch key {
299 case "BindsTo", "Requires", "After":
300 continue
301 case "StandardOutput":
302 line = "StandardOutput=journal"
303 case "ExecStart":
304 if programMode {
305 line = "ExecStart=/usr/bin/false"
306 }
307 }
308 lines = append(lines, line)
309 }
310 if tcpMode {
311 lines = append(lines, "[Service]", "IPAddressAllow=localhost")
312 }
313 unit := instancectl.UnitName(instancectl.RoleRun, host)
314 unitPath := "/run/systemd/system/carlos-instance@.service"
315 if verifiedOrdinary {
316 unitPath = "/etc/systemd/system/carlos-instance@.service"
317 lines = strings.Split(string(template), "\n")
318 }
319 if boundUnit != "" {
320 unit = boundUnit
321 unitPath = filepath.Join("/run/systemd/system", unit)
322 for i := range lines {
323 lines[i] = strings.ReplaceAll(strings.ReplaceAll(lines[i], "%i", provision.InstanceID(host)), "%I", provision.InstanceID(host))
324 }
325 f.req.Reservation.Group.RelativePath = instancectl.BoundClaimUnitControlGroup(host, unit)
326 }
327 if materialized {
328 unitPath = filepath.Join("/etc/systemd/system", unit)
329 }
330 legacyDefinition := []byte("[Service]\nType=simple\nUser=carlos\nKillMode=control-group\nExecStart=/bin/sh -c 'sleep infinity & wait'\n")
331 if migrated && !tcpMode {
332 if err := os.Chown("/run/carlos", uid, gid); err != nil {
333 t.Fatal(err)
334 }
335 legacyDefinition = []byte("[Service]\nType=simple\nUser=carlos\nKillMode=control-group\nEnvironment=PLAN14_TENANT_SOCKET=" + socketEndpoint + "\nExecStart=/opt/carlos/carlos plan14-tenant\n")
336 }
337 if migrated {
338 if err := os.WriteFile(unitPath, legacyDefinition, 0644); err != nil {
339 t.Fatal(err)
340 }
341 }
342 if !materialized {
343 out, err := os.OpenFile(unitPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
344 if err != nil {
345 t.Fatal(err)
346 }
347 if _, err := out.WriteString(strings.Join(lines, "\n")); err != nil {
348 t.Fatal(err)
349 }
350 out.Close()
351 }
352 t.Cleanup(func() {
353 ctl(context.Background(), "stop", unit)
354 ctl(context.Background(), "stop", instancectl.SliceName(host))
355 ctl(context.Background(), "reset-failed", unit)
356 os.Remove(unitPath)
357 os.RemoveAll(filepath.Join("/run/systemd/system", unit+".d"))
358 os.RemoveAll(filepath.Join("/run/systemd/system", instancectl.SliceName(host)+".d"))
359 ctl(context.Background(), "daemon-reload")
360 })
361 if out, err := ctl(t.Context(), "daemon-reload"); err != nil {
362 t.Fatal(out, err)
363 }
364 if migrated {
365 if out, err := ctl(t.Context(), "start", unit); err != nil {
366 t.Fatal(out, err)
367 }
368 if !tcpMode {
369 deadline := time.Now().Add(5 * time.Second)
370 for {
371 conn, err := net.DialTimeout("unix", socketEndpoint, time.Second)
372 if err == nil {
373 conn.Close()
374 break
375 }
376 if time.Now().After(deadline) {
377 t.Fatal("legacy socket did not serve", err)
378 }
379 time.Sleep(20 * time.Millisecond)
380 }
381 }
382 }
383 // Use a real controller lifetime, which must be joined before exact release.
384 controller := exec.Command("/bin/sleep", "240")
385 if err := controller.Start(); err != nil {
386 t.Fatal(err)
387 }
388 joined := false
389 join := func() {
390 if !joined {
391 controller.Process.Kill()
392 controller.Wait()
393 joined = true
394 }
395 }
396 t.Cleanup(join)
397 f.peer.PID = controller.Process.Pid
398 f.peer.ProcessStart, err = claimLaunchProcessStart("/proc", f.peer.PID)
399 if err != nil {
400 t.Fatal(err)
401 }
402 launch, err := f.journal.Read(t.Context(), f.target)
403 if err != nil {
404 t.Fatal(err)
405 }
406 boot, err := os.ReadFile("/proc/sys/kernel/random/boot_id")
407 if err != nil {
408 t.Fatal(err)
409 }
410 f.target.BootID, f.peer.BootID = strings.TrimSpace(string(boot)), strings.TrimSpace(string(boot))
411 f.req.Reservation.Target = f.target
412 launch.Target = f.target
413 f.claims.set(func(c *hostclaim.Claim) { c.Holder.WakeRetry.Attempt.BootID = f.peer.BootID })
414 launch.ControllerPID, launch.ProcessStart = f.peer.PID, f.peer.ProcessStart
415 // Open a fresh journal with the controller identity production will check.
416 journal, err := hostbox.OpenLaunchJournal(filepath.Join(dir, "launch.db"))
417 if err != nil {
418 t.Fatal(err)
419 }
420 t.Cleanup(func() { journal.Close() })
421 if err := journal.Prelaunch(t.Context(), launch); err != nil {
422 t.Fatal(err)
423 }
424 f.journal, f.root.Journal = journal, journal
425 state, err := rootstate.Open(filepath.Join(dir, "fences"), true, false)
426 if err != nil {
427 t.Fatal(err)
428 }
429 defer state.Close()
430 old, err := f.root.Guard.Root.Installation(t.Context(), host, hostguard.Captured{Incarnation: f.target.Incarnation, Stamp: f.target.Stamp})
431 if err != nil {
432 t.Fatal(err)
433 }
434 f.root.Guard.Root = hostguard.NewRootAuthority(state)
435 if err := f.root.Guard.EstablishRoot(t.Context(), old, func(*hostclaim.Claim) error { return nil }); err != nil {
436 t.Fatal(err)
437 }
438 if boundUnit != "" {
439 bindings, err := rootstate.Open(filepath.Join(dir, "unit-bindings"), true, false)
440 if err != nil {
441 t.Fatal(err)
442 }
443 defer bindings.Close()
444 shape := claimUnitShape{Host: host, Account: old.Account, App: old.App, Kind: "instance", Unit: boundUnit, Socket: provision.NestedSocket(host), DBPath: provision.NestedDBPath("/data", host)}
445 if strings.HasPrefix(boundUnit, "carlos-app@") {
446 shape.Backing = "unit"
447 }
448 if migrated {
449 shape.PredecessorDigest = claimUnitDigest(legacyDefinition)
450 }
451 if programMode {
452 shape.Program = hex.EncodeToString(programSum[:])
453 if materialized && !tcpMode {
454 shape.Socket = socketEndpoint
455 }
456 if tcpMode {
457 shape.Addr, shape.Socket = endpoint, ""
458 }
459 if err := os.MkdirAll("/etc/carlos/claim-units", 0700); err != nil {
460 t.Fatal(err)
461 }
462 if err := os.WriteFile(filepath.Join("/etc/carlos/claim-units", host+".json"), programRaw, 0600); err != nil {
463 t.Fatal(err)
464 }
465 programPath := filepath.Join("/etc/carlos/claim-units", host+".json")
466 if err := os.Chmod(programPath, 0666); err != nil {
467 t.Fatal(err)
468 }
469 if _, err := readClaimUnitProgram(shape); err == nil {
470 t.Fatal("service-writable program accepted")
471 }
472 if err := os.Chmod(programPath, 0600); err != nil {
473 t.Fatal(err)
474 }
475 f.root.unitProgram = readClaimUnitProgram
476 }
477 f.root.unitBindings = &claimUnitBindings{state: bindings, installation: func(context.Context, string) (hostguard.RootInstallation, error) { return old, nil }, canonical: func(context.Context, string) (claimUnitShape, error) { return shape, nil }}
478 if _, err := f.root.unitBindings.establish(t.Context(), host); err != nil {
479 t.Fatal(err)
480 }
481 }
482 f.root.Clock = execlease.SystemClock{}
483 f.root.DataRoot = "/data"
484 f.root.ReadyBudget = 10 * time.Second
485 f.root.ReadyPoll = 20 * time.Millisecond
486 f.root.Units = instancectl.ClaimUnits{BoundUnit: boundUnit, BoundHost: func() string {
487 if boundUnit != "" {
488 return host
489 }
490 return ""
491 }(), Systemctl: ctl, CgroupRoot: "/sys/fs/cgroup", ExeRoot: "/run/carlos-control/exe", UnitRoot: "/run/systemd/system", DataRoot: "/data", WaitBudget: 20 * time.Second}
492 if verifiedOrdinary {
493 oldOpen, oldMaterial, oldShapes := openClaimUnitBindings, openClaimUnitMaterializations, productionClaimUnitShapes
494 defer func() {
495 openClaimUnitBindings, openClaimUnitMaterializations, productionClaimUnitShapes = oldOpen, oldMaterial, oldShapes
496 }()
497 openClaimUnitBindings = func() (rootstate.Store, error) {
498 return rootstate.Open(filepath.Join(dir, "ordinary-bindings"), true, false)
499 }
500 openClaimUnitMaterializations = func() (rootstate.Store, error) {
501 return rootstate.Open(filepath.Join(dir, "ordinary-materials"), true, false)
502 }
503 productionClaimUnitShapes = func() (map[string]claimUnitShape, error) { return map[string]claimUnitShape{}, nil }
504 wrapped := ClaimRootWithLaunch{Launch: f.root}
505 wrapped.Root.Stop.Coordinator.Kernel = claimUnitStopKernel{Units: f.root.Units}
506 assembly := &ClaimAssembly{}
507 if err := bindProductionClaimUnits(t.Context(), &wrapped, assembly); err != nil {
508 t.Fatal(err)
509 }
510 t.Cleanup(func() { assembly.Close(context.Background()) })
511 dispatch := f.root.Units.(claimUnitDispatch)
512 plan14BoundOverrideRefusals(t, dispatch, host, unit, ctl)
513 if _, err := dispatch.forStart(t.Context(), host); err != nil {
514 t.Fatalf("ordinary authorized configuration: %#v", err)
515 }
516 }
517 var material *claimUnitMaterializer
518 if materialized {
519 ms, err := rootstate.Open(filepath.Join(dir, "unit-materializations"), true, false)
520 if err != nil {
521 t.Fatal(err)
522 }
523 defer ms.Close()
524 files := claimUnitFiles{dir: "/etc/systemd/system"}
525 material = &claimUnitMaterializer{idle: func(host string) error { return f.root.Units.Gone(t.Context(), host, "") }, state: ms, read: files.read, create: files.create, replace: files.replace}
526 binding, err := f.root.unitBindings.read(host)
527 if err != nil {
528 t.Fatal(err)
529 }
530 if migrated {
531 kernel := claimPredecessorKernel{ctl: ctl, group: claimPredecessorGroup, boot: claimUnitBoot}
532 material.predecessor, material.stopPredecessor = kernel.capture, kernel.stop
533 material.socketCapture, material.socketPark = captureProductionClaimUnitSocket, parkProductionClaimUnitSocket
534 if !tcpMode {
535 predecessor, err := kernel.capture(t.Context(), binding)
536 if err != nil {
537 t.Fatal(err)
538 }
539 predecessor.Group = "system.slice/unrelated.service"
540 if _, err := captureProductionClaimUnitSocket(t.Context(), binding.Shape, predecessor); err == nil {
541 t.Fatal("socket accepted a foreign predecessor cgroup")
542 }
543 }
544 }
545 if deferReplacement {
546 plan14ProductionStartupDefersReplacement(t, f.root, material, binding, ctl)
547 return
548 }
549 if err := material.install(t.Context(), binding); err != nil {
550 debug, _ := ctl(t.Context(), "show", unit, "-p", "ActiveState,InvocationID,ControlGroup,FragmentPath,DropInPaths")
551 t.Fatalf("migration/materialization: %v; unit: %s", err, debug)
552 }
553 if err := projectClaimBoundSocket(binding.Shape); err != nil {
554 t.Fatal(err)
555 }
556 if _, err := ctl(t.Context(), "daemon-reload"); err != nil {
557 t.Fatal(err)
558 }
559 }
560 if programMode {
561 base := f.root.Units.(instancectl.ClaimUnits)
562 base.BoundHost, base.BoundUnit = "", ""
563 binding, err := f.root.unitBindings.read(host)
564 if err != nil {
565 t.Fatal(err)
566 }
567 f.root.Units = claimUnitDispatch{base: base, bindings: f.root.unitBindings, shapes: func() (map[string]claimUnitShape, error) { return map[string]claimUnitShape{host: binding.Shape}, nil }, definition: func(s claimUnitShape) error {
568 if material != nil {
569 if err := material.verify(binding); err != nil {
570 return err
571 }
572 if err := claimEffectiveUnit(t.Context(), s, ctl); err != nil {
573 t.Logf("effective unit refused: %#v", err)
574 return err
575 }
576 return nil
577 }
578 // Dependencies and output are runner adapters; all isolation settings
579 // and the permanent no-reservation refusal remain in the unit.
580 raw, err := os.ReadFile(unitPath)
581 if err != nil {
582 return err
583 }
584 if string(raw) != strings.Join(lines, "\n") {
585 return os.ErrPermission
586 }
587 return nil
588 }}
589 }
590 if materialized {
591 plan14BoundOverrideRefusals(t, f.root.Units.(claimUnitDispatch), host, unit, ctl)
592 f.root.boundReady = func(ctx context.Context, host string, started instancectl.ClaimUnitStarted) error {
593 b, err := f.root.unitBindings.check(ctx, host, nil)
594 if err != nil {
595 return err
596 }
597 return claimBoundUnitReady(ctx, started, b.Shape)
598 }
599 }
600 f.root.Kernel = linuxClaimLaunchKernel{OptRoot: "/opt/carlos", CgroupRoot: "/sys/fs/cgroup", ProcRoot: "/proc"}
601 store := release.NewDirStore(filepath.Join(dir, "deployment"))
602 rec.Schema = 1
603 plan14PutRecord(t, store, rec)
604 pub, priv, err := ed25519.GenerateKey(nil)
605 if err != nil {
606 t.Fatal(err)
607 }
608 runnerBody := body
609 if consoleBinary {
610 body = plan14ConsoleExecutable
611 }
612 if woodstar {
613 body = plan14WoodstarExecutable
614 }
615 for _, v := range []string{"v1", "v3"} {
616 if err := release.StageArtifact("/opt/carlos", rec.Account, v, rec.App, body); err != nil {
617 t.Fatal(err)
618 }
619 m := release.Manifest{App: rec.App, Version: v, Kind: "binary", Artifacts: map[string]release.Artifact{release.AdoptPlatform(): {}}}
620 if err := release.Ship(t.Context(), store, rec.Account, m, map[string][]byte{release.AdoptPlatform(): body}); err != nil {
621 t.Fatal(err)
622 }
623 if err := release.Promote(t.Context(), store, priv, rec.Account, rec.App, "canary/console", v); err != nil {
624 t.Fatal(err)
625 }
626 }
627 path := release.VersionPath("/opt/carlos", rec.Account, "v1", rec.App)
628 file, err := rootstate.OpenTrustedFile(path)
629 if err != nil {
630 t.Fatal(err)
631 }
632 id, err := rootstate.Identity(file)
633 file.Close()
634 if err != nil {
635 t.Fatal(err)
636 }
637 digest := sha256.Sum256(body)
638 f.req.Executable = path
639 f.req.Reservation.Executable = hostbox.LaunchFile{Device: id.Device, Inode: id.Inode}
640 f.req.Spec.Executable = path
641 f.req.Spec.Digest = hex.EncodeToString(digest[:])
642 f.req.Spec.Args = []string{"--socket", filepath.Join(provision.InstanceRunDir(host), "app.sock"), "--db", provision.NestedDBPath("/data", host)}
643 f.req.Spec.Env = []string{"CARLOS_ADMIN_TOKEN=test", "PLAN14_TENANT_SOCKET=" + filepath.Join(provision.InstanceRunDir(host), "app.sock")}
644 if programMode {
645 f.req.Spec.Args, f.req.Spec.Env = program.Args, program.Env
646 f.req.Spec.Identity.Config = hex.EncodeToString(programSum[:])
647 if materialized {
648 b, err := f.root.unitBindings.read(host)
649 if err != nil {
650 t.Fatal(err)
651 }
652 route := b.Shape.route()
653 out, err := f.root.Serve(t.Context(), f.peer, hostbox.RoleLaunchRequest{V: hostbox.RoleLaunchV, Op: hostbox.RoleLaunchInputs, Route: &route})
654 if err != nil || out.Inputs == nil {
655 t.Fatal("bound admission inputs", err)
656 }
657 f.req.Spec.Args, f.req.Spec.Env = out.Inputs.Args, out.Inputs.Env
658 }
659 }
660 if len(runtimeUpdate) > 0 && !runtimeUpdate[0] {
661 f.req.Spec.Env = append(f.req.Spec.Env, "PLAN14_TENANT_FAIL=1")
662 f.root.ReadyBudget = 100 * time.Millisecond
663 }
664 inputs, err := f.req.Spec.Hash()
665 if err != nil {
666 t.Fatal(err)
667 }
668 f.req.Reservation.Inputs = inputs
669 f.claims.set(func(c *hostclaim.Claim) { c.Holder.WakeRetry.Attempt.LaunchInputs = inputs })
670 f.root.generations = &rootGenerationSource{store: store, optDir: "/opt/carlos", pub: func() (ed25519.PublicKey, error) { return pub, nil }, open: func() (rootstate.Store, error) { return rootstate.Open(filepath.Join(dir, "generations"), true, false) }, openFile: rootstate.OpenTrustedFile, persistent: rootstate.PersistentIdentity}
671 var updateSaved rootUpdateRecord
672 var runtimeBook *rollout.RootFailures
673 if len(runtimeUpdate) > 0 {
674 state, err := rootstate.Open(filepath.Join(dir, "failures"), true, false)
675 if err != nil {
676 t.Fatal(err)
677 }
678 defer state.Close()
679 book, err := rollout.InitializeRootFailures(state, "box/en/1", nil)
680 if err != nil {
681 t.Fatal(err)
682 }
683 updateSaved = rootUpdateRecord{Schema: 1, Installation: "fixture-installation", Route: registry.Route{Host: host, Account: rec.Account, App: rec.App, Socket: filepath.Join(provision.InstanceRunDir(host), "app.sock")}, Consumed: &release.Consumed{Resolved: &release.ResolvedRequest{ID: 7, Target: "v1"}}}
684 runtimeBook = book
685 source := rootUpdateSource{state: state, book: book, ready: func(rt registry.Route) error { return rootUpdateReady(t.Context(), rt) }}
686 f.root.generations.beginUpdate = func(ctx context.Context, _ *rootGenerationSource, b release.GenerationBinding) (func(string, error, func() error) error, func() error, error) {
687 finish, err := source.beginRuntime(&updateSaved, func() error { raw, _ := json.Marshal(updateSaved); return state.Write("settings.json", raw) }, b.Version, b.Digest, b.Attempt)
688 return finish, func() error { return nil }, err
689 }
690 }
691 lock := &release.ReleaseExclusion{Open: func() (rootstate.Store, error) { return rootstate.Open(filepath.Join(dir, "release"), true, false) }}
692 checkSibling := func() {}
693 if programMode {
694 checkSibling = plan14BoundSibling(t, runnerBody, ctl, uid, gid, lock)
695 checkSibling()
696 }
697 if err := f.req.Validate(); err != nil {
698 t.Fatal("request", err)
699 }
700 if _, err := f.root.launchSpec(f.req); err != nil {
701 t.Fatal("launch spec", err)
702 }
703 if err := f.root.Kernel.Verify(f.req.Executable, f.req.Reservation.Executable); err != nil {
704 t.Fatal("kernel build", err)
705 }
706 var response hostbox.RoleLaunchResponse
707 err = lock.With(t.Context(), func(ctx context.Context) error {
708 var err error
709 response, err = f.root.Serve(ctx, f.peer, f.req)
710 return err
711 })
712 if len(runtimeUpdate) > 0 && !runtimeUpdate[0] {
713 if err == nil || runtimeBook == nil || !runtimeBook.HeldDown(rec.Account, rec.App, "v1", host) || updateSaved.Build.State == nil || !updateSaved.Build.State.CompletedFailed || updateSaved.Build.State.Completed != 7 {
714 t.Fatal("installed failure lacked durable root refusal", err, updateSaved)
715 }
716 return
717 }
718 if err != nil {
719 logs, _ := exec.Command("journalctl", "-u", unit, "--no-pager", "-n", "80").CombinedOutput()
720 output, _ := os.ReadFile(provision.LogSocket(host, "run"))
721 t.Fatalf("production launch: %v\n%s\noutput: %s", err, logs, output)
722 }
723 if len(runtimeUpdate) > 0 && (updateSaved.Build.State == nil || updateSaved.Build.State.Completed != 7 || updateSaved.Build.State.CompletedFailed || updateSaved.Served["v1"].Invocation != response.Grant.UnitInvocationID) {
724 t.Fatal("root installed completion missing", updateSaved)
725 }
726 transport := &http.Transport{DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
727 if tcpMode {
728 return (&net.Dialer{}).DialContext(ctx, "tcp", endpoint)
729 }
730 return (&net.Dialer{}).DialContext(ctx, "unix", socketEndpoint)
731 }}
732 defer transport.CloseIdleConnections()
733 client := &http.Client{Transport: transport, Timeout: 3 * time.Second}
734 resp, err := client.Get(healthURL)
735 if err != nil {
736 t.Fatal("installed tenant did not serve", err)
737 }
738 resp.Body.Close()
739 transport.CloseIdleConnections()
740 if resp.StatusCode != 200 {
741 t.Fatal(resp.Status)
742 }
743 if checkService != nil {
744 checkService(client)
745 }
746 if isolation || realConsole || woodstar {
747 if tcpMode {
748 siblingTransport := &http.Transport{DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
749 return (&net.Dialer{}).DialContext(ctx, "unix", filepath.Join(provision.InstanceRunDir("sibling.example"), "app.sock"))
750 }}
751 siblingClient := &http.Client{Transport: siblingTransport, Timeout: 3 * time.Second}
752 response, err := siblingClient.Get("http://sibling/?dial=" + url.QueryEscape(endpoint))
753 if err != nil {
754 t.Fatal("sibling isolation probe", err)
755 }
756 response.Body.Close()
757 siblingTransport.CloseIdleConnections()
758 if response.StatusCode != http.StatusForbidden {
759 t.Fatal("sibling reached bound TCP upstream", response.StatusCode)
760 }
761 }
762 for _, name := range func() []string {
763 if realConsole || woodstar {
764 return nil
765 }
766 return []string{"console", "sibling"}
767 }() {
768 listener, err := net.Listen("tcp", "127.0.0.1:0")
769 if err != nil {
770 t.Fatal(err)
771 }
772 conn, err := net.DialTimeout("tcp", listener.Addr().String(), time.Second)
773 if err != nil {
774 t.Fatal("control listener unavailable", err)
775 }
776 conn.Close()
777 for _, address := range []string{listener.Addr().String(), "[::ffff:127.0.0.1]:" + strings.Split(listener.Addr().String(), ":")[1]} {
778 response, err := client.Get("http://tenant/?dial=" + url.QueryEscape(address))
779 if err != nil {
780 t.Fatal(err)
781 }
782 response.Body.Close()
783 if response.StatusCode != http.StatusForbidden {
784 t.Fatal("bound tenant reached", name, address, response.StatusCode)
785 }
786 }
787 listener.Close()
788 }
789 before, err := ctl(t.Context(), "show", "-p", "InvocationID", "--value", unit)
790 if err != nil {
791 t.Fatal(err)
792 }
793 if _, err := ctl(t.Context(), "restart", "carlos-edge.service"); err != nil {
794 t.Fatal(err)
795 }
796 after, err := ctl(t.Context(), "show", "-p", "InvocationID", "--value", unit)
797 if err != nil || before == "" || after != before {
798 t.Fatal("edge restart changed bound invocation", before, after, err)
799 }
800 response, err := client.Get(healthURL)
801 if err != nil {
802 t.Fatal("edge restart stopped bound service", err)
803 }
804 response.Body.Close()
805 if response.StatusCode != http.StatusOK {
806 t.Fatal(response.Status)
807 }
808 }
809 checkSibling()
810 if materialized {
811 b, err := f.root.unitBindings.read(host)
812 if err != nil {
813 t.Fatal(err)
814 }
815 unrelated := instancectl.ClaimUnitStarted{Unit: response.Grant.Unit, MainPID: response.Grant.PID, ControlGroup: instancectl.ClaimUnitControlGroup("unrelated.example")}
816 if b.Shape.Socket != "" {
817 unrelated.MainPID = os.Getpid()
818 }
819 if err := claimBoundUnitReady(t.Context(), unrelated, b.Shape); err == nil {
820 t.Fatal("readiness accepted unrelated process or group")
821 }
822 }
823 generations, err := rootstate.Open(filepath.Join(dir, "generations"), false, false)
824 if err != nil {
825 t.Fatal(err)
826 }
827 defer generations.Close()
828 ledger := &release.RootGenerations{Store: generations}
829 records, err := ledger.Reservations(t.Context())
830 if err != nil || len(records) != 1 {
831 t.Fatal(records, err)
832 }
833 v1 := records[0]
834 if consoleBinary || woodstar {
835 running, err := os.ReadFile(fmt.Sprintf("/proc/%d/exe", v1.Process.TenantPID))
836 if err != nil || sha256.Sum256(running) != sha256.Sum256(body) {
837 t.Fatal("running service is not the built executable", err)
838 }
839 }
840 if v1.Process.PID != response.Grant.PID || v1.Process.Containment != 1 || v1.Process.TenantPID == 0 {
841 t.Fatal("missing production process evidence", v1)
842 }
843 pending := v1.Binding
844 pending.Version = "v3"
845 pending.Attempt = "pending-v3"
846 pending.Invocation = "pending-v3"
847 pending.Path = release.VersionPath("/opt/carlos", rec.Account, "v3", rec.App)
848 pending.Tree = filepath.Dir(pending.Path)
849 for _, v := range []struct {
850 path string
851 id *rootstate.FileIdentity
852 }{{pending.Path, &pending.Executable}, {pending.Tree, &pending.TreeIdentity}} {
853 file, err := os.Open(v.path)
854 if err != nil {
855 t.Fatal(err)
856 }
857 *v.id, err = rootstate.Identity(file)
858 file.Close()
859 if err != nil {
860 t.Fatal(err)
861 }
862 }
863 if err := lock.With(t.Context(), func(ctx context.Context) error { _, err := ledger.Reserve(pending); return err }); err != nil {
864 t.Fatal(err)
865 }
866 retire := generationRetirement{guard: f.root.Guard, journal: journal, kernel: productionGenerationAbsence}
867 if err := lock.With(t.Context(), func(ctx context.Context) error { return retire.retire(ctx, ledger, v1) }); err != nil {
868 t.Fatal(err)
869 }
870 still, err := ledger.Read(v1.Binding)
871 if err != nil || still.Retirement != "" {
872 t.Fatal("live pin retired", err)
873 }
874 stop := f.req
875 stop.Op = hostbox.RoleLaunchStop
876 stop.Spec, stop.Executable, stop.MemoryMax = nil, "", 0
877 if _, err := f.root.Serve(t.Context(), f.peer, stop); err != nil {
878 t.Fatal("production stop", err)
879 }
880 if programMode {
881 ctl(t.Context(), "start", unit)
882 time.Sleep(100 * time.Millisecond)
883 state, err := ctl(t.Context(), "show", "-p", "MainPID", "--value", unit)
884 if err != nil || state != "0" {
885 t.Fatal("unreserved restart executed", state, err)
886 }
887 ctl(t.Context(), "stop", unit)
888 next := *f.req.Reservation
889 next.ID, next.InvocationID = "attempt-2:main", "inv-2"
890 f.req.Reservation = &next
891 f.claims.set(func(c *hostclaim.Claim) {
892 c.Holder.WakeRetry.AttemptsReserved++
893 c.Holder.WakeRetry.Attempt.ID = "attempt-2"
894 c.Holder.WakeRetry.Attempt.InvocationID = "inv-2"
895 })
896 var restarted hostbox.RoleLaunchResponse
897 if err := lock.With(t.Context(), func(ctx context.Context) error {
898 var err error
899 restarted, err = f.root.Serve(ctx, f.peer, f.req)
900 return err
901 }); err != nil {
902 logs, _ := exec.Command("journalctl", "-u", unit, "--no-pager", "-n", "100").CombinedOutput()
903 state, _ := ctl(t.Context(), "show", "-p", "ActiveState,SubState,InvocationID,ControlGroup,ExecStart,Result", unit)
904 t.Fatalf("reserved restart: %v\n%s\n%s", err, state, logs)
905 }
906 if restarted.Grant.UnitInvocationID == response.Grant.UnitInvocationID {
907 t.Fatal("restart reused invocation")
908 }
909 resp, err := client.Get(healthURL)
910 if err != nil {
911 t.Fatal("restarted endpoint", err)
912 }
913 resp.Body.Close()
914 transport.CloseIdleConnections()
915 if resp.StatusCode != 200 {
916 t.Fatal(resp.Status)
917 }
918 if checkService != nil {
919 checkService(client)
920 }
921 stop.Reservation = &next
922 if _, err := f.root.Serve(t.Context(), f.peer, stop); err != nil {
923 t.Fatal("restarted stop", err)
924 }
925 }
926 checkSibling()
927 if err := journal.CloseWriters(t.Context(), f.target); err != nil {
928 t.Fatal(err)
929 }
930 if err := lock.With(t.Context(), func(ctx context.Context) error { return retire.retire(ctx, ledger, v1) }); err == nil {
931 t.Fatal("living controller released pin")
932 }
933 join()
934 raw, _ := json.Marshal(f.target)
935 if err := os.WriteFile(filepath.Join(dir, "target.json"), raw, 0600); err != nil {
936 t.Fatal(err)
937 }
938 // Isolate the census from unrelated runner daemons, as the generation suite
939 // does. The installed process and its controller were stopped and joined in
940 // the outer namespace first; the child uses the retained root records.
941 cmd := exec.CommandContext(t.Context(), "unshare", "--mount", "--pid", "--fork", "--mount-proc", binary, "-test.run=^TestPlan14InstalledClaimLaunchRootRunner$", "-test.v", "-test.timeout=2m")
942 cmd.Env = append(os.Environ(), "PLAN14_INSTALLED_RETIRE_CHILD="+dir)
943 if output, err := cmd.CombinedOutput(); err != nil {
944 t.Fatalf("exact pin release: %v\n%s", err, output)
945 } else {
946 t.Logf("%s", output)
947 }
948 checkSibling()
949 if material != nil {
950 retireUnit := func() error {
951 return retireClaimBoundUnit(t.Context(), f.target, f.root.unitBindings, *material, f.root.Units.Gone)
952 }
953 verifyCombined := func() {}
954 if combined {
955 retireUnit, verifyCombined = plan14CombinedBoundRetirement(t, dir, f, material, startup)
956 }
957 original, err := os.ReadFile(unitPath)
958 if err != nil {
959 t.Fatal(err)
960 }
961 saved := unitPath + ".plan14-original"
962 if _, err := os.Lstat(saved); !os.IsNotExist(err) {
963 t.Fatal("fixture predecessor exists", err)
964 }
965 if err := os.Rename(unitPath, saved); err != nil {
966 t.Fatal(err)
967 }
968 t.Cleanup(func() { os.Remove(saved) })
969 files := claimUnitFiles{dir: filepath.Dir(unitPath)}
970 if err := files.create(unit, original); err != nil {
971 t.Fatal(err)
972 }
973 if err := retireUnit(); err == nil {
974 t.Fatal("same-byte successor retired")
975 }
976 after, err := os.ReadFile(unitPath)
977 if err != nil || string(after) != string(original) {
978 t.Fatal("successor changed", err)
979 }
980 if err := os.Remove(unitPath); err != nil {
981 t.Fatal(err)
982 }
983 if err := os.Rename(saved, unitPath); err != nil {
984 t.Fatal(err)
985 }
986 if err := retireUnit(); err != nil {
987 t.Fatal("unit retirement", err)
988 }
989 verifyCombined()
990 if _, err := ctl(t.Context(), "daemon-reload"); err != nil {
991 t.Fatal(err)
992 }
993 if _, err := ctl(t.Context(), "start", unit); err == nil {
994 t.Fatal("retired unit accepted start")
995 }
996 binding, err := f.root.unitBindings.read(host)
997 if err != nil || !binding.Retired {
998 t.Fatal(binding, err)
999 }
1000 for _, leaf := range []string{"launch.json", "permit.json", "exec.sock"} {
1001 if _, err := os.Lstat(filepath.Join("/run/carlos-control/exe/.claim", provision.InstanceID(host), leaf)); !os.IsNotExist(err) {
1002 t.Fatal("retired environment remains", leaf, err)
1003 }
1004 }
1005 // The root installation/claim source remains a fixture here. The durable
1006 // file transition and old-owner refusal use the real production adapters.
1007 f.root.unitBindings.handover = material.handover
1008 old.Stamp++
1009 successor, err := f.root.unitBindings.establish(t.Context(), host)
1010 if err != nil {
1011 t.Fatal("successor binding", err)
1012 }
1013 if err := material.verify(successor); err != nil {
1014 t.Fatal("successor definition", err)
1015 }
1016 if err := retireClaimBoundUnit(t.Context(), f.target, f.root.unitBindings, *material, f.root.Units.Gone); err == nil {
1017 t.Fatal("old owner retired successor")
1018 }
1019 checkSibling()
1020 }
1021}
1022
1023func plan14BoundOverrideRefusals(t *testing.T, dispatch claimUnitDispatch, host, unit string, ctl func(context.Context, ...string) (string, error)) {
1024 t.Helper()
1025 dir := filepath.Join("/etc/systemd/system", unit+".d")
1026 if err := os.Mkdir(dir, 0755); err != nil {
1027 t.Fatal(err)
1028 }
1029 t.Cleanup(func() { os.RemoveAll(dir) })
1030 for _, attack := range []struct{ name, body string }{
1031 {"90-operator.conf", "[Service]\nExecStart=\nExecStart=/usr/bin/true\n"},
1032 {"90-user.conf", "[Service]\nUser=root\n"},
1033 {"10-carlos-capabilities.conf", "[Service]\nCapabilityBoundingSet=CAP_SYS_ADMIN\n"},
1034 {"90-mount.conf", "[Service]\nBindPaths=/etc:/tenant-secrets\n"},
1035 } {
1036 path := filepath.Join(dir, attack.name)
1037 if err := os.WriteFile(path, []byte(attack.body), 0644); err != nil {
1038 t.Fatal(err)
1039 }
1040 if _, err := ctl(t.Context(), "daemon-reload"); err != nil {
1041 t.Fatal(err)
1042 }
1043 if _, err := dispatch.forStart(t.Context(), host); err == nil {
1044 t.Fatal("effective drop-in accepted", attack.name)
1045 }
1046 if err := os.Remove(path); err != nil {
1047 t.Fatal(err)
1048 }
1049 }
1050 if _, err := ctl(t.Context(), "daemon-reload"); err != nil {
1051 t.Fatal(err)
1052 }
1053}
1054
1055func plan14InstalledRetire(t *testing.T, dir string) {
1056 state, err := rootstate.Open(filepath.Join(dir, "fences"), false, false)
1057 if err != nil {
1058 t.Fatal(err)
1059 }
1060 defer state.Close()
1061 journal, err := openGenerationLaunchInventory(filepath.Join(dir, "launch.db"))
1062 if err != nil {
1063 t.Fatal(err)
1064 }
1065 defer journal.Close()
1066 generations, err := rootstate.Open(filepath.Join(dir, "generations"), false, false)
1067 if err != nil {
1068 t.Fatal(err)
1069 }
1070 defer generations.Close()
1071 ledger := &release.RootGenerations{Store: generations}
1072 records, err := ledger.Reservations(t.Context())
1073 if err != nil || len(records) < 2 {
1074 t.Fatal(records, err)
1075 }
1076 var v1s []release.GenerationReservation
1077 var v3 release.GenerationReservation
1078 for _, r := range records {
1079 if r.Binding.Version == "v1" {
1080 v1s = append(v1s, r)
1081 } else {
1082 v3 = r
1083 }
1084 }
1085 if err := os.Mkdir(filepath.Join(dir, "retire-locks"), 0700); err != nil {
1086 t.Fatal(err)
1087 }
1088 verifier := generationRetirement{guard: hostguard.Guard{DataDir: "/data", LockDir: filepath.Join(dir, "retire-locks"), Root: hostguard.NewRootAuthority(state)}, journal: journal, kernel: productionGenerationAbsence}
1089 lock := &release.ReleaseExclusion{Open: func() (rootstate.Store, error) { return rootstate.Open(filepath.Join(dir, "release"), false, false) }}
1090 for _, v1 := range v1s {
1091 if err := lock.With(t.Context(), func(ctx context.Context) error { return verifier.retire(ctx, ledger, v1) }); err != nil {
1092 t.Fatal(err)
1093 }
1094 released, err := ledger.Read(v1.Binding)
1095 if err != nil || released.Retirement == "" {
1096 t.Fatal("exact pin not released", released, err)
1097 }
1098 }
1099 pending, err := ledger.Read(v3.Binding)
1100 if err != nil || pending != v3 {
1101 t.Fatal("pending counterexample changed", pending, err)
1102 }
1103}
1104