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 "fmt"
8 "os"
9 "path/filepath"
10 "strings"
11 "sync"
12 "testing"
13 "time"
14
15 "github.com/carlosframework/platform/internal/hostfloor"
16 "github.com/carlosframework/platform/internal/registry"
17 "github.com/carlosframework/platform/internal/release"
18 "github.com/carlosframework/platform/internal/rootparticipation"
19 "github.com/carlosframework/platform/internal/rootstate"
20)
21
22func TestPlan14LaunchVerifierToken(t *testing.T) {
23 lock := &plan14ReleaseStore{}
24 x := &release.ReleaseExclusion{Open: func() (rootstate.Store, error) { return lock, nil }}
25 disk := &rollbackDisk{dir: t.TempDir()}
26 reads := 0
27 v := rootLaunchVerifier{exclude: x, open: func() (rootstate.Store, error) { reads++; return disk, nil }, reconcile: generationReconciler{
28 read: func(ctx context.Context) (generationInventory, error) {
29 if ctx.Value(launchRecoveryCensusKey{}) == true {
30 t.Fatal("ordinary verification admitted pending acquisition")
31 }
32 return generationInventory{Census: rootparticipation.Census{Schema: 1, Box: "box", Enrollment: "en", Migration: 1, Revision: 1, LaunchRevision: 7, Complete: true}}, nil
33 },
34 kernel: func(context.Context) (string, string, error) { return "boot", strings.Repeat("a", 64), nil },
35 }}
36 if !errors.Is(v.verify(t.Context(), 7), rootparticipation.ErrUnavailable) || reads != 0 {
37 t.Fatal("missing token reached authority")
38 }
39 foreign := &release.ReleaseExclusion{Open: x.Open}
40 if err := foreign.With(t.Context(), func(ctx context.Context) error { return v.verify(ctx, 7) }); !errors.Is(err, rootparticipation.ErrUnavailable) || reads != 0 {
41 t.Fatal("foreign token reached authority", err)
42 }
43 var stale context.Context
44 if err := x.With(t.Context(), func(ctx context.Context) error { stale = ctx; return v.verify(ctx, 7) }); err != nil {
45 t.Fatal(err)
46 }
47 if !errors.Is(v.verify(stale, 7), rootparticipation.ErrUnavailable) {
48 t.Fatal("stale token accepted")
49 }
50 if err := x.With(t.Context(), func(ctx context.Context) error { return v.verify(ctx, 8) }); err == nil {
51 t.Fatal("wrong launch revision accepted")
52 }
53}
54
55func TestPlan14LaunchVerifierCallerOrder(t *testing.T) {
56 t.Parallel()
57 for _, name := range []string{"idle", "begin", "end", "recover", "activation", "floor-begin", "floor-end", "system-begin", "system-clear", "system-recovery", "guard-close", "guard-rollback"} {
58 t.Run(name, func(t *testing.T) {
59 lock := &plan14ReleaseStore{}
60 x := &release.ReleaseExclusion{Open: func() (rootstate.Store, error) { return lock, nil }}
61 l := updateParticipationFixture(t)
62 l.State = &d3ParticipationDisk{Store: l.State}
63 l.ExcludeLaunches = x.With
64 verified := 0
65 l.VerifyLaunches = func(ctx context.Context, _ uint64) error {
66 verified++
67 if rootparticipation.IsAcquisitionRecovery(ctx) != (name == "recover") {
68 t.Error("wrong acquisition recovery scope")
69 }
70 if err := x.Check(ctx); err != nil {
71 return rootparticipation.ErrUnavailable
72 }
73 return nil
74 }
75 if name == "end" || name == "floor-end" || name == "system-clear" || name == "guard-close" || name == "guard-rollback" {
76 if err := x.With(t.Context(), func(ctx context.Context) error { return l.BeginUpdate(ctx, "update") }); err != nil {
77 t.Fatal(err)
78 }
79 }
80 if name == "recover" {
81 c, err := l.Census()
82 if err != nil {
83 t.Fatal(err)
84 }
85 c.Pending = "acquisition"
86 raw, _ := json.Marshal(c)
87 if err := l.State.Write("census.json", raw); err != nil {
88 t.Fatal(err)
89 }
90 }
91 floor := hostfloor.Disk{Dir: t.TempDir(), Participation: l}
92 a := &systemApplier{participation: l, optDir: t.TempDir(), dataDir: t.TempDir()}
93 if name == "guard-close" || name == "guard-rollback" {
94 armLocalFloor(t, a.dataDir, "v0.30.0")
95 a.openRegistry = func() (*registry.Registry, error) { return registry.Open(filepath.Join(a.dataDir, "registry.db")) }
96 }
97 run := func(ctx context.Context) error {
98 switch name {
99 case "idle":
100 return floor.ControllersIdle(ctx)
101 case "begin":
102 return floor.BeginParticipation(ctx, "update", a.dataDir, nil, func() error { t.Error("legacy selected"); return nil })
103 case "end":
104 return floor.EndParticipation(ctx, "update", nil)
105 case "recover":
106 return x.With(ctx, l.RecoverAcquisition)
107 case "activation":
108 return l.WithLaunchExclusion(ctx, func(ctx context.Context) error { return l.ActivationReady(ctx) })
109 case "floor-begin":
110 return floor.Begin(ctx, "update", a.dataDir)
111 case "floor-end":
112 return floor.EndParticipation(ctx, "update", nil)
113 case "system-begin":
114 return a.beginExecutableChange(ctx, "new", time.Now())
115 case "system-clear":
116 return a.clearUpdateBegun(ctx)
117 case "guard-close", "guard-rollback":
118 return a.activationGuard(ctx, "v0.32.0", "v0.31.0", func(ctx context.Context) error { return a.clearUpdateBegun(ctx) })
119 case "system-recovery":
120 return a.controllersRecovered(ctx)
121 }
122 return nil
123 }
124 held, leave, holderDone := make(chan struct{}), make(chan struct{}), make(chan error, 1)
125 go func() {
126 holderDone <- x.With(t.Context(), func(ctx context.Context) error {
127 close(held)
128 <-leave
129 if name == "guard-close" || name == "guard-rollback" {
130 if err := withFloorLock(a.dataDir, true, func(*movesDir) error { return nil }); err != nil {
131 return err
132 }
133 }
134 unlock, err := l.State.Lock(ctx, "transaction.lock")
135 if err == nil {
136 unlock()
137 }
138 return err
139 })
140 }()
141 <-held
142 done := make(chan error, 1)
143 go func() { done <- run(t.Context()) }()
144 select {
145 case err := <-done:
146 t.Fatal("caller escaped held release", err)
147 case <-time.After(15 * time.Millisecond):
148 }
149 close(leave)
150 select {
151 case err := <-holderDone:
152 if err != nil {
153 t.Fatal(err)
154 }
155 case <-time.After(2 * time.Second):
156 t.Fatal("release holder deadlocked")
157 }
158 select {
159 case err := <-done:
160 if err != nil {
161 t.Fatal(err)
162 }
163 case <-time.After(2 * time.Second):
164 t.Fatal("caller deadlocked")
165 }
166 if verified == 0 {
167 t.Fatal("verifier not reached")
168 }
169 })
170 }
171}
172
173type d3ParticipationDisk struct {
174 rootstate.Store
175 mu sync.Mutex
176}
177
178func (d *d3ParticipationDisk) Lock(ctx context.Context, name string) (func(), error) {
179 d.mu.Lock()
180 return d.mu.Unlock, nil
181}
182
183func TestPlan14LaunchVerifierNoCensus(t *testing.T) {
184 t.Setenv("CARLOS_HOST_CLAIM", "")
185 l := updateParticipationFixture(t)
186 if err := l.State.Remove("census.json"); err != nil {
187 t.Fatal(err)
188 }
189 l.ExcludeLaunches = func(context.Context, func(context.Context) error) error {
190 t.Fatal("no-census box acquired release exclusion")
191 return nil
192 }
193 l.VerifyLaunches = func(context.Context, uint64) error { t.Fatal("no-census box consulted verifier"); return nil }
194 floor := hostfloor.Disk{Dir: t.TempDir(), Participation: l}
195 data := t.TempDir()
196 if err := os.Mkdir(filepath.Join(data, ".hostexec"), 0700); err != nil {
197 t.Fatal(err)
198 }
199 if err := floor.ControllersIdle(t.Context(), data); err != nil {
200 t.Fatal(err)
201 }
202 if err := floor.Begin(t.Context(), "legacy", data); err != nil {
203 t.Fatal(err)
204 }
205 if err := floor.EndParticipation(t.Context(), "legacy", nil); err != nil {
206 t.Fatal(err)
207 }
208 a := &systemApplier{participation: l, optDir: t.TempDir(), dataDir: data}
209 if err := a.beginExecutableChange(t.Context(), "new", time.Now()); err != nil {
210 t.Fatal(err)
211 }
212 if err := a.clearUpdateBegun(t.Context()); err != nil {
213 t.Fatal(err)
214 }
215 if err := a.checkUnarmedParticipationRecovery(t.Context()); err != nil {
216 t.Fatal(err)
217 }
218}
219
220func TestPlan14SystemDeadlineNoCensusUnchanged(t *testing.T) {
221 for _, census := range []bool{false, true} {
222 t.Run(fmt.Sprint(census), func(t *testing.T) {
223 l := updateParticipationFixture(t)
224 if !census {
225 if err := l.State.Remove("census.json"); err != nil {
226 t.Fatal(err)
227 }
228 }
229 f := newApplyFixture(t, "stable", "old")
230 f.a.participation = l
231 f.a.floorHook = func(*systemApplier) error { return context.DeadlineExceeded }
232 err := f.a.Run(t.Context(), false)
233 if !errors.Is(err, context.DeadlineExceeded) || errors.Is(err, errSystemDeferred) != census {
234 t.Fatal("wrong timeout disposition", err)
235 }
236 })
237 }
238}
239