REST API — magnum — branch stable/2026.2
Branch
stable/2026.2@1910928b7d9f— repository https://opendev.org/openstack/magnum — analysed on 2026-11-09
Magnum exposes one API version, v1, served by pecan + WSME (magnum/api/controllers/root.py:80, magnum/api/controllers/v1/__init__.py:180). Microversions run from 1.1 to 1.14 (magnum/api/controllers/versions.py:42, magnum/api/controllers/versions.py:43); the client selects one with OpenStack-API-Version: container-infra 1.14 (container-infrastructure-management and container-infrastructure are accepted too, magnum/api/controllers/versions.py:58). Without the header the request runs at 1.1 (magnum/api/controllers/v1/__init__.py:45). Every v1 response carries OpenStack-API-Minimum-Version, OpenStack-API-Maximum-Version, OpenStack-API-Version and Vary: OpenStack-API-Version (magnum/api/controllers/v1/__init__.py:231).
Reading guide: "Policy" is the oslo.policy rule enforced (defaults in magnum/common/policies/*.py; the check strings are in 07 § 6). "Admin" means a token whose context_is_admin rule passes (role:admin, magnum/common/policies/base.py:77): for most resources an admin sees every project (the handler sets context.all_tenants = True and enforces an extra *_all_projects rule). The error statuses come from the exception classes of 02-errors; every error body has the shape {"errors": [{"request_id": "", "code": "client|server", "status": N, "title": …, "detail": …, "links": []}]} (magnum/api/middleware/parsable_error.py:56).
Summary
| Method | Path | Handler | Policy | Version | Success | Errors |
|---|---|---|---|---|---|---|
| GET | / | magnum/api/controllers/root.py:91 get | none (public) | — | 200 | — |
| GET | /v1 | magnum/api/controllers/v1/__init__.py:193 get | none (public) | ≥1.1 | 200 | 406 |
| GET | /v1/clustertemplates | magnum/api/controllers/v1/cluster_template.py:325 get_all | clustertemplate:get_all (+ get_all_all_projects if admin) | ≥1.1 | 200 | 400, 403, 404 (marker) |
| GET | /v1/clustertemplates/detail | magnum/api/controllers/v1/cluster_template.py:342 detail | clustertemplate:detail (+ detail_all_projects if admin) | ≥1.1 | 200 | 400, 403, 404 |
| GET | /v1/clustertemplates/{ident} | magnum/api/controllers/v1/cluster_template.py:366 get_one | clustertemplate:get (skipped if public) | ≥1.1 | 200 | 403, 404, 409 |
| POST | /v1/clustertemplates | magnum/api/controllers/v1/cluster_template.py:407 post | clustertemplate:create, clustertemplate:publish | ≥1.1 | 201 + Location | 400, 403, 404, 409, 500 |
| PATCH | /v1/clustertemplates/{ident} | magnum/api/controllers/v1/cluster_template.py:485 patch | clustertemplate:update (+ update_all_projects), publish | ≥1.1 | 200 | 400, 403, 404, 409 |
| DELETE | /v1/clustertemplates/{ident} | magnum/api/controllers/v1/cluster_template.py:552 delete | clustertemplate:delete (+ delete_all_projects) | ≥1.1 | 204 | 400, 403, 404, 409 |
| GET | /v1/clusters | magnum/api/controllers/v1/cluster.py:400 get_all | cluster:get_all (+ get_all_all_projects) | ≥1.1 | 200 | 400, 403, 404 |
| GET | /v1/clusters/detail | magnum/api/controllers/v1/cluster.py:417 detail | cluster:detail (+ detail_all_projects) | ≥1.1 | 200 | 400, 403, 404 |
| GET | /v1/clusters/{ident} | magnum/api/controllers/v1/cluster.py:453 get_one | cluster:get (+ get_one_all_projects) | ≥1.1 | 200 | 403, 404, 409 |
| POST | /v1/clusters | magnum/api/controllers/v1/cluster.py:508 post (1.1–1.9), magnum/api/controllers/v1/cluster.py:518 post (≥1.10) | cluster:create | ≥1.1 | 202 {"uuid"} | 400, 403, 404, 409 |
| PATCH | /v1/clusters/{ident} | magnum/api/controllers/v1/cluster.py:617 (1.1–1.2), magnum/api/controllers/v1/cluster.py:637 (1.3–1.9), magnum/api/controllers/v1/cluster.py:659 (≥1.10) | cluster:update, cluster:update_health_status (+ update_all_projects) | ≥1.1 | 202 {"uuid"} | 400, 403, 404, 409 |
| DELETE | /v1/clusters/{ident} | magnum/api/controllers/v1/cluster.py:727 delete | cluster:delete (+ delete_all_projects) | ≥1.1 | 204 | 403, 404, 409 |
| POST | /v1/clusters/{ident}/actions/resize | magnum/api/controllers/v1/cluster_actions.py:88 (1.7–1.9), magnum/api/controllers/v1/cluster_actions.py:96 (≥1.10) | cluster:resize | ≥1.7 | 202 {"uuid"} | 400, 403, 404, 406 |
| POST | /v1/clusters/{ident}/actions/upgrade | magnum/api/controllers/v1/cluster_actions.py:146 (1.7: always 400), magnum/api/controllers/v1/cluster_actions.py:152 (≥1.8) | cluster:upgrade (+ upgrade_all_projects) | ≥1.7 | 202 {"uuid"} | 400, 403, 404, 406, 409, 500 |
| GET | /v1/clusters/{ident}/nodegroups | magnum/api/controllers/v1/nodegroup.py:393 get_all | nodegroup:get_all (+ get_all_all_projects) | ≥1.9 | 200 | 403, 404, 406 |
| GET | /v1/clusters/{ident}/nodegroups/{ng} | magnum/api/controllers/v1/nodegroup.py:431 get_one | nodegroup:get (+ get_one_all_projects) | ≥1.9 | 200 | 403, 404, 406 |
| POST | /v1/clusters/{ident}/nodegroups | magnum/api/controllers/v1/nodegroup.py:450 post | nodegroup:create | ≥1.9 | 202 + nodegroup body | 400, 403, 404, 406, 409 |
| PATCH | /v1/clusters/{ident}/nodegroups/{ng} | magnum/api/controllers/v1/nodegroup.py:510 patch | nodegroup:update | ≥1.9 | 202 + nodegroup body | 400, 403, 404, 406, 409 |
| DELETE | /v1/clusters/{ident}/nodegroups/{ng} | magnum/api/controllers/v1/nodegroup.py:525 delete | nodegroup:delete | ≥1.9 | 204 | 400, 403, 404, 406 |
| GET | /v1/certificates/{cluster_ident} | magnum/api/controllers/v1/certificate.py:148 get_one | certificate:get | ≥1.1 | 200 | 403, 404, 500 |
| POST | /v1/certificates | magnum/api/controllers/v1/certificate.py:163 post | certificate:create | ≥1.1 | 201 | 400, 403, 500 |
| PATCH | /v1/certificates/{cluster_ident} | magnum/api/controllers/v1/certificate.py:182 patch | certificate:rotate_ca | ≥1.1 (documented 1.5) | 202 {"uuid"} | 400, 403, 404 |
| PATCH | /v1/credentials/{cluster_ident} | magnum/api/controllers/v1/credential.py:50 patch | credential:rotate | ≥1.12 | 202 {"uuid"} | 400, 403, 404, 406 |
| GET | /v1/quotas | magnum/api/controllers/v1/quota.py:126 get_all | quota:get_all (admin only) | ≥1.1 | 200 | 400, 403 |
| GET | /v1/quotas/{project_id}/{resource} | magnum/api/controllers/v1/quota.py:151 get_one | quota:get | ≥1.1 | 200 | 403 |
| POST | /v1/quotas | magnum/api/controllers/v1/quota.py:176 post | quota:create (admin) | ≥1.1 | 201 | 400, 403, 404, 409 |
| PATCH | /v1/quotas/{project_id}/{resource} | magnum/api/controllers/v1/quota.py:196 patch | quota:update (admin) | ≥1.1 | 202 + quota body | 400, 403, 404 |
| DELETE | /v1/quotas/{project_id}/{resource} | magnum/api/controllers/v1/quota.py:213 delete | quota:delete (admin) | ≥1.1 | 204 | 403, 404 |
| GET | /v1/stats | magnum/api/controllers/v1/stats.py:49 get_all | stats:get_all | ≥1.1 (documented 1.4) | 200 | 400, 403 |
| GET | /v1/mservices | magnum/api/controllers/v1/magnum_services.py:92 get_all | magnum-service:get_all (admin) | ≥1.1 | 200 | 403 |
| GET | /v1/federations | magnum/api/controllers/v1/federation.py:208 get_all | federation:get_all | ≥1.1 | 200 | 400, 403 |
| GET | /v1/federations/detail | magnum/api/controllers/v1/federation.py:225 detail | federation:detail | ≥1.1 | 200 | 400, 403, 404 |
| GET | /v1/federations/{ident} | magnum/api/controllers/v1/federation.py:250 get_one | federation:get | ≥1.1 | 200 | 403, 404, 409 |
| POST | /v1/federations | magnum/api/controllers/v1/federation.py:265 post | federation:create | ≥1.1 | 202 {"uuid"} (never persisted, see below) | 400, 403, 404 |
| PATCH | /v1/federations/{ident} | magnum/api/controllers/v1/federation.py:302 patch | federation:update | ≥1.1 | 202 {"uuid"} | 400, 403, 404, 409 |
| DELETE | /v1/federations/{ident} | magnum/api/controllers/v1/federation.py:443 delete | federation:delete | ≥1.1 | 204 | 403, 404 |
| GET | /healthcheck | oslo.middleware Healthcheck (etc/magnum/api-paste.ini:4) | none (outside the auth pipeline) | — | 200 / 503 | — |
Every authenticated route can also return 401 (keystonemiddleware, etc/magnum/api-paste.ini:7) and 406 when the OpenStack-API-Version header is malformed or out of range (magnum/api/controllers/versions.py:104, magnum/api/controllers/v1/__init__.py:204). A route reached below its minimum microversion returns 406 with "the requested API … is not supported for this version" (magnum/api/controllers/base.py:113).
The route tree is magnum/api/controllers/v1/__init__.py:183–magnum/api/controllers/v1/__init__.py:190, plus the nodegroups (magnum/api/controllers/v1/cluster.py:396) and actions (magnum/api/controllers/v1/cluster.py:349) sub-controllers.
Common list parameters
All collections take marker (UUID of the last item seen; for quotas it is the integer id, magnum/api/controllers/v1/quota.py:124), limit (capped to [api]/max_limit, default 1000, a negative value is a 400 "Limit must be positive", magnum/api/utils.py:41), sort_key (default id) and sort_dir (asc/desc, anything else is a 400, magnum/api/utils.py:51). An unknown sort_key is a 400 InvalidParameterValue (magnum/db/sqlalchemy/api.py:98). Responses carry a next link when the page is full.
Identification by UUID or name
{ident} is a UUID or a name (magnum/api/utils.py:110, magnum/api/utils.py:112). Names are not unique in the database (no unique constraint on cluster.name or cluster_template.name, magnum/db/sqlalchemy/models.py:97); a name that matches several rows returns 409 "Multiple clusters exist with same name. Please use the cluster uuid instead." (magnum/db/sqlalchemy/api.py:205). Lookups are filtered by the caller's project unless the caller is admin and all_tenants was set (magnum/db/sqlalchemy/api.py:110): another project's resource is a 404, not a 403.
Cluster templates
A cluster template is the reusable recipe of a cluster: COE (kubernetes only, magnum/objects/fields.py:101), image, flavors, networks, network and volume drivers, labels, TLS and load-balancer choices, and since the CAPI era the **driver** that will build the clusters.
POST /v1/clustertemplates
Functional description
- Purpose: register a template that users of the project (or of every project if public) can instantiate as clusters.
- Actor: project member (
clustertemplate:create,admin_or_project_member_deny_cluster_user,magnum/common/policies/cluster_template.py:22). Making itpublicorhiddenadditionally needsclustertemplate:publish(admin only,magnum/common/policies/cluster_template.py:151). - Preconditions: the Glance image exists, is visible to the caller and carries an
os_distroproperty; flavors, external network, fixed network and subnet, and keypair (if given) exist; a Magnum driver is installed that matches the template. - Business rules (decorators run outermost first, then the body):
server_typemust bevmorbm(magnum/api/validation.py:359, enforced bymagnum/api/validation.py:201).network_driverdefaults to[cluster_template]/kubernetes_default_network_driver(flannel); it must be one offlannel,calico,ciliumand be allowed by[cluster_template]/kubernetes_allowed_network_drivers(defaultflannel, calico: cilium is refused by default) (magnum/api/validation.py:184,magnum/api/validation.py:358,magnum/conf/cluster_templates.py:21).volume_driver, if set, must becinder(magnum/api/validation.py:365).- With
docker_storage_driver=devicemapper,docker_volume_sizemust be ≥ 3 GB (magnum/api/validation.py:256). - Driver resolution (
magnum/api/validation.py:66): ifcluster_distroordriveris missing, the image is read from Glance;cluster_distrocomes from the image'sos_distro, anddriverfrom the image propertymagnum_driver, else[drivers]/default_driver, else the alphabetically first installed driver (magnum/drivers/common/driver.py:166). The resolved driver must be loadable (ClusterDriverNotSupported, 400) and is written back into the template (magnum/api/validation.py:93). - OpenStack resources are checked: image (
os_distromandatory,magnum/api/attr_validator.py:32), flavors (by listing all Nova flavors,magnum/api/attr_validator.py:59), external network (must berouter:external,magnum/api/attr_validator.py:83), fixed network and subnet, and labels (magnum/api/attr_validator.py:232). cluster_distrois always overwritten from the image'sos_distro(magnum/api/controllers/v1/cluster_template.py:421).- If
labels.boot_volume_size(or[cinder]/default_boot_volume_size, default 0) is 0, the flavor must have a non-zero root disk (magnum/api/attr_validator.py:181). - A missing
nameis generated as<word>-<n>-template(magnum/api/controllers/v1/cluster_template.py:277).
- Flow: all synchronous. The row is inserted (
magnum/api/controllers/v1/cluster_template.py:474); [inferred] when magnum-api runs as themagnum-apibinary, the insert goes through the conductor over RPC (versioned-object indirection,magnum/cmd/api.py:65); under uWSGI (magnum/wsgi/api.py:37) the API process writes to the database itself. - Outcome: 201 with the template and a
Locationheader (magnum/api/controllers/v1/cluster_template.py:476). - Limitations: with
coe=kubernetesand an image whoseos_distroiscoreos, the handler referencesself._coreos_deprecation_note, which is not defined anywhere in the code (magnum/api/controllers/v1/cluster_template.py:462): the request fails with a 500 (AttributeError).
Technical sheet
- Request (WSME type
magnum/api/controllers/v1/cluster_template.py:45):coe(mandatory,kubernetes),image_id(mandatory),name(1–255),flavor_id,master_flavor_id,dns_nameserver(IP or comma-separated list),keypair_id,external_network_id,fixed_network,fixed_subnet,network_driver,apiserver_port(1024–65535),docker_volume_size(≥1),cluster_distro,http_proxy,https_proxy,no_proxy,volume_driver,registry_enabled(default false),labels(dict of str/int/bool/float),tls_disabled(false),public(false),server_type(vm),insecure_registry,docker_storage_driver,master_lb_enabled(false),floating_ip_enabled(true),hidden(false),tags(0–255),driver. - Response: the same fields plus
uuid,project_id,user_id,created_at,updated_at,links. - Errors: 400
InvalidParameterValue,ImageNotFound,OSDistroFieldNotFound,FlavorNotFound,ExternalNetworkNotFound,FixedNetworkNotFound,FixedSubnetNotFound,ClusterDriverNotSupported,ClusterTypeNotSupported,FlavorZeroRootVolumeNotSupported; 403PolicyNotAuthorized,ImageNotAuthorized,ClusterTemplatePublishDenied; 409Conflict(several images/networks with the same name),ClusterTemplateAlreadyExists; 500 (coreos case). - Spec/code mismatches: the api-ref does not document
driver,hiddenortagsfor every microversion [inferred fromapi-ref/source/clustertemplates.inc].
GET /v1/clustertemplates, /detail, /{ident}
- Purpose: list or read the templates the caller may use.
- Visibility rules (
magnum/db/sqlalchemy/api.py:294): the caller's project templates, plus everypublic=True, hidden=Falsetemplate, plus for an admin the public hidden ones. A hidden template of another project read by a non-admin is a 404 (magnum/api/controllers/v1/cluster_template.py:389). A public template is returned without evaluatingclustertemplate:get(magnum/api/controllers/v1/cluster_template.py:394). GET /v1/clustertemplatesreturns the full objects (no field trimming);/detailreturns the same body./detailunder another parent path is a 404 (magnum/api/controllers/v1/cluster_template.py:357).
PATCH /v1/clustertemplates/{ident}
- Purpose: change a template (JSON Patch, RFC 6902, list of
{op, path, value}). - Business rules:
addon a root attribute that does not exist, or on one that already has a value, is refused (magnum/api/utils.py:77,magnum/api/utils.py:81);replaceof/labelsaccepts a dict or a string parsed withast.literal_eval(magnum/api/utils.py:85)./network_driver,/external_network_id,/tls_disabled,/public,/server_type,/coe,/registry_enabled,/cluster_distro,/hiddencannot be removed (magnum/api/controllers/v1/cluster_template.py:232).- Network and volume drivers are re-validated on the patched document (
magnum/api/validation.py:164,magnum/api/validation.py:226), then the OpenStack resources (magnum/api/controllers/v1/cluster_template.py:512). - Changing
publicorhiddenneedsclustertemplate:publish(magnum/api/controllers/v1/cluster_template.py:515). - A template used by at least one cluster can only be renamed or (un)published: any other change raises
ClusterTemplateReferenced(400) inside the database transaction (magnum/db/sqlalchemy/api.py:446). uuidcannot change (magnum/db/sqlalchemy/api.py:429).
- Outcome: 200 with the updated template (synchronous).
DELETE /v1/clustertemplates/{ident}
- Purpose: remove a template that no cluster uses.
- Rule: refused with 400
ClusterTemplateReferencedwhile a cluster references it (magnum/db/sqlalchemy/api.py:420). 204 otherwise.
Clusters
POST /v1/clusters
Functional description
- Purpose: request a new Kubernetes cluster built by the template's driver.
- Actor: project member (
cluster:create,magnum/common/policies/cluster.py:22). - Preconditions: the template exists and is visible; a driver is installed for
(server_type, cluster_distro, coe); the project is under its cluster quota. - Business rules:
- (1.1–1.9 only)
node_count = 0is refused withZeroNodeCountNotSupported(magnum/api/controllers/v1/cluster.py:509), and a missing template becomes a 400 instead of a 404 (magnum/api/validation.py:37). From 1.10 a missing template is a 404. - The driver must exist for the template's
(server_type, cluster_distro, coe)(magnum/api/validation.py:51). **This check ignores the template'sdriverfield** (get_driveris called with three arguments), while the conductor later loads the driver by name (magnum/drivers/common/driver.py:196): with several installed drivers providing the same tuple, the API may validate one driver and the conductor use another. - Volume size rule for devicemapper (
magnum/api/validation.py:121). master_countmust be odd and ≥ 1 by default; the driver may overridevalidate_master_size(magnum/api/validation.py:104,magnum/drivers/common/driver.py:253).- Quota: the project's
Clusterhard limit (row inquotas, else[quotas]/max_clusters_per_project, default 20) must be strictly greater than the number of clusters the caller can see (magnum/api/controllers/v1/cluster.py:484). The count includes clusters in any status, and the new row is only inserted later by the conductor: concurrent requests can exceed the quota. - A hidden template can only be used by an admin (
magnum/api/controllers/v1/cluster.py:534, 403NotAuthorized). - Inheritance from the template (
magnum/api/controllers/v1/cluster.py:538–magnum/api/controllers/v1/cluster.py:574):keypair←keypair_id;labels← template labels when absent or empty, or template labels updated with the given ones whenmerge_labels=true(otherwise the given labels replace the template's);floating_ip_enabled,master_lb_enabled,docker_volume_size,master_flavor_id,flavor_id,fixed_network,fixed_subnet← template when unset or empty. - A deprecation warning is logged when
volume_driver=cinderandlabels.cinder_csi_enabledis false (magnum/api/controllers/v1/cluster.py:554). - Flavor root disk vs
boot_volume_sizefor both flavors (magnum/api/controllers/v1/cluster.py:580), OpenStack resources (keypair included,magnum/api/attr_validator.py:169), and **master_count > 1requiresmaster_lb_enabled** (magnum/api/attr_validator.py:173). namedefaults to<word>-<n>-cluster(magnum/api/controllers/v1/cluster.py:351); it must match^[a-zA-Z][a-zA-Z0-9_.-]*$, 1–242 characters (magnum/api/controllers/v1/cluster.py:74).create_timeoutdefaults to 60 minutes (magnum/api/controllers/v1/cluster.py:107).
- (1.1–1.9 only)
- Flow:
- (synchronous) validations above, UUID generated by the API (
magnum/api/controllers/v1/cluster.py:606). - (synchronous) RPC cast
cluster_createto the conductor (magnum/api/controllers/v1/cluster.py:607), response 202 with the UUID. Nothing is written to the database by the API. - (asynchronous, conductor) insert the cluster in
CREATE_IN_PROGRESS, then the two default nodegroupsdefault-masteranddefault-worker(magnum/conductor/handlers/cluster_conductor.py:47–magnum/conductor/handlers/cluster_conductor.py:58); generate three CAs and the Magnum client certificate (magnum/conductor/handlers/common/cert_manager.py:99); send the CADF notificationmagnum.cluster.createpending; call the driver'screate_cluster(magnum/conductor/handlers/cluster_conductor.py:71); copy the cluster'sstack_idinto each nodegroup. See 05 § cluster_create. - (asynchronous, periodic)
sync_cluster_statusasks the driver every 10 s to update the status untilCREATE_COMPLETEorCREATE_FAILED(05).
- (synchronous) validations above, UUID generated by the API (
- Outcome: 202
{"uuid": "…"}. AGETright after the 202 can return 404 until the conductor has inserted the row. Final stateCREATE_COMPLETE(driver) orCREATE_FAILEDwithstatus_reason. - Effects visible elsewhere: certificates stored in Barbican (default
[certificates]/cert_manager_type = barbican,magnum/conf/certificates.py:16) or in thex509keypairtable; everything the driver creates (servers, networks, load balancer, CAPI objects — see 08). - Limitations: if the cast is lost (RabbitMQ down after the 202), the cluster never exists and nothing reports it. Any error raised before
cluster.create()in the conductor (e.g. aDBDuplicateEntry) is only logged.
Technical sheet
- Request (
magnum/api/controllers/v1/cluster.py:64):cluster_template_id(mandatory, UUID or name),name,keypair,node_count(≥0, default 1),master_count(≥1, default 1),docker_volume_size(≥1),labels,merge_labels(false),master_flavor_id,flavor_id,create_timeout(≥0, default 60),discovery_url,fixed_network,fixed_subnet,floating_ip_enabled,master_lb_enabled. - Response:
202 {"uuid": "<uuid>"}. - Example:
json POST /v1/clusters OpenStack-API-Version: container-infra 1.14 {"name": "k8s-prod", "cluster_template_id": "k8s-1.31-capi", "master_count": 3, "node_count": 4, "keypair": "ops", "labels": {"kube_tag": "v1.31.4"}, "merge_labels": true} → 202 {"uuid": "5d12f6fd-a196-4bf0-ae4c-1f639a523a52"}
GET /v1/clusters, /v1/clusters/detail
- Purpose: list the project's clusters (all projects for an admin,
magnum/api/controllers/v1/cluster.py:362). GET /v1/clustersreturns a trimmed cluster:uuid, name, cluster_template_id, keypair, docker_volume_size, labels, node_count, status, master_flavor_id, flavor_id, create_timeout, master_count, health_statusandstack_id(<1.13) orcluster_id(≥1.13) (magnum/api/controllers/v1/cluster.py:214)./detailreturns everything, including the label diff fields (below).- The page prefetches the templates once per distinct template (
magnum/api/controllers/v1/cluster.py:308).
GET /v1/clusters/{ident}
- Purpose: read one cluster with its derived fields.
- Derived fields:
node_count,master_count,node_addresses,master_addressesare sums over nodegroups by role (magnum/objects/cluster.py:165–magnum/objects/cluster.py:181), not columns.labels_overridden,labels_added,labels_skipped: the difference between the cluster labels and its template labels (magnum/api/controllers/v1/cluster.py:221).faults: only when the cluster status is one of the*_FAILED; it maps each failed nodegroup's name to itsstatus_reason(magnum/api/controllers/v1/cluster.py:441,magnum/api/controllers/v1/cluster.py:479).- From 1.13:
cluster_id(same column asstack_id) replacesstack_id(magnum/api/controllers/v1/cluster.py:211,magnum/api/controllers/v1/cluster.py:240).
- Response fields:
uuid, name, cluster_template_id, keypair, node_count, master_count, docker_volume_size, labels, master_flavor_id, flavor_id, create_timeout, links, stack_id|cluster_id, status, status_reason, health_status (HEALTHY|UNHEALTHY|UNKNOWN), health_status_reason (dict), discovery_url, api_address, coe_version, container_version, project_id, user_id, node_addresses, master_addresses, faults, fixed_network, fixed_subnet, floating_ip_enabled, merge_labels, labels_overridden, labels_added, labels_skipped, master_lb_enabled, created_at, updated_at.
PATCH /v1/clusters/{ident}
Functional description
- Purpose: change the size of the default worker nodegroup, or report a health status (used by the in-cluster
magnum-auto-healer). - Actor: project member (
cluster:update), andcluster:update_health_statusis always enforced (magnum/api/controllers/v1/cluster.py:685), even when the patch does not touch health. - Business rules:
- Only
node_count,health_status,health_status_reasonmay change; any other changed field is a 400 "cannot change cluster property(ies) …" (magnum/api/validation.py:32,magnum/api/validation.py:264).api_address,node_addresses,master_addresses,stack_id,cluster_idand the certificate references are internal and cannot be patched at all (magnum/api/controllers/v1/cluster.py:283). node_counttargets the default worker nodegroup, not the whole cluster (magnum/api/controllers/v1/cluster.py:718): without/node_countin the patch the current default-worker size is sent; with it, the patched value (magnum/api/controllers/v1/cluster.py:721). Note thatGETreturnsnode_countas the sum of all worker nodegroups (magnum/objects/cluster.py:389), so a client that reads, increments and patchesnode_counton a cluster with extra nodegroups resizes the default worker nodegroup to the wrong value.- 1.1–1.9:
node_count = 0is a 400 (magnum/api/controllers/v1/cluster.py:626). - 1.3+: query parameter
rollback=trueis forwarded to the driver (magnum/api/controllers/v1/cluster.py:637).
- Only
- Flow: RPC cast
cluster_update(magnum/api/controllers/v1/cluster.py:669) and 202. The conductor refuses (silently, from the client's point of view) a cluster that is not in a*_COMPLETEstate (magnum/conductor/handlers/cluster_conductor.py:94), saves only the health fields when the size does not change, or else sets the worker nodegroup size, calls the driver'supdate_clusterand moves the cluster toUPDATE_IN_PROGRESS(05). - Outcome: 202
{"uuid"}, thenUPDATE_COMPLETE/UPDATE_FAILEDthrough the status poller. - Limitations: with the magnum-cluster-api driver, a size change always ends in
UPDATE_FAILED(the driver'supdate_clusterraisesNotImplementedError,magnum-cluster-api:magnum_cluster_api/driver.py:329); useactions/resize. A health-only patch still works, because it never reaches the driver.
DELETE /v1/clusters/{ident}
- Purpose: tear down a cluster and everything its driver created.
- Flow: RPC cast
cluster_delete(magnum/api/controllers/v1/cluster.py:742) and 204 — even though the deletion is asynchronous. The conductor calls the driver'sdelete_clusterand setsDELETE_IN_PROGRESS(magnum/conductor/handlers/cluster_conductor.py:166). When the driver reportsDELETE_COMPLETE, the poller deletes the certificates, the nodegroups and the row (magnum/service/periodic.py:86). - Outcome: 204; the cluster remains visible in
DELETE_IN_PROGRESSuntil the poller removes it. There is no status check: deleting a cluster that is being created is allowed.
POST /v1/clusters/{ident}/actions/resize
Functional description
- Purpose: set the number of nodes of a nodegroup (default: the default worker nodegroup), optionally naming the servers to remove. It is the endpoint the Kubernetes cluster-autoscaler's Magnum provider calls.
- Actor:
cluster:resize. No admin all-projects path: an admin can only resize clusters of the project in its token, becauseall_tenantsis not set (magnum/api/controllers/v1/cluster_actions.py:104). - Business rules:
node_countmandatory, ≥ 0; 0 is refused before 1.10 (magnum/api/controllers/v1/cluster_actions.py:89).- For a non-master nodegroup,
min_node_count ≤ node_count ≤ max_node_count(whenmax_node_countis set), else 400NGResizeOutBounds(magnum/api/controllers/v1/cluster_actions.py:120). - For the master nodegroup, the driver's
validate_master_resizedecides; the base driver always refuses (MasterNGResizeNotSupported,magnum/drivers/common/driver.py:258).
- Flow: RPC cast
cluster_resize(magnum/api/controllers/v1/cluster_actions.py:136), 202. The conductor accepts the cluster also inUPDATE_IN_PROGRESSandUPDATE_FAILEDso that the autoscaler can retry (magnum/conductor/handlers/cluster_conductor.py:198). - Request:
{"node_count": 5, "nodes_to_remove": ["<server-id>"], "nodegroup": "default-worker"}. Response:202 {"uuid"}.
POST /v1/clusters/{ident}/actions/upgrade
Functional description
- Purpose: move a cluster (or one nodegroup) to another template, typically a newer Kubernetes version.
- Actor:
cluster:upgrade(+upgrade_all_projectsfor admins). - Business rules:
- At 1.7 the action exists but always returns 400
ClusterUpgradeNotSupported(magnum/api/controllers/v1/cluster_actions.py:147). cluster_templatenames the target template (UUID or name). A non-default nodegroup can only be upgraded to the cluster's current template (magnum/api/controllers/v1/cluster_actions.py:181, 409InvalidClusterTemplateForUpgrade).max_batch_size(≥1) is passed to the driver.
- At 1.7 the action exists but always returns 400
- Flow: unlike every other cluster action, this is an RPC call (
magnum/api/controllers/v1/cluster_actions.py:189→magnum/conductor/api.py:84): the HTTP request waits for the conductor and the driver'supgrade_cluster. A driver or status error comes back to the client (400NotSupported"Upgrading a cluster when status is …",magnum/conductor/handlers/cluster_conductor.py:285), and a slow driver can exceedrpc_response_timeout(500MessagingTimeout). Thecluster_upgrade_asynchelper is also acall(magnum/conductor/api.py:94). - Outcome: 202
{"uuid"}once the driver has accepted the upgrade; cluster and nodegroup inUPDATE_IN_PROGRESS.
Nodegroups (microversion ≥ 1.9)
A nodegroup is a set of nodes with the same role, flavor, image and labels. Each cluster has two default nodegroups (default-master, default-worker) created with it (magnum/conductor/utils.py:157); additional worker nodegroups can be added.
POST /v1/clusters/{ident}/nodegroups
- Purpose: add a worker nodegroup to a running cluster.
- Actor:
nodegroup:create(magnum/common/policies/nodegroup.py:72). The rule is evaluated without a target, i.e. against the caller's own project (magnum/api/controllers/v1/nodegroup.py:457,magnum/common/policy.py:97); isolation relies on the project filter of the cluster lookup. - Business rules:
- The cluster must have an
api_address(409ClusterAPIAddressUnavailable,magnum/api/controllers/v1/nodegroup.py:462). - The name must be unique in the cluster (409
NodeGroupAlreadyExists,magnum/api/controllers/v1/nodegroup.py:465). min_node_count ≤ node_count ≤ max_node_count(409NodeGroupInvalidInput,magnum/api/controllers/v1/nodegroup.py:157).node_labelsinkubernetes.io/ork8s.io/namespaces are refused unless their prefix iskubelet.kubernetes.ioornode.kubernetes.ioor the key is in the kubelet's allowed list (magnum/api/controllers/v1/nodegroup.py:46,magnum/api/controllers/v1/nodegroup.py:78).node_taintsis a list of{key, value?, effect}with effectNoSchedule|PreferNoSchedule|NoExecute(magnum/api/controllers/v1/nodegroup.py:103).node_labelsandnode_taintsare forbidden on a master nodegroup (magnum/api/controllers/v1/nodegroup.py:138), and creating a master nodegroup is refused anyway (400CreateMasterNodeGroup,magnum/api/controllers/v1/nodegroup.py:473).- Defaults:
image_id← template image,flavor_id← cluster flavor,labels← cluster labels (or merged withmerge_labels=true) (magnum/api/controllers/v1/nodegroup.py:477); root disk vsboot_volume_sizecheck.
- The cluster must have an
- Flow: RPC cast
nodegroup_create, 202 with the nodegroup as built by the API (UUID set, no status yet) (magnum/api/controllers/v1/nodegroup.py:504). The conductor moves the cluster toUPDATE_IN_PROGRESS, inserts the nodegroup inCREATE_IN_PROGRESSand calls the driver (magnum/conductor/handlers/nodegroup_conductor.py:73). - Spec/code mismatch:
node_labelsandnode_taintsare documented as microversion 1.14 (magnum/api/rest_api_version_history.rst:123), but no handler checks the version: they are accepted and returned from 1.9 on.
PATCH /v1/clusters/{ident}/nodegroups/{ng}
- Purpose: change
min_node_count,max_node_count,node_labels,node_taints. Every other field is internal (magnum/api/controllers/v1/nodegroup.py:326);node_countchanges go throughactions/resize. - Flow: the same validations as creation, then cast
nodegroup_updateand 202 with the patched nodegroup (magnum/api/controllers/v1/nodegroup.py:519). The patched values are not saved by the API: the conductor saves them after the driver call (magnum/conductor/handlers/nodegroup_conductor.py:105).
DELETE /v1/clusters/{ident}/nodegroups/{ng}
- Rule: a default nodegroup cannot be deleted (400
DeletingDefaultNGNotSupported,magnum/api/controllers/v1/nodegroup.py:535). - Flow: cast
nodegroup_delete, 204. The conductor sets the cluster toUPDATE_IN_PROGRESSand calls the driver'sdelete_nodegroup; the nodegroup row is removed by the driver or its status sync [inferred: the conductor never destroys it,magnum/conductor/handlers/nodegroup_conductor.py:116].
GET /v1/clusters/{ident}/nodegroups[/{ng}]
- The list returns trimmed nodegroups (
uuid, name, flavor_id, node_count, role, is_default, image_id, status, stack_id,magnum/api/controllers/v1/nodegroup.py:302) and acceptsroleas a filter; a non-admin list is also filtered by project (magnum/api/controllers/v1/nodegroup.py:416). The single read addslinksand the label diff with the cluster labels (magnum/api/controllers/v1/nodegroup.py:313).
Certificates and credentials
GET /v1/certificates/{cluster_ident}?ca_cert_type=
- Purpose: download the cluster CA certificate (PEM) to build a kubeconfig.
ca_cert_type=etcd,front_proxy/front-proxy, or absent for the Kubernetes CA (magnum/conductor/handlers/common/cert_manager.py:131). - Flow: RPC call
get_ca_certificate(magnum/api/controllers/v1/certificate.py:158); the conductor reads the certificate from the cert manager (Barbican, local files or thex509keypairtable). - Response:
{"cluster_uuid": …, "pem": "-----BEGIN CERTIFICATE-----…", "links": […]}.
POST /v1/certificates
- Purpose: have the cluster CA sign a CSR — the user obtains a client certificate for the Kubernetes API.
- Rules:
cluster_uuid(UUID or name) must exist — a missing cluster is turned into a 400 (magnum/api/controllers/v1/certificate.py:66);csrmandatory;ca_cert_typeoptional (defaultkubernetes,magnum/conductor/handlers/ca_conductor.py:48). - Signing rules (
magnum/common/x509/operations.py:176): SHA-256, validity[x509]/term_of_validitydays (default 5 years,magnum/conf/x509.py:50), CA extensions refused unless[x509]/allow_ca, extensions and key usages filtered by[x509]/allowed_extensionsandallowed_key_usage(400CertificateValidationError). - Flow: RPC call
sign_certificate; 201 withpem. Nothing is stored.
PATCH /v1/certificates/{cluster_ident}
- Purpose: rotate the cluster CA (revoke every previously issued client certificate).
- Rules: refused for a template with
tls_disabled(400,magnum/api/controllers/v1/certificate.py:187); the conductor requires a*_COMPLETEstatus (magnum/conductor/handlers/ca_conductor.py:83). - Flow: RPC call
rotate_ca_certificate: new CAs are generated and stored, then the driver'srotate_ca_certificateis called — the base driver raisesNotSupported(magnum/drivers/common/driver.py:306), which sets the cluster to **UPDATE_FAILED** and returns 400 (magnum/conductor/handlers/ca_conductor.py:100). With a driver that supports it the cluster goes toUPDATE_IN_PROGRESSand the response is 202. - Spec/code mismatch: the api-ref documents
PATCH /v1/certificates/without the cluster in the path; the code requires/v1/certificates/{cluster_ident}.
PATCH /v1/credentials/{cluster_ident} (≥ 1.12)
- Purpose: have the driver rotate the cloud credential the cluster uses (for CAPI drivers, the application credential given to the cloud controller manager and the CSI driver).
- Actor:
credential:rotate=project_member_deny_cluster_user— admins are not allowed unless they are members of the project (magnum/common/policies/credential.py:20). - Flow: RPC call
credential_rotate(magnum/api/controllers/v1/credential.py:63); 400 if the cluster is not in an allowed state (magnum/conductor/handlers/credential_conductor.py:47) or the driver does not implement it (magnum/conductor/handlers/credential_conductor.py:56). 202 when done.
Quotas, stats, services
/v1/quotas
- Purpose: per-project limit on the number of clusters (the only resource is
Cluster,magnum/objects/fields.py:112). POST(admin):project_idmust exist in Keystone (404ProjectNotFound,magnum/api/validation.py:144),hard_limit≥ 0 (default 1),resource=Cluster; a duplicate is 409QuotaAlreadyExists.PATCH /{project_id}/{resource}(admin) returns 202 with the updated quota although it is synchronous (magnum/api/controllers/v1/quota.py:194); 404 if no explicit quota.GET /{project_id}/{resource}: a non-admin may only read its own project (403,magnum/api/controllers/v1/quota.py:160); without an explicit row the response is synthesised withhard_limit = [quotas]/max_clusters_per_project(magnum/api/controllers/v1/quota.py:168).GET /v1/quotas:quota:get_allis admin-only by default, so the non-admin branch of the handler (magnum/api/controllers/v1/quota.py:141) is unreachable;all_tenants=truelists every project.DELETE /{project_id}/{resource}: 204, 404 if absent.
GET /v1/stats?project_id=&type=cluster
- Purpose: count clusters and nodes.
typemust becluster(400 otherwise). A non-admin gets its own project, and asking for another one is 403 (magnum/api/controllers/v1/stats.py:66). Response{"clusters": N, "nodes": M}, where nodes is the sum ofnodegroup.node_count(magnum/db/sqlalchemy/api.py:221).
GET /v1/mservices
- Purpose: list the Magnum services (in practice
magnum-conductorprocesses) withstateup/down, computed fromlast_seen_upagainst[DEFAULT]/service_down_time(180 s,magnum/api/servicegroup.py:26,magnum/conf/utils.py:38). Admin only.
Federations
The federation API is exposed and validated but not implemented: the three conductor methods raise NotImplementedError (magnum/conductor/handlers/federation_conductor.py:25, magnum/conductor/handlers/federation_conductor.py:28, magnum/conductor/handlers/federation_conductor.py:31).
POST /v1/federationsvalidateshostcluster_id(must be an existing cluster) andproperties.dns-zone(mandatory,magnum/api/attr_validator.py:219), then castsfederation_createand returns 202 with a UUID that will never exist: the row is never inserted, because insertion was the conductor's job.PATCH(join/unjoin byadd/removeon/member_ids, rewritten intoreplace,magnum/api/controllers/v1/federation.py:327) andDELETEcast to the same unimplemented handlers. They can only act on rows inserted by other means.- The
GETroutes read thefederationtable normally.
A rewrite can drop the federation API or keep it as "always empty".
Lifecycle: cluster
stateDiagram-v2 [*] --> CREATE_IN_PROGRESS: cast cluster_create (conductor inserts the row) CREATE_IN_PROGRESS --> CREATE_COMPLETE: driver.update_cluster_status (periodic, 10 s) CREATE_IN_PROGRESS --> CREATE_FAILED: exception in conductor or driver status CREATE_COMPLETE --> UPDATE_IN_PROGRESS: PATCH node_count / resize / upgrade / nodegroup create-update-delete / CA rotation UPDATE_COMPLETE --> UPDATE_IN_PROGRESS: same UPDATE_IN_PROGRESS --> UPDATE_COMPLETE: driver.update_cluster_status UPDATE_IN_PROGRESS --> UPDATE_FAILED: driver error or status UPDATE_FAILED --> UPDATE_IN_PROGRESS: resize, nodegroup operations (allowed from UPDATE_FAILED) UPDATE_IN_PROGRESS --> ROLLBACK_IN_PROGRESS: driver (rollback=true) [inferred] ROLLBACK_IN_PROGRESS --> ROLLBACK_COMPLETE: driver.update_cluster_status ROLLBACK_IN_PROGRESS --> ROLLBACK_FAILED: driver.update_cluster_status CREATE_COMPLETE --> DELETE_IN_PROGRESS: DELETE (cast cluster_delete) CREATE_FAILED --> DELETE_IN_PROGRESS: DELETE UPDATE_FAILED --> DELETE_IN_PROGRESS: DELETE CREATE_IN_PROGRESS --> DELETE_IN_PROGRESS: DELETE (no status check) DELETE_IN_PROGRESS --> DELETE_FAILED: driver error DELETE_IN_PROGRESS --> [*]: DELETE_COMPLETE seen by the poller → certificates, nodegroups and row deleted
RESUME_*, RESTORE_COMPLETE, SNAPSHOT_COMPLETE, CHECK_COMPLETE, ADOPT_COMPLETE are legacy Heat stack states (magnum/objects/fields.py:28–magnum/objects/fields.py:36); they are accepted as "stable" states by the conductor but no code in Magnum sets them.
Lifecycle: nodegroup
stateDiagram-v2 [*] --> CREATE_IN_PROGRESS: cluster_create (default nodegroups) or nodegroup_create (cast) CREATE_IN_PROGRESS --> CREATE_COMPLETE: driver status sync [inferred] CREATE_IN_PROGRESS --> CREATE_FAILED: driver.create_nodegroup raised CREATE_COMPLETE --> UPDATE_IN_PROGRESS: resize / nodegroup PATCH / upgrade UPDATE_IN_PROGRESS --> UPDATE_COMPLETE: driver status sync [inferred] UPDATE_IN_PROGRESS --> UPDATE_FAILED: driver raised CREATE_COMPLETE --> DELETE_IN_PROGRESS: nodegroup DELETE (cast) DELETE_IN_PROGRESS --> DELETE_FAILED: driver.delete_nodegroup raised DELETE_IN_PROGRESS --> [*]: driver removes the row [inferred]
The conductor only sets the *_IN_PROGRESS and *_FAILED nodegroup states; the *_COMPLETE states and the removal of deleted nodegroups are the driver's job (magnum/drivers/common/driver.py:199). For the Cluster API driver see 08.
Spec/code mismatches (summary)
| Item | api-ref | Code |
|---|---|---|
Nodegroups, federations, credentials, /detail routes | not documented | implemented (magnum/api/controllers/v1/nodegroup.py:357, magnum/api/controllers/v1/federation.py:250, magnum/api/controllers/v1/credential.py:50) |
| CA rotation path | PATCH /v1/certificates/ | PATCH /v1/certificates/{cluster_ident} (magnum/api/controllers/v1/certificate.py:182) |
node_labels / node_taints | microversion 1.14 | accepted from 1.9 |
| Federation create | 202, federation created | 202, nothing persisted |
| Quota PATCH | — | 202 for a synchronous update |
Source: reverse-spec-magnum/01-rest-api_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.