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 "context"
5 "errors"
6 "github.com/carlosframework/platform/internal/hostbox"
7 "log"
8 "os"
9 "sort"
10
11 "github.com/carlosframework/platform/internal/hostguard"
12 "github.com/carlosframework/platform/internal/instancectl"
13 "github.com/carlosframework/platform/internal/rootstate"
14)
15
16var openClaimUnitBindings = func() (rootstate.Store, error) {
17 return rootstate.Open("/var/lib/carlos-claims/unit-bindings", true, false)
18}
19
20var openClaimUnitMaterializations = func() (rootstate.Store, error) {
21 return rootstate.Open("/var/lib/carlos-claims/unit-materializations", true, false)
22}
23
24var productionClaimUnitShapes = func() (map[string]claimUnitShape, error) {
25 shapes, err := readClaimUnitShapes("/etc/carlos/claim-units.json")
26 if errors.Is(err, os.ErrNotExist) {
27 return map[string]claimUnitShape{}, nil
28 }
29 return shapes, err
30}
31
32// Called only in the census-gated root control startup. The root configuration
33// chooses unit placement; the existing root installation chooses its owner.
34func bindProductionClaimUnits(ctx context.Context, h *ClaimRootWithLaunch, a *ClaimAssembly) error {
35 guard := h.Launch.Guard
36 if guard.Root == nil || guard.Claims == nil {
37 return os.ErrPermission
38 }
39 base, ok := h.Launch.Units.(instancectl.ClaimUnits)
40 if !ok {
41 return os.ErrPermission
42 }
43 shapes, configErr := productionClaimUnitShapes()
44 if configErr != nil {
45 log.Print(configErr)
46 shapes = map[string]claimUnitShape{}
47 }
48 state, err := openClaimUnitBindings()
49 if err != nil {
50 return err
51 }
52 admit := func() error {
53 if !claimNamedUnit4aComplete {
54 return hostbox.ErrUnsupported
55 }
56 return nil
57 }
58 b := &claimUnitBindings{state: state, admit: admit}
59 b.canonical = func(ctx context.Context, host string) (claimUnitShape, error) {
60 current, err := productionClaimUnitShapes()
61 if err != nil {
62 return claimUnitShape{}, err
63 }
64 shape, ok := current[host]
65 if !ok {
66 return claimUnitShape{}, os.ErrNotExist
67 }
68 return shape, nil
69 }
70 b.installation = func(ctx context.Context, host string) (hostguard.RootInstallation, error) {
71 captured, err := guard.Capture(host)
72 if err != nil {
73 return hostguard.RootInstallation{}, err
74 }
75 in, err := guard.Root.Installation(ctx, host, captured)
76 if err != nil {
77 return in, err
78 }
79 observed, err := guard.Claims.Read(ctx, host)
80 if err != nil {
81 return in, err
82 }
83 if observed.Absent || observed.Claim == nil || observed.Claim.Holder == nil {
84 return in, os.ErrPermission
85 }
86 holder := observed.Claim.Holder
87 if holder.Incarnation != in.Incarnation || holder.Stamp != in.Stamp || holder.Account != in.Account || holder.App != in.App || holder.Key != in.Record {
88 return in, os.ErrPermission
89 }
90 return in, nil
91 }
92 materialState, err := openClaimUnitMaterializations()
93 if err != nil {
94 state.Close()
95 return err
96 }
97 files := claimUnitFiles{dir: "/etc/systemd/system"}
98 material := claimUnitMaterializer{admit: admit, idle: func(host string) error {
99 binding, err := b.read(host)
100 if err != nil {
101 return err
102 }
103 unit := base
104 unit.BoundHost, unit.BoundUnit = host, binding.Shape.Unit
105 return unit.Gone(ctx, host, "")
106 }, state: materialState, read: files.read, create: files.create, replace: files.replace}
107 predecessor := claimPredecessorKernel{ctl: base.Systemctl, group: claimPredecessorGroup, boot: claimUnitBoot}
108 material.predecessor, material.stopPredecessor = predecessor.capture, predecessor.stop
109 material.socketCapture, material.socketPark = captureProductionClaimUnitSocket, parkProductionClaimUnitSocket
110 b.handover = material.handover
111 dispatch := claimUnitDispatch{base: base, bindings: b, shapes: productionClaimUnitShapes, effective: func(ctx context.Context, shape claimUnitShape) error {
112 return claimEffectiveUnit(ctx, shape, base.Systemctl)
113 }, definition: func(shape claimUnitShape) error {
114 binding, err := b.read(shape.Host)
115 if err != nil {
116 return err
117 }
118 if binding.Shape != shape {
119 return os.ErrPermission
120 }
121 return material.verify(binding)
122 }}
123 h.Launch.Units, h.Launch.unitBindings = dispatch, b
124 h.Launch.unitProgram = readClaimUnitProgram
125 h.Launch.boundReady = func(ctx context.Context, host string, started instancectl.ClaimUnitStarted) error {
126 binding, err := b.read(host)
127 if errors.Is(err, os.ErrNotExist) {
128 return nil
129 }
130 if err != nil {
131 return err
132 }
133 if binding.Shape.Unit != started.Unit {
134 return os.ErrPermission
135 }
136 binding, err = b.check(ctx, host, nil)
137 if err != nil {
138 return err
139 }
140 return claimBoundUnitReady(ctx, started, binding.Shape)
141 }
142 h.Root.retireBoundUnit = func(ctx context.Context, target hostguard.FenceTarget) error {
143 if err := retireClaimBoundUnit(ctx, target, b, material, dispatch.Gone); err != nil {
144 return err
145 }
146 _, err := base.Systemctl(ctx, "daemon-reload")
147 return err
148 }
149 kernel, ok := h.Root.Stop.Coordinator.Kernel.(claimUnitStopKernel)
150 if !ok {
151 state.Close()
152 materialState.Close()
153 return os.ErrPermission
154 }
155 kernel.Units = dispatch
156 h.Root.Stop.Coordinator.Kernel = kernel
157 if h.Root.executionRuntime != nil {
158 h.Root.executionRuntime.root = &h.Root
159 }
160 closePrevious := a.Close
161 a.Close = func(ctx context.Context) error {
162 var err error
163 if closePrevious != nil {
164 err = closePrevious(ctx)
165 }
166 return errors.Join(err, state.Close(), materialState.Close())
167 }
168 if h.Root.Reconcile.Retirement.JournalDir != "" {
169 journals, e := hostbox.SavedRetirementJournals(h.Root.Reconcile.Retirement.JournalDir)
170 if e != nil && !errors.Is(e, os.ErrNotExist) {
171 log.Print(e)
172 }
173 for _, journal := range journals {
174 if journal.Target != nil {
175 if e := h.Root.retire(ctx, *journal.Target); e != nil {
176 log.Print(e)
177 }
178 }
179 }
180 }
181 hosts := make([]string, 0, len(shapes))
182 for host := range shapes {
183 hosts = append(hosts, host)
184 }
185 sort.Strings(hosts)
186 for _, host := range hosts {
187 shape := shapes[host]
188 // Named services must keep their operator definition until a reserved
189 // boot/adoption controller can keep them running independently of edge.
190 if !claimNamedUnit4aComplete || shape.Backing == "" {
191 log.Print(hostbox.ErrUnsupported)
192 continue
193 }
194 if !claimHexDigest(shape.Program) {
195 log.Print(os.ErrPermission)
196 continue
197 }
198 if _, e := readClaimUnitProgram(shape); e != nil {
199 log.Print(e)
200 continue
201 }
202 next, e := b.current(ctx, host)
203 if e != nil {
204 log.Print(e)
205 continue
206 }
207 if e = guard.WithLocal(ctx, host, func() error {
208 current, e := guard.Root.Installation(ctx, host, hostguard.Captured{Incarnation: next.Owner.Incarnation, Stamp: next.Owner.Stamp})
209 if e != nil {
210 return e
211 }
212 if unitBindingOwner(current) != next.Owner {
213 return os.ErrPermission
214 }
215 binding, e := b.establish(ctx, host, next)
216 if e != nil {
217 return e
218 }
219 if e = material.install(ctx, binding); e != nil {
220 return e
221 }
222 if _, e = base.Systemctl(ctx, "daemon-reload"); e != nil {
223 return e
224 }
225 if e = claimEffectiveUnit(ctx, binding.Shape, base.Systemctl); e != nil {
226 return e
227 }
228 return projectClaimBoundSocket(binding.Shape)
229 }); e != nil {
230 log.Print(e)
231 }
232 }
233 if len(hosts) != 0 {
234 if _, e := base.Systemctl(ctx, "daemon-reload"); e != nil {
235 log.Print(e)
236 }
237 }
238 return nil
239}
240