Interactions — zun — branch stable/2026.2
Branch
stable/2026.2@34135a563699— repository https://opendev.org/openstack/zun — analysed on 2026-11-01
All frames below are reconstructed from the code. No capture of a real system was made, since none was available.
RabbitMQ
Library: oslo.messaging (zun/common/rpc_service.py:17). Transport: [DEFAULT]/transport_url. Control exchange: zun (zun/common/config.py:33). allowed_remote_exmods carries zun.common.exception (zun/common/rpc.py:36), so a remote exception keeps its class and status when it reaches the API.
Topology
| Exchange | Type | Routing key / queue | Producer | Consumer |
|---|---|---|---|---|
| control exchange | topic | zun-compute (no named server) | zun-api for image_search without host, network_create, network_delete | any zun-compute |
| control exchange | topic | zun-compute.<host> | zun-api for all per-node methods | zun-compute on <host> |
reply_<uuid> | direct | caller's reply queue | zun-compute | zun-api |
Only zun-api sends RPC. zun-compute does not call other services over the bus. There is no conductor: every process with a role writes the database directly.
RPC methods
Client: zun/compute/rpcapi.py. Server: zun/compute/manager.py. Topic and server resolved at zun/common/rpc_service.py:98, zun/common/rpc_service.py:102. The target carries no version (zun/common/rpc_service.py:51). Client history states 1.0 and 1.1 in a comment, but the value is not sent (zun/compute/rpcapi.py:44).
| Method | Mode | Host check | Arguments | Response | Client | Server |
|---|---|---|---|---|---|---|
container_create | cast | no | limits, requested_networks, requested_volumes, container, run, pci_requests | — | zun/compute/rpcapi.py:58 | zun/compute/manager.py:290 |
container_delete | cast | yes | container, force | — | zun/compute/rpcapi.py:69 | zun/compute/manager.py:538 |
container_show | call | yes | container | container | zun/compute/rpcapi.py:74 | zun/compute/manager.py:618 |
container_rebuild | cast | no | container, run | — | zun/compute/rpcapi.py:78 | zun/compute/manager.py:675 |
container_reboot | cast | no | container, timeout | — | zun/compute/rpcapi.py:82 | zun/compute/manager.py:645 |
container_stop | cast | no | container, timeout | — | zun/compute/rpcapi.py:86 | zun/compute/manager.py:663 |
container_start | cast | no | container | — | zun/compute/rpcapi.py:90 | zun/compute/manager.py:762 |
container_pause | cast | no | container | — | zun/compute/rpcapi.py:93 | zun/compute/manager.py:782 |
container_unpause | cast | no | container | — | zun/compute/rpcapi.py:96 | zun/compute/manager.py:800 |
container_logs | call | yes | container, stdout, stderr, timestamps, tail, since | string | zun/compute/rpcapi.py:100 | zun/compute/manager.py:808 |
container_exec | call | yes | container, command, run, interactive | {output, exit_code, exec_id, token} | zun/compute/rpcapi.py:107 | zun/compute/manager.py:826 |
container_exec_resize | call | yes | exec_id, height, width | — | zun/compute/rpcapi.py:113 | zun/compute/manager.py:859 |
container_kill | cast | no | container, signal | — | zun/compute/rpcapi.py:118 | zun/compute/manager.py:883 |
container_update | call | yes | container, patch | container | zun/compute/rpcapi.py:123 | zun/compute/manager.py:891 |
resize_container | cast | no | container, patch | — | zun/compute/rpcapi.py:127 | zun/compute/manager.py:1290 |
container_attach | call | yes | container | token | zun/compute/rpcapi.py:132 | zun/compute/manager.py:928 |
container_resize | call | yes | container, height, width | container | zun/compute/rpcapi.py:137 | zun/compute/manager.py:945 |
container_top | call | yes | container, ps_args | dict | zun/compute/rpcapi.py:142 | zun/compute/manager.py:958 |
container_get_archive | call | yes | container, path, encode_data | [data, stat] | zun/compute/rpcapi.py:147 | zun/compute/manager.py:973 |
container_put_archive | call | yes | container, path, data, decode_data | — | zun/compute/rpcapi.py:153 | zun/compute/manager.py:991 |
container_stats | call | yes | container | dict | zun/compute/rpcapi.py:160 | zun/compute/manager.py:1009 |
container_commit | call | yes | container, repository, tag | {uuid} | zun/compute/rpcapi.py:165 | zun/compute/manager.py:1023 |
add_security_group, remove_security_group | cast | no | container, security_group | — | zun/compute/rpcapi.py:169, zun/compute/rpcapi.py:173 | zun/compute/manager.py:578, zun/compute/manager.py:596 |
image_delete, image_pull | cast | no | image | — | zun/compute/rpcapi.py:177, zun/compute/rpcapi.py:180 | zun/compute/manager.py:1105, zun/compute/manager.py:1116 |
image_search | call | no | image, image_driver_name, exact_match, registry | list | zun/compute/rpcapi.py:183 | zun/compute/manager.py:1165 |
network_detach, network_attach | cast | no | container, network or requested_network | — | zun/compute/rpcapi.py:202, zun/compute/rpcapi.py:206 | zun/compute/manager.py:1246, zun/compute/manager.py:1264 |
network_create, network_delete | call, no server | no | neutron_net_id or network | network | zun/compute/rpcapi.py:211, zun/compute/rpcapi.py:216 | zun/compute/manager.py:1282, zun/compute/manager.py:1286 |
capsule_create, capsule_delete | dead | — | — | — | zun/compute/rpcapi.py:190, zun/compute/rpcapi.py:198 | no such method on the manager: capsules travel through container_create and container_delete |
container_rebuild, start, stop and other casts do not check the node, so a request to a dead node gives 202 and is lost until the queue is consumed. "Host check" is check_container_host (zun/compute/rpcapi.py:25). container is a versioned object: Container 1.44, Capsule 1.4, CapsuleContainer 1.4, CapsuleInitContainer 1.4 (zun/objects/container.py:431, zun/objects/container.py:443, zun/objects/container.py:487, zun/objects/container.py:517).
Notifications
None. A search over the non-test code finds no get_notifier and no caller passing notifier= to wrap_exception (zun/common/exception.py:56).
Sample frame
Cast container_start to zun-compute.node1:
{"oslo.version": "2.0",
"oslo.message": "{\"method\": \"container_start\", \"args\": {\"container\": {\"zun_object.name\": \"Container\", \"zun_object.namespace\": \"zun\", \"zun_object.version\": \"1.44\", \"zun_object.data\": {\"uuid\": \"…\", \"status\": \"Created\", \"host\": \"node1\", \"container_id\": \"…\"}}}, \"namespace\": null, \"_unique_id\": \"…\", \"_context_project_id\": \"…\", \"_context_user_id\": \"…\", \"_context_auth_token\": \"…\", \"_context_request_id\": \"req-…\", \"_context_roles\": [\"member\"]}"}
The zun_object prefix is OBJ_SERIAL_NAMESPACE (zun/objects/base.py:38). The context travels in _context_*, including the user's token, which zun-compute reuses to call Neutron, Cinder and Glance (zun/common/keystone.py:72). A long image pull can outlive that token [inferred]; capsule and auto-remove flows that run later rely on it too.
Database
Library: oslo.db and SQLAlchemy (zun/db/sqlalchemy/api.py:19). Single backend sqlalchemy (zun/db/api.py:27); zun/db/etcd/__init__.py is an empty leftover. Access from zun-api, zun-compute, zun-cni-daemon, zun-wsproxy. Migrations: Alembic, head 3f2b36231bee, 84 revisions, run with zun-db-manage upgrade (zun/cmd/db_manage.py:28). They do not run on SQLite. Schema: [_rewrite/schema-mariadb-migrated_stable-2026.2.sql](_rewrite/schema-mariadb-migrated_stable-2026.2.sql), differences with the models in [_rewrite/schema-diff_stable-2026.2.md](_rewrite/schema-diff_stable-2026.2.md).
Tables
| Table | Role | Written by | Read by | Model |
|---|---|---|---|---|
zun_service | service heartbeats, disabled and forced-down flags | zun-compute, zun-api | zun-api | zun/db/sqlalchemy/models.py:103 |
container | containers, capsules and capsule members, told apart by container_type | zun-api, zun-compute, zun-cni-daemon | all | zun/db/sqlalchemy/models.py:124 |
volume, volume_mapping | a volume and its mounts per container | zun-api, zun-compute | zun-compute | zun/db/sqlalchemy/models.py:208, zun/db/sqlalchemy/models.py:184 |
exec_instance | exec session tokens | zun-compute | zun-wsproxy | zun/db/sqlalchemy/models.py:228 |
image | images pulled per node, unique on (repo, tag, host) | zun-api, zun-compute | zun-api | zun/db/sqlalchemy/models.py:252 |
compute_node | node inventory and usage, NUMA, runtimes, labels | zun-compute | scheduler in zun-api, zun-api | zun/db/sqlalchemy/models.py:360 |
pci_device | SR-IOV devices | zun-compute | zun-compute | zun/db/sqlalchemy/models.py:399 |
container_actions, container_actions_events | audit trail | zun-api, zun-compute | zun-api | zun/db/sqlalchemy/models.py:453, zun/db/sqlalchemy/models.py:479 |
quotas, quota_classes | limits per project and per class | zun-api | zun-api | zun/db/sqlalchemy/models.py:504, zun/db/sqlalchemy/models.py:520 |
quota_usages | model only: usage is counted from container | none found | none found [inferred] | zun/db/sqlalchemy/models.py:533 |
network | Docker network per Neutron network per node, unique on (neutron_net_id, host) | zun-compute | zun-compute, zun-api | zun/db/sqlalchemy/models.py:552 |
registry | private registry credentials | zun-api | zun-compute | zun/db/sqlalchemy/models.py:572 |
resource_provider, resource_class, inventory, allocation | leftovers of an internal Placement model; Placement is the reference now | no live caller found [inferred] | none found [inferred] | zun/db/sqlalchemy/models.py:272, zun/db/sqlalchemy/models.py:288, zun/db/sqlalchemy/models.py:301, zun/db/sqlalchemy/models.py:335 |
Notable transactions
- Updates: a
SELECT … FOR UPDATEthen assignment, in the same transaction, withretry_on_deadlock(zun/db/sqlalchemy/api.py:251,zun/db/sqlalchemy/api.py:258). 12 sites. No optimistic generation, no status compare-and-swap: the last writer wins, andtask_stateis an advisory marker. - Container update rejects a name change that breaks uniqueness:
SELECT … WHERE name = ? [AND project_id = ?]according tounique_container_name_scope(zun/db/sqlalchemy/api.py:165,zun/db/sqlalchemy/api.py:240). A check then write, so not race-free. - Project scope: filter added by
_add_project_filtersunless admin withall_projects(zun/db/sqlalchemy/api.py:111). - Quotas: read-then-check, not reservations:
SELECT SUM(...), then compare; two concurrent requests can both pass (zun/api/controllers/v1/containers.py:463,zun/common/quota.py:166,zun/common/quota.py:568). - Registry password: Fernet with
auth_encryption_key, decrypted on read and copied in clear in the RPC object (zun/db/sqlalchemy/api.py:1402,zun/db/sqlalchemy/api.py:1414,zun/common/crypt.py:23,zun/conf/utils.py:21). - Foreign keys:
container.capsule_idcascades;volume_mapping.volume_idcascades;container_actions.container_uuidcascades; see_rewrite/schema-mariadb-migrated_stable-2026.2.sql.
Cache
oslo.cache is not used in the code read. keystonemiddleware's own token cache may be configured with [keystone_authtoken]/memcached_servers.
Outbound REST calls
Identity: [keystone_auth] for admin clients, and [keystone_authtoken]/www_authenticate_uri for token auth (zun/conf/keystone.py:16, zun/conf/keystone.py:17). Selection: auth_token_info, else the user's token, else admin credentials (zun/common/keystone.py:72).
| Target service | Method + path | When | Authentication | Timeout / retry | Errors handled | Defined at |
|---|---|---|---|---|---|---|
| Keystone | GET /v3/auth/tokens | each API request | service token | keystonemiddleware | 401 | etc/zun/api-paste.ini:12 |
| Placement | GET /allocation_candidates?resources=…&required=!CUSTOM_ZUN_COMPUTE_STATUS_DISABLED&limit=1000 | scheduling, microversion 1.31 | admin | @safe_connect, keystoneauth | no candidate gives NoValidHost | zun/scheduler/client/report.py:282, zun/scheduler/client/query.py:61 |
| Placement | GET then PUT /allocations/{uuid} | claim, in scheduling; consumer generation 1.28 | admin | @retries on conflict (zun/scheduler/client/report.py:1470) | conflict gives the next host | zun/scheduler/client/report.py:1471, zun/scheduler/utils.py:395 |
| Placement | GET, PUT /allocations/{uuid} with empty body | delete | admin | as above | AllocationDeleteFailed | zun/scheduler/client/report.py:1897 |
| Placement | POST /resource_providers, PUT /resource_providers/{id}/inventories, .../traits, .../aggregates, GET /traits, PUT /traits/{t} | each inventory_host when changed | admin | up to 4 tries on conflict | ResourceProviderUpdateConflict | zun/scheduler/client/report.py:501, zun/scheduler/client/report.py:888, zun/scheduler/client/report.py:1009, zun/scheduler/client/report.py:1092, zun/scheduler/client/report.py:959, zun/compute/compute_node_tracker.py:338 |
| Neutron | GET /v2.0/networks, /subnets, /ports, /security-groups | create, attach | user | client | 404 mapped to NetworkNotFound, PortNotFound | zun/network/neutron.py:274, zun/network/neutron.py:285, zun/network/neutron.py:301 |
| Neutron | POST /v2.0/ports, PUT /v2.0/ports/{id} with device_id, optional binding:host_id, device_owner: compute:zun | port creation | admin | client | cleanup on Docker failure | zun/network/neutron.py:79, zun/common/consts.py:80 |
| Neutron | DELETE /v2.0/ports/{id}, or PUT to unbind for a user-supplied port | delete | admin | client | PortNotFoundClient logged | zun/network/neutron.py:148, zun/network/neutron.py:161 |
| Neutron | POST /v2.0/security-groups, /security-group-rules, DELETE | exposed_ports | user | client | Could not create required security group rules | zun/container/docker/driver.py:382, zun/network/neutron.py:340 |
| Neutron | GET /v2.0/ports?binding:host_id= | VIF polling, each second | admin | — | exception in the timer | zun/cni/daemon/service.py:199 |
| Cinder | GET /volumes, POST /volumes, DELETE /volumes/{id}, POST /volumes/{id}/action (os-reserve, os-initialize_connection, os-attach, os-begin_detaching, os-detach, os-terminate_connection, os-unreserve) | mounts | user, elevated on attach | client | VolumeNotFound, VolumeInUse, VolumeNotUsable, VolumeCreateFailed | zun/volume/cinder_api.py:38, zun/volume/cinder_workflow.py:89 |
| Glance | GET /v2/images, GET /v2/images/{id}, GET /v2/images/{id}/file, POST /v2/images, PATCH, PUT .../file, DELETE | Glance driver, commit | user | client | ImageNotFound | zun/image/glance/utils.py:55, zun/image/glance/utils.py:63, zun/image/glance/utils.py:102, zun/image/glance/utils.py:80, zun/image/glance/utils.py:95 |
| Registry | docker pull by the daemon, with auth_config | Docker driver | registry credentials | Docker | ImageNotFound, DockerError | zun/image/docker/driver.py:66 |
Port creation is the sensitive one: tenant_id is the caller's project and the call is admin, while the requested network was first read with the user's token (zun/common/utils.py:508).
Docker daemon
- Protocol: Docker Engine API, version
[docker]/docker_remote_api_version, 1.40 by default (zun/conf/docker.py:20). Library: docker-py (zun/container/docker/utils.py:52). - Endpoints:
unix:///var/run/docker.sockforzun-compute, andtcp://$my_ip:2375forzun-wsproxy(zun/conf/docker.py:28,zun/conf/docker.py:31,zun/conf/docker.py:49). The install guide opens 2375 on the node with no TLS setting (doc/source/install/compute-install.rst:224). TLS exists throughapi_insecure,ca_file,cert_file,key_file(zun/container/docker/utils.py:35). - Errors:
is_not_foundis'404' in str(e), so an error whose text merely contains 404 counts as not found (zun/container/docker/driver.py:47).errors.APIErroris rewritten toDockerError(zun/container/docker/utils.py:48);wrap_docker_errorturns "not found" into state changes and "not running", "not paused", "paused" intoConflict(zun/container/docker/driver.py:62,zun/container/docker/driver.py:87). - Time-out:
[docker]/default_timeout, 60 s. - Names:
zun-<uuid>. - Calls:
create_container,start,stop,restart,kill,pause,unpause,remove_container,inspect_container,list_containers,logs,exec_create,exec_start,exec_inspect,exec_resize,resize,top,stats,update_container,get_archive,put_archive,commit,pull,load_image,inspect_image,get_image,images,info,create_network,remove_network,networks,connect_container_to_network,disconnect_container_from_network.
Isolation applied through Docker and CRI
| Setting | Source | Where |
|---|---|---|
runtime from the request, else container_runtime, default runc; RuntimeFilter keeps nodes that list it | admin or operator | zun/container/docker/driver.py:288, zun/conf/container_driver.py:55, zun/scheduler/filters/runtime_filter.py:28 |
privileged | policy denied by default | zun/container/docker/driver.py:291, zun/common/policies/container.py:43 |
mem_limit, cpu_shares, cpuset_cpus, cpuset_mems, storage_opt.size | request or defaults | zun/container/docker/driver.py:307, zun/container/docker/driver.py:320 |
no cap_drop, seccomp, security_opt, userns, pids_limit, read_only set | absent | search over zun/container/ |
| capsule: shared network and IPC, PID left out | sandbox | zun/container/docker/driver.py:1255 |
Other
- Images: selection
image_driverper container,default_image_driverdefaultdocker, listimage_driver_listglance,docker(zun/conf/image_driver.py:20,zun/conf/image_driver.py:32). Pull policyalwayswhen unset (zun/common/utils.py:252). With Docker, policiesifnotpresentandneverlook at the node's store, with no project check (zun/image/docker/driver.py:54,zun/image/docker/driver.py:90). With Glance: image located with the user's token,container_format: docker, tar written to[glance]/images_directory,docker load(zun/image/glance/utils.py:56,zun/image/glance/driver.py:74,zun/container/docker/driver.py:128). - Capsule images under CRI:
PullImageonly; the registry is not usable (zun/container/cri/driver.py:194). - containerd CRI: gRPC over
unix:///run/containerd/containerd.sock,runtime.v1alpha2, insecure channel (zun/container/cri/driver.py:45,zun/criapi/api_pb2.py:31). - CNI:
zun-cnibinary called withCNI_*environment and config on stdin (zun/network/cni_network.py:132); talks HTTP tozun-cni-daemonat 127.0.0.1:9036 (zun/conf/cni_daemon.py:24). Spec version 0.3.1 (zun/cni/api.py:35). Bindings throughos-vif:VIFBridge,VIFOpenVSwitch(setup.cfg:82). - Kuryr:
kuryr-libnetworkis a Docker network plugin, called by Docker rather than by Zun. Zun names the driver with[network]/driver_name(zun/conf/network.py:23). - Privileged commands:
mount,umount,mkfsthrough rootwrap or privsep (zun/common/mount.py:44,zun/common/utils.py:343). Filter file:etc/zun/rootwrap.d/zun.filters:1. os-brick connects the Cinder device (zun/volume/cinder_workflow.py:62). - Files: volumes under
[volume]/volume_dir,$state_path/mnt/<volume uuid>(zun/common/mount.py:168,zun/conf/volume.py:35); Docker root measured throughpsutil(zun/container/docker/driver.py:1091). - Host capabilities: NUMA, memory and PCI information read locally (
zun/container/os_capability/linux/os_capability_linux.py:1). - WebSocket: see 05 § zun-wsproxy.
Source: reverse-spec-zun/03-interactions_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.