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

Reverse-spec · Services

cloudkitty — Rewrite pack

Rewrite pack — CloudKitty — branch stable/2026.2

Branch stable/2026.2 @ 0685cc330e83 — produced on 2026-11-09 for the Go rewrite openkeel-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

ArtefactContentHow it was produced
routes_stable-2026.2.json72 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.json23 schemas: v2 voluptuous input schemas converted to JSON schema (x-query: single|multi|dict, x-coerce, x-voluptuous for custom validators), v1 WSME body typesidem
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.confoslo config sample, 27 groupsrewrite_pack.py --config-generator etc/oslo-config-generator/cloudkitty.conf
schema-mysql_stable-2026.2.sql, schema-postgresql_stable-2026.2.sqlDDL of the 12 model tables (core, hashmap, pyscripts, storage state, storage v1)SQLAlchemy CreateTable on every model module
schema-mysql-migrated_stable-2026.2.sqlthe production schema: cloudkitty-dbsync upgrade + storage-state migrations on MariaDB 11.4mariadb-dump --no-data
schema-diff_stable-2026.2.mdmodel vs migrated differences, with their readingsqlalchemy.inspect
exchanges_stable-2026.2.json223 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.json19 seeds (one per gabbi YAML): DB rows, config overrides, policyidem
response-schemas_stable-2026.2.{json,md}inferred response schemas, 51 operationsidem
observed-errors_stable-2026.2.md51 (operation, status, body shape, message) combinations_rewrite/tools/observed_errors.py (WSME and flask-restful bodies)
cases-hashmap-point_stable-2026.2.json23 vectors of HashMap.add_rating_informations (accumulator → price)conformance_capture.py calls on cloudkitty.tests.test_hashmap
cases-hashmap_stable-2026.2.json1 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 read Invalid data '<repr of input>' : <voluptuous message> (path: '<repr of path>') — Python reprs 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 None answers 204 whatever status_code says (GET /v1/rating/reload_modules); WSME validation errors are 400 Invalid input for field/attribute <name>. Value: '<v>'. <reason>. Unknown body attributes are 400 **[inferred: WSME UnknownAttribute]**.
  • Odd success statuses that clients see: PUT /v1/rating/modules/{id} and PUT …/hashmap/mappings|thresholds/{id} 302 + Location; PUT …/pyscripts/scripts/{id} 201; POST creations 201 + Location (with a trailing / before the id); PUT /v2/scope and POST /v2/task/reprocesses 202; GET /v2/dataframes 404 when empty; POST /v2/scope 404 when the scope already exists.
  • Trailing slashes. Pecan accepts …/services/ and …/services; flask-restful does not (/v2/scope/ is 404).
  • Policies. GET /v2/rating/modules enforces nothing (rule registered, unused). module_config sub-routes are all rating:module_config. info:* and rating:quote are open (empty rule = any authenticated user).
  • Aliases: /v1/billing/* is the same controller instance as /v1/rating/*.

3. Algorithms only the code defines

  1. Hashmap rating — 01 § 4.2, vectors in cases-hashmap-point_*. Arithmetic is Python Decimal with the default context (28 significant digits, ROUND_HALF_EVEN); costs come from NUMERIC(40,28) columns but **seed values pass through float** in the tests (0.1337000000000000132782673745): compare as decimals at 28 digits.
  2. 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 + period if last + period + wait_periods × period < now (strict), otherwise nothing to do.
  3. Quote — 01 § 3.1, 05 § 2: frame [now, now + period), desc.groupby/desc.metadata only, enabled modules by decreasing priority, tenant None, answer str(Decimal) returned as a JSON number.
  4. Module chaining — enabled modules sorted by priority desc; each gets the previous output (cloudkitty/orchestrator.py:509-521). noop leaves prices untouched.
  5. **Summary (/v2/summary)** — 01 § 6.1: groupby (default type + scope key), filters, custom_fields (InfluxDB only), response_format table (columns + results) or object; begin default first of month, end default next month; totals are SUM(qty), SUM(price) as qty, rate; limit 100 offset 0.
  6. Reprocessing — 05 § 3; the purge is [start, end) while the last period end itself is re-rated.
  7. Scope reset — 05 § 2 reset_state: delete from last_processed_timestamp to the end of time, then set the state.

4. Client expectations

ClientRelies 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 consolenone: skyline-console has no rating page (checked in reverse-spec-skyline-console)
Processor ↔ APIoslo.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 (409 Script <name> already exists, 409 Mapping … already exists), not by unique keys.
  • Alembic version tables: cloudkitty_alembic, hashmap_alembic, pyscripts_alembic, storage_states_alembic (heads: core 385e33fef139, pyscripts c6e4cda29654, storage-state 9feccd32, hashmap at its head after 8a591f85865f). A rewrite sharing the database with the Python service must keep them untouched.
  • Pyscript data is zlib-compressed in the column; checksum is 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.