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

Reverse-spec · Services

heat — Interactions

Interactions — heat — branch stable/2026.2

Branch stable/2026.2 @ 5872396e548c (27.0.1) — repository https://opendev.org/openstack/heat — analysed on 2026-10-29

SystemProtocolLibraryConfigurationUsed by
RabbitMQ (or any oslo.messaging driver)AMQP 0-9-1 RPC + notificationsoslo.messaging (heat/common/messaging.py)[DEFAULT]/transport_url, [oslo_messaging_*], rpc_response_timeoutheat-api, heat-api-cfn → heat-engine; heat-engine ↔ heat-engine
MariaDB / MySQL / PostgreSQLSQLoslo.db + SQLAlchemy 2 (heat/db/api.py)[database]/connection, db_max_retries…heat-engine, heat-manage (the APIs never open the database)
KeystoneRESTkeystonemiddleware, keystoneauth1, python-keystoneclient (heat/engine/clients/os/keystone/heat_keystoneclient.py)[keystone_authtoken], [trustee], [clients_keystone], [ec2authtoken], stack_user_domain_*, stack_domain_admin*all
Other OpenStack servicesRESTone client plugin per service (setup.cfg heat.clients, heat/engine/clients/os/*.py)[clients], [clients_<service>], region_name_for_servicesheat-engine (resource plugins)
Swift (signals, files, metadata)REST / TempURLpython-swiftclientcatalogheat-engine
Any HTTP(S) serverHTTP GETrequests (heat/common/urlfetch.py:38-84)template_fetch_timeout, max_template_sizeheat-api (template_url), heat-engine (get_file/type: http://… template resources)
Memcached / Rediscache protocoloslo.cache[cache], [constraint_validation_cache], [service_extension_cache], [resource_finder_cache]heat-engine (constraint and lookup caches, disabled unless [cache]/enabled)

1. RabbitMQ

1.1 Topology

Transport defaults set the control exchange to **heat** (heat/common/messaging.py:83). All RPC uses the oslo.messaging topic exchange; the JSON payload serialiser wraps a context serialiser that sends RequestContext.to_dict() (heat/common/messaging.py:29-61, heat/common/context.py:140-154). Remote exceptions are re-raised on the caller side only for classes of heat.common.exception (allowed_remote_exmods, heat/common/messaging.py:84); anything else arrives as RemoteError.

TopicServerConsumersProducersVersion (server)Pattern
engine[DEFAULT]/host of each engine process; clients send without server → any engineEngineService (heat/engine/service.py:462-468)heat-api, heat-api-cfn, heat-engine (nested stacks)1.36call (one cast variant, never used)
engine_workerengine idWorkerService (heat/engine/worker.py:85-97)heat-engine1.9cast (to any engine, or to one engine for cancel_check_resource)
heat-engine-listenerengine idEngineListener (heat/engine/service.py:369-374)heat-engine1.0call addressed to one engine

Notifications are sent on the notification transport with publisher_id = orchestration.<host> (or [DEFAULT]/default_publisher_id) and event_type = orchestration.<…> (heat/engine/notification/__init__.py, notify).

1.2 RPC methods (API → engine, topic engine)

Generated from [_rewrite/rpc-contract_stable-2026.2.json](_rewrite/rpc-contract_stable-2026.2.json) (_rewrite/tools/heat_rpc.py). Every server method except get_revision, list_resource_types, list_template_versions, list_template_functions, resource_schema and generate_template is wrapped by @context.request_context, which turns a HeatException into an expected remote error, i.e. no server-side traceback log (heat/common/context.py:362-369). The reply is the method's return value, JSON-serialised.

MethodModeVersionArgumentsTimeoutClientServer
abandon_stackcall1.0stack_identityrpc_response_timeoutheat/rpc/client.py:467heat/engine/service.py:1618
authenticated_to_backendcall1.0—rpc_response_timeoutheat/rpc/client.py:409heat/engine/service.py:1391
check_software_deploymentcall1.27deployment_id, timeouttimeout argumentheat/rpc/client.py:725heat/engine/service.py:2415
count_stackscall1.33filters, show_deleted, show_nested, show_hidden, tags, tags_any, not_tags, not_tags_anyrpc_response_timeoutheat/rpc/client.py:192heat/engine/service.py:684
create_software_configcall1.0group, name, config, inputs, outputs, optionsrpc_response_timeoutheat/rpc/client.py:700heat/engine/service.py:2375
create_software_deploymentcall1.0server_id, config_id, deployment_id, input_values, action, status, status_reason, stack_user_project_idrpc_response_timeoutheat/rpc/client.py:736heat/engine/service.py:2420
create_stackcall1.36stack_name, template, params, files, environment_files, files_container, args, owner_id, nested_depth, user_creds_id, stack_user_project_id, parent_resource_name, template_idrpc_response_timeoutheat/rpc/client.py:282heat/engine/service.py:858
delete_snapshotcall1.0stack_identity, snapshot_idrpc_response_timeoutheat/rpc/client.py:788heat/engine/service.py:2266
delete_software_configcall1.0config_idrpc_response_timeoutheat/rpc/client.py:709heat/engine/service.py:2396
delete_software_deploymentcall1.0deployment_idrpc_response_timeoutheat/rpc/client.py:765heat/engine/service.py:2469
delete_stackcall (cast when cast=True)1.0stack_identityrpc_response_timeoutheat/rpc/client.py:457heat/engine/service.py:1489
describe_stack_resourcecall1.2stack_identity, resource_name, with_attrrpc_response_timeoutheat/rpc/client.py:576heat/engine/service.py:1935
describe_stack_resourcescall1.0stack_identity, resource_namerpc_response_timeoutheat/rpc/client.py:601heat/engine/service.py:2108
export_stackcall1.22stack_identityrpc_response_timeoutheat/rpc/client.py:821heat/engine/service.py:1606
find_physical_resourcecall1.0physical_resource_idrpc_response_timeoutheat/rpc/client.py:589heat/engine/service.py:2081
generate_templatecall1.9type_name, template_typerpc_response_timeoutheat/rpc/client.py:535heat/engine/service.py:1806
get_environmentcall1.28stack_identityrpc_response_timeoutheat/rpc/client.py:428heat/engine/service.py:1410
get_filescall1.32stack_identityrpc_response_timeoutheat/rpc/client.py:441heat/engine/service.py:1430
get_revisioncall1.0—rpc_response_timeoutheat/rpc/client.py:682heat/engine/service.py:623
get_templatecall1.0stack_identityrpc_response_timeoutheat/rpc/client.py:417heat/engine/service.py:1400
identify_stackcall1.0stack_namerpc_response_timeoutheat/rpc/client.py:122heat/engine/service.py:539
list_eventscall1.31stack_identity, filters, limit, marker, sort_keys, sort_dir, nested_depthrpc_response_timeoutheat/rpc/client.py:558heat/engine/service.py:1829
list_outputscall1.19stack_identityrpc_response_timeoutheat/rpc/client.py:805heat/engine/service.py:1443
list_resource_typescall1.30support_status, type_name, heat_version, with_descriptionrpc_response_timeoutheat/rpc/client.py:485heat/engine/service.py:1663
list_servicescall1.4—rpc_response_timeoutheat/rpc/client.py:802heat/engine/service.py:2474
list_software_configscall1.33limit, markerrpc_response_timeoutheat/rpc/client.py:689heat/engine/service.py:2364
list_software_deploymentscall1.0server_idrpc_response_timeoutheat/rpc/client.py:713heat/engine/service.py:2400
list_stack_resourcescall1.25stack_identity, nested_depth, with_detail, filtersrpc_response_timeoutheat/rpc/client.py:616heat/engine/service.py:2118
list_stackscall1.33limit, sort_keys, marker, sort_dir, filters, show_deleted, show_nested, show_hidden, tags, tags_any, not_tags, not_tags_anyrpc_response_timeoutheat/rpc/client.py:157heat/engine/service.py:627
list_template_functionscall1.35template_version, with_conditionrpc_response_timeoutheat/rpc/client.py:510heat/engine/service.py:1721
list_template_versionscall1.11—rpc_response_timeoutheat/rpc/client.py:498heat/engine/service.py:1685
metadata_software_deploymentscall1.0server_idrpc_response_timeoutheat/rpc/client.py:717heat/engine/service.py:2405
migrate_convergence_1call1.34stack_idrpc_response_timeoutheat/rpc/client.py:832heat/engine/service.py:2480
preview_stackcall1.36stack_name, template, params, files, environment_files, files_container, argsrpc_response_timeoutheat/rpc/client.py:234heat/engine/service.py:823
preview_update_stackcall1.36stack_identity, template, params, files, environment_files, files_container, argsrpc_response_timeoutheat/rpc/client.py:362heat/engine/service.py:1131
resource_mark_unhealthycall1.26stack_identity, resource_name, mark_unhealthy, resource_status_reasonrpc_response_timeoutheat/rpc/client.py:672heat/engine/service.py:2007
resource_schemacall1.30type_name, with_descriptionrpc_response_timeoutheat/rpc/client.py:522heat/engine/service.py:1744
resource_signalcall1.3stack_identity, resource_name, details, sync_callrpc_response_timeoutheat/rpc/client.py:654heat/engine/service.py:1953
show_outputcall1.19stack_identity, output_keyrpc_response_timeoutheat/rpc/client.py:810heat/engine/service.py:1456
show_snapshotcall1.0stack_identity, snapshot_idrpc_response_timeoutheat/rpc/client.py:783heat/engine/service.py:2259
show_software_configcall1.0config_idrpc_response_timeoutheat/rpc/client.py:685heat/engine/service.py:2360
show_software_deploymentcall1.0deployment_idrpc_response_timeoutheat/rpc/client.py:721heat/engine/service.py:2410
show_stackcall1.20stack_identity, resolve_outputsrpc_response_timeoutheat/rpc/client.py:211heat/engine/service.py:585
signal_software_deploymentcall1.6deployment_id, details, updated_atrpc_response_timeoutheat/rpc/client.py:770heat/engine/service.py:2445
stack_cancel_updatecall1.14stack_identity, cancel_with_rollbackrpc_response_timeoutheat/rpc/client.py:638heat/engine/service.py:1241
stack_checkcall1.0stack_identityrpc_response_timeoutheat/rpc/client.py:633heat/engine/service.py:2307
stack_list_snapshotscall1.0stack_identityrpc_response_timeoutheat/rpc/client.py:793heat/engine/service.py:2353
stack_restorecall1.0stack_identity, snapshot_idrpc_response_timeoutheat/rpc/client.py:797heat/engine/service.py:2325
stack_resumecall1.0stack_identityrpc_response_timeoutheat/rpc/client.py:629heat/engine/service.py:2170
stack_snapshotcall1.0stack_identity, namerpc_response_timeoutheat/rpc/client.py:778heat/engine/service.py:2191
stack_suspendcall1.0stack_identityrpc_response_timeoutheat/rpc/client.py:625heat/engine/service.py:2149
update_software_deploymentcall1.5deployment_id, config_id, input_values, output_values, action, status, status_reason, updated_atrpc_response_timeoutheat/rpc/client.py:752heat/engine/service.py:2454
update_stackcall1.36stack_identity, template, params, files, environment_files, files_container, args, template_idrpc_response_timeoutheat/rpc/client.py:330heat/engine/service.py:1065
validate_templatecall1.36template, params, files, show_nested, environment_files, files_container, ignorable_errorsrpc_response_timeoutheat/rpc/client.py:390heat/engine/service.py:1315

stack_identity is the dict form of HeatIdentifier: {"tenant": "<project id>", "stack_name": "s", "stack_id": "<uuid>", "path": ""} (heat/common/identifier.py:30-119). args is the dict of the API body minus the known keys (heat/api/openstack/v1/stacks.py:164-167): timeout_mins, disable_rollback, adopt_stack_data, tags, existing, clear_parameters, converge (heat/rpc/api.py:17-31).

validate_template returns either the validation result or {"Error": "<message>"}; for "No Template provided." and invalid ignore_errors codes it **returns a webob.exc.HTTPBadRequest object** through RPC (heat/engine/service.py:1337-1347), which the JSON serialiser turns into a string — a quirk the API never sees in practice because it always sends a template.

1.3 Engine ↔ engine

MethodTopic / serverModeVersionArgumentsTimeoutClientServer
check_resourceengine_worker, any enginecast1.9resource_id, current_traversal, data, is_update, adopt_stack_data, converge, skip_propagate, accumulated_failures, node_type, abandon—heat/rpc/worker_client.py:73-87heat/engine/worker.py:202
check_resource_delete_snapshotengine_worker, any enginecast1.7snapshot_id, resource_name, start_time, is_stack_delete, current_traversal—heat/rpc/worker_client.py:58-71heat/engine/worker.py:170
cancel_check_resourceengine_worker, server = engine idcast1.3stack_id—heat/rpc/worker_client.py:89-103heat/engine/worker.py:369
listeningheat-engine-listener, server = engine idcall1.0—[DEFAULT]/engine_life_check_timeout (2 s); a MessagingTimeout means "dead"heat/rpc/listener_client.py:36-48heat/engine/service.py:386
sendheat-engine-listener, server = engine idcall1.0stack_identity, message (cancel, cancel_with_rollback)engine_life_check_timeoutheat/engine/service.py:1295-1298heat/engine/service.py:399
stop_stackheat-engine-listener, server = engine idcall1.0stack_identitySTOP_STACK_TIMEOUT = 30 s (heat/engine/service.py:89)heat/engine/service.py:1585-1588heat/engine/service.py:394

check_resource.data is {"input_data": {"tuple:(<id>, True, 'resource')": <node data> | <resource id> | null, ...}} — see § 2.4 and 05 § 2.4. accumulated_failures is {"<resource name>": "<reason>"} or null.

1.4 Sample frame [reconstructed]

check_resource cast as published on the heat exchange, routing key engine_worker (oslo.messaging "message envelope" v2.0; oslo.message is a JSON string, shown decoded):

{"oslo.version": "2.0",
 "oslo.message": {
   "method": "check_resource",
   "args": {"resource_id": 42, "current_traversal": "1d0c…", "is_update": true,
            "data": {"input_data": {"tuple:(41, True, 'resource')": {
                "id": 41, "name": "a", "uuid": "8b2e…", "reference_id": "a-xyz", "action": "CREATE", "status": "COMPLETE",
                "attrs": {"output": "one", "tuple:('show', 'value')": "one"}}}},
            "adopt_stack_data": null, "converge": false, "skip_propagate": false,
            "accumulated_failures": null, "node_type": "resource", "abandon": false},
   "namespace": null, "version": "1.9",
   "_context_user_id": "…", "_context_project_id": "…", "_context_auth_token": "gAAAA…",
   "_context_auth_token_info": {"token": {"…": "full Keystone token body, catalog included"}},
   "_context_trust_id": null, "_context_password": null, "_context_aws_creds": null,
   "_context_username": "demo", "_context_project_name": "demo", "_context_roles": ["member", "reader"],
   "_context_is_admin": false, "_context_request_id": "req-…", "_context_auth_url": "http://keystone/v3",
   "_context_region_name": null, "_context_trustor_user_id": null}}
  • The context carries credentials: the user's token and its full Keystone body (auth_token_info, catalog included) on every message, and the clear-text password when deferred_auth_method = password (heat/common/context.py:140-154). Workers use this token to call the other services (heat/common/context.py:214-251); only signal handling, scaling and token-refresh paths switch to the stored trust (heat/engine/service.py:1986, heat/engine/stack.py:324-350). RabbitMQ access must be treated as credential access.
  • A large stack makes large casts: each check_resource carries the resolved attributes of all its predecessors.

1.5 Notifications

event_typePriorityEmitted whenPayloadEmitted at
orchestration.stack.<action>.startINFO ([DEFAULT]/default_notification_level)stack state IN_PROGRESS persistedtenant_id, user_id, user_identity, username, stack_identity, stack_name, state (<ACTION>_<STATUS>), state_reason, create_at, updated_at, tags, descriptionheat/engine/notification/stack.py:18-39, heat/engine/api.py:402-431
orchestration.stack.<action>.endINFOCOMPLETEsamesame
orchestration.stack.<action>.errorERRORFAILEDsamesame
orchestration.autoscaling.start|end|errorINFO/ERRORscaling group adjustmentstack payload + adjustment, adjustment_type, capacity, groupname, messageheat/engine/notification/autoscaling.py:18-41

2. Database

2.1 Tables

Schema: [_rewrite/schema-sqlite-migrated_stable-2026.2.sql](_rewrite/schema-sqlite-migrated_stable-2026.2.sql) (Alembic head 97b2a986f922, heat/db/migrations/versions/97b2a986f922_make_snapshot_state_aware.py); the ORM models match the migrations (0 differences, [_rewrite/schema-diff_stable-2026.2.md](_rewrite/schema-diff_stable-2026.2.md)). JSON columns use heat.db.types.Json/List: jsonutils.dumps into LONGTEXT on MySQL, TEXT elsewhere; None is stored as the string null (heat/db/types.py:35-65).

TableRoleWritten byRead by
stackone row per stack, nested ones included (owner_id); soft delete (deleted_at); current_traversal, current_deps, convergencecreate/update/delete, every state change (CAS on current_traversal)everything
raw_templatetemplate, environment, legacy files (JSON)stack store, update (new row per template version)resource load, show template/environment
raw_template_filesfiles dict, shared between template rows (raw_template.files_id)heat/db/api.py:244template load
stack_tagtagsstack storelist filters
stack_locklegacy engine lock, PK stack_idheat/db/api.py:1151-1198legacy operations
user_credsencrypted trust id (or password), trustor, project, auth_url, region_name, decrypt_methodstack store, trust refreshstored context
resourceone row per resource version (replacements are new rows); nova_instance = physical id; lock fields engine_id, atomic_keyworkers (CAS), signalseverything
resource_dataper-resource key/value store (redact, decrypt_method): passwords, EC2 keys, signal URLs, hooks, plugin statepluginsplugins, signals
resource_properties_dataproperties (and resolved attributes) as JSON, optionally encrypted (encrypted)heat/db/api.py:706-727resource load, events
eventone row per state change; rsrc_prop_data_id, legacy resource_properties (PickleType)heat/db/api.py:1467-1500 (prunes over max_events_per_stack)events API
sync_pointconvergence join points, PK (entity_id, traversal_id, is_update)traversal start, workers (CAS on atomic_key)workers
software_configimmutable configsAPIdeployments, metadata
software_deploymentdeployments (server_id, values, status)API, signalsmetadata
snapshotstack snapshots (data, state)snapshot/deleterestore
resource_snapshotper-resource snapshot data (convergence), migration 88be64598c0fsnapshot traversalrestore, delete
serviceengine heartbeatsheat/engine/service.py:2523-2548GET /services

2.2 Notable statements [reconstructed from the ORM]

-- stack state change in convergence (heat/db/api.py:1003-1028): compare-and-swap on the traversal
UPDATE stack SET action=?, status=?, status_reason=?, updated_at=?
 WHERE id=? AND deleted_at IS NULL
   AND (tenant=? OR stack_user_project_id=?)          -- non-admin contexts only
   AND current_traversal=?;                           -- 0 rows => another traversal took over

-- resource lock acquire / release (heat/db/api.py:536-546, heat/engine/resource.py:2159-2183)
UPDATE resource SET action='UPDATE', status='IN_PROGRESS', engine_id=:me, atomic_key=:n+1, …
 WHERE id=:rid AND engine_id IS NULL AND atomic_key=:n;
UPDATE resource SET status='COMPLETE', engine_id=NULL, atomic_key=:n+2, …
 WHERE id=:rid AND engine_id=:me AND atomic_key=:n+1;

-- replacement, one transaction (heat/db/api.py:287-309)
INSERT INTO resource (stack_id, name, replaces, action, status, current_template_id, requires, …)
 VALUES (?, ?, :old, 'INIT', 'COMPLETE', ?, ?, …);
UPDATE resource SET replaced_by=:new, atomic_key=:n+1 WHERE id=:old AND engine_id IS NULL AND atomic_key=:n;
-- 0 rows => UpdateInProgress, the INSERT is rolled back

-- sync point merge (heat/db/api.py:2120-2135)
SELECT * FROM sync_point WHERE entity_id=? AND traversal_id=? AND is_update=?;
UPDATE sync_point SET input_data=?, extra_data=?, atomic_key=:n+1
 WHERE entity_id=? AND traversal_id=? AND is_update=? AND atomic_key=:n;

-- engines holding resource locks of a stack (heat/db/api.py:374-380)
SELECT DISTINCT engine_id FROM resource WHERE stack_id=? AND engine_id IS NOT NULL;

-- legacy stack lock (heat/db/api.py:1151-1198)
INSERT INTO stack_lock (stack_id, engine_id) VALUES (?, ?);            -- DBDuplicateEntry => read holder
UPDATE stack_lock SET engine_id=:new WHERE stack_id=? AND engine_id=:old;   -- steal
DELETE FROM stack_lock WHERE stack_id=? AND engine_id=?;                  -- release
  • Writers that use @retry_on_db_error are retried on deadlock and disconnect ([database]/db_max_retries, heat/db/api.py:143-155).
  • status_reason is truncated to 65535 UTF-8 bytes on MySQL before writing (heat/db/api.py:100-136); event.resource_status_reason to 255 characters (heat/db/models.py:228-230).
  • Stack name uniqueness is not a database constraint: stack_create inserts, then re-reads the earliest live stack of that name in the project and deletes its own row if it is not the earliest (heat/db/api.py:982-998).
  • heat-manage purge_deleted <age> hard-deletes soft-deleted stacks and their events, resources, templates and credentials in batches (heat/db/api.py:1827-2090).

2.3 Encrypted columns

heat/common/crypt.py:

  • encrypt() uses Fernet with key base64(auth_encryption_key[:32]) (the key is doubled when shorter than 32 characters) and records decrypt_method = "cryptography_decrypt_v1" (heat/common/crypt.py, encrypt / cryptography_decrypt_v1).
  • Rows written by older releases may name oslo_decrypt_v1 (AES-256-CBC with the 32-character key, IV prefix, ANSI X9.23 padding, last plaintext byte dropped) or heat_decrypt (AES-CFB, IV prefix): the method name stored in the row is a function name looked up by getattr at read time (heat/common/crypt.py, decrypt). A rewrite must implement all three to read existing databases; vectors are in [_rewrite/crypt-vectors_stable-2026.2.json](_rewrite/crypt-vectors_stable-2026.2.json).
  • Encrypted: user_creds.password and user_creds.trust_id, resource_data.value when redact, hidden template parameters and resource_properties_data.data when [DEFAULT]/encrypt_parameters_and_properties (heat/db/api.py:2138-2306, heat-manage update_params).

3. Keystone

CallWhenCredentialsDefined at
token validation (GET /v3/auth/tokens)every native API requestkeystonemiddleware service user ([keystone_authtoken])etc/heat/api-paste.ini:69
POST /v3/ec2tokensevery CFN API request[ec2authtoken] adapterheat/api/aws/ec2token.py:209-222
POST /v3/OS-TRUST/trusts (trustor = user, trustee = [trustee] user, impersonation=True, roles = all the token's roles unless trusts_delegated_roles)stack create; update/delete when the caller differs from the trustor (heat/engine/stack.py:594-619)the user's tokenheat/engine/clients/os/keystone/heat_keystoneclient.py:196-236
POST /v3/auth/tokens with a trust scopeevery deferred operation (signals, scaling, stored context)[trustee] user + trust_idheat/common/context.py:201-212
DELETE /v3/OS-TRUST/trusts/{id}stack delete, trust refreshuser/trustheat/engine/clients/os/keystone/heat_keystoneclient.py:255-260
domain-scoped token for stack_domain_admincreating stack-domain projects, users, credentials[DEFAULT]/stack_domain_admin(_password) in stack_user_domain_*heat/engine/clients/os/keystone/heat_keystoneclient.py:134-167
POST /v3/projects (<project id>-<stack id>, truncated to 64)stack create (stack_user_project_id)domain adminheat/engine/clients/os/keystone/heat_keystoneclient.py:437-451
POST /v3/users + role grant heat_stack_user on that projectfirst signal URL / server credentials of a resourcedomain adminheat/engine/clients/os/keystone/heat_keystoneclient.py:344-395
POST /v3/credentials (type ec2, blob {"access", "secret"})CFN signal URLs, AWS::IAM::AccessKeydomain admin or userheat/engine/clients/os/keystone/heat_keystoneclient.py:527-556
PATCH /v3/users/{id} (enabled)suspend/resume of stack usersdomain adminheat/engine/clients/os/keystone/heat_keystoneclient.py:570-590
OS::Keystone::* resources (projects, users, roles, groups, domains, endpoints, services, regions, role assignments)resource pluginsstack owner's tokenheat/engine/resources/openstack/keystone/

Without stack_user_domain_* configured, Heat falls back to creating stack users in the user's own project and domain with the user's token (heat/engine/clients/os/keystone/heat_keystoneclient.py:354-357, :437-442); a deployment for OpenKeel must configure the domain.

4. Other services (resource plugins)

  • One client plugin per service, loaded through the heat.clients entry points (20 plugins, [_rewrite/client-plugins_stable-2026.2.json](_rewrite/client-plugins_stable-2026.2.json)). Endpoints come from the token's catalog: service_type of the plugin, interface [clients_<name>]/endpoint_type (default publicURL from [clients]), region context.region_name or [DEFAULT]/region_name_for_services, or region_name_for_shared_services for the shared services list (heat/engine/clients/client_plugin.py:89-125).
  • A resource type is offered only if its service is in the catalog (is_service_available, heat/engine/resource.py:785-828); otherwise templates using it fail validation with ResourceTypeUnavailable (400, error code 99001).
  • Retries: [DEFAULT]/client_retry_limit (2) connection retries for keystoneauth sessions; resource plugins poll their resource in check_<action>_complete with the scheduler's back-off; 404 on delete is ignored through each plugin's ignore_not_found.
  • The full per-type list, with the porting verdict for OpenKeel, is [_rewrite/resource-types_stable-2026.2.json](_rewrite/resource-types_stable-2026.2.json) and 07 § 3.

5. Other exchanges

  • template_url / get_file / template resources by URL: GET with requests, http and https only, timeout template_fetch_timeout (60 s), streaming stopped at max_template_size (DownloadLimitExceeded) (heat/common/urlfetch.py:38-84). The fetch for template_url happens in heat-api; for nested template URLs in heat-engine.
  • Swift: files_container objects (heat/engine/template_files.py), signal TempURLs and containers, server metadata TempURLs (PUT from the engine, heat/engine/service_software_config.py:139-147).
  • Zaqar: signal queues, metadata queues, the zaqar-queue event sink (absent in OpenKeel).
  • Instance agents → Heat: os-collect-config / heat-config-notify / cfn-signal call heat-api-cfn (DescribeStackResource, /v1/signal, /v1/waitcondition), heat-api (…/metadata, …/signal), or Swift/Zaqar, with the stack-domain user's credentials (05 § 5).

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