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

Reverse-spec · Services

designate — Interactions

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

ExchangeTypeRouting key / queueProducerConsumer
openstacktopiccentral, central.<host>api, worker, producer, sink, mdns (no), designate-managedesignate-central
openstacktopicworker, worker.<host>central, producer, worker, mdnsdesignate-worker
openstacktopicproducer, producer.<host>nobody (the producer exposes no RPC method)designate-producer (designate/producer/service.py:34)
reply_<uuid>direct—serverscallers of call
openstack (notifications)topicnotifications.info, notifications.error ([oslo_messaging_notifications]/topics)central, producer, api (faults)Ceilometer, audit consumers
novatopicnotifications.info ([handler:nova_fixed]/notification_topics, control_exchange)Novadesignate-sink (designate/conf/sink.py:75)
neutrontopicnotifications.info ([handler:neutron_floatingip])Neutrondesignate-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).

MethodModeTopicArgumentsClientServer
get_absolute_limitscallcentral—designate/central/rpcapi.py:109designate/central/service.py:602
get_quotascallcentraltenant_iddesignate/central/rpcapi.py:113designate/central/service.py:608
set_quotacallcentraltenant_id, resource, hard_limitdesignate/central/rpcapi.py:116designate/central/service.py:622
reset_quotascallcentraltenant_iddesignate/central/rpcapi.py:120designate/central/service.py:639
create_tsigkey / find_tsigkeys / get_tsigkey / update_tsigkey / delete_tsigkeycallcentraltsigkey or tsigkey_id, pagingdesignate/central/rpcapi.py:124designate/central/service.py:699
find_tenants / get_tenantcallcentraltenant_iddesignate/central/rpcapi.py:144designate/central/service.py:793
increment_zone_serialcallcentralzonedesignate/central/rpcapi.py:151designate/central/service.py:839
create_zonecallcentralzonedesignate/central/rpcapi.py:154designate/central/service.py:848
get_zonecallcentralzone_iddesignate/central/rpcapi.py:157designate/central/service.py:976
get_zone_ns_recordscallcentralzone_iddesignate/central/rpcapi.py:160designate/central/service.py:1007
find_zonescallcentralcriterion, marker, limit, sort_key, sort_dirdesignate/central/rpcapi.py:164designate/central/service.py:1033
update_zonecallcentralzone, increment_serialdesignate/central/rpcapi.py:170designate/central/service.py:1054
delete_zonecallcentralzone_iddesignate/central/rpcapi.py:174designate/central/service.py:1122
purge_zonescallcentralcriterion, limitdesignate/central/rpcapi.py:177designate/central/service.py:1196
pool_move_zonecallcentralzone_id, target_pool_iddesignate/central/rpcapi.py:181designate/central/service.py:1415
share_zone / unshare_zone / find_shared_zones / get_shared_zonecallcentralzone_id, shared_zone / zone_share_id, pagingdesignate/central/rpcapi.py:187designate/central/service.py:1285
create_tld / find_tlds / get_tld / update_tld / delete_tldcallcentraltld or tld_id, pagingdesignate/central/rpcapi.py:208designate/central/service.py:650
create_recordsetcallcentralzone_id, recordsetdesignate/central/rpcapi.py:227designate/central/service.py:1508
get_recordsetcallcentralzone_id, recordset_iddesignate/central/rpcapi.py:231designate/central/service.py:1605
find_recordsetscallcentralcriterion, paging, force_indexdesignate/central/rpcapi.py:235designate/central/service.py:1648
create_managed_recordscallcentralzone_id, records_values, recordset_valuesdesignate/central/rpcapi.py:242designate/central/service.py:1691
delete_managed_recordscallcentralzone_id, criteriondesignate/central/rpcapi.py:248designate/central/service.py:1698
export_zonecallcentralzone_iddesignate/central/rpcapi.py:253designate/central/service.py:1706
update_recordsetcallcentralrecordset, increment_serialdesignate/central/rpcapi.py:256designate/central/service.py:1718
delete_recordsetcallcentralzone_id, recordset_id, increment_serialdesignate/central/rpcapi.py:261designate/central/service.py:1815
count_reportcallcentralcriteriondesignate/central/rpcapi.py:269designate/central/service.py:1253
list_floatingips / get_floatingip / update_floatingipcallcentralregion, floatingip_id, valuesdesignate/central/rpcapi.py:272designate/central/service.py:2014
create_blacklist … delete_blacklistcallcentralblacklist or blacklist_id, pagingdesignate/central/rpcapi.py:284designate/central/service.py:2357
create_pool / find_pools / find_pool / get_pool / update_pool / delete_poolcallcentralpool or pool_id, pagingdesignate/central/rpcapi.py:307designate/central/service.py:2410
update_statuscastcentralzone_id, status, serial, actiondesignate/central/rpcapi.py:328designate/central/service.py:2543
create_zone_transfer_request … find_zone_transfer_acceptscallcentralrequest/accept objects or ids, pagingdesignate/central/rpcapi.py:333designate/central/service.py:2672
xfr_zonecallcentralzone_iddesignate/central/rpcapi.py:378designate/central/service.py:1209
create_zone_import / find_zone_imports / get_zone_import / delete_zone_importcallcentralrequest_body, zone_attributes / ids, pagingdesignate/central/rpcapi.py:382designate/central/service.py:2867
create_zone_export / find_zone_exports / get_zone_export / update_zone_export / delete_zone_exportcallcentralzone_id / zone_exportdesignate/central/rpcapi.py:404designate/central/service.py:3026
find_service_status / find_service_statusescallcentralcriterion, pagingdesignate/central/rpcapi.py:427designate/central/service.py:3120
update_service_statuscastcentralservice_statusdesignate/central/rpcapi.py:438designate/central/service.py:3136
delete_service_statuscastcentralservice_statusdesignate/central/rpcapi.py:442designate/central/service.py:3194

