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

Reverse-spec · Services

magnum — Rewrite pack

Rewrite pack — magnum — branch stable/2026.2

Branch stable/2026.2 @ 1910928b7d9f — repository https://opendev.org/openstack/magnum — analysed on 2026-11-09

This pack is for re-implementing Magnum (the container-infra API, v1, microversions 1.1–1.14) as openkeel-magnum, a Go service on Kubernetes, while keeping its clients working unchanged. Those clients are python-magnumclient / openstack coe, the Skyline "Container Infra" pages, gophercloud and Terraform, the Kubernetes cluster-autoscaler Magnum provider, and magnum-auto-healer. Magnum core is a thin layer: the REST API, a status poller and certificate management. Every cluster is built by a driver, which since 2026.2 lives outside Magnum. The pack therefore has two halves: the API contract (this file and _rewrite/), and the driver contract, Cluster API objects included, in 08.

Client repositories cited: python-magnumclient stable/2026.2 @ cf374db1a93c, cloud-provider-openstack @ b210c18f9c3e, gophercloud @ 4aae510cb4d2.

1. Contents of _rewrite/

FileUse in a rewrite
[openapi_stable-2026.2.json](_rewrite/openapi_stable-2026.2.json)types, stubs and request validation; x-microversions and x-handlers say which handler serves which version; not the authority on behaviour
[routes_stable-2026.2.json](_rewrite/routes_stable-2026.2.json), [versioned-methods_stable-2026.2.json](_rewrite/versioned-methods_stable-2026.2.json)the route table: 43 handlers on 38 routes, with success status, query parameters, body type, response type and policy names
[request-schemas_stable-2026.2.json](_rewrite/request-schemas_stable-2026.2.json)the WSME types as JSON Schema: lengths, patterns (cluster name ^[a-zA-Z][a-zA-Z0-9_.-]*$, ≤ 242), enums (statuses, coe, server_type), mandatory fields, read-only fields, defaults
[response-schemas_stable-2026.2.json](_rewrite/response-schemas_stable-2026.2.json) / [.md](_rewrite/response-schemas_stable-2026.2.md)response bodies observed in 532 exchanges, per operation and status
[exchanges_stable-2026.2.json](_rewrite/exchanges_stable-2026.2.json)reference exchanges for differential tests (status, headers, body shape)
[observed-errors_stable-2026.2.md](_rewrite/observed-errors_stable-2026.2.md)error messages exactly as returned
[microversions_stable-2026.2.md](_rewrite/microversions_stable-2026.2.md)the 14 microversions
[api-ref_stable-2026.2.json](_rewrite/api-ref_stable-2026.2.json), [api-ref-vs-code_stable-2026.2.json](_rewrite/api-ref-vs-code_stable-2026.2.json)the documented contract and its 16 differences with the code
[schema-mariadb-migrated_stable-2026.2.sql](_rewrite/schema-mariadb-migrated_stable-2026.2.sql)the database schema to use (Alembic head b3e1cf4a2d5e, MariaDB 11.4)
[schema-diff_stable-2026.2.md](_rewrite/schema-diff_stable-2026.2.md), [schema-mysql_stable-2026.2.sql](_rewrite/schema-mysql_stable-2026.2.sql), [schema-postgresql_stable-2026.2.sql](_rewrite/schema-postgresql_stable-2026.2.sql)the model DDL and its 9 differences with the migrations
[policies_stable-2026.2.json](_rewrite/policies_stable-2026.2.json), [policies_stable-2026.2.yaml](_rewrite/policies_stable-2026.2.yaml)the 66 default rules (check strings, scope types, operations)
[config_stable-2026.2.conf](_rewrite/config_stable-2026.2.conf)every configuration option with its default; keep the names that deployment tools set (§ 6)
_rewrite/tools/the extraction tools, to re-run on a later branch

Library versions used for the extraction are in [rewrite-pack-index_stable-2026.2.md](_rewrite/rewrite-pack-index_stable-2026.2.md).

2. HTTP behaviour that the contracts do not show

