package host import ( "context" "errors" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/lease" "github.com/carlosframework/platform/internal/park" "strings" "time" ) // ClaimRetirementCensus supplies identity; the regional engine retains retirement // authority and region-specific live-set checks. Bare paths need complete history. func ClaimRetirementCensus(o hostclaim.Observed, versions []lease.ObjectVersion) (map[string]string, error) { if o.Absent || o.Claim == nil || o.ETag == "" { return nil, hostbox.ErrChanged } if err := o.Claim.Validate(); err != nil { return nil, err } locations, err := park.ResidueLocations(o.Host, versions) if err != nil { return nil, err } labels := map[string]string{} var dependency error for _, loc := range locations { if loc == "moves/"+o.Host { continue } if loc != "instances/"+o.Host { // Identity is encoded in this validated qualified path, even beyond the history cap. labels[loc] = strings.TrimPrefix(loc, "instances/"+o.Host+"/") continue } inc, err := claimLegacyIncarnation(o.Claim) if err != nil { dependency = err continue } labels[loc] = inc } return labels, dependency } func claimRetirementDependency() error { return &ClaimDependencyError{Contracts: []string{"claim archived incarnation and location evidence"}} } func claimLegacyIncarnation(c *hostclaim.Claim) (string, error) { if c == nil || len(c.RetiredIncarnations) >= 32 { return "", claimRetirementDependency() } loc, inc := "instances/"+c.Host, "" if c.Holder != nil && c.Holder.Location == loc { inc = c.Holder.Incarnation } for _, retired := range c.RetiredIncarnations { where := c.RetiredLocations[retired] if where == "" { return "", claimRetirementDependency() } if where == loc { if inc != "" && inc != retired { return "", claimRetirementDependency() } inc = retired } } if inc == "" { return "", claimRetirementDependency() } return inc, nil } func claimArchivedLocations(c *hostclaim.Claim) map[string]string { labels := map[string]string{} if c == nil { return labels } if inc, err := claimLegacyIncarnation(c); err == nil { labels["instances/"+c.Host] = inc } return labels } func claimFreeMoveProof(o hostclaim.Observed) (park.MoveSweepProof, error) { if o.Absent || o.Claim == nil || o.ETag == "" || !o.Claim.Free() || len(o.Claim.RetiredIncarnations) == 0 { return park.MoveSweepProof{}, claimRetirementDependency() } var at time.Time for _, bound := range o.Claim.QuietAfter { if bound.After(at) { at = bound } } if at.IsZero() { return park.MoveSweepProof{}, claimRetirementDependency() } return park.MoveSweepProof{Host: o.Host, ClaimETag: o.ETag, MoveID: "claim-free", Incarnation: o.Claim.RetiredIncarnations[0], RetiredAt: at}, nil } func (p productionClaimRegional) retirement(ctx context.Context, o hostclaim.Observed, v []lease.ObjectVersion) (string, time.Time, error) { _, err := ClaimRetirementCensus(o, v) var dependency *ClaimDependencyError if err != nil && !errors.As(err, &dependency) { return "", time.Time{}, err } now := time.Now().UTC() if p.Now != nil { now = p.Now().UTC() } if now.IsZero() { return "", time.Time{}, hostbox.ErrChanged } if proof, err := claimFreeMoveProof(o); err == nil { if proof.RetiredAt.After(now) { return "", time.Time{}, hostbox.ErrChanged } return proof.Incarnation, proof.RetiredAt, nil } if p.Parks == nil || p.Parks.Evidence == nil { return "", time.Time{}, claimRetirementDependency() } // The moves provider must vouch for instance generations as well as lanes. inc, at, err := p.Parks.Evidence.VerifyRetirement(ctx, o, v) if err != nil { return "", time.Time{}, err } if inc == "" || at.IsZero() || at.After(now) { return "", time.Time{}, hostbox.ErrChanged } return inc, at, nil }