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 "context"
7 "encoding/json"
8 "github.com/carlosframework/platform/internal/hostbox"
9 "github.com/carlosframework/platform/internal/hostclaim"
10 "github.com/carlosframework/platform/internal/hostguard"
11 "os"
12 "path/filepath"
13 "reflect"
14 "testing"
15)
16
17func TestPlan14ServingHandoverPins(t *testing.T) {
18 f := newServingRootFixture(t, true)
19 root := servingHandoverRoot(t, f)
20 f.mutateRoot(func(m map[string]any) {
21 m["Installation"].(map[string]any)["Artifacts"] = map[string]any{"Schema": 1, "StateDirectory": true}
22 })
23 if os.Getenv("PLAN14_R11_HANDOVER_ROOT") == "1" {
24 root.Reconcile.Retirement.JournalDir = claimRetireJournalDir("", "")
25 }
26 readHandovers := func() ([]hostbox.GenerationHandover, error) {
27 if os.Getenv("PLAN14_R11_HANDOVER_ROOT") == "1" {
28 return productionGenerationHandovers(t.Context())
29 }
30 return generationReadHandovers(t.Context(), &rollbackDisk{dir: filepath.Join(root.Reconcile.Retirement.JournalDir, "serving")})
31 }
32 next := hostbox.ColdSuccessor{Host: f.current.Host, Incarnation: f.current.Incarnation, Box: f.actor.Box, Enrollment: f.actor.Enrollment, DirectoryRevision: f.actor.Binding, ETag: f.f.read().ETag, Generation: f.current.Generation, Stamp: f.current.Stamp, Session: f.current.Session}
33 if err := root.servingHandover().Run(t.Context(), f.receipt.Target, next); err != nil {
34 t.Fatal(err)
35 }
36 installs, err := f.f.guard.Root.GenerationInstallations(t.Context())
37 if err != nil {
38 t.Fatal(err)
39 }
40 payloads, err := f.f.journal.GenerationPayloads(t.Context())
41 if err != nil {
42 t.Fatal(err)
43 }
44 snapshot := generationInventory{Installations: installs, Payloads: payloads}
45 snapshot.Handovers, err = readHandovers()
46 if err != nil {
47 t.Fatal(err)
48 }
49
50 if _, err = generationTransfers(t.Context(), snapshot); err != nil {
51 t.Fatal("transfers", err)
52 }
53 if _, err = generationRegistryInventory(t.Context(), root.Reconcile.Registry, snapshot); err != nil {
54 t.Fatal("registry after actual serving handover", err)
55 }
56 physical, err := generationPhysicalPayloads(t.Context(), snapshot)
57 if err != nil {
58 t.Fatal(err)
59 }
60 if physical[0].Inventory.Entries[0].Identity != physical[1].Inventory.Entries[0].Identity {
61 t.Fatal("predecessor did not follow verified persistent identity")
62 }
63 t.Run("recreated-namespace", func(t *testing.T) {
64 raw, _ := json.Marshal(snapshot)
65 var recreated generationInventory
66 if err := json.Unmarshal(raw, &recreated); err != nil {
67 t.Fatal(err)
68 }
69 for i, e := range recreated.Payloads[0].Inventory.Entries {
70 if e.Kind != "path" {
71 continue
72 }
73 current := recreated.Payloads[1].Inventory.Entries[i].Identity
74 old := current
75 old.Mount += 100
76 recreated.Payloads[0].Inventory.Entries[i].Identity = old
77 recreated.Handovers[0].Inventory.Entries[i].Identity = old
78 }
79 if _, err := generationRegistryInventory(t.Context(), root.Reconcile.Registry, recreated); err != nil {
80 t.Fatal("recreated namespace transfer", err)
81 }
82 if _, err := generationPhysicalPayloads(t.Context(), recreated); err != nil {
83 t.Fatal("recreated namespace payloads", err)
84 }
85 })
86 original, _ := json.Marshal(snapshot)
87 for _, attack := range []string{"missing journal", "prepared", "successor", "barrier", "receipt", "retired", "path", "row", "revision", "duplicate", "cycle", "old boot"} {
88 t.Run(attack, func(t *testing.T) {
89 var bad generationInventory
90 if err := json.Unmarshal(original, &bad); err != nil {
91 t.Fatal(err)
92 }
93 switch attack {
94 case "missing journal":
95 bad.Handovers = nil
96 case "prepared":
97 bad.Handovers[0].Phase = "prepared"
98 case "successor":
99 bad.Handovers[0].Successor.Stamp++
100 case "barrier":
101 bad.Handovers[0].Barrier.ControllerID = "other"
102 case "receipt":
103 bad.Handovers[0].Receipt.Bytes = []byte("other")
104 case "retired":
105 bad.Installations[0].Retired = nil
106 case "path":
107 bad.Payloads[1].Inventory.Entries[0].Inode++
108 case "row":
109 bad.Payloads[1].Inventory.Entries[1].RowIdentity = "other"
110 case "revision":
111 bad.Payloads[0].Revision++
112 case "duplicate":
113 bad.Handovers = append(bad.Handovers, bad.Handovers[0])
114 case "cycle":
115 bad.Handovers[0].Successor.Stamp = bad.Handovers[0].Prior.Stamp
116 case "old boot":
117 bad.Payloads[1].Inventory.Entries[0].Identity.Boot = "other-boot"
118 }
119 if _, err := generationRegistryInventory(t.Context(), root.Reconcile.Registry, bad); err == nil {
120 t.Fatal("accepted", attack)
121 }
122 })
123 }
124 before := f.f.read()
125 receipt := servingStop(t, f.f, before, f.receipt.Target.BootID)
126 admitted, err := hostclaim.Update(t.Context(), f.f.claims, before, hostclaim.AdmitWriter(hostclaim.Admission{ObservedSession: before.Claim.Holder.Session, ObservedGeneration: before.Claim.Holder.Generation, Box: f.actor.Box, Region: f.actor.Region, PredecessorRegion: f.actor.Region, Mode: "cold", NewGeneration: "G3"}))
127 if err != nil {
128 t.Fatal(err)
129 }
130 next.ETag, next.Generation, next.Stamp, next.Session = admitted.ETag, admitted.Claim.Holder.Generation, admitted.Claim.Holder.Stamp, admitted.Claim.Holder.Session
131 if err = root.servingHandover().Run(t.Context(), receipt.Target, next); err != nil {
132 t.Fatal("second transfer", err)
133 }
134 snapshot.Installations, err = f.f.guard.Root.GenerationInstallations(t.Context())
135 if err != nil {
136 t.Fatal(err)
137 }
138 snapshot.Payloads, err = f.f.journal.GenerationPayloads(t.Context())
139 if err != nil {
140 t.Fatal(err)
141 }
142 snapshot.Handovers, err = readHandovers()
143 if err != nil {
144 t.Fatal(err)
145 }
146 if _, err = generationRegistryInventory(t.Context(), root.Reconcile.Registry, snapshot); err != nil {
147 t.Fatal("second transfer registry", err)
148 }
149 if len(snapshot.Handovers) != 2 || len(snapshot.Payloads) != 3 {
150 t.Fatal("transfer lost retained authority")
151 }
152 physical, err = generationPhysicalPayloads(t.Context(), snapshot)
153 if err != nil {
154 t.Fatal(err)
155 }
156 for i := range physical {
157 if !reflect.DeepEqual(physical[i].Inventory.Entries[0].Identity, physical[2].Inventory.Entries[0].Identity) {
158 t.Fatal("chain identity", i)
159 }
160 }
161 // The initial installation and stop evidence are fixtures. Transfers, root
162 // retirement, row removal and directory parking below use their production adapters.
163 stopped := servingStop(t, f.f, f.f.read(), f.receipt.Target.BootID)
164 terminal := snapshot.Payloads[2].Inventory
165 inventory := hostbox.RetirementInventory{Host: terminal.Host, Captured: hostguard.Captured{Incarnation: terminal.Incarnation, Stamp: terminal.InstallationStamp}, Ownership: terminal.Entries[0].Ownership}
166 for _, e := range terminal.Entries {
167 inventory.Artifacts = append(inventory.Artifacts, hostbox.Artifact{Kind: hostbox.ArtifactKind(e.Kind), Root: e.ManagedRoot, Name: e.RelativePath, Owner: e.Ownership, RowID: e.RowIdentity, Identity: e.Identity, Dev: e.Device, Ino: e.Inode, Type: e.Type})
168 }
169 retirement, err := hostbox.NewRetirementJournal(stopped.Target.FenceID, inventory)
170 if err != nil {
171 t.Fatal(err)
172 }
173 retirement.Target, retirement.StopReceipt = &stopped.Target, stopped.Bytes
174 data := f.f.guard.DataDir
175 t.Setenv("CARLOS_DATA_DIR", data)
176 if err = f.f.claims.Delete(t.Context(), snapshot.Installations[0].Installation.Record); err != nil {
177 t.Fatal(err)
178 }
179 parker := hostbox.TypedParker{Roots: map[string]string{"data": data}, ParkedRoots: map[string]string{"data": data}, Incarnation: terminal.Incarnation, Host: terminal.Host, Retirement: retirement.ID}
180 runner := hostbox.TypedRetirement{JournalDir: root.Reconcile.Retirement.JournalDir, Authority: hostbox.InstallationCleanup{Guard: f.f.guard, Verify: verifyRetirementReceipt}, Filesystem: parker, Rows: hostbox.RegistryRows{Registry: root.Reconcile.Registry, Inventory: inventory}, Ack: func(context.Context, hostbox.RetirementJournal) error { return nil }}
181 if err = runner.Run(t.Context(), stopped.Target, retirement); err != nil {
182 t.Fatal("terminal retirement", err)
183 }
184 raw, err := os.ReadFile(filepath.Join(runner.JournalDir, retirement.ID+".json"))
185 if err != nil {
186 t.Fatal(err)
187 }
188 if err = json.Unmarshal(raw, &retirement); err != nil || !retirement.Complete {
189 t.Fatal(err)
190 }
191 snapshot.Retirements = []hostbox.RetirementJournal{retirement}
192 snapshot.Installations, err = f.f.guard.Root.GenerationInstallations(t.Context())
193 if err != nil {
194 t.Fatal(err)
195 }
196 if _, err = generationRequiredInventory(t.Context(), f.f.claims, snapshot, nil); err != nil {
197 t.Fatal("deleted record through transfer chain", err)
198 }
199 if _, err = generationRegistryInventory(t.Context(), root.Reconcile.Registry, snapshot); err != nil {
200 t.Fatal("retired transfer chain", err)
201 }
202 physical, err = generationPhysicalPayloads(t.Context(), snapshot)
203 if err != nil {
204 t.Fatal(err)
205 }
206 for _, payload := range physical {
207 for _, e := range payload.Inventory.Entries {
208 if e.Kind == "path" {
209 if _, err = os.Stat(filepath.Join(data, e.RelativePath)); err != nil {
210 t.Fatal("retained predecessor location", err)
211 }
212 }
213 }
214 }
215
216}
217