Interactions — python-designateclient — branch stable/2026.2
Branch
stable/2026.2@9231eef90192— repository https://opendev.org/openstack/python-designateclient — analysed on 2026-11-01
[observed] means seen on the local stub. The stub shows what the client sends, not what a service accepts.
Keystone
OSC authenticates, not the plugin. The plugin takes instance.session (designateclient/osc/plugin.py:39). The token comes once per process, when the first request needs it [observed: tokens_requested is 1 in the scenarios]. On a 401, keystoneauth invalidates the token, fetches a new one and retries once (keystoneauth1:keystoneauth1/session.py:1130) [observed: 401]. The quota commands also read the project of the token: client.session.get_project_id() (designateclient/v2/cli/quotas.py:56).
Endpoint discovery
The adapter is built with service_type='dns', interface, region_name and version='2' (designateclient/v2/client.py:131). keystoneauth therefore looks up the dns catalog entry, then reads the version document.
| Situation | Result [observed] | Scenario |
|---|---|---|
catalog /dns; root answers the list of Designate (v2, v2.0 to v2.4, all linking to /dns/v2) | requests go to /dns/v2/... | discovery-real-shape |
root lists only v2 | same | discovery-only-v2 |
root uses the Keystone wrapper {"versions": {"values": [...]}} | same | discovery-values-wrapper |
| root answers 404 | warning Failed to contact the endpoint ... Fallback to using that endpoint as the base url.; requests go to /dns/zones, **without /v2** | discovery-root-404 |
root lists only v3 | Could not find requested endpoint in Service Catalog., exit 1 | discovery-only-v3 |
catalog /dns/v2 | no discovery request; requests go to /dns/v2/..., even if the root answers 404 | discovery-catalog-v2-url, discovery-catalog-v2-url-root-404 |
no dns entry | public endpoint for dns service in RegionOne region not found, exit 1 | no-dns-in-catalog |
The warning is in keystoneauth1:keystoneauth1/discover.py:1500. The real document was read in designate:designate/api/versions.py:37.
Designate: request format
| Element | Value | Source |
|---|---|---|
| Transport | keystoneauth LegacyJsonAdapter, raise_exc=False, no retry added by the client | designateclient/v2/client.py:40, designateclient/v2/client.py:58 |
Accept | application/json, except text/dns on zone export showfile | keystoneauth1:keystoneauth1/adapter.py:671, designateclient/v2/zones.py:175 |
Content-Type | application/json on every request, GET and DELETE included, except text/dns on a plain import | designateclient/v2/client.py:89, designateclient/v2/zones.py:188 |
User-Agent | python-designateclient-7.0.1 in this run [observed] | designateclient/v2/client.py:137 |
X-Auth-Token | the token, through keystoneauth [observed] | |
| Body | jsonutils.dumps, unless the content type is text/dns | designateclient/client.py:46 |
| Query string | urlencode of the criterion; * goes as %2A [observed: pagination-two-pages] | designateclient/client.py:32 |
build_url always appends ?, even with no parameter (designateclient/client.py:41). It does not reach the wire: the stub logs /dns/v2/zones [observed].
Optional headers
| Header | Value | Set by | Read in Designate at |
|---|---|---|---|
X-Auth-All-Projects | True | --all-projects, or implicitly by quota commands | designate:designate/api/middleware.py:65 |
X-Auth-Sudo-Project-ID | project ID | --sudo-project-id | designate:designate/api/middleware.py:57 |
X-Designate-Edit-Managed-Records | True | --edit-managed | designate:designate/api/middleware.py:87 |
X-Designate-Hard-Delete | True | --hard-delete | designate:designate/api/middleware.py:95 |
X-Designate-Delete-Shares | true | --delete-shares | designate:designate/api/middleware.py:108 |
Python str(True) gives the capital.
Routes
The full list is in [api-surface_stable-2026.2.md](_rewrite/api-surface_stable-2026.2.md), per command in 01 § 5.
| Family | Base route | Verbs | Notes |
|---|---|---|---|
| Zones | /zones, /zones/{id} | GET, POST, PATCH, DELETE | |
| Zone tasks | /zones/{id}/tasks/abandon, xfr, pool_move, export, transfer_requests | POST | |
| Name servers | /zones/{id}/nameservers | GET | |
| Shares | /zones/{id}/shares, /zones/{id}/shares/{id} | GET, POST, DELETE | |
| Recordsets | /zones/{id}/recordsets, /recordsets | GET, POST, PUT, DELETE | |
| Transfers | /zones/tasks/transfer_requests, /zones/tasks/transfer_accepts | GET, POST, PATCH, DELETE | |
| Exports | /zones/tasks/exports, .../{id}/export | GET, DELETE | |
| Imports | /zones/tasks/imports | GET, POST, DELETE | |
| Floating IPs | /reverse/floatingips, /reverse/floatingips/{region}:{id} | GET, PATCH | |
| Administration | /blacklists, /tlds, /tsigkeys, /quotas/{project}, /limits, /service_statuses | various |
Filters per list command
Read in Designate, a filter outside the accepted list raises 400 Invalid filters (designate:designate/api/v2/controllers/rest.py:73).
| Command | Filters sent | Accepted by Designate, by reading |
|---|---|---|
zone list | type, name, ttl, description, email, status | all (designate:designate/api/v2/controllers/zones/__init__.py:70) |
recordset list | type, name, data, ttl, description, status, **action** | all except action (designate:designate/api/v2/controllers/common.py:25) |
tld list | name | yes (designate:designate/api/v2/controllers/tlds.py:54) |
tsigkey list | name, algorithm, scope | yes (designate:designate/api/v2/controllers/tsigkeys.py:55) |
zone blacklist list | pattern | yes (designate:designate/api/v2/controllers/blacklists.py:56) |
dns service list | hostname, service_name, status | yes (designate:designate/api/v2/controllers/service_status.py:34) |
zone export list | status, zone_id | yes (designate:designate/api/v2/controllers/zones/tasks/exports.py:102) |
zone import list | status, zone_id, message | yes (designate:designate/api/v2/controllers/zones/tasks/imports.py:58) |
zone transfer request list | status | yes (designate:designate/api/v2/controllers/zones/tasks/transfer_requests.py:60) |
zone transfer accept list | status | yes (designate:designate/api/v2/controllers/zones/tasks/transfer_accepts.py:59) |
zone share list | target_project_id | yes (designate:designate/api/v2/controllers/zones/sharedzones.py:58) |
Sample exchanges [observed, stub answers]
$ openstack recordset create 11111111-2222-4333-8444-555555555555 www --type A --record 192.0.2.1 --record 192.0.2.2 --ttl 60 --description d
GET /dns/v2/zones/11111111-2222-4333-8444-555555555555
POST /dns/v2/zones/11111111-2222-4333-8444-555555555555/recordsets
Content-Type: application/json
{"name": "www.example.org.", "type": "A", "records": ["192.0.2.1", "192.0.2.2"], "ttl": 60, "description": "d"}
$ openstack zone import create zone.file --attributes k:v
POST /dns/v2/zones/tasks/imports
Content-Type: application/json
{"zonefile": "$ORIGIN example.org.\n", "attributes": {"k": "v"}}
Local files
zone import create opens the given path in text mode with the default encoding (designateclient/v2/cli/zones.py:736). Nothing else touches the disk. No command runs a binary or reads a terminal; grep on subprocess, getpass and input( outside tests finds nothing.
Python API users
Read in the local clones, not run. They rely on the exception classes and the name resolution of the controllers.
| User | Calls | Relies on | Source |
|---|---|---|---|
| Neutron external DNS driver | Client(session=, auth=), endpoint_override=, edit_managed=True, all_projects=True; recordsets.create, recordsets.delete, recordsets.list, zones.create | NotFound, Conflict, OverQuota, BadRequest, Forbidden; a zone given by name | neutron:neutron/services/externaldns/drivers/designate/driver.py:46, neutron:neutron/services/externaldns/drivers/designate/driver.py:104, neutron:neutron/services/externaldns/drivers/designate/driver.py:177 |
| Heat | client.Client(version='2', session=, endpoint_type=, service_type=, region_name=); zones.*, recordsets.* | NotFound (is_not_found) | heat:heat/engine/clients/os/designate.py:32, heat:heat/engine/resources/openstack/designate/zone.py:160, heat:heat/engine/resources/openstack/designate/recordset.py:105 |
Neutron's edit_managed=True is set at construction, so Client keeps it for all calls; only the commands reset these values.
Not used
Database, AMQP, Redis, gRPC, subprocess: none. In the inventory, the db occurrences are false positives on client.session.delete (designateclient/client.py:84), the config occurrences are the options of the functional tests (designateclient/functionaltests/config.py:21), and the async occurrence is the pagination loop.
Source: reverse-spec-python-designateclient/03-interactions_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.