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

Reverse-spec · Services

designate — Scenarios

End-to-end scenarios — designate — branch stable/2026.2

Branch stable/2026.2 @ e3dd774db145 — repository https://opendev.org/openstack/designate — analysed on 2026-10-29

The scenarios assume one pool whose targets are PowerDNS servers (pdns4 driver) configured as secondaries of designate-mdns, and a tooz backend. Statuses and errors refer to errors; triggers to async processing.

1. Creating a primary zone

The API answers 202 as soon as the zone, its SOA and its NS recordsets are in the database. The zone becomes ACTIVE only when every pool nameserver serves its serial.

sequenceDiagram
  autonumber
  actor U as User
  participant API as designate-api
  participant C as designate-central
  participant DB as MariaDB
  participant W as designate-worker
  participant P as PowerDNS (pool target)
  participant M as designate-mdns
  U->>API: POST /v2/zones {"name":"example.org.","email":"…"}
  API->>C: AMQP call create_zone(zone)
  C->>C: policy create_zone, quota, name/TLD/blacklist, ownership, scheduler → pool
  alt invalid name, quota, collision
    C-->>API: InvalidZoneName 400 / OverQuota 413 / DuplicateZone 409 / IllegalChildZone 403
    API-->>U: error JSON
  end
  C->>DB: INSERT zones (CREATE, PENDING), recordsets/records SOA + NS (managed)
  C->>W: AMQP cast create_zone(zone)
  C-->>API: zone
  API-->>U: 202 {status: PENDING, action: CREATE}
  W->>P: POST /api/v1/servers/localhost/zones {kind: slave, masters: [mdns:5354]}
  W->>P: DNS NOTIFY example.org. SOA
  P->>M: DNS SOA query, then AXFR example.org. (TCP 5354)
  M->>DB: SELECT recordsets/records of the zone
  M-->>P: SOA, NS, …, SOA
  W->>W: sleep poll_delay (5 s)
  loop until every nameserver serves serial ≥ zone.serial (≤ 10 tries, 15 s apart)
    W->>P: DNS SOA query example.org.
  end
  alt threshold met
    W->>C: AMQP cast update_status(SUCCESS, serial, CREATE)
    C->>DB: UPDATE zones/records SET action=NONE, status=ACTIVE
  else backend error or nameservers late
    W->>C: AMQP cast update_status(ERROR or NO_ZONE)
    C->>DB: UPDATE zones SET status=ERROR
  end
  U->>API: GET /v2/zones/{id} (polling)

Diagnostics: NoServersConfigured (500) means designate-manage pool update was not run (step 3). A zone stuck in PENDING with no worker log means the cast was lost: worker_periodic_recovery re-sends it after 455 s. A zone in ERROR right after creation points to the backend (step 8, worker log Failed to CREATE zone_name=…); in ERROR after a few minutes, to the AXFR between PowerDNS and mdns (mdns log Unknown TSIG key, firewall on 5354) or to the NOTIFY.

2. Adding a recordset (serial batching)

sequenceDiagram
  autonumber
  actor U as User
  participant API as designate-api
  participant C as designate-central
  participant DB as MariaDB
  participant PR as designate-producer
  participant W as designate-worker
  participant P as DNS servers
  U->>API: POST /v2/zones/{id}/recordsets {"name":"www.example.org.","type":"A","records":["192.0.2.1"]}
  API->>C: call create_recordset
  C->>C: lock zone-<id>; policy create_recordset; quotas; placement and record checks
  C->>DB: INSERT recordsets, records (CREATE, PENDING, serial=now)
  C->>DB: UPDATE zones SET action=UPDATE, status=PENDING, increment_serial=1
  API-->>U: 202 {status: PENDING}
  Note over PR: every 5 s (increment_serial task, own shard range)
  PR->>C: call find_zones(increment_serial=1, shard BETWEEN …)
  PR->>C: call increment_zone_serial(zone)
  C->>DB: UPDATE zones SET serial=<unix time>, increment_serial=0; UPDATE SOA record
  PR->>W: cast update_zone(zone)
  W->>P: backend update (no-op for pdns4) + DNS NOTIFY
  P->>P: AXFR from mdns
  W->>P: SOA polling
  W->>C: cast update_status(SUCCESS, serial, UPDATE)
  C->>DB: UPDATE records SET status=ACTIVE WHERE serial <= reported serial

Diagnostics: changes made within the same 5 s window share one serial and one push. If the recordset stays PENDING, check that a producer owns the zone's shard (GET /v2/service_statuses, tooz group) — without any producer, serial increments never happen and nothing is pushed.

3. Deleting a zone

sequenceDiagram
  autonumber
  actor U as User
  participant API as designate-api
  participant C as designate-central
  participant DB as MariaDB
  participant W as designate-worker
  participant P as DNS servers
  participant PR as designate-producer
  U->>API: DELETE /v2/zones/{id}
  API->>C: call delete_zone
  alt sub-zones exist / zone shared
    C-->>U: 400 zone_has_sub_zone / zone_is_shared
  end
  C->>DB: UPDATE zones SET action=DELETE, status=PENDING
  C->>W: cast delete_zone(zone, hard_delete)
  API-->>U: 202 {action: DELETE, status: PENDING}
  W->>P: DELETE /api/v1/servers/localhost/zones/example.org.
  loop until no nameserver answers for the zone (serial 0)
    W->>P: DNS SOA query
  end
  W->>C: cast update_status(SUCCESS, 0, DELETE)
  C->>DB: UPDATE zones SET status=DELETED, deleted=<id>, deleted_at=now
  U->>API: GET /v2/zones/{id} → 404
  Note over PR: 7 days later, zone_purge
  PR->>C: call purge_zones(deleted != 0, deleted_at <= now-604800)
  C->>DB: DELETE FROM zones (re-parent children)

