OpenKeel: the sovereign French Cloud solution of the inherent group, with a European reach Discover inherent

Reverse-spec · Services

designate — Summary

Reverse-specification of designate — branch stable/2026.2

  • Repository: https://opendev.org/openstack/designate — specified branch: stable/2026.2 @ e3dd774db145 (23.0.0 + 17 commits) — analysed on: 2026-10-29
  • Scope: the whole service: designate-api (v2 REST API, no microversions, and the admin API), designate-central, designate-worker and designate-producer with their periodic tasks, designate-mdns (AXFR/IXFR, NOTIFY over DNS), designate-sink (Nova and Neutron handlers), the pool and backend model (the pools YAML file, sample etc/designate/pools.yaml.sample, PowerDNS, BIND9, Infoblox… drivers). Change history of the branch (automatic range, no target). Rewrite pack for openkeel-designate on ExternalDNS, with the client expectations of Neutron's DNS integration, python-designateclient and its OSC plugin, Heat's OS::Designate::* resources and Skyline.

Overview

Designate is OpenStack's DNS-as-a-Service. Projects create zones and recordsets through a REST API; Designate stores them in its database and publishes them on the DNS servers of a pool. The DNS servers are secondaries: a driver per pool target only creates or deletes an empty slave zone on the server (PowerDNS API, rndc for BIND, Infoblox WAPI…) and sends a DNS NOTIFY; the server then pulls the zone by AXFR from designate-mdns, which renders it from the database. designate-central holds all business rules and policies and is called over RabbitMQ by every other process. designate-worker pushes zone changes to the targets and polls the nameservers' SOA serial until the change is live, then reports the zone ACTIVE or ERROR. designate-producer runs sharded periodic tasks: serial increments (batched every 5 s), retries of stuck zones, purge of deleted zones, refresh of secondary zones, heartbeats housekeeping. designate-sink turns Nova and Neutron notifications into A/AAAA records. Dependencies: MariaDB/MySQL, RabbitMQ, Keystone, optionally a tooz backend (locks, producer sharding) and Neutron (floating IP PTR records).

Architecture

flowchart LR
  client[OSC / Neutron / Heat / Skyline] -->|HTTPS REST /v2, /admin| api[designate-api]
  api -->|keystonemiddleware| ks[Keystone]
  api -->|AMQP call topic central| central[designate-central]
  central -->|SQL| db[(MariaDB)]
  central -->|REST floating IPs| neutron[Neutron]
  central -->|AMQP cast topic worker| worker[designate-worker]
  central -->|tooz locks| tooz[(tooz)]
  producer[designate-producer] -->|AMQP| central
  producer -->|AMQP cast| worker
  producer -->|tooz group| tooz
  worker -->|driver API: REST / rndc / WAPI| dns[(DNS servers of the pool)]
  worker -->|DNS NOTIFY, SOA polling| dns
  dns -->|DNS AXFR :5354| mdns[designate-mdns]
  mdns -->|SQL read| db
  worker -->|AMQP cast update_status| central
  notif{{Nova / Neutron notifications}} -->|AMQP| sink[designate-sink]
  sink -->|AMQP call| central

Processes

ProcessEntry pointRoleListens to / consumes
designate-apidesignate.cmd.api:main (pyproject.toml:43), WSGI designate/wsgi/api.pyREST API (Pecan /v2, /admin; Flask /), no business logicHTTP :9001 (designate/conf/api.py:36)
designate-centraldesignate.cmd.central:main (pyproject.toml:44)business rules, policies, database writes, notificationsRPC topic central (designate/central/service.py:57)
designate-workerdesignate.cmd.worker:main (pyproject.toml:48)push zones to pool targets, NOTIFY, SOA polling, AXFR of secondary zones, exportsRPC topic worker (designate/worker/service.py:45)
designate-producerdesignate.cmd.producer:main (pyproject.toml:49)periodic tasks over 4096 zone shardstimers; RPC topic producer (no methods) (designate/producer/service.py:34)
designate-mdnsdesignate.cmd.mdns:main (pyproject.toml:46)authoritative "mini DNS" serving AXFR to the pool servers, NOTIFY receiverDNS UDP/TCP :5354 (designate/mdns/service.py:33)
designate-sinkdesignate.cmd.sink:main (pyproject.toml:47)Nova/Neutron notification handlersAMQP notifications (designate/sink/service.py:33)
designate-managedesignate.cmd.manage:main (pyproject.toml:45)database sync, pool update from the pools YAML file, TLD importCLI
designate-statusdesignate.cmd.status:main (pyproject.toml:50)upgrade checksCLI

External dependencies

SystemProtocolLibraryConfigurationUsage
MariaDB / MySQL (PostgreSQL possible)SQLSQLAlchemy Core 2.0 + oslo.db, Alembic[storage:sqlalchemy]/connectionall state; 22 tables
RabbitMQAMQPoslo.messaging[DEFAULT]/transport_urlRPC (central, worker), notifications dns.*, sink input
KeystoneHTTPS RESTkeystonemiddleware, keystoneauth1[keystone_authtoken], [keystone]tokens; project id checks for shares and quotas
NeutronHTTPS RESTopenstacksdk[network_api:neutron] or the catalogfloating IPs for reverse DNS
tooz backend (Redis, etcd, ZooKeeper…)backend-specifictooz[coordination]/backend_urlper-zone locks, producer group and shards
DNS servers of the poolsDNS (UDP/TCP 53), driver APIs (PowerDNS HTTP, rndc, Infoblox WAPI, NS1, Akamai, DynECT, NSD)dnspython 2.8, requests, infoblox-clientthe pools YAML file (designate-manage pool update)zone publication

