End-to-end scenarios — CloudKitty — branch stable/2026.2
Branch
stable/2026.2@0685cc330e83— repository https://opendev.org/openstack/cloudkitty — analysed on 2026-11-09
1. Rating cycle of one scope (the core flow)
Default deployment: Keystone fetcher, Gnocchi collector, hashmap, InfluxDB storage. Trigger: the processor loop (05 § 1).
sequenceDiagram
participant P as cloudkitty-processor worker
participant KS as Keystone
participant TZ as tooz back end
participant DB as MariaDB
participant G as Gnocchi
participant I as InfluxDB
P->>KS: GET /v3/projects + role check per project (fetcher)
KS-->>P: scopes with role "rating"
loop each scope (shuffled)
P->>TZ: acquire lock sha256(cloudkitty-<scope>-…) (non-blocking)
alt lock busy
Note over P: skip scope this loop
else acquired
P->>DB: SELECT cloudkitty_storage_states (last_processed_timestamp)
Note over P: next = last + period, if next + wait_periods×period < now<br/>new scope: start of current month
loop while a period is ready
P->>DB: SELECT modules_state, hashmap_* / pyscripts_scripts active at period start
par one thread per metric (max_threads)
P->>G: POST /v1/aggregates (metric, scope, period, groupby)
G-->>P: measures per group
P->>G: POST /v1/search/resource/<type> (only if metadata)
end
alt every metric empty
Note over P: WARNING "No usage data…"; nothing stored
else data
Note over P: hashmap/pyscripts set prices<br/>skip_datapoints_expression filters points
P->>I: write_points measurement "dataframes" (chunks of 500)
end
P->>DB: UPDATE cloudkitty_storage_states SET last_processed_timestamp = period start
end
P->>TZ: release lock
end
end
Note over P: sleep [collect]/period, then start againKey references: fetcher cloudkitty/fetcher/keystone.py:84-105; lock cloudkitty/orchestrator.py:700-735; next period cloudkitty/utils/__init__.py:200-223; collection cloudkitty/orchestrator.py:341-391; rating cloudkitty/orchestrator.py:509-521; storage cloudkitty/orchestrator.py:445-460, cloudkitty/storage/v2/influx.py:766-773; state cloudkitty/orchestrator.py:523-524.
Where to look when it fails: a scope whose last_processed_timestamp stays behind (GET /v2/scope) and the processor log Error processing scope %s, we will try it again later. (cloudkitty/orchestrator.py:719-723). A collection error aborts the whole period for that scope; a bad rule or script blocks it until fixed (02 § 3).
Latency: a period [T, T+p) is rated no earlier than T + p + wait_periods × p (3 h with defaults), then up to one more loop (sleep(period)) later.
2. Instant quote
sequenceDiagram
participant C as Client (Skyline, CLI)
participant A as cloudkitty-api
participant MQ as RabbitMQ
participant P as cloudkitty-processor
participant DB as MariaDB
C->>A: POST /v1/rating/quote {"resources":[{"service","desc","volume"}]}
A->>A: policy rating:quote (open)
A->>MQ: RPC call quote(res_data={"usage":{…}}) topic cloudkitty, ns rating
MQ->>P: quote
P->>DB: load enabled modules + all rules (tenant NULL only)
Note over P: DataFrame [now, now+period)<br/>DataPoint(groupby=desc.groupby, metadata=desc.metadata)<br/>modules in priority order, sum of prices
P-->>MQ: "0.042"
MQ-->>A: reply
alt no processor / timeout
A-->>C: 500 (MessagingTimeout) [inferred]
else
A-->>C: 200 0.042
endReferences: cloudkitty/api/v1/controllers/rating.py:190-210, cloudkitty/orchestrator.py:145-180, cloudkitty/orchestrator.py:268-280. Trap: a flat desc loses every attribute (00, point 1).
3. Changing a price (hashmap mapping)
sequenceDiagram
participant Op as Administrator
participant A as cloudkitty-api
participant KS as Keystone
participant DB as MariaDB
participant P as cloudkitty-processor
Op->>A: POST /v1/rating/module_config/hashmap/mappings {"field_id","value":"m1.small","type":"flat","cost":"0.05","start":"2026-12-01"}
A->>A: policy rating:module_config (admin)
A->>A: validate start/end (start in the past → 400 unless force=true)
A->>KS: GET /v3/auth/tokens (X-Subject-Token = caller token, verify=False)
KS-->>A: user id → created_by
A->>DB: SELECT … FOR UPDATE (same name or overlapping window)
alt conflict
A-->>Op: 409 Mapping '…' already exists for field '…', tenant: '…'
else
A->>DB: INSERT hashmap_mappings
A-->>Op: 201 Location: …/mappings/<uuid>
end
Note over P: at each scope cycle, rules active at the period start are reloaded:<br/>periods from 2026-12-01 use 0.05, earlier periods keep the old mappingReferences: cloudkitty/rating/hash/controllers/mapping.py:152-185, cloudkitty/rating/common/validations/fields.py:73-87, cloudkitty/common/custom_session.py:38-58, cloudkitty/rating/hash/db/sqlalchemy/api.py:296-445, cloudkitty/orchestrator.py:329-339. To end the old price, the operator sets its end (PUT, end only) to 2026-12-01; without it, both rules are active from that date and both contribute to the group (flat: the highest wins; rate: they multiply) (cloudkitty/rating/hash/__init__.py:200-207).
Already-rated periods are not re-priced: that needs a reprocessing (§ 4).
4. Reprocessing a past window
sequenceDiagram
participant Op as Administrator
participant A as cloudkitty-api
participant DB as MariaDB
participant R as cloudkitty-processor (reprocessor worker)
participant TZ as tooz
participant S as Storage back end
participant Col as Collector back end
Op->>A: POST /v2/task/reprocesses {"scope_ids":["p1"],"start_reprocess_time","end_reprocess_time","reason"}
A->>DB: SELECT scope state, existing schedules
alt misaligned / beyond last processed / overlapping
A-->>Op: 400 …
else
A->>DB: INSERT storage_scope_reprocessing_schedule
A-->>Op: 202
end
loop reprocessor loop (every period)
R->>DB: SELECT unfinished schedules
R->>TZ: acquire lock (schedule)
R->>DB: next = start or current + period (≤ end)
alt first period of the schedule
R->>S: delete [start, end) for {scope_key: p1}
end
R->>Col: collect, rate with rules active at that period
R->>S: push rated frame
R->>DB: UPDATE current_reprocess_time
endReferences: cloudkitty/api/v2/task/reprocess.py:53-93, cloudkitty/orchestrator.py:527-631, cloudkitty/orchestrator.py:768-806.
Failure points: with Elasticsearch/OpenSearch the delete erases every scope's data in the window (00, point 2); the reprocessing re-rates only the scheduled scopes. The schedule processes the period that starts at end_reprocess_time too (00, point 13).
5. Resetting a scope (PUT /v2/scope)
sequenceDiagram
participant Op as Administrator
participant A as cloudkitty-api
participant DB as MariaDB
participant MQ as RabbitMQ
participant P as cloudkitty-processor (any worker)
participant TZ as tooz
participant S as Storage back end
Op->>A: PUT /v2/scope {"scope_id":"p1","last_processed_timestamp":"2026-10-01T00:00:00"}
A->>DB: SELECT matching scopes
A->>MQ: cast reset_state(res_data)
A-->>Op: 202
MQ->>P: reset_state
loop each scope (shuffled)
P->>TZ: acquire lock (blocking)
P->>S: delete from last_processed_timestamp to now for {scope_key: id}
P->>DB: UPDATE last_processed_timestamp
P->>TZ: release
end
Note over P: the next rating loop re-rates from last_processed_timestamp + periodReferences: cloudkitty/api/v2/scope/state.py:105-150, cloudkitty/orchestrator.py:221-246, cloudkitty/storage_state/__init__.py:148-179. The client gets no completion signal: it polls GET /v2/scope. InfluxDB v2's delete_api.delete(begin, None, …) with end=None is untested here [inferred].
Source: reverse-spec-cloudkitty/04-scenarios_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.