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.
| System | Protocol | Library | Direction | Configuration | Usage |
|---|---|---|---|---|---|
| MariaDB / MySQL (SQLite in tests) | SQL | oslo.db, SQLAlchemy | glance-api, glance-scrubber, glance-manage → DB | [database]/connection | image metadata, members, tags, locations, tasks, metadefs, centralized cache metadata |
| Image stores | depends on the driver: local files, Ceph RADOS, Swift/S3 REST, Cinder REST + iSCSI/RBD attach, HTTP | glance_store | glance-api, glance-scrubber → store | [glance_store] (single store) or enabled_backends + one section per store | image data: write on upload/import, read on download, delete |
| RabbitMQ (or any oslo.messaging transport) | AMQP, notifications only | oslo.messaging | glance-api → bus | [oslo_messaging_notifications]/driver, transport_url | image.*, task.*, metadef_*.* events |
| Keystone | REST | keystonemiddleware, keystoneauth, python-keystoneclient, oslo.limit | glance-api → Keystone | [keystone_authtoken], [oslo_limit] | token validation, trusts for long uploads, unified limits |
| Barbican (key manager) | REST | castellan, cursive | glance-api → Barbican | [key_manager] | signature certificates, deletion of Cinder encryption keys |
| Other glance-api workers | REST (Glance API) | keystoneauth | worker → worker | worker_self_reference_url | proxy of import/delete to the worker that holds staged data or runs a hash |
| Remote Glance (another region) | REST (Glance API) | urllib | glance-api → remote Glance | service catalog from the request | glance-download import method |
| Any HTTP(S) server | HTTP(S) GET | urllib (pinned IP) | glance-api → server | [import_filtering_opts] | web-download import, legacy import task, HTTP locations |
| Local file system | files | os, glance_store file driver | glance-api | node_staging_uri or reserved os_glance_staging_store / os_glance_tasks_store, image_cache_dir | staging, task work dir, image cache |
qemu-img | process | oslo.concurrency processutils | glance-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).
| Table | Role | Written by | Read by |
|---|---|---|---|
images | one 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, scrubber | every image endpoint, cache, scrubber, staging cleanup |
image_properties | extra properties (name, value), including the internal os_glance_* properties (glance/db/sqlalchemy/models.py:148) | PATCH, import tasks (locks, progress markers), add-location | image views, quotas, import lock |
image_tags | image tags (glance/db/sqlalchemy/models.py:167) | PATCH tags, PUT/DELETE /v2/images/{id}/tags/{tag} | image views, list filter tag= |
image_locations | store 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, scrubber | download, delete, locations/direct_url/stores in views |
image_members | sharing: member (project), status (pending, accepted, rejected), can_share (glance/db/sqlalchemy/models.py:195) | /v2/images/{id}/members | list visibility (shared images), member endpoints |
tasks, task_info | asynchronous 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_types | metadata definitions catalog (glance/db/sqlalchemy/models_metadef.py:66-160) | /v2/metadefs/*, glance-manage db load_metadefs | /v2/metadefs/* |
node_reference, cached_images | centralized 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_updateandimage_destroyretry 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=…)addsWHERE status = :from_stateand raisesConflictwhen no row matches (glance/db/sqlalchemy/api.py:1036-1054). Upload uses it twice:queued → saving, thensaving → 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_atomicinsertsimage_properties(image_id, name, value)and relies on the unique constraint(image_id, name)to fail withDuplicatewhen the property exists (glance/db/sqlalchemy/api.py:891);image_delete_property_atomicdeletes 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, andsharedimages where the project is a member with the requestedmember_status(defaultaccepted). Without avisibilityfilter, community images are excluded unless owned by the caller (glance/db/sqlalchemy/api.py:697-713). - Usage queries for quotas:
image_size_totalsums the sizes of the owner's non-deleted images (glance/db/sqlalchemy/api.py:770),image_stage_totalthe sizes ofuploading/importingimages (glance/db/sqlalchemy/api.py:788),image_count_totalandimage_count_uploadingcount rows (glance/db/sqlalchemy/api.py:823,glance/db/sqlalchemy/api.py:831). They run on each quota check whenuse_keystone_limitsis true. - Purge:
glance-manage db purge --age_in_days N --max_rows Mhard-deletes soft-deleted rows;imagesrows are purged by the separateglance-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:
| Operation | When | Call | Defined at |
|---|---|---|---|
| Write | upload, import into each store, legacy import | store_api.add_with_multihash / add_to_backend_with_multihash: streams the data and returns location, size, md5 checksum, multihash | glance/location.py:490-534 |
| Read | download, copy-image, cache prefetch, location hash | store_api.get (multi-store) or get_from_backend, tried location by location | glance/location.py:702-735 |
| Delete | image delete, location removal, delete-from-store, scrubber | delete_from_backend (through glance/common/store_utils.py) | glance/scrubber.py:390 |
| Validate a location | locations PATCH/POST, image create with locations | get_size_from_backend, scheme check | glance/location.py:133-164 |
| Staging | stage, import | reserved os_glance_staging_store and os_glance_tasks_store, always file | glance/common/wsgi_app.py:48-51 |
- Multi-store: with
enabled_backends = id:type,…, each location carriesmetadata.store = <id>; the upload target comes from theX-Image-Meta-Storeheader (default[glance_store]/default_backend), the import targets fromstores/all_storesin 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_type | Priority | Emitted when | Payload | Emitted at |
|---|---|---|---|---|
image.create | INFO | image row created | image payload (below) | glance/notifier.py:535 |
image.update | INFO | any image save (PATCH, status changes, tags) | image payload | glance/notifier.py:531 |
image.delete | INFO | image deleted | image payload + deleted: true, deleted_at | glance/notifier.py:539 |
image.prepare | INFO | before 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.upload | INFO | data written | image payload | glance/notifier.py:500 |
image.upload | ERROR | data write failed (storage full, write denied, quota, timeout, signature…) | error message string | glance/notifier.py:446-497 |
image.activate | INFO | image became active after the write | image payload | glance/notifier.py:503 |
image.send | INFO / ERROR | download stream finished (bytes sent, receiver) or failed | bytes_sent, image_id, owner_id, receiver_tenant_id, receiver_user_id; ERROR when fewer bytes than expected were sent | glance/notifier.py:421 |
image.member.create / .update / .delete | INFO | sharing changes | image_id, member_id, status, timestamps | glance/notifier.py:572, glance/notifier.py:568, glance/notifier.py:576 |
task.create / task.run / task.processing / task.success / task.failure / task.delete | INFO | task lifecycle | task 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_tags | INFO | metadef namespace changes | namespace payload | glance/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 / .delete | INFO | metadef children changes | object/property/tag/association payload with *_old names | glance/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 service | Method + path | When | Authentication | Timeout / retry | Errors handled | Defined at |
|---|---|---|---|---|---|---|
| Keystone | GET /v3/auth/tokens (keystonemiddleware) | every request on the keystone* pipelines | service credentials of [keystone_authtoken] | keystonemiddleware settings, memcached token cache | 401 to the client | etc/glance-api-paste.ini:77-79 |
| Keystone | POST /v3/OS-TRUST/trusts, token for the trust, DELETE trust | upload of image data on the keystone pipelines, so that the final DB update survives token expiry | user token, then trust | keystoneauth defaults | failure to create a trust is logged ("Unable to create trust") and the user token is used | glance/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] credentials | oslo.limit | ProjectOverLimit → 413; SessionInitError re-raised → 500 | glance/quota/keystone.py:36-73 |
| Barbican | GET /v1/secrets/{cert_uuid} | upload or import of an image carrying img_signature* properties | user token (castellan) | castellan | signature errors → 400 | glance/location.py:582-594 |
| Barbican | DELETE /v1/secrets/{id} | image delete when cinder_encryption_key_deletion_policy = on_image_deletion | user token | castellan | Forbidden / not found / error only logged | glance/api/v2/images.py:720-744 |
| Another glance-api worker | same 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 worker | the user's token (keystoneauth token_endpoint) | 60 s, no retry | connection error → 504, other request error → 502, remote 4xx/5xx → same status | glance/api/v2/images.py:246-291 |
| Remote Glance (region from the body) | GET {endpoint}/v2/images/{id}, then GET {endpoint}/v2/images/{id}/file | glance-download import | the user's token in X-Auth-Token | urllib defaults | non-active remote image or size mismatch → task failure | glance/async_/flows/api_image_import.py:823-860, glance/async_/flows/_internal_plugins/glance_download.py:46-80 |
| Any HTTP(S) server | GET <uri> with redirects re-validated | web-download, legacy import, HTTP locations | none | urllib defaults | filtered URL → 400 at request time or task failure | glance/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, defaultfile:///tmp/staging/,glance/common/config.py:585, or the reservedos_glance_staging_storewithenabled_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=jsonandqemu-img convertforimage_conversion, run withprocessutilsand the[wsgi]/python_interpreterprlimit wrapper (glance/async_/flows/plugins/image_conversion.py:150,glance/async_/flows/plugins/image_conversion.py:212);glance-controlstarts daemons withsubprocess(glance/cmd/control.py:130). - Format inspection: every
bareupload goes throughoslo_utils.imageutils.format_inspectoron the fly; the detected format must matchdisk_formatwhen[image_format]/require_image_format_matchis 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.