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

Reverse-spec · Services

glance — Interactions

Interactions — glance — branch stable/2026.2

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

Library versions in the analysis venv (upper-constraints of requirements stable/2026.2): glance_store 5.7.0, oslo.messaging, oslo.db, oslo.limit, castellan, keystonemiddleware at their constrained versions. Prefixed references glance_store: point to openstack/glance_store stable/2026.2.

SystemProtocolLibraryDirectionConfigurationUsage
MariaDB / MySQL (SQLite in tests)SQLoslo.db, SQLAlchemyglance-api, glance-scrubber, glance-manage → DB[database]/connectionimage metadata, members, tags, locations, tasks, metadefs, centralized cache metadata
Image storesdepends on the driver: local files, Ceph RADOS, Swift/S3 REST, Cinder REST + iSCSI/RBD attach, HTTPglance_storeglance-api, glance-scrubber → store[glance_store] (single store) or enabled_backends + one section per storeimage data: write on upload/import, read on download, delete
RabbitMQ (or any oslo.messaging transport)AMQP, notifications onlyoslo.messagingglance-api → bus[oslo_messaging_notifications]/driver, transport_urlimage.*, task.*, metadef_*.* events
KeystoneRESTkeystonemiddleware, keystoneauth, python-keystoneclient, oslo.limitglance-api → Keystone[keystone_authtoken], [oslo_limit]token validation, trusts for long uploads, unified limits
Barbican (key manager)RESTcastellan, cursiveglance-api → Barbican[key_manager]signature certificates, deletion of Cinder encryption keys
Other glance-api workersREST (Glance API)keystoneauthworker → workerworker_self_reference_urlproxy of import/delete to the worker that holds staged data or runs a hash
Remote Glance (another region)REST (Glance API)urllibglance-api → remote Glanceservice catalog from the requestglance-download import method
Any HTTP(S) serverHTTP(S) GETurllib (pinned IP)glance-api → server[import_filtering_opts]web-download import, legacy import task, HTTP locations
Local file systemfilesos, glance_store file driverglance-apinode_staging_uri or reserved os_glance_staging_store / os_glance_tasks_store, image_cache_dirstaging, task work dir, image cache
qemu-imgprocessoslo.concurrency processutilsglance-api → binary[image_conversion]image_conversion import plugin

Database

Tables

The model DDL (glance/db/sqlalchemy/models.py, glance/db/sqlalchemy/models_metadef.py) matches the migrated schema on SQLite: 15 tables, 11 foreign keys, 28 indexes and 9 unique constraints on both sides, 0 difference (see 07 § 5). Every table except task_info, node_reference and cached_images is soft-deleted (deleted, deleted_at columns, glance/db/sqlalchemy/models.py:62).

