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 FetchReply
- type FetchRequest
- type FunctionStore
- type HandlerHolder
- type Invoker
- type KV
- type Manager
- type Option
- type QueryResult
- type Ref
- type Resolver
- type SQL
- type Secrets
- 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.bindings.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 FetchReplyHeader = "X-Funcd-Fetch-Reply"
FetchReplyHeader marks a context.fetch answer that is the target's reply, so the shim tells it from a call the local API refused before it reached the target (ADR-0243 Decision 6).
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.10.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.10.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 FetchReply ¶ added in v0.14.0
FetchReply is the target's whole reply to a FetchRequest, at most maxInvokeBytes of body.
type FetchRequest ¶ added in v0.14.0
type FetchRequest struct {
Method string
// Path is percent-encoded as the caller sent it, so an escaped space, % or ? reaches the target as sent.
Path string
RawQuery string
Header http.Header
Body []byte
}
FetchRequest is a context.fetch call: the request the caller's shim sent, its path relative to the target.
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)
// Fetch sends req to target, a Function that serves HTTP, and returns its whole reply (ADR-0243 Decision 6).
Fetch(ctx context.Context, target Ref, req FetchRequest, timeout time.Duration) (FetchReply, 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.
func WithSQL ¶ added in v0.13.0
WithSQL routes /sql/{binding}/query and /execute to s (ADR-0233); without it there are no /sql routes.
func WithSecrets ¶ added in v0.15.0
WithSecrets routes GET /secret and GET /secret/{key} to s (ADR-0249); without it there are no /secret routes.
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 returns the link's target, how it is called (ADR-0243) and the link's timeout.
Resolve(ctx context.Context, caller Ref, alias string) (target Ref, protocol v1.FunctionProtocol, 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 Secrets ¶ added in v0.15.0
type Secrets interface {
List(ctx context.Context, ns v1.NamespaceName, fn v1.ObjectName) ([]string, error)
Get(ctx context.Context, ns v1.NamespaceName, fn v1.ObjectName, key string) (value string, found bool, err error)
}
Secrets is the function-facing secret port the local API routes to (ADR-0249): the keys and values of the Secrets the caller binds, checked on every read. As for KV, the caller's namespace and function come from the sandbox's fixed Ref, never from the request.
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