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 ¶
- func Denied(resp string) (status int, reason string, ok bool)
- func DrainResponse(resp string) string
- func IsDraining() bool
- func MarkDenied(resp string, d Decision, tenant string) string
- func NewSQLiteProvider(logger *zap.Logger) *sqliteProvider
- func SetDraining(on bool)
- func WithLease(ctx context.Context, l *Lease) context.Context
- type Decision
- type Lease
- type NopProvider
- type Provider
- type TenantRuntimeState
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Denied ¶
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 ¶
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 ¶
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 ¶
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.
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 ¶
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.
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) 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.)