TableRoleWritten byRead by
imagesone row per image: status, formats, size, virtual_size, checksum, os_hash_algo/os_hash_value, visibility, protected, os_hidden, owner, min_disk/min_ram (glance/db/sqlalchemy/models.py:113)image create/update/delete, upload, import tasks, scrubberevery image endpoint, cache, scrubber, staging cleanup
image_propertiesextra properties (name, value), including the internal os_glance_* properties (glance/db/sqlalchemy/models.py:148)PATCH, import tasks (locks, progress markers), add-locationimage views, quotas, import lock
image_tagsimage tags (glance/db/sqlalchemy/models.py:167)PATCH tags, PUT/DELETE /v2/images/{id}/tags/{tag}image views, list filter tag=
image_locationsstore URLs of the data, meta_data (JSON, holds store), status (active, pending_delete, deleted) (glance/db/sqlalchemy/models.py:181)upload, import, locations PATCH/POST, delete-from-store, scrubberdownload, delete, locations/direct_url/stores in views
image_memberssharing: member (project), status (pending, accepted, rejected), can_share (glance/db/sqlalchemy/models.py:195)/v2/images/{id}/memberslist visibility (shared images), member endpoints
tasks, task_infoasynchronous tasks; task_info holds input, result, message (glance/db/sqlalchemy/models.py:220, glance/db/sqlalchemy/models.py:241)task creation, executor, flows/v2/tasks, /v2/images/{id}/tasks, import lock
metadef_namespaces, metadef_objects, metadef_properties, metadef_tags, metadef_resource_types, metadef_namespace_resource_typesmetadata definitions catalog (glance/db/sqlalchemy/models_metadef.py:66-160)/v2/metadefs/*, glance-manage db load_metadefs/v2/metadefs/*
node_reference, cached_imagescentralized image-cache metadata: one row per worker URL, one per cached image and node (glance/db/sqlalchemy/models.py:260, glance/db/sqlalchemy/models.py:274)cache driver centralized_db/v2/cache, cache middleware

Notable transactions

  • Retries: image_create, image_update and image_destroy retry up to 50 times, every 0.5 s, on deadlock and disconnect (glance/db/sqlalchemy/api.py:148-149, glance/db/sqlalchemy/api.py:165-166, glance/db/sqlalchemy/api.py:203-204).
  • Status compare-and-swap: image_update(..., from_state=…) adds WHERE status = :from_state and raises Conflict when no row matches (glance/db/sqlalchemy/api.py:1036-1054). Upload uses it twice: queued → saving, then saving → active (glance/api/v2/image_data.py:161, glance/api/v2/image_data.py:167). A concurrent delete therefore makes the upload fail with 410 instead of resurrecting the image. sql UPDATE images SET status = 'saving', updated_at = :now WHERE id = :image_id AND status = 'queued' -- 0 rows → Conflict → HTTP 409/410
  • Atomic properties (import lock): image_set_property_atomic inserts image_properties(image_id, name, value) and relies on the unique constraint (image_id, name) to fail with Duplicate when the property exists (glance/db/sqlalchemy/api.py:891); image_delete_property_atomic deletes only if the value still matches (glance/db/sqlalchemy/api.py:946).
  • Visibility of the image list (glance/db/sqlalchemy/api.py:588-627): for a non-admin, the list is the union of public and community images, images owned by the caller's project, and shared images where the project is a member with the requested member_status (default accepted). Without a visibility filter, community images are excluded unless owned by the caller (glance/db/sqlalchemy/api.py:697-713).
  • Usage queries for quotas: image_size_total sums the sizes of the owner's non-deleted images (glance/db/sqlalchemy/api.py:770), image_stage_total the sizes of uploading/importing images (glance/db/sqlalchemy/api.py:788), image_count_total and image_count_uploading count rows (glance/db/sqlalchemy/api.py:823, glance/db/sqlalchemy/api.py:831). They run on each quota check when use_keystone_limits is true.
  • Purge: glance-manage db purge --age_in_days N --max_rows M hard-deletes soft-deleted rows; images rows are purged by the separate glance-manage db purge_images_table (default age 180 days, glance/cmd/manage.py:371), as image IDs must not be reused [inferred: reason from the command split] (glance/db/sqlalchemy/api.py:1561, glance/db/sqlalchemy/api.py:1660).

Image stores (glance_store)

glance_store is a separate library with one driver per backend (glance_store:glance_store/_drivers/: filesystem.py, rbd.py, swift/, s3.py, cinder/, http.py, vmware_datastore.py). Glance calls it for:

OperationWhenCallDefined at
Writeupload, import into each store, legacy importstore_api.add_with_multihash / add_to_backend_with_multihash: streams the data and returns location, size, md5 checksum, multihashglance/location.py:490-534
Readdownload, copy-image, cache prefetch, location hashstore_api.get (multi-store) or get_from_backend, tried location by locationglance/location.py:702-735
Deleteimage delete, location removal, delete-from-store, scrubberdelete_from_backend (through glance/common/store_utils.py)glance/scrubber.py:390
Validate a locationlocations PATCH/POST, image create with locationsget_size_from_backend, scheme checkglance/location.py:133-164
Stagingstage, importreserved os_glance_staging_store and os_glance_tasks_store, always fileglance/common/wsgi_app.py:48-51
  • Multi-store: with enabled_backends = id:type,…, each location carries metadata.store = <id>; the upload target comes from the X-Image-Meta-Store header (default [glance_store]/default_backend), the import targets from stores/all_stores in the body (glance/api/v2/image_data.py:117-127).
  • Location URL schemes: file://, rbd://<fsid>/<pool>/<image>/<snap>, swift+https://…, s3://…, cinder://<volume_id>, http(s)://… (read-only). Swift and S3 URLs may embed credentials, which is why notifications never include locations (glance/notifier.py:138-143).
  • Error mapping: the store exceptions are translated into HTTP statuses by the handlers and by the notifier layer (glance/notifier.py:436-503); see 02.

RabbitMQ (notifications)

Glance sends no RPC. It only publishes notifications through oslo_messaging.Notifier on the notification transport (glance/notifier.py:94-104), with publisher_id = [DEFAULT]/default_publisher_id (default image.localhost, glance/notifier.py:33). Every event is sent synchronously, in the API worker, after the database change. [DEFAULT]/disabled_notifications drops an event or a whole group (image, task, metadef_namespace…) (glance/notifier.py:121-135).

Notifications

event_typePriorityEmitted whenPayloadEmitted at
image.createINFOimage row createdimage payload (below)glance/notifier.py:535
image.updateINFOany image save (PATCH, status changes, tags)image payloadglance/notifier.py:531
image.deleteINFOimage deletedimage payload + deleted: true, deleted_atglance/notifier.py:539
image.prepareINFObefore the data is written (upload, import into a store)image payload + os_glance_importing_to_stores, os_glance_failed_import (lists)glance/notifier.py:436
image.uploadINFOdata writtenimage payloadglance/notifier.py:500
image.uploadERRORdata write failed (storage full, write denied, quota, timeout, signature…)error message stringglance/notifier.py:446-497
image.activateINFOimage became active after the writeimage payloadglance/notifier.py:503
image.sendINFO / ERRORdownload stream finished (bytes sent, receiver) or failedbytes_sent, image_id, owner_id, receiver_tenant_id, receiver_user_id; ERROR when fewer bytes than expected were sentglance/notifier.py:421
image.member.create / .update / .deleteINFOsharing changesimage_id, member_id, status, timestampsglance/notifier.py:572, glance/notifier.py:568, glance/notifier.py:576
task.create / task.run / task.processing / task.success / task.failure / task.deleteINFOtask lifecycletask payload without input (may hold secrets)glance/notifier.py:625, glance/notifier.py:602, glance/notifier.py:590, glance/notifier.py:594, glance/notifier.py:598, glance/notifier.py:630
metadef_namespace.create / .update / .delete / .delete_objects / .delete_properties / .delete_tagsINFOmetadef namespace changesnamespace payloadglance/notifier.py:687, glance/notifier.py:679, glance/notifier.py:694, glance/notifier.py:702, glance/notifier.py:709, glance/notifier.py:716
metadef_object.*, metadef_property.*, metadef_tag.*, metadef_resource_type.create / .deleteINFOmetadef children changesobject/property/tag/association payload with *_old namesglance/notifier.py:750-927

Image payload (glance/notifier.py:138-165): id, name, status, created_at, updated_at, min_disk, min_ram, protected, checksum, owner, disk_format, container_format, size, virtual_size, is_public, visibility, properties, tags, deleted, deleted_at. No locations.

Sample frame

Message published to exchange glance (or [DEFAULT]/control_exchange), routing key notifications.info:

{"oslo.version": "2.0",
 "oslo.message": "{\"message_id\": \"6c1f…\", \"publisher_id\": \"image.localhost\", \"event_type\": \"image.activate\", \"priority\": \"INFO\", \"timestamp\": \"2026-10-28 21:00:00.000000\", \"payload\": {\"id\": \"8c4a…\", \"name\": \"cirros\", \"status\": \"active\", \"disk_format\": \"qcow2\", \"container_format\": \"bare\", \"size\": 21430272, \"virtual_size\": 117440512, \"checksum\": \"c8fc…\", \"visibility\": \"private\", \"is_public\": false, \"owner\": \"5ef7…\", \"properties\": {\"os_hash_algo\": \"sha512\"}, \"tags\": [], \"deleted\": false, \"deleted_at\": null}, \"_context_…\": {}}"}

The context is empty: Notifier.info/warn/error pass {} as context (glance/notifier.py:107-114), so no _context_* user or project fields travel with Glance notifications.

Outbound REST calls

Target serviceMethod + pathWhenAuthenticationTimeout / retryErrors handledDefined at
KeystoneGET /v3/auth/tokens (keystonemiddleware)every request on the keystone* pipelinesservice credentials of [keystone_authtoken]keystonemiddleware settings, memcached token cache401 to the clientetc/glance-api-paste.ini:77-79
KeystonePOST /v3/OS-TRUST/trusts, token for the trust, DELETE trustupload of image data on the keystone pipelines, so that the final DB update survives token expiryuser token, then trustkeystoneauth defaultsfailure to create a trust is logged ("Unable to create trust") and the user token is usedglance/common/trust_auth.py:27-80, glance/api/v2/image_data.py:143-182
Keystone (unified limits)GET /v3/limits, GET /v3/registered_limits, GET /v3/endpoints/{id} [inferred: done by oslo.limit, not traced]each quota check with use_keystone_limits = true[oslo_limit] credentialsoslo.limitProjectOverLimit → 413; SessionInitError re-raised → 500glance/quota/keystone.py:36-73
BarbicanGET /v1/secrets/{cert_uuid}upload or import of an image carrying img_signature* propertiesuser token (castellan)castellansignature errors → 400glance/location.py:582-594
BarbicanDELETE /v1/secrets/{id}image delete when cinder_encryption_key_deletion_policy = on_image_deletionuser tokencastellanForbidden / not found / error only loggedglance/api/v2/images.py:720-744
Another glance-api workersame method and path as the client request (POST …/import, DELETE /v2/images/{id})the image was staged (os_glance_stage_host) or is being hashed (os_glance_hash_op_host) on another workerthe user's token (keystoneauth token_endpoint)60 s, no retryconnection error → 504, other request error → 502, remote 4xx/5xx → same statusglance/api/v2/images.py:246-291
Remote Glance (region from the body)GET {endpoint}/v2/images/{id}, then GET {endpoint}/v2/images/{id}/fileglance-download importthe user's token in X-Auth-Tokenurllib defaultsnon-active remote image or size mismatch → task failureglance/async_/flows/api_image_import.py:823-860, glance/async_/flows/_internal_plugins/glance_download.py:46-80
Any HTTP(S) serverGET <uri> with redirects re-validatedweb-download, legacy import, HTTP locationsnoneurllib defaultsfiltered URL → 400 at request time or task failureglance/common/scripts/utils.py:246-258

**Endpoint discovery for glance-download** is done in the X-Service-Catalog header that keystonemiddleware adds: the first image service endpoint whose region matches glance_region, with interface glance_service_interface (default public) (glance/async_/utils.py:81-95). Missing endpoint → GlanceEndpointNotFound.

URL filtering ([import_filtering_opts], glance/async_/flows/_internal_plugins/__init__.py:26-173): schemes allowed by default http, https; ports 80 and 443; allow-lists win over deny-lists. Loopback and link-local addresses (including 169.254.169.254) are refused unless the host is allow-listed; the TCP connection is pinned to the validated IP so that DNS cannot rebind during the download (glance/common/utils.py:229-260, glance/common/scripts/utils.py:246-258).

Other

  • Files: staging (node_staging_uri, default file:///tmp/staging/, glance/common/config.py:585, or the reserved os_glance_staging_store with enabled_backends); legacy task work dir ([task]/work_dir); image cache (image_cache_dir); cancellation markers of hash operations in the tasks store (glance/task_cancellation_tracker.py:29-36).
  • Processes: qemu-img info --output=json and qemu-img convert for image_conversion, run with processutils and the [wsgi]/python_interpreter prlimit wrapper (glance/async_/flows/plugins/image_conversion.py:150, glance/async_/flows/plugins/image_conversion.py:212); glance-control starts daemons with subprocess (glance/cmd/control.py:130).
  • Format inspection: every bare upload goes through oslo_utils.imageutils.format_inspector on the fly; the detected format must match disk_format when [image_format]/require_image_format_match is true (default), and GPT/MBR safety checks apply (glance/location.py:596-700).

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