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 "errors"
5
6 "github.com/carlosframework/platform/internal/genselect"
7 "github.com/carlosframework/platform/internal/hostclaim"
8)
9
10// The start check (spec §5.1, INV-4, J2-R2-8): before every spawn the three
11// markers a generation leaves on a box are compared with the host claim. A
12// marker that merely exists proves nothing; the claim says which generation
13// this box may run.
14//
15// The three markers have three formats (Paul's ruling on H11):
16//
17// - generation.json is genselect.Marker, read from the box's own location;
18// absent is the origin.
19// - .files-gen is the JSON {op, incarnation} (FilesGen); absent is "no
20// files directory yet".
21// - <dbPath>.gen is the plain generation name hostbox.WriteGenerationMarker
22// writes and ColdRestore rewrites; "" is absent. It carries no incarnation
23// and no location: the claim's holder and the restore vouch for those.
24//
25// Refusals are typed and carry member-safe reasons, never marker contents.
26var (
27 // ErrStartMarker: generation.json does not name a generation the claim
28 // expects, or names another incarnation.
29 ErrStartMarker = errors.New("this box holds another generation of the instance")
30 // ErrStartFiles: .files-gen does not name a generation and incarnation
31 // the claim expects.
32 ErrStartFiles = errors.New("the instance's files belong to another generation")
33 // ErrStartDB: the database's marker does not name the claim's generation.
34 ErrStartDB = errors.New("the instance's database belongs to another generation")
35)
36
37// FilesGen is the .files-gen body (§5.1): the op and the incarnation.
38type FilesGen struct {
39 Op string `json:"op"`
40 Incarnation string `json:"incarnation"`
41}
42
43// originOr names the origin generation the way the claim's admissions do.
44func originOr(g string) string {
45 if g == "" {
46 return hostclaim.RootGeneration
47 }
48 return g
49}
50
51// coldAdmission is the newest admission into generation g that is not a warm
52// self-link (F from F, the warm keep genselect's unfinished skips). Its
53// predecessor is where g came from.
54func coldAdmission(h *hostclaim.Holder, g string) (hostclaim.AdmissionRecord, bool) {
55 for i := len(h.Admissions) - 1; i >= 0; i-- {
56 a := h.Admissions[i]
57 if originOr(a.Generation) != g || a.Mode == "warm" {
58 continue
59 }
60 return a, true
61 }
62 return hostclaim.AdmissionRecord{}, false
63}
64
65// markerGenerations is the set of generations generation.json may name for
66// this holder: holder.generation, as the admission's CAS left it, and the
67// lagging ones. A marker is published once per admission, at Replicate, so
68// after a restarted attempt (a fresh generation restored from the prepared
69// one) or a run of admissions that published nothing, the marker still names
70// an earlier generation. Walking holder.admissions from the newest, skipping
71// warm self-links, each cold admission into an accepted generation adds its
72// predecessor. The origin is hostclaim.RootGeneration here.
73func markerGenerations(h *hostclaim.Holder) map[string]bool {
74 cur := originOr(h.Generation)
75 ok := map[string]bool{cur: true}
76 for {
77 a, found := coldAdmission(h, cur)
78 if !found {
79 return ok
80 }
81 next := originOr(a.Predecessor)
82 if ok[next] { // damaged record: predecessors that loop
83 return ok
84 }
85 ok[next] = true
86 cur = next
87 }
88}
89
90// filesGenerationOK reports whether .files-gen may name g: exactly
91// holder.generation, or the cold predecessor of holder.generation (the
92// prepared generation a restarted attempt was restored from, whose files
93// are still the ones installed). Nothing further back counts: files from
94// before the preparation are stale.
95func filesGenerationOK(h *hostclaim.Holder, g string) bool {
96 if g == "" || g == hostclaim.RootGeneration {
97 return false
98 }
99 if g == h.Generation {
100 return true
101 }
102 a, found := coldAdmission(h, originOr(h.Generation))
103 return found && a.Predecessor == g
104}
105
106// StartCheck is the §5.1 start check against the claim, run after the
107// admission CAS and before every spawn.
108//
109// 1. marker (generation.json; nil is the origin) names a generation the
110// claim expects (holder.generation or, lagging, an earlier link of the
111// admission chain) and the holder's incarnation.
112// 2. filesGen, when present, names holder.generation or its cold
113// predecessor (a restarted attempt's files still name the prepared
114// generation; nothing older) and the holder's incarnation.
115// 3. dbGen, when not "", is exactly holder.generation: the restore rewrites
116// it, so it is current after every restore. A warm database that fails
117// here is from an earlier stay; the caller parks it and restores cold.
118//
119// A claim with no holder expects no generation, so every start is refused
120// with ErrStartMarker.
121func StartCheck(c *hostclaim.Claim, marker *genselect.Marker, filesGen *FilesGen, dbGen string) error {
122 if c == nil || c.Holder == nil {
123 return ErrStartMarker
124 }
125 h := c.Holder
126 want := markerGenerations(h)
127 if marker == nil {
128 if !want[hostclaim.RootGeneration] {
129 return ErrStartMarker
130 }
131 } else if marker.Op == "" || marker.Op == hostclaim.RootGeneration ||
132 !want[marker.Op] || marker.Incarnation != h.Incarnation {
133 return ErrStartMarker
134 }
135 if filesGen != nil {
136 if !filesGenerationOK(h, filesGen.Op) || filesGen.Incarnation != h.Incarnation {
137 return ErrStartFiles
138 }
139 }
140 if dbGen != "" && dbGen != h.Generation {
141 return ErrStartDB
142 }
143 return nil
144}
145
146// PreparedCheck validates a landing probe's inputs before admission (Plan 3
147// Task 5 step 3): the files marker and the database marker both exist and
148// name the prepared generation (the move id), and the files marker names the
149// holder's incarnation. The database marker is the plain name (H11); the
150// location is vouched for by the holder.
151func PreparedCheck(prepared string, holder hostclaim.Holder, filesGen *FilesGen, dbGen string) error {
152 if prepared == "" || prepared == hostclaim.RootGeneration {
153 return ErrStartFiles
154 }
155 if filesGen == nil || filesGen.Op != prepared || filesGen.Incarnation != holder.Incarnation {
156 return ErrStartFiles
157 }
158 if dbGen != prepared {
159 return ErrStartDB
160 }
161 return nil
162}
163