Interactions with other components — designate — branch stable/2026.2
Branch
stable/2026.2@e3dd774db145— repository https://opendev.org/openstack/designate — analysed on 2026-10-29
Designate talks to six kinds of systems: its MariaDB/MySQL database (SQLAlchemy Core, no ORM), RabbitMQ (oslo.messaging RPC and notifications), a tooz coordination backend, the DNS protocol (to and from the DNS servers of each pool), the DNS servers' management APIs (one driver per pool target), and Keystone/Neutron (REST).
flowchart LR cli[Clients: OSC, Neutron, Heat, Skyline] -->|HTTPS REST /v2 /admin| api[designate-api] api -->|keystonemiddleware GET /v3/auth/tokens| ks[Keystone] api -->|AMQP call topic central| central[designate-central] central -->|SQL| db[(MariaDB)] central -->|AMQP cast topic worker| worker[designate-worker] central -->|REST GET /v2.0/floatingips| neutron[Neutron] central -->|tooz locks| coord[(tooz backend)] producer[designate-producer] -->|AMQP call/cast| central producer -->|AMQP cast| worker producer -->|tooz group producer| coord worker -->|AMQP cast update_status| central worker -->|backend driver: REST, rndc, WAPI| dns[(DNS servers of the pool)] worker -->|DNS NOTIFY, SOA queries UDP/TCP 53| dns dns -->|DNS AXFR TCP 5354| mdns[designate-mdns] mdns -->|SQL read| db masters[(external masters)] -->|DNS NOTIFY| mdns worker -->|DNS AXFR| masters nova[Nova] -->|AMQP notifications| sink[designate-sink] neutron -->|AMQP notifications| sink sink -->|AMQP call| central
RabbitMQ
Topology
oslo.messaging defaults apply: Designate never calls set_transport_defaults, so the control exchange is openstack unless [DEFAULT]/control_exchange is set (designate/rpc.py:81 is never called) [inferred from a code search]. The RPC servers listen on <topic> and <topic>.<host> (designate/service.py:107).
| Exchange | Type | Routing key / queue | Producer | Consumer |
|---|---|---|---|---|
openstack | topic | central, central.<host> | api, worker, producer, sink, mdns (no), designate-manage | designate-central |
openstack | topic | worker, worker.<host> | central, producer, worker, mdns | designate-worker |
openstack | topic | producer, producer.<host> | nobody (the producer exposes no RPC method) | designate-producer (designate/producer/service.py:34) |
reply_<uuid> | direct | — | servers | callers of call |
openstack (notifications) | topic | notifications.info, notifications.error ([oslo_messaging_notifications]/topics) | central, producer, api (faults) | Ceilometer, audit consumers |
nova | topic | notifications.info ([handler:nova_fixed]/notification_topics, control_exchange) | Nova | designate-sink (designate/conf/sink.py:75) |
neutron | topic | notifications.info ([handler:neutron_floatingip]) | Neutron | designate-sink (designate/conf/sink.py:62) |
Serialisation: Designate objects travel as {"designate_object.name", "designate_object.version", "designate_object.data", "designate_object.changes", "designate_object.original_values"} primitives (designate/rpc.py:103), the context as DesignateContext.to_dict() (designate/rpc.py:153). Remote exceptions are rebuilt in the caller when their module is designate.exceptions or designate.backend.impl_dynect (designate/rpc.py:46). Timeout: rpc_response_timeout (60 s), after which the API answers 504 timeout (designate/api/middleware.py:289).
RPC methods
Topic central, version cap 6.12 (designate/central/rpcapi.py:92).
| Method | Mode | Topic | Arguments | Client | Server |
|---|---|---|---|---|---|
get_absolute_limits | call | central | — | designate/central/rpcapi.py:109 | designate/central/service.py:602 |
get_quotas | call | central | tenant_id | designate/central/rpcapi.py:113 | designate/central/service.py:608 |
set_quota | call | central | tenant_id, resource, hard_limit | designate/central/rpcapi.py:116 | designate/central/service.py:622 |
reset_quotas | call | central | tenant_id | designate/central/rpcapi.py:120 | designate/central/service.py:639 |
create_tsigkey / find_tsigkeys / get_tsigkey / update_tsigkey / delete_tsigkey | call | central | tsigkey or tsigkey_id, paging | designate/central/rpcapi.py:124 | designate/central/service.py:699 |
find_tenants / get_tenant | call | central | tenant_id | designate/central/rpcapi.py:144 | designate/central/service.py:793 |
increment_zone_serial | call | central | zone | designate/central/rpcapi.py:151 | designate/central/service.py:839 |
create_zone | call | central | zone | designate/central/rpcapi.py:154 | designate/central/service.py:848 |
get_zone | call | central | zone_id | designate/central/rpcapi.py:157 | designate/central/service.py:976 |
get_zone_ns_records | call | central | zone_id | designate/central/rpcapi.py:160 | designate/central/service.py:1007 |
find_zones | call | central | criterion, marker, limit, sort_key, sort_dir | designate/central/rpcapi.py:164 | designate/central/service.py:1033 |
update_zone | call | central | zone, increment_serial | designate/central/rpcapi.py:170 | designate/central/service.py:1054 |
delete_zone | call | central | zone_id | designate/central/rpcapi.py:174 | designate/central/service.py:1122 |
purge_zones | call | central | criterion, limit | designate/central/rpcapi.py:177 | designate/central/service.py:1196 |
pool_move_zone | call | central | zone_id, target_pool_id | designate/central/rpcapi.py:181 | designate/central/service.py:1415 |
share_zone / unshare_zone / find_shared_zones / get_shared_zone | call | central | zone_id, shared_zone / zone_share_id, paging | designate/central/rpcapi.py:187 | designate/central/service.py:1285 |
create_tld / find_tlds / get_tld / update_tld / delete_tld | call | central | tld or tld_id, paging | designate/central/rpcapi.py:208 | designate/central/service.py:650 |
create_recordset | call | central | zone_id, recordset | designate/central/rpcapi.py:227 | designate/central/service.py:1508 |
get_recordset | call | central | zone_id, recordset_id | designate/central/rpcapi.py:231 | designate/central/service.py:1605 |
find_recordsets | call | central | criterion, paging, force_index | designate/central/rpcapi.py:235 | designate/central/service.py:1648 |
create_managed_records | call | central | zone_id, records_values, recordset_values | designate/central/rpcapi.py:242 | designate/central/service.py:1691 |
delete_managed_records | call | central | zone_id, criterion | designate/central/rpcapi.py:248 | designate/central/service.py:1698 |
export_zone | call | central | zone_id | designate/central/rpcapi.py:253 | designate/central/service.py:1706 |
update_recordset | call | central | recordset, increment_serial | designate/central/rpcapi.py:256 | designate/central/service.py:1718 |
delete_recordset | call | central | zone_id, recordset_id, increment_serial | designate/central/rpcapi.py:261 | designate/central/service.py:1815 |
count_report | call | central | criterion | designate/central/rpcapi.py:269 | designate/central/service.py:1253 |
list_floatingips / get_floatingip / update_floatingip | call | central | region, floatingip_id, values | designate/central/rpcapi.py:272 | designate/central/service.py:2014 |
create_blacklist … delete_blacklist | call | central | blacklist or blacklist_id, paging | designate/central/rpcapi.py:284 | designate/central/service.py:2357 |
create_pool / find_pools / find_pool / get_pool / update_pool / delete_pool | call | central | pool or pool_id, paging | designate/central/rpcapi.py:307 | designate/central/service.py:2410 |
update_status | cast | central | zone_id, status, serial, action | designate/central/rpcapi.py:328 | designate/central/service.py:2543 |
create_zone_transfer_request … find_zone_transfer_accepts | call | central | request/accept objects or ids, paging | designate/central/rpcapi.py:333 | designate/central/service.py:2672 |
xfr_zone | call | central | zone_id | designate/central/rpcapi.py:378 | designate/central/service.py:1209 |
create_zone_import / find_zone_imports / get_zone_import / delete_zone_import | call | central | request_body, zone_attributes / ids, paging | designate/central/rpcapi.py:382 | designate/central/service.py:2867 |
create_zone_export / find_zone_exports / get_zone_export / update_zone_export / delete_zone_export | call | central | zone_id / zone_export | designate/central/rpcapi.py:404 | designate/central/service.py:3026 |
find_service_status / find_service_statuses | call | central | criterion, paging | designate/central/rpcapi.py:427 | designate/central/service.py:3120 |
update_service_status | cast | central | service_status | designate/central/rpcapi.py:438 | designate/central/service.py:3136 |
delete_service_status | cast | central | service_status | designate/central/rpcapi.py:442 | designate/central/service.py:3194 |
Topic worker, version cap 1.2 (designate/worker/rpcapi.py:50).
| Method | Mode | Topic | Arguments | Client | Server |
|---|---|---|---|---|---|
create_zone | cast | worker | zone | designate/worker/rpcapi.py:66 | designate/worker/service.py:166 |
update_zone | cast | worker | zone | designate/worker/rpcapi.py:70 | designate/worker/service.py:175 |
delete_zone | cast | worker | zone, hard_delete | designate/worker/rpcapi.py:74 | designate/worker/service.py:184 |
recover_shard | cast | worker | begin, end | designate/worker/rpcapi.py:78 | designate/worker/service.py:197 |
start_zone_export | cast | worker | zone, export | designate/worker/rpcapi.py:82 | designate/worker/service.py:208 |
perform_zone_xfr | cast | worker | zone, servers | designate/worker/rpcapi.py:86 | designate/worker/service.py:219 |
get_serial_number | call | worker | zone, host, port | designate/worker/rpcapi.py:90 | designate/worker/service.py:230 |
Notifications
Emitted by central with notifier.info(context, event_type, payload) after the outermost decorated call returns (designate/common/decorators/notification.py:85), publisher central.<host> (designate/rpc.py:225). The payload is the returned object (Default plugin, designate/notifications.py:71) or, with notification_plugin = audit, one change record per modified field (designate/notifications.py:80).
| event_type | Priority | Emitted when | Payload | Emitted at |
|---|---|---|---|---|
dns.zone.create, dns.domain.create | info | zone created | Zone primitive | designate/central/service.py:845 |
dns.zone.update, dns.domain.update | info | zone updated, moved, status changed | Zone primitive | designate/central/service.py:1051, designate/central/service.py:2539 |
dns.zone.delete, dns.domain.delete | info | zone delete accepted (status PENDING) or abandoned | Zone primitive | designate/central/service.py:1119 |
dns.zone.share, dns.zone.unshare | info | share created/removed | SharedZone | designate/central/service.py:1283, designate/central/service.py:1309 |
dns.recordset.create/update/delete | info | recordset change | RecordSet with records | designate/central/service.py:1507, designate/central/service.py:1717, designate/central/service.py:1814 |
dns.tld.*, dns.tsigkey.*, dns.blacklist.*, dns.pool.* | info | admin resources | object | designate/central/service.py:648, designate/central/service.py:697, designate/central/service.py:2355, designate/central/service.py:2408 |
dns.zone_transfer_request.*, dns.zone_transfer_accept.create | info | transfers | object (the request includes the key) | designate/central/service.py:2670, designate/central/service.py:2772 |
dns.zone_import.*, dns.zone_export.* | info | import/export tasks | ZoneImport / ZoneExport | designate/central/service.py:2866, designate/central/service.py:3025 |
dns.domain.exists, dns.zone.exists | info | hourly, per zone, by the producer | zone dict + audit_period_beginning/ending | designate/producer/tasks.py:175 |
dns.api.fault | error | API error, when notify_api_faults = True | {url, exception, status} | designate/notifications.py:33 |
Sample frames
RPC call from the API (create_recordset), as sent on the central queue (shortened):
{"oslo.version": "2.0",
"oslo.message": "{\"method\": \"create_recordset\", \"version\": \"6.12\", \"namespace\": null,
\"args\": {\"zone_id\": \"a86dba58-0043-4cc6-a1bb-69d5e86f3ca3\",
\"recordset\": {\"designate_object.name\": \"RecordSet\", \"designate_object.version\": \"1.0\",
\"designate_object.data\": {\"name\": \"www.example.org.\", \"type\": \"A\", \"ttl\": 3600,
\"records\": {\"designate_object.name\": \"RecordList\", \"designate_object.data\":
{\"objects\": [{\"designate_object.name\": \"Record\", \"designate_object.data\": {\"data\": \"192.0.2.1\"}}]}}},
\"designate_object.changes\": [\"name\", \"type\", \"ttl\", \"records\"]}},
\"_msg_id\": \"<uuid>\", \"_reply_q\": \"reply_<uuid>\", \"_unique_id\": \"<uuid>\",
\"_context_project_id\": \"<project>\", \"_context_roles\": [\"member\", \"reader\"],
\"_context_all_tenants\": false, \"_context_edit_managed_records\": false,
\"_context_hide_counts\": false, \"_context_request_id\": \"req-<uuid>\"}"}
Status report from the worker (cast, no _msg_id):
{"method": "update_status", "version": "6.12",
"args": {"zone_id": "<uuid>", "status": "SUCCESS", "serial": 1793269000, "action": "UPDATE"}}
Database
SQLAlchemy Core tables (designate/storage/sqlalchemy/tables.py:49), Alembic migrations in designate/storage/sqlalchemy/alembic/versions (head f49c4409c8ba). Production schema and its differences with the tables: [_rewrite/schema-diff_stable-2026.2.md](_rewrite/schema-diff_stable-2026.2.md).
Tables
| Table | Role | Written by | Read by |
|---|---|---|---|
zones | zones, SOA fields, status/action, serial, shard, pool_id, flags increment_serial/delayed_notify, soft delete (deleted = id) — unique (name, deleted, pool_id) (designate/storage/sqlalchemy/tables.py:147) | central, worker (via central) | all |
zone_attributes, zone_masters | scheduling attributes (pool_id, tier…); masters of SECONDARY zones | central | central, scheduler, worker, mdns |
shared_zones | zone shares to other projects | central | central (visibility joins) |
recordsets | name, type, ttl, description, reverse_name (designate/storage/sqlalchemy/tables.py:207) | central | central, mdns, producer |
records | data, hash = md5(recordset_id:data), unique (designate/storage/sqlalchemy/__init__.py:1178), managed fields, status/action/serial | central | central, mdns, worker |
tlds, blacklists | allowed TLDs, forbidden name patterns (regex) | central (admin) | central |
tsigkeys | TSIG keys, scope POOL or ZONE, resource_id, secret in clear | central (admin) | mdns, worker, central |
pools, pool_attributes, pool_ns_records, pool_nameservers, pool_targets, pool_target_masters, pool_target_options, pool_also_notifies | the pool model loaded from the pools YAML file (designate-manage pool update) | designate-manage, central | central, worker, mdns |
quotas | per-project overrides (tenant_id, resource, hard_limit) | central | central |
zone_transfer_requests, zone_transfer_accepts | ownership transfers (key, target_tenant_id, status) | central | central |
zone_tasks | zone imports and exports (task_type, status, message, location) | central, worker | central |
service_statuses | heartbeats (service_name, hostname, status, heartbeated_at, stats) | central (from heartbeats) | central, producer |
The migrations insert the default pool 794ccc2c-d751-44fe-b57f-8894c9f5c842 (designate/conf/central.py:41), without NS records nor targets: designate-manage pool update must run before the first zone (designate/central/service.py:914).
Notable transactions and queries
- Write sessions:
@transactionwraps central methods in one write session; nested decorated calls join it (designate/storage/__init__.py:107). Retries onDBDeadlockre-run the whole method (designate/storage/__init__.py:39). - Optimistic version: every
UPDATEsetsversion = version + 1(designate/storage/sqlalchemy/base.py:215), but no update filters on the version: last writer wins, the per-zone tooz lock is the only serialisation. - Tenant filter:
tenant_id = :project OR tenant_id IS NULL, plusshared_zones.target_project_id = :projectwhere shares apply, unlessall_tenants(designate/storage/sqlalchemy/base.py:165). - Criterion mini-language: string values starting with
!,<,<=,>,>=,BETWEEN a,b, or containing%(LIKE); lists becomeIN(designate/storage/sqlalchemy/base.py:129). The API maps*in filter values to%(designate/api/v2/controllers/rest.py:65). - Paging: marker = id of the last item,
ORDER BY <sort_key> <sort_dir>, id(designate/storage/sqlalchemy/utils.py:130). - Recordset listing: one join
recordsets ⟕ recordswith atotal_countsub-query, optionallyFORCE INDEXon MySQL for the cross-zone listing (designate/api/v2/controllers/common.py:31).
Coordination (tooz)
[coordination]/backend_url(unset by default,designate/conf/coordination.py:26): Redis, etcd3, ZooKeeper, memcached… Central uses it for the per-zone lockszone-<id>andcreate-new-zone(designate/common/decorators/lock.py:85); the producer for theproducergroup and shard partitioning (designate/coordination.py:104). Without it, locks are local file locks and every producer owns every shard.
DNS protocol
| Exchange | Direction | Transport | When | Defined at |
|---|---|---|---|---|
NOTIFY (opcode 4, SOA question) for a zone | worker → each pool target host:port option, and each also_notifies entry | UDP (TCP with [service:worker]/all_tcp), timeout 10 s, TSIG of the target when set | after each backend create/update | designate/dnsutils.py:231, designate/worker/tasks/zone.py:184 |
| SOA query for a zone | worker → each pool nameserver | UDP/TCP, TSIG of the nameserver when set | polling after each change | designate/dnsutils.py:272 |
| AXFR (IXFR answered as AXFR) | pool DNS servers → mdns :5354 | TCP, optional TSIG (key = scope) | after NOTIFY, on SOA refresh | designate/mdns/handler.py:241 |
| QUERY (any type) | anyone → mdns | UDP/TCP | debugging, some backends | designate/mdns/handler.py:371 |
| NOTIFY | external masters → mdns | UDP/TCP | SECONDARY zone changed on its master | designate/mdns/handler.py:91 |
| SOA query, AXFR | worker → masters of SECONDARY zones | UDP/TCP, xfr_timeout 10 s | secondary zone creation, refresh, NOTIFY | designate/dnsutils.py:152, designate/worker/tasks/zone.py:776 |
Sample NOTIFY sent by the worker for example.org. (header fields as built by dnspython make_query then set_opcode(NOTIFY), designate/dnsutils.py:222):
ID=<random> QR=0 OPCODE=NOTIFY (4) RD=1 (make_query default) QDCOUNT=1
QUESTION: example.org. IN SOA
ADDITIONAL: TSIG <key name> hmac-sha256 … (only when the target has a tsigkey_id)
The DNS server answers with QR=1 OPCODE=NOTIFY; the worker does not check the answer's rcode, only that one came back before the timeout (designate/worker/tasks/zone.py:188). The SOA served by mdns for a primary zone is <lowest-priority NS hostname> <email with '@' → '.'>. <serial> <refresh> <retry> <expire> <minimum> (designate/central/service.py:414), the serial being a Unix timestamp set by central (designate/storage/sqlalchemy/__init__.py:628).
Backend drivers (pool targets)
The worker calls the driver named by each target's type (stevedore designate.backend, designate/backend/__init__.py:27). All the drivers below configure the DNS server as a secondary of mdns: Designate never writes records to the DNS server, the server pulls the whole zone by AXFR from mdns.
| Driver | Protocol | create_zone | delete_zone | Defined at |
|---|---|---|---|---|
bind9 | rndc over TCP 953 (subprocess, rootwrap) | rndc addzone <zone> { type slave; masters { <mdns> port 5354;}; file "slave.<zone><id>"; }; | rndc delzone [-clean] <zone> | designate/backend/impl_bind9.py:75 |
pdns4 | PowerDNS HTTP API, header X-API-Key | POST /api/v1/servers/localhost/zones {"name", "kind": "slave", "masters": ["<mdns>:5354"]} (deletes a pre-existing zone first) | DELETE /api/v1/servers/localhost/zones/<zone> | designate/backend/impl_pdns4.py:109 |
infoblox | Infoblox WAPI (infoblox-client) | secondary zone with the mdns masters in a DNS view | delete the zone | designate/backend/impl_infoblox.py:85 |
nsd4 | NSD control protocol over TLS | addzone | delzone | designate/backend/impl_nsd4.py:149 |
akamai_v2, ns1, dynect | provider REST APIs | secondary zone pointing to mdns | delete | designate/backend/impl_akamai_v2.py:183, designate/backend/impl_ns1.py:76, designate/backend/impl_dynect.py:275 |
designate | another Designate (designateclient) | zones.create(type=SECONDARY, masters=mdns) | zones.delete | designate/backend/impl_designate.py:78 |
fake | none | no-op | no-op | designate/backend/impl_fake.py:24 |
Outbound REST calls
| Target service | Method + path | When | Authentication | Timeout / retry | Errors handled | Defined at |
|---|---|---|---|---|---|---|
| Keystone | GET /v3/auth/tokens (keystonemiddleware) | every API request (cached) | service user [keystone_authtoken] | keystonemiddleware defaults | 401 | etc/designate/api-paste.ini:52 |
| Keystone | GET /v3/projects/{id} | share a zone; PATCH /v2/quotas/{project} when quotas_verify_project_id | the user's token (context.get_auth_plugin()) | [keystone] adapter options | 404 → InvalidProject 400; 403 or connection error → accepted | designate/common/keystone.py:26 |
| Neutron | GET /v2.0/floatingips?project_id=<project> (openstacksdk network.ips) per region | every /v2/reverse/floatingips call | the user's token on the catalog endpoint, or [network_api:neutron]/endpoints | [network_api:neutron]/timeout, 5 regions in parallel | any exception → NeutronCommunicationFailure 504 | designate/network_api/neutron.py:60 |
Other
- Binaries:
rndcthroughdesignate-rootwrap(designate/backend/impl_bind9.py:208,etc/designate/rootwrap.d/bind9.filters). - Files: none besides configuration, the pools YAML file (read by
designate-manage) and the Jinja2 export templatedesignate/resources/templates/export-zone.jinja2.
Source: reverse-spec-designate/03-interactions_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.