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 "encoding/json"
5 "errors"
6 "fmt"
7 "os"
8 "time"
9)
10
11var ErrRetiredIncarnation = errors.New("hostclaim: incarnation was retired on this host")
12
13type NewHolder struct {
14 Acquisition string
15 Family, Key, Account, App, Name, Host string
16 RecordRegion string // Region encoded in the record; empty means home region.
17 Region string // Resolved storage region; must be non-empty.
18 CreatedAt time.Time
19}
20
21func keyMatches(nh NewHolder) bool {
22 return holderKeyMatches(nh.Family, nh.Key, nh.Account, nh.App, nh.Name, nh.Host)
23}
24
25// recordMatches checks the record body that create persists as its
26// reconstruction intent is this holder's record: valid JSON naming the
27// same host, account, app, sidecar name (empty for instances) and region,
28// and the same CreatedAt.
29func recordMatches(nh NewHolder, body []byte) bool {
30 var r struct {
31 Name string `json:"name"` // sidecar records only
32 Host string `json:"host"`
33 Account string `json:"account"`
34 App string `json:"app"`
35 Region string `json:"region"`
36 CreatedAt time.Time `json:"created_at"`
37 }
38 if err := json.Unmarshal(body, &r); err != nil {
39 return false
40 }
41 return r.Host == nh.Host && r.Account == nh.Account && r.App == nh.App && r.Name == nh.Name &&
42 r.Region == nh.RecordRegion && r.CreatedAt.Equal(nh.CreatedAt)
43}
44
45func retire(c *Claim, inc string) {
46 if os.Getenv("CARLOS_HOST_CLAIM_RETIRED_LOCATIONS") == "1" && c.Holder != nil && c.Holder.Incarnation == inc && isHex64(inc) {
47 if c.RetiredLocations == nil {
48 c.RetiredLocations = map[string]string{}
49 }
50 c.RetiredLocations[inc] = c.Holder.Location
51 }
52 c.RetiredIncarnations = append([]string{inc}, c.RetiredIncarnations...)
53 if len(c.RetiredIncarnations) > retiredCap {
54 c.RetiredIncarnations = c.RetiredIncarnations[:retiredCap]
55 for retired := range c.RetiredLocations {
56 if !stringPresent(c.RetiredIncarnations, retired) {
57 delete(c.RetiredLocations, retired)
58 }
59 }
60 }
61}
62
63func BeginCreate(nh NewHolder, recordBody []byte, opID, by string, now time.Time) func(*Claim) error {
64 return func(c *Claim) error {
65 if nh.Host != c.Host || !keyMatches(nh) || nh.Region == "" {
66 return fmt.Errorf("%w: create inputs are not this host's", ErrWrongState)
67 }
68 if !c.Free() {
69 return fmt.Errorf("%w: host is held", ErrWrongState)
70 }
71 if nh.CreatedAt.IsZero() || opID == "" {
72 return fmt.Errorf("%w: create needs CreatedAt and an op id", ErrMalformed)
73 }
74 if !recordMatches(nh, recordBody) {
75 return fmt.Errorf("%w: the record body is not this holder's record", ErrWrongState)
76 }
77 inc, err := Incarnation(nh.Account, nh.App, nh.Host, nh.CreatedAt)
78 if err != nil {
79 return err
80 }
81 for _, r := range c.RetiredIncarnations {
82 if r == inc {
83 return ErrRetiredIncarnation
84 }
85 }
86 c.Holder = &Holder{Acquisition: nh.Acquisition, Family: nh.Family, Key: nh.Key, Account: nh.Account, App: nh.App, Name: nh.Name,
87 Incarnation: inc, CreatedAt: nh.CreatedAt, Location: "instances/" + nh.Host + "/" + inc}
88 c.Op = Op{Kind: KindCreating, ID: opID, By: by, StartedAt: now.UTC(),
89 Intent: map[string]string{"key": nh.Key, "record": string(recordBody)}}
90 c.Touched = Touched{Complete: true, Regions: []string{nh.Region}}
91 return nil
92 }
93}
94
95// ExecutorSelection is the box create selects once, with
96// boxchoice.Choose over the eligible set (claim P5-D2, region HA §5.2,
97// H12), and its roster enrollment.
98type ExecutorSelection struct {
99 Box, Enrollment string
100}
101
102// BeginCreateOn is BeginCreate recording the selected executor in the
103// create intent, in the same step 2 CAS, before any effect (claim §5.1,
104// P5-D6 `selected_executor`, `executor_enrollment`). CompleteCreate then
105// sets op.exec to exactly that box; recovery never substitutes another.
106func BeginCreateOn(nh NewHolder, recordBody []byte, opID, by string, now time.Time, sel ExecutorSelection) func(*Claim) error {
107 begin := BeginCreate(nh, recordBody, opID, by, now)
108 return func(c *Claim) error {
109 if !validBoxReference(sel.Box) || sel.Box == "none" || sel.Enrollment == "" {
110 return fmt.Errorf("%w: create needs a selected executor", ErrMalformed)
111 }
112 if err := begin(c); err != nil {
113 return err
114 }
115 c.Op.Intent["selected_executor"] = sel.Box
116 c.Op.Intent["executor_enrollment"] = sel.Enrollment
117 return nil
118 }
119}
120
121func CompleteCreate(opID, execBox string) func(*Claim) error {
122 return func(c *Claim) error {
123 if err := RequireOp(opID)(c); err != nil {
124 return err
125 }
126 if c.Op.Kind != KindCreating || c.Op.Phase == "cancelling" || execBox == "" || (c.Op.Intent["selected_executor"] != "" && execBox != c.Op.Intent["selected_executor"]) {
127 return fmt.Errorf("%w: kind %q", ErrWrongState, c.Op.Kind)
128 }
129 if err := requireNoCreateAuthority(c); err != nil {
130 return err
131 }
132 c.Op = Op{Kind: KindServing, Exec: execBox, Participants: []string{execBox}}
133 c.Holder.Stamp = c.Gen + 1 // the resulting gen: Update sets gen = observed+1
134 if !set(c.Touched.Boxes)[execBox] { // the executor will hold local state (§2 touched)
135 c.Touched.Boxes = append(c.Touched.Boxes, execBox)
136 }
137 return nil
138 }
139}
140
141func CancelCreate(opID string, now time.Time) func(*Claim) error {
142 return func(c *Claim) error {
143 if err := RequireOp(opID)(c); err != nil {
144 return err
145 }
146 if c.Op.Kind != KindCreating {
147 return fmt.Errorf("%w: kind %q", ErrWrongState, c.Op.Kind)
148 }
149 if err := requireNoCreateAuthority(c); err != nil {
150 return err
151 }
152 if err := RequireLossEvidenceArchived(c); err != nil {
153 return err
154 }
155 inc, key := c.Holder.Incarnation, c.Holder.Key
156 retire(c, inc)
157 c.CancelledCreates = append(c.CancelledCreates, CancelledCreate{Key: key, Incarnation: inc, Until: now.UTC().Add(time.Hour)})
158 c.Holder, c.Op, c.Touched = nil, Op{}, Touched{}
159 return nil
160 }
161}
162
163func requireNoCreateAuthority(c *Claim) error {
164 selected := c.Op.Intent["selected_executor"]
165 selectedOnly := len(c.Op.Participants) == 1 && selected != "" && c.Op.Participants[0] == selected && c.Op.Intent["executor_enrollment"] != ""
166 if c.Op.Exec != "" || (len(c.Op.Participants) > 0 && !selectedOnly) || c.Holder.Stamp != 0 {
167 return fmt.Errorf("%w: create carries execution authority", ErrWrongState)
168 }
169 return nil
170}
171