| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "slices" |
| 6 | |
| 7 | "github.com/carlosframework/platform/internal/hostbox" |
| 8 | "github.com/carlosframework/platform/internal/hostclaim" |
| 9 | execlease "github.com/carlosframework/platform/internal/hostexec" |
| 10 | "github.com/carlosframework/platform/internal/hostwrite" |
| 11 | ) |
| 12 | |
| 13 | type participationCleanupRead func(context.Context, string) (hostclaim.Observed, error) |
| 14 | |
| 15 | func (f participationCleanupRead) Read(ctx context.Context, host string) (hostclaim.Observed, error) { |
| 16 | return f(ctx, host) |
| 17 | } |
| 18 | |
| 19 | func previousParticipationRetired(o hostclaim.Observed, binding execlease.Binding) bool { |
| 20 | if o.Absent || o.Claim == nil || o.Host != binding.Host || o.Claim.Host != binding.Host || o.Claim.Validate() != nil { |
| 21 | return false |
| 22 | } |
| 23 | c := o.Claim |
| 24 | return c.Free() || (c.Holder != nil && c.Holder.Incarnation != binding.Incarnation && slices.Contains(c.RetiredIncarnations, binding.Incarnation)) |
| 25 | } |
| 26 | |
| 27 | func (p *productionClaimLocal) clearUntracked(ctx context.Context, actor hostbox.Actor, o hostclaim.Observed, state execlease.State, cfg execlease.Config) error { |
| 28 | if p.Resources.Mode != "agent" || state.Box != actor.Box || state.Enrollment != actor.Enrollment || cfg.Binding != state.Binding { |
| 29 | return execlease.ErrState |
| 30 | } |
| 31 | if o.Claim != nil && o.Claim.Holder != nil && state.Incarnation == o.Claim.Holder.Incarnation { |
| 32 | if err := p.cleanup(ctx, o, state.Binding); err != nil { |
| 33 | return err |
| 34 | } |
| 35 | return execlease.ClearStale(ctx, cfg) |
| 36 | } |
| 37 | if !previousParticipationRetired(o, state.Binding) { |
| 38 | return execlease.ErrState |
| 39 | } |
| 40 | p.mu.Lock() |
| 41 | a := p.assembly |
| 42 | p.mu.Unlock() |
| 43 | if a == nil || a.Launches == nil || a.Reconcile == nil || a.Reconcile.Census == nil || cfg.Guard.Claims == nil { |
| 44 | return hostwrite.ErrUnavailable |
| 45 | } |
| 46 | p.cancelWork(o.Host) |
| 47 | reader := cfg.Guard.Claims |
| 48 | cfg.Guard.Claims = participationCleanupRead(func(ctx context.Context, host string) (hostclaim.Observed, error) { |
| 49 | fresh, err := reader.Read(ctx, host) |
| 50 | if err != nil { |
| 51 | return fresh, err |
| 52 | } |
| 53 | if !previousParticipationRetired(fresh, state.Binding) { |
| 54 | return fresh, execlease.ErrState |
| 55 | } |
| 56 | has, err := a.Launches.HasHost(ctx, host) |
| 57 | if err != nil { |
| 58 | return fresh, err |
| 59 | } |
| 60 | if has { |
| 61 | return fresh, ErrClaimStopInventory |
| 62 | } |
| 63 | // ClearStale holds the controller lifetime lock and has durably denied |
| 64 | // the previous state. Prove the exact old incarnation empty here; never |
| 65 | // infer a stop target from the replacement holder or fabricate a stamp. |
| 66 | err = a.Reconcile.Census.CheckLocal(ctx, fresh, hostbox.RecordSelection{Host: host, Incarnation: state.Incarnation}) |
| 67 | return fresh, err |
| 68 | }) |
| 69 | return execlease.ClearStale(ctx, cfg) |
| 70 | } |
| 71 | |