2.1 Middleware pipeline

Outermost first (etc/magnum/api-paste.ini:10): cors → http_proxy_to_wsgi → request_id → osprofiler → authtoken → pecan app (hooks: ContextHook, RPCHook, NoExceptionTracebackHook, magnum/api/config.py:21) wrapped in ParsableErrorMiddleware (magnum/api/app.py:47). /healthcheck is a separate branch of the composite app and bypasses everything (etc/magnum/api-paste.ini:4).

Consequences a rewrite must keep:

  • GET / and GET /v1 (with or without .json and trailing slash) are public: the auth filter skips them (magnum/api/middleware/auth_token.py:54, etc/magnum/api-paste.ini:14). Everything else returns 401 before version negotiation: a request without a token and with a bad OpenStack-API-Version header gets 401, not 406.
  • Version negotiation happens in the v1 router (magnum/api/controllers/v1/__init__.py:226), before policy checks. A request at an unsupported version gets 406 even if the policy would refuse it.
  • A path without the v1 prefix is routed to v1 (magnum/api/controllers/root.py:105): GET /clusters behaves as GET /v1/clusters.
  • The policy check is done inside the handler, usually after the resource lookup. A missing resource is therefore a 404 even for a caller the policy would refuse, and another project's resource is a 404 rather than a 403.

2.2 Microversion headers

  • Request header: OpenStack-API-Version: <service> <X.Y|latest>. <service> may be container-infra, container-infrastructure or container-infrastructure-management (magnum/api/controllers/versions.py:58); latest means 1.14. With no header the request runs at 1.1.
  • Response headers on every v1 response, errors included: OpenStack-API-Minimum-Version: container-infrastructure-management 1.1, OpenStack-API-Maximum-Version: container-infrastructure-management 1.14, OpenStack-API-Version: container-infrastructure-management <used>, Vary: OpenStack-API-Version (magnum/api/controllers/v1/__init__.py:231). The service name in the responses is the long one, whatever the client sent (seen in the exchanges).
  • Version-dependent behaviour to reproduce: 1.10 (zero-node nodegroups; ClusterTemplateNotFound on cluster create becomes a 404 instead of a 400), 1.13 (cluster_id replaces stack_id in cluster responses, magnum/api/controllers/v1/cluster.py:240), 1.3 (rollback query parameter), 1.7/1.8 (resize, upgrade), 1.9 (nodegroups), 1.12 (credentials). 1.14 (node_labels, node_taints) is not enforced by the code: both fields are accepted and returned from 1.9 on. A rewrite should either gate them at 1.14 and document the fix, or keep the leak for compatibility.

2.3 Error body

{"errors": [{"request_id": "", "code": "client"|"server", "status": <int>, "title": <first sentence>, "detail": <message>, "links": []}]} (magnum/api/middleware/parsable_error.py:56). The 406 microversion errors use code: "magnum.microversion-unsupported" plus min_version, max_version and a links entry (magnum/api/http_error.py:59). python-magnumclient shows errors[0].title as the error message and detail as the debug info (python-magnumclient:magnumclient/common/httpclient.py:53), so **title must stay meaningful**. It is the message cut at the first period: "Cluster x could not be found".

