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

Reverse-spec · Services

glance — Rewrite pack

Rewrite pack — glance — branch stable/2026.2

Branch stable/2026.2 @ a3e5f490b7a2 — repository https://opendev.org/openstack/glance — analysed on 2026-10-28

Client repositories cited with a prefix, all on stable/2026.2: nova: @ 8df1922926dd, cinder: @ c117f9cf08b4, glanceclient: (python-glanceclient) @ 9463fb3e4e77. Black-box suites: tempest @ 9831863ccfea, glance-tempest-plugin.

This file adds to the contracts of _rewrite/ what only the code and the clients tell: the cross-cutting HTTP behaviour, the parts of the behaviour a rewrite would get wrong from the api-ref alone (image data path, import lock, list visibility), what Nova, Cinder and glanceclient rely on, and how to prove conformance. For OpenKeel, the storage target is a block CSI through CDI (generate-go-from-spec reference references/backends/glance-csi-block.md); § 6 gives the part of glance_store a rewrite must keep.

1. Contents of _rewrite/

FileUse in a rewrite
[routes_stable-2026.2.json](_rewrite/routes_stable-2026.2.json)the 80 routes (78 under /v2, /, /versions) with handler and policy; dumped from the live mapper. Add the 7 legacy cache routes of 01 if the cache-management pipeline is kept
[openapi_stable-2026.2.json](_rewrite/openapi_stable-2026.2.json)operation list and parameters from the api-ref; no request bodies (Glance's schemas are built at run time, see the index)
[api-ref_stable-2026.2.json](_rewrite/api-ref_stable-2026.2.json)the 79 documented operations, with parameters and samples
[policies_stable-2026.2.json](_rewrite/policies_stable-2026.2.json), policies_stable-2026.2.yamlthe 67 default rules with check strings, scope types, deprecated rules and operations
[config_stable-2026.2.conf](_rewrite/config_stable-2026.2.conf)every option of glance-api (oslo-config-generator etc/oslo-config-generator/glance-api.conf), with defaults
[schema-mysql_stable-2026.2.sql](_rewrite/schema-mysql_stable-2026.2.sql), schema-postgresql_…, schema-sqlite-migrated_…, [schema-diff_stable-2026.2.md](_rewrite/schema-diff_stable-2026.2.md)database schema; models and migrations match (0 differences)
[exchanges_stable-2026.2.json](_rewrite/exchanges_stable-2026.2.json)2441 real request/response pairs from 239 functional tests: the reference for differential testing
[response-schemas_stable-2026.2.json](_rewrite/response-schemas_stable-2026.2.json)inferred response schemas of 69 operations
[observed-errors_stable-2026.2.md](_rewrite/observed-errors_stable-2026.2.md)288 (operation, status, message) combinations

Library versions: upper-constraints of requirements stable/2026.2 (glance_store 5.7.0, oslo.policy, oslo.limit, keystonemiddleware at their pinned versions), Python 3.13.

2. HTTP behaviour that the contracts do not show

2.1 Middleware pipeline

Outermost first, keystone flavour (etc/glance-api-paste.ini:42-44):

  1. cors (oslo.middleware), with Glance's allowed and exposed headers (glance/common/config.py:797-821).
  2. http_proxy_to_wsgi: honours X-Forwarded-Proto / Forwarded when enabled.
  3. versionnegotiation: before authentication. / and an unknown version prefix answer 300 with the version document, /versions answers 200, both without a token (glance/api/middleware/version_negotiation.py:41-71). It rewrites /v2.x/… to /v2/….
  4. osprofiler.
  5. authtoken (keystonemiddleware) with delay_auth_decision = true: invalid tokens are not refused here; the next filter refuses them.
  6. context: 401 unless X-Identity-Status: Confirmed (or anonymous access is allowed); builds the context; adds x-openstack-request-id to every response (glance/api/middleware/context.py:72-112).
  7. cache / cachemanage (optional): may answer a download from the local cache, or the 7 legacy cache routes, before the router.
  8. Router → wsgi.Resource: route match (else 404), then body_reject check (400 when a body is sent to a body-less route), deserializer (400/403/415), controller (policy, then business checks), serializer.

Consequences a rewrite must keep: version errors win over authentication; 401 comes before routing, so an unknown path without a token is 401, with a token 404; schema validation (400) happens before the policy check (403) on create and PATCH, because the deserializer runs first.

2.2 Error body

webob's default error page, negotiated on Accept (text, JSON {"message", "code", "title"}, or HTML), no business code: see 02 § Error body. python-glanceclient builds its exception detail from the JSON message values, or strips the HTML, or keeps the text (glanceclient:glanceclient/exc.py:154-170). A rewrite should produce exactly webob's three forms, including the trailing <br /><br /> of the JSON message, if any client compares texts.

2.3 Other cross-cutting rules

  • **Unknown query parameters are not rejected on GET /v2/images**: every parameter that is not a known filter becomes an exact-match filter on an extra property (glance/db/sqlalchemy/api.py:570-575). ?foo=bar returns images whose property foo equals bar.
  • Content types: JSON bodies are parsed whatever the Content-Type, except PATCH (application/openstack-images-v2.0-json-patch or …-v2.1-json-patch, 415 + Accept-Patch otherwise) and data uploads (application/octet-stream, 415 otherwise).
  • Response headers: Location and OpenStack-image-import-methods (plus OpenStack-image-store-ids with multi-store) on image creation; Content-MD5 = image checksum on downloads; Content-Range with 206; no cache headers (ETag, Last-Modified).
  • Request id: an incoming X-Openstack-Request-ID is reused; one longer than max_request_id_length (64) makes _get_authenticated_context return an HTTPRequestHeaderFieldsTooLarge that becomes the request context (glance/api/middleware/context.py:136-141) [inferred: the client then gets an error from the first use of the context, not the intended 431; not exercised by the tests].
  • Proxying between workers: worker_self_reference_url must be set per worker when imports or location hashing are used on several workers; requests reaching the wrong worker are replayed to the right one with the user's token (03).

3. Core behaviour: formal specification

3.1 Image state machine

States and allowed transitions: glance/domain/__init__.py:99-115 (diagram in 01). Rules attached to transitions:

  • queued|uploading → saving|active|importing requires disk_format and container_format (glance/domain/__init__.py:159-170), else ValueError → 400.
  • Any transition to queued clears size and virtual_size (glance/domain/__init__.py:173-175).
  • disk_format/container_format can change only in queued and importing.
  • Delete: protected → 403; delayed_delete and locations → pending_delete, else deleted.
  • Status writes during upload use compare-and-swap on the previous status (from_state), see 03.

3.2 Image data path (upload, import into a store)

Inputs: the byte stream, the declared size (from x-openstack-image-size or Content-Length, may be absent), the target store, the image (disk_format, container_format, img_signature* properties).

check unified limits: image_size_total (+ size MiB), then image_count_uploading       glance/api/v2/image_data.py:109-115, glance/api/v2/image_data.py:162-163
status queued -> saving (CAS)                                                          glance/api/v2/image_data.py:161
stream = LimitingReader(stream, min(remaining legacy user_storage_quota, image_size_cap))  glance/quota/__init__.py:312-320, glance/common/utils.py:461-498
if img_signature* properties: verifier = cursive.get_verifier(certificate from Barbican)   glance/location.py:582-594
if container_format == bare: stream = format_inspector.InspectWrapper(stream)            glance/location.py:596-602
location, size, md5, multihash = store.add(stream, hashing_algorithm=os_hash_algo or [DEFAULT]/hashing_algorithm)  glance/location.py:490-534
verify signature on the hashes                                                         glance/location.py:536-558
if values already recorded on the image differ (size, os_hash_value, checksum): UploadException   glance/location.py:560-572
format rules (bare only):                                                              glance/location.py:620-693
  detected == disk_format          -> virtual_size = inspector.virtual_size
  detected iso+gpt                 -> treated as iso
  detected raw, disk_format not inspectable (ami, aki…) -> accepted, no virtual size
  disk_format == raw, detected other -> accepted, virtual_size = actual bytes (warning)
  other mismatch                   -> 415 if [image_format]/require_image_format_match (default true)
  safety checks (GPT/MBR…)         -> 415 unless listed in gpt_safety_checks_nonfatal (mbr always nonfatal for aki/ari)
re-check legacy quota with the final size; on excess delete the data, 413              glance/quota/__init__.py:322-357
status saving -> active (CAS); trust-refreshed token if the user token expired         glance/api/v2/image_data.py:165-174

Outputs a rewrite must reproduce byte for byte: checksum = md5 hex of the stored bytes; os_hash_algo = sha512 by default and os_hash_value = its hex digest; size = bytes stored; virtual_size as computed above. Nova and glanceclient verify downloads against these values (§ 4).

3.3 Import lock

os_glance_import_task image property, set atomically with the task id (unique (image_id, name) insert), taken by import and by add-location; stolen when its task is missing, or older than 60 minutes in processing/success/failure, or 120 minutes in pending (glance/api/v2/images.py:171-220); released by the last task of the flow. Internal progress properties: os_glance_importing_to_stores, os_glance_failed_import (comma-separated store ids), os_glance_stage_host, os_glance_hash_op_host; every os_glance_* name is reserved for clients (403).

3.4 Image list visibility and ordering

  • Visibility set: § "Visibility of the image list" of 03; admins see everything unless they filter.
  • Ordering: sort_key list (default created_at) and sort_dir (default desc); created_at and id are appended as tie-breakers, so pages are stable (glance/db/sqlalchemy/api.py:730-733).
  • Pagination: marker = last image id of the previous page; next link present when the database returned limit rows, even if policy then removed some (glance/api/v2/images.py:568-573): a page may be shorter than limit without being the last one.
  • os_hidden images are excluded unless os_hidden=true is asked.

3.5 Test cases

No pure algorithm here lends itself to calls vectors the way Placement's allocation candidates or Keystone's tokens did. The behaviour is covered by the recorded exchanges (§ 7). The format-inspection rules of § 3.2 rely on oslo_utils.imageutils.format_inspector; a Go rewrite needs an equivalent inspector (qcow2, vmdk, vhd/vhdx, vdi, iso, gpt/mbr, raw) and should be tested on the same sample images (glance/tests/functional/v2/test_images.py builds them).

4. What the clients expect

ExpectationWhere the client relies on it
GET /versions lists v2.12 (tasks of an image), v2.14 (cache API) and v2.17 (locations API) with status CURRENT, SUPPORTED or EXPERIMENTAL; otherwise the client raises HTTPNotImplemented without calling the endpoint. With Glance's own document, v2.12 is only listed with enabled_backends and v2.14 only with image_cache_dirglanceclient:glanceclient/common/utils.py:182-188, glanceclient:glanceclient/v2/images.py:209, glanceclient:glanceclient/v2/images.py:587, glanceclient:glanceclient/v2/cache.py:24-28
POST /v2/images/{id}/locations and GET …/locations exist (2024.2+) and accept a token with the service role; Nova and Cinder fall back to the PATCH locations API when they get HTTPNotImplementednova:nova/image/glance.py:548-562, cinder:cinder/image/glance.py:399-421, cinder:cinder/image/glance.py:466-473
GET …/locations returns a list of {"url", "metadata": {"store": …}}; the old path reads direct_url and locations on the imagecinder:cinder/image/glance.py:428-441
a download of an image without data returns 204 (glanceclient yields no iterator) and Nova turns it into ImageUnacceptable "Image has no associated data"nova:nova/image/glance.py:380-383
downloads are verified with os_hash_algo/os_hash_value, else with the Content-MD5 headerglanceclient:glanceclient/v2/images.py:225-300
503 is retried [glance]/num_retries times by Nova (with connection errors)nova:nova/image/glance.py:162-166
statuses → Nova exceptions: 401/403 → ImageNotAuthorized, 404 → ImageNotFound, 400 → ImageBadRequest/Invalid, 413 → ImageQuotaExceeded; delete 409 → ImageDeleteConflict; copy-image 409 → ImageImportImpossible, 400 → ImageBadRequest ("already present at store")nova:nova/image/glance.py:993-1015, nova:nova/image/glance.py:729-733, nova:nova/image/glance.py:761-768
import with method: copy-image and stores is how Nova copies an image to another store (multi-store)nova:nova/image/glance.py:756-760
PATCH uses application/openstack-images-v2.1-json-patch; uploads may name a store with X-Image-Meta-Storeglanceclient:glanceclient/v2/images.py:476, glanceclient:glanceclient/v2/images.py:320
GET /v2/schemas/image describes the accepted disk_formats: Nova picks the default snapshot format from itnova:nova/image/glance.py:589-610
every response carries x-openstack-request-id (clients log it and attach it to their results)glanceclient:glanceclient/common/http.py:297

5. Database strategy

  • Schema: reuse it as is (schema-mysql_stable-2026.2.sql); models and migrations agree. Keep the soft-delete columns: Glance never reuses an image id, and glance-manage db purge_images_table is the only place where image rows disappear.
  • Integrity: foreign keys exist for every child table (11). The import lock relies on the unique constraint image_properties(image_id, name).
  • Concurrency: no SELECT … FOR UPDATE. Consistency comes from compare-and-swap updates on status (from_state), atomic insert/delete of properties, and deadlock retries (50 × 0.5 s) on image create/update/destroy. A Galera deployment needs the same retry on certification failures (they surface as deadlocks).
  • Start-up data: none required; metadefs are loaded by glance-manage db load_metadefs from etc/metadefs/*.json (≈ 30 namespaces).
  • Online data migrations: glance-manage db migrate (expand/contract) with data migrations in glance/db/sqlalchemy/alembic_migrations/data_migrations/; the 2024.1 expand adds the cache tables.

6. Authentication, policies, configuration, CLI, stores

  • Authentication: keystonemiddleware semantics, including X-Service-Token (service roles feed add_image_location), X-Service-Catalog (needed by glance-download), and the trust dance for long uploads (optional in a rewrite if uploads finish within token lifetime).
  • Policies: oslo.policy rule language with target fields project_id, owner, member_id, visibility ('public':%(visibility)s); the default rules in policies_stable-2026.2.json are the secure-RBAC ones (enforce_new_defaults true). Deployment tools pass overrides in a policy file: a rewrite that wants to be a drop-in must evaluate the same language. The 403→404 hiding of § 2 of 01 is part of the contract.
  • Property protections (property_protection_file, roles or policies per property regex) are optional; without the file, the layer is absent.
  • Configuration: the options of config_stable-2026.2.conf; the ones that change the API surface are enabled_backends, image_cache_dir, enabled_import_methods, show_multiple_locations, show_image_direct_url, use_keystone_limits, delayed_delete, [image_format], [import_filtering_opts].
  • CLI: glance-manage db sync|upgrade|expand|migrate|contract|purge|purge_images_table|load_metadefs|unload_metadefs|export_metadefs, glance-scrubber, glance-status upgrade check; the cache commands are deprecated.
  • glance_store contract (what to keep when replacing the library with a Go Store interface on a CSI backend):
    • write returns location URL, size, md5 and multihash computed on the stream; read supports offset and length (for Range); delete; size of a location;
    • errors mapped as in 02 § Store errors (StorageFull 413, StorageWriteDenied 503, NotFound on download 204, TimeoutError 503, InUseByStore/HasSnapshot 409);
    • location format {"url": "<scheme>://…", "metadata": {"store": "<id>"}}, with a stable scheme for the CSI backend (for example csi://<namespace>/<pvc>), and the multi-store discovery (/v2/info/stores) if more than one store class is offered;
    • the reserved staging store (os_glance_staging_store, file) for glance-direct, web-download, copy-image, glance-download. The Swift, Ceph, S3, Cinder and VMware drivers do not need porting for OpenKeel.

7. Conformance strategy

  • Black-box suites: Tempest tempest/api/image (102 tests, including admin metadefs, tasks and cache) and glance-tempest-plugin (174 tests, API, RBAC and scenario) run against any implementation; enable [image-feature-enabled] flags to match the options kept (import, multi-store, cache).
  • Differential testing: replay exchanges_stable-2026.2.json test by test against the rewrite, comparing status codes, response bodies (normalising ids, timestamps and {TEST_DIR}), and headers listed in § 2.3. The captures are not seeded: each test builds its data through earlier requests of the same test (and a few through direct database or store access), so replay in recorded order per test, on an empty database, with the same configuration as the test (start_server options in glance/tests/functional/__init__.py:507-549). Validate bodies against response-schemas_stable-2026.2.json.
  • Data path: upload the same files to Python Glance and to the rewrite and compare checksum, os_hash_value, size, virtual_size and the 415 cases (§ 3.2).
  • Original fixtures for reference: SynchronousAPIBase.setUp (database, stores and staging under a temporary directory, glance/tests/functional/__init__.py:436-470, glance/tests/functional/__init__.py:85, glance/tests/functional/__init__.py:407), start_server (pipelines and options), glance/tests/functional/__init__.py:586-626 (api_request, identity headers X-Identity-Status, X-Roles, X-Tenant-Id).

8. Gaps found while building the pack

  • The api-ref and the code differ by 4 undocumented operations and none documented-but-missing (01 § "Spec/code mismatches").
  • Request bodies are not in the OpenAPI document (run-time schemas).
  • The recorder had to be adapted to webob-style calls (app(request)) and to webob error pages; one test differs because the recorder reads bodies to the end (index notes).
  • Not covered: the glance-cache-* deprecated commands beyond their deprecation notice, glance-replicator, glance-control, the VMware/Swift/S3 driver-specific behaviour, property protections with a real rules file.

9. Non-functional requirements

  • Streaming: uploads and downloads must stream (images of hundreds of GiB); Glance never buffers image data in memory, and computes hashes on the fly.
  • Long requests: uploads can outlive the user token (trusts) and the HTTP timeouts of proxies; imports are asynchronous for that reason.
  • Worker affinity: staging and hash operations are local to a worker; either keep the proxying, or put staging on shared storage.
  • Security: URL filtering with IP pinning for every outbound fetch (SSRF); format inspection on upload (malicious qcow2/vmdk headers); os_glance_* properties reserved; locations hidden by default (they may hold credentials).

10. Rewrite checklist

  • [ ] Version negotiation before authentication; version document listing v2.12, v2.14, v2.17 according to the features kept (§ 4).
  • [ ] webob-compatible error bodies for the three Accept forms; x-openstack-request-id on every response.
  • [ ] Image state machine and compare-and-swap status writes (§ 3.1).
  • [ ] Data path with md5 + sha512 on the stream, image_size_cap, quotas, format inspection rules (§ 3.2).
  • [ ] Import methods kept (glance-direct, web-download, copy-image, glance-download), import lock and progress properties (§ 3.3), task records and expiry.
  • [ ] Locations API for services (POST/GET …/locations) and the PATCH locations behaviour if show_multiple_locations is offered.
  • [ ] List visibility, filters (including arbitrary property filters), sorting, pagination (§ 3.4).
  • [ ] Members, tags, actions, delete-from-store, discovery, /v2/info/usage with unified limits.
  • [ ] Metadefs CRUD and load_metadefs.
  • [ ] oslo.policy-compatible evaluation of the 67 default rules and of operator overrides; 403→404 hiding.
  • [ ] Notifications with the same event types and payloads (03).
  • [ ] Store interface replacing glance_store (§ 6), staging store, scrubber if delayed_delete is kept.
  • [ ] Tempest image tests and glance-tempest-plugin green; differential replay of exchanges_stable-2026.2.json.

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