REST API — glance — branch stable/2026.2
Branch
stable/2026.2@a3e5f490b7a2— repository https://opendev.org/openstack/glance — analysed on 2026-10-28
How the API is served
- Routing: a
routes.Mapperbuilt at start-up byglance/api/v2/router.py:37, dispatched towsgi.Resourceobjects (deserializer → controller → serializer,glance/common/wsgi.py:461). The table below was dumped from the live mapper (78 operations under/v2, plus/and/versions); every path also has a catch-all entry that answers 405 for the other methods (reject_method_resource,glance/api/v2/router.py:39,glance/api/v2/router.py:47-50). - Versions: there are no per-request microversions.
/v2,/v2.0…/v2.18are aliases of the same API: the version negotiation filter strips the version segment (or readsAccept: application/vnd.openstack.images-v2.x) and rewrites the path to/v2/…(glance/api/middleware/version_negotiation.py:41-71). The set of accepted aliases depends on the configuration:v2.8,v2.10–v2.13only withenabled_backends,v2.14,v2.16,v2.18only withimage_cache_dir(glance/api/middleware/version_negotiation.py:73-97). An unknown version gets the version document with 300 (glance/api/middleware/version_negotiation.py:62-65). The document itself listsv2.18asCURRENTwhen the cache is enabled,v2.17otherwise (glance/api/versions.py:83-128). - Pipeline (
etc/glance-api-paste.ini:39-44), outermost first:cors→http_proxy_to_wsgi→versionnegotiation→osprofiler→authtoken(keystonemiddleware,delay_auth_decision = true) →context→ optionalcacheandcachemanage→ router. Thedefault,cachingandcachemanagementflavours replaceauthtoken contextwithunauthenticated-context, which makes every request admin (glance/api/middleware/context.py:160-170): a test-only flavour that must never be deployed. - Identity:
ContextMiddlewarebuilds the request context from the headers that keystonemiddleware sets whenX-Identity-Status: Confirmed; roles are lower-cased;X-Service-Catalogis parsed for later outbound calls. Without a confirmed identity, the request is refused with 401 unlessallow_anonymous_access(default false) gives a read-only anonymous context (glance/api/middleware/context.py:107-112). Every response carriesx-openstack-request-id(glance/api/middleware/context.py:72-85). - Authorisation: oslo.policy rules of
glance/policies/(67 rules, policies_stable-2026.2.json), enforced in the controllers throughglance/api/v2/policy.py. With the defaultenforce_new_defaults = truethey are the secure-RBAC rules shown below (admin,member,readerroles, project scope). A refusal on an image or metadef namespace that the caller cannot see is turned into 404 (glance/api/v2/policy.py:173-193). - Errors: see 02-errors_stable-2026.2.md.
Summary
Handler lines come from the live mapper (_rewrite/routes_stable-2026.2.json). Policy names are those whose operations declare the route; most handlers also check other rules internally (listed in the sheets). Errors are the statuses the handler can return besides 401 from keystonemiddleware and 500.
| Method | Path | Handler | Policy | Success | Errors |
|---|---|---|---|---|---|
| GET | /v2/schemas/image | glance/api/v2/schemas.py:58 Controller.image | — | 200 | — |
| GET | /v2/schemas/images | glance/api/v2/schemas.py:61 Controller.images | — | 200 | — |
| GET | /v2/schemas/member | glance/api/v2/schemas.py:64 Controller.member | — | 200 | — |
| GET | /v2/schemas/members | glance/api/v2/schemas.py:67 Controller.members | — | 200 | — |
| GET | /v2/schemas/task | glance/api/v2/schemas.py:70 Controller.task | — | 200 | — |
| GET | /v2/schemas/tasks | glance/api/v2/schemas.py:73 Controller.tasks | — | 200 | — |
| GET | /v2/schemas/metadefs/namespace | glance/api/v2/schemas.py:76 Controller.metadef_namespace | — | 200 | — |
| GET | /v2/schemas/metadefs/namespaces | glance/api/v2/schemas.py:79 Controller.metadef_namespaces | — | 200 | — |
| GET | /v2/schemas/metadefs/resource_type | glance/api/v2/schemas.py:82 Controller.metadef_resource_type | — | 200 | — |
| GET | /v2/schemas/metadefs/resource_types | glance/api/v2/schemas.py:85 Controller.metadef_resource_types | — | 200 | — |
| GET | /v2/schemas/metadefs/property | glance/api/v2/schemas.py:88 Controller.metadef_property | — | 200 | — |
| GET | /v2/schemas/metadefs/properties | glance/api/v2/schemas.py:91 Controller.metadef_properties | — | 200 | — |
| GET | /v2/schemas/metadefs/object | glance/api/v2/schemas.py:94 Controller.metadef_object | — | 200 | — |
| GET | /v2/schemas/metadefs/objects | glance/api/v2/schemas.py:97 Controller.metadef_objects | — | 200 | — |
| GET | /v2/schemas/metadefs/tag | glance/api/v2/schemas.py:100 Controller.metadef_tag | — | 200 | — |
| GET | /v2/schemas/metadefs/tags | glance/api/v2/schemas.py:103 Controller.metadef_tags | — | 200 | — |
| GET | /v2/metadefs/resource_types | glance/api/v2/metadef_resource_types.py:49 ResourceTypeController.index | list_metadef_resource_types | 200 | 403, 404 |
| GET | /v2/metadefs/namespaces/{namespace}/resource_types | glance/api/v2/metadef_resource_types.py:77 ResourceTypeController.show | get_metadef_resource_type | 200 | 403, 404 |
| POST | /v2/metadefs/namespaces/{namespace}/resource_types | glance/api/v2/metadef_resource_types.py:120 ResourceTypeController.create | add_metadef_resource_type_association | 201 | 400, 403, 404, 409 |
| DELETE | /v2/metadefs/namespaces/{namespace}/resource_types/{resource_type} | glance/api/v2/metadef_resource_types.py:158 ResourceTypeController.delete | remove_metadef_resource_type_association | 204 | 403, 404 |
| GET | /v2/metadefs/namespaces | glance/api/v2/metadef_namespaces.py:60 NamespaceController.index | get_metadef_namespaces | 200 | 403, 404 |
| POST | /v2/metadefs/namespaces | glance/api/v2/metadef_namespaces.py:131 NamespaceController.create | add_metadef_namespace | 201 | 400, 403, 404, 409 |
| GET | /v2/metadefs/namespaces/{namespace} | glance/api/v2/metadef_namespaces.py:261 NamespaceController.show | get_metadef_namespace | 200 | 403, 404 |
| PUT | /v2/metadefs/namespaces/{namespace} | glance/api/v2/metadef_namespaces.py:342 NamespaceController.update | modify_metadef_namespace | 200 | 400, 403, 404, 409 |
| DELETE | /v2/metadefs/namespaces/{namespace} | glance/api/v2/metadef_namespaces.py:389 NamespaceController.delete | delete_metadef_namespace | 204 | 403, 404 |
| GET | /v2/metadefs/namespaces/{namespace}/properties | glance/api/v2/metadef_properties.py:64 NamespacePropertiesController.index | get_metadef_properties | 200 | 403, 404 |
| POST | /v2/metadefs/namespaces/{namespace}/properties | glance/api/v2/metadef_properties.py:148 NamespacePropertiesController.create | add_metadef_property | 201 | 400, 403, 404, 409 |
| DELETE | /v2/metadefs/namespaces/{namespace}/properties | glance/api/v2/metadef_namespaces.py:475 NamespaceController.delete_properties | — | 204 | 403, 404 |
| GET | /v2/metadefs/namespaces/{namespace}/properties/{property_name} | glance/api/v2/metadef_properties.py:98 NamespacePropertiesController.show | get_metadef_property | 200 | 403, 404 |
| PUT | /v2/metadefs/namespaces/{namespace}/properties/{property_name} | glance/api/v2/metadef_properties.py:185 NamespacePropertiesController.update | modify_metadef_property | 200 | 400, 403, 404, 409 |
| DELETE | /v2/metadefs/namespaces/{namespace}/properties/{property_name} | glance/api/v2/metadef_properties.py:223 NamespacePropertiesController.delete | remove_metadef_property | 204 | 403, 404 |
| GET | /v2/metadefs/namespaces/{namespace}/objects | glance/api/v2/metadef_objects.py:94 MetadefObjectsController.index | get_metadef_objects | 200 | 403, 404 |
| POST | /v2/metadefs/namespaces/{namespace}/objects | glance/api/v2/metadef_objects.py:50 MetadefObjectsController.create | add_metadef_object | 201 | 400, 403, 404, 409 |
| DELETE | /v2/metadefs/namespaces/{namespace}/objects | glance/api/v2/metadef_namespaces.py:415 NamespaceController.delete_objects | — | 204 | 403, 404 |
| GET | /v2/metadefs/namespaces/{namespace}/objects/{object_name} | glance/api/v2/metadef_objects.py:141 MetadefObjectsController.show | get_metadef_object | 200 | 403, 404 |
| PUT | /v2/metadefs/namespaces/{namespace}/objects/{object_name} | glance/api/v2/metadef_objects.py:174 MetadefObjectsController.update | modify_metadef_object | 200 | 400, 403, 404, 409 |
| DELETE | /v2/metadefs/namespaces/{namespace}/objects/{object_name} | glance/api/v2/metadef_objects.py:223 MetadefObjectsController.delete | delete_metadef_object | 204 | 403, 404 |
| GET | /v2/metadefs/namespaces/{namespace}/tags | glance/api/v2/metadef_tags.py:139 TagsController.index | get_metadef_tags | 200 | 403, 404 |
| POST | /v2/metadefs/namespaces/{namespace}/tags | glance/api/v2/metadef_tags.py:95 TagsController.create_tags | add_metadef_tags | 201 | 400, 403, 404, 409 |
| DELETE | /v2/metadefs/namespaces/{namespace}/tags | glance/api/v2/metadef_namespaces.py:443 NamespaceController.delete_tags | delete_metadef_tags | 204 | 403, 404 |
| GET | /v2/metadefs/namespaces/{namespace}/tags/{tag_name} | glance/api/v2/metadef_tags.py:185 TagsController.show | get_metadef_tag | 200 | 403, 404 |
| POST | /v2/metadefs/namespaces/{namespace}/tags/{tag_name} | glance/api/v2/metadef_tags.py:51 TagsController.create | add_metadef_tag | 201 | 400, 403, 404, 409 |
| PUT | /v2/metadefs/namespaces/{namespace}/tags/{tag_name} | glance/api/v2/metadef_tags.py:214 TagsController.update | modify_metadef_tag | 200 | 400, 403, 404, 409 |
| DELETE | /v2/metadefs/namespaces/{namespace}/tags/{tag_name} | glance/api/v2/metadef_tags.py:253 TagsController.delete | delete_metadef_tag | 204 | 403, 404 |
| GET | /v2/images | glance/api/v2/images.py:517 ImagesController.index | get_images | 200 | 400, 401, 403 |
| POST | /v2/images | glance/api/v2/images.py:93 ImagesController.create | add_image | 201 | 400, 401, 403, 409, 413, 503 |
| PATCH | /v2/images/{image_id} | glance/api/v2/images.py:616 ImagesController.update | modify_image, publicize_image, communitize_image, delete_image_location, set_image_location | 200 | 400, 401, 403, 404, 409, 413, 415, 503 |
| GET | /v2/images/{image_id} | glance/api/v2/images.py:587 ImagesController.show | get_image, get_image_location | 200 | 401, 404 |
| DELETE | /v2/images/{image_id} | glance/api/v2/images.py:865 ImagesController.delete | delete_image | 204 | 400, 401, 403, 404, 409, 503 |
| GET | /v2/images/{image_id}/tasks | glance/api/v2/images.py:599 ImagesController.get_task_info | — | 200 | 404 |
| POST | /v2/images/{image_id}/import | glance/api/v2/images.py:316 ImagesController.import_image | copy_image | 202 | 400, 403, 404, 409, 413, 502, 504 |
| DELETE | /v2/stores/{store_id}/{image_id} | glance/api/v2/images.py:746 ImagesController.delete_from_store | — | 204 | 401, 403, 404, 409, 500, 503 |
| POST | /v2/images/{image_id}/actions/deactivate | glance/api/v2/image_actions.py:46 ImageActionsController.deactivate | deactivate | 204 | 400, 403, 404 |
| POST | /v2/images/{image_id}/actions/reactivate | glance/api/v2/image_actions.py:75 ImageActionsController.reactivate | reactivate | 204 | 400, 403, 404 |
| GET | /v2/images/{image_id}/file | glance/api/v2/image_data.py:479 ImageDataController.download | download_image, download_from_store | 200, 206 (Range) | 204 (no data), 400, 403, 404, 416, 503 |
| PUT | /v2/images/{image_id}/file | glance/api/v2/image_data.py:107 ImageDataController.upload | upload_image | 204 | 400, 401, 403, 404, 409, 410, 413, 415, 503 |
| PUT | /v2/images/{image_id}/stage | glance/api/v2/image_data.py:329 ImageDataController.stage | — | 204 | 400, 403, 404, 409, 413, 415, 503 |
| POST | /v2/images/{image_id}/locations | glance/api/v2/images.py:1161 ImagesController.add_location | add_image_location | 202 | 400, 401, 403, 404, 409 |
| GET | /v2/images/{image_id}/locations | glance/api/v2/images.py:1259 ImagesController.get_locations | fetch_image_location | 200 | 403, 404 |
| PUT | /v2/images/{image_id}/tags/{tag_value} | glance/api/v2/image_tags.py:46 Controller.update | — | 204 | 400, 403, 404, 413 |
| DELETE | /v2/images/{image_id}/tags/{tag_value} | glance/api/v2/image_tags.py:74 Controller.delete | — | 204 | 403, 404 |
| GET | /v2/images/{image_id}/members | glance/api/v2/image_members.py:234 ImageMembersController.index | get_members | 200 | 403, 404 |
| POST | /v2/images/{image_id}/members | glance/api/v2/image_members.py:125 ImageMembersController.create | add_member | 200 | 400, 403, 404, 409, 413 |
| GET | /v2/images/{image_id}/members/{member_id} | glance/api/v2/image_members.py:282 ImageMembersController.show | get_member | 200 | 404 |
| PUT | /v2/images/{image_id}/members/{member_id} | glance/api/v2/image_members.py:185 ImageMembersController.update | modify_member | 200 | 400, 403, 404 |
| DELETE | /v2/images/{image_id}/members/{member_id} | glance/api/v2/image_members.py:314 ImageMembersController.delete | delete_member | 204 | 403, 404 |
| POST | /v2/tasks | glance/api/v2/tasks.py:69 TasksController.create | add_task, tasks_api_access | 201 | 400, 403 |
| GET | /v2/tasks | glance/api/v2/tasks.py:106 TasksController.index | get_tasks, tasks_api_access | 200 | 400, 403 |
| GET | /v2/tasks/{task_id} | glance/api/v2/tasks.py:136 TasksController.get | get_task, tasks_api_access | 200 | 403, 404 |
| DELETE | /v2/tasks/{task_id} | glance/api/v2/tasks.py:157 TasksController.delete | modify_task, tasks_api_access | 405 (always) | 403, 405 |
| GET | /v2/info/import | glance/api/v2/discovery.py:41 InfoController.get_image_import | — | 200 | — |
| GET | /v2/info/stores | glance/api/v2/discovery.py:53 InfoController.get_stores | — | 200 | 404 (no multi-store) |
| GET | /v2/info/usage | glance/api/v2/discovery.py:197 InfoController.get_usage | — | 200 | — |
| GET | /v2/info/stores/detail | glance/api/v2/discovery.py:165 InfoController.get_stores_detail | stores_info_detail | 200 | 403, 404 |
| GET | /v2/cache | glance/api/v2/cached_images.py:242 CacheController.get_cache_state | cache_list | 200 | 403, 404 (cache disabled) |
| DELETE | /v2/cache | glance/api/v2/cached_images.py:219 CacheController.clear_cache | cache_delete | 204 | 400, 403, 404 |
| DELETE | /v2/cache/{image_id} | glance/api/v2/cached_images.py:156 CacheController.delete_cache_entry | cache_delete | 204 | 403, 404 |
| PUT | /v2/cache/{image_id} | glance/api/v2/cached_images.py:252 CacheController.queue_image_from_api | cache_image | 202 | 400, 403, 404 |
| GET | /v2/cache/nodes/{image_id} | glance/api/v2/cached_images.py:189 CacheController.get_cached_nodes | list_cached_nodes | 200 | 403, 404, 409 |
| POST | /v2/cache/clean | glance/api/v2/cached_images.py:278 CacheController.clean_cache | cache_clean | 200 | 403, 404 |
| POST | /v2/cache/prune | glance/api/v2/cached_images.py:289 CacheController.prune_cache | cache_prune | 200 | 403, 404 |
| GET | / | glance/api/versions.py:61 Controller.index | — (public) | 300 | — |
| GET | /versions | glance/api/middleware/version_negotiation.py:48 VersionNegotiationFilter.process_request | — (public) | 200 | — |
Legacy cache-management routes (not in the router)
With the cachemanagement and keystone+cachemanagement pipelines, the cachemanage middleware answers 7 more routes before the router (glance/api/middleware/cache_manage.py:31-68). They are deprecated in favour of /v2/cache (glance/cmd/cache_manage.py:504), absent from the api-ref and from the route dump, and were seen in the recorded tests. All need manage_image_cache (admin) and return 404 when image_cache_dir is not set (glance/api/v2/cached_images.py:73-87).
| Method | Path | Handler | Success |
|---|---|---|---|
| GET | /v2/cached_images | glance/api/v2/cached_images.py:89 CacheController.get_cached_images | 200 {"cached_images": [...]} |
| DELETE | /v2/cached_images | glance/api/v2/cached_images.py:108 CacheController.delete_cached_images | 200 {"num_deleted": n} |
| DELETE | /v2/cached_images/{image_id} | glance/api/v2/cached_images.py:99 CacheController.delete_cached_image | 200 |
| GET | /v2/queued_images | glance/api/v2/cached_images.py:117 CacheController.get_queued_images | 200 {"queued_images": [...]} |
| PUT | /v2/queued_images/{image_id} | glance/api/v2/cached_images.py:127 CacheController.queue_image | 200 |
| DELETE | /v2/queued_images | glance/api/v2/cached_images.py:147 CacheController.delete_queued_images | 200 {"num_deleted": n} |
| DELETE | /v2/queued_images/{image_id} | glance/api/v2/cached_images.py:138 CacheController.delete_queued_image | 200 |
Spec/code mismatches
Comparing the routes with the rest_method directives of api-ref/source (79 documented operations, including the 2 of api-ref/source/versions/versions.inc):
- In the code, not documented:
DELETE /v2/metadefs/namespaces/{namespace}/objectsandDELETE /v2/metadefs/namespaces/{namespace}/properties(delete all objects / properties of a namespace,glance/api/v2/metadef_namespaces.py:415,glance/api/v2/metadef_namespaces.py:475);GET /v2/cache/nodes/{image_id}(glance/api/v2/cached_images.py:189);DELETE /v2/tasks/{task_id}(always 405); the 7 legacy cache routes above. - Documented, not in the code: none.
- The api-ref lists API versions up to 2.18; the code only advertises the versions allowed by the configuration (see How the API is served).
Images
POST /v2/images
Functional description
- Purpose: create the record of an image (metadata only). No data is stored yet.
- Actor: project member (
add_image: admin, ormemberof the project whose id equals the requestedowner,glance/policies/image.py:25). Making itpublicneedspublicize_image(admin);communityneedscommunitize_image. - Preconditions: unified-limits quota
image_count_totalnot reached (glance/api/v2/images.py:104). - Business rules:
- defaults:
visibility = shared,min_disk = min_ram = 0,protected = false,os_hidden = false,owner= caller's project (glance/domain/__init__.py:71-74,glance/api/v2/images.py:98-99); status always startsqueued; - the body is validated against
/v2/schemas/image(glance/api/v2/images.py:1331), whosedisk_formatandcontainer_formatenums come from[image_format](glance/api/v2/images.py:1955-1964); - read-only attributes (
idaside) anddirect_url,self,file,schema,storesare refused with 403, as arelocation,deleted,deleted_atand anyos_glance_*property (glance/api/v2/images.py:1285-1290,glance/api/v2/images.py:1357-1362); property names longer than 255 characters → 400; - every other key becomes an extra property (string value);
image_property_quota(128) andimage_tag_quota(128) apply; - a client-supplied
idmust be a UUID and unique (409 on duplicate).
- defaults:
- Flow: synchronous only: policy check, quota,
image.createnotification,INSERT images(+ properties, tags). - Outcome: 201 with the image view,
Location: /v2/images/{id}, andOpenStack-image-import-methods(andOpenStack-image-store-idswith multi-store) headers (glance/api/v2/images.py:1829-1843). - Limitations:
locationscannot be set at creation through the v2 schema.
GET /v2/images
- Purpose: list the images visible to the caller.
- Actor:
get_images(reader of the project); each image of the page is then filtered withget_image(glance/api/v2/images.py:554-566). - Visibility rule: public and community images, the caller's own images, and
sharedimages where the caller's project is a member with statusmember_status(defaultaccepted). Without avisibilityfilter, other projects' community images are not listed (see 03). - Parameters:
limit(defaultlimit_param_default= 25, capped byapi_limit_max= 1000),marker; sorting withsort_key/sort_dir(repeatable) orsort=key:dir,…(keys:name,status,container_format,disk_format,size,id,created_at,updated_at; defaultcreated_at desc); filtersname,id,status,container_format,disk_format(within:andeq:operators),created_at/updated_at(gt:,gte:,lt:,lte:,eq:,neq:+ ISO 8601),size_min,size_max,visibility(public,private,shared,community,all),owner,tag(repeatable, all must match),member_status,os_hidden(defaultfalse: hidden images are not listed),protected,checksum,os_hash_value, and any other key as an exact match on an extra property (glance/db/sqlalchemy/api.py:467-579,glance/api/v2/images.py:1589-1700). - Outcome: 200
{"images": [...], "first": "/v2/images?…", "schema": "/v2/schemas/images", "next": "/v2/images?…&marker=<id>"}.nextis present when the database returned a full page, even if policy filtering removed images from it (glance/api/v2/images.py:568-573).
GET /v2/images/{image_id}, PATCH /v2/images/{image_id}
- GET: returns the image view (
get_image): base attributes, extra properties at top level (except the internalos_glance_stage_hostandos_glance_hash_op_host),tags,self,file,schema;locationsonly withshow_multiple_locations,direct_urlonly withshow_image_direct_url,storeswith multi-store (glance/api/v2/images.py:1756-1825). An invisible image gives 404. - PATCH: JSON-patch with media type
application/openstack-images-v2.1-json-patch(RFC 6902 subsetadd,remove,replaceon top-level paths, and/locations/Nor/locations/-) or the older…-v2.0-json-patch(draft 4:addfails with 409 when the property already exists) (glance/api/v2/images.py:1493-1550,glance/api/v2/images.py:680-698).- Rules:
modify_imagefor every change; changingvisibilitycheckspublicize_image/communitize_image;ownercan only be changed by an admin (glance/api/v2/images.py:667-669); base attributes cannot be removed (403);disk_formatandcontainer_formatcan only change while the image isqueuedorimporting(glance/domain/__init__.py:202-221);replaceof an unknown property → 409. - Locations (only with
show_multiple_locations = true, otherwise 403): adding or replacing locations on aqueuedimage with consistentvalidation_data(checksum,os_hash_algo,os_hash_value) activates it (glance/api/v2/images.py:1066-1129); removing the last location is refused (403); each location is validated against the stores (400BadStoreUri). - Outcome: 200 with the full image view;
image.updatenotification.
- Rules:
DELETE /v2/images/{image_id}
- Actor:
delete_image. Protected images → 403. - Flow:
- if the data is staged on another worker (
os_glance_stage_host), the request is proxied there with the user's token, and its answer is returned (glance/api/v2/images.py:878-885); - a running location hash calculation is canceled (
glance/api/v2/images.py:890-894); - local staging residue is deleted (
glance/api/v2/images.py:896-931); - status
deleted, orpending_deletewithdelayed_delete(then the data is removed later by glance-scrubber, see 05); otherwise each location is deleted from its store right away (glance/location.py:481-488); - the Cinder encryption key is deleted when the image says
cinder_encryption_key_deletion_policy = on_image_deletion(glance/api/v2/images.py:720-744); image.deletenotification, soft delete of the rows.
- if the data is staged on another worker (
- Outcome: 204. Store refusals (
InUseByStore,HasSnapshot) → 409.
PUT /v2/images/{image_id}/file (upload)
- Purpose: upload the image data in one synchronous request.
- Actor:
upload_image(member of the owner project). - Preconditions: image
queued,disk_formatandcontainer_formatset; content typeapplication/octet-stream; quotasimage_size_total(with theContent-Lengthas delta) and thenimage_count_uploading. - Flow (all before the response):
queued → savingwith compare-and-swap (glance/api/v2/image_data.py:161);- the data is streamed to the store chosen by
X-Image-Meta-Store(multi-store) or the default store; md5checksum,os_hash_value(hashing_algorithm, default sha512) andsizeare computed on the stream; forcontainer_format = bare, the format is inspected on the fly andvirtual_sizecomputed (glance/location.py:574-700);image_size_capand the legacyuser_storage_quotacut the stream; - signature verification if the
img_signature*properties are set; saving → activewith compare-and-swap (glance/api/v2/image_data.py:167); on the Keystone pipelines a trust refreshes the token if it expired during a long upload.
- Outcome: 204; image
active; notificationsimage.prepare,image.upload,image.activate. - Failures: on most errors the image goes back to
queuedso that the upload can be retried (glance/api/v2/image_data.py:56-71); 410 if the image was deleted during the upload (the data written is deleted).
PUT /v2/images/{image_id}/stage
- Purpose: first step of
glance-directimport: store the data in the staging area of this worker. - Preconditions:
glance-directinenabled_import_methods(else 404), imagequeued, quotaimage_stage_total. - Flow: status
uploading, data written tonode_staging_uri/<id>or the reserved staging store,os_glance_stage_host = worker_self_reference_urlrecorded so that the later import is proxied to this worker (glance/api/v2/image_data.py:396-416). - Outcome: 204; image
uploading. On failure, the staged file is removed and the image goes back toqueued.
GET /v2/images/{image_id}/file (download)
- Actor:
download_image(owner or member);deactivatedimages can only be downloaded by admins (403 otherwise). - Rules:
prefer=store1,store2reorders the locations (needsdownload_from_storeand multi-store); locations are tried in order until one works (glance/location.py:702-735);Range/Content-Rangegive 206 withContent-Range, or 416 when unsatisfiable;Content-MD5is the image checksum (glance/api/v2/image_data.py:617-696). - Outcome: 200 (or 206)
application/octet-stream; 204 with no body when the image has no data or the store lost it; 503 when the remote store is down. With the cache middleware, a cached image is served from the local cache, and a miss fills the cache while streaming (05).image.sendnotification at the end of the stream.
POST /v2/images/{image_id}/import
Functional description
- Purpose: import data into an image through one of the interoperable import methods, asynchronously.
- Actor:
modify_imageon the image (owner), exceptcopy-image, which needscopy_image(admin by default) and runs with an admin context (glance/api/v2/images.py:380-387,glance/api/v2/images.py:451-457). - Body:
{"method": {"name": "glance-direct" | "web-download" | "copy-image" | "glance-download", "uri": …, "glance_region": …, "glance_image_id": …, "glance_service_interface": "public"}, "stores": [...], "all_stores": bool, "all_stores_must_succeed": bool}; the method must be inenabled_import_methods(defaultglance-direct,web-download,copy-image,glance/common/config.py:611). - Preconditions (409 otherwise):
glance-directneeds a staged image (uploading);web-downloadandglance-downloadneedqueued;copy-imageneedsactiveand at least one target store where the image is not yet (400 "Image is already present at store" otherwise);disk_formatandcontainer_formatset; no valid import lock held by another task; quotaimage_size_total(glance/api/v2/images.py:327-357). - Business rules:
web-downloadURIs go through[import_filtering_opts](400 when refused);glance-downloadneedsglance_regionand a UUIDglance_image_id(400);all_stores_must_succeed = falseneedsenabled_backends(400);stores/all_storesselect the targets, unknown stores → 409. - Flow:
- (synchronous) checks above; if the image is staged on another worker, the request is proxied there and its answer returned (
glance/api/v2/images.py:441-445); - (synchronous) task
api_image_importcreated (pending), import lockos_glance_import_task = <task id>taken atomically (409 if lost), task spawned in the worker's thread pool; - (asynchronous) the flow of 05 § api_image_import.
- (synchronous) checks above; if the image is staged on another worker, the request is proxied there and its answer returned (
- Outcome: 202 with no body. The image is
importing(not forcopy-image), thenactive, or back toqueuedon failure. The client followsGET /v2/images/{id}(status,os_glance_importing_to_stores,os_glance_failed_import) orGET /v2/images/{id}/tasks. - Effects visible elsewhere: staging space used on the worker; outbound HTTP for
web-downloadandglance-download.
POST /v2/images/{image_id}/locations, GET /v2/images/{image_id}/locations
- POST (new add-location API): body
{"url": "…", "validation_data": {"os_hash_algo": …, "os_hash_value": …}}validated by the location schema. Only on aqueuedimage (409 otherwise). Policyadd_image_location:servicerole, or member of the owner project; non-service callers may only addhttpURLs, and only when thehttpstore is configured (403 otherwise,glance/api/v2/images.py:1180-1187). HTTP(S) URLs go through[import_filtering_opts](400). The location is added and hashed asynchronously by alocation_importtask (05). 202, no body. - GET:
fetch_image_location(service role only by default). Returns the list of{"url", "metadata"}without internalid/status(glance/api/v2/images.py:1259-1280). This is the API that Nova and Cinder use instead ofshow_multiple_locations.
DELETE /v2/stores/{store_id}/{image_id}
- Removes the image data from one store when the image has several locations (multi-store only: 404 without
enabled_backends; 409 if the store is not configured on this node or the image is notactive; 403 for the last location; 404 if the image has no location in that store) (glance/api/v2/images.py:747-824). Needsget_image_locationanddelete_image_location(admin). 204.
POST /v2/images/{image_id}/actions/deactivate, …/reactivate
deactivate(active → deactivated) hides the data: downloads by non-admins get 403;reactivaterestoresactive. Both are no-ops when already in the target status, 403 from other statuses (glance/domain/__init__.py:257-281). Policiesdeactivate/reactivate(member of the owner project). 204.
PUT / DELETE /v2/images/{image_id}/tags/{tag_value}
- Add or remove one tag (
modify_image).image_tag_quota→ 413; deleting an absent tag → 404. 204.
GET /v2/images/{image_id}/tasks
- Lists the non-expired tasks of the image (
tasks_get_by_image, which also soft-deletes expired tasks), visible to whoever can see the image; 404 if the image is not visible (glance/api/v2/images.py:599-614).
Image sharing (members)
- Model: only
sharedimages have members (403 otherwise). The owner adds a project (POST {"member": "<project_id>"}→ 200 withstatus: pending); the member project decides (PUT {"status": "accepted" | "rejected" | "pending"}, policymodify_member= member's own project); the owner removes it (DELETE, 204). An accepted shared image appears in the member's default list (glance/api/v2/image_members.py:126-345,glance/policies/image.py). - Limits:
image_member_quota(128) → 413; duplicates → 409. - Visibility trap:
GET …/members/{member_id}answers 404 for both "not a member" and "not allowed" (glance/api/v2/image_members.py:305-312).
Tasks (deprecated API)
POST /v2/tasks({"type": "import", "input": {...}}→ 201),GET /v2/tasks(filterstype,status; sortcreated_at),GET /v2/tasks/{id}. All needtasks_api_access(admin by default) in addition toadd_task/get_tasks/get_task(glance/api/v2/tasks.py:433).DELETE /v2/tasks/{id}is always 405Allow: GET. Tasks disappeartask_time_to_livehours (48) after they end. The API is marked for removal (glance/api/v2/tasks.py:106).
Discovery
GET /v2/info/import:{"import-methods": {"description": …, "type": "array", "value": [...]}}(enabled_import_methods).GET /v2/info/stores: the stores ofenabled_backendsexcept the reservedos_glance_*ones, withid,description,default: "true",read-only: "true"forhttp; 404 "Multi backend is not supported at this site." without multi-store (glance/api/v2/discovery.py:53-93).GET /v2/info/stores/detail: same plustype,weightand per-driverproperties(rbd pool/fsid, file data_dir, cinder volume type, swift container…);stores_info_detail(admin or service) (glance/api/v2/discovery.py:165-195).GET /v2/info/usage: the unified-limits usage and limit of the caller's project forimage_size_total,image_stage_total,image_count_total,image_count_uploading(glance/api/v2/discovery.py:197-201,glance/quota/keystone.py:147).
Cache management (/v2/cache)
Requires image_cache_dir (404 "Caching via API is not supported at this site." otherwise) and admin policies (cache_list, cache_image, cache_delete, cache_clean, cache_prune, list_cached_nodes). The cache is per glance-api node: each call acts on the node that receives it.
| Operation | Effect | Success |
|---|---|---|
GET /v2/cache | cached and queued images of this node | 200 {"cached_images": [...], "queued_images": [...]} |
PUT /v2/cache/{id} | queue an active image (400 otherwise) and start fetching it in the background | 202 |
DELETE /v2/cache/{id} | remove it from cache and queue | 204 |
DELETE /v2/cache | clear cache and/or queue; header x-image-cache-clear-target: cache | queue | "" | 204 (the counts computed by the handler are not returned) |
POST /v2/cache/clean | remove invalid entries and those stalled longer than image_cache_stall_time | 200 |
POST /v2/cache/prune | shrink the cache under image_cache_max_size (least recently accessed first) | 200 {"total_files_pruned", "total_bytes_pruned"} |
GET /v2/cache/nodes/{id} | worker URLs that have the image cached; centralized_db driver only (409 otherwise) | 200 |
Metadata definitions (/v2/metadefs)
- Purpose: a catalog of documented metadata keys (namespaces → properties, objects, tags; associations to resource types such as
OS::Glance::Image,OS::Nova::Flavor,OS::Cinder::Volume) that UIs use to propose image, flavor or volume properties. It has no effect on images by itself. - Actors: reads for readers of the owner project or for public namespaces (
get_metadef_*); all writes are admin-only by default (metadef_admin) (glance/policies/metadef.py). - Rules: namespace names are unique (409); a
protectednamespace cannot be deleted, nor its children (403);privatenamespaces of other projects look missing (404); deleting a namespace deletes its objects, properties, tags and associations; seeded system resource types cannot be deleted (403).POST /v2/metadefs/namespacesmay embedproperties,objects,tagsandresource_type_associations, created one by one: if a child fails with 409 or 403 the namespace is deleted again, but not on a 400 (glance/api/v2/metadef_namespaces.py:132-230). - Statuses: create 201 (namespace
Locationheader), update 200, delete 204, list/show 200;POST …/tags(bulk) 201, with headerX-Openstack-Append: trueto append instead of replacing (glance/api/v2/metadef_tags.py:117). - Seeding:
glance-manage db load_metadefsloadsetc/metadefs/*.json(glance/cmd/manage.py:309).
Schemas
GET /v2/schemas/{image,images,member,members,task,tasks,metadefs/*} return the JSON schemas used to validate the bodies (glance/api/v2/schemas.py:58-105). The image schema includes the configured disk_format/container_format enums and the custom properties of schema-image.json when present. No policy. A body sent to these routes → 400.
Lifecycle: image
stateDiagram-v2 [*] --> queued: POST /v2/images queued --> saving: PUT …/file (upload starts) saving --> active: upload done saving --> queued: upload failed (restore) saving --> killed: legacy failure path queued --> uploading: PUT …/stage uploading --> importing: POST …/import (glance-direct) uploading --> queued: stage failed queued --> importing: POST …/import (web-download, glance-download) importing --> active: import task, last store written importing --> queued: import task failed (revert) queued --> active: PATCH locations / location_import task active --> deactivated: POST …/actions/deactivate deactivated --> active: POST …/actions/reactivate active --> pending_delete: DELETE with delayed_delete pending_delete --> deleted: glance-scrubber pending_delete --> active: glance-scrubber --restore queued --> deleted: DELETE active --> deleted: DELETE deactivated --> deleted: DELETE killed --> deleted: DELETE deleted --> [*]
Transitions allowed by the domain model: glance/domain/__init__.py:99-115. copy-image never changes the status of the (active) image.
Lifecycle: task
stateDiagram-v2 [*] --> pending: import / add-location / POST /v2/tasks pending --> processing: executor starts the flow pending --> failure: setup error, or lock preempted processing --> success: flow completed processing --> failure: flow reverted, or lock preempted after 60 min success --> [*]: soft-deleted after task_time_to_live (48 h) failure --> [*]: soft-deleted after task_time_to_live
Allowed transitions: glance/domain/__init__.py:402-412.
Lifecycle: image member
stateDiagram-v2 [*] --> pending: POST …/members (owner) pending --> accepted: PUT status (member) pending --> rejected: PUT status (member) accepted --> rejected: PUT status (member) rejected --> accepted: PUT status (member) accepted --> pending: PUT status (member) pending --> [*]: DELETE (owner) accepted --> [*]: DELETE (owner) rejected --> [*]: DELETE (owner)
Source: reverse-spec-glance/01-rest-api_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.