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 "bytes"
5 "context"
6 "crypto/ed25519"
7 "encoding/json"
8 "errors"
9 "testing"
10 "time"
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/hostops"
16 "github.com/carlosframework/platform/internal/registry"
17 "github.com/carlosframework/platform/internal/release"
18)
19
20type migrationSeeds struct {
21 journal []string
22 routes []registry.Route
23}
24
25func (s *migrationSeeds) ImportJournalHosts() ([]string, error) { return s.journal, nil }
26func (s *migrationSeeds) List() ([]registry.Route, error) { return s.routes, nil }
27
28func TestPlan14RootMigrationCensusSeeds(t *testing.T) {
29 store, _, owners, _ := fix4JournalFixture(t)
30 actor, err := owners.Identity.Verify(t.Context())
31 if err != nil {
32 t.Fatal(err)
33 }
34 key := ed25519.NewKeyFromSeed(bytes.Repeat([]byte{1}, ed25519.SeedSize))
35 plan := hostops.BackfillPlan{V: 1, ID: "migration", Phase: "collecting", DirectoryRevision: 1, CreatedAt: time.Now(), Hosts: map[string]hostops.BackfillHost{}}
36 raw, err := release.SignRootEnvelope(release.RootSigner(key), plan)
37 if err != nil {
38 t.Fatal(err)
39 }
40 if _, err := store.Put(t.Context(), hostops.BackfillKey, raw, ""); err != nil {
41 t.Fatal(err)
42 }
43 seeds := &migrationSeeds{}
44 c := rootMigrationCollector{store: store, directory: owners.RetirementAuthority.Directory, identity: rootMigrationIdentity{Deployment: actor.ControlBucket, Box: actor.Box, Enrollment: actor.Enrollment, Generation: 1}, seeds: seeds, installations: func(context.Context) ([]hostguard.FenceView, error) { return nil, nil }, launches: func(context.Context) ([]hostbox.LaunchInventory, error) { return nil, nil }, reservations: func(context.Context) ([]release.GenerationReservation, error) { return nil, nil }, boot: func() (string, error) { return "boot", nil }}
45 s, err := c.collect(t.Context())
46 if err != nil {
47 t.Fatal(err)
48 }
49 if err := s.preflight(c.identity); err != nil {
50 t.Fatal(err)
51 }
52 if s.Report.Seeds == nil || s.Report.Seeds.Journaled == nil || s.Report.Seeds.Gated == nil {
53 t.Fatal("missing seed census", s.Report)
54 }
55 if err := hostops.PublishCensusReport(t.Context(), store, c.directory, s.Report, owners.EvidenceKey); err != nil {
56 t.Fatal(err)
57 }
58 keys, err := store.List(t.Context(), "control/host-census/")
59 if err != nil || len(keys) != 1 {
60 t.Fatal(keys, err)
61 }
62 body, _, err := store.Get(t.Context(), keys[0])
63 if err != nil {
64 t.Fatal(err)
65 }
66 var envelope hostops.CensusEnvelope
67 if json.Unmarshal(body, &envelope) != nil || envelope.Report.Seeds == nil {
68 t.Fatal("not a real signed seeds report")
69 }
70 for _, kind := range []string{"journal", "gate", "unknown-launch", "reservation"} {
71 t.Run(kind, func(t *testing.T) {
72 cc := c
73 defer func() { seeds.journal = nil; seeds.routes = nil }()
74 switch kind {
75 case "journal":
76 seeds.journal = []string{"a.example"}
77 case "gate":
78 seeds.routes = []registry.Route{{Host: "a.example", ImportGate: "closed"}}
79 case "unknown-launch":
80 cc.launches = func(context.Context) ([]hostbox.LaunchInventory, error) {
81 return []hostbox.LaunchInventory{{Target: hostguard.FenceTarget{Host: "unknown.example", Op: "move"}}}, nil
82 }
83 case "reservation":
84 cc.reservations = func(context.Context) ([]release.GenerationReservation, error) {
85 return []release.GenerationReservation{{}}, nil
86 }
87 }
88 s, err := cc.collect(t.Context())
89 if err == nil {
90 err = s.preflight(c.identity)
91 }
92 if err == nil {
93 t.Fatal("unsafe census accepted")
94 }
95 })
96 }
97}
98
99// Census condition 1 (fix round 2): journal-migrate's collector refuses a
100// settled installation with ErrFenced, as MigrationInstallations refused
101// every quarantined record before settlement existed. The installation is
102// one the claim still names on this box, so without the refusal it would
103// enter the migration snapshot as a live, local item. The control is the
104// same view, not settled: it does enter the snapshot.
105func TestPlan14RootMigrationCollectorRefusesSettled(t *testing.T) {
106 ctx := t.Context()
107 store, _, owners, _ := fix4JournalFixture(t)
108 actor, err := owners.Identity.Verify(ctx)
109 if err != nil {
110 t.Fatal(err)
111 }
112 r := leoRecords(t)[0]
113 var body struct {
114 Region string `json:"region"`
115 CreatedAt time.Time `json:"created_at"`
116 }
117 if err := json.Unmarshal(r.Body, &body); err != nil {
118 t.Fatal(err)
119 }
120 if _, err := store.Put(ctx, r.Holder.Key, r.Body, ""); err != nil {
121 t.Fatal(err)
122 }
123 absent, err := hostclaim.Read(ctx, store, r.Holder.Host)
124 if err != nil {
125 t.Fatal(err)
126 }
127 o, err := hostclaim.Update(ctx, store, absent, hostclaim.BeginCreate(hostclaim.NewHolder{Family: "instance", Key: r.Holder.Key, Account: r.Holder.Account, App: r.Holder.App, Host: r.Holder.Host, Region: "ie", RecordRegion: body.Region, CreatedAt: body.CreatedAt}, r.Body, "create", "console", time.Now()))
128 if err != nil {
129 t.Fatal(err)
130 }
131 if o, err = hostclaim.Update(ctx, store, o, hostclaim.CompleteCreate("create", actor.Box)); err != nil {
132 t.Fatal(err)
133 }
134 // TEST-ONLY transition: a writer session on this box.
135 if o, err = hostclaim.Update(ctx, store, o, func(c *hostclaim.Claim) error { c.Holder.Session, c.Holder.WriterBox = 1, actor.Box; return nil }); err != nil {
136 t.Fatal(err)
137 }
138 r, err = hostops.ReadRecord(ctx, store, r.Holder.Key)
139 if err != nil {
140 t.Fatal(err)
141 }
142 plan := hostops.BackfillPlan{V: 1, ID: "migration", Phase: "collecting", DirectoryRevision: 1, CreatedAt: time.Now(), Hosts: map[string]hostops.BackfillHost{r.Holder.Host: {Key: r.Holder.Key, ETag: r.ETag, Acquisition: r.Holder.Acquisition}}}
143 raw, err := release.SignRootEnvelope(release.RootSigner(ed25519.NewKeyFromSeed(bytes.Repeat([]byte{1}, ed25519.SeedSize))), plan)
144 if err != nil {
145 t.Fatal(err)
146 }
147 if _, err := store.Put(ctx, hostops.BackfillKey, raw, ""); err != nil {
148 t.Fatal(err)
149 }
150 h := o.Claim.Holder
151 view := hostguard.FenceView{Schema: 1, Host: o.Host, Captured: hostguard.Captured{Incarnation: h.Incarnation, Stamp: h.Stamp}, Installation: hostguard.RootInstallation{Host: o.Host, Incarnation: h.Incarnation, Stamp: h.Stamp, Account: h.Account, App: h.App, Record: h.Key, Box: actor.Box, Enrollment: actor.Enrollment, Migration: 1, InventoryRevision: 2}}
152 for _, settled := range []bool{false, true} {
153 v := view
154 v.Settled = settled
155 c := rootMigrationCollector{store: store, directory: owners.RetirementAuthority.Directory, identity: rootMigrationIdentity{Deployment: actor.ControlBucket, Box: actor.Box, Enrollment: actor.Enrollment, Generation: 1}, seeds: &migrationSeeds{}, installations: func(context.Context) ([]hostguard.FenceView, error) { return []hostguard.FenceView{v}, nil }, launches: func(context.Context) ([]hostbox.LaunchInventory, error) { return nil, nil }, reservations: func(context.Context) ([]release.GenerationReservation, error) { return nil, nil }, boot: func() (string, error) { return "boot", nil }}
156 s, err := c.collect(ctx)
157 if settled {
158 if !errors.Is(err, hostguard.ErrFenced) {
159 t.Fatalf("collector admitted a settled installation: %v, items %+v", err, s.Items)
160 }
161 continue
162 }
163 if err != nil || len(s.Items) != 1 || s.Items[0].Installation != view.Installation {
164 t.Fatalf("control: the unsettled view: %v, items %+v", err, s.Items)
165 }
166 }
167}
168