Documentation
¶
Overview ¶
Package local is the per-sandbox worker-node local API (ADR-0064): an HTTP-over-UDS surface the runtime shim calls for the synchronous fn-to-fn invoke verb (context.invoke). One listener per sandbox; the caller Ref is fixed at provisioning, so identity is connection-scoped — a request body can never name a different caller. A pool worker's listener serves the pool's members, each request as the member it names in the X-Funcd-Member header. No published SDK — reached only through the built-in shim.
It nests under the blueprint's internal/workernode node-agent component.
Index ¶
- Constants
- Variables
- func CheckDir(dir string) error
- func NewHandler(caller Ref, res Resolver, inv Invoker, authz auth.Authorizer, kv KV, blob Blob, ...) http.Handler
- func Serve(ctx context.Context, path string, h http.Handler, logger *slog.Logger) error
- type Blob
- type DependencyChecker
- type DependencyReport
- type ExecResult
- type FunctionStore
- type HandlerHolder
- type Invoker
- type KV
- type Manager
- type Option
- type QueryResult
- type Ref
- type Resolver
- type SQL
- type UpstreamError
Constants ¶
const ( DependencyKV = "kv" DependencyBlob = "blob" DependencyLink = "link" DependencySocket = "socket" ReasonForbidden = "Forbidden" ReasonNotFound = "NotFound" ReasonNotReady = "NotReady" ReasonStorageUnreachable = "StorageUnreachable" ReasonTimeout = "Timeout" ReasonUnreachable = "Unreachable" )
The kinds and reasons of a DependencyReport (ADR-0215 Contracts).
const ( DependencySQL = "sql" ReasonEngineNotReady = "EngineNotReady" )
DependencySQL is the dependency-report kind of a spec.sql binding, and ReasonEngineNotReady its reason while the database's engine is not Ready (ADR-0233 Decision 8).
const DependencyCheckBudget = 50 * time.Millisecond
DependencyCheckBudget bounds one dependency check: half the reconciler's 100 ms readiness probe, and a pool host's bound for all its members (ADR-0215 Decision 4).
const (
MaxSQLStatements = 500
)
The SQL request bounds (ADR-0233 Decision 3).
const MemberHeader = "X-Funcd-Member"
MemberHeader names, on every request to a pool worker's local API, the pool member it is made for.
const NestedCapOp = "workernode.local.nested-cap"
NestedCapOp is the fault Op of a nested call refused by the per-target in-flight cap (ADR-0147).
Variables ¶
var ErrEngineNotReady = errors.New("the database's engine is not ready")
ErrEngineNotReady marks a SQL call or check whose database has no Ready engine.
Functions ¶
func CheckDir ¶ added in v0.2.1
CheckDir reports an error when dir is too long to hold the local API sockets. Every socket name has the same length, so a dir that passes holds a socket for any function.
func NewHandler ¶
func NewHandler(caller Ref, res Resolver, inv Invoker, authz auth.Authorizer, kv KV, blob Blob, deps DependencyChecker, logger *slog.Logger, opts ...Option) http.Handler
NewHandler builds the per-sandbox local API handler: POST /invoke/{alias} (ADR-0064/0075) plus, when kv is non-nil, the KV verbs GET/PUT/DELETE /kv/{binding}/{key} + list (ADR-0069). caller is the fixed sandbox identity (connection-scoped) — the handler never reads a caller from the request. authz is the PDP (ADR-0075): after the Resolver maps the alias→target (naming; unknown alias ⇒ Forbidden), the handler asks it `link::invoke` on the target Function — a deny (a forbid Policy revoking a declared link) ⇒ Forbidden, before forwarding. The caller principal is built from the fixed Ref, never the request. Every invoke is logged through logger (the broker is the audit point): an allowed call at Info, a denial / upstream error at Warn. A nil logger defaults to slog.Default(). deps non-nil registers GET /health/dependencies (ADR-0215 Decision 3); nil registers no route, so a shim reads 404 as a pass.
Types ¶
type Blob ¶
type Blob interface {
Get(ctx context.Context, ns v1.NamespaceName, fn v1.ObjectName, binding, key string) ([]byte, bool, error)
Put(ctx context.Context, ns v1.NamespaceName, fn v1.ObjectName, binding, key string, data []byte) error
Delete(ctx context.Context, ns v1.NamespaceName, fn v1.ObjectName, binding, key string) error
List(ctx context.Context, ns v1.NamespaceName, fn v1.ObjectName, binding, prefix string) ([]string, error)
SignedURL(ctx context.Context, ns v1.NamespaceName, fn v1.ObjectName, binding, key string, opts blob.SignOptions) (string, error)
}
Blob is the function-facing blob port the worker-node local API routes to — the services/blob.Facade satisfies it. The caller's namespace + function are supplied by the handler (connection-scoped from the sandbox's fixed Ref), NEVER read from the request: a function can only reach its own namespace's blob, and its binding resolution is keyed by its trustworthy fixed function identity (ADR-0127/0073).
type DependencyChecker ¶ added in v0.11.0
type DependencyChecker interface {
Check(ctx context.Context, caller Ref) *DependencyReport
}
DependencyChecker checks the bindings of caller (ADR-0215 Decision 3): nil when all pass, else the first failure.
type DependencyReport ¶ added in v0.11.0
type DependencyReport struct {
Kind string `json:"kind"` // kv | blob | sql | link | socket
Binding string `json:"binding"` // the alias
Reason string `json:"reason"` // Forbidden | NotFound | NotReady | StorageUnreachable | Timeout | Unreachable | EngineNotReady
Message string `json:"message"`
}
DependencyReport is the first binding of a sandbox's caller that fails its dependency check (ADR-0215 Decision 3), the body of a 503 from GET /health/dependencies and, passed through by the shim, from its /health/readiness.
type ExecResult ¶ added in v0.13.0
type ExecResult struct {
RowsAffected int64 `json:"rowsAffected"`
LastInsertID int64 `json:"lastInsertId"`
Columns []string `json:"columns,omitempty"`
Values [][]json.RawMessage `json:"values,omitempty"`
}
ExecResult is one execute statement's outcome; a RETURNING statement also carries its rows.
type FunctionStore ¶
type FunctionStore interface {
Get(ctx context.Context, gvk v1.GroupVersionKind, ns v1.NamespaceName, name v1.ObjectName) (v1.Object, error)
}
FunctionStore is the read-only store access the resolver needs (a subset of store.Store).
type HandlerHolder ¶
type HandlerHolder struct {
// contains filtered or unexported fields
}
HandlerHolder is a settable http.Handler indirection. The local API Invoker is built from it BEFORE the data-plane handler exists (the reconciler is constructed before the data plane, since the data plane wraps the reconciler's activator/Endpoints); Set is called once the data plane is built. Until then it answers 503.
func (*HandlerHolder) ServeHTTP ¶
func (hh *HandlerHolder) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP delegates to the installed handler, or 503 until one is set.
func (*HandlerHolder) Set ¶
func (hh *HandlerHolder) Set(h http.Handler)
Set installs the real target handler.
type Invoker ¶
type Invoker interface {
Invoke(ctx context.Context, target Ref, input []byte, timeout time.Duration) ([]byte, error)
}
Invoker performs the synchronous call to target and returns its output bytes. On a target non-2xx (e.g. the shim's 422 input / 500 output contract failures, ADR-0058) it returns an *UpstreamError carrying that exact status+body to propagate verbatim; on a transport/cold-wake-timeout failure it returns a fault error (e.g. fault.Unavailable → 503).
func NewInvoker ¶
NewInvoker returns an Invoker that forwards through the given in-process data-plane handler.
func NewNestedCapInvoker ¶ added in v0.4.0
NewNestedCapInvoker wraps inner with the per-target nested-call in-flight cap (ADR-0147): a call is counted from before the data-plane hand-off until it returns (a cold wake included). With maxPerTarget (≥ 1) calls to the target in flight, the next is refused at once without calling inner: fault.ResourceExhausted (429, no Retry-After: a slot frees on return, not on a clock), Op NestedCapOp, and the Int64Counter "funcd.invoke.nested.refused" +1 with the target's namespace and function.
type KV ¶
type KV interface {
Get(ctx context.Context, ns v1.NamespaceName, fn v1.ObjectName, binding, key string) ([]byte, bool, error)
Put(ctx context.Context, ns v1.NamespaceName, fn v1.ObjectName, binding, key string, value []byte) error
Delete(ctx context.Context, ns v1.NamespaceName, fn v1.ObjectName, binding, key string) error
List(ctx context.Context, ns v1.NamespaceName, fn v1.ObjectName, binding, prefix string) ([]string, error)
}
KV is the function-facing KV port the worker-node local API routes to — the services/kv.Facade satisfies it. The caller's namespace + function are supplied by the handler (connection-scoped from the sandbox's fixed Ref), NEVER read from the request: a function can only reach its own namespace's KV, and its binding resolution is keyed by its trustworthy fixed function identity (ADR-0073).
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager provisions one per-function worker-node local API listener (ADR-0064), lazily and idempotently: SocketFor(ns, name) ensures a UDS listener for that caller is serving and returns its path — the reconciler sets it as FUNCD_INVOKE_SOCKET so the caller's shim can dial context.invoke. The caller Ref is fixed per listener (connection-scoped identity); a pool worker's listener (PoolSocketFor) serves each request as the pool member it names in MemberHeader. Close() stops every listener. This is the same-node (process/containerd) provisioning; the V2 multi-node lattice is a transport swap behind the Invoker.
func NewManager ¶
func NewManager(dir string, store FunctionStore, invoker Invoker, authz auth.Authorizer, kv KV, blob Blob, deps DependencyChecker, logger *slog.Logger, opts ...Option) *Manager
NewManager builds a Manager serving sockets under dir. invoker is the (possibly late-bound) data-plane forwarder; store backs link resolution; authz (nil-able) is the invoke PDP (ADR-0075) the per-sandbox handler asks link::invoke; kv (nil-able) is the function-facing KV port (ADR-0069) the per-sandbox handler routes /kv/… to; blob (nil-able) is the function-facing blob port (ADR-0127) the handler routes /blob/… to; deps (nil-able) answers GET /health/dependencies (ADR-0215).
func (*Manager) Close ¶
func (m *Manager) Close()
Close stops every serving listener and returns once each is closed. SocketFor fails after Close.
func (*Manager) PoolSocketFor ¶ added in v0.5.0
func (m *Manager) PoolSocketFor(ns v1.NamespaceName, pool v1.ObjectName, members []v1.ObjectName) (string, error)
PoolSocketFor ensures the local API of the pool worker named pool is serving and returns its socket path, which every member's process shares. A request naming one of members in MemberHeader is served as that member's Ref, as its own SocketFor listener would serve it (invoke, KV, blob); any other request, one with no MemberHeader included, is refused 403 before any port is called. A repeated call keeps the path and replaces the member set, as members join and leave the pool. Remove(ns, pool) stops it.
func (*Manager) Remove ¶
func (m *Manager) Remove(ns v1.NamespaceName, name v1.ObjectName)
Remove stops + deletes the local API listener for (ns, name), a Function's or a pool worker's, if any — called when the Function or the pool is deleted, so the socket lifecycle tracks the resource (controller-driven, not leaked). It returns once the listener is closed: closing a Unix listener unlinks its path, which a Function re-created under the same name binds again (issue #491). It holds mu until then, so a concurrent SocketFor binds after it.
func (*Manager) SocketFor ¶
func (m *Manager) SocketFor(ns v1.NamespaceName, name v1.ObjectName) (string, error)
SocketFor ensures the local API for (ns, name) is serving and returns its socket path. Idempotent.
type Option ¶ added in v0.13.0
type Option func(*options)
Option adds an optional port to the local API.
type QueryResult ¶ added in v0.13.0
type QueryResult struct {
Columns []string `json:"columns"`
Values [][]json.RawMessage `json:"values"`
}
QueryResult is one query statement's rows in rqlite's array format, which keeps duplicate column names.
type Ref ¶
type Ref struct {
Namespace v1.NamespaceName
Function v1.ObjectName
}
Ref identifies a function replica's caller identity (namespace + function).
type Resolver ¶
type Resolver interface {
Resolve(ctx context.Context, caller Ref, alias string) (target Ref, timeout time.Duration, err error)
}
Resolver maps (caller, alias) → the link target, applying the link-as-grant rule (default-deny): fault.Forbidden when the caller declares no such link. Same-namespace only (V1.1).
func NewResolver ¶
func NewResolver(s FunctionStore) Resolver
NewResolver returns the store-backed link Resolver.
type SQL ¶ added in v0.13.0
type SQL interface {
Query(ctx context.Context, ns v1.NamespaceName, fn v1.ObjectName, binding string, stmts []json.RawMessage) ([]QueryResult, error)
Execute(ctx context.Context, ns v1.NamespaceName, fn v1.ObjectName, binding string, stmts []json.RawMessage) ([]ExecResult, error)
}
SQL is the function-facing SQL port the local API routes to; the services/sql.Facade satisfies it. As for KV, the caller's namespace and function come from the sandbox's fixed Ref, never from the request (ADR-0233).
type UpstreamError ¶
UpstreamError carries a target's non-2xx response so the local API propagates it verbatim — the daemon does NOT re-validate the contract (the target's shim already did, ADR-0064).
func (*UpstreamError) Error ¶
func (e *UpstreamError) Error() string