carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package hostbox
2
3import (
4 "context"
5 "crypto/rand"
6 "crypto/sha256"
7 "encoding/hex"
8 "errors"
9 "fmt"
10
11 "github.com/carlosframework/platform/internal/hostclaim"
12 "github.com/carlosframework/platform/internal/hostguard"
13 "github.com/carlosframework/platform/internal/hostwrite"
14 "github.com/carlosframework/platform/internal/provision"
15 "github.com/carlosframework/platform/internal/registry"
16)
17
18func (a ReconcileAdapter) CaptureInventory(ctx context.Context, s RecordSelection, c hostguard.Captured, revision uint64) (InventoryExpectation, error) {
19 obs, err := hostclaim.Read(ctx, a.Reconciler.Claims, s.Host)
20 if err != nil {
21 return InventoryExpectation{}, err
22 }
23 if obs.Absent || obs.Claim == nil || obs.Claim.Holder == nil {
24 return InventoryExpectation{}, ErrChanged
25 }
26 h := obs.Claim.Holder
27 if h.Incarnation != c.Incarnation || h.Stamp != c.Stamp || h.Key != s.Key {
28 return InventoryExpectation{}, ErrChanged
29 }
30 _, etag, err := a.Reconciler.Claims.Get(ctx, s.Key)
31 if err != nil {
32 return InventoryExpectation{}, err
33 }
34 if etag != s.ETag {
35 return InventoryExpectation{}, ErrChanged
36 }
37 if revision != 0 {
38 if err := a.inventoryRevision(ctx, s.Host, c, revision); err != nil {
39 return InventoryExpectation{}, err
40 }
41 }
42 return InventoryExpectation{Revision: revision, RecordKey: s.Key, RecordETag: etag, Target: hostguard.FenceTarget{Host: s.Host, Incarnation: c.Incarnation, Stamp: c.Stamp, Session: h.Session, Box: a.Box, Enrollment: a.Enrollment, ClaimETag: obs.ETag, Op: obs.Claim.Op.ID, Phase: obs.Claim.Op.Phase}}, nil
43}
44func (a ReconcileAdapter) inventoryRevision(ctx context.Context, host string, c hostguard.Captured, revision uint64) error {
45 if a.Reconciler.Guard.FenceView == nil {
46 return ErrUnsupported
47 }
48 view, err := a.Reconciler.Guard.FenceView(ctx, host, c)
49 if err != nil {
50 return err
51 }
52 if view.Schema != 1 || view.Host != host || view.Captured != c || view.Installation.InventoryRevision != revision {
53 return ErrChanged
54 }
55 return nil
56}
57func (a ReconcileAdapter) RevalidateInventory(ctx context.Context, s RecordSelection, c hostguard.Captured, e InventoryExpectation, revision uint64) error {
58 fresh, err := a.CaptureInventory(ctx, s, c, revision)
59 if err != nil {
60 return err
61 }
62 if fresh.Target != e.Target {
63 return ErrChanged
64 }
65 return nil
66}
67func (a ReconcileAdapter) prepareRemoteInventory(ctx context.Context, s RecordSelection, c hostguard.Captured) error {
68 _, revision, err := a.Inventory.Snapshot(ctx, s.Host, c)
69 if err != nil {
70 return err
71 }
72 if revision != 0 {
73 return nil
74 }
75 var expected InventoryExpectation
76 err = a.Reconciler.Guard.WithLocal(ctx, s.Host, func() error { var err error; expected, err = a.CaptureInventory(ctx, s, c, 0); return err })
77 if err != nil {
78 return err
79 }
80 in := StopInventory{Complete: true, Host: s.Host, Incarnation: c.Incarnation, InstallationStamp: c.Stamp, Box: a.Box, Enrollment: a.Enrollment, DirectoryRevision: a.DirectoryRevision}
81 revision, err = a.Inventory.WriteExpected(ctx, in, expected)
82 if err != nil {
83 return err
84 }
85 return a.Reconciler.Guard.WithLocal(ctx, s.Host, func() error { return a.RevalidateInventory(ctx, s, c, expected, revision) })
86}
87func (a ReconcileAdapter) installRemote(ctx context.Context, rec provision.ClaimRecord, s RecordSelection, r Reconciler, write func(registry.ClaimOwnership) error) error {
88 if a.Census != nil {
89 row, err := a.Registry.Get(s.Host)
90 if err != nil {
91 return err
92 }
93 if row != nil && row.ClaimOwnership.Valid() {
94 captured := hostguard.Captured{Incarnation: row.ClaimIncarnation, Stamp: row.ClaimStamp}
95 in, revision, err := a.Inventory.Snapshot(ctx, s.Host, captured)
96 if err != nil {
97 return err
98 }
99 r.CheckHeld = func(ctx context.Context, obs hostclaim.Observed, selected RecordSelection) error {
100 if obs.Claim == nil || obs.Claim.Holder == nil || obs.Host != selected.Host || obs.Claim.Holder.Incarnation != selected.Incarnation {
101 return ErrChanged
102 }
103 current, err := a.Registry.Get(selected.Host)
104 if err != nil {
105 return err
106 }
107 if current == nil || current.ClaimOwnership != row.ClaimOwnership {
108 return ErrChanged
109 }
110 if err := a.inventoryRevision(ctx, selected.Host, captured, revision); err != nil {
111 return err
112 }
113 return a.Census.checkHeldInventory(selected, row.ClaimOwnership, in)
114 }
115 } else {
116 r.CheckHeld = func(context.Context, hostclaim.Observed, RecordSelection) error { return ErrTypedInventory }
117 }
118 }
119 r.CheckLocal = func(context.Context, hostclaim.Observed, RecordSelection) error { return nil }
120 r.PrepareInventory = a.prepareRemoteInventory
121 return r.installWithWork(ctx, s, func(work hostwrite.Work) error {
122 captured, err := r.Guard.Capture(s.Host)
123 if err != nil {
124 return err
125 }
126 for attempt := 0; attempt < 3; attempt++ {
127 err = a.registerRemote(ctx, s, captured, work, write)
128 if !errors.Is(err, ErrChanged) {
129 return err
130 }
131 }
132 return ErrChanged
133 })
134}
135func (a ReconcileAdapter) registerRemote(ctx context.Context, s RecordSelection, c hostguard.Captured, work hostwrite.Work, write func(registry.ClaimOwnership) error) error {
136 in, revision, err := a.Inventory.Snapshot(ctx, s.Host, c)
137 if err != nil {
138 return err
139 }
140 if !in.Complete || in.Host != s.Host || in.Incarnation != c.Incarnation || in.InstallationStamp != c.Stamp || in.Box != a.Box || in.Enrollment != a.Enrollment || in.DirectoryRevision != a.DirectoryRevision {
141 return ErrTypedInventory
142 }
143 sum := sha256.Sum256([]byte(fmt.Sprintf("%s\x00%s\x00%d\x00%s", s.Host, c.Incarnation, c.Stamp, a.Box)))
144 owner := registry.ClaimOwnership{ClaimIncarnation: c.Incarnation, ClaimStamp: c.Stamp, ClaimOwner: hex.EncodeToString(sum[:])}
145 index := -1
146 for i, e := range in.Entries {
147 if e.Kind == "route" && e.RelativePath == s.Host {
148 if index != -1 || e.Ownership != owner.ClaimOwner || e.RowIdentity == "" {
149 return ErrTypedInventory
150 }
151 index = i
152 owner.ClaimRowID = e.RowIdentity
153 }
154 }
155 if index < 0 {
156 var id [16]byte
157 if _, err := rand.Read(id[:]); err != nil {
158 return err
159 }
160 owner.ClaimRowID = hex.EncodeToString(id[:])
161 index = len(in.Entries)
162 in.Entries = append(in.Entries, StopInventoryEntry{Kind: "route", ArtifactID: owner.ClaimRowID, ManagedRoot: "registry", RelativePath: s.Host, RowIdentity: owner.ClaimRowID, Ownership: owner.ClaimOwner, Materialization: "intended"})
163 var expected InventoryExpectation
164 err = work.With(ctx, hostwrite.Identity{}, func() error { var err error; expected, err = a.CaptureInventory(ctx, s, c, revision); return err })
165 if err != nil {
166 return err
167 }
168 revision, err = a.Inventory.WriteExpected(ctx, in, expected)
169 if err != nil {
170 return err
171 }
172 if err = work.With(ctx, hostwrite.Identity{}, func() error { return a.RevalidateInventory(ctx, s, c, expected, revision) }); err != nil {
173 return err
174 }
175 }
176 var expected InventoryExpectation
177 err = work.With(ctx, hostwrite.Identity{}, func() error {
178 var err error
179 expected, err = a.CaptureInventory(ctx, s, c, revision)
180 if err != nil {
181 return err
182 }
183 row, err := a.Registry.Get(s.Host)
184 if err != nil {
185 return err
186 }
187 if row == nil && in.Entries[index].Materialization == "materialized" {
188 return ErrTypedInventory
189 }
190 if row != nil && row.ClaimOwnership != owner {
191 return registry.ErrClaimOwnership
192 }
193 if err = write(owner); err != nil {
194 return err
195 }
196 row, err = a.Registry.Get(s.Host)
197 if err != nil {
198 return err
199 }
200 if row == nil || row.ClaimOwnership != owner {
201 return registry.ErrClaimOwnership
202 }
203 return nil
204 })
205 if err != nil {
206 return err
207 }
208 if in.Entries[index].Materialization == "materialized" {
209 return nil
210 }
211 in.Entries[index].Materialization = "materialized"
212 revision, err = a.Inventory.WriteExpected(ctx, in, expected)
213 if err != nil {
214 return err
215 }
216 return work.With(ctx, hostwrite.Identity{}, func() error { return a.RevalidateInventory(ctx, s, c, expected, revision) })
217}
218
219func (a ReconcileAdapter) PrepareInstallation(ctx context.Context, s RecordSelection, captured hostguard.Captured) error {
220 if !a.Inventory.Remote() {
221 return nil
222 }
223 return a.prepareRemoteInventory(ctx, s, captured)
224}
225
226func (a ReconcileAdapter) RegisterLanding(ctx context.Context, observed hostclaim.Observed) error {
227 if !a.Inventory.Remote() || observed.Claim == nil || observed.Claim.Holder == nil {
228 return ErrUnsupported
229 }
230 c := observed.Claim
231 if c.Op.Kind != hostclaim.KindLanding || c.Op.Exec != a.Box || c.Op.Intent["target"] != a.Box || (c.Op.Phase != provision.LandChecking && c.Op.Phase != provision.LandConfigured) {
232 return ErrChanged
233 }
234 h := c.Holder
235 _, etag, err := a.Reconciler.Claims.Get(ctx, h.Key)
236 if err != nil {
237 return err
238 }
239 s := RecordSelection{Host: c.Host, Key: h.Key, ETag: etag, Incarnation: h.Incarnation, Family: h.Family, Layout: "flat"}
240 captured := hostguard.Captured{Incarnation: h.Incarnation, Stamp: h.Stamp}
241 in, rev, err := a.Inventory.Snapshot(ctx, c.Host, captured)
242 if err != nil {
243 return err
244 }
245 if rev == 0 {
246 if c.Op.Phase != provision.LandChecking {
247 return ErrChanged
248 }
249 in = StopInventory{Complete: true, Host: c.Host, Incarnation: h.Incarnation, InstallationStamp: h.Stamp, Box: a.Box, Enrollment: a.Enrollment, DirectoryRevision: a.DirectoryRevision}
250 }
251 var expected InventoryExpectation
252 if err = a.Reconciler.Guard.WithLocal(ctx, c.Host, func() error { var e error; expected, e = a.CaptureInventory(ctx, s, captured, rev); return e }); err != nil {
253 return err
254 }
255 rev, err = a.Inventory.WriteExpected(ctx, in, expected)
256 if err != nil {
257 return err
258 }
259 if err = a.Reconciler.Guard.WithLocal(ctx, c.Host, func() error { return a.RevalidateInventory(ctx, s, captured, expected, rev) }); err != nil {
260 return err
261 }
262
263 return nil
264}
265