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
| System | Protocol | Direction | Library | Purpose |
|---|---|---|---|---|
| Keystone | HTTPS | out | keystoneauth1 | token and catalog. Not used by the baremetal shell with --os-auth-type none |
| Ironic | HTTPS, JSON | out, synchronous | ironicclient/common/http.py:368 on keystoneauth1 | every command |
| Cache file | dbm | local | dogpile.cache | negotiated version |
| Files, stdin | in | ironicclient/common/utils.py:449, ironicclient/v1/create_resources.py:86, ironicclient/v1/node.py:789 | JSON arguments, resource files, config drive | |
| Files, stdout | out | ironicclient/osc/v1/baremetal_node.py:2604 | inventory | |
| ISO tools | child process | local | ironicclient/common/utils.py:282 | config 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]:
| Kind | X-OpenStack-Ironic-API-Version | Accept | Content-Type | User-Agent | X-Auth-Token |
|---|---|---|---|---|---|
JSON call (ironicclient/common/http.py:494), even with no body | yes | application/json | application/json | python-ironicclient | yes |
DELETE (ironicclient/common/http.py:524) | yes | */* | application/octet-stream | same | yes |
GET /v1 of negotiation (ironicclient/common/http.py:421) | no | */* | none | same | yes |
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:
| Answer | Read as | Missing key | Source |
|---|---|---|---|
| list | body[<collection>], and next if a limit was given | empty list, exit 0 [observed shape-list-without-key] | ironicclient/common/base.py:147 |
| single resource | the whole object; {} means none | crash in the command, see 02 § 5 | ironicclient/common/base.py:357 |
PATCH, PUT, POST | any non-empty body becomes a resource | None | ironicclient/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].
- Request with
1.115. Server answers 406. 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) [observedneg-406-v1-500].- Read
X-OpenStack-Ironic-API-Minimum-VersionandX-OpenStack-Ironic-API-Maximum-Version(ironicclient/common/http.py:298). Missing headers: a secondGET /v1, thenRuntimeError[observedneg-406-no-headers, 2 such requests]. - The maximum is capped at 1.115 (
ironicclient/common/http.py:204). - If the version was explicit, or was already negotiated:
UnsupportedVersion[observedneg-explicit-too-high] (ironicclient/common/http.py:218). - 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). - 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,ConnectionRefusedand keystoneauth'sRetriableConnectionFailure(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
--timeoutof the shell orapi_timeout, not followed here [inferred]. Thetimeoutargument of_construct_http_clientis 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 endlessnextwould loop forever when no limit is reached [inferred].
5. Local files
| File | Written | Read | Notes | Source |
|---|---|---|---|---|
<user cache dir>/python-ironicclient/ironic-api-version.dbm, key host:port, value the version, expiry 300 s or IRONICCLIENT_CACHE_EXPIRY | after negotiation, by both shells [observed: file present, under an empty HOME] | only when the Python API gets no version | a 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_client | not exercised | ironicclient/client.py:88 | |
| JSON or YAML arguments | any --steps-like option; a value that is an existing path wins over parsing as JSON | ironicclient/common/utils.py:403 | ||
| Resource files | baremetal create, .yaml or .json only | ironicclient/v1/create_resources.py:96 | ||
| Config drive | temp files, removed after | --config-drive | ironicclient/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.