2.4 Other cross-cutting rules

  • JSON only (magnum/api/expose.py:17). Unknown body attributes are refused by WSME with a 400 [inferred from WSME's strict type parsing]. Unknown query parameters are ignored.
  • Success statuses are not REST-uniform and clients rely on them:
    • POST /v1/clusters, cluster PATCH, resize, upgrade, nodegroup POST/PATCH, PATCH /v1/certificates/{id}, PATCH /v1/credentials/{id} and quota PATCH return 202;
    • template POST, certificate POST and quota POST return 201;
    • every DELETE returns 204, cluster deletion included, even though it is asynchronous.
  • Asynchronous creates return only {"uuid"} (clusters, federations) or the unsaved object (nodegroups). There is no Location header except on template creation (magnum/api/controllers/v1/cluster_template.py:476).
  • Lists return {"<collection>": [...], "next": "<url>"}. next is present only when the page is full, and the limit is capped at [api]/max_limit (1000).
  • JSON Patch semantics are Magnum-specific (magnum/api/utils.py:70): add is only allowed on an attribute that is currently unset; replace of /labels, /health_status_reason, /node_labels or /node_taints accepts a JSON value or a string parsed as a Python literal; mandatory and internal attributes cannot be removed or patched.

3. Core logic: formal specification

Magnum has no heavy algorithm. What a rewrite from the API reference alone would get wrong is (a) how the values the driver receives are computed, (b) the state guards, and (c) the status poller.

3.1 Driver selection

  1. On template create: if the template has no cluster_distro or no driver, read the image from Glance. cluster_distro is taken from os_distro (mandatory, else 400). driver is the request's driver, else the image property magnum_driver, else [drivers]/default_driver (it must be installed, else 400), else the alphabetically first installed driver (magnum/api/validation.py:66, magnum/drivers/common/driver.py:166). The driver must load, and it is stored on the template. cluster_distro is then always overwritten from the image (magnum/api/controllers/v1/cluster_template.py:421).
  2. On cluster create: the API checks that some driver provides (server_type, cluster_distro, coe) (magnum/api/validation.py:51). The conductor then loads the driver **by the template's driver name** (magnum/drivers/common/driver.py:193). A rewrite should use the stored name for both.
  3. A driver provides a list of {server_type, os, coe}; the magnum-cluster-api driver provides vm × ubuntu|ubuntu-focal|debian|flatcar|rockylinux × kubernetes (08 § 2). A beta driver must be listed in [drivers]/enabled_beta_drivers (magnum/drivers/common/driver.py:149).

3.2 Values sent to the driver on cluster create

Input: request R, template T (magnum/api/controllers/v1/cluster.py:538, magnum/conductor/utils.py:157).

cluster.cluster_template_id = T.uuid                       # R may name T by name
cluster.keypair      = R.keypair ?? T.keypair_id
cluster.labels       = (R.labels empty or unset) ? T.labels
                     : R.merge_labels ? T.labels ∪ R.labels (R wins)
                     : R.labels                              # replaces, does not merge
for f in floating_ip_enabled, master_lb_enabled: cluster.f = (R.f unset) ? T.f : R.f
for f in docker_volume_size, master_flavor_id, flavor_id, fixed_network, fixed_subnet:
    cluster.f = (R.f unset or falsy) ? T.f : R.f
cluster.name         = R.name ?? "<word>-<n>-cluster"
cluster.coe_version = cluster.container_version = null
cluster.status = CREATE_IN_PROGRESS                          # set by the conductor
nodegroup default-master: role master, node_count = R.master_count (1), flavor = cluster.master_flavor_id ?? T.master_flavor_id,
                          image = T.image_id, labels = cluster.labels, docker_volume_size = cluster ?? T
nodegroup default-worker: role worker, node_count = R.node_count (1), flavor = cluster.flavor_id ?? T.flavor_id,
                          min_node_count = labels.min_node_count (if present), same image/labels/volume
both: is_default = true, status CREATE_IN_PROGRESS, stack_id copied from the cluster after driver.create_cluster

Validation order and statuses: microversion → body types (400) → template exists (404, or 400 below 1.10) → driver exists (400) → devicemapper volume (400) → master size (driver rule, base: odd and ≥ 1) → policy cluster:create (403) → quota (403) → hidden template (403) → flavor root disk (400) → OpenStack resources (400/404/409) → master_count > 1 ⇒ master_lb_enabled (400). The decorator order puts the driver and template checks before the policy check (magnum/api/controllers/v1/cluster.py:502–magnum/api/controllers/v1/cluster.py:528).

3.3 Quota

limit = quotas[(project, 'Cluster')].hard_limit ?? [quotas]/max_clusters_per_project (20); refuse if count(cluster where project_id = caller) ≥ limit (magnum/api/controllers/v1/cluster.py:484). The count includes every status, failed and deleting included. The check is not atomic with the insert, which happens later in the conductor. A rewrite that inserts the row in the API transaction can make it atomic without breaking clients.

3.4 State guards (conductor)

OperationAllowed cluster statusesDefined at
update (PATCH node_count/health)CREATE_COMPLETE, UPDATE_COMPLETE, RESUME_COMPLETE, RESTORE_COMPLETE, ROLLBACK_COMPLETE, SNAPSHOT_COMPLETE, CHECK_COMPLETE, ADOPT_COMPLETEmagnum/conductor/handlers/cluster_conductor.py:94
resizethe above + UPDATE_FAILED, UPDATE_IN_PROGRESSmagnum/conductor/handlers/cluster_conductor.py:205
upgrade, CA rotationthe *_COMPLETE listmagnum/conductor/handlers/cluster_conductor.py:271, magnum/conductor/handlers/ca_conductor.py:73
credential rotation*_COMPLETE + UPDATE_IN_PROGRESS, UPDATE_FAILEDmagnum/conductor/handlers/credential_conductor.py:26
nodegroup create/updatenodegroup status (if set) in *_COMPLETE + UPDATE_IN_PROGRESS, UPDATE_FAILEDmagnum/conductor/handlers/nodegroup_conductor.py:37
delete, nodegroup deleteno guardmagnum/conductor/handlers/cluster_conductor.py:166

Upgrade, CA rotation and credential rotation are RPC calls, so a refusal reaches the client as a 400. Update and resize are casts: their refusals are silent (202, then nothing). A rewrite should return 409 synchronously for these refusals. That is a visible change, so document it, but no client in the workspace depends on the silent behaviour [inferred from the clients cited in § 4].

3.5 Status poller

Every 10 s, for each cluster in CREATE_IN_PROGRESS, UPDATE_IN_PROGRESS, DELETE_IN_PROGRESS, ROLLBACK_IN_PROGRESS (magnum/service/periodic.py:166):

  1. driver.update_cluster_status(ctx, cluster): the driver sets the cluster and nodegroup statuses, api_address, coe_version, addresses;
  2. if the new status ends in _COMPLETE or _FAILED, emit the CADF notification magnum.cluster.<action> (§ 4);
  3. if DELETE_COMPLETE, delete the Kubernetes CA and client certificate, every nodegroup row, then the cluster row.

In OpenKeel the natural replacement is a watch on the Cluster API objects (08 § 10) instead of a 10-second poll, with a single active instance (leader election) so that deletions are not raced.

3.6 Health

HEALTHY ⇔ GET /healthz returns ok and every node has Ready=True; the reason is {"<node>.Ready": bool, "api": "<healthz>"}. Without a public API address the status is UNKNOWN, with reason {"api": "The cluster <name> is not accessible."}. The poll is skipped when labels.auto_healing_controller == magnum-auto-healer and auto_healing_enabled is true (magnum/drivers/common/k8s_monitor.py:49). The magnum-cluster-api driver supplies its own monitor, based on CAPI conditions (08 § 5.4), so a rewrite that uses CAPI does not need to reach each cluster's API.

3.7 Certificates

  • Per cluster: three self-signed CAs (Kubernetes, etcd, front-proxy) named after the cluster (or its UUID), plus one client certificate CN=admin, O=system:masters signed by the Kubernetes CA (magnum/conductor/handlers/common/cert_manager.py:54). Keys are RSA 2048, encrypted with a random passphrase stored next to them.
  • GET /v1/certificates/{id}?ca_cert_type= returns the CA PEM. POST /v1/certificates signs a CSR with the chosen CA (validity 1825 days, allowed extensions and key usages filtered, magnum/common/x509/operations.py:176).
  • The CAPI driver reuses these CAs as the kubeadm CAs (08 § 6). The CAs are therefore part of the cluster identity: a rewrite must create them before the CAPI objects and keep the same storage contract, or move the CA into Kubernetes Secrets and serve GET/POST /v1/certificates from there.
  • Barbican stores them under the Magnum service project. In OpenKeel, Kubernetes Secrets in the management namespace (encrypted at rest) are the natural replacement, and they remove the Barbican load of the health poller.

3.8 Nodegroup input rules

These are the ones in 01 § Nodegroups: kubelet label namespaces, taint shape and effects, min/max/count consistency, no labels or taints on master, no master creation, no deletion of default nodegroups (magnum/api/controllers/v1/nodegroup.py:46–magnum/api/controllers/v1/nodegroup.py:176). These errors are 409 (NodeGroupInvalidInput is a Conflict, magnum/common/exception.py:461); keep the status.

4. What the clients expect

ExpectationWhere the client relies on it
Service type container-infra in the catalogue; header OpenStack-API-Version: container-infra <v>, default latestpython-magnumclient:magnumclient/v1/client.py:33, python-magnumclient:magnumclient/common/httpclient.py:40, python-magnumclient:magnumclient/common/httpclient.py:176
Error text in errors[0].title (shown to the user), details in errors[0].detailpython-magnumclient:magnumclient/common/httpclient.py:53
POST /v1/clusters/{id}/actions/resize with nodegroup, node_count and nodes_to_remove (server UUIDs), accepted while the cluster is UPDATE_IN_PROGRESS / UPDATE_FAILEDcloud-provider-openstack:pkg/autohealing/cloudprovider/openstack/provider.go:422; cluster-autoscaler Magnum provider [external, not in the workspace]
GET /v1/clusters/{id}/nodegroups and /{ng} return node_count, role, uuid, namecloud-provider-openstack:pkg/autohealing/cloudprovider/openstack/provider.go:528
PATCH /v1/clusters/{id} with replace /health_status and /health_status_reason, allowed by cluster:update_health_statuscloud-provider-openstack:pkg/autohealing/cloudprovider/openstack/provider.go:592
magnum-auto-healer uses microversion latest and reads stack_id, which is absent from 1.13 on (renamed cluster_id): its Heat-based repair path gets an empty stack id. CAPI clusters do not use that path, but a rewrite must keep the 1.13 rename exactlycloud-provider-openstack:pkg/autohealing/cloudprovider/openstack/provider.go:710, cloud-provider-openstack:pkg/autohealing/cloudprovider/openstack/provider.go:435, gophercloud:openstack/containerinfra/v1/clusters/results.go:108
gophercloud and Terraform parse stack_id (not cluster_id) and only send old microversions by defaultgophercloud:openstack/containerinfra/v1/clusters/results.go:108
The Skyline console shows the Container Infra menu only when the catalogue has container-infra, and calls 16 Magnum operationsreverse-spec-skyline-console/01-ui-modules_stable-2026.2.md
openstack coe cluster config = GET /v1/clusters/{id}, GET /v1/certificates/{id}, POST /v1/certificates with a CSR; the signed certificate must be accepted by the cluster's API server04 § 5

5. Database strategy

  • Reuse the migrated schema ([schema-mariadb-migrated_stable-2026.2.sql](_rewrite/schema-mariadb-migrated_stable-2026.2.sql)) if OpenKeel must take over an existing Magnum database; otherwise a new schema can drop the federation table, the legacy Heat columns (stack_id kept as the driver's cluster id), discovery_url and container_version. The 9 model/migration differences (constraint names uniq_bay0uuid, uniq_baymodel0uuid, docker_storage_driver 512 characters, NOT NULL columns of nodegroup and quotas, default server_type = 'vm') are listed in [schema-diff_stable-2026.2.md](_rewrite/schema-diff_stable-2026.2.md).
  • No foreign keys: cluster.cluster_template_id (UUID string), nodegroup.cluster_id (UUID string) and x509keypair references are kept by code. A rewrite can add foreign keys if it handles the "template in use" rule (400) and the cascade on cluster delete itself.
  • JSON columns: labels, health_status_reason, node_labels, properties are JSON-encoded dicts; node_addresses, node_taints, member_ids are JSON lists (magnum/db/sqlalchemy/models.py:110, magnum/db/sqlalchemy/models.py:285).
  • Locking: SELECT … FOR UPDATE on single-row updates, retry_on_deadlock (magnum/db/sqlalchemy/api.py:267). There is no optimistic version, and the last writer wins. A Go rewrite with a single reconciler per cluster avoids the races between poller instances.
  • Names are not unique: lookups by name must return 409 on ambiguity.
  • Startup data: none.
  • Galera: the FOR UPDATE usage is compatible with Galera's write-set replication, but certification conflicts surface as deadlocks; keep a retry [inferred].

6. Authentication, policies, configuration and CLI

  • Authentication: keystonemiddleware auth_token (validates tokens with Keystone, [keystone_authtoken]), then the context comes from X-User-Id, X-Project-Id, X-Roles… (magnum/api/hooks.py:49). The full token and its body are forwarded to the conductor, which acts as the user towards Glance, Nova, Neutron and the driver (magnum/common/keystone.py:66). The CAPI driver creates a Keystone application credential per cluster with the user's token (08 § 6), so a rewrite must also call Keystone as the user at create time.
  • Policies: 66 oslo.policy rules ([policies_stable-2026.2.yaml](_rewrite/policies_stable-2026.2.yaml)). The rule language needed: role:, project_id:%(project_id)s, user_id:%(user_id)s, is_admin:True, rule:, and/or. OpenKeel already has an oslo.policy evaluator (openkeel-skyline/internal/skypolicy, which carries the 69 Magnum rules of the Skyline copy). Behaviours to keep:
    • target: the resource as a dict when the handler passes one, else {project_id, user_id} of the caller (magnum/common/policy.py:97); nodegroup create, update and delete, credential:rotate, the quota rules and stats:get_all pass none;
    • the *_all_projects rules are evaluated only when the caller is admin, and make the database lookup ignore the project;
    • context_is_admin (role:admin) is computed at context creation (magnum/common/context.py:59);
    • the *_deny_cluster_user rule names suggest a denial, but their check strings do not deny anything (magnum/common/policies/base.py:127): they denied Heat trustee users until trusts were removed in 2026.2 (06 § 22). Keep the strings, not the names.
  • Configuration: the options OpenKeel's chart would set are [DEFAULT]/transport_url, [database]/connection, [keystone_authtoken], [keystone_auth] (service credentials), [certificates]/cert_manager_type, [drivers]/default_driver, enabled_beta_drivers, disabled_drivers, verify_ca, openstack_ca_file, [quotas]/max_clusters_per_project, [api]/max_limit, [kubernetes]/health_polling_interval, [cluster_template]/kubernetes_allowed_network_drivers (add cilium for the CAPI driver), [x509]/*. Everything Heat-, etcd-discovery- or docker-related can go.
  • CLI: magnum-db-manage upgrade|version, magnum-driver-manage list-drivers [--details], magnum-status upgrade check. okadm replaces the DB tool; keep a driver listing for diagnostics.

7. Conformance strategy

  • Recorded data: 532 exchanges from 456 API tests, 0 failures (conformance_capture.py http --wrap magnum.api.app:setup_app, adapter _rewrite/tools/magnum_adapter.py, test modules magnum.tests.unit.api.controllers.v1.test_* and magnum.tests.unit.api.controllers.test_root/test_base). The tests build the real pecan application without the paste pipeline, so there is no auth, CORS or request ID in these exchanges. They mock the conductor RPC API and the OpenStack clients, and create their data through the Python objects (magnum/tests/unit/api/base.py:40). The exchanges are evidence of the API layer: status codes, error messages, body shapes, microversion headers.
  • No seed replay was made: the data come from Python fixtures and mocks, so the exchanges cannot be replayed from an empty database as they stand. Use them for differential testing of status and body shape, and validate responses against [response-schemas_stable-2026.2.json](_rewrite/response-schemas_stable-2026.2.json).
  • Black-box suite: magnum-tempest-plugin is not in the workspace. Its API tests (templates, clusters, certificates) need a real driver. For OpenKeel, a better conformance target is an end-to-end test that drives python-magnumclient and the cluster-autoscaler/auto-healer calls of § 4 against openkeel-magnum with the CAPI back end in the kind lab.
  • Original fixtures, for reference: magnum/tests/unit/api/base.py:40 (app), magnum/tests/unit/db/utils.py (get_test_cluster, get_test_cluster_template, get_test_nodegroup), magnum/tests/unit/objects/utils.py.

8. Gaps found while building the pack

  • rewrite_pack.py has no Pecan/WSME extractor: replaced by _rewrite/tools/magnum_api.py, which derives the routes from the RestController rules and the request types from the WSME definitions. 43 handlers were found. The api-ref documents 25 operations; 15 code routes are undocumented (nodegroups, federations, credentials, /detail), and the api-ref's PATCH /v1/certificates/ has no code route (the code needs the cluster ident) ([api-ref-vs-code_stable-2026.2.json](_rewrite/api-ref-vs-code_stable-2026.2.json)).
  • The in-script config generator failed because osprofiler was not installed (magnum/conf/profiler.py needs it). The sample was then generated with oslo-config-generator after installing it.
  • magnum.drivers is empty in this branch (pyproject.toml:69): no request that reaches a driver can be exercised with Magnum alone. The driver contract is documented in 08 from magnum-cluster-api v0.39.0.
  • Federation is exposed but not implemented (01 § Federations).
  • Response schemas only cover what the tests exercise: no DELETE_FAILED/UPDATE_FAILED cluster bodies with faults, no 1.13+ cluster body for every route.

9. Non-functional requirements

  • GET /v1/clusters must not cost one template read per cluster: the original prefetches one template per distinct id (magnum/api/controllers/v1/cluster.py:308).
  • Validation costs: the original lists all Nova flavors and Neutron networks/subnets several times per request (magnum/api/attr_validator.py:59, magnum/api/attr_validator.py:104). A rewrite should use filtered GETs.
  • Per-cluster background cost of the original: two HTTPS calls and two Barbican reads every 60 s (health), plus one driver poll every 10 s while in progress. A watch-based reconciler removes both.
  • Every outbound HTTP call needs a timeout; the original's health requests have none (magnum/conductor/k8s_api.py:44).
  • Request bodies (CSRs, labels) are logged at DEBUG by the original (magnum/api/controllers/v1/__init__.py:241); do not log them.

10. Rewrite checklist

  • [ ] v1 routes of [routes_stable-2026.2.json](_rewrite/routes_stable-2026.2.json), unprefixed paths routed to v1, /healthcheck
  • [ ] microversion negotiation 1.1–1.14, long service name in response headers, 406 bodies with magnum.microversion-unsupported
  • [ ] 1.10, 1.13 (cluster_id/stack_id) behaviour changes; decide about 1.14 gating
  • [ ] error body {"errors":[…]} with title = first sentence
  • [ ] WSME validation rules ([request-schemas_stable-2026.2.json](_rewrite/request-schemas_stable-2026.2.json)), Magnum JSON Patch semantics
  • [ ] success statuses (202/201/204 per route)
  • [ ] template visibility (project ∪ public ∪ admin hidden), "template in use" rules, publish policy
  • [ ] driver resolution (§ 3.1) and the inheritance rules (§ 3.2)
  • [ ] quota (§ 3.3), state guards (§ 3.4), nodegroup rules (§ 3.8)
  • [ ] three CAs + admin client certificate per cluster, CA download and CSR signing, CA reuse by the CAPI back end (08 § 6)
  • [ ] status reconciliation from CAPI (08 § 5) and deletion cascade (§ 3.5)
  • [ ] health from CAPI conditions (08 § 5.4)
  • [ ] CADF notifications magnum.cluster.{create,update,delete} if CloudKitty or auditing consumes them
  • [ ] 66 policy rules with the targets of § 6
  • [ ] differential tests against [exchanges_stable-2026.2.json](_rewrite/exchanges_stable-2026.2.json) and the client expectations of § 4

Source: reverse-spec-magnum/07-rewrite-pack_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.