Diagnostics: a DNS server that answers with recursion for a deleted zone used to keep the zone in PENDING/ERROR; since 26d9b96afaa7 a non-authoritative answer with RA counts as "no zone" (history). Heat and Neutron consider a zone deleted on the 404 (step 13).

4. Secondary zone and NOTIFY from an external master

sequenceDiagram
  autonumber
  actor U as User
  participant API as designate-api
  participant C as designate-central
  participant W as designate-worker
  participant MA as External master
  participant M as designate-mdns
  participant P as DNS servers
  U->>API: POST /v2/zones {"name":"sec.example.","type":"SECONDARY","masters":["203.0.113.5"]}
  API->>C: call create_zone (serial = 1, email = managed_resource_email)
  C->>W: cast create_zone
  C->>W: cast perform_zone_xfr
  W->>MA: DNS AXFR sec.example. (xfr_timeout 10 s)
  W->>C: call update_zone(zone with recordsets, increment_serial=False)
  W->>P: create slave zone + NOTIFY; P AXFRs from mdns
  MA->>M: DNS NOTIFY sec.example.
  M->>M: zone found, sender is a master
  M->>MA: DNS SOA query
  alt master serial != stored serial
    M->>W: cast perform_zone_xfr(zone, [master])
  end
  M-->>MA: NOTIFY response (AA)

Diagnostics: NOTIFY from another address is refused (mdns log NOTIFY for … from non-master server). Without NOTIFY, the producer's periodic_secondary_refresh checks the master at most hourly.

5. Floating IP PTR record (Skyline "Reverse DNS", openstack ptr record set)

sequenceDiagram
  autonumber
  actor U as User
  participant API as designate-api
  participant C as designate-central
  participant N as Neutron
  participant DB as MariaDB
  participant W as designate-worker
  U->>API: PATCH /v2/reverse/floatingips/RegionOne:{fip} {"ptrdname":"host.example.org."}
  API->>C: call update_floatingip
  C->>C: policy set_floatingip_reverse
  C->>N: GET /v2.0/floatingips?project_id=<project> (user token)
  alt FIP not in the list
    C-->>U: 404 FloatingIP … is not associated for project
  end
  C->>DB: find zone 2.0.192.in-addr.arpa. owned by managed_resource_tenant_id
  opt missing
    C->>C: create_zone (managed tenant, managed_resource_email) → worker push
  end
  C->>DB: create or update managed PTR recordset 10.2.0.192.in-addr.arpa. (managed_resource_type ptr:floatingip)
  C->>W: (via the zone update) push
  API-->>U: 202 {ptrdname, status: PENDING}

Diagnostics: 409 duplicate_zone means another project owns a zone with the reverse zone's name (protection added in e3dd774db145). 504 neutron_communication_failure means Neutron failed or no endpoint was found.

6. Neutron port or floating IP with a DNS name (Neutron's external DNS driver)

Neutron's Designate driver calls the API with the user's token for the forward record and with its own admin credentials (edit_managed) for the PTR (neutron:neutron/services/externaldns/drivers/designate/driver.py:38).

sequenceDiagram
  autonumber
  participant N as neutron-server
  participant API as designate-api
  N->>API: GET /v2/zones?name=example.org. (user token, resolve by name)
  N->>API: POST /v2/zones/{id}/recordsets {name: "vm1.example.org.", type: A, records: [IP]}
  alt 404
    API-->>N: DNSDomainNotFound
  else 409
    API-->>N: DuplicateRecordSet
  else 413
    API-->>N: ExternalDNSOverQuota
  end
  opt allow_reverse_dns_lookup
    N->>API: GET /v2/zones?name=<in-addr zone> (admin client)
    N->>API: POST /v2/zones/{id}/recordsets PTR (admin, X-Designate-Edit-Managed-Records)
    alt 404: zone missing
      N->>API: POST /v2/zones {name: <in-addr zone>, email: admin@<domain>} (409 ignored)
      N->>API: POST …/recordsets PTR
    end
  end

The sink path (Nova compute.instance.create.end, Neutron floatingip.update.end → create_managed_records) is the alternative integration; it does not involve the API (async).

7. Zone import

sequenceDiagram
  autonumber
  actor U as User
  participant API as designate-api
  participant C as designate-central
  participant T as central import thread
  U->>API: POST /v2/zones/tasks/imports (Content-Type text/dns, zone file)
  API->>C: call create_zone_import(body)
  C->>C: INSERT zone_tasks (IMPORT, PENDING); start thread
  API-->>U: 202 {status: PENDING}
  T->>T: dnspython parse ($ORIGIN, SOA required)
  alt parse error
    T->>C: update_zone_import(ERROR, "Malformed zonefile.")
  else ok
    T->>C: create_zone(zone + recordsets) — same path as scenario 1
    T->>C: update_zone_import(COMPLETE, zone_id)
  end
  U->>API: GET /v2/zones/tasks/imports/{id} (polling)

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