package activator import ( "context" "github.com/carlosframework/platform/internal/registry" ) // PlannedSelection is the planner's checked choice of a build in place of // another, for one exec (settings plan 4, ruling R-PLANNED-RECOVERY; zero- // downtime spec §8.2): Build is the good build the plan boots instead of // InPlaceOf. It is one of // // - a planned fallback: a first wake's B failed and was proven stopped, // and the good build starts in its place (spawnPlanned, // spawnPlannedWorker); // - the good build booted untried, when too little of B's health window // was left at its exec (spawnPlanned); // - a recovery boot: decideWake planned the good build in place of the // installed build, which the failure book blocks (settleWakeIdentity, // spawnPlanned, a claimed start). // // The activator puts it on the context of that exec's record only, after // the plan's selection gate (Check) passed for Build. The agent's pre-exec // revalidation (release.HeldExecStale) admits Build when the state read under // the lease holds InPlaceOf as the instance's target: the planner's reason // for leaving InPlaceOf is then the reason the held target is left, as // zero-downtime spec §6 and settings spec §6 require. The record is still // written. It never admits the good build in general: a context with no // selection, or one made in place of another build, is revalidated as // before. type PlannedSelection struct{ Build, InPlaceOf string } type plannedSelectionKey struct{} // WithPlannedSelection returns ctx carrying s. A selection with either part // empty reads back as none, so a caller can mask one it inherited. func WithPlannedSelection(ctx context.Context, s PlannedSelection) context.Context { return context.WithValue(ctx, plannedSelectionKey{}, s) } // PlannedSelectionFrom is the selection ctx carries, if a whole one. func PlannedSelectionFrom(ctx context.Context) (PlannedSelection, bool) { s, ok := ctx.Value(plannedSelectionKey{}).(PlannedSelection) if !ok || s.Build == "" || s.InPlaceOf == "" { return PlannedSelection{}, false } return s, true } // recoverySelection is the selection a recovery plan makes (decideWake's // recovery: the good build booted because the failure book blocks the // installed build, or the installed build is not staged correctly): plan // boots the route's good build, as no first wake, in place of the build root // installed (rt.Version, the planner's "installed"). A plan that boots the // installed build itself, the good build included, selects nothing in place // of anything, and its record is revalidated as any other. func recoverySelection(plan WakePlan, rt registry.Route) (PlannedSelection, bool) { boot := plan.Boot.Version if !plan.planned() || plan.FirstWake || boot == "" || boot != rt.GoodVersion || rt.Version == "" || rt.Version == boot { return PlannedSelection{}, false } return PlannedSelection{Build: boot, InPlaceOf: rt.Version}, true } // withRecoverySelection is ctx carrying plan's recovery selection when plan // is a recovery and build is its boot; ctx itself otherwise. func withRecoverySelection(ctx context.Context, plan WakePlan, rt registry.Route, build string) context.Context { if s, ok := recoverySelection(plan, rt); ok && s.Build == build { return WithPlannedSelection(ctx, s) } return ctx }