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
8// WriteSet is where a delete parks: the touched set, plus the whole
9// deployment while discovery is incomplete (§5.2 step 3, §9).
10func WriteSet(c *Claim, allRegions, allBoxes []string) (regions, boxes []string) {
11 if c.Touched.Complete {
12 return c.Touched.Regions, c.Touched.Boxes
13 }
14 return union(c.Touched.Regions, allRegions), union(c.Touched.Boxes, allBoxes)
15}
16
17func union(known, supplied []string) []string {
18 var result []string
19 seen := make(map[string]bool)
20 for _, locations := range [][]string{known, supplied} {
21 for _, location := range locations {
22 if !seen[location] {
23 result = append(result, location)
24 seen[location] = true
25 }
26 }
27 }
28 return result
29}
30
31// BeginDelete starts a delete from serving. fencedCompletion is
32// StartGuard's (ruling 86): the completion box whose termination fence the
33// caller verified, or "".
34func BeginDelete(opID, by, recordETag string, now time.Time, allRegions, allBoxes []string, fencedCompletion string) func(*Claim) error {
35 return func(c *Claim) error {
36 regions, boxes := WriteSet(c, allRegions, allBoxes)
37 if err := StartGuard(c, regions, boxes, fencedCompletion); err != nil {
38 return err
39 }
40 if opID == "" || recordETag == "" {
41 return fmt.Errorf("%w: delete needs an op id and the record's etag", ErrMalformed)
42 }
43 exec, parts := c.Op.Exec, c.Op.Participants // execution authority is kept (R6-4)
44 c.Op = Op{Kind: KindDeleting, ID: opID, By: by, StartedAt: now.UTC(), Exec: exec, Participants: parts,
45 Intent: map[string]string{"key": c.Holder.Key, "etag": recordETag}}
46 return nil
47 }
48}
49
50func ToParking(opID string, allRegions, allBoxes []string, newID func() string) func(*Claim) error {
51 return func(c *Claim) error {
52 if err := RequireOp(opID)(c); err != nil {
53 return err
54 }
55 // purging: §5.3 step 4 retires records under the purge's own claim
56 // (R4-4); Plan 5 adds the purge journal's preconditions around it.
57 if c.Op.Kind != KindDeleting && c.Op.Kind != KindPurging {
58 return fmt.Errorf("%w: kind %q", ErrWrongState, c.Op.Kind)
59 }
60 if c.Holder == nil { // a holderless purge fence (CC21) has nothing to park
61 return fmt.Errorf("%w: no holder to park", ErrWrongState)
62 }
63 regions, boxes := WriteSet(c, allRegions, allBoxes)
64 appendParkObligations(c, opID, regions, boxes, newID)
65 retire(c, c.Holder.Incarnation)
66 if c.Op.Kind == KindDeleting && c.Op.Phase == "" {
67 c.Op.Phase = "park"
68 }
69 c.Op.Kind = KindParking
70 return nil
71 }
72}
73
74func appendParkObligations(c *Claim, opID string, regions, boxes []string, newID func() string) {
75 target := ""
76 if c.Holder != nil {
77 target = c.Holder.Location
78 }
79 for _, r := range regions {
80 c.Obligations = append(c.Obligations, Obligation{ParkTarget: target, ID: newID(), Op: opID, Kind: "park-replica", Region: r, Retirement: newID(), Owner: "region:" + r})
81 }
82 for _, b := range boxes {
83 c.Obligations = append(c.Obligations, Obligation{ID: newID(), Op: opID, Kind: "park-local", Box: b, Retirement: newID(), Owner: "box:" + b})
84 }
85}
86
87// CompleteObligation removes one chore, matched by its OWN op id and id.
88// Discovery chores outlive the adoption that made them (§9).
89func CompleteObligation(obligationOp, obligationID string) func(*Claim) error {
90 return func(c *Claim) error {
91 idx := -1
92 for i, o := range c.Obligations {
93 if o.ID == obligationID && o.Op == obligationOp {
94 idx = i
95 break
96 }
97 }
98 if idx < 0 {
99 return fmt.Errorf("%w: no obligation %q of op %q", ErrWrongState, obligationID, obligationOp)
100 }
101 if c.Obligations[idx].Kind == "relocation-release" {
102 return ErrWrongState
103 }
104 c.Obligations = append(c.Obligations[:idx], c.Obligations[idx+1:]...)
105 if c.Touched.DiscoveryOp != "" && c.Touched.DiscoveryOp == obligationOp {
106 for _, o := range c.Obligations {
107 if o.Op == obligationOp {
108 return nil
109 }
110 }
111 c.Touched.Complete, c.Touched.DiscoveryOp = true, ""
112 }
113 return nil
114 }
115}
116
117func FreeAfterPark(opID string) func(*Claim) error {
118 return func(c *Claim) error {
119 if err := RequireOp(opID)(c); err != nil {
120 return err
121 }
122 if c.Op.Kind != KindParking || len(c.Obligations) > 0 {
123 return fmt.Errorf("%w: kind %q, %d obligations left", ErrWrongState, c.Op.Kind, len(c.Obligations))
124 }
125 if err := retainNativeAdmissionLosses(c); err != nil {
126 return err
127 }
128 c.Holder, c.Op, c.Touched, c.Inventory = nil, Op{}, Touched{}, nil
129 return nil
130 }
131}
132