Rewrite pack — CloudKitty — branch stable/2026.2
Branch
stable/2026.2@0685cc330e83— produced on 2026-11-09 for the Go rewriteopenkeel-cloudkitty. Machine-readable artefacts are in [_rewrite/](_rewrite/rewrite-pack-index_stable-2026.2.md); this file gives what they cannot: the contracts' reading rules, the algorithms only the code defines, the client expectations, the database strategy and the gaps.
1. Artefacts
| Artefact | Content | How it was produced |
|---|---|---|
routes_stable-2026.2.json | 72 paths, 123 operations = 66 distinct + 57 aliases (/v1/billing/… mirrors /v1/rating/…; …/hashmap/services/fields|mappings mirror …/hashmap/fields|mappings). Per operation: handler file:line, policy rules read from the handler source (and from RatingRestControllerBase._route for module_config), WSME status, query parameters, body and response schemas (WSME → JSON schema) | _rewrite/tools/cloudkitty_routes.py (Pecan tree walk + Flask url_map; rewrite_pack.py has no Pecan or voluptuous extractor) |
request-schemas_stable-2026.2.json | 23 schemas: v2 voluptuous input schemas converted to JSON schema (x-query: single|multi|dict, x-coerce, x-voluptuous for custom validators), v1 WSME body types | idem |
policies_stable-2026.2.{json,yaml} | 38 default rules (7 base + 31 operation rules) | rewrite_pack.py --policies cloudkitty.common.policies:list_rules, oslopolicy-sample-generator --namespace cloudkitty |
config_stable-2026.2.conf | oslo config sample, 27 groups | rewrite_pack.py --config-generator etc/oslo-config-generator/cloudkitty.conf |
schema-mysql_stable-2026.2.sql, schema-postgresql_stable-2026.2.sql | DDL of the 12 model tables (core, hashmap, pyscripts, storage state, storage v1) | SQLAlchemy CreateTable on every model module |
schema-mysql-migrated_stable-2026.2.sql | the production schema: cloudkitty-dbsync upgrade + storage-state migrations on MariaDB 11.4 | mariadb-dump --no-data |
schema-diff_stable-2026.2.md | model vs migrated differences, with their reading | sqlalchemy.inspect |
exchanges_stable-2026.2.json | 223 HTTP exchanges of the 3 gabbi suites (all passing) | conformance_capture.py gabbi, one run per suite (the suites patch module-level stevedore managers and cannot share a process), merged |
fixture-seeds_stable-2026.2.json | 19 seeds (one per gabbi YAML): DB rows, config overrides, policy | idem |
response-schemas_stable-2026.2.{json,md} | inferred response schemas, 51 operations | idem |
observed-errors_stable-2026.2.md | 51 (operation, status, body shape, message) combinations | _rewrite/tools/observed_errors.py (WSME and flask-restful bodies) |
cases-hashmap-point_stable-2026.2.json | 23 vectors of HashMap.add_rating_informations (accumulator → price) | conformance_capture.py calls on cloudkitty.tests.test_hashmap |
cases-hashmap_stable-2026.2.json | 1 vector of HashMap.process (rules + frame → rated frame) | idem |
_rewrite/tools/ | the extractors above and the capture adapter adapter_cloudkitty.py | — |
Not produced: OpenAPI (no api-ref parser for CloudKitty's rst), microversions (none), SQLite migrated schema (hashmap revision 8a591f85865f fails on SQLite: its data migration queries the current model inside a batch operation).
2. Reading rules for the contracts
- Two error shapes. v1 (Pecan/WSME, including hashmap and pyscripts):
{"faultcode": "Client"|"Server", "faultstring": "<msg>", "debuginfo": null}. v2 (flask-restful):{"message": "<msg>"}. voluptuous errors in v2 readInvalid data '<repr of input>' : <voluptuous message> (path: '<repr of path>')— Pythonreprs inside:cloudkitty/api/v2/utils.py. Unknown v2 URLs get Werkzeug's HTML 404; wrong method on v1 gets Pecan's HTML 405. - WSME status codes. A handler returning
Noneanswers 204 whateverstatus_codesays (GET /v1/rating/reload_modules); WSME validation errors are 400Invalid input for field/attribute <name>. Value: '<v>'. <reason>. Unknown body attributes are 400 **[inferred: WSMEUnknownAttribute]**. - Odd success statuses that clients see:
PUT /v1/rating/modules/{id}andPUT …/hashmap/mappings|thresholds/{id}302 +Location;PUT …/pyscripts/scripts/{id}201;POSTcreations 201 +Location(with a trailing/before the id);PUT /v2/scopeandPOST /v2/task/reprocesses202;GET /v2/dataframes404 when empty;POST /v2/scope404 when the scope already exists. - Trailing slashes. Pecan accepts
…/services/and…/services; flask-restful does not (/v2/scope/is 404). - Policies.
GET /v2/rating/modulesenforces nothing (rule registered, unused).module_configsub-routes are allrating:module_config.info:*andrating:quoteare open (empty rule = any authenticated user). - Aliases:
/v1/billing/*is the same controller instance as/v1/rating/*.
3. Algorithms only the code defines
- Hashmap rating — 01 § 4.2, vectors in
cases-hashmap-point_*. Arithmetic is PythonDecimalwith the default context (28 significant digits, ROUND_HALF_EVEN); costs come fromNUMERIC(40,28)columns but **seed values pass throughfloat** in the tests (0.1337000000000000132782673745): compare as decimals at 28 digits. - Period scheduling —
cloudkitty/utils/__init__.py:200-223: first period of a new scope = first day of the current month 00:00 (host local time, UTC in practice); next =last + periodiflast + period + wait_periods × period < now(strict), otherwise nothing to do. - Quote — 01 § 3.1, 05 § 2: frame
[now, now + period),desc.groupby/desc.metadataonly, enabled modules by decreasing priority, tenantNone, answerstr(Decimal)returned as a JSON number. - Module chaining — enabled modules sorted by
prioritydesc; each gets the previous output (cloudkitty/orchestrator.py:509-521).noopleaves prices untouched. - **Summary (
/v2/summary)** — 01 § 6.1:groupby(defaulttype+ scope key),filters,custom_fields(InfluxDB only),response_formattable(columns+results) orobject;begindefault first of month,enddefault next month; totals areSUM(qty),SUM(price)asqty,rate;limit100offset0. - Reprocessing — 05 § 3; the purge is
[start, end)while the last periodenditself is re-rated. - Scope reset — 05 § 2
reset_state: delete fromlast_processed_timestampto the end of time, then set the state.
4. Client expectations
| Client | Relies on |
|---|---|
python-cloudkittyclient (openstack rating …) | v1 and v2 paths and bodies above; the 302/201 statuses (it reads the body, not Location); faultstring for error display; hashmap get_types returning ["flat","rate"] |
| cloudkitty-dashboard (Horizon) | /v1/rating/quote (float), /v2/summary table format, hashmap CRUD |
| Grafana / reporting tools | /v2/summary and /v2/dataframes JSON shapes |
| Skyline console | none: skyline-console has no rating page (checked in reverse-spec-skyline-console) |
| Processor ↔ API | oslo.messaging RPC on topic cloudkitty, namespace rating: quote (call), reset_state (cast), reload_*/enable_*/disable_* (casts, no effect) |
5. Database strategy
- The migrated schema (
schema-mysql-migrated_*) is authoritative: hashmap mapping uniqueness over time and pyscript name uniqueness are enforced in code (409Script <name> already exists, 409Mapping … already exists), not by unique keys. - Alembic version tables:
cloudkitty_alembic,hashmap_alembic,pyscripts_alembic,storage_states_alembic(heads: core385e33fef139, pyscriptsc6e4cda29654, storage-state9feccd32, hashmap at its head after8a591f85865f). A rewrite sharing the database with the Python service must keep them untouched. - Pyscript
datais zlib-compressed in the column;checksumis SHA-512 of the uncompressed text (cloudkitty/rating/pyscripts/db/sqlalchemy/models.py:64-106). - Overlap checks take
SELECT … FOR UPDATE(hashmap mapping creation): on Galera, replace by a retried transaction.
6. Gaps and points to decide in a rewrite
See 00 "Points of attention" (14 items). The ones a rewrite must take a position on: quote ignoring flat desc (1), ES/OpenSearch delete without scope filter (2), Loki totals truncated (3), unprotected GET /v2/rating/modules (10), verify=False Keystone call (7), exec/eval of pyscripts and skip_datapoints_expression (6), InfluxQL string formatting (11), the inclusive reprocessing end (13), local time zones (14), dead RPC reload messages (8) and dead v1 collector configuration (9).
Source: reverse-spec-cloudkitty/07-rewrite-pack_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.