Auto-provision the per-domain PDS instance in the domain saga

closed
#63547c4 opened by agent Sep 20

Problem

pdsRegistryProvisioner.EnsureActive (router.go:454-474) only looks up an existing ACTIVE ZDS registry record and fails loudly if absent (“onboard the ZDS instance before creating the domain”). The saga therefore cannot create a domain end-to-end without an operator first standing up the per-domain PDS.

Deliverable

A saga provisioning adapter that calls pdslifecycle.Supervisor.Activate (internal/pdslifecycle/supervisor.go:245) as the saga’s PDS step: allocate slug/port, render the instance env, start the zds@ unit, and reload the Caddy maps. It must be idempotent on retry (reuse an already-active record) and keep the existing rollback posture: Activate rolls itself back on its own failure, and the saga deliberately does not auto-Retire a healthy instance on a later-step failure (that would burn slug/port tombstones).

Acceptance

  • domain.create provisions the PDS instance with no operator pre-step.
  • A crash/retry between PDS activation and the DB insert reuses the instance and converges.
  • Tests cover: activate-on-create, reuse-on-retry, activate failure compensation, and the “later step fails → instance left healthy” rule.

2 Comments

agent 6c31534 Sep 20

Auto-provision the per-domain PDS instance in the domain saga

Epic 516fcde subtask. Current-architecture (ADR-0009): one ZDS PDS per hosted domain, owned by the saga.

Problem

pdsRegistryProvisioner (router.go) only looks up an existing ACTIVE ZDS registry record and fails if absent (“onboard the ZDS instance before creating the domain”). So domain.create cannot complete end-to-end; an operator must pre-create the per-domain PDS. pdslifecycle.Supervisor.Activate (supervisor.go:245) already implements the full onboarding (allocate slug+port → render env → enable/start → Caddy maps → health gate → active, self-rolling-back on failure). It just isn’t wired to the saga.

Design

  • Origin naming: add config pds.origin_suffix (env SOVRN_PDS_ORIGIN_SUFFIX, toml [pds] origin_suffix). A cell sets e.g. .pds1.eu.sovrn.at; the per-domain origin is pdslifecycle.SlugForDomain(domain) + origin_suffix. Empty suffix keeps the existing lookup-only behavior (dev/test; no silent activation).
  • Adapter: new pdslifecycle.ActivationProvisioner{Supervisor, Secrets, OriginSuffix} implementing the saga’s PDS seam:
    • an ACTIVE record for the domain → return (Origin, Port) (no mutation);
    • absent or retiring → Supervisor.Activate with Origin/ServerDID/ HandleDomains/Secrets + the statics already on Supervisor.Config; return the active record;
    • empty OriginSuffix → the old lookup-only error, with a message naming the config key (so prod misconfig is obvious).
    • Idempotent: reuses an active row; Registry.Allocate reclaims a retiring row (same slug/port).
  • Interface rename: appview PDSProvisioner.EnsureActive → EnsureProvisioned(ctx, domain) (origin, port, err); update the saga call and fakePDS.
  • Shared Supervisor: build one *pdslifecycle.Supervisor in sagaWiring and reuse it for the orphan sweep (single registry handle + one mutex), so concurrent Activate/Retire cannot interleave.
  • Rollback posture unchanged: Activate rolls itself back on its own failure; the saga deliberately never auto-Retires a healthy instance.

Tasks

  1. Config pds.origin_suffix (+ bindEnv, toml, Ansible var, test).
  2. pdslifecycle.OriginForDomain(domain, suffix) helper (+ test).
  3. pdslifecycle.ActivationProvisioner (+ tests with FakeRunner/registry).
  4. Rename the appview interface + saga call + fakePDS.
  5. Wire in sagaWiring: shared Supervisor + ActivationProvisioner; reuse in orphanSweepDeps.
  6. Ansible: sovrn_pds_origin_suffix (derived default per cell) rendered into [pds] origin_suffix.
  7. Full suite + test-deploy green.

Acceptance

  • With pds.origin_suffix set and supervision on, domain.create provisions the PDS instance with no operator step; retries converge on the same slug/port; an Activate failure surfaces the original error and leaves no active record.
  • Empty suffix preserves today’s lookup-only semantics exactly.
agent 6b3d524 Sep 20

Implemented and verified (commit 1535d509). New pdslifecycle.ActivationProvisioner{Supervisor, Secrets, OriginSuffix} satisfies the saga’s EnsureProvisioned seam: active row reused; absent/retiring row activated via Supervisor.Activate (allocate slug+port -> env -> start -> Caddy maps -> health gate -> active, self-rolling-back); empty pds.origin_suffix preserves the old lookup-only behavior with an actionable error. Supervisor.EnsureActive/FindActive make check-then-act atomic under the supervisor mutex, and one shared Supervisor is now used by both the saga and the orphan sweep. Config pds.origin_suffix wired (config.go, bindEnv, sovrn.toml, group_vars default empty, mx99 set to .pds1.eu.sovrn.at). Review fixes applied: atomic ensure + concurrency test (-race), OriginForDomain leading-dot validation, retiring-reclaim retry test, detached rollback context, ServerDIDForOrigin/normalized domain. go test ./…, go vet, test-deploy (23), and gofmt all green. Ready for your check; not closing. Follow-up noted in the epic: concurrent same-domain creates now converge via EnsureActive.