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 "crypto/ed25519"
6 "encoding/json"
7 "errors"
8 "os"
9 "path/filepath"
10 "strings"
11 "testing"
12 "time"
13
14 "github.com/carlosframework/platform/internal/hostactors"
15 "github.com/carlosframework/platform/internal/hostbox"
16 "github.com/carlosframework/platform/internal/hostclaim"
17 execlease "github.com/carlosframework/platform/internal/hostexec"
18 "github.com/carlosframework/platform/internal/hostfloor"
19 "github.com/carlosframework/platform/internal/hostguard"
20 "github.com/carlosframework/platform/internal/hostwrite"
21 "github.com/carlosframework/platform/internal/release"
22)
23
24func TestPlan9ProductionRosterRequiresPublishedMatchingFloor(t *testing.T) {
25 key := ed25519.NewKeyFromSeed(make([]byte, 32))
26 s := release.NewDirStore(t.TempDir())
27 bucket := "deployment"
28 floor := hostfloor.Floor{Min: 1, MinRelease: "v0.1.0"}
29 if err := hostfloor.Publish(t.Context(), s, release.RootSigner(key), bucket, floor); err != nil {
30 t.Fatal(err)
31 }
32 raw, _, _ := s.Get(t.Context(), hostfloor.Key)
33 auth, _, _ := s.Get(t.Context(), hostfloor.AuthKey)
34 dir := t.TempDir()
35 policy := hostfloor.Policy{Schema: 1, DeploymentBucket: bucket, Mode: "enforced", Floor: raw, Authorization: auth}
36 body, _ := json.Marshal(policy)
37 if err := os.WriteFile(filepath.Join(dir, "policy.json"), body, 0600); err != nil {
38 t.Fatal(err)
39 }
40 row := hostactors.Box{BoxID: "box", Enrollment: strings.Repeat("a", 32), Namespace: "platform", Label: "box", Region: "ie", Pool: "default", Cloud: hostactors.Cloud{Provider: "aws", Account: "123", Region: "eu-west-1", InstanceID: "i-box"}, EvidenceSHA256: strings.Repeat("c", 64), State: "active"}
41 roster := hostactors.Roster{V: 1, DeploymentBucket: bucket, Revision: 1, RequestLimitNS: int64(hostbox.RequestLimit), Publication: "published", FloorSHA256: hostfloor.Digest(raw), Boxes: map[string]hostactors.Box{"box": row}, Consoles: map[string]hostactors.Console{}, Holds: map[string]hostactors.Reservation{}, Activations: map[string]hostactors.Reservation{}}
42 write := func() {
43 wire, err := release.SignRootEnvelope(release.RootSigner(key), roster)
44 if err != nil {
45 t.Fatal(err)
46 }
47 _, etag, _ := s.Get(t.Context(), hostactors.Key)
48 if _, err = s.Put(t.Context(), hostactors.Key, wire, etag); err != nil {
49 t.Fatal(err)
50 }
51 }
52 write()
53 provider := productionClaimRoster{Directory: hostactors.Directory{Store: s, PublicKey: key.Public().(ed25519.PublicKey), Bucket: bucket}, DataBucket: "data", FloorDir: dir}
54 actor, err := provider.Actor(t.Context(), "box")
55 if err != nil {
56 t.Fatal(err)
57 }
58 if actor.Bucket != "data" || actor.ControlBucket != bucket {
59 t.Fatal("mixed bucket authority")
60 }
61 roster.Publication = "preparing"
62 write()
63 if _, err = provider.Actor(t.Context(), "box"); err == nil {
64 t.Fatal("accepted unpublished floor")
65 }
66 roster.Publication = "published"
67 row.State = "quarantined"
68 row.QuarantineRevision = 1
69 row.LossIDs = []string{"loss"}
70 roster.Boxes["box"] = row
71 write()
72 if _, err = provider.Actor(t.Context(), "box"); err == nil {
73 t.Fatal("accepted quarantined actor")
74 }
75}
76func TestPlan9ProductionLocalServingDoesNotCreateLease(t *testing.T) {
77 dir := t.TempDir()
78 if err := os.Mkdir(filepath.Join(dir, ".hostexec"), 0700); err != nil {
79 t.Fatal(err)
80 }
81 p := productionClaimLocal{legacyParticipation: true, Resources: ClaimRuntimeResources{Mode: "agent", DataDir: dir}, slots: map[string]*claimParticipation{}}
82 c := &hostclaim.Claim{Host: "x.example", Holder: &hostclaim.Holder{Incarnation: "inc"}, Op: hostclaim.Op{Kind: hostclaim.KindServing}}
83 actor := hostbox.Actor{Box: "box", Region: "ie"}
84 selection := hostbox.Selection{Box: "box", Region: "ie", Captured: hostguard.Captured{Incarnation: "inc"}}
85 if err := p.Check(t.Context(), actor, c, selection); err != nil {
86 t.Fatal(err)
87 }
88 entries, err := os.ReadDir(filepath.Join(dir, ".hostexec"))
89 if err != nil || len(entries) != 0 {
90 t.Fatalf("serving created lease: %v %v", entries, err)
91 }
92 c.Op.Kind = hostclaim.KindLanding
93 c.Op.ID = "operation"
94 if err := p.Check(t.Context(), actor, c, selection); err == nil {
95 t.Fatal("active operation used no controller")
96 }
97 p.closed = true
98 if err := p.Check(t.Context(), actor, c, selection); !errors.Is(err, execlease.ErrLost) {
99 t.Fatalf("closed=%v", err)
100 }
101}
102func TestPlan9ProductionMissingMovesSourcesAreTypedRefusals(t *testing.T) {
103 p := productionClaimRegional{}
104 if _, err := p.View(context.Background(), hostclaim.Observed{}); err == nil {
105 t.Fatal("missing view granted")
106 }
107 if _, err := p.LeaseSet(context.Background(), "bucket", "scope"); err == nil {
108 t.Fatal("missing staging lease inventory granted")
109 }
110 if _, _, err := p.VerifyRetirement(context.Background(), hostclaim.Observed{}, nil); err == nil {
111 t.Fatal("missing archived evidence granted")
112 }
113}
114
115func TestPlan9ParticipationCleanupDoesNotWaitForItsOpeningSlot(t *testing.T) {
116 f := newRecoveryFixture(t)
117 controller := f.controller("boot-1", "controller-1", 4101, "start-1")
118 o := controller.admit(t, f)
119 controller.assembly.Reconcile = &hostbox.ReconcileAdapter{Census: &hostbox.EmptyInstallationCensus{}}
120 pending := &claimParticipation{Ready: make(chan struct{})}
121 p := &productionClaimLocal{legacyParticipation: true, assembly: &controller.assembly, slots: map[string]*claimParticipation{recoveryHost: pending}}
122 ctx, cancel := context.WithTimeout(t.Context(), 2*time.Second)
123 defer cancel()
124 if err := p.cleanup(ctx, o, execlease.Binding{}); err != nil {
125 t.Fatal(err)
126 }
127 select {
128 case <-pending.Ready:
129 t.Fatal("cleanup altered the opening slot")
130 default:
131 }
132 if len(f.receipts()) != 1 {
133 t.Fatal("cleanup omitted root stop evidence")
134 }
135}
136
137func TestPlan9ParticipationFailedOpenCanDischargeAfterPositiveCleanup(t *testing.T) {
138 t.Parallel()
139 f := newRecoveryFixture(t)
140 o := f.read()
141 if err := os.Mkdir(filepath.Join(f.guard.DataDir, ".hostexec"), 0700); err != nil {
142 t.Fatal(err)
143 }
144 identity := handoverRecoveryIdentity(t, hostbox.Actor{Box: "box-1", Enrollment: "enrollment-1", Revision: 1, Binding: "1", Region: "ie"})
145 ready := make(chan struct{})
146 close(ready)
147 slot := &claimParticipation{Ready: ready, Err: execlease.ErrLost, CleanupSucceeded: true, Captured: o,
148 Config: execlease.Config{Binding: execlease.Binding{Host: recoveryHost, Box: "box-1", Enrollment: "enrollment-1", Op: "old-op", Incarnation: o.Claim.Holder.Incarnation}, Guard: f.guard, LockDir: t.TempDir()}}
149 p := &productionClaimLocal{legacyParticipation: true, Resources: ClaimRuntimeResources{Mode: "agent", DataDir: f.guard.DataDir}, Identity: identity, assembly: &ClaimAssembly{}, lifetime: t.Context(), slots: map[string]*claimParticipation{recoveryHost: slot}}
150 if err := p.Prepare(t.Context(), o); err != nil {
151 t.Fatal(err)
152 }
153 if p.slots[recoveryHost] != nil {
154 t.Fatal("historical Open error prevented CC9 discharge")
155 }
156}
157
158func TestPlan9ParticipationCleanupSurvivesRosterWithdrawal(t *testing.T) {
159 f := newRecoveryFixture(t)
160 controller := f.controller("boot-1", "controller-1", 4101, "start-1")
161 o := controller.admit(t, f)
162 controller.assembly.Reconcile = &hostbox.ReconcileAdapter{Census: &hostbox.EmptyInstallationCensus{}}
163 p := &productionClaimLocal{legacyParticipation: true, slots: map[string]*claimParticipation{}}
164 if err := p.bind(t.Context(), &controller.assembly); err != nil {
165 t.Fatal(err)
166 }
167 // New authority fails after withdrawal. Cleanup retains only this already
168 // verified process identity; root still checks the exact captured claim.
169 controller.assembly.stopLocal = nil
170 controller.assembly.Identity = hostbox.IdentityProvider{}
171 ctx, cancel := context.WithTimeout(t.Context(), 2*time.Second)
172 defer cancel()
173 if err := p.cleanup(ctx, o, execlease.Binding{}); err != nil {
174 t.Fatalf("withdrawal prevented cleanup: %v", err)
175 }
176 if len(f.receipts()) != 1 {
177 t.Fatal("cleanup omitted root evidence")
178 }
179}
180
181func TestPlan9RootAndAdoptLeaveStaleParticipationForAgent(t *testing.T) {
182 for _, mode := range []string{"adopt", "instance-control"} {
183 t.Run(mode, func(t *testing.T) {
184 f := newRecoveryFixture(t)
185 o := f.read()
186 directory := filepath.Join(f.guard.DataDir, ".hostexec")
187 if err := os.Mkdir(directory, 0700); err != nil {
188 t.Fatal(err)
189 }
190 state := execlease.State{V: 1, Binding: execlease.Binding{Host: o.Host, Box: "box-1", Enrollment: "enrollment-1", Op: "old-op", Incarnation: o.Claim.Holder.Incarnation, BootID: "boot", ControllerID: "old"}, State: "lost", SafeUntil: 1, Expires: time.Now()}
191 raw, err := json.Marshal(state)
192 if err != nil {
193 t.Fatal(err)
194 }
195 path := filepath.Join(directory, o.Host+".json")
196 if err = os.WriteFile(path, raw, 0600); err != nil {
197 t.Fatal(err)
198 }
199 actor := hostbox.Actor{Box: "box-1", Enrollment: "enrollment-1", Revision: 1, Binding: "1", Region: "ie"}
200 // No cleanup dependencies: a root observer must refuse before touching
201 // the journal, root socket or lifetime locks.
202 p := productionClaimLocal{legacyParticipation: true, Resources: ClaimRuntimeResources{Mode: mode, DataDir: f.guard.DataDir}, assembly: &ClaimAssembly{}, lifetime: t.Context(), slots: map[string]*claimParticipation{}}
203 if err = p.prepareWith(t.Context(), actor, o); err == nil {
204 t.Fatal("root admitted without root participation")
205 }
206 after, err := os.ReadFile(path)
207 if err != nil {
208 t.Fatal(err)
209 }
210 if string(raw) != string(after) {
211 t.Fatal("root rewrote stale participation")
212 }
213 })
214 }
215}
216
217func TestPlan9ParticipationDoesNotSkipPreviousAgentState(t *testing.T) {
218 t.Parallel()
219 for _, serving := range []bool{true, false} {
220 f := newRecoveryFixture(t)
221 o := f.read()
222 if !serving {
223 o.Claim.Op = hostclaim.Op{Kind: hostclaim.KindDeleting, ID: "elsewhere", Phase: "stop", Exec: "box-2", Participants: []string{"box-2"}}
224 body, _ := json.Marshal(o.Claim)
225 key, _ := hostclaim.Key(o.Host)
226 if _, err := f.claims.Put(t.Context(), key, body, o.ETag); err != nil {
227 t.Fatal(err)
228 }
229 }
230 directory := filepath.Join(f.guard.DataDir, ".hostexec")
231 if err := os.Mkdir(directory, 0700); err != nil {
232 t.Fatal(err)
233 }
234 state := execlease.State{V: 1, Binding: execlease.Binding{Host: o.Host, Box: "box-1", Enrollment: "enrollment-1", Op: "previous", Incarnation: o.Claim.Holder.Incarnation, BootID: "boot", ControllerID: "previous"}, State: "lost", SafeUntil: 1, Expires: time.Now()}
235 body, _ := json.Marshal(state)
236 if err := os.WriteFile(filepath.Join(directory, o.Host+".json"), body, 0600); err != nil {
237 t.Fatal(err)
238 }
239 actor := hostbox.Actor{Box: "box-1", Enrollment: "enrollment-1", Revision: 1, Binding: "1", Region: "ie"}
240 p := productionClaimLocal{legacyParticipation: true, Resources: ClaimRuntimeResources{Mode: "agent", DataDir: f.guard.DataDir}, Identity: handoverRecoveryIdentity(t, actor), Store: f.claims, assembly: &ClaimAssembly{}, lifetime: t.Context(), slots: map[string]*claimParticipation{}}
241 // Missing cleanup proof must surface. Success would mean the scan silently
242 // skipped a surviving prior controller just because no slot exists in RAM.
243 if err := p.participate(t.Context()); !errors.Is(err, hostwrite.ErrUnavailable) {
244 t.Fatalf("serving=%v skipped previous controller: %v", serving, err)
245 }
246 }
247}
248
249type plan9DeadlineStore struct {
250 release.Store
251 deadline time.Time
252}
253
254func (s *plan9DeadlineStore) Get(ctx context.Context, key string) ([]byte, string, error) {
255 if key == "control/hosts/"+recoveryHost+".json" {
256 s.deadline, _ = ctx.Deadline()
257 }
258 return s.Store.Get(ctx, key)
259}
260func TestPlan9ParticipationBoundsEachClaimReadIndependently(t *testing.T) {
261 t.Parallel()
262 f := newRecoveryFixture(t)
263 actor := hostbox.Actor{Box: "box-1", Enrollment: "enrollment-1", Revision: 1, Binding: "1", Region: "ie"}
264 store := &plan9DeadlineStore{Store: f.claims}
265 p := productionClaimLocal{legacyParticipation: true, Resources: ClaimRuntimeResources{Mode: "agent", DataDir: f.guard.DataDir}, Identity: handoverRecoveryIdentity(t, actor), Store: store, slots: map[string]*claimParticipation{}}
266 before := time.Now()
267 if err := p.participate(t.Context()); err != nil {
268 t.Fatal(err)
269 }
270 if store.deadline.IsZero() || store.deadline.Before(before.Add(time.Minute)) || store.deadline.After(time.Now().Add(hostbox.RequestLimit)) {
271 t.Fatalf("claim read lacks independent request budget: %v", store.deadline)
272 }
273}
274