Topic worker, version cap 1.2 (designate/worker/rpcapi.py:50).

MethodModeTopicArgumentsClientServer
create_zonecastworkerzonedesignate/worker/rpcapi.py:66designate/worker/service.py:166
update_zonecastworkerzonedesignate/worker/rpcapi.py:70designate/worker/service.py:175
delete_zonecastworkerzone, hard_deletedesignate/worker/rpcapi.py:74designate/worker/service.py:184
recover_shardcastworkerbegin, enddesignate/worker/rpcapi.py:78designate/worker/service.py:197
start_zone_exportcastworkerzone, exportdesignate/worker/rpcapi.py:82designate/worker/service.py:208
perform_zone_xfrcastworkerzone, serversdesignate/worker/rpcapi.py:86designate/worker/service.py:219
get_serial_numbercallworkerzone, host, portdesignate/worker/rpcapi.py:90designate/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_typePriorityEmitted whenPayloadEmitted at
dns.zone.create, dns.domain.createinfozone createdZone primitivedesignate/central/service.py:845
dns.zone.update, dns.domain.updateinfozone updated, moved, status changedZone primitivedesignate/central/service.py:1051, designate/central/service.py:2539
dns.zone.delete, dns.domain.deleteinfozone delete accepted (status PENDING) or abandonedZone primitivedesignate/central/service.py:1119
dns.zone.share, dns.zone.unshareinfoshare created/removedSharedZonedesignate/central/service.py:1283, designate/central/service.py:1309
dns.recordset.create/update/deleteinforecordset changeRecordSet with recordsdesignate/central/service.py:1507, designate/central/service.py:1717, designate/central/service.py:1814
dns.tld.*, dns.tsigkey.*, dns.blacklist.*, dns.pool.*infoadmin resourcesobjectdesignate/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.createinfotransfersobject (the request includes the key)designate/central/service.py:2670, designate/central/service.py:2772
dns.zone_import.*, dns.zone_export.*infoimport/export tasksZoneImport / ZoneExportdesignate/central/service.py:2866, designate/central/service.py:3025
dns.domain.exists, dns.zone.existsinfohourly, per zone, by the producerzone dict + audit_period_beginning/endingdesignate/producer/tasks.py:175
dns.api.faulterrorAPI 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

