Documentation
¶
Overview ¶
Package runnerpool is the pure half of the runner pool contract: the closed set of refusal reasons with their sentences, the default-precedence resolver and the rule that only a person's own runners are ever candidates. It reads no store and touches no network; the storage and the admission fold call it.
Index ¶
- Constants
- func Available(l *types.RunnerLimits, inUse types.RunnerTally, online bool) types.RunnerTally
- func BarrierNotAllowedMsg(name string, asked types.ConfinementClass, allowed []types.ConfinementClass) string
- func DefaultUnavailableOrgMsg() string
- func DefaultUnavailablePersonalMsg() string
- func Effective(runType types.RunnerPoolRunType, sources ...SourceLimits) (types.EffectiveRunLimits, error)
- func FullMsg(name string, n int) string
- func Int(n int) *int
- func InvalidMsg() string
- func KindMismatchMsg(name string) string
- func MemberCap(ceiling, profileMax, untouched int) int
- func MemberMismatchMsg(runner, name string) string
- func NoEligibleMemberMsg(name string) string
- func NoOnlineRunnerMsg(name string) string
- func NoOwnRunnerMsg(name string) string
- func NotFoundMsg() string
- func OrgPoolSelfHostedMsg() string
- func OwnRunnerCandidates(principal string, members []types.RunnerPoolMember, runners []types.Runner) []uuid.UUID
- func PoolSource(p types.RunnerPool, runType types.RunnerPoolRunType) (SourceLimits, *Refusal)
- func QueueFullMsg(name string, n int) string
- func RemoteNotAvailableMsg(id string) string
- func RemoteNotConfiguredMsg(id string) string
- func RequiredMsg() string
- func RunTypeNotAllowedMsg(name string, t types.RunnerPoolRunType) string
- func RunnerAtCapacityMsg(name string) string
- func RunnerLimitsUnsetMsg(name string) string
- func Select(pool, run uuid.UUID, eligible []string) string
- func SortedUnique(ids []string) []string
- func StaleMsg(name string) string
- func UnavailableMsg(name string) string
- func UnavailableServerMsg() string
- func Usage(l *types.RunnerLimits, inUse types.RunnerTally, online, diskCapped bool, ...) types.RunnerUsage
- func WaitAtCapacityMsg(pool string, max int) string
- func WaitNoRunnerFreeMsg(pool string) string
- func WaitRunnersAtCapacityMsg(pool string) string
- type BarrierDefaultSource
- type BarrierFacts
- type BarrierOption
- type BarrierReason
- type Bound
- type Choice
- type Fit
- type Reason
- type Refusal
- type RunnerCapacity
- type SourceLimits
- func DeploymentSource(defaults types.ResourceLimits, eph types.EphemeralProvider, ...) (SourceLimits, error)
- func GovernanceSource(l types.GovernanceLimits, ...) (SourceLimits, error)
- func RunnerCapacitySource(candidates ...RunnerCapacity) (SourceLimits, error)
- func WithInteractiveLifetimeDefault(deployment SourceLimits, runType types.RunnerPoolRunType) SourceLimits
- type Wait
- type WaitReason
Constants ¶
const BarrierDetailKVMMissing = "kvm_missing"
BarrierDetailKVMMissing is the one reason detail: Vault is not offered because this deployment's Docker host has no KVM.
const InteractiveDefaultLifetimeSec = 24 * 60 * 60
InteractiveDefaultLifetimeSec is the end an untouched Interactive run gets when no source (pool, governance, deployment) names a lifetime default: 24 hours (owner ruling, #1319). An Interactive run never defaults to No end; that is an explicit choice. Idle stop still ends an abandoned run sooner.
Variables ¶
This section is empty.
Functions ¶
func Available ¶
func Available(l *types.RunnerLimits, inUse types.RunnerTally, online bool) types.RunnerTally
Available is a runner's total less what its runs hold, never below zero, and nothing at all while it is offline or declares no limits: such a runner's capacity is not counted as available.
func BarrierNotAllowedMsg ¶
func BarrierNotAllowedMsg(name string, asked types.ConfinementClass, allowed []types.ConfinementClass) string
BarrierNotAllowedMsg names the barrier asked for and the ones the pool allows.
func DefaultUnavailableOrgMsg ¶
func DefaultUnavailableOrgMsg() string
func DefaultUnavailablePersonalMsg ¶
func DefaultUnavailablePersonalMsg() string
func Effective ¶
func Effective(runType types.RunnerPoolRunType, sources ...SourceLimits) (types.EffectiveRunLimits, error)
Effective is the one function that decides a run's CPU, memory, main-drive size, lifetime and idle stop: for each field, the strictest of every source it is given. The caller passes the sources that apply to this run:
- the pool: PoolSource (a pool with no limits passes an empty one, or none);
- the person's governance ceiling: GovernanceSource (an operator has none);
- the deployment: DeploymentSource, ALWAYS, exactly once (its default sandbox size, the request contract's bounds, WARDYN_RUN_MAX_AGE, its concurrency cap);
- self-hosted pools only: RunnerCapacitySource over the run's candidates.
A value is unlimited only when no source bounds it, and the deployment source is always there to bound it: Effective refuses to compute without exactly one, and without more than one pool source, because a dropped source would silently widen every field. An untouched run gets the lowest default any source names, never above the cap. The deployment's default is its fallback sandbox size: it counts only when no other source names a default, so it never lowers a pool's own. With no default at all a run gets the cap, which is today's rule for a run's end. Each answer names the source that bound it, for the preview and for explaining a reduction. A source with a non-positive bound, an unknown source or an unknown run type is an error: the caller refuses, it never guesses.
func InvalidMsg ¶
func InvalidMsg() string
func KindMismatchMsg ¶
KindMismatchMsg: the pool has no runner of the kind the request's placement asks for.
func MemberCap ¶
MemberCap is one field of the member cap (#1949), the one rule every bound on a governed member's CPU and memory uses: max(untouched, size), where the profile's maximum is the size when the ceiling sets none and lowers it when it does. A caller with no baseline passes the deployment's default as untouched, so the cap is at least 1 whenever untouched is.
func MemberMismatchMsg ¶
MemberMismatchMsg names the runner the caller asked for: their own runner's name, or the id as they sent it. Never another person's runner's name; a runner that is not the caller's is answered exactly like an unknown one.
func NoEligibleMemberMsg ¶
func NoOnlineRunnerMsg ¶
NoOnlineRunnerMsg is the one sentence for a user pool whose own runners are all offline: the pool list's reason, the dry doors' blocker and the create refusal.
func NoOwnRunnerMsg ¶
func NotFoundMsg ¶
func NotFoundMsg() string
func OrgPoolSelfHostedMsg ¶
func OrgPoolSelfHostedMsg() string
OrgPoolSelfHostedMsg refuses a self-hosted runner offered to an organization pool.
func OwnRunnerCandidates ¶
func OwnRunnerCandidates(principal string, members []types.RunnerPoolMember, runners []types.Runner) []uuid.UUID
OwnRunnerCandidates is the only way a self-hosted pool yields execution candidates: the claimed runners of members that principal owns, in a stable order. A pool lists many people's runners, and none of the others is ever a candidate for this person's run, whatever role the person holds. Liveness, capacity and every other admission fact are checked afresh on each candidate by the caller; membership is only the starting set.
func PoolSource ¶
func PoolSource(p types.RunnerPool, runType types.RunnerPoolRunType) (SourceLimits, *Refusal)
PoolSource is the pool's side of Effective for one run type. A pool with no limits adds no bound. A run type the pool does not serve is a refusal, so a pool never answers limits for a run it would not take.
runType is the run's STORED experience (background or interactive), never one inferred from the request: an empty or unknown value (the older-client shape) never satisfies a pool's run-type list and is refused.
func QueueFullMsg ¶
QueueFullMsg: the person already has n runs waiting for the pool.
func RemoteNotAvailableMsg ¶
RemoteNotAvailableMsg refuses a person's add of a remote-hosted runner to their own user pool when its people policy does not admit them; an unknown id is answered the same, as they sent it.
func RemoteNotConfiguredMsg ¶
RemoteNotConfiguredMsg refuses an administrator's add of a runner id this server is not set up with as a remote-hosted runner, the id as they sent it.
func RunTypeNotAllowedMsg ¶
func RunTypeNotAllowedMsg(name string, t types.RunnerPoolRunType) string
func RunnerAtCapacityMsg ¶
RunnerAtCapacityMsg is the create and preflight refusal (runner_at_capacity) for a run that would start at once while every runner in the pool that could take it is at one of its limits: its Runs at once, or a total this run would pass.
func RunnerLimitsUnsetMsg ¶
RunnerLimitsUnsetMsg is the refusal (runner_limits_unset) when no runner in the pool has declared its limits, so none may take a run.
func Select ¶
Select picks the one target a run goes to from the fully eligible targets of its pool: the highest SHA-256 rendezvous score over an unambiguous (length-prefixed) encoding of the domain, the pool id, the server-allocated run id and the stable target id; a bytewise tie on the target id breaks the theoretical hash tie. No list order, label, clock or random draw takes part, so the same inputs choose the same target on every replica and on a retry, and eligible requests spread across the pool without any claim to know its free capacity. It returns "" for no targets.
func SortedUnique ¶
SortedUnique returns ids in bytewise order without repeats, the stable order every caller hands Select and shows.
func UnavailableMsg ¶
func UnavailableServerMsg ¶
func UnavailableServerMsg() string
func Usage ¶
func Usage(l *types.RunnerLimits, inUse types.RunnerTally, online, diskCapped bool, reported *types.RunnerAmounts) types.RunnerUsage
Usage is a runner's usage as every read shows it.
func WaitAtCapacityMsg ¶
WaitAtCapacityMsg is the sentence of a run waiting behind the pool's cap.
func WaitNoRunnerFreeMsg ¶
WaitNoRunnerFreeMsg is the sentence of a queued run no runner of its owner can take yet.
func WaitRunnersAtCapacityMsg ¶
WaitRunnersAtCapacityMsg is the sentence of a queued run whose runners are all at one of their limits.
Types ¶
type BarrierDefaultSource ¶
type BarrierDefaultSource string
BarrierDefaultSource says who chose the default barrier.
const ( // BarrierDefaultServer: the strongest class offered at or above the floor. BarrierDefaultServer BarrierDefaultSource = "server" // BarrierDefaultOrganisation: the organisation's default barrier, at or above the floor and offered. BarrierDefaultOrganisation BarrierDefaultSource = "organisation" )
type BarrierFacts ¶
type BarrierFacts struct {
// Limits are the pool's own; nil allows every barrier.
Limits *types.RunnerPoolLimits
// Known: some candidate's facts are known. Offered is the union of the known candidates' classes.
Known bool
Offered []types.ConfinementClass
// Floor is the governance floor. PolicyFloor and AccessFloor are a draft's (Review); zero on the list.
Floor, PolicyFloor types.ConfinementClass
AccessFloor bool
// Org is the organisation's default barrier; zero when it sets none.
Org types.ConfinementClass
// KVMMissing: Vault is not offered because the deployment's Docker host has no KVM.
KVMMissing bool
}
BarrierFacts is what one pool's barriers are judged on for one caller.
type BarrierOption ¶
type BarrierOption struct {
Class types.ConfinementClass `json:"class"`
Available bool `json:"available"`
Default bool `json:"default,omitempty"`
DefaultSource BarrierDefaultSource `json:"default_source,omitempty"`
ReasonCode BarrierReason `json:"reason_code,omitempty"`
ReasonDetail string `json:"reason_detail,omitempty"`
// Floor is the class a *_floor reason names.
Floor types.ConfinementClass `json:"floor,omitempty"`
}
BarrierOption is one barrier as a pool offers it to the caller: always CC1, CC2 and CC3, in that order.
func BarrierOptions ¶
func BarrierOptions(f BarrierFacts) []BarrierOption
BarrierOptions is the three barriers for one pool. The default is the class an untouched run gets: the organisation's when it is available, else the strongest available one (the strongest offered at or above every floor).
type BarrierReason ¶
type BarrierReason string
BarrierReason says why a barrier is not available for a run in a pool. The set is closed; runner-pool-refusals.ts repeats it as BARRIER_REASON. When several apply, the first in this order wins.
const ( // BarrierBelowFloor: below the caller's governance floor. BarrierBelowFloor BarrierReason = "below_floor" // BarrierPolicyFloor: at or above the governance floor but below this run's policy floor (Review only). BarrierPolicyFloor BarrierReason = "policy_floor" // BarrierAccessFloor: this run's access needs Vault (composer.RequiredConfinementFloor; Review only). BarrierAccessFloor BarrierReason = "access_floor" // BarrierPoolNotAllowed: the pool's own limits do not allow it (CheckBarrier). BarrierPoolNotAllowed BarrierReason = "pool_not_allowed" // BarrierUnknown: no candidate's facts are known (every own runner offline, or the executor read failed). BarrierUnknown BarrierReason = "unknown" // BarrierNotOffered: some candidate is known and none enforces it. BarrierNotOffered BarrierReason = "not_offered" )
func BarrierReasons ¶
func BarrierReasons() []BarrierReason
BarrierReasons lists the closed set in precedence order.
type Bound ¶
type Bound struct{ Max, Default *int }
Bound is one source's limit on one field. A nil Max is "this source sets no maximum"; a nil Default is "this source suggests no default". A bound that is present is positive: a source that means "nothing" leaves it nil, so a zero can never be read as a limit of nothing or as no limit.
type Choice ¶
Choice is what a request itself says: an explicit pool, and the placement when the person asked for one, which narrows the pool's runners to one kind.
type Fit ¶
type Fit int
Fit is a runner's answer for one run of a given size: the one rule placement, the store's locked admission, the promotion, the queue's verdict and every usage read share, so none of them can disagree about whether a run fits.
const ( // FitOK: the run fits now. FitOK Fit = iota // FitUnset: the runner declares no limits, so it takes no run at all. FitUnset // FitTooBig: the run asks for more than the runner's per-run limit, so it never fits there. FitTooBig // FitNoRoom: the run is within the per-run limit, but the runner is at its Runs at once or one of // its totals would be passed; it fits once runs there end. FitNoRoom )
func Fits ¶
func Fits(l *types.RunnerLimits, inUse types.RunnerTally, need types.RunnerAmounts) Fit
Fits answers whether a run reserving need fits on a runner with limits l whose runs already hold inUse.
type Reason ¶
type Reason string
Reason is a machine-readable refusal reason of pool selection. The set is closed; ui/src/app/lib/runner-pool-refusals.ts repeats it and a test pins the two together.
const ( ReasonInvalid Reason = "runner_pool_invalid" ReasonRequired Reason = "runner_pool_required" ReasonNotFound Reason = "runner_pool_not_found" ReasonStale Reason = "runner_pool_stale" ReasonNoEligibleMember Reason = "runner_pool_no_eligible_member" ReasonMemberMismatch Reason = "runner_pool_member_mismatch" // The pool's own limits refuse a request that names a pool: a run type or a // barrier the pool does not allow. A pool at its concurrent-run cap is not a // refusal: see Wait. ReasonRunTypeNotAllowed Reason = "runner_pool_run_type_not_allowed" ReasonBarrierNotAllowed Reason = "runner_pool_barrier_not_allowed" // The pool queue's two refusals (a wait is never one: see Wait). QueueFull answers a create when the // person already has the most runs waiting for the pool; Full answers a revive into a pool at its cap // or with others waiting. ReasonQueueFull Reason = "runner_pool_queue_full" ReasonFull Reason = "runner_pool_full" )
const ( AvailabilityNoOnlineRunner Reason = "runner_pool_no_online_runner" AvailabilityPlacementDenied Reason = "placement_denied" )
What a pool row's reason holds when the pool cannot take a run for the caller, beyond the refusal reasons above: GET /runner-pools and the dry doors answer 200 with them. A launch on such a pool answers the same token, except a pool whose own runners are all offline, which create answers runner_offline with NoOnlineRunnerMsg. The two placement tokens are placement.Reason's.
func AvailabilityReasons ¶
func AvailabilityReasons() []Reason
AvailabilityReasons is the closed set a pool row's reason may hold, in the order the list checks them. runner-pool-refusals.ts repeats it as POOL_AVAILABILITY_REASONS.
type Refusal ¶
type Refusal struct {
Reason Reason
// Source is whose default was refused, for ReasonDefaultUnavailable.
Source types.RunnerPoolSelection
// Name is the pool the sentence is about; empty when there is none to name.
Name string
// Scope is the scope of the pool the sentence is about.
Scope types.RunnerPoolScope
// Runner names the runner a ReasonMemberMismatch is about: the caller's own
// runner's name, or the id as the caller sent it. A runner that is not the
// caller's is never named, it is answered like an unknown one.
Runner string
// RunType, Barrier and Allowed carry what the pool-limit reasons name: the run
// type or barrier asked for and the barriers the pool allows.
RunType types.RunnerPoolRunType
Barrier types.ConfinementClass
Allowed []types.ConfinementClass
// Count is the number the queue reasons name: the waiting runs a person holds (ReasonQueueFull) or
// the pool's concurrent-run cap (ReasonFull).
Count int
}
Refusal is a pool selection that cannot proceed, and the sentence to show.
func CheckBarrier ¶
func CheckBarrier(p types.RunnerPool, barrier types.ConfinementClass) *Refusal
CheckBarrier refuses a barrier the pool does not allow. A pool with no limits allows every barrier.
func Resolve ¶
func Resolve(permitted []types.RunnerPool, c Choice, personal, org types.RunnerPoolDefaults) (types.ResolvedRunnerPool, *Refusal)
Resolve picks the pool a request runs against, from the pools the caller may use. permitted is the caller's own catalogue: the caller of Resolve has already dropped every pool the person may not use, so a pool absent from it is indistinguishable from one that does not exist.
An explicit pool wins, then the person's own default pool, then the organisation's. A default that is gone or switched off is refused, never replaced by another default or another pool: the person must choose. A placement the request asks for narrows the pool to that kind of runner, and a pool with no runner of that kind is refused.
type RunnerCapacity ¶
type RunnerCapacity struct{ CPUMillis, MemoryMiB, DiskMiB int64 }
RunnerCapacity is one runner's declared per-run limit: the most one run may reserve there.
type SourceLimits ¶
type SourceLimits struct {
Source types.LimitSource
// CPUMillis, MemoryMiB, DiskMiB, LifetimeSec and IdleSec are the fields. IdleSec
// is read for an Interactive run only.
CPUMillis, MemoryMiB, DiskMiB, LifetimeSec, IdleSec Bound
// ConcurrentRuns caps running runs; see types.ConcurrencyBound for what it counts.
ConcurrentRuns *int
// NoEnd says whether the source lets a run have no end. Nil means it does
// exactly when it sets no lifetime maximum; governance sets it, because a
// profile can offer No end and also bound a finite end.
NoEnd *bool
}
SourceLimits is what one source says about one run. Every source is folded by Effective the same way: the lowest maximum wins, per field.
func DeploymentSource ¶
func DeploymentSource(defaults types.ResourceLimits, eph types.EphemeralProvider, maxAgeSec, concurrentRuns int) (SourceLimits, error)
DeploymentSource is the deployment's side of Effective, the one every call carries: defaults is its default sandbox size (sizing.EffectiveLimits), CPU and memory are bounded by the request contract's own bounds, eph is the organisation's ephemeral-disk default and maximum (0 = unset), maxAgeSec is WARDYN_RUN_MAX_AGE (0 = none) and concurrentRuns the deployment's cap on running runs (0 = none).
func GovernanceSource ¶
func GovernanceSource(l types.GovernanceLimits, ceiling, untouched, deploymentDefault types.ResourceLimits) (SourceLimits, error)
GovernanceSource is the person's governance ceiling's side of Effective: CPU, memory, the main drive's size, how long a run may live and how many a person may run at once.
CPU and memory are the member cap (#1949): the most a governed request may ask for is max(untouched, size), where untouched is what the run already gets (the admin-authored resolved policy's size after the policy step, never derived from the request) and size is the ceiling policy's own size. A profile's maximum counts as the ceiling's size: with no ceiling size set, the maximum IS the size (a maximum above the deployment's default raises the cap to it); with both set, the lower of the two. A governed member is never left unbounded: a field where untouched is 0 (unset) starts from deploymentDefault, per field, so a ceiling that sets only memory still bounds CPU at the default. A deploymentDefault with a field below 1 is an error.
l must be the limits the run is judged under: for an extension, the limits the run captured at create, not the live profile.
The lifetime is a lease: a finite end may be at most MaxEndAheadSec ahead of now, whether or not No end is also on offer (AllowNoEnd with UserChangesLimits), and NoEnd says whether it is. With no maximum, a profile that does not offer No end still bounds a finite end, at the run-limit ceiling, exactly as an end change does; only one that offers it leaves the lease unbounded. The default is the profile's default end, else its maximum, as a new run's end is today. Governance has no idle-stop ceiling (PauseIdleAfterSec pauses, it does not stop), so IdleSec is never set.
func RunnerCapacitySource ¶
func RunnerCapacitySource(candidates ...RunnerCapacity) (SourceLimits, error)
RunnerCapacitySource is a pooled run's runner side of Effective: the per-run limit of ONE of its candidates, the largest by CPU, then memory, then disk, so the size the run is cut to is one a real runner takes whole. A cap built field by field from different runners (one's CPU, another's memory) would be a size no runner takes. Placement then keeps only the candidates whose own per-run limit holds the size the run gets, so the runner it is placed on always bounds it. It bounds CPU, memory and the main drive alike, so no resource of a pooled run is unlimited. No candidate, or one that declares no limit, is an error: a missing limit refuses the run, it is never read as zero or as unlimited.
func WithInteractiveLifetimeDefault ¶
func WithInteractiveLifetimeDefault(deployment SourceLimits, runType types.RunnerPoolRunType) SourceLimits
WithInteractiveLifetimeDefault adds the Interactive fallback to the deployment source for runType. It rides the deployment's default slot, which Effective reads only when nothing more specific names a default and never above a cap, so a pool's or a profile's own default and every maximum win over it, and it changes no source's No end permission. Any other run type is returned as given.
type Wait ¶
Wait is the answer when a new run must queue behind a pool's cap. Whoever owns the queue records Reason on the queued run and shows Sentence; this package does not queue.
func AtCapacity ¶
func AtCapacity(p types.RunnerPool, running int) *Wait
AtCapacity reports that the pool is running its cap, so a new run waits for a slot: it returns a Wait, not a Refusal, so it cannot be answered as a refusal. nil means a slot is free. running counts the pool's runs that HOLD a slot (dispatched or running), never queued ones; a queue of max waiting runs must not read the pool as full for ever. It is counted in the same transaction that inserts the new run, under the pool's row lock; a count read outside it lets two launches both pass. The queue itself is not here.
func (*Wait) Reason ¶
func (w *Wait) Reason() WaitReason
type WaitReason ¶
type WaitReason string
WaitReason says why a run is waiting for a slot rather than starting. It is a run's state, never a refusal: it has no HTTP status, is not in Reasons, and a request is never answered with it.
const WaitAtCapacity WaitReason = "runner_pool_at_capacity"
WaitAtCapacity: the pool is running its concurrent-run cap, so the run waits for one of its slots.
const WaitNoRunnerFree WaitReason = "runner_pool_no_runner_free"
WaitNoRunnerFree: the pool has room, but none of the person's runners in it can take the run right now (each is offline or cannot hold it), so the run keeps its place. It is a wait for a run that was already queued: a pool whose candidates are all offline at create is refused, never queued.
const WaitRunnersAtCapacity WaitReason = "runner_pool_runners_at_capacity"
WaitRunnersAtCapacity: the pool has room and its runners are online, but each is at one of its limits (its Runs at once, or a total this run would pass), so the run keeps its place until one has room.
func (WaitReason) Valid ¶
func (w WaitReason) Valid() bool
Valid reports whether w is a wait reason.