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 foropenkeel-designateon ExternalDNS, with the client expectations of Neutron's DNS integration, python-designateclient and its OSC plugin, Heat'sOS::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| centralProcesses
| Process | Entry point | Role | Listens to / consumes |
|---|---|---|---|
| designate-api | designate.cmd.api:main (pyproject.toml:43), WSGI designate/wsgi/api.py | REST API (Pecan /v2, /admin; Flask /), no business logic | HTTP :9001 (designate/conf/api.py:36) |
| designate-central | designate.cmd.central:main (pyproject.toml:44) | business rules, policies, database writes, notifications | RPC topic central (designate/central/service.py:57) |
| designate-worker | designate.cmd.worker:main (pyproject.toml:48) | push zones to pool targets, NOTIFY, SOA polling, AXFR of secondary zones, exports | RPC topic worker (designate/worker/service.py:45) |
| designate-producer | designate.cmd.producer:main (pyproject.toml:49) | periodic tasks over 4096 zone shards | timers; RPC topic producer (no methods) (designate/producer/service.py:34) |
| designate-mdns | designate.cmd.mdns:main (pyproject.toml:46) | authoritative "mini DNS" serving AXFR to the pool servers, NOTIFY receiver | DNS UDP/TCP :5354 (designate/mdns/service.py:33) |
| designate-sink | designate.cmd.sink:main (pyproject.toml:47) | Nova/Neutron notification handlers | AMQP notifications (designate/sink/service.py:33) |
| designate-manage | designate.cmd.manage:main (pyproject.toml:45) | database sync, pool update from the pools YAML file, TLD import | CLI |
| designate-status | designate.cmd.status:main (pyproject.toml:50) | upgrade checks | CLI |
External dependencies
| System | Protocol | Library | Configuration | Usage |
|---|---|---|---|---|
| MariaDB / MySQL (PostgreSQL possible) | SQL | SQLAlchemy Core 2.0 + oslo.db, Alembic | [storage:sqlalchemy]/connection | all state; 22 tables |
| RabbitMQ | AMQP | oslo.messaging | [DEFAULT]/transport_url | RPC (central, worker), notifications dns.*, sink input |
| Keystone | HTTPS REST | keystonemiddleware, keystoneauth1 | [keystone_authtoken], [keystone] | tokens; project id checks for shares and quotas |
| Neutron | HTTPS REST | openstacksdk | [network_api:neutron] or the catalog | floating IPs for reverse DNS |
| tooz backend (Redis, etcd, ZooKeeper…) | backend-specific | tooz | [coordination]/backend_url | per-zone locks, producer group and shards |
| DNS servers of the pools | DNS (UDP/TCP 53), driver APIs (PowerDNS HTTP, rndc, Infoblox WAPI, NS1, Akamai, DynECT, NSD) | dnspython 2.8, requests, infoblox-client | the pools YAML file (designate-manage pool update) | zone publication |
Key figures
- 78 REST operations: 70 on
/v2(including the two/v2/service_statusalias routes and the deliberate 405 ofGET …/tasks/abandon), 8 on/adminwhen enabled, plusGET /; 68 documented in the api-ref (REST API). - ≈ 75 RPC methods on
central(3 casts), 7 onworker(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
- Statuses are asynchronous and polled: every write answers 202
PENDING;ACTIVEneeds the worker to see the new serial on every nameserver (threshold_percentage100) within 455 s, otherwiseERROR, retried every 120 s forever (async). Heat and Skyline depend on these statuses. - 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. - No tooz backend = no cross-process lock and no shard split: two central or two producer processes without
[coordination]/backend_urlare unsafe [inferred] (designate/coordination.py:57). - Security fixes in this branch: missing authorisation on floating IP PTR updates and cross-tenant hijack of reverse zones (history); reads of
/v2/reverse/floatingipsstill have no policy of their own. - 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). - Unexpected 500s: PATCH with
application/json-patch+json, abandon and pool-move handlers forcing 500,NoServersConfigured/MultiplePoolsFoundconfiguration errors (errors). - The migrations, not the table definitions, are the schema: 24 differences, mostly indexes and one unique constraint on
quotas(schema diff). - TSIG secrets are stored and returned in clear to admins (
designate/objects/adapters/api_v2/tsigkey.py:29). - 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). - 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
| File | Content |
|---|---|
| 01-rest-api_stable-2026.2.md | endpoints, functional descriptions, lifecycles |
| 02-errors_stable-2026.2.md | error translation, situations, asynchronous errors, catalogue |
| 03-interactions_stable-2026.2.md | RabbitMQ, database, tooz, DNS protocol, backends, REST |
| 04-scenarios_stable-2026.2.md | 7 sequence diagrams with diagnostics |
| 05-async-processing_stable-2026.2.md | RPC servers, producer tasks, mdns, sink, heartbeats |
| 06-change-history_stable-2026.2.md | 11 commits since 23.0.0, carry-over plan |
| 07-rewrite-pack_stable-2026.2.md | rewrite pack: algorithms, client expectations, ExternalDNS mapping |
| _verification_stable-2026.2.md | reference check |
_inventory/, _rewrite/ | raw inventory and history; machine-readable contracts and conformance data |
Blind spots / [inferred]
- Backend drivers other than
bind9andpdns4were read at the level of their create/delete calls only (Akamai, NS1, DynECT, NSD, Infoblox details); none was exercised (the functional tests use thefakebackend). - 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/extensionsloaded fromenabled_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.