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

Reverse-spec · Services

glance — REST API

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.Mapper built at start-up by glance/api/v2/router.py:37, dispatched to wsgi.Resource objects (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.18 are aliases of the same API: the version negotiation filter strips the version segment (or reads Accept: 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.13 only with enabled_backends, v2.14, v2.16, v2.18 only with image_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 lists v2.18 as CURRENT when the cache is enabled, v2.17 otherwise (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 → optional cache and cachemanage → router. The default, caching and cachemanagement flavours replace authtoken context with unauthenticated-context, which makes every request admin (glance/api/middleware/context.py:160-170): a test-only flavour that must never be deployed.
  • Identity: ContextMiddleware builds the request context from the headers that keystonemiddleware sets when X-Identity-Status: Confirmed; roles are lower-cased; X-Service-Catalog is parsed for later outbound calls. Without a confirmed identity, the request is refused with 401 unless allow_anonymous_access (default false) gives a read-only anonymous context (glance/api/middleware/context.py:107-112). Every response carries x-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 through glance/api/v2/policy.py. With the default enforce_new_defaults = true they are the secure-RBAC rules shown below (admin, member, reader roles, 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.

MethodPathHandlerPolicySuccessErrors
GET/v2/schemas/imageglance/api/v2/schemas.py:58 Controller.image—200—
GET/v2/schemas/imagesglance/api/v2/schemas.py:61 Controller.images—200—
GET/v2/schemas/memberglance/api/v2/schemas.py:64 Controller.member—200—
GET/v2/schemas/membersglance/api/v2/schemas.py:67 Controller.members—200—
GET/v2/schemas/taskglance/api/v2/schemas.py:70 Controller.task—200—
GET/v2/schemas/tasksglance/api/v2/schemas.py:73 Controller.tasks—200—
GET/v2/schemas/metadefs/namespaceglance/api/v2/schemas.py:76 Controller.metadef_namespace—200—
GET/v2/schemas/metadefs/namespacesglance/api/v2/schemas.py:79 Controller.metadef_namespaces—200—
GET/v2/schemas/metadefs/resource_typeglance/api/v2/schemas.py:82 Controller.metadef_resource_type—200—
GET/v2/schemas/metadefs/resource_typesglance/api/v2/schemas.py:85 Controller.metadef_resource_types—200—
GET/v2/schemas/metadefs/propertyglance/api/v2/schemas.py:88 Controller.metadef_property—200—
GET/v2/schemas/metadefs/propertiesglance/api/v2/schemas.py:91 Controller.metadef_properties—200—
GET/v2/schemas/metadefs/objectglance/api/v2/schemas.py:94 Controller.metadef_object—200—
GET/v2/schemas/metadefs/objectsglance/api/v2/schemas.py:97 Controller.metadef_objects—200—
GET/v2/schemas/metadefs/tagglance/api/v2/schemas.py:100 Controller.metadef_tag—200—
GET/v2/schemas/metadefs/tagsglance/api/v2/schemas.py:103 Controller.metadef_tags—200—
GET/v2/metadefs/resource_typesglance/api/v2/metadef_resource_types.py:49 ResourceTypeController.indexlist_metadef_resource_types200403, 404
GET/v2/metadefs/namespaces/{namespace}/resource_typesglance/api/v2/metadef_resource_types.py:77 ResourceTypeController.showget_metadef_resource_type200403, 404
POST/v2/metadefs/namespaces/{namespace}/resource_typesglance/api/v2/metadef_resource_types.py:120 ResourceTypeController.createadd_metadef_resource_type_association201400, 403, 404, 409
DELETE/v2/metadefs/namespaces/{namespace}/resource_types/{resource_type}glance/api/v2/metadef_resource_types.py:158 ResourceTypeController.deleteremove_metadef_resource_type_association204403, 404
GET/v2/metadefs/namespacesglance/api/v2/metadef_namespaces.py:60 NamespaceController.indexget_metadef_namespaces200403, 404
POST/v2/metadefs/namespacesglance/api/v2/metadef_namespaces.py:131 NamespaceController.createadd_metadef_namespace201400, 403, 404, 409
GET/v2/metadefs/namespaces/{namespace}glance/api/v2/metadef_namespaces.py:261 NamespaceController.showget_metadef_namespace200403, 404
PUT/v2/metadefs/namespaces/{namespace}glance/api/v2/metadef_namespaces.py:342 NamespaceController.updatemodify_metadef_namespace200400, 403, 404, 409
DELETE/v2/metadefs/namespaces/{namespace}glance/api/v2/metadef_namespaces.py:389 NamespaceController.deletedelete_metadef_namespace204403, 404
GET/v2/metadefs/namespaces/{namespace}/propertiesglance/api/v2/metadef_properties.py:64 NamespacePropertiesController.indexget_metadef_properties200403, 404
POST/v2/metadefs/namespaces/{namespace}/propertiesglance/api/v2/metadef_properties.py:148 NamespacePropertiesController.createadd_metadef_property201400, 403, 404, 409
DELETE/v2/metadefs/namespaces/{namespace}/propertiesglance/api/v2/metadef_namespaces.py:475 NamespaceController.delete_properties—204403, 404
GET/v2/metadefs/namespaces/{namespace}/properties/{property_name}glance/api/v2/metadef_properties.py:98 NamespacePropertiesController.showget_metadef_property200403, 404
PUT/v2/metadefs/namespaces/{namespace}/properties/{property_name}glance/api/v2/metadef_properties.py:185 NamespacePropertiesController.updatemodify_metadef_property200400, 403, 404, 409
DELETE/v2/metadefs/namespaces/{namespace}/properties/{property_name}glance/api/v2/metadef_properties.py:223 NamespacePropertiesController.deleteremove_metadef_property204403, 404
GET/v2/metadefs/namespaces/{namespace}/objectsglance/api/v2/metadef_objects.py:94 MetadefObjectsController.indexget_metadef_objects200403, 404
POST/v2/metadefs/namespaces/{namespace}/objectsglance/api/v2/metadef_objects.py:50 MetadefObjectsController.createadd_metadef_object201400, 403, 404, 409
DELETE/v2/metadefs/namespaces/{namespace}/objectsglance/api/v2/metadef_namespaces.py:415 NamespaceController.delete_objects—204403, 404
GET/v2/metadefs/namespaces/{namespace}/objects/{object_name}glance/api/v2/metadef_objects.py:141 MetadefObjectsController.showget_metadef_object200403, 404
PUT/v2/metadefs/namespaces/{namespace}/objects/{object_name}glance/api/v2/metadef_objects.py:174 MetadefObjectsController.updatemodify_metadef_object200400, 403, 404, 409
DELETE/v2/metadefs/namespaces/{namespace}/objects/{object_name}glance/api/v2/metadef_objects.py:223 MetadefObjectsController.deletedelete_metadef_object204403, 404
GET/v2/metadefs/namespaces/{namespace}/tagsglance/api/v2/metadef_tags.py:139 TagsController.indexget_metadef_tags200403, 404
POST/v2/metadefs/namespaces/{namespace}/tagsglance/api/v2/metadef_tags.py:95 TagsController.create_tagsadd_metadef_tags201400, 403, 404, 409
DELETE/v2/metadefs/namespaces/{namespace}/tagsglance/api/v2/metadef_namespaces.py:443 NamespaceController.delete_tagsdelete_metadef_tags204403, 404
GET/v2/metadefs/namespaces/{namespace}/tags/{tag_name}glance/api/v2/metadef_tags.py:185 TagsController.showget_metadef_tag200403, 404
POST/v2/metadefs/namespaces/{namespace}/tags/{tag_name}glance/api/v2/metadef_tags.py:51 TagsController.createadd_metadef_tag201400, 403, 404, 409
PUT/v2/metadefs/namespaces/{namespace}/tags/{tag_name}glance/api/v2/metadef_tags.py:214 TagsController.updatemodify_metadef_tag200400, 403, 404, 409
DELETE/v2/metadefs/namespaces/{namespace}/tags/{tag_name}glance/api/v2/metadef_tags.py:253 TagsController.deletedelete_metadef_tag204403, 404
GET/v2/imagesglance/api/v2/images.py:517 ImagesController.indexget_images200400, 401, 403
POST/v2/imagesglance/api/v2/images.py:93 ImagesController.createadd_image201400, 401, 403, 409, 413, 503
PATCH/v2/images/{image_id}glance/api/v2/images.py:616 ImagesController.updatemodify_image, publicize_image, communitize_image, delete_image_location, set_image_location200400, 401, 403, 404, 409, 413, 415, 503
GET/v2/images/{image_id}glance/api/v2/images.py:587 ImagesController.showget_image, get_image_location200401, 404
DELETE/v2/images/{image_id}glance/api/v2/images.py:865 ImagesController.deletedelete_image204400, 401, 403, 404, 409, 503
GET/v2/images/{image_id}/tasksglance/api/v2/images.py:599 ImagesController.get_task_info—200404
POST/v2/images/{image_id}/importglance/api/v2/images.py:316 ImagesController.import_imagecopy_image202400, 403, 404, 409, 413, 502, 504
DELETE/v2/stores/{store_id}/{image_id}glance/api/v2/images.py:746 ImagesController.delete_from_store—204401, 403, 404, 409, 500, 503
POST/v2/images/{image_id}/actions/deactivateglance/api/v2/image_actions.py:46 ImageActionsController.deactivatedeactivate204400, 403, 404
POST/v2/images/{image_id}/actions/reactivateglance/api/v2/image_actions.py:75 ImageActionsController.reactivatereactivate204400, 403, 404
GET/v2/images/{image_id}/fileglance/api/v2/image_data.py:479 ImageDataController.downloaddownload_image, download_from_store200, 206 (Range)204 (no data), 400, 403, 404, 416, 503
PUT/v2/images/{image_id}/fileglance/api/v2/image_data.py:107 ImageDataController.uploadupload_image204400, 401, 403, 404, 409, 410, 413, 415, 503
PUT/v2/images/{image_id}/stageglance/api/v2/image_data.py:329 ImageDataController.stage—204400, 403, 404, 409, 413, 415, 503
POST/v2/images/{image_id}/locationsglance/api/v2/images.py:1161 ImagesController.add_locationadd_image_location202400, 401, 403, 404, 409
GET/v2/images/{image_id}/locationsglance/api/v2/images.py:1259 ImagesController.get_locationsfetch_image_location200403, 404
PUT/v2/images/{image_id}/tags/{tag_value}glance/api/v2/image_tags.py:46 Controller.update—204400, 403, 404, 413
DELETE/v2/images/{image_id}/tags/{tag_value}glance/api/v2/image_tags.py:74 Controller.delete—204403, 404
GET/v2/images/{image_id}/membersglance/api/v2/image_members.py:234 ImageMembersController.indexget_members200403, 404
POST/v2/images/{image_id}/membersglance/api/v2/image_members.py:125 ImageMembersController.createadd_member200400, 403, 404, 409, 413
GET/v2/images/{image_id}/members/{member_id}glance/api/v2/image_members.py:282 ImageMembersController.showget_member200404
PUT/v2/images/{image_id}/members/{member_id}glance/api/v2/image_members.py:185 ImageMembersController.updatemodify_member200400, 403, 404
DELETE/v2/images/{image_id}/members/{member_id}glance/api/v2/image_members.py:314 ImageMembersController.deletedelete_member204403, 404
POST/v2/tasksglance/api/v2/tasks.py:69 TasksController.createadd_task, tasks_api_access201400, 403
GET/v2/tasksglance/api/v2/tasks.py:106 TasksController.indexget_tasks, tasks_api_access200400, 403
GET/v2/tasks/{task_id}glance/api/v2/tasks.py:136 TasksController.getget_task, tasks_api_access200403, 404
DELETE/v2/tasks/{task_id}glance/api/v2/tasks.py:157 TasksController.deletemodify_task, tasks_api_access405 (always)403, 405
GET/v2/info/importglance/api/v2/discovery.py:41 InfoController.get_image_import—200—
GET/v2/info/storesglance/api/v2/discovery.py:53 InfoController.get_stores—200404 (no multi-store)
GET/v2/info/usageglance/api/v2/discovery.py:197 InfoController.get_usage—200—
GET/v2/info/stores/detailglance/api/v2/discovery.py:165 InfoController.get_stores_detailstores_info_detail200403, 404
GET/v2/cacheglance/api/v2/cached_images.py:242 CacheController.get_cache_statecache_list200403, 404 (cache disabled)
DELETE/v2/cacheglance/api/v2/cached_images.py:219 CacheController.clear_cachecache_delete204400, 403, 404
DELETE/v2/cache/{image_id}glance/api/v2/cached_images.py:156 CacheController.delete_cache_entrycache_delete204403, 404
PUT/v2/cache/{image_id}glance/api/v2/cached_images.py:252 CacheController.queue_image_from_apicache_image202400, 403, 404
GET/v2/cache/nodes/{image_id}glance/api/v2/cached_images.py:189 CacheController.get_cached_nodeslist_cached_nodes200403, 404, 409
POST/v2/cache/cleanglance/api/v2/cached_images.py:278 CacheController.clean_cachecache_clean200403, 404
POST/v2/cache/pruneglance/api/v2/cached_images.py:289 CacheController.prune_cachecache_prune200403, 404
GET/glance/api/versions.py:61 Controller.index— (public)300—
GET/versionsglance/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).

MethodPathHandlerSuccess
GET/v2/cached_imagesglance/api/v2/cached_images.py:89 CacheController.get_cached_images200 {"cached_images": [...]}
DELETE/v2/cached_imagesglance/api/v2/cached_images.py:108 CacheController.delete_cached_images200 {"num_deleted": n}
DELETE/v2/cached_images/{image_id}glance/api/v2/cached_images.py:99 CacheController.delete_cached_image200
GET/v2/queued_imagesglance/api/v2/cached_images.py:117 CacheController.get_queued_images200 {"queued_images": [...]}
PUT/v2/queued_images/{image_id}glance/api/v2/cached_images.py:127 CacheController.queue_image200
DELETE/v2/queued_imagesglance/api/v2/cached_images.py:147 CacheController.delete_queued_images200 {"num_deleted": n}
DELETE/v2/queued_images/{image_id}glance/api/v2/cached_images.py:138 CacheController.delete_queued_image200

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}/objects and DELETE /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, or member of the project whose id equals the requested owner, glance/policies/image.py:25). Making it public needs publicize_image (admin); community needs communitize_image.
  • Preconditions: unified-limits quota image_count_total not 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 starts queued;
    • the body is validated against /v2/schemas/image (glance/api/v2/images.py:1331), whose disk_format and container_format enums come from [image_format] (glance/api/v2/images.py:1955-1964);
    • read-only attributes (id aside) and direct_url, self, file, schema, stores are refused with 403, as are location, deleted, deleted_at and any os_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) and image_tag_quota (128) apply;
    • a client-supplied id must be a UUID and unique (409 on duplicate).
  • Flow: synchronous only: policy check, quota, image.create notification, INSERT images (+ properties, tags).
  • Outcome: 201 with the image view, Location: /v2/images/{id}, and OpenStack-image-import-methods (and OpenStack-image-store-ids with multi-store) headers (glance/api/v2/images.py:1829-1843).
  • Limitations: locations cannot 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 with get_image (glance/api/v2/images.py:554-566).
  • Visibility rule: public and community images, the caller's own images, and shared images where the caller's project is a member with status member_status (default accepted). Without a visibility filter, other projects' community images are not listed (see 03).
  • Parameters: limit (default limit_param_default = 25, capped by api_limit_max = 1000), marker; sorting with sort_key/sort_dir (repeatable) or sort=key:dir,… (keys: name, status, container_format, disk_format, size, id, created_at, updated_at; default created_at desc); filters name, id, status, container_format, disk_format (with in: and eq: 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 (default false: 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>"}. next is 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 internal os_glance_stage_host and os_glance_hash_op_host), tags, self, file, schema; locations only with show_multiple_locations, direct_url only with show_image_direct_url, stores with 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 subset add, remove, replace on top-level paths, and /locations/N or /locations/-) or the older …-v2.0-json-patch (draft 4: add fails with 409 when the property already exists) (glance/api/v2/images.py:1493-1550, glance/api/v2/images.py:680-698).
    • Rules: modify_image for every change; changing visibility checks publicize_image/communitize_image; owner can only be changed by an admin (glance/api/v2/images.py:667-669); base attributes cannot be removed (403); disk_format and container_format can only change while the image is queued or importing (glance/domain/__init__.py:202-221); replace of an unknown property → 409.
    • Locations (only with show_multiple_locations = true, otherwise 403): adding or replacing locations on a queued image with consistent validation_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 (400 BadStoreUri).
    • Outcome: 200 with the full image view; image.update notification.

DELETE /v2/images/{image_id}

  • Actor: delete_image. Protected images → 403.
  • Flow:
    1. 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);
    2. a running location hash calculation is canceled (glance/api/v2/images.py:890-894);
    3. local staging residue is deleted (glance/api/v2/images.py:896-931);
    4. status deleted, or pending_delete with delayed_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);
    5. 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);
    6. image.delete notification, soft delete of the rows.
  • 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_format and container_format set; content type application/octet-stream; quotas image_size_total (with the Content-Length as delta) and then image_count_uploading.
  • Flow (all before the response):
    1. queued → saving with compare-and-swap (glance/api/v2/image_data.py:161);
    2. the data is streamed to the store chosen by X-Image-Meta-Store (multi-store) or the default store; md5 checksum, os_hash_value (hashing_algorithm, default sha512) and size are computed on the stream; for container_format = bare, the format is inspected on the fly and virtual_size computed (glance/location.py:574-700); image_size_cap and the legacy user_storage_quota cut the stream;
    3. signature verification if the img_signature* properties are set;
    4. saving → active with 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; notifications image.prepare, image.upload, image.activate.
  • Failures: on most errors the image goes back to queued so 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-direct import: store the data in the staging area of this worker.
  • Preconditions: glance-direct in enabled_import_methods (else 404), image queued, quota image_stage_total.
  • Flow: status uploading, data written to node_staging_uri/<id> or the reserved staging store, os_glance_stage_host = worker_self_reference_url recorded 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 to queued.

