carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostclaim
2
3import (
4 "fmt"
5 "time"
6)
7
8const WakeRetryLimit = 5
9
10var wakeRetryDelays = [...]time.Duration{time.Second, 2 * time.Second, 4 * time.Second, 8 * time.Second, 16 * time.Second}
11
12type WakeRetry struct {
13 EpisodeID string `json:"episode_id"`
14 AttemptsReserved int `json:"attempts_reserved"`
15 State string `json:"state"`
16 NotBefore time.Time `json:"not_before,omitempty"`
17 Attempt *WakeRetryAttempt `json:"attempt,omitempty"`
18 ResetRequest string `json:"reset_request,omitempty"`
19}
20
21type WakeRetryAttempt struct {
22 ID string `json:"id"`
23 Box string `json:"box"`
24 Enrollment string `json:"enrollment"`
25 BootID string `json:"boot_id"`
26 ControllerID string `json:"controller_id"`
27 Session uint64 `json:"session,omitempty"`
28 InvocationID string `json:"invocation_id"`
29 LaunchInputs string `json:"launch_inputs"`
30}
31
32func (r *WakeRetry) validate() error {
33 if r == nil {
34 return nil
35 }
36 if r.EpisodeID == "" || r.AttemptsReserved < 0 || r.AttemptsReserved > WakeRetryLimit {
37 return fmt.Errorf("%w: wake retry episode", ErrMalformed)
38 }
39 switch r.State {
40 case "open":
41 if r.Attempt != nil || r.AttemptsReserved >= WakeRetryLimit {
42 return fmt.Errorf("%w: open wake retry", ErrMalformed)
43 }
44 case "attempting":
45 if r.AttemptsReserved == 0 || r.Attempt == nil || !r.NotBefore.IsZero() || !r.Attempt.valid() {
46 return fmt.Errorf("%w: attempting wake retry", ErrMalformed)
47 }
48 case "failed":
49 if r.AttemptsReserved != WakeRetryLimit || r.Attempt != nil || r.NotBefore.IsZero() {
50 return fmt.Errorf("%w: failed wake retry", ErrMalformed)
51 }
52 default:
53 return fmt.Errorf("%w: wake retry state %q", ErrMalformed, r.State)
54 }
55 return nil
56}
57
58func (a *WakeRetryAttempt) valid() bool {
59 return a.ID != "" && a.Box != "" && a.Enrollment != "" && a.BootID != "" &&
60 a.ControllerID != "" && a.InvocationID != "" && a.LaunchInputs != ""
61}
62
63type WakeRetryReservation struct {
64 HolderIncarnation string
65 EpisodeID string
66 Attempt WakeRetryAttempt
67 Now time.Time
68}
69
70// ReserveWakeRetryAttempt spends one serving retry after the caller has
71// established all read-only prerequisites. Session zero denotes the first
72// admission and must be bound with BindWakeRetrySession before app code runs.
73func ReserveWakeRetryAttempt(in WakeRetryReservation) func(*Claim) error {
74 return func(c *Claim) error {
75 if c.Holder == nil || c.Op.Kind != KindServing || c.Holder.Incarnation != in.HolderIncarnation ||
76 in.Now.IsZero() || !in.Attempt.valid() || (in.Attempt.Session != 0 && in.Attempt.Session != c.Holder.Session) {
77 return ErrWrongState
78 }
79 r := c.Holder.WakeRetry
80 if r == nil {
81 if in.EpisodeID == "" {
82 return ErrWrongState
83 }
84 r = &WakeRetry{EpisodeID: in.EpisodeID, State: "open"}
85 c.Holder.WakeRetry = r
86 } else if in.EpisodeID != "" && in.EpisodeID != r.EpisodeID {
87 return ErrWrongState
88 }
89 if r.State != "open" || r.AttemptsReserved >= WakeRetryLimit || in.Now.Before(r.NotBefore) {
90 return ErrWrongState
91 }
92 r.AttemptsReserved++
93 r.State, r.NotBefore = "attempting", time.Time{}
94 a := in.Attempt
95 r.Attempt = &a
96 return nil
97 }
98}
99
100// BindWakeRetrySession attaches the cold admission resulting from a first
101// reservation. It is required before that invocation may execute app code.
102func BindWakeRetrySession(incarnation, episode, attempt string, session uint64) func(*Claim) error {
103 return func(c *Claim) error {
104 if c.Holder == nil || c.Holder.Incarnation != incarnation || session == 0 || c.Holder.Session != session {
105 return ErrWrongState
106 }
107 r := c.Holder.WakeRetry
108 if r == nil || r.EpisodeID != episode || r.State != "attempting" || r.Attempt == nil ||
109 r.Attempt.ID != attempt || r.Attempt.Session != 0 {
110 return ErrWrongState
111 }
112 r.Attempt.Session = session
113 return nil
114 }
115}
116
117type WakeRetryResult struct {
118 HolderIncarnation string
119 EpisodeID string
120 Attempt WakeRetryAttempt
121 Now time.Time
122}
123
124func sameWakeAttempt(a *WakeRetryAttempt, b WakeRetryAttempt) bool { return a != nil && *a == b }
125
126// FailWakeRetryAttempt settles the exact reserved attempt. A crash or
127// ambiguous result uses this transition too; the reservation is never refunded.
128func FailWakeRetryAttempt(in WakeRetryResult) func(*Claim) error {
129 return func(c *Claim) error {
130 if c.Holder == nil || c.Holder.Incarnation != in.HolderIncarnation || in.Now.IsZero() {
131 return ErrWrongState
132 }
133 r := c.Holder.WakeRetry
134 if r == nil || r.EpisodeID != in.EpisodeID || r.State != "attempting" || !sameWakeAttempt(r.Attempt, in.Attempt) {
135 return ErrWrongState
136 }
137 r.Attempt = nil
138 r.NotBefore = in.Now.Add(wakeRetryDelays[r.AttemptsReserved-1])
139 if r.AttemptsReserved == WakeRetryLimit {
140 r.State = "failed"
141 } else {
142 r.State = "open"
143 }
144 return nil
145 }
146}
147
148type WakeRetryHealth struct {
149 WakeRetryResult
150 ReadinessPassed bool
151}
152
153// ClearWakeRetryHealthy accepts only a readiness result for the exact live
154// reservation. The caller is responsible for performing the backend checks.
155func ClearWakeRetryHealthy(in WakeRetryHealth) func(*Claim) error {
156 return func(c *Claim) error {
157 if !in.ReadinessPassed || c.Holder == nil || c.Holder.Incarnation != in.HolderIncarnation ||
158 in.Attempt.Session == 0 || c.Holder.Session != in.Attempt.Session {
159 return ErrWrongState
160 }
161 r := c.Holder.WakeRetry
162 if r == nil || r.EpisodeID != in.EpisodeID || r.State != "attempting" || !sameWakeAttempt(r.Attempt, in.Attempt) {
163 return ErrWrongState
164 }
165 c.Holder.WakeRetry = nil
166 return nil
167 }
168}
169
170type WakeRetryReset struct {
171 HolderIncarnation string
172 ObservedEpisode string
173 NewEpisodeID string
174 OperatorRequest string
175 AttemptSettled bool
176}
177
178// ResetWakeRetry is the operator-authorized primitive. Authorization and any
179// required fencing happen outside this package and are asserted by the input.
180func ResetWakeRetry(in WakeRetryReset) func(*Claim) error {
181 return func(c *Claim) error {
182 if c.Holder == nil || c.Holder.Incarnation != in.HolderIncarnation || in.ObservedEpisode == "" ||
183 in.NewEpisodeID == "" || in.NewEpisodeID == in.ObservedEpisode || in.OperatorRequest == "" {
184 return ErrWrongState
185 }
186 r := c.Holder.WakeRetry
187 if r == nil || r.EpisodeID != in.ObservedEpisode || (r.State == "attempting" && !in.AttemptSettled) {
188 return ErrWrongState
189 }
190 c.Holder.WakeRetry = &WakeRetry{EpisodeID: in.NewEpisodeID, State: "open", ResetRequest: in.OperatorRequest}
191 return nil
192 }
193}
194