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 "encoding/json"
6 "errors"
7 "os"
8 "os/exec"
9 "path/filepath"
10 "testing"
11
12 "github.com/carlosframework/platform/internal/hostbox"
13 "github.com/carlosframework/platform/internal/hostclaim"
14 "github.com/carlosframework/platform/internal/hostguard"
15 "github.com/carlosframework/platform/internal/provision"
16 "github.com/carlosframework/platform/internal/release"
17)
18
19func TestPlan14GenerationRetirementBindingProcessCrash(t *testing.T) {
20 if dir := os.Getenv("PLAN14_E21_RETIRE_DIR"); dir != "" {
21 f, disk, rec := generationLaunchFixture(t)
22 state := &rollbackDisk{dir: filepath.Join(dir, "fences")}
23 disk.dir = filepath.Join(dir, "generations")
24 for _, path := range []string{state.dir, disk.dir} {
25 if err := os.Mkdir(path, 0700); err != nil {
26 t.Fatal(err)
27 }
28 }
29 f.root.Guard.Root = hostguard.NewRootAuthority(state)
30 installation := hostguard.RootInstallation{Host: rec.Host, Account: rec.Account, App: rec.App, Record: provision.RecordKey(rec.Account, rec.App, rec.Host), Incarnation: f.target.Incarnation, Stamp: f.target.Stamp, Box: f.target.Box, Enrollment: f.target.Enrollment, Migration: 1, InventoryRevision: 1}
31 if err := f.root.Guard.EstablishRoot(t.Context(), installation, func(*hostclaim.Claim) error { return nil }); err != nil {
32 t.Fatal(err)
33 }
34 in, err := f.journal.Read(t.Context(), f.target)
35 if err != nil {
36 t.Fatal(err)
37 }
38 journal, err := hostbox.OpenLaunchJournal(filepath.Join(dir, "launch.db"))
39 if err != nil {
40 t.Fatal(err)
41 }
42 defer journal.Close()
43 if err := journal.Prelaunch(t.Context(), in); err != nil {
44 t.Fatal(err)
45 }
46 f.root.Journal = journal
47 if _, err := f.root.Serve(t.Context(), f.peer, f.req); err != nil {
48 t.Fatal(err)
49 }
50 r := readGenerationLaunch(t, disk)
51 if err := f.root.stop(t.Context(), f.peer, *f.req.Reservation); err != nil {
52 t.Fatal(err)
53 }
54 if err := journal.CloseWriters(t.Context(), f.target); err != nil {
55 t.Fatal(err)
56 }
57 phase := os.Getenv("PLAN14_E21_RETIRE_PHASE")
58 disk.afterWrite = func(_ string, raw []byte) {
59 var record release.GenerationReservation
60 if err := json.Unmarshal(raw, &record); err != nil {
61 t.Fatal(err)
62 }
63 if phase == "closed" && record.Closed && record.Retirement == "" || phase == "retired" && record.Retirement != "" {
64 os.Exit(75)
65 }
66 }
67 verifier := generationRetirement{guard: f.root.Guard, journal: journal, kernel: func(context.Context, release.GenerationReservation) (string, error) {
68 if phase == "kernel" {
69 os.Exit(75)
70 }
71 return r.Binding.Digest, nil
72 }}
73 if err := verifier.retire(t.Context(), &release.RootGenerations{Store: disk}, r); err != nil {
74 t.Fatal(err)
75 }
76 t.Fatal("crash boundary not reached")
77 }
78 for _, phase := range []string{"closed", "kernel", "retired"} {
79 t.Run(phase, func(t *testing.T) {
80 dir := t.TempDir()
81 cmd := exec.Command(os.Args[0], "-test.run=^TestPlan14GenerationRetirementBindingProcessCrash$", "-test.count=1")
82 cmd.Dir = dir
83 cmd.Env = append(os.Environ(), "PLAN14_E21_RETIRE_DIR="+dir, "PLAN14_E21_RETIRE_PHASE="+phase)
84 out, err := cmd.CombinedOutput()
85 var exit *exec.ExitError
86 if !errors.As(err, &exit) || exit.ExitCode() != 75 {
87 t.Fatalf("child: %v %s", err, out)
88 }
89 disk := &generationDisk{rollbackDisk: &rollbackDisk{dir: filepath.Join(dir, "generations")}}
90 r := readGenerationLaunch(t, disk)
91 g := &release.RootGenerations{Store: disk}
92 got, err := g.Read(r.Binding)
93 if err != nil || !got.Closed || (got.Retirement != "") != (phase == "retired") {
94 t.Fatalf("lost restriction: %+v %v", got, err)
95 }
96 if _, err := g.Reserve(r.Binding); err == nil {
97 t.Fatal("crash reopened launch")
98 }
99 journal, err := hostbox.OpenLaunchJournal(filepath.Join(dir, "launch.db"))
100 if err != nil {
101 t.Fatal(err)
102 }
103 defer journal.Close()
104 verifier := generationRetirement{guard: hostguard.Guard{DataDir: filepath.Join(dir, "data"), LockDir: t.TempDir(), Root: hostguard.NewRootAuthority(&rollbackDisk{dir: filepath.Join(dir, "fences")})}, journal: journal, kernel: func(context.Context, release.GenerationReservation) (string, error) { return "", os.ErrPermission }}
105 if phase != "retired" {
106 if err := verifier.retire(t.Context(), g, r); err == nil {
107 t.Fatal("reopened without kernel evidence")
108 }
109 got, err = g.Read(r.Binding)
110 if err != nil || got.Retirement != "" {
111 t.Fatal("failed reconciliation released pin", err)
112 }
113 }
114 verifier.kernel = func(context.Context, release.GenerationReservation) (string, error) { return r.Binding.Digest, nil }
115 if err := verifier.retire(t.Context(), g, r); err != nil {
116 t.Fatal(err)
117 }
118 got, err = g.Read(r.Binding)
119 if err != nil || got.Retirement == "" {
120 t.Fatal("verified recovery not retained", err)
121 }
122 })
123 }
124}
125