TableRoleWritten byRead by
zoneszones, 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_mastersscheduling attributes (pool_id, tier…); masters of SECONDARY zonescentralcentral, scheduler, worker, mdns
shared_zoneszone shares to other projectscentralcentral (visibility joins)
recordsetsname, type, ttl, description, reverse_name (designate/storage/sqlalchemy/tables.py:207)centralcentral, mdns, producer
recordsdata, hash = md5(recordset_id:data), unique (designate/storage/sqlalchemy/__init__.py:1178), managed fields, status/action/serialcentralcentral, mdns, worker
tlds, blacklistsallowed TLDs, forbidden name patterns (regex)central (admin)central
tsigkeysTSIG keys, scope POOL or ZONE, resource_id, secret in clearcentral (admin)mdns, worker, central
pools, pool_attributes, pool_ns_records, pool_nameservers, pool_targets, pool_target_masters, pool_target_options, pool_also_notifiesthe pool model loaded from the pools YAML file (designate-manage pool update)designate-manage, centralcentral, worker, mdns
quotasper-project overrides (tenant_id, resource, hard_limit)centralcentral
zone_transfer_requests, zone_transfer_acceptsownership transfers (key, target_tenant_id, status)centralcentral
zone_taskszone imports and exports (task_type, status, message, location)central, workercentral
service_statusesheartbeats (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: @transaction wraps central methods in one write session; nested decorated calls join it (designate/storage/__init__.py:107). Retries on DBDeadlock re-run the whole method (designate/storage/__init__.py:39).
  • Optimistic version: every UPDATE sets version = 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, plus shared_zones.target_project_id = :project where shares apply, unless all_tenants (designate/storage/sqlalchemy/base.py:165).
  • Criterion mini-language: string values starting with !, <, <=, >, >=, BETWEEN a,b, or containing % (LIKE); lists become IN (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 ⟕ records with a total_count sub-query, optionally FORCE INDEX on 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 locks zone-<id> and create-new-zone (designate/common/decorators/lock.py:85); the producer for the producer group and shard partitioning (designate/coordination.py:104). Without it, locks are local file locks and every producer owns every shard.

DNS protocol

ExchangeDirectionTransportWhenDefined at
NOTIFY (opcode 4, SOA question) for a zoneworker → each pool target host:port option, and each also_notifies entryUDP (TCP with [service:worker]/all_tcp), timeout 10 s, TSIG of the target when setafter each backend create/updatedesignate/dnsutils.py:231, designate/worker/tasks/zone.py:184
SOA query for a zoneworker → each pool nameserverUDP/TCP, TSIG of the nameserver when setpolling after each changedesignate/dnsutils.py:272
AXFR (IXFR answered as AXFR)pool DNS servers → mdns :5354TCP, optional TSIG (key = scope)after NOTIFY, on SOA refreshdesignate/mdns/handler.py:241
QUERY (any type)anyone → mdnsUDP/TCPdebugging, some backendsdesignate/mdns/handler.py:371
NOTIFYexternal masters → mdnsUDP/TCPSECONDARY zone changed on its masterdesignate/mdns/handler.py:91
SOA query, AXFRworker → masters of SECONDARY zonesUDP/TCP, xfr_timeout 10 ssecondary zone creation, refresh, NOTIFYdesignate/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.

DriverProtocolcreate_zonedelete_zoneDefined at
bind9rndc 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
pdns4PowerDNS HTTP API, header X-API-KeyPOST /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
infobloxInfoblox WAPI (infoblox-client)secondary zone with the mdns masters in a DNS viewdelete the zonedesignate/backend/impl_infoblox.py:85
nsd4NSD control protocol over TLSaddzonedelzonedesignate/backend/impl_nsd4.py:149
akamai_v2, ns1, dynectprovider REST APIssecondary zone pointing to mdnsdeletedesignate/backend/impl_akamai_v2.py:183, designate/backend/impl_ns1.py:76, designate/backend/impl_dynect.py:275
designateanother Designate (designateclient)zones.create(type=SECONDARY, masters=mdns)zones.deletedesignate/backend/impl_designate.py:78
fakenoneno-opno-opdesignate/backend/impl_fake.py:24

Outbound REST calls

Target serviceMethod + pathWhenAuthenticationTimeout / retryErrors handledDefined at
KeystoneGET /v3/auth/tokens (keystonemiddleware)every API request (cached)service user [keystone_authtoken]keystonemiddleware defaults401etc/designate/api-paste.ini:52
KeystoneGET /v3/projects/{id}share a zone; PATCH /v2/quotas/{project} when quotas_verify_project_idthe user's token (context.get_auth_plugin())[keystone] adapter options404 → InvalidProject 400; 403 or connection error → accepteddesignate/common/keystone.py:26
NeutronGET /v2.0/floatingips?project_id=<project> (openstacksdk network.ips) per regionevery /v2/reverse/floatingips callthe user's token on the catalog endpoint, or [network_api:neutron]/endpoints[network_api:neutron]/timeout, 5 regions in parallelany exception → NeutronCommunicationFailure 504designate/network_api/neutron.py:60

Other

  • Binaries: rndc through designate-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 template designate/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.