| 1 | package moves |
| 2 | |
| 3 | import "time" |
| 4 | |
| 5 | // The steps of a StoppedReport, in §5.3's order. StepTree to StepLease |
| 6 | // are what stopevidence.Reconstruct observes from the box's durable state; |
| 7 | // StepReplica is the caller's (the cut's step 5: the final checkpoint and |
| 8 | // the digests), set after the checkpoint. |
| 9 | const ( |
| 10 | // StepTree: the host's cgroup was killed and drained (or never existed). |
| 11 | StepTree = "tree" |
| 12 | // StepStray: the agent's own cgroup leaf holds only the agent. |
| 13 | StepStray = "stray" |
| 14 | // StepFDs: no visible process holds the database, its WAL or its SHM. |
| 15 | StepFDs = "fds" |
| 16 | // StepLease: the host's lock.json is absent, released, or expired by |
| 17 | // more than the lease's renewal margin. |
| 18 | StepLease = "lease" |
| 19 | // StepReplica: the final checkpoint reached the replica. |
| 20 | StepReplica = "replica" |
| 21 | ) |
| 22 | |
| 23 | // treeSteps are §5.3 steps 1 to 4, what `stopping` needs before the final |
| 24 | // checkpoint. allSteps adds the replica. |
| 25 | var ( |
| 26 | treeSteps = [...]string{StepTree, StepStray, StepFDs, StepLease} |
| 27 | allSteps = [...]string{StepTree, StepStray, StepFDs, StepLease, StepReplica} |
| 28 | ) |
| 29 | |
| 30 | // StoppedReport is a role's stop evidence for one move operation (§5.3), |
| 31 | // written to moves/<acct>/<app>/<host>/<op id>/stopped-<role>.json. Every |
| 32 | // observation is from durable, observable state, so a restarted agent |
| 33 | // writes the same report. Steps holds one Check per step id; a step that |
| 34 | // never ran is absent, and an absent step is not OK. Stray lists the pids |
| 35 | // StepStray found in the agent's leaf besides the agent, for the alert. A 0 |
| 36 | // in it marks members the box could not identify (not visible in its pid |
| 37 | // namespace). |
| 38 | type StoppedReport struct { |
| 39 | V int `json:"v"` |
| 40 | Role string `json:"role"` |
| 41 | Box string `json:"box"` |
| 42 | Incarnation string `json:"incarnation"` |
| 43 | ClaimGen uint64 `json:"claim_gen"` |
| 44 | Host string `json:"host"` |
| 45 | OpID string `json:"op_id"` |
| 46 | Steps map[string]Check `json:"steps"` |
| 47 | Stray []int `json:"stray,omitempty"` |
| 48 | FinalTXID uint64 `json:"final_txid"` |
| 49 | DBSHA256 string `json:"db_sha256"` |
| 50 | FilesPlain string `json:"files_plain"` |
| 51 | TimersSHA256 string `json:"timers_sha256"` |
| 52 | CheckedAt time.Time `json:"checked_at"` |
| 53 | } |
| 54 | |
| 55 | // TreeStopped reports §5.3 steps 1 to 4 all present and OK: the writer has |
| 56 | // fully stopped, so the final checkpoint may run. |
| 57 | func (r StoppedReport) TreeStopped() bool { |
| 58 | return r.stepsOK(treeSteps[:]) |
| 59 | } |
| 60 | |
| 61 | // Complete reports every step OK, StepReplica included. It fails closed on |
| 62 | // any other step recorded in the report and not OK, so a step a later |
| 63 | // version adds cannot be ignored by an older reader. |
| 64 | func (r StoppedReport) Complete() bool { |
| 65 | if !r.stepsOK(allSteps[:]) { |
| 66 | return false |
| 67 | } |
| 68 | for _, c := range r.Steps { |
| 69 | if !c.OK { |
| 70 | return false |
| 71 | } |
| 72 | } |
| 73 | return true |
| 74 | } |
| 75 | |
| 76 | func (r StoppedReport) stepsOK(ids []string) bool { |
| 77 | for _, id := range ids { |
| 78 | if c, ok := r.Steps[id]; !ok || !c.OK { |
| 79 | return false |
| 80 | } |
| 81 | } |
| 82 | return true |
| 83 | } |
| 84 | |