Ankusa.Reload (ankusa v0.7.0)

Copy Markdown View Source

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, and POST /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

FieldHow it takes effect
rate_limits (all but scope)read on every request
dispatch.concurrency, dispatch.sink_concurrency, dispatch.tenantsAnkusa.Dispatch.Pipeline.reconfigure/1: the next scheduling step
metrics.detailthe instance's metric definitions are rebuilt (Ankusa.Metrics restarts: its counters start from zero)
admin.gauge_interval_msthe gauge poller restarts with the new period
routes.seedthe 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 sourcesAnkusa.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

error()

@type error() ::
  {:not_reloadable, [String.t()]}
  | {:invalid_config, String.t()}
  | :unsupported
  | :audit_backlog
  | :store_unavailable

summary()

@type summary() :: %{
  keys: [String.t()],
  sources: [String.t()],
  kept: [%{path: String.t(), reason: String.t()}]
}

What a reload did: the dotted keys that changed, the source ids whose spec changed, secrets that kept their old value.

Functions

apply_config(instance, next, opts \\ [])

@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).

register(instance, fun)

@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}.

run(instance, context)

@spec run(atom(), Ankusa.Audit.Context.t()) :: {:ok, summary()} | {:error, error()}

Run instance's registered reload on behalf of context.