GET /v2/images/{image_id}/file (download)

  • Actor: download_image (owner or member); deactivated images can only be downloaded by admins (403 otherwise).
  • Rules: prefer=store1,store2 reorders the locations (needs download_from_store and multi-store); locations are tried in order until one works (glance/location.py:702-735); Range / Content-Range give 206 with Content-Range, or 416 when unsatisfiable; Content-MD5 is 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.send notification 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_image on the image (owner), except copy-image, which needs copy_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 in enabled_import_methods (default glance-direct, web-download, copy-image, glance/common/config.py:611).
  • Preconditions (409 otherwise): glance-direct needs a staged image (uploading); web-download and glance-download need queued; copy-image needs active and at least one target store where the image is not yet (400 "Image is already present at store" otherwise); disk_format and container_format set; no valid import lock held by another task; quota image_size_total (glance/api/v2/images.py:327-357).
  • Business rules: web-download URIs go through [import_filtering_opts] (400 when refused); glance-download needs glance_region and a UUID glance_image_id (400); all_stores_must_succeed = false needs enabled_backends (400); stores/all_stores select the targets, unknown stores → 409.
  • Flow:
    1. (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);
    2. (synchronous) task api_image_import created (pending), import lock os_glance_import_task = <task id> taken atomically (409 if lost), task spawned in the worker's thread pool;
    3. (asynchronous) the flow of 05 § api_image_import.
  • Outcome: 202 with no body. The image is importing (not for copy-image), then active, or back to queued on failure. The client follows GET /v2/images/{id} (status, os_glance_importing_to_stores, os_glance_failed_import) or GET /v2/images/{id}/tasks.
  • Effects visible elsewhere: staging space used on the worker; outbound HTTP for web-download and glance-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 a queued image (409 otherwise). Policy add_image_location: service role, or member of the owner project; non-service callers may only add http URLs, and only when the http store 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 a location_import task (05). 202, no body.
  • GET: fetch_image_location (service role only by default). Returns the list of {"url", "metadata"} without internal id/status (glance/api/v2/images.py:1259-1280). This is the API that Nova and Cinder use instead of show_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 not active; 403 for the last location; 404 if the image has no location in that store) (glance/api/v2/images.py:747-824). Needs get_image_location and delete_image_location (admin). 204.

