Error codes and messages — placement — branch stable/2026.2
Branch
stable/2026.2@f4a89d3803cb— repository https://opendev.org/openstack/placement — analysed on 2026-10-28
Exception → HTTP translation
Placement has no central translation table: its exceptions (placement/exception.py) carry no HTTP status. Each handler catches business exceptions and raises a webob.exc.HTTP* itself, with a structured code passed in comment=errors.XXX. Three safety nets complete this mechanism:
| Layer | Behaviour | Where |
|---|---|---|
PlacementHandler | any uncaught exception.NotFound yields 404; PolicyNotAuthorized yields 403 | placement/handler.py:219-229 |
PlacementWsgify | takes the comment of the HTTP exception and stores it in environ['placement.error_code'] | placement/wsgi_wrapper.py |
MicroversionMiddleware | formats HTTP errors as JSON and adds the microversion header | placement/deploy.py:58-60 |
FaultWrapper | any other exception yields 500, with LOG.exception('Placement API unexpected error: …') | placement/fault_wrap.py |
The inventory's http_conversions section lists the 68 local conversions found in the handlers (_inventory/inventory_stable-2026.2.md).
Body format (placement/util.py, json_error_formatter):
{"errors": [{"status": 409,
"title": "Conflict",
"detail": "There was a conflict when trying to complete your request.\n\n resource provider generation conflict ",
"code": "placement.concurrent_update",
"request_id": "req-6f0…"}]}
codeis only present from microversion 1.23. It isplacement.undefined_codewhen the handler supplied none (placement/util.py:30-32,placement/errors.py:42).- For a 406 caused by an invalid microversion, the body carries
min_versionandmax_version.
Structured error codes (placement/errors.py:42-53)
| Code | Meaning | Emitted by |
|---|---|---|
placement.undefined_code | error with no specific code | default value |
placement.concurrent_update | stale provider or consumer generation, or concurrent write | inventories, allocations, traits, aggregates, reshaper |
placement.inventory.inuse | deletion of an inventory class that has allocations | PUT/DELETE …/inventories, reshaper |
placement.duplicate_name | provider name or uuid already taken | POST/PUT /resource_providers |
placement.resource_provider.inuse | deletion of a provider that has allocations | DELETE /resource_providers/{uuid} |
placement.resource_provider.cannot_delete_parent | deletion of a provider that has children | same |
placement.resource_provider.not_found | unknown provider in a reshaper request | POST /reshaper |
placement.query.duplicate_key | query parameter repeated although it must be unique | GET /allocation_candidates, GET /resource_providers |
placement.query.bad_value / placement.query.missing_value | malformed or empty query parameter | same |
Catalogue by situation
400 Bad Request
| Message (excerpt) | Situation | Where |
|---|---|---|
| "Malformed JSON: …" / "JSON does not validate: …" | body that is not JSON, or does not match the schema of the requested microversion: a missing field such as consumer_generation from 1.28 or consumer_type from 1.38, an invalid type, an unknown field | placement/util.py:69-84 |
| "Invalid query string parameters: …" | query string outside the microversion's schema | placement/util.py:210-217 |
| "content-type header required when content-length > 0" / "content-length header must be an integer" | inconsistent headers | placement/handler.py:209-218 |
"Unable to create resource provider "…": parent provider UUID does not exist / cannot be same as UUID / name is required" (ObjectActionError) | provider creation with an invalid parent | placement/handlers/resource_provider.py:129-133; placement/objects/resource_provider.py:563-572,690-708 |
| "Unable to save resource provider …: re-parenting a provider is not currently allowed." / "un-parenting …" / "creating loop in the provider tree is not allowed." | parent change before 1.37, or one that would create a loop | placement/objects/resource_provider.py:780-876; placement/handlers/resource_provider.py:310-313 |
| "Invalid resource class in resources parameter" / "Invalid trait(s) in "required" parameter" | GET /resource_providers or /allocation_candidates filter citing a class or trait that does not exist | placement/handlers/resource_provider.py:253-261; placement/handlers/allocation_candidate.py:286-294 |
| "Bad inventory …" / "Unable to create inventory …: Invalid inventory … The reserved value is greater than or equal to total." | invalid inventory values, or reserved ≥ total (before 1.26) or reserved > total (from 1.26) | placement/handlers/inventory.py:80-94,152-176,208-213 |
| "Unknown resource class in inventory …" | PUT …/inventories with a class that does not exist | placement/handlers/inventory.py:347-352 |
| "No inventory record with resource class …" | PUT …/inventories/{rc} on a class absent from the inventory | placement/handlers/inventory.py:451-456 |
| "Allocation for resource provider '…' that does not exist." | PUT/POST /allocations to an unknown provider. This is 400, not 404 | placement/handlers/allocation.py:329-352 |
"Unable to allocate inventory for consumer …: …" (NotFound) | unknown resource class in an allocation | placement/handlers/allocation.py:479-483,586-588 |
| "Malformed consumer_uuid" | malformed consumer UUID in the URL | placement/handlers/allocation.py:395-398 |
| "The "group_policy" parameter is required when specifying more than one "resources{N}" parameter." | several granular groups without group_policy | placement/handlers/allocation_candidate.py:274-281 |
messages from placement/lib.py (orphan group, empty suffix, invalid same_subtree, trait both forbidden AND required…) | inconsistent granular request syntax | placement/lib.py:153-232,420-455 |
| "Cannot delete standard trait / resource class …"; "Cannot update standard resource class …" | change to an item that comes from os-traits or os-resource-classes | placement/handlers/trait.py:139-140; placement/handlers/resource_class.py:101-104,193-196 |
| "The trait is invalid. A valid trait must be no longer than 255 characters, start with the prefix "CUSTOM_" …" | PUT /traits/{name} with a non-custom name | placement/handlers/trait.py:72-79 |
| "No such trait CUSTOM_X, …" | PUT /resource_providers/{uuid}/traits with a trait that does not exist | placement/handlers/trait.py:237-242 |
"Resource provider … in inventories not found" (placement.resource_provider.not_found) | reshaper citing an unknown provider | placement/handlers/reshaper.py:64-72 |
| "malformed reshaper data" | reshaper with an unknown class or consumer | placement/handlers/reshaper.py:133-136 |
401, 403, 404, 405, 406, 415
| Status | Situation | Where |
|---|---|---|
| 401 | missing or invalid token (keystonemiddleware); or a context without X_USER_ID | placement/auth.py:75-90,93-116 |
| 403 "Policy does not allow placement:… to be performed." | insufficient role. Everything requires admin or service, except GET /usages (project reader) and POST /reshaper (service only). With oslo.policy ≥ 6.0.0, a token of the wrong scope is always refused, as enforce_scope can no longer be turned off (see change history, change 6) | placement/handler.py:226-229; placement/policies/*.py |
| 404 | resource not found: provider, class, trait, inventory of a class, or consumer with no allocation for DELETE; or existing route but microversion too low (for example GET /traits at 1.5) | handlers; placement/microversion.py:130-170 |
| 405 | method not declared on the path (with the Allow header); DELETE …/inventories before 1.5 | placement/handler.py:149-174; placement/handlers/inventory.py:378 |
| 406 | Accept other than JSON; requested microversion outside 1.0–1.39 | placement/util.py (check_accept); microversion-parse |
| 415 | Content-Type other than application/json on a write | placement/util.py (require_content) |
409 Conflict
| Message (excerpt) | Code | Situation | Where |
|---|---|---|---|
| "resource provider generation conflict" / "Resource provider's generation already changed. Please update the generation and try again." | concurrent_update | the client sends a resource_provider_generation different from the current one: inventories, traits, aggregates (≥ 1.19), reshaper | placement/handlers/inventory.py:333-336,434-437; placement/handlers/trait.py:229-233; placement/handlers/aggregate.py:118-125; placement/handlers/reshaper.py:75-83 |
| "Update conflict: … Another thread concurrently updated the resource provider data." | concurrent_update | the generation changed between read and write, server side (UPDATE … WHERE generation = n modified no row) | placement/objects/resource_provider.py:669-688 |
| "consumer generation conflict - expected X but got Y" / "expected null but got …" | concurrent_update | allocations from 1.28: stale consumer generation, or non-null generation for a consumer that does not exist yet. No server-side retry | placement/handlers/util.py:139-163 |
| "Inventory and/or allocations changed while attempting to allocate" | concurrent_update | provider generation conflict during an allocation, after allocation_conflict_retry_count retries (10). Log: WARNING Exceeded retry limit of 10 on allocations write | placement/handlers/allocation.py:487-491,595-599; placement/objects/allocation.py:592-596 |
| "Unable to allocate inventory: Unable to create allocation for 'VCPU' on resource provider '…'. The requested amount would exceed the capacity." | (none) | not enough capacity. Log: WARNING Over capacity for VCPU on resource provider … Needed: …, Used: …, Capacity: … | placement/handlers/allocation.py:484-486,592-594; placement/objects/allocation.py:272-291 |
| "… The requested amount would violate inventory constraints." | (none) | amount outside min_unit, max_unit or step_size | placement/objects/allocation.py:229-251 |
| "Inventory for '…' on resource provider '…' invalid." | (none) | allocation on a provider that has no inventory of that class | placement/objects/allocation.py:198-227 |
| "update conflict: Inventory for '…' on resource provider '…' in use." | inventory.inuse | PUT …/inventories that omits an allocated class, or DELETE …/inventories | placement/handlers/inventory.py:364-367,401-404 |
| same | **concurrent_update** (inconsistent) | DELETE …/inventories/{rc} on an allocated class | placement/handlers/inventory.py:240-245 |
| "Update conflict: …" (duplicate) | concurrent_update | POST …/inventories of a class that is already present | placement/handlers/inventory.py:203-207 |
| "Race condition detected when setting inventory. …" | (none) | inventory deleted concurrently during a PUT …/inventories | placement/handlers/inventory.py:353-358 |
| "Conflicting resource provider name/uuid: … already exists." | duplicate_name | creation or renaming of a provider into a duplicate | placement/handlers/resource_provider.py:104-128,305-309 |
| "Unable to delete resource provider …: Resource provider has allocations." | resource_provider.inuse | deletion of an allocated provider | placement/handlers/resource_provider.py:164-168 |
| "Unable to delete parent resource provider …: It has child resource providers." | resource_provider.cannot_delete_parent | deletion of a parent provider | placement/handlers/resource_provider.py:172-176 |
| "Conflicting resource class already exists"; "Max retries of DB transaction exceeded attempting to perform …" | (none) | duplicate custom class; ID allocation retries exhausted | placement/handlers/resource_class.py:70-80,189-192 |
| "Cannot delete resource class …. Class is in use in inventory." | (none) | deletion of a class in use | placement/handlers/resource_class.py:105-107 |
| "The trait … is in use by a resource provider." | (none) | deletion of an attached trait | placement/handlers/trait.py:141-142 |
| "Update conflict" (duplicate aggregate) | (none) | concurrent aggregate creation beyond 10 retries | placement/handlers/aggregate.py:76-78 |
500 Internal Server Error
| Situation | Where |
|---|---|
any unexpected exception: unreachable database (DBConnectionError after [placement_database]/max_retries = 10 attempts spaced by retry_interval = 10 s), bug, or KeyError("… already in usage_map, bad query") | placement/fault_wrap.py; placement/conf/database.py:87-94; placement/objects/allocation.py:193-195 |
Errors outside requests
Placement has no asynchronous processing, so no deferred error. The only errors outside an HTTP request happen:
- at startup: unreachable database or schema behind.
sync_on_startupis False by default, soplacement-manage db syncmust be run (placement/deploy.py:122-130). A failure while syncing the standard traits or classes prevents the application from loading [inferred]; - in the CLI:
placement-manage db online_data_migrationsreturns 1 or 2 when batches fail (placement/cmd/manage.py:70-131);placement-status upgrade checkprints warnings (placement/cmd/status.py:38-109).
Key log messages
| Level | Message | Situation | Where |
|---|---|---|---|
| WARNING | Resource provider %(uuid)s is now over-capacity for %(resource)s | inventory reduced below current usage | placement/objects/resource_provider.py:628-641 |
| WARNING | Over capacity for %(rc)s on resource provider %(rp)s. Needed: …, Used: …, Capacity: … | allocation refused for lack of capacity | placement/objects/allocation.py:285-291 |
| WARNING | Over capacity … but allocation of … reduces existing overage from … to … | allocation accepted on a provider already over capacity | placement/objects/allocation.py:278-284 |
| WARNING | Allocation for … violates min_unit, max_unit, or step_size. … | unit constraints | placement/objects/allocation.py:236-246 |
| WARNING | Exceeded retry limit of %d on allocations write | heavy contention on one provider | placement/objects/allocation.py:594-595 |
| DEBUG | Retrying allocations write on resource provider generation conflict | internal allocation retry | placement/objects/allocation.py:566-567 |
| WARNING | Got an exception when deleting auto-created consumer with UUID … | cleanup after a failed allocation | placement/handlers/allocation.py:386-388 |
| WARNING | Exceeded retry limit on ID generation while … | custom class creation under contention | placement/objects/resource_class.py:137 |
| ERROR (exception) | Placement API unexpected error: … | any 500 | placement/fault_wrap.py |
Source: reverse-spec-placement/02-errors_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.