Rewrite pack — cinder — branch stable/2026.2
Branch
stable/2026.2@c117f9cf08b4(29.0.0-1-gc117f9cf0) — repository https://opendev.org/openstack/cinder — analysed on 2026-10-28 Client references: Novastable/2026.2@8df1922926dd(nova:)
This file is the entry point for re-implementing Cinder in Go (OpenKeel) as a drop-in replacement of the Block Storage API v3:
- Nova, Glance (cinder store), Horizon/Skyline, python-cinderclient/openstacksdk and Tempest must keep working unchanged;
- the deployment and operation conventions (paste-free, but same configuration names where they matter, same policies, same database) should stay recognisable.
OpenKeel replaces the storage drivers by Kubernetes block CSI volumes (Longhorn…). This pack therefore specifies the API, state machine, scheduling, quota and messaging contracts; the hundreds of vendor drivers are out of scope, and the driver interface is described only where its results are visible to clients (connection_info, capabilities).
1. Contents of _rewrite/
| File | Use in a rewrite |
|---|---|
[routes_stable-2026.2.json](_rewrite/routes_stable-2026.2.json) | the route table to implement: 218 operations of the live router, with the body action for …/action, the handler and file:line, each microversion variant, the request schema(s) applied per variant, the success status, the controller extensions that also run, and the policy rule(s) |
[openapi_stable-2026.2.json](_rewrite/openapi_stable-2026.2.json) | OpenAPI 3.0.3 for code generation (155 paths, 219 operations). Body actions are listed as <path>#<action> because OpenAPI cannot dispatch on a body key; x-microversions, x-schema-selection (schema per microversion range), x-policy; response fields from the api-ref. Not the authority on behaviour |
[request-schemas_stable-2026.2.json](_rewrite/request-schemas_stable-2026.2.json) | the 112 JSON schemas used by @validation.schema — load them as is in the Go validator; they use Cinder format checkers (uuid, date-time, name, description, metadata size, boolean-like strings) defined in cinder/api/validation/validators.py and cinder/api/validation/parameter_types.py |
[microversions_stable-2026.2.md](_rewrite/microversions_stable-2026.2.md) | the 72 microversions with their changes |
[api-ref_stable-2026.2.json](_rewrite/api-ref_stable-2026.2.json) | documented operations, parameters, response fields and samples |
[exchanges_stable-2026.2.json](_rewrite/exchanges_stable-2026.2.json) | 967 real HTTP request/response pairs of the functional and API-sample tests, for differential testing (§ 7) |
[response-schemas_stable-2026.2.json](_rewrite/response-schemas_stable-2026.2.json), [response-schemas_stable-2026.2.md](_rewrite/response-schemas_stable-2026.2.md) | response body schemas observed for 102 operations, with the microversions in which each field appears |
[observed-errors_stable-2026.2.md](_rewrite/observed-errors_stable-2026.2.md) | error bodies seen in the run |
[schema-sqlite-migrated_stable-2026.2.sql](_rewrite/schema-sqlite-migrated_stable-2026.2.sql), [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) | database contract (§ 5) |
[policies_stable-2026.2.json](_rewrite/policies_stable-2026.2.json), [policies_stable-2026.2.yaml](_rewrite/policies_stable-2026.2.yaml) | the 167 default policy rules |
[config_stable-2026.2.conf](_rewrite/config_stable-2026.2.conf) | every option with its default |
[routes-unimplemented_stable-2026.2.json](_rewrite/routes-unimplemented_stable-2026.2.json), [policy-operations-unmatched_stable-2026.2.json](_rewrite/policy-operations-unmatched_stable-2026.2.json) | routes not to implement; policy documentation errors |
[tools/](_rewrite/tools) | the extraction scripts (route walker, post-processing, config generation, capture adapter, schema diff) |
Library versions used for the extraction: Python 3.12.7, oslo.policy 6.0.1, oslo.config 10.7.0, SQLAlchemy 2.0.52, Routes 2.5.1, WebOb 1.8.11, all from the requirements stable/2026.2 upper-constraints.
2. HTTP behaviour that the contracts do not show
2.1 Pipeline and which error wins
Outermost first (etc/cinder/api-paste.ini:15): request ID → CORS → proxy headers → fault wrapper → body size limit → profiler → keystonemiddleware (401) → context (401 without body if no user) → router. Then, inside Resource.__call__ (cinder/api/openstack/wsgi.py:814-966), in this order:
- microversion header: malformed → 400, out of range → 406 — checked before routing to the method, so an unsupported version on an unknown resource gives 406;
Content-Typenot JSON → 415;- method lookup: unknown action key → 400, controller without the method → 404;
- body JSON parsing → 400 "Malformed request body";
project_idin the URL different from the token's → 400 "Malformed request url";- handler: schema validation (400), policy (403), not found (404), state (400/409), quota (413);
- a versioned method outside its range → 404 with no message.
2.2 Error body
{"<faultName>": {"code": <int>, "message": "<text>"}}, faultName by status (badRequest, unauthorized, forbidden, itemNotFound, badMethod, conflictingRequest, overLimit, badMediaType, notImplemented, serviceUnavailable, else computeFault); 413 adds retryAfter and Retry-After: 0 (cinder/api/openstack/wsgi.py:1310-1363). Clients (cinderclient, hence Nova) map the HTTP status to exception classes; the message text is displayed but not parsed by Nova (nova:nova/volume/cinder.py:371-420). Masking: for non-Invalid/NotFound errors the message is replaced by webob's generic text unless the exception is safe, and safe messages are prefixed with the class name ("VolumeSizeExceedsAvailableQuota: …", cinder/api/middleware/fault.py:55-79).
2.3 Other cross-cutting rules
- Default microversion is 3.0 when the header is absent; responses always echo
OpenStack-API-Version: volume X.YandVary: OpenStack-API-Version. - Project-less URLs (
/v3/volumes) are accepted at every microversion, in addition to/v3/{project_id}/volumes. - Empty path segments are tolerated:
/v3//volumesand/v3/<project>//volumes/<id>are served (200) — Cinder's own functional client depends on it (158 such requests inexchanges_stable-2026.2.json). A Go router must collapse//. - Body actions: a single top-level key selects the handler; more than one key → 400.
- Request ID headers: both
x-openstack-request-idandx-compute-request-id. - Version discovery:
GET /→ 300 with{"versions": [{"id": "v3.0", "status": "CURRENT", "version": "3.71", "min_version": "3.0", "media-types": […], "links": […]}]}(cinder/api/versions.py:32-46); Nova readsversionandmin_versionfrom the entry whoseversioncontains"3."(nova:nova/volume/cinder.py:84-134). - Lists:
limit≤osapi_max_limit(1000),marker,offset,sort=key:dir,…,with_count(3.45 →countin the body),*_linkswithrel: next. Filters not inresource_filters.json→ 400 from 3.34 [inferred: strictness tied tomv.LIKE_FILTER/RESOURCE_FILTERincinder/volume/api.py:2342-2474]. - QoS
associate/disassociateare GET requests with side effects — keep them. DELETEreturns202with empty body for volumes, snapshots, backups;204for metadata, attachments and types [see theSuccesscolumn of 01].
3. Algorithms and contracts a rewrite must reproduce
3.1 Resource state machine (compare-and-swap)
Every transition started by the API is one SQL UPDATE … WHERE <expected> (conditional_update, cinder/db/api.py:551); 0 rows updated means a 400 (or 409) with an explanatory message. A rewrite must keep the same expected sets, because clients and Tempest assert on them:
| Operation | Expected (volume unless noted) | New value | Source |
|---|---|---|---|
| delete | not attached; migration done/none; no group; no snapshots (unless cascade); status ∈ {available, error, error_restoring, error_extending, error_managing} unless force | deleting (unmanaging, error_managing_deleting) | cinder/volume/api.py:477-520 |
| extend | available (or in-use from 3.42) | extending, previous_status | cinder/volume/api.py:1551-1600 |
| revert | volume available; snapshot available (latest) | reverting; snapshot restoring | cinder/volume/api.py:387-412 |
| reserve (attachments) | available, downloading (+ in-use, reserved if multiattach); override when the same instance already has an attachment | reserved | cinder/volume/api.py:2430-2463 |
| backup create | volume available (or in-use with force) / snapshot available | backing-up | cinder/backup/api.py:218-374 |
| transfer create | available | awaiting-transfer | cinder/transfer/api.py:131-233 |
| upload to image | available (or in-use with force and enable_force_upload) | uploading | cinder/volume/api.py:1480-1550 |
The statuses are strings in the database and in the API (cinder/objects/fields.py:25-210); os-reset_status writes any of them.
3.2 Scheduling (where a volume is placed)
Inputs: the volume size, the volume type extra specs (capabilities, RESKEY:availability_zones, provisioning:type), the AZ, scheduler hints (same_host, different_host, local_to_instance), and the per-pool stats reported every minute by each backend (§ 3.5 of async).
- Candidates: pools of
cinder-volumeservices that are up (updated_atwithinservice_down_time), not disabled, not frozen (cinder/scheduler/host_manager.py:680-700). - Filters (default AZ, Capacity, Capabilities; configurable): capacity rule of async § 2.2;
CapabilitiesFiltermatches every extra spec of the type (operators<is> True,<in>,>=,<or>…) against the pool's capabilities, ignoring scoped keys other thancapabilities:[inferred:cinder/scheduler/filters/capabilities_filter.py]. - Weighers (default
CapacityWeigher, multiplier 1.0), normalised byOrderedHostWeightHandler; highest weight wins; ties broken by order [inferred]. - Retry: the attempted backends travel in
filter_properties.retry; at mostscheduler_max_attempts(3).
With CSI, OpenKeel can keep this algorithm with a single "pool" per storage class, or delegate placement to the CSI driver. Either way, the observable contract is: host = host@backend#pool on the volume (admin attribute os-vol-host-attr:host), GET /scheduler-stats/get_pools shape, and error + VOLUME_VOLUME_001_003 when nothing fits.
3.3 Quotas
- Resources:
volumes,snapshots,gigabytes,backups,backup_gigabytes,groups, and per volume typevolumes_<type>,snapshots_<type>,gigabytes_<type>;per_volume_gigabytesis a limit check, not a reservation. Defaultsquota_*options;-1= unlimited (cinder/quota.py:36-80). - Two-phase:
reserve(locksquota_usagesrows of the projectFOR UPDATE, refreshes usage whenuntil_refresh/max_agesay so, raisesOverQuotaifin_use + reserved + delta > limit, insertsreservationsexpiring afterreservation_expire), thencommitorrollback; expired reservations are rolled back by the scheduler (cinder/quota.py:319-440,cinder/db/api.py:1585-1760). - Translation to HTTP 413 messages per resource (
cinder/quota_utils.py:90-140). - Transfers move quota from donor to recipient (
cinder/transfer/api.py:235-330).
3.4 Transfer authentication key
salt = first 8 hex characters of sha224(urandom(255)) concatenations, auth_key = 16 such characters (volume_transfer_salt_length, volume_transfer_key_length); stored crypt_hash = HMAC-SHA1(key=salt, msg=auth_key).hexdigest() (cinder/transfer/api.py:106-129). Existing transfers created by Python must be acceptable by the rewrite: reproduce the HMAC exactly.
3.5 User messages
event_id = "VOLUME_" + resource_type + "_" + action_id + "_" + detail_id (cinder/message/api.py:97-105), with the action and detail tables of cinder/message/message_field.py, message_level ERROR, expires_at = now + message_ttl. Clients (Horizon, Skyline) display user_message = "<action text>: <detail text>" [inferred: cinder/api/v3/views/messages.py].
3.6 Microversion-dependent views
Volume detail fields by version are listed in 01 § 2.1; the per-field x-microversions of response-schemas_stable-2026.2.json show what the tests observed. View builders: cinder/api/v3/views/volumes.py, cinder/api/views/*.py.
4. What the clients expect
| Expectation | Where the client relies on it |
|---|---|
version document at the unversioned endpoint, entry with version containing 3. and min_version | nova:nova/volume/cinder.py:84-134 |
attachments API at 3.44 (POST/GET/PUT/DELETE /attachments, os-complete) | nova:nova/volume/cinder.py:747-950 |
os-reimage at 3.68 with reimage_reserved | nova:nova/volume/cinder.py:953 |
attachment_update and attachment_delete are retried 5 times on 500 or 504: they must be idempotent | nova:nova/volume/cinder.py:853-860, nova:nova/volume/cinder.py:905-911 |
DELETE /attachments/{id} answering 404 is treated as success | nova:nova/volume/cinder.py:910-922 |
404 → VolumeNotFound, 413 → OverQuota, 400 → InvalidInput, 403 → Forbidden (status codes, not messages) | nova:nova/volume/cinder.py:371-420 |
connection_info = {"driver_volume_type": …, "data": {…}} understood by os-brick / libvirt volume drivers — for OpenKeel's KubeVirt-based Nova, the shape the Nova rewrite expects for CSI volumes must be agreed separately [inferred] | nova:nova/virt/block_device.py |
Cinder calls Nova os-server-external-events volume-extended (2.51) and volume-reimaged (2.93) with tag = volume ID | cinder/compute/nova.py:142-241 |
| deleting a connected attachment requires Nova's service token (bug 2004555) | cinder/volume/api.py:2588-2661 |
5. Database strategy
- Reuse the schema to run Python and Go side by side during the migration: use
schema-sqlite-migrated_stable-2026.2.sql(or the MySQL result ofcinder-manage db sync) as the contract, including the two unused columns ofschema-diff_stable-2026.2.md. - Soft delete everywhere (
deleted,deleted_at);volumes.deletedis a boolean, ids are UUID strings;quota_*,reservations,*_metadatahave integer ids. - Concurrency: compare-and-swap updates (§ 3.1),
SELECT … FOR UPDATEfor quotas, tooz locks per resource for long operations ([coordination]/backend_url, default local files — use a distributed backend with more than one replica). servicesrows are the liveness and version registry; the Go services must write heartbeats in the same table if Python services coexist, and setrpc_current_version/object_current_versionso that Python clients pin correctly.- The
workerstable implements crash recovery; keep its semantics or the equivalent.
6. Authentication, policies, configuration and CLI
- Tokens validated like keystonemiddleware (cache, service token for Nova,
X-Service-Token); context from the validated token: roles, project, domain,is_admin= policycontext_is_admin(role:admin). - 167 oslo.policy rules with the
xena_system_admin_or_project_*andadmin_apidefaults (§ 1.7 of 01); operators' policy files must keep working, so the rule names and the check language (role:,project_id:%(project_id)s,rule:,is_admin:True,is_admin_project:True) are part of the contract. - Configuration: the options that change behaviour visible to clients are listed in 01–05 (
osapi_max_limit,default_volume_type,allow_availability_zone_fallback,snapshot_same_host,enable_force_upload,message_ttl, quotas, scheduler filters/weighers,service_down_time,report_interval…);config_stable-2026.2.confhas them all. - CLI parity worth keeping:
cinder-manage db sync|purge,service list|remove,volume update_host(cinder/cmd/manage.py).
7. Conformance strategy
- **Differential testing on
exchanges_stable-2026.2.json: 967 requests from 196 passing tests, at microversions 3.0 to 3.71. The database is built by the tests through the API itself, so the exchanges replay **in order against an empty Go Cinder configured like the test fixture (noauthuser:projecttokens,default_volume_typeset, a fake backend that always succeeds,cinder/tests/fake_driver.py); compare statuses and bodies with IDs, timestamps and URLs normalised. Test IDs are not recorded (§ index). - Response shapes: validate Go responses against
response-schemas_stable-2026.2.json; for the 116 operations without an observed schema, use the api-ref fields of the OpenAPI document. - Tempest volume API tests (
tempest/api/volume) and the cinder-tempest-plugin (both cloned under OpenKeel) are the black-box suite to run against the rewrite, with the RBAC tests underenforce_scope/enforce_new_defaults. - Nova: the Nova functional tests that use
CinderFixturedo not hit a real Cinder; the end-to-end check is Tempesttempest/scenariovolume attach/extend tests. - Original fixtures for reference:
cinder/tests/functional/functional_helpers.py:117-170(services started, paste pipeline),cinder/tests/functional/api/client.py(client),cinder/tests/fake_driver.py(backend).
8. Gaps found while building the pack
- No gabbi suite and no recorded test IDs for exchanges; replay of the exchanges against the Python service was not run (the recording itself is the run: 196/196 tests passed).
- Only 102 of 218 operations have observed response schemas (the functional suite covers volumes, snapshots, backups, types, QoS, quotas, services, transfers, groups; it does not cover attachments, messages, workers, manageable resources, clusters in depth).
os-extend_volume_completionmicroversion 3.71 is not enforced (01 § spec/code mismatches); decide whether the rewrite enforces it (Novastable/2026.2does not call the action at all).- 13 policy
operationsentries point to non-existent routes; 58 operations have no documented policyoperations. - Config sample generated with
googleapiclientstubbed (GCS backup driver options are present, their defaults are literal values and unaffected).
9. Non-functional requirements
- Request path never blocks on storage except the synchronous RPC calls of § 1.3 of 03 (
attachment_update,initialize_connection…), bounded byrpc_response_timeout(60 s) — Nova retries them on 500/504. - Capabilities flow: one stats message per backend per minute to every scheduler; liveness with
report_interval10 s /service_down_time60 s. - Message retention 30 days; reservations expire after 1 day.
10. Rewrite checklist
- [ ] Router with project-less and
//tolerant paths, body-key action dispatch, microversion negotiation (default 3.0, max 3.71, 406/400/404 rules). - [ ] Fault body, masking rules,
Retry-Afteron 413, request ID headers. - [ ] The 112 request schemas with their microversion ranges.
- [ ] Views per microversion (volume, snapshot, backup, attachment, type…).
- [ ] State machine with the expected sets of § 3.1 and the same error messages for the common cases (Tempest checks statuses, not texts).
- [ ] Quotas with reservations, per-type resources, 413 messages, transfer quota moves.
- [ ] Scheduler (or CSI delegation) with
host@backend#poolandget_pools. - [ ] Attachments API compatible with Nova 3.44 flow, idempotent update/delete, service-token check on delete.
- [ ] Nova external events after extend and reimage.
- [ ] User messages (
event_idformat, TTL, purge). - [ ] Notifications
volume.*,snapshot.*,backup.*if Ceilometer is used. - [ ] Policies with the same rule names and defaults.
- [ ] Database: migrated schema, soft delete,
servicesheartbeats,workersrecovery. - [ ] Transfer HMAC-SHA1 compatibility.
- [ ] Replay
exchanges_stable-2026.2.jsonand run Tempest volume tests.
Source: reverse-spec-cinder/07-rewrite-pack_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.