Changing a running instance's config without a restart: the part of a config reload that core owns.
What a reload is (re-reading a file, resolving secrets, deciding which keys
changed and whether they may) belongs to whoever loads the config; for the
image that is AnkusaServer.Reload. This module is the seam between that and
the instance:
register/2/run/2— the loader registers a reload function for an instance, andPOST /v1/config/reload(Ankusa.Admin.Router) calls it. An instance nobody registered one for answers{:error, :unsupported}.apply_config/3— what the loader calls once it holds a validated%Ankusa.Config{}: the reloadable set is taken from it, everything else stays as it is, and the result is applied atomically.
The reloadable set
| Field | How it takes effect |
|---|---|
rate_limits (all but scope) | read on every request |
dispatch.concurrency, dispatch.sink_concurrency, dispatch.tenants | Ankusa.Dispatch.Pipeline.reconfigure/1: the next scheduling step |
metrics.detail | the instance's metric definitions are rebuilt (Ankusa.Metrics restarts: its counters start from zero) |
admin.gauge_interval_ms | the gauge poller restarts with the new period |
routes.seed | the difference is applied through Ankusa.Routes: a changed or new seed route is replaced, a route that left the seed is deleted; routes created through the API are left alone |
static sources | Ankusa.SourceStore.Static.with_sources/2: the next fetch/2 sees them |
Every other field of the running config is kept, whatever next says; the
loader refuses a reload that changes one ({:error, {:not_reloadable, keys}}).
Atomicity and audit
The merged config is validated with the checks the instance boots under
before anything is touched, so a reload that would not boot is refused with
nothing applied. One audit row (config.reload, detail.keys) brackets the
application; its result is ok or failed. A reload the trail cannot record
is not made (:audit_backlog, :store_unavailable). The seed routes are
applied one by one through Ankusa.Routes (each its own audited change), so a
seed route that conflicts with a route made through the API fails the reload
part-way: the routes already applied stay, the config is not replaced, and the
same reload can be run again.
Summary
Types
What a reload did: the dotted keys that changed, the source ids whose spec changed, secrets that kept their old value.
Functions
Apply the reloadable fields of next to the running instance.
Register fun as instance's reload: it takes the Ankusa.Audit.Context of
whoever asked and returns {:ok, summary} or {:error, error}.
Run instance's registered reload on behalf of context.
Types
Functions
@spec apply_config(atom(), Ankusa.Config.t(), keyword()) :: {:ok, %{sources: [String.t()]}} | {:error, error()}
Apply the reloadable fields of next to the running instance.
Options: :audit (the Ankusa.Audit.Context, default Context.system/0) and
:keys (the dotted config keys the loader found changed, recorded in the audit
row). Returns {:ok, %{sources: ids}} with the source ids whose spec changed,
or {:error, {:invalid_config, message}} and nothing applied when the merged
config would not boot (or a seed route is refused part-way, see above).
@spec register(atom(), (Ankusa.Audit.Context.t() -> {:ok, summary()} | {:error, error()})) :: :ok
Register fun as instance's reload: it takes the Ankusa.Audit.Context of
whoever asked and returns {:ok, summary} or {:error, error}.
@spec run(atom(), Ankusa.Audit.Context.t()) :: {:ok, summary()} | {:error, error()}
Run instance's registered reload on behalf of context.