POST /v2/images/{image_id}/actions/deactivate, …/reactivate

  • deactivate (active → deactivated) hides the data: downloads by non-admins get 403; reactivate restores active. Both are no-ops when already in the target status, 403 from other statuses (glance/domain/__init__.py:257-281). Policies deactivate/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 shared images have members (403 otherwise). The owner adds a project (POST {"member": "<project_id>"} → 200 with status: pending); the member project decides (PUT {"status": "accepted" | "rejected" | "pending"}, policy modify_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 (filters type, status; sort created_at), GET /v2/tasks/{id}. All need tasks_api_access (admin by default) in addition to add_task/get_tasks/get_task (glance/api/v2/tasks.py:433). DELETE /v2/tasks/{id} is always 405 Allow: GET. Tasks disappear task_time_to_live hours (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 of enabled_backends except the reserved os_glance_* ones, with id, description, default: "true", read-only: "true" for http; 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 plus type, weight and per-driver properties (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 for image_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.

OperationEffectSuccess
GET /v2/cachecached and queued images of this node200 {"cached_images": [...], "queued_images": [...]}
PUT /v2/cache/{id}queue an active image (400 otherwise) and start fetching it in the background202
DELETE /v2/cache/{id}remove it from cache and queue204
DELETE /v2/cacheclear 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/cleanremove invalid entries and those stalled longer than image_cache_stall_time200
POST /v2/cache/pruneshrink 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 protected namespace cannot be deleted, nor its children (403); private namespaces 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/namespaces may embed properties, objects, tags and resource_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 Location header), update 200, delete 204, list/show 200; POST …/tags (bulk) 201, with header X-Openstack-Append: true to append instead of replacing (glance/api/v2/metadef_tags.py:117).
  • Seeding: glance-manage db load_metadefs loads etc/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.