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/
| File | Use 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 /andGET /v1(with or without.jsonand 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 badOpenStack-API-Versionheader 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
v1prefix is routed tov1(magnum/api/controllers/root.py:105):GET /clustersbehaves asGET /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 becontainer-infra,container-infrastructureorcontainer-infrastructure-management(magnum/api/controllers/versions.py:58);latestmeans 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;
ClusterTemplateNotFoundon cluster create becomes a 404 instead of a 400), 1.13 (cluster_idreplacesstack_idin cluster responses,magnum/api/controllers/v1/cluster.py:240), 1.3 (rollbackquery 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, clusterPATCH,resize,upgrade, nodegroupPOST/PATCH,PATCH /v1/certificates/{id},PATCH /v1/credentials/{id}and quotaPATCHreturn 202;- template
POST, certificatePOSTand quotaPOSTreturn 201; - every
DELETEreturns 204, cluster deletion included, even though it is asynchronous.
- Asynchronous creates return only
{"uuid"}(clusters, federations) or the unsaved object (nodegroups). There is noLocationheader except on template creation (magnum/api/controllers/v1/cluster_template.py:476). - Lists return
{"<collection>": [...], "next": "<url>"}.nextis 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):addis only allowed on an attribute that is currently unset;replaceof/labels,/health_status_reason,/node_labelsor/node_taintsaccepts 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
- On template create: if the template has no
cluster_distroor nodriver, read the image from Glance.cluster_distrois taken fromos_distro(mandatory, else 400).driveris the request'sdriver, else the image propertymagnum_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_distrois then always overwritten from the image (magnum/api/controllers/v1/cluster_template.py:421). - 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'sdrivername** (magnum/drivers/common/driver.py:193). A rewrite should use the stored name for both. - A driver provides a list of
{server_type, os, coe}; the magnum-cluster-api driver providesvm×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)
| Operation | Allowed cluster statuses | Defined at |
|---|---|---|
| update (PATCH node_count/health) | CREATE_COMPLETE, UPDATE_COMPLETE, RESUME_COMPLETE, RESTORE_COMPLETE, ROLLBACK_COMPLETE, SNAPSHOT_COMPLETE, CHECK_COMPLETE, ADOPT_COMPLETE | magnum/conductor/handlers/cluster_conductor.py:94 |
| resize | the above + UPDATE_FAILED, UPDATE_IN_PROGRESS | magnum/conductor/handlers/cluster_conductor.py:205 |
| upgrade, CA rotation | the *_COMPLETE list | magnum/conductor/handlers/cluster_conductor.py:271, magnum/conductor/handlers/ca_conductor.py:73 |
| credential rotation | *_COMPLETE + UPDATE_IN_PROGRESS, UPDATE_FAILED | magnum/conductor/handlers/credential_conductor.py:26 |
| nodegroup create/update | nodegroup status (if set) in *_COMPLETE + UPDATE_IN_PROGRESS, UPDATE_FAILED | magnum/conductor/handlers/nodegroup_conductor.py:37 |
| delete, nodegroup delete | no guard | magnum/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):
driver.update_cluster_status(ctx, cluster): the driver sets the cluster and nodegroup statuses,api_address,coe_version, addresses;- if the new status ends in
_COMPLETEor_FAILED, emit the CADF notificationmagnum.cluster.<action>(§ 4); - 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:masterssigned 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/certificatessigns 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/certificatesfrom 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
| Expectation | Where the client relies on it |
|---|---|
Service type container-infra in the catalogue; header OpenStack-API-Version: container-infra <v>, default latest | python-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].detail | python-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_FAILED | cloud-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, name | cloud-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_status | cloud-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 exactly | cloud-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 default | gophercloud: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 operations | reverse-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 server | 04 § 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_idkept as the driver's cluster id),discovery_urlandcontainer_version. The 9 model/migration differences (constraint namesuniq_bay0uuid,uniq_baymodel0uuid,docker_storage_driver512 characters, NOT NULL columns ofnodegroupandquotas, defaultserver_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) andx509keypairreferences 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,propertiesare JSON-encoded dicts;node_addresses,node_taints,member_idsare JSON lists (magnum/db/sqlalchemy/models.py:110,magnum/db/sqlalchemy/models.py:285). - Locking:
SELECT … FOR UPDATEon 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 UPDATEusage 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 fromX-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 andstats:get_allpass none; - the
*_all_projectsrules 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_userrule 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.
- target: the resource as a dict when the handler passes one, else
- 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(addciliumfor 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.okadmreplaces 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 modulesmagnum.tests.unit.api.controllers.v1.test_*andmagnum.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-pluginis 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 againstopenkeel-magnumwith 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.pyhas no Pecan/WSME extractor: replaced by_rewrite/tools/magnum_api.py, which derives the routes from theRestControllerrules 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'sPATCH /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
osprofilerwas not installed (magnum/conf/profiler.pyneeds it). The sample was then generated withoslo-config-generatorafter installing it. magnum.driversis 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_FAILEDcluster bodies withfaults, no 1.13+ cluster body for every route.
9. Non-functional requirements
GET /v1/clustersmust 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":[…]}withtitle= 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.