Key figures

  • 78 REST operations: 70 on /v2 (including the two /v2/service_status alias routes and the deliberate 405 of GET …/tasks/abandon), 8 on /admin when enabled, plus GET /; 68 documented in the api-ref (REST API).
  • ≈ 75 RPC methods on central (3 casts), 7 on worker (6 casts) (interactions).
  • Non-HTTP triggers: 8 producer periodic tasks, 2 RPC servers, 1 DNS server (QUERY/AXFR/IXFR/NOTIFY), 1 notification listener with 2 handlers, heartbeats (async).
  • 106 exception classes (errors); 92 default policy rules; 17 record types; 22 tables, 24 differences between the migrated schema and the table definitions.
  • Conformance data: 548 HTTP exchanges from 300 functional API tests (501 identical on replay), 263 algorithm vectors (263 pass) (rewrite pack).

Points of attention

  1. Statuses are asynchronous and polled: every write answers 202 PENDING; ACTIVE needs the worker to see the new serial on every nameserver (threshold_percentage 100) within 455 s, otherwise ERROR, retried every 120 s forever (async). Heat and Skyline depend on these statuses.
  2. Serials are Unix timestamps set by the producer at most every 5 s (designate/storage/sqlalchemy/__init__.py:628); without a running producer, recordset changes are never pushed.
  3. No tooz backend = no cross-process lock and no shard split: two central or two producer processes without [coordination]/backend_url are unsafe [inferred] (designate/coordination.py:57).
  4. Security fixes in this branch: missing authorisation on floating IP PTR updates and cross-tenant hijack of reverse zones (history); reads of /v2/reverse/floatingips still have no policy of their own.
  5. Empty-body errors: 401 for invalid identity and 403 for refused context flags (all_projects, sudo…) have no JSON body, unlike every other error (designate/api/middleware.py:142, designate/api/middleware.py:168).
  6. Unexpected 500s: PATCH with application/json-patch+json, abandon and pool-move handlers forcing 500, NoServersConfigured/MultiplePoolsFound configuration errors (errors).
  7. The migrations, not the table definitions, are the schema: 24 differences, mostly indexes and one unique constraint on quotas (schema diff).
  8. TSIG secrets are stored and returned in clear to admins (designate/objects/adapters/api_v2/tsigkey.py:29).
  9. Name resolution by clients: python-designateclient, Neutron and Heat find zones by name and expect exactly one match; the same project owning one name in two pools (allowed since the cross-pool ownership check) breaks them (python-designateclient:designateclient/v2/utils.py:24).
  10. ExternalDNS is a different model: Designate's DNS servers are secondaries of mdns; with ExternalDNS the provider is the primary. SOA content, serials, NOTIFY, AXFR, catalog zones, secondary zones and pool targets cannot be reproduced through ExternalDNS, and SPF, SSHFP, CAA, CERT, HTTPS and SVCB records are not supported by it (rewrite pack § 8).

Files

FileContent
01-rest-api_stable-2026.2.mdendpoints, functional descriptions, lifecycles
02-errors_stable-2026.2.mderror translation, situations, asynchronous errors, catalogue
03-interactions_stable-2026.2.mdRabbitMQ, database, tooz, DNS protocol, backends, REST
04-scenarios_stable-2026.2.md7 sequence diagrams with diagnostics
05-async-processing_stable-2026.2.mdRPC servers, producer tasks, mdns, sink, heartbeats
06-change-history_stable-2026.2.md11 commits since 23.0.0, carry-over plan
07-rewrite-pack_stable-2026.2.mdrewrite pack: algorithms, client expectations, ExternalDNS mapping
_verification_stable-2026.2.mdreference check
_inventory/, _rewrite/raw inventory and history; machine-readable contracts and conformance data

Blind spots / [inferred]

  • Backend drivers other than bind9 and pdns4 were read at the level of their create/delete calls only (Akamai, NS1, DynECT, NSD, Infoblox details); none was exercised (the functional tests use the fake backend).
  • No dynamic capture of the DNS side (mdns AXFR frames, NOTIFY, SOA polling) nor of RabbitMQ frames: the sample frames of interactions are reconstructed from the code.
  • designate-sink and the worker/producer were read, not run end to end; the producer's tooz sharding was not tested with several instances.
  • designate-tempest-plugin (branchless, master) was not run.
  • Statements marked [inferred] were not followed to the end (behaviour under MySQL collations, multi-process safety without tooz, IPv6 floating IP PTR zones, serial collisions within one second).
  • designate/api/v2/controllers/extensions loaded from enabled_extensions_v2: no extension ships in the tree (designate/api/v2/controllers/root.py:41).

Source: reverse-spec-designate/00-summary_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.