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 "errors"
7 "fmt"
8 "io"
9 "os"
10 "strings"
11
12 "github.com/carlosframework/platform/internal/hostbox"
13 "github.com/carlosframework/platform/internal/hostclaim"
14 "github.com/carlosframework/platform/internal/hostfiles"
15 "github.com/carlosframework/platform/internal/hostwrite"
16 "github.com/carlosframework/platform/internal/instancefs"
17 "github.com/carlosframework/platform/internal/lease"
18 "github.com/carlosframework/platform/internal/registry"
19 "github.com/carlosframework/platform/internal/release"
20)
21
22type filesSeedOperation struct {
23 ctx context.Context
24 claims release.Store
25 registry *registry.Registry
26 actor hostbox.Actor
27 files *movesFiles
28 selected filesSeedSelection
29 bound servingArtifactIdentity
30 stats hostfiles.SyncStats
31}
32
33func (s *filesSeedOperation) reportSkipped(out io.Writer, host string) error {
34 for _, path := range s.stats.TooLarge {
35 if _, err := fmt.Fprintf(out, "Host %q: skipped file %q: exceeds 64 MiB and will not be restored on a later cold wake.\n", host, path); err != nil {
36 return err
37 }
38 }
39 for _, path := range s.stats.NotRegular {
40 if _, err := fmt.Fprintf(out, "Host %q: skipped file %q: not a regular file.\n", host, path); err != nil {
41 return err
42 }
43 }
44 return nil
45}
46
47// The private provider is freshly opened. No live provider or mutex-bearing
48// owner is copied or modified. The command binds admission before materializing.
49func newFilesSeedProvider(ctx context.Context, source *movesFilesSource, claims release.Store, data lease.VersionedStore, actor hostbox.Actor, reg *registry.Registry, selected filesSeedSelection) (*movesFiles, *filesSeedOperation, error) {
50 if ctx == nil || reg == nil || selected.State != "eligible" {
51 return nil, nil, hostbox.ErrUnsupported
52 }
53 opened, err := source.Open(ctx, claims, data, actor)
54 if err != nil {
55 return nil, nil, err
56 }
57 f, ok := opened.(*movesFiles)
58 if !ok {
59 return nil, nil, hostbox.ErrUnsupported
60 }
61 op := &filesSeedOperation{ctx: ctx, claims: claims, registry: reg, actor: actor, files: f, selected: selected}
62 f.Seed = op
63 return f, op, nil
64}
65
66func (s *filesSeedOperation) admitted(ctx context.Context, o hostclaim.Observed) error {
67 if s == nil || s.ctx == nil || s.ctx.Err() != nil || s.bound != (servingArtifactIdentity{}) || s.selected.State != "eligible" {
68 return hostbox.ErrChanged
69 }
70 current, err := servingWriterIdentity(o, s.actor.Box, s.actor.Region)
71 if err != nil {
72 return err
73 }
74 old := s.selected.Observation.Claim.Holder
75 if current.Host != s.selected.Observation.Host || current.Incarnation != old.Incarnation || current.Location != old.Location || current.Session <= old.Session || current.Generation == old.Generation {
76 return hostbox.ErrChanged
77 }
78 row, err := s.registry.Get(o.Host)
79 if err != nil {
80 return err
81 }
82 selected, err := inspectFilesSeed(ctx, s.claims, s.files, o, s.actor, row)
83 if err != nil {
84 return err
85 }
86 if selected.State != "eligible" {
87 return hostfiles.ErrManifestExists
88 }
89 s.bound = current
90 return nil
91}
92
93func (s *filesSeedOperation) check(ctx context.Context, o hostclaim.Observed, in ClaimLaunchInputs) error {
94 if s == nil || s.ctx == nil || s.ctx.Err() != nil || in.Empty || in.Prepared || in.Admission.NewGeneration == "" {
95 return hostbox.ErrChanged
96 }
97 current, err := servingWriterIdentity(o, s.actor.Box, s.actor.Region)
98 if err != nil || current != s.bound || current.Generation != in.Admission.NewGeneration {
99 return hostbox.ErrChanged
100 }
101 row, err := s.registry.Get(o.Host)
102 if err != nil {
103 return err
104 }
105 selected, err := inspectFilesSeed(ctx, s.claims, s.files, o, s.actor, row)
106 if err != nil {
107 return err
108 }
109 if selected.State != "eligible" {
110 return hostfiles.ErrManifestExists
111 }
112 _, err = s.files.proveOrigins(ctx, o, in.Admission, current.Location, []string{current.Generation})
113 return err
114}
115
116func (s *filesSeedOperation) stage(ctx context.Context, o hostclaim.Observed, in ClaimLaunchInputs, work hostwrite.Work, p *hostfiles.Publisher, env claimEnvFile) error {
117 if err := s.check(ctx, o, in); err != nil {
118 return err
119 }
120 var root *os.File
121 var wake hostfiles.WakeState
122 err := work.With(ctx, in.Identity, func() error {
123 var err error
124 root, err = instancefs.OpenDirBeneath(env.Dir, o.Host)
125 if err != nil {
126 return err
127 }
128 wake, err = hostfiles.ExportWake(s.registry, o.Host)
129 return err
130 })
131 if root != nil {
132 defer root.Close()
133 }
134 if err != nil {
135 return err
136 }
137 before, err := root.Stat()
138 if err != nil {
139 return err
140 }
141 s.stats, err = hostfiles.Seed(ctx, p, root, wake, nil)
142 if err != nil {
143 return err
144 }
145 return work.With(ctx, in.Identity, func() error {
146 fresh, err := instancefs.OpenDirBeneath(env.Dir, o.Host)
147 if err != nil {
148 return err
149 }
150 defer fresh.Close()
151 after, err := fresh.Stat()
152 if err != nil {
153 return err
154 }
155 if !os.SameFile(before, after) {
156 return hostbox.ErrChanged
157 }
158 return nil
159 })
160}
161
162type filesSeedCheckingStore struct {
163 release.Store
164 data lease.VersionedStore
165 bucket, prefix string
166}
167
168// A successful create needs no readback. On conflict, pin the current object
169// version and compare bounded bytes before the publisher may reference it.
170func (s filesSeedCheckingStore) Put(ctx context.Context, key string, body []byte, etag string) (string, error) {
171 result, err := s.Store.Put(ctx, key, body, etag)
172 if !errors.Is(err, release.ErrCASConflict) || etag != "" || !strings.HasPrefix(key, s.prefix+"objects/") {
173 return result, err
174 }
175 versions, e := s.data.ListVersions(ctx, s.bucket, key)
176 if e != nil {
177 return "", e
178 }
179 version := ""
180 for _, v := range versions {
181 if v.Key == key && v.IsLatest {
182 if version != "" || v.DeleteMarker || v.VersionID == "" || v.VersionID == "null" {
183 return "", hostfiles.ErrSuperseded
184 }
185 version = v.VersionID
186 }
187 }
188 if version == "" {
189 return "", hostfiles.ErrSuperseded
190 }
191 reader, e := s.data.OpenVersion(ctx, s.bucket, key, version)
192 if e != nil {
193 return "", e
194 }
195 actual, e := io.ReadAll(io.LimitReader(reader, int64(len(body))+1))
196 e = errors.Join(e, reader.Close())
197 if e != nil {
198 return "", e
199 }
200 if !bytes.Equal(actual, body) {
201 return "", hostfiles.ErrSuperseded
202 }
203 return result, err
204}
205