| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "errors" |
| 6 | "github.com/carlosframework/platform/internal/hostbox" |
| 7 | "github.com/carlosframework/platform/internal/hostclaim" |
| 8 | "github.com/carlosframework/platform/internal/lease" |
| 9 | "github.com/carlosframework/platform/internal/park" |
| 10 | "strings" |
| 11 | "time" |
| 12 | ) |
| 13 | |
| 14 | // ClaimRetirementCensus supplies identity; the regional engine retains retirement |
| 15 | // authority and region-specific live-set checks. Bare paths need complete history. |
| 16 | func ClaimRetirementCensus(o hostclaim.Observed, versions []lease.ObjectVersion) (map[string]string, error) { |
| 17 | if o.Absent || o.Claim == nil || o.ETag == "" { |
| 18 | return nil, hostbox.ErrChanged |
| 19 | } |
| 20 | if err := o.Claim.Validate(); err != nil { |
| 21 | return nil, err |
| 22 | } |
| 23 | locations, err := park.ResidueLocations(o.Host, versions) |
| 24 | if err != nil { |
| 25 | return nil, err |
| 26 | } |
| 27 | labels := map[string]string{} |
| 28 | var dependency error |
| 29 | for _, loc := range locations { |
| 30 | if loc == "moves/"+o.Host { |
| 31 | continue |
| 32 | } |
| 33 | if loc != "instances/"+o.Host { |
| 34 | // Identity is encoded in this validated qualified path, even beyond the history cap. |
| 35 | labels[loc] = strings.TrimPrefix(loc, "instances/"+o.Host+"/") |
| 36 | continue |
| 37 | } |
| 38 | inc, err := claimLegacyIncarnation(o.Claim) |
| 39 | if err != nil { |
| 40 | dependency = err |
| 41 | continue |
| 42 | } |
| 43 | labels[loc] = inc |
| 44 | } |
| 45 | return labels, dependency |
| 46 | } |
| 47 | func claimRetirementDependency() error { |
| 48 | return &ClaimDependencyError{Contracts: []string{"claim archived incarnation and location evidence"}} |
| 49 | } |
| 50 | |
| 51 | func claimLegacyIncarnation(c *hostclaim.Claim) (string, error) { |
| 52 | if c == nil || len(c.RetiredIncarnations) >= 32 { |
| 53 | return "", claimRetirementDependency() |
| 54 | } |
| 55 | loc, inc := "instances/"+c.Host, "" |
| 56 | if c.Holder != nil && c.Holder.Location == loc { |
| 57 | inc = c.Holder.Incarnation |
| 58 | } |
| 59 | for _, retired := range c.RetiredIncarnations { |
| 60 | where := c.RetiredLocations[retired] |
| 61 | if where == "" { |
| 62 | return "", claimRetirementDependency() |
| 63 | } |
| 64 | if where == loc { |
| 65 | if inc != "" && inc != retired { |
| 66 | return "", claimRetirementDependency() |
| 67 | } |
| 68 | inc = retired |
| 69 | } |
| 70 | } |
| 71 | if inc == "" { |
| 72 | return "", claimRetirementDependency() |
| 73 | } |
| 74 | return inc, nil |
| 75 | } |
| 76 | func claimArchivedLocations(c *hostclaim.Claim) map[string]string { |
| 77 | labels := map[string]string{} |
| 78 | if c == nil { |
| 79 | return labels |
| 80 | } |
| 81 | if inc, err := claimLegacyIncarnation(c); err == nil { |
| 82 | labels["instances/"+c.Host] = inc |
| 83 | } |
| 84 | return labels |
| 85 | } |
| 86 | |
| 87 | func claimFreeMoveProof(o hostclaim.Observed) (park.MoveSweepProof, error) { |
| 88 | if o.Absent || o.Claim == nil || o.ETag == "" || !o.Claim.Free() || len(o.Claim.RetiredIncarnations) == 0 { |
| 89 | return park.MoveSweepProof{}, claimRetirementDependency() |
| 90 | } |
| 91 | var at time.Time |
| 92 | for _, bound := range o.Claim.QuietAfter { |
| 93 | if bound.After(at) { |
| 94 | at = bound |
| 95 | } |
| 96 | } |
| 97 | if at.IsZero() { |
| 98 | return park.MoveSweepProof{}, claimRetirementDependency() |
| 99 | } |
| 100 | return park.MoveSweepProof{Host: o.Host, ClaimETag: o.ETag, MoveID: "claim-free", Incarnation: o.Claim.RetiredIncarnations[0], RetiredAt: at}, nil |
| 101 | } |
| 102 | |
| 103 | func (p productionClaimRegional) retirement(ctx context.Context, o hostclaim.Observed, v []lease.ObjectVersion) (string, time.Time, error) { |
| 104 | _, err := ClaimRetirementCensus(o, v) |
| 105 | var dependency *ClaimDependencyError |
| 106 | if err != nil && !errors.As(err, &dependency) { |
| 107 | return "", time.Time{}, err |
| 108 | } |
| 109 | now := time.Now().UTC() |
| 110 | if p.Now != nil { |
| 111 | now = p.Now().UTC() |
| 112 | } |
| 113 | if now.IsZero() { |
| 114 | return "", time.Time{}, hostbox.ErrChanged |
| 115 | } |
| 116 | if proof, err := claimFreeMoveProof(o); err == nil { |
| 117 | if proof.RetiredAt.After(now) { |
| 118 | return "", time.Time{}, hostbox.ErrChanged |
| 119 | } |
| 120 | return proof.Incarnation, proof.RetiredAt, nil |
| 121 | } |
| 122 | if p.Parks == nil || p.Parks.Evidence == nil { |
| 123 | return "", time.Time{}, claimRetirementDependency() |
| 124 | } |
| 125 | // The moves provider must vouch for instance generations as well as lanes. |
| 126 | inc, at, err := p.Parks.Evidence.VerifyRetirement(ctx, o, v) |
| 127 | if err != nil { |
| 128 | return "", time.Time{}, err |
| 129 | } |
| 130 | if inc == "" || at.IsZero() || at.After(now) { |
| 131 | return "", time.Time{}, hostbox.ErrChanged |
| 132 | } |
| 133 | return inc, at, nil |
| 134 | } |
| 135 | |