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

Reverse-spec · Clients

python-heatclient — Async processing

Processing outside commands — python-heatclient — branch stable/2026.2

Branch stable/2026.2 @ 88120f11830d (5.3.0-2-g88120f1) — repository https://opendev.org/openstack/python-heatclient — analysed on 2026-11-01

There is no background processing: no bus consumer, no timer, no event listener, no thread. The inventory found 0 triggers and 0 RPC calls. The process ends with its command. What remains is client-side waiting inside a command, by HTTP polling. All of it talks to Heat, and Heat does the asynchronous work.

ProcessTriggerTypePurposeReadsWrites / emitsDefined at
openstack--wait on stack create, adopt, update, delete, suspend, resume, check, cancelHTTP polling, 5 s by default; --poll exists on stack create onlywait for a final stack statusGET /stacks/{name}/eventsevent log on stdoutheatclient/common/event_utils.py:179
openstackstack event list --followHTTP polling, fixed 5 s, never endsfollow the event logsameevent log on stdoutheatclient/osc/v1/event.py:193-217
heat--poll on legacy commandssame functionsameeventsevent logheatclient/v1/shell.py:143
nonewait_for_events, on a websocket given by the callernot pollingPython API helper; no caller in the package outside testswebsocket messagesevent logheatclient/common/event_utils.py:243

Client-side wait: poll_for_events

  • Trigger: --wait. Called at heatclient/osc/v1/stack.py:207, heatclient/osc/v1/stack.py:408, heatclient/osc/v1/stack.py:802, heatclient/osc/v1/stack.py:893, heatclient/osc/v1/stack.py:1198.
  • Emitter: none. Heat moves the stack; the client watches.
  • Purpose: turn an accepted asynchronous operation into a blocking command.
  • Data read: GET /stacks/{name}/events?marker=<last id>&sort_dir=asc [observed, scenarios G02, G05].
  • Flow (heatclient/common/event_utils.py:205-240):
    1. stack update, delete and the action commands first read the last event, with limit=1&sort_dir=desc, to use its ID as the starting marker (heatclient/osc/v1/stack.py:395-399). stack create and adopt start with no marker.
    2. Each round fetches the events after the marker and prints them in log format.
    3. An event is a stack event when its resource_name equals the name given to the function, and its physical_resource_id equals the last segment of its stack link, or the link is absent (heatclient/common/event_utils.py:195-203).
    4. A stack event with a final status ends the loop. With an action, final means <ACTION>_COMPLETE or <ACTION>_FAILED. Without one (stack cancel), any *_COMPLETE or *_FAILED (heatclient/common/event_utils.py:183-187).
    5. After 2 rounds with no event, it reads the stack (GET /stacks/{name}?resolve_outputs=False) and checks stack_status the same way (heatclient/common/event_utils.py:229-238) [observed, scenario G04].
    6. It sleeps poll_period between rounds. The sleep is skipped when the round ends the loop.
  • Concurrency and idempotency: polling is read-only. Interrupting the command does not cancel the operation on Heat [inferred].
  • Error behaviour:
    • A 404 on events becomes CommandError with Heat's message (heatclient/common/event_utils.py:167-170).
    • stack delete --wait treats that CommandError as "the stack is gone", which is how the wait normally ends (heatclient/osc/v1/stack.py:804-805) [observed, scenario G08].
    • A 404 on the fallback GET is not converted, so stack delete --wait then reports a failure although the stack is gone (defect 8 in 02) [observed, scenario D09].
    • A final *_FAILED matching the action gives CommandError and exit 1 (heatclient/osc/v1/stack.py:210-211) [observed, scenario G03].
    • Other HTTP errors and connection errors end the command, while the operation continues on Heat [inferred].
  • No time limit: the loop has no deadline and no retry count. A stack stuck in progress holds the command until it is interrupted.
  • Name matching: stack delete --wait passes what the user typed. With an ID, no stack event matches, because resource_name holds the name. The final status then comes only from the 404 or from the fallback after 2 empty rounds [inferred from heatclient/common/event_utils.py:196; not run with an ID].
  • Cost: one request per stack every 5 seconds, plus a lookup redirect for each fallback.

stack event list --follow

  • Flow (heatclient/osc/v1/event.py:193-217): refused unless the format is log; then get events after the marker, print them, sleep a fixed 5 s, forever.
  • Exit: only on KeyboardInterrupt or EOFError, with exit 0 [observed with a fake clock, scenario G15], or on an error from Heat.

Client-side recursion, not background work

stack event list --nested-depth N and stack failures list walk nested stacks inside the command, with sequential requests:

  • Events: when the first event has no root_stack link, the client assumes the server ignored nested_depth and walks resources and events itself (heatclient/common/event_utils.py:86-120) [observed, scenarios G13, G14]. Marker and limit are then applied on the client.
  • Failures: one GET /stacks/{id}/resources per failed nested stack, plus one GET /software_deployments/{id} per failed deployment resource (heatclient/osc/v1/stack_failures.py:49-108) [observed, scenario G16].

Source: reverse-spec-python-heatclient/05-async-processing_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.