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
2
3package host
4
5import (
6 "bytes"
7 "context"
8 "crypto/ed25519"
9 "errors"
10 "path/filepath"
11 "testing"
12 "time"
13
14 "github.com/carlosframework/platform/internal/hostbox"
15 "github.com/carlosframework/platform/internal/hostguard"
16 "github.com/carlosframework/platform/internal/hostops"
17 "github.com/carlosframework/platform/internal/registry"
18 "github.com/carlosframework/platform/internal/release"
19)
20
21// S1: the migration census reports a legacy route row as the box's route
22// installation of its frozen record, so backfill-adopt can give the host
23// to this box. Every other row shape refuses the census.
24func TestRootMigrationCensusReportsLegacyRoutes(t *testing.T) {
25 store, _, owners, _ := fix4JournalFixture(t)
26 actor, err := owners.Identity.Verify(t.Context())
27 if err != nil {
28 t.Fatal(err)
29 }
30 key := ed25519.NewKeyFromSeed(bytes.Repeat([]byte{1}, ed25519.SeedSize))
31 plan := hostops.BackfillPlan{V: 1, ID: "migration", Phase: "collecting", DirectoryRevision: 1, CreatedAt: time.Now(), Hosts: map[string]hostops.BackfillHost{}}
32 records := map[string]hostops.Record{}
33 for _, r := range leoRecords(t) {
34 if _, err := store.Put(t.Context(), r.Holder.Key, r.Body, ""); err != nil {
35 t.Fatal(err)
36 }
37 r, err = hostops.ReadRecord(t.Context(), store, r.Holder.Key)
38 if err != nil {
39 t.Fatal(err)
40 }
41 records[r.Holder.Host] = r
42 plan.Hosts[r.Holder.Host] = hostops.BackfillHost{Key: r.Holder.Key, ETag: r.ETag, Acquisition: r.Holder.Acquisition}
43 }
44 raw, err := release.SignRootEnvelope(release.RootSigner(key), plan)
45 if err != nil {
46 t.Fatal(err)
47 }
48 if _, err := store.Put(t.Context(), hostops.BackfillKey, raw, ""); err != nil {
49 t.Fatal(err)
50 }
51 dataDir := t.TempDir()
52 var rows []registry.Route
53 c := rootMigrationCollector{store: store, directory: owners.RetirementAuthority.Directory, identity: rootMigrationIdentity{Deployment: actor.ControlBucket, Box: actor.Box, Enrollment: actor.Enrollment, Generation: 1},
54 seeds: &migrationSeeds{}, installations: func(context.Context) ([]hostguard.FenceView, error) { return nil, nil }, launches: func(context.Context) ([]hostbox.LaunchInventory, error) { return nil, nil },
55 reservations: func(context.Context) ([]release.GenerationReservation, error) { return nil, nil }, boot: func() (string, error) { return "boot", nil },
56 routes: func() ([]registry.Route, error) { return rows, nil }, dataDir: dataDir}
57 rows = []registry.Route{leoRoute(dataDir)}
58 s, err := c.collect(t.Context())
59 if err != nil {
60 t.Fatal(err)
61 }
62 if err := s.preflight(c.identity); err != nil {
63 t.Fatal(err)
64 }
65 inc, _ := records[leoClaimtrial].Incarnation()
66 want := hostops.CensusInstallation{Host: leoClaimtrial, Account: "bab", App: "claimtrial", Family: "instance", Key: records[leoClaimtrial].Holder.Key, Incarnation: inc, Route: true}
67 if len(s.Report.Installations) != 1 || s.Report.Installations[0] != want || len(s.Items) != 0 {
68 t.Fatalf("report = %+v, items %+v", s.Report.Installations, s.Items)
69 }
70 if err := hostops.PublishCensusReport(t.Context(), store, c.directory, s.Report, owners.EvidenceKey); err != nil {
71 t.Fatal(err)
72 }
73 // No registry listing reports nothing, as before.
74 without := c
75 without.routes = nil
76 if s, err := without.collect(t.Context()); err != nil || len(s.Report.Installations) != 0 {
77 t.Fatalf("no listing = %+v, %v", s.Report.Installations, err)
78 }
79 for name, mutate := range map[string]func(*registry.Route){
80 "unit": func(r *registry.Route) { r.Unit = "app.service" },
81 "backing": func(r *registry.Route) { r.Backing = "unit" },
82 "replica": func(r *registry.Route) { r.Replica = true },
83 "seed": func(r *registry.Route) { r.Seed = "seed" },
84 "tunnel": func(r *registry.Route) { r.Tunnel = "t" },
85 "sidecar": func(r *registry.Route) { r.Kind = "sidecar" },
86 "service": func(r *registry.Route) { r.Kind = "service" },
87 "flat layout": func(r *registry.Route) { r.DBPath = filepath.Join(dataDir, r.Host+".db") },
88 "claimed row": func(r *registry.Route) {
89 r.ClaimIncarnation, r.ClaimStamp, r.ClaimOwner, r.ClaimRowID = "inc", 1, "owner", "row"
90 },
91 "another app": func(r *registry.Route) { r.App = "wakecheck" },
92 "another account": func(r *registry.Route) { r.Account = "zzz" },
93 "no version": func(r *registry.Route) { r.Version = "" },
94 "no record": func(r *registry.Route) {
95 r.Host, r.DBPath = "other.example.com", filepath.Join(dataDir, "instances", "other.example.com", "other.example.com.db")
96 },
97 } {
98 t.Run(name, func(t *testing.T) {
99 row := leoRoute(dataDir)
100 mutate(&row)
101 rows = []registry.Route{row}
102 if _, err := c.collect(t.Context()); !errors.Is(err, errRootMigrationBusy) {
103 t.Fatalf("collect = %v, want the census refused", err)
104 }
105 })
106 }
107}
108
109// The dev trial deletes every instance record before the window, so the
110// collecting plan freezes zero hosts ("hosts":{}). The migration census
111// accepts it on a box with no route rows, reports zero installations, and
112// the signer's backfill-done closes it over that report.
113func TestRootMigrationCensusAcceptsAZeroHostPlan(t *testing.T) {
114 store, _, owners, _ := fix4JournalFixture(t)
115 actor, err := owners.Identity.Verify(t.Context())
116 if err != nil {
117 t.Fatal(err)
118 }
119 key := ed25519.NewKeyFromSeed(bytes.Repeat([]byte{1}, ed25519.SeedSize))
120 signer := release.RootSigner(key)
121 directory := owners.RetirementAuthority.Directory
122 roster, err := directory.Read(t.Context())
123 if err != nil {
124 t.Fatal(err)
125 }
126 plan := hostops.BackfillPlan{V: 1, ID: "migration", Phase: "collecting", DirectoryRevision: roster.Roster.Revision, CreatedAt: time.Now().Add(-time.Minute), Hosts: map[string]hostops.BackfillHost{}}
127 raw, err := release.SignRootEnvelope(signer, plan)
128 if err != nil {
129 t.Fatal(err)
130 }
131 tag, err := store.Put(t.Context(), hostops.BackfillKey, raw, "")
132 if err != nil {
133 t.Fatal(err)
134 }
135 c := rootMigrationCollector{store: store, directory: directory, identity: rootMigrationIdentity{Deployment: actor.ControlBucket, Box: actor.Box, Enrollment: actor.Enrollment, Generation: 1},
136 seeds: &migrationSeeds{}, installations: func(context.Context) ([]hostguard.FenceView, error) { return nil, nil }, launches: func(context.Context) ([]hostbox.LaunchInventory, error) { return nil, nil },
137 reservations: func(context.Context) ([]release.GenerationReservation, error) { return nil, nil }, boot: func() (string, error) { return "boot", nil },
138 routes: func() ([]registry.Route, error) { return nil, nil }, dataDir: t.TempDir()}
139 s, err := c.collect(t.Context())
140 if err != nil {
141 t.Fatal("collect over a zero-host plan:", err)
142 }
143 if err := s.preflight(c.identity); err != nil {
144 t.Fatal(err)
145 }
146 if s.Report.Installations == nil || len(s.Report.Installations) != 0 || len(s.Records) != 0 || len(s.Items) != 0 {
147 t.Fatalf("zero-host census = %+v", s)
148 }
149 if err := hostops.PublishCensusReport(t.Context(), store, directory, s.Report, owners.EvidenceKey); err != nil {
150 t.Fatal(err)
151 }
152 b := &hostops.Backfill{Store: store, Directory: directory}
153 unreported, err := b.Done(t.Context(), signer, tag)
154 if err != nil {
155 t.Fatal("done over the zero-host census:", err)
156 }
157 if err := hostops.CheckCreateFreeze(t.Context(), store, directory.PublicKey); err != nil {
158 t.Fatal(err)
159 }
160 t.Logf("done over zero hosts; unreported %v", unreported)
161}
162