Processing outside the API — magnum — branch stable/2026.2
Branch
stable/2026.2@1910928b7d9f— repository https://opendev.org/openstack/magnum — analysed on 2026-11-09
Most of what Magnum does happens in magnum-conductor, outside the HTTP request: the API validates, then hands the work to the conductor over RabbitMQ. The conductor runs the cluster driver and two periodic loops: one that asks the driver for the cluster status, and one that polls the Kubernetes API for health. Magnum has no notification listener. It emits CADF notifications but consumes none, and it has no OVSDB or cache events.
Since stable/2026.2 Magnum ships no driver (the magnum.drivers entry point is empty, pyproject.toml:69). Every processing step below that says "the driver" runs code from an out-of-tree package: the Cluster API driver is described in 08.
Summary
| Process | Trigger | Type | Purpose | Reads | Writes / emits | Defined at |
|---|---|---|---|---|---|---|
| magnum-conductor | cluster_create | RPC cast received (topic magnum-conductor) | create the cluster row, default nodegroups and CAs, start the driver | template | cluster, nodegroup, certificates (Barbican / x509keypair), CADF magnum.cluster.create | magnum/conductor/handlers/cluster_conductor.py:43 |
| magnum-conductor | cluster_update | RPC cast | resize the default worker nodegroup, or store a health status | cluster, nodegroup | nodegroup.node_count, cluster.status/health_*, CADF update | magnum/conductor/handlers/cluster_conductor.py:89 |
| magnum-conductor | cluster_delete | RPC cast | start the driver's teardown | cluster, template | cluster.status = DELETE_IN_PROGRESS, CADF delete | magnum/conductor/handlers/cluster_conductor.py:166 |
| magnum-conductor | cluster_resize | RPC cast | resize one nodegroup (autoscaler path) | cluster, nodegroup | nodegroup.node_count/status, cluster.status | magnum/conductor/handlers/cluster_conductor.py:193 |
| magnum-conductor | cluster_upgrade | RPC call | upgrade a cluster or nodegroup to a template | cluster, template | cluster.status, nodegroup.status | magnum/conductor/handlers/cluster_conductor.py:266 |
| magnum-conductor | nodegroup_create / nodegroup_update / nodegroup_delete | RPC cast | manage extra nodegroups | cluster | nodegroup, cluster.status | magnum/conductor/handlers/nodegroup_conductor.py:73, magnum/conductor/handlers/nodegroup_conductor.py:95, magnum/conductor/handlers/nodegroup_conductor.py:116 |
| magnum-conductor | sign_certificate, get_ca_certificate | RPC call | sign a CSR / return a CA | certificates | — | magnum/conductor/handlers/ca_conductor.py:42, magnum/conductor/handlers/ca_conductor.py:60 |
| magnum-conductor | rotate_ca_certificate | RPC call | regenerate the CAs and have the driver roll them out | certificates | new certificates, cluster refs and status | magnum/conductor/handlers/ca_conductor.py:70 |
| magnum-conductor | credential_rotate | RPC call | have the driver rotate the cloud credential | cluster | driver-specific | magnum/conductor/handlers/credential_conductor.py:46 |
| magnum-conductor | federation_create/update/delete | RPC cast | (not implemented) | — | — (raises NotImplementedError) | magnum/conductor/handlers/federation_conductor.py:25 |
| magnum-conductor | object_action, object_class_action_versions, object_backport_versions | RPC call | versioned-object indirection: database access on behalf of magnum-api | all tables | all tables | magnum/conductor/handlers/indirection_api.py:37 |
| magnum-conductor | ping_conductor | RPC call | liveness probe between conductors | — | — | magnum/conductor/handlers/conductor_listener.py:23 |
| magnum-conductor (parent process) | sync_cluster_status | SQL polling, every 10 s, runs at start | ask the driver for the status of every cluster in progress; finish deletions | cluster where status in 4 *_IN_PROGRESS states | cluster, nodegroup, certificates, CADF success/failure | magnum/service/periodic.py:161 |
| magnum-conductor (parent process) | sync_cluster_health_status | SQL polling + HTTPS to each cluster, every [kubernetes]/health_polling_interval s (60), runs at start | refresh health_status from the Kubernetes API | cluster in 9 states; Kubernetes /healthz, /api/v1/nodes | cluster.health_status* | magnum/service/periodic.py:190 |
| magnum-conductor (parent process) | update_magnum_service | SQL heartbeat, every [DEFAULT]/periodic_interval_max s (60) [inferred], runs at start | record that this conductor is alive | magnum_service | magnum_service.last_seen_up, report_count | magnum/servicegroup/magnum_service_periodic.py:41 |
The RPC server
- Process:
magnum-conductor(magnum/cmd/conductor.py:44) starts[conductor]/workersworker processes (default: CPU count,magnum/cmd/conductor.py:71), each with an oslo.messaging RPC server on **topic[conductor]/topic=magnum-conductor** and a random 12-characterservername generated at start (magnum/cmd/conductor.py:57,magnum/conf/conductor.py:20). - Endpoints: indirection API, cluster, listener, credential, CA, federation and nodegroup handlers (
magnum/cmd/conductor.py:58). NoTargetversion is declared (magnum/common/rpc_service.py:37): there is no RPC version negotiation, and calls go to any conductor through the topic queue. - Serializer:
MagnumObjectSerializer(magnum/common/rpc_service.py:41) — clusters, nodegroups and templates travel as oslo versioned objects (magnum_object.name,.version,.data,.changes). - Executor: threading, with
[DEFAULT]/rpc_response_timeoutfor calls [inferred from oslo.messaging defaults]. - Periodic tasks only run in the parent process, once per host, not per worker (
magnum/cmd/conductor.py:76). With several conductor hosts, every host polls every cluster: there is no leader election and no lock (see each task).
cluster_create (RPC cast)
- Trigger: cast from
POST /v1/clusters(magnum/conductor/api.py:39). - Purpose: materialise the cluster requested by the API and start building it.
- Flow and effects:
- Status
CREATE_IN_PROGRESS,INSERT cluster(magnum/conductor/handlers/cluster_conductor.py:47). The UUID comes from the API; a duplicate raisesClusterAlreadyExists(magnum/db/sqlalchemy/api.py:183), which is only logged. INSERT nodegroupdefault-master(size =master_count, flavor = cluster or template master flavor) anddefault-worker(size =node_count, flavor = cluster or template flavor,min_node_count= labelmin_node_countif present), bothis_default = true,CREATE_IN_PROGRESS, labels copied from the cluster (magnum/conductor/utils.py:157).- Generate the Kubernetes CA, etcd CA and front-proxy CA (3 self-signed RSA CAs, SHA-256, password-encrypted private keys) and the Magnum client certificate (
CN=admin, O=system:masters), store them with the cert manager, and record the four references on the cluster (magnum/conductor/handlers/common/cert_manager.py:99). - CADF notification
magnum.cluster.create, outcomepending(magnum/conductor/handlers/cluster_conductor.py:64). - Load the driver by the template's
drivername (magnum/drivers/common/driver.py:193) and callcreate_cluster(context, cluster, create_timeout). - Save the cluster (the driver typically sets
stack_id), and copystack_idto each nodegroup (magnum/conductor/handlers/cluster_conductor.py:72).
- Status
- Error behaviour: any exception from step 3 onwards sets
CREATE_FAILEDwithstatus_reason = str(e), emitsmagnum.cluster.createfailure (priorityerror) and re-raises; the exception is logged by oslo.messaging and lost for the client (magnum/conductor/handlers/cluster_conductor.py:77). The nodegroups stayCREATE_IN_PROGRESS. - Concurrency and idempotency: a replayed cast fails at step 1 (duplicate UUID). Nothing protects two concurrent creates from exceeding the project quota (checked in the API before the insert).
- Cost: 3 inserts, 3 to 4 certificate stores (with Barbican: 3 or 4 secrets + 1 container per certificate, i.e. up to 20 Barbican calls), then the driver.
cluster_update (RPC cast)
- Trigger:
PATCH /v1/clusters/{ident}(magnum/conductor/api.py:58). - Flow:
- Refuse unless the cluster is
CREATE_COMPLETE,UPDATE_COMPLETE,ROLLBACK_COMPLETEor one of the legacy*_COMPLETEstates (magnum/conductor/handlers/cluster_conductor.py:94) —NotSupported, CADF update failure, nothing changes and the client is not told. - Nothing changed → return (
magnum/conductor/handlers/cluster_conductor.py:115). - Only health changed → save the health fields, done (
magnum/conductor/handlers/cluster_conductor.py:125). - Else set the default worker nodegroup to the new size, call
driver.update_cluster(context, cluster, scale_manager, rollback), clusterUPDATE_IN_PROGRESS(magnum/conductor/handlers/cluster_conductor.py:146).
- Refuse unless the cluster is
- Error behaviour: driver exception → cluster
UPDATE_FAILEDwith the reason, the nodegroup size is restored (magnum/conductor/handlers/cluster_conductor.py:151). - Scale manager:
scale_manager.get_scale_managerasks the driver for a manager that picks the nodes to remove; the base driver returns none (magnum/drivers/common/driver.py:301).
cluster_delete (RPC cast)
- Flow: CADF
deletepending,driver.delete_cluster, clusterDELETE_IN_PROGRESS(magnum/conductor/handlers/cluster_conductor.py:174). Removal of the row is done later bysync_cluster_statuswhen the driver reportsDELETE_COMPLETE. - Error behaviour:
DELETE_FAILEDwith the reason, CADF failure (magnum/conductor/handlers/cluster_conductor.py:181). ADELETE_FAILEDcluster can be deleted again. Driver.pre_delete_clusterexists in the interface (magnum/drivers/common/driver.py:228) but the conductor never calls it in this branch [verified by search: no caller outside the drivers].
cluster_resize (RPC cast)
- Flow: allowed in
*_COMPLETE, **UPDATE_FAILEDandUPDATE_IN_PROGRESS** (magnum/conductor/handlers/cluster_conductor.py:205), so that the autoscaler can retry and stack requests. The nodegroup gets the new size andUPDATE_IN_PROGRESS, thendriver.resize_cluster(context, cluster, resize_manager, node_count, nodes_to_remove, nodegroup), clusterUPDATE_IN_PROGRESS. - Error behaviour: cluster and nodegroup
UPDATE_FAILED, size restored (magnum/conductor/handlers/cluster_conductor.py:250). - Concurrency: two resizes of the same nodegroup can run at once on two conductors; the last write wins [inferred: no lock,
nodegroup.save()without version check].
cluster_upgrade (RPC call)
- Flow: allowed in
*_COMPLETEstates only;driver.upgrade_cluster(context, cluster, cluster_template, max_batch_size, nodegroup, rollback); cluster and nodegroupUPDATE_IN_PROGRESS(magnum/conductor/handlers/cluster_conductor.py:301). - Error behaviour:
NotSupportedfrom the driver is returned to the caller without changing any status (magnum/conductor/handlers/cluster_conductor.py:306); any other exception setsUPDATE_FAILEDon both. - The new template is not recorded on the cluster by the conductor: it is the driver's job to set
cluster.cluster_template_id[inferred from the absence of an assignment inmagnum/conductor/handlers/cluster_conductor.py:266].
nodegroup_create / nodegroup_update / nodegroup_delete (RPC cast)
- Guard:
allowed_operationrefuses an operation when the nodegroup status is not in an allowed list (magnum/conductor/handlers/nodegroup_conductor.py:51); a nodegroup freshly built by the API has no status, so creation is never refused. The error message prints the cluster status instead of the nodegroup's (magnum/conductor/handlers/nodegroup_conductor.py:62). - create: cluster
UPDATE_IN_PROGRESS, insert nodegroupCREATE_IN_PROGRESS,driver.create_nodegroup(magnum/conductor/handlers/nodegroup_conductor.py:75). - update: cluster
UPDATE_IN_PROGRESS, nodegroupUPDATE_IN_PROGRESS,driver.update_nodegroup, then the nodegroup (with the patched min/max/labels/taints) is saved (magnum/conductor/handlers/nodegroup_conductor.py:97). - delete: cluster
UPDATE_IN_PROGRESS, nodegroupDELETE_IN_PROGRESS(not saved by the conductor),driver.delete_nodegroup(magnum/conductor/handlers/nodegroup_conductor.py:118). - Error behaviour: nodegroup
*_FAILEDwith the reason, clusterUPDATE_FAILED. No CADF notification for nodegroups (magnum/conductor/handlers/nodegroup_conductor.py:33).
CA and credential handlers (RPC call)
sign_certificate: reads the selected CA (Kubernetes by default) and signs the CSR with it (magnum/conductor/handlers/common/cert_manager.py:215). Nothing is stored.rotate_ca_certificate: status guard, then new CAs and client certificate are generated and the cluster references updated in memory, thendriver.rotate_ca_certificate. The old certificates are not deleted. On failure the cluster goes toUPDATE_FAILED(magnum/conductor/handlers/ca_conductor.py:100).credential_rotate: status guard, thendriver.rotate_credential;NotImplementedErrorbecomesNotSupported(magnum/conductor/handlers/credential_conductor.py:56). No status change by the conductor.
Versioned-object indirection (RPC call)
- Emitter: magnum-api when started with the
magnum-apibinary (magnum/cmd/api.py:65). Every@remotablemethod of a Magnum object (create,save,destroy,get_by_uuid,list…) is then executed by the conductor, which returns the changed fields (magnum/conductor/handlers/indirection_api.py:52). - Not used when the API runs under a WSGI server through
magnum/wsgi/api.py, which does not setindirection_api(magnum/wsgi/api.py:37): the API then opens its own database connections. The same deployment therefore has two database access patterns depending on how magnum-api is started. - Error behaviour: exceptions are wrapped in
ExpectedException(magnum/conductor/handlers/indirection_api.py:35) and re-raised in the API as<Class>_Remotewith the original message (magnum/common/exception.py:114).
Periodic tasks
All three are oslo.service periodic_tasks scheduled by a dynamic timer whose maximum sleep is [DEFAULT]/periodic_interval_max (60 s, magnum/conf/rpc.py:20), and are enabled by [DEFAULT]/periodic_enable (true, magnum/conf/rpc.py:17, magnum/common/rpc_service.py:51). They run with an **admin context with all_tenants** (magnum/service/periodic.py:39). An exception in one task is logged and the task runs again on the next cycle (magnum/service/periodic.py:144).
sync_cluster_status
- Trigger: every 10 s, also at start (
magnum/service/periodic.py:159). - Purpose: drive every cluster that is in progress towards its final state, and finish deletions.
- Data read:
SELECT * FROM cluster WHERE status IN ('CREATE_IN_PROGRESS','UPDATE_IN_PROGRESS','DELETE_IN_PROGRESS','ROLLBACK_IN_PROGRESS')(magnum/service/periodic.py:166,magnum/db/sqlalchemy/api.py:135). - Flow and effects, per cluster (
magnum/service/periodic.py:66), each in its own one-shot looping call:- Load the driver of the cluster's template and call
driver.update_cluster_status(ctx, cluster). The driver reads its back end (for the CAPI driver: the Cluster API objects on the management cluster) and updatescluster.status,status_reason, nodegroup statuses,api_address, addresses,coe_version. - If the status ends with
_COMPLETEor_FAILED, emit the CADF notificationmagnum.cluster.<create|update|delete>with outcomesuccessorfailure(magnum/service/periodic.py:77). - If
DELETE_COMPLETE: delete the Kubernetes CA and the client certificate from the cert manager (not the etcd and front-proxy CAs,magnum/conductor/handlers/common/cert_manager.py:240), destroy every nodegroup, then the cluster row (magnum/service/periodic.py:86).
- Load the driver of the cluster's template and call
- Concurrency and idempotency: no lock. With N conductor hosts, each in-progress cluster is polled N times every 10 s, and the deletion sequence can run concurrently on two hosts; the second one fails on a missing row (
ClusterNotFound, logged). Driver status reads must be idempotent. - Error behaviour: an exception stops that cluster's looping call (
stop_on_exception=True,magnum/service/periodic.py:179) and is retried 10 s later. A status ending in_COMPLETEthat is not instatus_to_event(for exampleRESUME_COMPLETE) raisesKeyError[inferred frommagnum/service/periodic.py:79]; drivers only set the standard states. - Cost: 1 query per cycle plus the driver's calls per in-progress cluster; nothing for clusters at rest. **The base driver's
update_cluster_statusdoes nothing** (magnum/drivers/common/driver.py:199): without a driver that implements it, clusters stay in progress forever.
sync_cluster_health_status
- Trigger: every
[kubernetes]/health_polling_intervals (60,magnum/conf/kubernetes.py:31), also at start (magnum/service/periodic.py:186). - Purpose: keep
health_status(HEALTHY,UNHEALTHY,UNKNOWN) andhealth_status_reasonup to date. - Data read: clusters in
CREATE_IN_PROGRESS,CREATE_FAILED,CREATE_COMPLETE,UPDATE_*,DELETE_IN_PROGRESS,DELETE_FAILED,ROLLBACK_IN_PROGRESS(magnum/service/periodic.py:194); then, per cluster, the driver's monitor (magnum/conductor/monitors.py:59). - Flow (with the Kubernetes monitor of
magnum/drivers/common/k8s_monitor.py, when the driver returns it):- Skip the cluster if labels
auto_healing_enabled=trueandauto_healing_controller=magnum-auto-healer: the in-cluster healer reports health throughPATCH /v1/clustersinstead (magnum/drivers/common/k8s_monitor.py:64). - If the API is not reachable from outside (no floating IP on the API load balancer or on the masters,
magnum/drivers/common/k8s_monitor.py:70), setUNKNOWNwith reason{"api": "The cluster <name> is not accessible."}. - Else
GET <api_address>/healthzandGET <api_address>/api/v1/nodeswith the Magnum client certificate (magnum/conductor/k8s_api.py:57);HEALTHYif healthz isokand every node'sReadycondition is true; the reason maps<node>.Readyto a boolean andapito the healthz answer (magnum/drivers/common/k8s_monitor.py:226). - Save the cluster if a status was computed (
magnum/service/periodic.py:121).
- Skip the cluster if labels
- Concurrency: same as above, no lock; every conductor host polls every cluster.
- Error behaviour: an exception in the monitor is logged as a warning and the previous health value is kept (
magnum/service/periodic.py:111). The HTTP requests have no timeout (magnum/conductor/k8s_api.py:44): an unreachable API server blocks that green thread until the TCP timeout. - Cost: per cluster and cycle, 2 HTTPS requests and, when
[cluster]/temp_cache_dirdoes not exist, 2 cert-manager reads (with Barbican: several secret reads) to write temporary certificate files (magnum/conductor/handlers/common/cert_manager.py:157). On a large fleet this is the main load Magnum puts on Barbican.
update_magnum_service
- Trigger: every run of the dynamic timer (no
spacing, so at most everyperiodic_interval_maxs) [inferred], also at start (magnum/servicegroup/magnum_service_periodic.py:39). - Purpose: heartbeat used by
GET /v1/mservices. - Flow: read
magnum_serviceby(host, binary), insert it the first time, thenreport_state_up(setslast_seen_up, incrementsreport_count) (magnum/servicegroup/magnum_service_periodic.py:44). - Consumer: the API marks a service
downwhen its last heartbeat is older thanservice_down_time(180 s) orforced_downis set (magnum/api/servicegroup.py:26).
Source: reverse-spec-magnum/05-async-processing_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.