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

Reverse-spec · Clients

python-ironicclient — Interactions

Interactions with other components — python-ironicclient — branch stable/2026.2

Branch stable/2026.2 @ af2ceeb9c4f2 — repository https://opendev.org/openstack/python-ironicclient — analysed on 2026-11-01

A command-line process with no database, no bus and no server. [observed] means seen against a local stub, not against Ironic.

1. Summary

SystemProtocolDirectionLibraryPurpose
KeystoneHTTPSoutkeystoneauth1token and catalog. Not used by the baremetal shell with --os-auth-type none
IronicHTTPS, JSONout, synchronousironicclient/common/http.py:368 on keystoneauth1every command
Cache filedbmlocaldogpile.cachenegotiated version
Files, stdininironicclient/common/utils.py:449, ironicclient/v1/create_resources.py:86, ironicclient/v1/node.py:789JSON arguments, resource files, config drive
Files, stdoutoutironicclient/osc/v1/baremetal_node.py:2604inventory
ISO toolschild processlocalironicclient/common/utils.py:282config drive from a directory

2. Requests to Ironic

Base URL: the catalog endpoint for type baremetal, with a trailing /v1 and slashes removed (ironicclient/common/http.py:63, ironicclient/common/http.py:384). Paths start with /v1/ (ironicclient/common/base.py:73). Identifiers and query values go into the URL as they are.

Headers, as received by the stub [observed in _rewrite/scenarios_stable-2026.2.json]:

KindX-OpenStack-Ironic-API-VersionAcceptContent-TypeUser-AgentX-Auth-Token
JSON call (ironicclient/common/http.py:494), even with no bodyyesapplication/jsonapplication/jsonpython-ironicclientyes
DELETE (ironicclient/common/http.py:524)yes*/*application/octet-streamsameyes
GET /v1 of negotiation (ironicclient/common/http.py:421)no*/*nonesameyes

X-OpenStack-Request-ID is added if global_request_id is set, in the Python API (ironicclient/common/http.py:453). noauth sends a dummy token, seen as a header being present in legacy-noauth-endpoint.

Frames [observed]:

PUT /baremetal/v1/nodes/x/states/provision HTTP/1.1
X-OpenStack-Ironic-API-Version: 1.115
Accept: application/json
Content-Type: application/json
User-Agent: python-ironicclient
X-Auth-Token: …

{"target": "manage"}
PATCH /baremetal/v1/allocations/x HTTP/1.1

[{"op": "add", "path": "/name", "value": "y"}]
GET /baremetal/v1/nodes/?description_contains=a&retired=true%20b HTTP/1.1

Slash before the query: with no filter the path is /v1/nodes; with a filter it is /v1/nodes/?… (ironicclient/common/base.py:73). On a next link, the client uses the path the server gave [observed page-limit-2]. A service must accept both.

Response handling (ironicclient/common/http.py:505): 204 and 205, or no Content-Type, give an empty list. A JSON content type is decoded. Anything else gives no body, so a list or get then fails with API response body must be a JSON object [observed shape-show-text-body].

Body keys read, by command type:

AnswerRead asMissing keySource
listbody[<collection>], and next if a limit was givenempty list, exit 0 [observed shape-list-without-key]ironicclient/common/base.py:147
single resourcethe whole object; {} means nonecrash in the command, see 02 § 5ironicclient/common/base.py:357
PATCH, PUT, POSTany non-empty body becomes a resourceNoneironicclient/common/base.py:429, ironicclient/common/base.py:506

3. Version negotiation

Only triggered by a 406 (or by latest on the baremetal shell and the Python API). There is no discovery call otherwise [observed neg-default-no-406: one request].

  1. Request with 1.115. Server answers 406.
  2. GET /v1, because the 406 is false as a boolean (ironicclient/common/http.py:182, requests:requests/models.py:835). A failure here raises (ironicclient/common/http.py:153) [observed neg-406-v1-500].
  3. Read X-OpenStack-Ironic-API-Minimum-Version and X-OpenStack-Ironic-API-Maximum-Version (ironicclient/common/http.py:298). Missing headers: a second GET /v1, then RuntimeError [observed neg-406-no-headers, 2 such requests].
  4. The maximum is capped at 1.115 (ironicclient/common/http.py:204).
  5. If the version was explicit, or was already negotiated: UnsupportedVersion [observed neg-explicit-too-high] (ironicclient/common/http.py:218).
  6. Otherwise pick min(requested, max), and no less than the minimum (ironicclient/common/http.py:243, ironicclient/common/http.py:277). Save it in the client and in the cache (ironicclient/common/http.py:290).
  7. Send the same request again with the new header [observed: 1.115, GET /v1, 1.90].

The state values are user, negotiated, cached and default (ironicclient/common/http.py:54). Once negotiated, any further 406 is final (ironicclient/common/http.py:230), for example a route that needs a higher version than the server's.

The Python API's latest and lists of versions negotiate before the first request (ironicclient/common/http.py:436, ironicclient/common/http.py:318) [observed for latest in legacy-latest].

4. Retries, timeouts, pagination

  • Retries: on Conflict, ServiceUnavailable, ConnectionRefused and keystoneauth's RetriableConnectionFailure (ironicclient/common/http.py:324). Fixed interval, no backoff, every method. Defaults are 5 and 2 s (ironicclient/common/http.py:57). The plugin gives no way to change them.
  • Interaction with negotiation: a 406 recursion goes through the retry wrapper again (ironicclient/common/http.py:468) [inferred from code].
  • Timeouts: the client sets none. The session's timeout applies if configured, for example --timeout of the shell or api_timeout, not followed here [inferred]. The timeout argument of _construct_http_client is ignored with a warning (ironicclient/common/http.py:553).
  • Pagination: see 01 § 1. The prefix of the endpoint is removed from next (ironicclient/common/base.py:278). An endless next would loop forever when no limit is reached [inferred].

5. Local files

FileWrittenReadNotesSource
<user cache dir>/python-ironicclient/ironic-api-version.dbm, key host:port, value the version, expiry 300 s or IRONICCLIENT_CACHE_EXPIRYafter negotiation, by both shells [observed: file present, under an empty HOME]only when the Python API gets no versiona second process still starts at 1.115 [observed neg-second-process-ignores-cache]ironicclient/common/filecache.py:34, ironicclient/v1/client.py:82
clouds.yaml, OS_*by the OSC shell or ironicclient/shell.py:113; not by get_clientnot exercisedironicclient/client.py:88
JSON or YAML argumentsany --steps-like option; a value that is an existing path wins over parsing as JSONironicclient/common/utils.py:403
Resource filesbaremetal create, .yaml or .json onlyironicclient/v1/create_resources.py:96
Config drivetemp files, removed after--config-driveironicclient/common/utils.py:272

Secrets: --rescue-password and values in --driver-info are visible in the process list. Debug output goes through keystoneauth's logging, not reviewed here [inferred]. SENSITIVE_HEADERS is defined at ironicclient/common/http.py:59, and no other module of the package refers to it (grep).

6. Local binary

--config-drive <directory> runs the first available of genisoimage, mkisofs and xorrisofs with -o <temp> -ldots -allow-lowercase -allow-multidot -l -publisher ironicclient-configdrive 0.1 -quiet -J -r -V config-2 <dir>, then gzips and base64-encodes the result (ironicclient/common/utils.py:282, ironicclient/common/utils.py:311). Not run in this analysis, since it executes a local binary.

Source: reverse-spec-python-ironicclient/03-interactions_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.