carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package moves
2
3import (
4 "strconv"
5
6 "github.com/carlosframework/platform/internal/provision"
7)
8
9// Transition is what one record write did to a move, as the move log names
10// it (log.go): the move, the event, the identity that keys its log object,
11// the fields it carries (the regions, the evidence's digests, timings, a
12// stuck's role), and the instance incarnation the move's record carried,
13// which binds a queued entry to that instance (the console's outbox). The
14// box's writers and the console's move pass name their writes with it, so a
15// transition has one identity whoever writes it.
16type Transition struct {
17 Op, Event, Identity string
18 Incarnation string
19 Fields map[string]string
20}
21
22// phaseOf is the phase a move's state is at: the cut's, the landing's, or
23// (rolling back) the target's and the source's.
24func phaseOf(m *provision.MoveState) (phase, target, source string) {
25 switch m.State {
26 case provision.MoveCutting:
27 return m.CutPhase, "", ""
28 case provision.MoveLanding:
29 return m.LandingPhase, "", ""
30 case provision.MoveRollingBack:
31 if m.Rollback != nil {
32 return "", m.Rollback.TargetPhase, m.Rollback.SourcePhase
33 }
34 }
35 return "", "", ""
36}
37
38// transitionFields are a move's regions, the digests its evidence has
39// recorded so far (the source's stop: final TXID, database, files, timers;
40// the landing's installed database; the pinned inputs: release, env report)
41// and its admitted cut budget.
42func transitionFields(m *provision.MoveState) map[string]string {
43 f := map[string]string{"from": m.From, "to": m.To}
44 set := func(k, v string) {
45 if v != "" {
46 f[k] = v
47 }
48 }
49 if s := m.Source; s != nil {
50 if s.FinalTXID != 0 {
51 f["final_txid"] = strconv.FormatUint(s.FinalTXID, 10)
52 }
53 set("db_sha256", s.DBSHA256)
54 set("timers_sha256", s.TimersSHA256)
55 if s.Files != nil {
56 set("files_plain", s.Files.Plain)
57 }
58 }
59 if l := m.Landing; l != nil {
60 set("installed_db_sha256", l.InstalledDBSHA256)
61 }
62 if in := m.Inputs; in != nil {
63 set("release", in.Release)
64 set("env_report", in.EnvReport)
65 }
66 if m.CutBudgetS > 0 {
67 // The admitted cut's budget, which the member line for it states.
68 f["cut_budget_s"] = strconv.Itoa(m.CutBudgetS)
69 }
70 return f
71}
72
73// seconds is b - a in whole seconds, "" unless both are known.
74func seconds(a, b interface{ Unix() int64 }) string {
75 return strconv.FormatInt(b.Unix()-a.Unix(), 10)
76}
77
78// DeclaredLostIdentity is the identity of a declared loss's entry: the
79// rollback's target phase going to lost (TransitionOf), whoever logs it.
80const DeclaredLostIdentity = "rolling_back-target-" + provision.RbTargetLost
81
82// DeclaredLost is the entry of a declared loss of m's target (§7.10), event
83// declared_lost, named as TransitionOf names the rollback's target going to
84// lost. The console that performs a declaration whose signed consequence is
85// that loss names it from the record it read before (a landing, which the
86// declaration rolls back, or a rollback whose target is releasing or
87// returning), and a box whose pass sees the target lost names it from that
88// record. The identity, DeclaredLostIdentity, is the same for both, so
89// create-only storage keeps one (final review I4). A target loss is declared
90// only on that path, so ok is false for a move in any other state (a
91// committed move's destroyed-local-only declaration changes no move state:
92// final re-review 1, N1) and for a move with no id.
93func DeclaredLost(m *provision.MoveState) (Transition, bool) {
94 if m == nil || m.ID == "" || (m.State != provision.MoveLanding && m.State != provision.MoveRollingBack) {
95 return Transition{}, false
96 }
97 var rb provision.MoveRollback
98 if m.Rollback != nil {
99 rb = *m.Rollback
100 }
101 before, after := *m, *m
102 before.State, after.State = provision.MoveRollingBack, provision.MoveRollingBack
103 brb, arb := rb, rb
104 brb.TargetPhase, arb.TargetPhase = "", provision.RbTargetLost
105 before.Rollback, after.Rollback = &brb, &arb
106 return TransitionOf(provision.Record{Move: &before}, provision.Record{Move: &after})
107}
108
109// TransitionOf names the transition from before to after, one record write:
110// before as the write found it, after as written. ok is false when the
111// write changed nothing the log records (a pin, recovery inputs, a replay).
112//
113// Identities: <state>-<phase> for a phase step (a rollback names the half
114// that moved, rolling_back-target-<p> or rolling_back-source-<p>), the
115// flip as cutting-flipped, committed-opened and committed-source_parked,
116// reverting-opened, probe_attempt-<n> for the landing's n-th probe,
117// stuck-<epoch> keyed by the record's recovery retry epoch, the new state
118// itself (requested for preparing, cutting-requested, ready, aborting,
119// reverting, relocating, rolling_back), and the terminal outcome (done,
120// rolled_back, aborted). A done or rolled_back outcome carries the move's
121// timings (Timings) over the journal as the close found it.
122func TransitionOf(before, after provision.Record) (Transition, bool) {
123 bm, am := before.Move, after.Move
124 if am == nil {
125 // A close clears the move and leaves its outcome (D11).
126 if bm == nil || after.LastMove == nil || after.LastMove.ID != bm.ID || bm.ID == "" {
127 return Transition{}, false
128 }
129 f := transitionFields(bm)
130 if out := after.LastMove.Outcome; out == "done" || out == "rolled_back" {
131 // The timings (Plan 6 Task 4), from the journal the close
132 // found: the same for every writer of the entry.
133 for k, v := range Timings(bm) {
134 f[k] = timingText(v)
135 }
136 }
137 f["offline_s"] = timingText(after.LastMove.OfflineS)
138 return Transition{Op: bm.ID, Event: after.LastMove.Outcome, Identity: after.LastMove.Outcome, Incarnation: bm.Incarnation, Fields: f}, true
139 }
140 if am.ID == "" || (bm != nil && bm.ID != am.ID) {
141 return Transition{}, false
142 }
143 e := Transition{Op: am.ID, Incarnation: am.Incarnation, Fields: transitionFields(am)}
144 if bm == nil {
145 bm = &provision.MoveState{}
146 }
147 switch {
148 case am.State == provision.MoveStuck:
149 if bm.State == provision.MoveStuck {
150 return Transition{}, false
151 }
152 e.Event, e.Identity = "stuck", "stuck-"+strconv.FormatUint(am.RecoveryRetryEpoch, 10)
153 // A rollback's target stops for an operator at its return
154 // (returning) and at its release (releasing, whose files cannot be
155 // archived: final review M1); every other stuck is the source's.
156 e.Fields["role"] = "source"
157 if rb := am.Rollback; rb != nil && (rb.TargetPhase == provision.RbTargetReturning ||
158 rb.TargetPhase == provision.RbTargetReleasing && am.StuckReason == provision.StuckReleaseTargetFiles) {
159 e.Fields["role"] = "target"
160 }
161 if am.StuckReason != "" {
162 e.Fields["reason"] = am.StuckReason
163 }
164 return e, true
165 case am.State == provision.MoveLanding && am.Landing != nil && am.Landing.Attempts > 0 && (bm.Landing == nil || am.Landing.Attempts > bm.Landing.Attempts):
166 e.Event, e.Identity = "probe_attempt", "probe_attempt-"+strconv.Itoa(am.Landing.Attempts)
167 return e, true
168 case bm.State == provision.MoveCutting && am.State == provision.MoveLanding:
169 e.Event, e.Identity = "flipped", "cutting-flipped"
170 return e, true
171 case am.State == provision.MoveCommitted && bm.State == provision.MoveCommitted:
172 switch {
173 case am.SourceParkedAt != nil && bm.SourceParkedAt == nil:
174 e.Event, e.Identity = "source_parked", "committed-source_parked"
175 case am.OpenedAt != nil && bm.OpenedAt == nil:
176 e.Event, e.Identity = "opened", "committed-opened"
177 if am.CutAt != nil {
178 e.Fields["offline_s"] = seconds(*am.CutAt, *am.OpenedAt)
179 }
180 default:
181 return Transition{}, false
182 }
183 return e, true
184 case am.State == provision.MoveReverting && bm.State == provision.MoveReverting:
185 if am.ReopenedAt == nil || bm.ReopenedAt != nil {
186 return Transition{}, false
187 }
188 e.Event, e.Identity = "rev_opened", "reverting-opened"
189 if am.CutAt != nil {
190 e.Fields["offline_s"] = seconds(*am.CutAt, *am.ReopenedAt)
191 }
192 return e, true
193 }
194 phase, target, source := phaseOf(am)
195 bphase, btarget, bsource := phaseOf(bm)
196 if am.State == bm.State {
197 switch {
198 case am.State == provision.MoveRollingBack && target != btarget:
199 e.Event, e.Identity = target, "rolling_back-target-"+target
200 if target == provision.RbTargetLost {
201 e.Event = "declared_lost"
202 }
203 case am.State == provision.MoveRollingBack && source != bsource:
204 e.Event, e.Identity = source, "rolling_back-source-"+source
205 rb := am.Rollback
206 switch source {
207 case provision.RbSourceRecovered:
208 if rb.RecoveredAt != nil {
209 if rb.RequestedAt != nil {
210 e.Fields["recovery_s"] = seconds(*rb.RequestedAt, *rb.RecoveredAt)
211 }
212 if am.CutAt != nil {
213 e.Fields["offline_s"] = seconds(*am.CutAt, *rb.RecoveredAt)
214 }
215 }
216 case provision.RbSourceOpened:
217 if rb.OpenedAt != nil && am.CutAt != nil {
218 e.Fields["offline_s"] = seconds(*am.CutAt, *rb.OpenedAt)
219 }
220 }
221 case phase != bphase && phase != "":
222 e.Event, e.Identity = phase, am.State+"-"+phase
223 if am.State == provision.MoveCutting && phase == provision.CutRequested {
224 e.Event = "cut_requested"
225 }
226 default:
227 return Transition{}, false
228 }
229 return e, true
230 }
231 // A new state.
232 switch am.State {
233 case provision.MovePreparing:
234 e.Event, e.Identity = "requested", "requested"
235 case provision.MoveCutting:
236 e.Event, e.Identity = phase, "cutting-"+phase
237 if phase == provision.CutRequested || phase == "" {
238 e.Event, e.Identity = "cut_requested", "cutting-requested"
239 }
240 case provision.MoveLanding:
241 e.Event, e.Identity = phase, "landing-"+phase
242 case provision.MoveRollingBack:
243 e.Event, e.Identity = "rolling_back", "rolling_back"
244 if am.Rollback != nil && am.Rollback.Reason != "" {
245 e.Fields["reason"] = am.Rollback.Reason
246 }
247 default:
248 // ready, committed, aborting, reverting, relocating: the state is the
249 // event.
250 e.Event, e.Identity = am.State, am.State
251 }
252 return e, true
253}
254