admission

package
v0.2.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 9, 2026 License: MPL-2.0 Imports: 12 Imported by: 0

Documentation

Overview

Package admission holds the chassis's pre-pipeline admission gates: a per-tenant runtime-state check (a suspended or disabled tenant is denied before its stack runs), per-tenant node-local rate-limit and concurrency caps, and a process-level drain flag (signal-driven; bleeds the node out of a load balancer). All are open-core mechanism and unopinionated: a tenant with no state row (or 0 limits) is admitted, and drain is off until signaled, so a single-node chassis behaves exactly as it did before this package existed. The commercial policy that decides WHO is suspended/limited lives outside the chassis and arrives as tenant_runtime_state rows (an operator edit or a fleet-sync entitlement.updated apply).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Denied

func Denied(resp string) (status int, reason string, ok bool)

Denied reports whether resp carries an admission-denial marker, and if so the status + reason an outlet should render. Each personality calls this in its response path to map the neutral denial to its protocol.

func DrainResponse

func DrainResponse(resp string) string

DrainResponse stamps the transport-neutral admission marker for a 503 "draining" denial. Each outlet renders it in its own protocol (web → 503 + Retry-After; lmtp → 451 so mail retries; tcp → close). Used by the server bus loop while the node is draining.

func IsDraining

func IsDraining() bool

IsDraining reports whether the node is draining. Read by the health endpoint (returns 503 so the load balancer removes the node) and by the server bus loop (new requests get a 503 terminal response while in-flight requests are allowed to finish).

func MarkDenied

func MarkDenied(resp string, d Decision, tenant string) string

MarkDenied stamps the transport-neutral admission-denial marker onto resp: _txc.admission.{denied,status,reason}, plus _txc.tenant (when non-empty) so the usage convergence point attributes the denial to the right tenant. It is deliberately transport-agnostic — each personality's outlet reads this marker (via Denied) and renders the rejection in its own protocol: web → HTTP status, lmtp → SMTP code, tcp → line+close, cron → log. The shared gate must not know about transports.

func NewSQLiteProvider

func NewSQLiteProvider(logger *zap.Logger) *sqliteProvider

NewSQLiteProvider returns a provider seeded with empty state, so reads before the first Rebuild admit everyone.

func SetDraining

func SetDraining(on bool)

SetDraining turns the node's drain state on or off. Idempotent. Wired to SIGUSR1 (on) / SIGUSR2 (off) by the app signal loop.

func WithLease

func WithLease(ctx context.Context, l *Lease) context.Context

WithLease attaches a lease to ctx for the processor gate to read.

Types

type Decision

type Decision struct {
	Admit  bool
	Status int           // HTTP status when !Admit (402 | 403 | 429 | ...)
	Reason string        // machine token for the response header
	Retry  time.Duration // suggested Retry-After (0 => none); rendered by the outlet
}

Decision is the result of an admission check.

type Lease

type Lease struct {
	// contains filtered or unexported fields
}

Lease is a per-request handle that carries deferred releases — currently the concurrency-slot decrement registered by AcquireConcurrency. It is created in the server bus-loop goroutine that owns a request's lifetime; that goroutine's `defer lease.Release()` runs the registered releases exactly once when the request returns (pipeline done OR continuation suspend). Nil-safe so the bus loop can defer unconditionally, and Release is idempotent (sync.Once) so a stray double-call can't underflow a counter.

Only AcquireConcurrency calls onRelease; only the bus-loop defer calls Release. Op / fan-out goroutines carry the lease via context but must never Release it.

func LeaseFromContext

func LeaseFromContext(ctx context.Context) *Lease

LeaseFromContext returns the request's lease, or nil if none is set (tests, non-server callers). A nil lease is safe to pass to AcquireConcurrency.

func NewLease

func NewLease() *Lease

NewLease returns a fresh, empty lease.

func (*Lease) Release

func (l *Lease) Release()

Release runs every registered release function exactly once. Nil-safe and idempotent — the bus loop defers it unconditionally, even for requests that never acquired a slot (cap==0, denied earlier, or never re-tenanted).

type NopProvider

type NopProvider struct{}

NopProvider admits everyone. The nil/test/disabled default so call sites can stay unconditional.

func (NopProvider) AcquireConcurrency

func (NopProvider) AcquireConcurrency(string, *Lease) bool

func (NopProvider) AllowRate

func (NopProvider) AllowRate(string) (bool, time.Duration)

func (NopProvider) Decide

func (NopProvider) Decide(string) Decision

type Provider

type Provider interface {
	// Decide returns the suspend/enable decision for a tenant slug. An
	// unknown tenant (no row) and the system tenant are always admitted.
	Decide(tenant string) Decision
	// AllowRate reports whether the tenant is under its node-local rate
	// limit; retry is a suggested Retry-After when denied. No limit
	// configured (rps==0) / unknown tenant => allow.
	AllowRate(tenant string) (allow bool, retry time.Duration)
	// AcquireConcurrency takes a node-local concurrency slot for the
	// tenant, registering its release on lease. No limit (cap==0) / unknown
	// tenant => admit without taking a slot. Returns false when at capacity.
	AcquireConcurrency(tenant string, lease *Lease) bool
}

Provider answers the per-tenant admission questions on the request hot path. Implementations must be lock-light and non-blocking — no DB or network I/O per request.

type TenantRuntimeState

type TenantRuntimeState struct {
	Enabled    bool
	Suspended  bool
	DenyStatus int    // HTTP status to render when denied; 0 => default 403
	DenyReason string // short machine token surfaced as a response header

	// Phase 2 — node-local operational limits. 0 => unlimited.
	RateLimitRPS     float64 // resolved per-second rate (may be fractional, e.g. 50/min => 0.833)
	RateBurst        int     // token-bucket size (max burst); defaults to ceil(2*rps) upstream
	ConcurrencyLimit int     // max simultaneous in-flight requests
}

TenantRuntimeState is the operational admission state for one tenant, mirrored from a tenant_runtime_state row.

func (TenantRuntimeState) Admitted

func (s TenantRuntimeState) Admitted() bool

Admitted reports whether a tenant in this state may enter its stack. Either knob denies: a disabled OR a suspended tenant is refused. (Rate and concurrency are enforced separately — see AllowRate/AcquireConcurrency.)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL