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 "crypto/sha256"
6 "encoding/hex"
7 "encoding/json"
8 "os/exec"
9 "time"
10
11 "github.com/carlosframework/platform/internal/activator"
12 "github.com/carlosframework/platform/internal/hostclaim"
13 "github.com/carlosframework/platform/internal/hostguard"
14 "github.com/carlosframework/platform/internal/hostwrite"
15 "github.com/carlosframework/platform/internal/registry"
16)
17
18// OperationStart is one writer admitted under a move op kind on this
19// controller: the admitted observation, the start policy it was admitted
20// under, the selected inputs and the pre-launch record's controller tuple
21// (box, enrollment, boot, controller, session). The claim builds it once per
22// Prepare; an OperationAttempts implementation never constructs one.
23type OperationStart struct {
24 Admitted hostclaim.Observed
25 Policy activator.StartPolicy
26 Inputs ClaimLaunchInputs
27 Owner hostguard.FenceTarget
28}
29
30// OperationAttempts is the moves-owned attempt journal for a writer admitted
31// under landing (claim Plan 9 D2, moves ruling H2), and region HA's for a
32// probe admitted under relocating/probing (claim Plan 11 R1). It is the operation
33// analogue of activator.ServingRetries: the claim binds its methods into
34// activator.ClaimStart for the exact admitted session, and moves journals,
35// counts and settles the attempts in its own record. Every method must refuse
36// with hostbox.ErrChanged once the claim or the moves record no longer names
37// s.Admitted's op, phase step, session, stamp and generation.
38type OperationAttempts interface {
39 // OpenProbe journals the attempt's first app-start time before execution
40 // and returns that same time on every later start, including after a
41 // controller restart. It never resets the opened time. Probes only.
42 OpenProbe(ctx context.Context, s OperationStart, proposed time.Time) (time.Time, error)
43 // CurrentAttempt returns the exact stable value last journalled by Reserve.
44 // It never generates IDs; repeated reads between Reserve calls are identical.
45 // CurrentAttempt is the reservation the role launcher journals for the
46 // next start: ID and InvocationID unique per start, Session, Box,
47 // Enrollment, BootID and ControllerID equal to s.Owner's, and
48 // LaunchInputs equal to ClaimLaunchDigest(s.Inputs).
49 CurrentAttempt(s OperationStart) hostclaim.WakeRetryAttempt
50 // Reserve(0) validates the attempt journalled for this admission. Reserve
51 // with n > 0 journals start n of the same admission before it runs.
52 Reserve(ctx context.Context, s OperationStart, n int) error
53 // Failed settles start n after its process was stopped and drained.
54 Failed(ctx context.Context, s OperationStart, n int) error
55 // Healthy accepts start n after readiness, replication and publication.
56 // again re-runs the op kind's own readiness check against cmd, for the
57 // probe's settle and second request (moves §7.7).
58 Healthy(ctx context.Context, s OperationStart, n int, cmd *exec.Cmd, again func(context.Context) error) error
59}
60
61// ClaimProbeReadiness is the probe kind's readiness check (claim Plan 9 D3,
62// moves ruling H12). roleCgroup is the absolute cgroup v2 directory of the
63// started main role, resolved from its journalled reservation. Serving and a
64// landing wake after the commit keep ClaimWakeAssembly.Ready.
65type ClaimProbeReadiness func(ctx context.Context, cmd *exec.Cmd, rt registry.Route, in ClaimLaunchInputs, roleCgroup string) error
66
67// ClaimLaunchDigest names the launch inputs an attempt reservation binds: the
68// identity, executable, digest, arguments and environment, never the
69// admission. Serving retries and operation attempts use the same digest.
70func ClaimLaunchDigest(in ClaimLaunchInputs) (string, error) {
71 raw, err := json.Marshal(struct {
72 Identity hostwrite.Identity
73 Executable, Digest string
74 Args, Env []string
75 }{in.Identity, in.Executable, in.ExecutableSHA256, in.Args, in.Env})
76 if err != nil {
77 return "", err
78 }
79 digest := sha256.Sum256(raw)
80 return hex.EncodeToString(digest[:]), nil
81}
82
83// claimOperationRefusal is main's typed refusal for every operation or probe
84// admission the bound moves C1 owners do not cover.
85func claimOperationRefusal() error {
86 return &ClaimDependencyError{Contracts: []string{"moves C1 operation attempt journal and prepared-open adapter"}}
87}
88
89// ClaimOperationOwner is one op kind's attempt journal and probe readiness
90// check (claim Plan 11 R1). ProbeReady nil refuses that kind's probes.
91type ClaimOperationOwner struct {
92 Attempts OperationAttempts
93 ProbeReady ClaimProbeReadiness
94}
95
96// claimOperationRouter is the wake's OperationAttempts when more than one op
97// kind admits operation writers: landing to the moves journal, relocating to
98// region HA's. Each call goes to the admitted observation's kind; any other
99// kind is refused before any journal is touched. With region HA unbound the
100// wake keeps Plan 9's single moves journal and no router exists.
101type claimOperationRouter map[string]ClaimOperationOwner
102
103func (r claimOperationRouter) route(s OperationStart) (OperationAttempts, error) {
104 if s.Admitted.Claim == nil {
105 return nil, claimOperationRefusal()
106 }
107 owner, ok := r[s.Admitted.Claim.Op.Kind]
108 if !ok || owner.Attempts == nil {
109 return nil, claimOperationRefusal()
110 }
111 return owner.Attempts, nil
112}
113
114func (r claimOperationRouter) OpenProbe(ctx context.Context, s OperationStart, proposed time.Time) (time.Time, error) {
115 ops, err := r.route(s)
116 if err != nil {
117 return time.Time{}, err
118 }
119 return ops.OpenProbe(ctx, s, proposed)
120}
121
122// CurrentAttempt of an unrouted kind is the zero attempt, which no role
123// launch accepts; Reserve refuses that kind first in every start.
124func (r claimOperationRouter) CurrentAttempt(s OperationStart) hostclaim.WakeRetryAttempt {
125 ops, err := r.route(s)
126 if err != nil {
127 return hostclaim.WakeRetryAttempt{}
128 }
129 return ops.CurrentAttempt(s)
130}
131
132func (r claimOperationRouter) Reserve(ctx context.Context, s OperationStart, n int) error {
133 ops, err := r.route(s)
134 if err != nil {
135 return err
136 }
137 return ops.Reserve(ctx, s, n)
138}
139
140func (r claimOperationRouter) Failed(ctx context.Context, s OperationStart, n int) error {
141 ops, err := r.route(s)
142 if err != nil {
143 return err
144 }
145 return ops.Failed(ctx, s, n)
146}
147
148func (r claimOperationRouter) Healthy(ctx context.Context, s OperationStart, n int, cmd *exec.Cmd, again func(context.Context) error) error {
149 ops, err := r.route(s)
150 if err != nil {
151 return err
152 }
153 return ops.Healthy(ctx, s, n, cmd, again)
154}
155
156// operationOwner is the attempt journal and probe readiness check bound for
157// o's op kind, or nil for a kind no operation owner covers. With Plan 9's
158// single moves journal only landing has one; with the router, each routed
159// kind has its own.
160func (w *ClaimWakeAssembly) operationOwner(o hostclaim.Observed) (OperationAttempts, ClaimProbeReadiness) {
161 if w == nil || w.Operations == nil || o.Claim == nil {
162 return nil, nil
163 }
164 kind := o.Claim.Op.Kind
165 if r, ok := w.Operations.(claimOperationRouter); ok {
166 owner := r[kind]
167 if owner.Attempts == nil {
168 return nil, nil
169 }
170 return owner.Attempts, owner.ProbeReady
171 }
172 if kind != hostclaim.KindLanding && !claimRecoveryKind(kind) {
173 return nil, nil
174 }
175 return w.Operations, w.ProbeReady
176}
177
178// operationKind reports whether o's op kind admits writers through an
179// operation attempt journal bound on this wake.
180func (w *ClaimWakeAssembly) operationKind(o hostclaim.Observed) bool {
181 ops, _ := w.operationOwner(o)
182 return ops != nil
183}
184
185// claimSpawnAction is the action a wake attempt selects its launch work
186// under. A relocating attempt is always the target's probe (WakePhase admits
187// nothing else under relocating), and it selects "probe", the only action
188// hostbox.Policy gives relocating authority to, so an ordinary spawn never
189// gets probe authority. Every other kind keeps "spawn".
190func claimSpawnAction(c *hostclaim.Claim) string {
191 if c != nil && c.Op.Kind == hostclaim.KindRelocating {
192 return "probe"
193 }
194 return "spawn"
195}
196
197func (w *ClaimWakeAssembly) recoveryAttemptsReady() bool {
198 for _, kind := range []string{hostclaim.KindRestoring, hostclaim.KindReverting} {
199 ops, ready := w.operationOwner(hostclaim.Observed{Claim: &hostclaim.Claim{Op: hostclaim.Op{Kind: kind}}})
200 provider, ok := ops.(interface{ RecoveryProbeAttemptsReady() bool })
201 if !ok || !provider.RecoveryProbeAttemptsReady() || ready == nil {
202 return false
203 }
204 }
205 return true
206}
207