package hostclaim import ( "fmt" "time" ) // WriteSet is where a delete parks: the touched set, plus the whole // deployment while discovery is incomplete (§5.2 step 3, §9). func WriteSet(c *Claim, allRegions, allBoxes []string) (regions, boxes []string) { if c.Touched.Complete { return c.Touched.Regions, c.Touched.Boxes } return union(c.Touched.Regions, allRegions), union(c.Touched.Boxes, allBoxes) } func union(known, supplied []string) []string { var result []string seen := make(map[string]bool) for _, locations := range [][]string{known, supplied} { for _, location := range locations { if !seen[location] { result = append(result, location) seen[location] = true } } } return result } // BeginDelete starts a delete from serving. fencedCompletion is // StartGuard's (ruling 86): the completion box whose termination fence the // caller verified, or "". func BeginDelete(opID, by, recordETag string, now time.Time, allRegions, allBoxes []string, fencedCompletion string) func(*Claim) error { return func(c *Claim) error { regions, boxes := WriteSet(c, allRegions, allBoxes) if err := StartGuard(c, regions, boxes, fencedCompletion); err != nil { return err } if opID == "" || recordETag == "" { return fmt.Errorf("%w: delete needs an op id and the record's etag", ErrMalformed) } exec, parts := c.Op.Exec, c.Op.Participants // execution authority is kept (R6-4) c.Op = Op{Kind: KindDeleting, ID: opID, By: by, StartedAt: now.UTC(), Exec: exec, Participants: parts, Intent: map[string]string{"key": c.Holder.Key, "etag": recordETag}} return nil } } func ToParking(opID string, allRegions, allBoxes []string, newID func() string) func(*Claim) error { return func(c *Claim) error { if err := RequireOp(opID)(c); err != nil { return err } // purging: §5.3 step 4 retires records under the purge's own claim // (R4-4); Plan 5 adds the purge journal's preconditions around it. if c.Op.Kind != KindDeleting && c.Op.Kind != KindPurging { return fmt.Errorf("%w: kind %q", ErrWrongState, c.Op.Kind) } if c.Holder == nil { // a holderless purge fence (CC21) has nothing to park return fmt.Errorf("%w: no holder to park", ErrWrongState) } regions, boxes := WriteSet(c, allRegions, allBoxes) appendParkObligations(c, opID, regions, boxes, newID) retire(c, c.Holder.Incarnation) if c.Op.Kind == KindDeleting && c.Op.Phase == "" { c.Op.Phase = "park" } c.Op.Kind = KindParking return nil } } func appendParkObligations(c *Claim, opID string, regions, boxes []string, newID func() string) { target := "" if c.Holder != nil { target = c.Holder.Location } for _, r := range regions { c.Obligations = append(c.Obligations, Obligation{ParkTarget: target, ID: newID(), Op: opID, Kind: "park-replica", Region: r, Retirement: newID(), Owner: "region:" + r}) } for _, b := range boxes { c.Obligations = append(c.Obligations, Obligation{ID: newID(), Op: opID, Kind: "park-local", Box: b, Retirement: newID(), Owner: "box:" + b}) } } // CompleteObligation removes one chore, matched by its OWN op id and id. // Discovery chores outlive the adoption that made them (§9). func CompleteObligation(obligationOp, obligationID string) func(*Claim) error { return func(c *Claim) error { idx := -1 for i, o := range c.Obligations { if o.ID == obligationID && o.Op == obligationOp { idx = i break } } if idx < 0 { return fmt.Errorf("%w: no obligation %q of op %q", ErrWrongState, obligationID, obligationOp) } if c.Obligations[idx].Kind == "relocation-release" { return ErrWrongState } c.Obligations = append(c.Obligations[:idx], c.Obligations[idx+1:]...) if c.Touched.DiscoveryOp != "" && c.Touched.DiscoveryOp == obligationOp { for _, o := range c.Obligations { if o.Op == obligationOp { return nil } } c.Touched.Complete, c.Touched.DiscoveryOp = true, "" } return nil } } func FreeAfterPark(opID string) func(*Claim) error { return func(c *Claim) error { if err := RequireOp(opID)(c); err != nil { return err } if c.Op.Kind != KindParking || len(c.Obligations) > 0 { return fmt.Errorf("%w: kind %q, %d obligations left", ErrWrongState, c.Op.Kind, len(c.Obligations)) } if err := retainNativeAdmissionLosses(c); err != nil { return err } c.Holder, c.Op, c.Touched, c.Inventory = nil, Op{}, Touched{}, nil return nil } }