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

Reverse-spec · Services

magnum — REST API

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

MethodPathHandlerPolicyVersionSuccessErrors
GET/magnum/api/controllers/root.py:91 getnone (public)—200—
GET/v1magnum/api/controllers/v1/__init__.py:193 getnone (public)≥1.1200406
GET/v1/clustertemplatesmagnum/api/controllers/v1/cluster_template.py:325 get_allclustertemplate:get_all (+ get_all_all_projects if admin)≥1.1200400, 403, 404 (marker)
GET/v1/clustertemplates/detailmagnum/api/controllers/v1/cluster_template.py:342 detailclustertemplate:detail (+ detail_all_projects if admin)≥1.1200400, 403, 404
GET/v1/clustertemplates/{ident}magnum/api/controllers/v1/cluster_template.py:366 get_oneclustertemplate:get (skipped if public)≥1.1200403, 404, 409
POST/v1/clustertemplatesmagnum/api/controllers/v1/cluster_template.py:407 postclustertemplate:create, clustertemplate:publish≥1.1201 + Location400, 403, 404, 409, 500
PATCH/v1/clustertemplates/{ident}magnum/api/controllers/v1/cluster_template.py:485 patchclustertemplate:update (+ update_all_projects), publish≥1.1200400, 403, 404, 409
DELETE/v1/clustertemplates/{ident}magnum/api/controllers/v1/cluster_template.py:552 deleteclustertemplate:delete (+ delete_all_projects)≥1.1204400, 403, 404, 409
GET/v1/clustersmagnum/api/controllers/v1/cluster.py:400 get_allcluster:get_all (+ get_all_all_projects)≥1.1200400, 403, 404
GET/v1/clusters/detailmagnum/api/controllers/v1/cluster.py:417 detailcluster:detail (+ detail_all_projects)≥1.1200400, 403, 404
GET/v1/clusters/{ident}magnum/api/controllers/v1/cluster.py:453 get_onecluster:get (+ get_one_all_projects)≥1.1200403, 404, 409
POST/v1/clustersmagnum/api/controllers/v1/cluster.py:508 post (1.1–1.9), magnum/api/controllers/v1/cluster.py:518 post (≥1.10)cluster:create≥1.1202 {"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.1202 {"uuid"}400, 403, 404, 409
DELETE/v1/clusters/{ident}magnum/api/controllers/v1/cluster.py:727 deletecluster:delete (+ delete_all_projects)≥1.1204403, 404, 409
POST/v1/clusters/{ident}/actions/resizemagnum/api/controllers/v1/cluster_actions.py:88 (1.7–1.9), magnum/api/controllers/v1/cluster_actions.py:96 (≥1.10)cluster:resize≥1.7202 {"uuid"}400, 403, 404, 406
POST/v1/clusters/{ident}/actions/upgrademagnum/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.7202 {"uuid"}400, 403, 404, 406, 409, 500
GET/v1/clusters/{ident}/nodegroupsmagnum/api/controllers/v1/nodegroup.py:393 get_allnodegroup:get_all (+ get_all_all_projects)≥1.9200403, 404, 406
GET/v1/clusters/{ident}/nodegroups/{ng}magnum/api/controllers/v1/nodegroup.py:431 get_onenodegroup:get (+ get_one_all_projects)≥1.9200403, 404, 406
POST/v1/clusters/{ident}/nodegroupsmagnum/api/controllers/v1/nodegroup.py:450 postnodegroup:create≥1.9202 + nodegroup body400, 403, 404, 406, 409
PATCH/v1/clusters/{ident}/nodegroups/{ng}magnum/api/controllers/v1/nodegroup.py:510 patchnodegroup:update≥1.9202 + nodegroup body400, 403, 404, 406, 409
DELETE/v1/clusters/{ident}/nodegroups/{ng}magnum/api/controllers/v1/nodegroup.py:525 deletenodegroup:delete≥1.9204400, 403, 404, 406
GET/v1/certificates/{cluster_ident}magnum/api/controllers/v1/certificate.py:148 get_onecertificate:get≥1.1200403, 404, 500
POST/v1/certificatesmagnum/api/controllers/v1/certificate.py:163 postcertificate:create≥1.1201400, 403, 500
PATCH/v1/certificates/{cluster_ident}magnum/api/controllers/v1/certificate.py:182 patchcertificate:rotate_ca≥1.1 (documented 1.5)202 {"uuid"}400, 403, 404
PATCH/v1/credentials/{cluster_ident}magnum/api/controllers/v1/credential.py:50 patchcredential:rotate≥1.12202 {"uuid"}400, 403, 404, 406
GET/v1/quotasmagnum/api/controllers/v1/quota.py:126 get_allquota:get_all (admin only)≥1.1200400, 403
GET/v1/quotas/{project_id}/{resource}magnum/api/controllers/v1/quota.py:151 get_onequota:get≥1.1200403
POST/v1/quotasmagnum/api/controllers/v1/quota.py:176 postquota:create (admin)≥1.1201400, 403, 404, 409
PATCH/v1/quotas/{project_id}/{resource}magnum/api/controllers/v1/quota.py:196 patchquota:update (admin)≥1.1202 + quota body400, 403, 404
DELETE/v1/quotas/{project_id}/{resource}magnum/api/controllers/v1/quota.py:213 deletequota:delete (admin)≥1.1204403, 404
GET/v1/statsmagnum/api/controllers/v1/stats.py:49 get_allstats:get_all≥1.1 (documented 1.4)200400, 403
GET/v1/mservicesmagnum/api/controllers/v1/magnum_services.py:92 get_allmagnum-service:get_all (admin)≥1.1200403
GET/v1/federationsmagnum/api/controllers/v1/federation.py:208 get_allfederation:get_all≥1.1200400, 403
GET/v1/federations/detailmagnum/api/controllers/v1/federation.py:225 detailfederation:detail≥1.1200400, 403, 404
GET/v1/federations/{ident}magnum/api/controllers/v1/federation.py:250 get_onefederation:get≥1.1200403, 404, 409
POST/v1/federationsmagnum/api/controllers/v1/federation.py:265 postfederation:create≥1.1202 {"uuid"} (never persisted, see below)400, 403, 404
PATCH/v1/federations/{ident}magnum/api/controllers/v1/federation.py:302 patchfederation:update≥1.1202 {"uuid"}400, 403, 404, 409
DELETE/v1/federations/{ident}magnum/api/controllers/v1/federation.py:443 deletefederation:delete≥1.1204403, 404
GET/healthcheckoslo.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 it public or hidden additionally needs clustertemplate:publish (admin only, magnum/common/policies/cluster_template.py:151).
  • Preconditions: the Glance image exists, is visible to the caller and carries an os_distro property; 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):
    1. server_type must be vm or bm (magnum/api/validation.py:359, enforced by magnum/api/validation.py:201).
    2. network_driver defaults to [cluster_template]/kubernetes_default_network_driver (flannel); it must be one of flannel, calico, cilium and be allowed by [cluster_template]/kubernetes_allowed_network_drivers (default flannel, calico: cilium is refused by default) (magnum/api/validation.py:184, magnum/api/validation.py:358, magnum/conf/cluster_templates.py:21).
    3. volume_driver, if set, must be cinder (magnum/api/validation.py:365).
    4. With docker_storage_driver=devicemapper, docker_volume_size must be ≥ 3 GB (magnum/api/validation.py:256).
    5. Driver resolution (magnum/api/validation.py:66): if cluster_distro or driver is missing, the image is read from Glance; cluster_distro comes from the image's os_distro, and driver from the image property magnum_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).
    6. OpenStack resources are checked: image (os_distro mandatory, magnum/api/attr_validator.py:32), flavors (by listing all Nova flavors, magnum/api/attr_validator.py:59), external network (must be router:external, magnum/api/attr_validator.py:83), fixed network and subnet, and labels (magnum/api/attr_validator.py:232).
    7. cluster_distro is always overwritten from the image's os_distro (magnum/api/controllers/v1/cluster_template.py:421).
    8. 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).
    9. A missing name is 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 the magnum-api binary, 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 Location header (magnum/api/controllers/v1/cluster_template.py:476).
  • Limitations: with coe=kubernetes and an image whose os_distro is coreos, the handler references self._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; 403 PolicyNotAuthorized, ImageNotAuthorized, ClusterTemplatePublishDenied; 409 Conflict (several images/networks with the same name), ClusterTemplateAlreadyExists; 500 (coreos case).
  • Spec/code mismatches: the api-ref does not document driver, hidden or tags for every microversion [inferred from api-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 every public=True, hidden=False template, 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 evaluating clustertemplate:get (magnum/api/controllers/v1/cluster_template.py:394).
  • GET /v1/clustertemplates returns the full objects (no field trimming); /detail returns the same body. /detail under 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:
    • add on 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); replace of /labels accepts a dict or a string parsed with ast.literal_eval (magnum/api/utils.py:85).
    • /network_driver, /external_network_id, /tls_disabled, /public, /server_type, /coe, /registry_enabled, /cluster_distro, /hidden cannot 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 public or hidden needs clustertemplate: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).
    • uuid cannot 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 ClusterTemplateReferenced while 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–1.9 only) node_count = 0 is refused with ZeroNodeCountNotSupported (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.
    2. The driver must exist for the template's (server_type, cluster_distro, coe) (magnum/api/validation.py:51). **This check ignores the template's driver field** (get_driver is 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.
    3. Volume size rule for devicemapper (magnum/api/validation.py:121).
    4. master_count must be odd and ≥ 1 by default; the driver may override validate_master_size (magnum/api/validation.py:104, magnum/drivers/common/driver.py:253).
    5. Quota: the project's Cluster hard limit (row in quotas, 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.
    6. A hidden template can only be used by an admin (magnum/api/controllers/v1/cluster.py:534, 403 NotAuthorized).
    7. 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 when merge_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.
    8. A deprecation warning is logged when volume_driver=cinder and labels.cinder_csi_enabled is false (magnum/api/controllers/v1/cluster.py:554).
    9. Flavor root disk vs boot_volume_size for both flavors (magnum/api/controllers/v1/cluster.py:580), OpenStack resources (keypair included, magnum/api/attr_validator.py:169), and **master_count > 1 requires master_lb_enabled** (magnum/api/attr_validator.py:173).
    10. name defaults 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_timeout defaults to 60 minutes (magnum/api/controllers/v1/cluster.py:107).
  • Flow:
    1. (synchronous) validations above, UUID generated by the API (magnum/api/controllers/v1/cluster.py:606).
    2. (synchronous) RPC cast cluster_create to the conductor (magnum/api/controllers/v1/cluster.py:607), response 202 with the UUID. Nothing is written to the database by the API.
    3. (asynchronous, conductor) insert the cluster in CREATE_IN_PROGRESS, then the two default nodegroups default-master and default-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 notification magnum.cluster.create pending; call the driver's create_cluster (magnum/conductor/handlers/cluster_conductor.py:71); copy the cluster's stack_id into each nodegroup. See 05 § cluster_create.
    4. (asynchronous, periodic) sync_cluster_status asks the driver every 10 s to update the status until CREATE_COMPLETE or CREATE_FAILED (05).
  • Outcome: 202 {"uuid": "…"}. A GET right after the 202 can return 404 until the conductor has inserted the row. Final state CREATE_COMPLETE (driver) or CREATE_FAILED with status_reason.
  • Effects visible elsewhere: certificates stored in Barbican (default [certificates]/cert_manager_type = barbican, magnum/conf/certificates.py:16) or in the x509keypair table; 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. a DBDuplicateEntry) 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/clusters returns 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_status and stack_id (<1.13) or cluster_id (≥1.13) (magnum/api/controllers/v1/cluster.py:214). /detail returns 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_addresses are 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 its status_reason (magnum/api/controllers/v1/cluster.py:441, magnum/api/controllers/v1/cluster.py:479).
    • From 1.13: cluster_id (same column as stack_id) replaces stack_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), and cluster:update_health_status is 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_reason may 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_id and the certificate references are internal and cannot be patched at all (magnum/api/controllers/v1/cluster.py:283).
    • node_count targets the default worker nodegroup, not the whole cluster (magnum/api/controllers/v1/cluster.py:718): without /node_count in the patch the current default-worker size is sent; with it, the patched value (magnum/api/controllers/v1/cluster.py:721). Note that GET returns node_count as the sum of all worker nodegroups (magnum/objects/cluster.py:389), so a client that reads, increments and patches node_count on a cluster with extra nodegroups resizes the default worker nodegroup to the wrong value.
    • 1.1–1.9: node_count = 0 is a 400 (magnum/api/controllers/v1/cluster.py:626).
    • 1.3+: query parameter rollback=true is forwarded to the driver (magnum/api/controllers/v1/cluster.py:637).
  • 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 *_COMPLETE state (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's update_cluster and moves the cluster to UPDATE_IN_PROGRESS (05).
  • Outcome: 202 {"uuid"}, then UPDATE_COMPLETE / UPDATE_FAILED through the status poller.
  • Limitations: with the magnum-cluster-api driver, a size change always ends in UPDATE_FAILED (the driver's update_cluster raises NotImplementedError, magnum-cluster-api:magnum_cluster_api/driver.py:329); use actions/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's delete_cluster and sets DELETE_IN_PROGRESS (magnum/conductor/handlers/cluster_conductor.py:166). When the driver reports DELETE_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_PROGRESS until 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, because all_tenants is not set (magnum/api/controllers/v1/cluster_actions.py:104).
  • Business rules:
    • node_count mandatory, ≥ 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 (when max_node_count is set), else 400 NGResizeOutBounds (magnum/api/controllers/v1/cluster_actions.py:120).
    • For the master nodegroup, the driver's validate_master_resize decides; 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 in UPDATE_IN_PROGRESS and UPDATE_FAILED so 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_projects for admins).
  • Business rules:
    • At 1.7 the action exists but always returns 400 ClusterUpgradeNotSupported (magnum/api/controllers/v1/cluster_actions.py:147).
    • cluster_template names 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, 409 InvalidClusterTemplateForUpgrade).
    • max_batch_size (≥1) is passed to the driver.
  • 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's upgrade_cluster. A driver or status error comes back to the client (400 NotSupported "Upgrading a cluster when status is …", magnum/conductor/handlers/cluster_conductor.py:285), and a slow driver can exceed rpc_response_timeout (500 MessagingTimeout). The cluster_upgrade_async helper is also a call (magnum/conductor/api.py:94).
  • Outcome: 202 {"uuid"} once the driver has accepted the upgrade; cluster and nodegroup in UPDATE_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 (409 ClusterAPIAddressUnavailable, 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 (409 NodeGroupInvalidInput, magnum/api/controllers/v1/nodegroup.py:157).
    • node_labels in kubernetes.io/ or k8s.io/ namespaces are refused unless their prefix is kubelet.kubernetes.io or node.kubernetes.io or the key is in the kubelet's allowed list (magnum/api/controllers/v1/nodegroup.py:46, magnum/api/controllers/v1/nodegroup.py:78).
    • node_taints is a list of {key, value?, effect} with effect NoSchedule|PreferNoSchedule|NoExecute (magnum/api/controllers/v1/nodegroup.py:103).
    • node_labels and node_taints are forbidden on a master nodegroup (magnum/api/controllers/v1/nodegroup.py:138), and creating a master nodegroup is refused anyway (400 CreateMasterNodeGroup, magnum/api/controllers/v1/nodegroup.py:473).
    • Defaults: image_id ← template image, flavor_id ← cluster flavor, labels ← cluster labels (or merged with merge_labels=true) (magnum/api/controllers/v1/nodegroup.py:477); root disk vs boot_volume_size check.
  • 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 to UPDATE_IN_PROGRESS, inserts the nodegroup in CREATE_IN_PROGRESS and calls the driver (magnum/conductor/handlers/nodegroup_conductor.py:73).
  • Spec/code mismatch: node_labels and node_taints are 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_count changes go through actions/resize.
  • Flow: the same validations as creation, then cast nodegroup_update and 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 to UPDATE_IN_PROGRESS and calls the driver's delete_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 accepts role as a filter; a non-admin list is also filtered by project (magnum/api/controllers/v1/nodegroup.py:416). The single read adds links and 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 the x509keypair table).
  • 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); csr mandatory; ca_cert_type optional (default kubernetes, magnum/conductor/handlers/ca_conductor.py:48).
  • Signing rules (magnum/common/x509/operations.py:176): SHA-256, validity [x509]/term_of_validity days (default 5 years, magnum/conf/x509.py:50), CA extensions refused unless [x509]/allow_ca, extensions and key usages filtered by [x509]/allowed_extensions and allowed_key_usage (400 CertificateValidationError).
  • Flow: RPC call sign_certificate; 201 with pem. 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 *_COMPLETE status (magnum/conductor/handlers/ca_conductor.py:83).
  • Flow: RPC call rotate_ca_certificate: new CAs are generated and stored, then the driver's rotate_ca_certificate is called — the base driver raises NotSupported (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 to UPDATE_IN_PROGRESS and 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_id must exist in Keystone (404 ProjectNotFound, magnum/api/validation.py:144), hard_limit ≥ 0 (default 1), resource = Cluster; a duplicate is 409 QuotaAlreadyExists.
  • 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 with hard_limit = [quotas]/max_clusters_per_project (magnum/api/controllers/v1/quota.py:168).
  • GET /v1/quotas: quota:get_all is admin-only by default, so the non-admin branch of the handler (magnum/api/controllers/v1/quota.py:141) is unreachable; all_tenants=true lists every project.
  • DELETE /{project_id}/{resource}: 204, 404 if absent.

GET /v1/stats?project_id=&type=cluster

  • Purpose: count clusters and nodes. type must be cluster (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 of nodegroup.node_count (magnum/db/sqlalchemy/api.py:221).

GET /v1/mservices

  • Purpose: list the Magnum services (in practice magnum-conductor processes) with state up/down, computed from last_seen_up against [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/federations validates hostcluster_id (must be an existing cluster) and properties.dns-zone (mandatory, magnum/api/attr_validator.py:219), then casts federation_create and returns 202 with a UUID that will never exist: the row is never inserted, because insertion was the conductor's job.
  • PATCH (join/unjoin by add/remove on /member_ids, rewritten into replace, magnum/api/controllers/v1/federation.py:327) and DELETE cast to the same unimplemented handlers. They can only act on rows inserted by other means.
  • The GET routes read the federation table 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)

Itemapi-refCode
Nodegroups, federations, credentials, /detail routesnot documentedimplemented (magnum/api/controllers/v1/nodegroup.py:357, magnum/api/controllers/v1/federation.py:250, magnum/api/controllers/v1/credential.py:50)
CA rotation pathPATCH /v1/certificates/PATCH /v1/certificates/{cluster_ident} (magnum/api/controllers/v1/certificate.py:182)
node_labels / node_taintsmicroversion 1.14accepted from 1.9
Federation create202, federation created202, 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.