Error codes and messages — CloudKitty — branch stable/2026.2
Branch
stable/2026.2@0685cc330e83— repository https://opendev.org/openstack/cloudkitty — analysed on 2026-11-09
CloudKitty has no structured error code: errors are an HTTP status plus a free-text message, and the body format depends on the API version. Processor errors never reach a client: they appear in logs and, at most, as a scope that stops advancing.
1. Exception → HTTP translation
1.1 v1 (Pecan + WSME)
| Mechanism | Behaviour | Where |
|---|---|---|
pecan.abort(code, msg) in handlers | the explicit way v1 returns 4xx | e.g. cloudkitty/rating/hash/controllers/mapping.py:182-185 |
PolicyNotAuthorized (has code = 403) | raised by policy.authorize; under module_config it is caught and turned into pecan.abort(403, msg) | cloudkitty/common/policy.py:41-50, cloudkitty/rating/__init__.py:139-143 |
| other v1 controllers | PolicyNotAuthorized propagates to WSME, which uses the exception's code → 403 | **[inferred: wsmeext.pecan / wsme.api.format_exception]** |
| WSME type validation | wrong type, bad UUID (UuidType.validate), bad enum, missing mandatory attribute → 400 | cloudkitty/api/v1/types.py:27-36, cloudkitty/rating/hash/datamodels/mapping.py:23-44 **[inferred: WSME InvalidInput]** |
| any other exception | 500 | [inferred] |
Body [inferred: WSME JSON error format]:
{"faultcode": "Client", "faultstring": "No such mapping: 6b1b…", "debuginfo": null}
faultcode is Server for 5xx.
1.2 v2 (Flask + flask-restful)
| Mechanism | Behaviour | Where |
|---|---|---|
werkzeug.exceptions.BadRequest/NotFound/Forbidden raised in handlers | status of the exception | e.g. cloudkitty/api/v2/dataframes/dataframes.py:106-108 |
PolicyNotAuthorized | caught by BaseResource.dispatch_request → 403 You are not authorized to perform this action | cloudkitty/api/v2/base.py:29-34 |
schema validation (voluptuous.Invalid) | 400 Invalid data '<args>' : <msg> (path: '<path>') | cloudkitty/api/v2/utils.py:189-195 |
voluptuous.Invalid raised inside a handler | not caught → 500 | cloudkitty/api/v2/summary/summary.py:54-57 [inferred] |
| any other exception (storage, DB, RPC) | 500 | **[inferred: flask-restful handle_error]** |
Body [inferred: flask-restful]: {"message": "<description>"}.
1.3 Authentication
keystonemiddleware answers 401 before the application for every path except /, /v1, /v2 (cloudkitty/api/middleware.py:29-36, etc/cloudkitty/api_paste.ini:20-22).
2. Catalogue of exceptions
2.1 HashMap (cloudkitty/rating/hash/db/api.py)
All derive from ClientHashMapError (cloudkitty/rating/hash/db/api.py:38), so a handler's except ClientHashMapError → 400 also catches the "not found" ones when no more specific branch precedes it.
| Exception | Message template | Situation | Raised at | Client sees |
|---|---|---|---|---|
ClientHashMapError | free text | You must specify either name or uuid. (service lookup/delete without key) | cloudkitty/rating/hash/db/sqlalchemy/api.py:50-51, cloudkitty/rating/hash/db/sqlalchemy/api.py:596-597 | 400 / 500 depending on handler |
You can only specify one parent. — mapping/threshold with both service_id and field_id | cloudkitty/rating/hash/db/sqlalchemy/api.py:311-312, cloudkitty/rating/hash/db/sqlalchemy/api.py:455-456 | 400 | ||
You must specify one parent. | cloudkitty/rating/hash/db/sqlalchemy/api.py:313-314, cloudkitty/rating/hash/db/sqlalchemy/api.py:457-458 | 400 | ||
You can't specify a value and a service_id. | cloudkitty/rating/hash/db/sqlalchemy/api.py:315-318 | 400 | ||
You must specify a value for a field mapping. (create, or update setting value to empty) | cloudkitty/rating/hash/db/sqlalchemy/api.py:319-322, cloudkitty/rating/hash/db/sqlalchemy/api.py:510-513 | 400 | ||
You must specify either service_uuid, field_uuid or group_uuid. — threshold list without parent | cloudkitty/rating/hash/db/sqlalchemy/api.py:235-238 | 500 (not caught by get_all) [inferred] | ||
No such attribute: <name> / No attribute to update. | cloudkitty/rating/hash/db/sqlalchemy/api.py:525-533, cloudkitty/rating/hash/db/sqlalchemy/api.py:565-573 | 400 | ||
NoSuchService | No such service: %(name)s (UUID: %(uuid)s) | unknown service id/name | cloudkitty/rating/hash/db/sqlalchemy/api.py:54-55, cloudkitty/rating/hash/db/sqlalchemy/api.py:599-600 | 404 on GET/DELETE service; 400 when it is the parent of a new field/mapping/threshold |
NoSuchField | No such field: %s | unknown field id | cloudkitty/rating/hash/db/sqlalchemy/api.py:76-77, cloudkitty/rating/hash/db/sqlalchemy/api.py:609-610 | 404 on GET/DELETE field; 400 as a parent |
NoSuchGroup | No such group: %(name)s (UUID: %(uuid)s) | unknown group id | cloudkitty/rating/hash/db/sqlalchemy/api.py:91-92, cloudkitty/rating/hash/db/sqlalchemy/api.py:620-621 | 404 on GET/DELETE group; 400 when referenced by a mapping/threshold |
NoSuchMapping | No such mapping: %s | unknown or already soft-deleted mapping (update/delete filter deleted IS NULL) | cloudkitty/rating/hash/db/sqlalchemy/api.py:102-103, cloudkitty/rating/hash/db/sqlalchemy/api.py:543-544, cloudkitty/rating/hash/db/sqlalchemy/api.py:639-640 | 404 |
NoSuchThreshold | No such threshold: %s | unknown threshold | cloudkitty/rating/hash/db/sqlalchemy/api.py:125-126, cloudkitty/rating/hash/db/sqlalchemy/api.py:583-584, cloudkitty/rating/hash/db/sqlalchemy/api.py:649-650 | 404 |
NoSuchType | No mapping type: %s | any other DBError while inserting a mapping/threshold (in practice: invalid enum on a DB that enforces it) | cloudkitty/rating/hash/db/sqlalchemy/api.py:440-441, cloudkitty/rating/hash/db/sqlalchemy/api.py:492-493 | 400 |
ServiceAlreadyExists | Service %(name)s already exists (UUID: %(uuid)s) | duplicate service name | cloudkitty/rating/hash/db/sqlalchemy/api.py:260-264 | 409 |
FieldAlreadyExists | Field %(field)s already exists (UUID: %(uuid)s) | duplicate (service, name) | cloudkitty/rating/hash/db/sqlalchemy/api.py:278-280 | 409 |
GroupAlreadyExists | Group %(name)s already exists (UUID: %(uuid)s) | duplicate group name | cloudkitty/rating/hash/db/sqlalchemy/api.py:292-294 | 409 |
MappingAlreadyExists | Mapping '%(mapping)s' already exists for %(p_type)s '%(p_id)s', tenant: '%(t_id)s' | same name, or same value/tenant/parent/group with an overlapping validity window, or a unique-key violation | cloudkitty/rating/hash/db/sqlalchemy/api.py:394-405, cloudkitty/rating/hash/db/sqlalchemy/api.py:428-439, cloudkitty/rating/hash/db/sqlalchemy/api.py:535-542 | 409 |
ThresholdAlreadyExists | Threshold '%(threshold)s' already exists for %(p_type)s '%(p_id)s', tenant: '%(t_id)s' | duplicate (level, parent, tenant) | cloudkitty/rating/hash/db/sqlalchemy/api.py:484-491, cloudkitty/rating/hash/db/sqlalchemy/api.py:575-582 | 409 |
MappingHasNoGroup | Mapping has no group (UUID: %s) | GET …/mappings/group on a mapping without group | cloudkitty/rating/hash/db/sqlalchemy/api.py:138-139 | 404 |
ThresholdHasNoGroup | Threshold has no group (UUID: %s) | idem for thresholds | cloudkitty/rating/hash/db/sqlalchemy/api.py:151-152 | 404 |
Defects worth knowing: on a unique-key violation during update, update_mapping/update_threshold build the message with the loop variable value (cloudkitty/rating/hash/db/sqlalchemy/api.py:535-542, cloudkitty/rating/hash/db/sqlalchemy/api.py:575-582); ThresholdAlreadyExists receives four positional arguments where the class takes threshold, parent_id, parent_type, uuid (cloudkitty/rating/hash/db/api.py:159-176) [inferred: misleading message, not a crash].
2.2 Validity-window validation (hashmap mappings, pyscripts)
Raised with pecan.abort(400, …) (cloudkitty/rating/common/validations/fields.py:40-87):
| Message | Situation |
|---|---|
Cannot create a rule with start in the past. start=[…] current time=[…]. | start < now and no force=true |
Cannot create a rule with start after end. start=[…] end=[…]. | end < start |
You are allowed to update only the attribute [end] as this rule is already running as it started on […] | PUT on a running rule changing anything else than end |
End date must be in the future. end=[…] current time=[…] | PUT setting a past end on a running rule |
Cannot update a rule that was already processed and has a defined end date. | PUT on a rule that started and has an end |
2.3 PyScripts (cloudkitty/rating/pyscripts/db/api.py)
| Exception | Message | Situation | Raised at | Client sees |
|---|---|---|---|---|
NoSuchScript | No such script: %(name)s (UUID: %(uuid)s) | unknown or soft-deleted script | cloudkitty/rating/pyscripts/db/sqlalchemy/api.py:56, cloudkitty/rating/pyscripts/db/sqlalchemy/api.py:129, cloudkitty/rating/pyscripts/db/sqlalchemy/api.py:149 | 404 |
ScriptAlreadyExists | Script %(name)s already exists (UUID: %(uuid)s) | duplicate name | cloudkitty/rating/pyscripts/db/sqlalchemy/api.py:82-84, cloudkitty/rating/pyscripts/db/sqlalchemy/api.py:100-102 | 409 |
ValueError | You must specify either name or uuid. / No such attribute: … / No attribute to update. | internal misuse | cloudkitty/rating/pyscripts/db/sqlalchemy/api.py:49, cloudkitty/rating/pyscripts/db/sqlalchemy/api.py:123-126, cloudkitty/rating/pyscripts/db/sqlalchemy/api.py:140 | 500 [inferred] |
A script that raises or is not valid Python is not detected at upload: compile happens in the processor (cloudkitty/rating/pyscripts/__init__.py:62-70), see § 3.
2.4 v1 core
| Error | Message | Situation | Where |
|---|---|---|---|
| 404 | Module not found. | unknown rating module | cloudkitty/api/v1/controllers/rating.py:102-103, cloudkitty/api/v1/controllers/rating.py:124-125 |
| 409 | Module is not configurable | module_config/<module> for a module without controller (noop) | cloudkitty/api/v1/controllers/rating.py:136-141 |
| 404 | No mapping for service: <service> (NoSuchMapping) | unknown collector mapping | cloudkitty/db/api.py:105-111, cloudkitty/api/v1/controllers/collector.py:48-49 |
| 405 | Invalid endpoint: no metrics in current configuration. | invalid metric file | cloudkitty/api/v1/controllers/info.py:40-42, cloudkitty/api/v1/controllers/info.py:65-67 |
| 404 | <metric_name> | unknown metric | cloudkitty/api/v1/controllers/info.py:71-72 |
| 403 | Policy doesn't allow %(action)s to be performed. | policy refusal | cloudkitty/common/policy.py:41-47 |
| 500/504 | — | quote: no processor answers within the oslo.messaging timeout (MessagingTimeout) | cloudkitty/api/v1/controllers/rating.py:209 [inferred] |
InvalidFilter (cloudkitty/api/v1/controllers/report.py:38) is declared but never raised.
2.5 v2
| Status | Message | Situation | Where |
|---|---|---|---|
| 400 | Parameter dataframes must not be empty. | POST /v2/dataframes with [] | cloudkitty/api/v2/dataframes/dataframes.py:43-45 |
| 400 | <dict> isn't a valid DataFrame: … / isn't a valid DataPoint (wrapped in Invalid data …) | malformed frame | cloudkitty/dataframe.py:152-153, cloudkitty/dataframe.py:233-234 |
| 404 | No resource found for provided filters. | dataframes total 0; no scope matches (GET/PUT/PATCH scope) | cloudkitty/api/v2/dataframes/dataframes.py:106-108, cloudkitty/api/v2/scope/state.py:73-75, cloudkitty/api/v2/scope/state.py:134-136, cloudkitty/api/v2/scope/state.py:184-186 |
| 404 | Too many resources found with the same scope_id: %s. | PATCH when the id exists for several fetcher/collector/scope_key combinations | cloudkitty/api/v2/scope/state.py:188-192 |
| 404 | Cannot create a scope with an already existing scope_id: %s. | POST scope for an existing id (a 409 would be expected) | cloudkitty/api/v2/scope/state.py:251-256 |
| 400 | Either all_scopes or a scope_id should be specified. | PUT scope | cloudkitty/api/v2/scope/state.py:119-121 |
| 400 | Variable 'last_processed_timestamp' cannot be empty/None. | PUT scope | cloudkitty/api/v2/scope/state.py:123-125 |
| 400 | Invalid groupby time option. There is no groupby processing for [%s]. | groupby=time-x with x ∉ {d,w,m,y} | cloudkitty/storage/v2/__init__.py:183-187 |
| 400 | Limit %s exceeds maximum allowed limit of 5000 for Loki storage. … | Loki back end | cloudkitty/storage/v2/loki/__init__.py:180-183, cloudkitty/storage/v2/loki/__init__.py:218-221 |
| 400 | Empty or blank reason text is not allowed. … | reprocess | cloudkitty/api/v2/task/reprocess.py:123-127 |
| 400 | End reprocessing timestamp [%s] cannot be less than start reprocessing timestamp [%s]. (arguments swapped in the message) | reprocess | cloudkitty/api/v2/task/reprocess.py:128-132 |
| 400 | The provided reprocess time window does not comply with the configured collector period. A valid time window near the provided one is %s | not aligned on [collect]/period | cloudkitty/api/v2/task/reprocess.py:134-140 |
| 400 | Cannot use 'ALL' with scope ID [%s]. … | ALL mixed with ids | cloudkitty/api/v2/task/reprocess.py:150-155 |
| 400 | Scopes %s scheduled to reprocess [start=%s, end=%s] do not exist. | unknown ids | cloudkitty/api/v2/task/reprocess.py:173-177 |
| 400 | Cannot execute a reprocessing [start|end=%s] for scope [%s] … after the last possible timestamp [%s]. | window beyond what was processed | cloudkitty/api/v2/task/reprocess.py:186-195 |
| 400 | Cannot schedule a reprocessing for scope [%s] for reprocessing time [%s], because it already has a schedule for a similar time range [%s]. | overlap | cloudkitty/api/v2/task/reprocess.py:223-241 |
| 400 | The order [%s] is not valid. Accepted values are %s. | GET reprocesses | cloudkitty/api/v2/task/reprocess.py:289-292 |
| 404 | Module '<id>' not found | v2 rating modules | cloudkitty/api/v2/rating/modules.py:72-74, cloudkitty/api/v2/rating/modules.py:92-94 |
| 500 | RuntimeError("Clause [%s] is not allowed …") | forbidden word in custom_fields (InfluxDB) | cloudkitty/storage/v2/influx.py:260-268 [inferred: not converted to 400] |
| 500 | InvalidStatusCode, PushError, influxdb client errors, requests errors | storage back end unreachable or refusing | cloudkitty/storage/v2/elasticsearch/client.py:144-148, cloudkitty/storage/v2/loki/client.py:236-241 |
3. Errors of the processor (never seen by API clients)
| Error | Message / log | Situation | Effect | Where |
|---|---|---|---|---|
NoDataCollected | Collector '%s' returned no data for resource '%s'; logged at INFO No data collected for metric %s at timestamp %s | a metric has no measure for the period | the metric is skipped; if all metrics are empty the period is still marked processed with a WARNING No usage data for storage scope [%s] … consider de-activating it. | cloudkitty/collector/__init__.py:162-172, cloudkitty/orchestrator.py:358-365, cloudkitty/orchestrator.py:433-436 |
| any collection exception | Error while collecting metric %s at timestamp %s: %s. Exiting. | back-end error, timeout (collector_request_timeout, 300 s) | the whole scope cycle aborts; last_processed_timestamp is not advanced; retried at the next loop | cloudkitty/orchestrator.py:366-374, cloudkitty/orchestrator.py:386-389 |
CollectError | <errorType>: <error> | Prometheus/Aetos answered status: error or non-JSON | as above | cloudkitty/collector/prometheus_base.py:196-207, cloudkitty/collector/exceptions.py:17 |
PrometheusResponseError | Could not get a valid json response for {} (response: {}) | non-JSON body | wrapped in CollectError / PrometheusFetcherError | cloudkitty/common/prometheus_client.py:36-47 |
gexceptions.MetricNotFound, BadRequest with Metrics not found | WARNING [%s] Skipping this metric for the current cycle. | Gnocchi metric missing for the scope | returns no data | cloudkitty/collector/gnocchi.py:335-343 |
AssociatedResourceNotFound | Resource with {}={} could not be found (WARNING) | a measure's group has no matching Gnocchi resource for metadata | the point is dropped | cloudkitty/collector/gnocchi.py:136-143, cloudkitty/collector/gnocchi.py:482-488 |
InvalidConfiguration, voluptuous errors | Metric {} already exists…, has duplicates in groupby and metadata, uses MAP mutator but mutate_map is missing | invalid metric file | processor/API start fails, or /v1/info answers 405 | cloudkitty/collector/__init__.py:312-360, cloudkitty/collector/__init__.py:178-188 |
PrometheusFetcherError | Unexpected Prometheus server response "{}" for "{}" | fetcher response malformed | scope list fails → whole loop fails **[inferred: exception propagates out of internal_run]** | cloudkitty/fetcher/prometheus.py:123-145 |
VersionNotAvailable | Keystone version you've specified is not supported | [fetcher_keystone]/keystone_version not 2 or 3 | idem | cloudkitty/fetcher/keystone.py:73-82 |
rating module exception (pyscript error, hashmap Decimal conversion of a non-numeric field with a threshold) | Error processing scope %s, we will try it again later. + traceback | bad rule or script | the scope is stuck on that period until the rule is fixed | cloudkitty/orchestrator.py:716-723, cloudkitty/rating/hash/__init__.py:262-265 |
| storage push failure | idem | back end down | state not advanced; points already committed in earlier chunks stay → possible duplicates on retry [inferred] | cloudkitty/orchestrator.py:445-460, cloudkitty/storage/v2/influx.py:194-195 |
IndexDoesNotExist / InvalidStatusCode | Elasticsearch index {} does not exist | cloudkitty-storage-init on a missing index; _req raises InvalidStatusCode first on a 404 | init fails | cloudkitty/storage/v2/elasticsearch/__init__.py:103-112, cloudkitty/storage/v2/elasticsearch/client.py:144-148 |
UnsupportedContentType | Content-Type {} is not supported… | [storage_loki]/content_type ≠ application/json | driver load fails | cloudkitty/storage/v2/loki/client.py:35-36 |
reprocessing Exception | Scope [%s] scheduled for reprocessing does not seem to exist anymore. / Unexpected number of storage state entries found for scope [%s]. | the scope state row vanished or is duplicated | that schedule fails at every loop | cloudkitty/orchestrator.py:540-551 |
update_reprocessing_time miss | WARNING Trying to update current time to [%s] … could not find a this task in the database. (the format string contains a broken %,) | schedule removed while running | logging error [inferred] | cloudkitty/storage_state/__init__.py:419-426 |
| RPC errors | UnsupportedVersion for reload_modules v1.1 [inferred] | API casts a version above the endpoint's | logged by oslo.messaging on the processor | cloudkitty/api/v1/controllers/rating.py:51-53, cloudkitty/orchestrator.py:122-123 |
Source: reverse-spec-cloudkitty/02-errors_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.