Client contract (rewrite pack) — osc-placement → Placement — branch stable/2026.2
Branch
stable/2026.2@402610a21872— repository https://opendev.org/openstack/osc-placement — analysed on 2026-10-28 Placement reference:../reverse-spec-placement/(branchstable/2026.2@f4a89d3803cb), used by the functional tests throughplacement.tests.functional.fixtures.placement.PlacementFixture
The goal here is not to rewrite the CLI. It is to state what osc-placement requires from Placement, so that a rewrite (OpenKeel Placement, in Go) keeps the openstack resource provider|resource class|trait|allocation candidate … commands working unchanged, and to provide a replayable suite that proves it.
1. Contents of _rewrite/
| File | Content |
|---|---|
[client-contract_stable-2026.2.md](_rewrite/client-contract_stable-2026.2.md), [client-contract_stable-2026.2.json](_rewrite/client-contract_stable-2026.2.json) | for each of the 31 commands: entry point, number of runs in the tests (and failures), Placement operations called, microversions and statuses observed, request examples; plus the Placement operations the CLI never calls |
[exchanges_stable-2026.2.json](_rewrite/exchanges_stable-2026.2.json) | 928 invocations of the openstack CLI (the command text and the test that ran it), and the 1,381 HTTP exchanges each one caused (request, headers, body, response) |
[response-schemas_stable-2026.2.json](_rewrite/response-schemas_stable-2026.2.json), [response-schemas_stable-2026.2.md](_rewrite/response-schemas_stable-2026.2.md) | Placement response schemas observed through the CLI (32 operations, 57 operation × status pairs) |
[observed-errors_stable-2026.2.md](_rewrite/observed-errors_stable-2026.2.md) | the 29 error messages received by the CLI |
[replay-report_stable-2026.2.json](_rewrite/replay-report_stable-2026.2.json) | replay of the 928 invocations against a fresh Placement |
How it was produced: conformance_capture.py http, with the placement adapter for responses, over the 9 functional test modules of osc-placement (160 tests, 0 failures).
--wrap placement.deploy:loadapprecords every HTTP exchange.--record-calls osc_placement.tests.functional.base:BaseTestCase.openstackrecords every CLI command and links each one to its exchanges.
Versions: osc-lib 4.7.0, python-openstackclient 10.3.1, keystoneauth1 5.17.0.
2. What osc-placement requires from Placement
| Requirement | Why | Source |
|---|---|---|
GET / answers **406 with errors[0].max_version** when the requested microversion is too high | default negotiation with 1.29 | osc_placement/http.py:69-86 |
every 4xx carries {"errors": [{"detail": "…"}]}; the useful message on the last line of detail | turned into an osc-lib exception; otherwise KeyError | osc_placement/http.py:33-46 |
POST /resource_providers returns a **Location** header that can be followed | resource provider create reads the provider through Location | osc_placement/resources/resource_provider.py:65-66 |
GET /allocations/{consumer} on an unknown consumer: 200 with {"allocations": {}} and consumer_generation null (1.28+) | allocation set reads the generation before writing; a 404 would break the first creation | osc_placement/resources/allocation.py:120-125 |
PUT /allocations/{c} accepts allocations: {} with consumer_generation (1.28+) | allocation unset of everything | osc_placement/resources/allocation.py:274-284 |
DELETE /resource_providers/{uuid}/inventories (1.5+) | inventory delete without a class | osc_placement/resources/inventory.py:339-347 |
repeated query parameters (required, member_of, resourcesN…) and the syntaxes in:, ! | lists and candidates | osc_placement/resources/common.py:41-58, osc_placement/resources/allocation_candidate.py:179-250 |
| microversions used: 1.0 to 1.29 (all except 1.13, 1.15, 1.20, 1.23, 1.26, 1.27, unused), 1.38 and 1.39 | SUPPORTED_MICROVERSIONS | osc_placement/version.py:21-55 |
Placement operations never called by the CLI: POST /allocations (multiple consumers), POST /reshaper, POST /resource_providers/{uuid}/inventories. GET / (negotiation) is not exercised by the functional tests, because they all pass an explicit version (osc_placement/tests/functional/base.py). A Placement rewrite can therefore be validated by this suite without those operations, but it must be validated for them by other means (gabbi, Nova).
3. Conformance suite and proof
Each functional test starts from an empty Placement and builds its data only through CLI commands. Its sequence of invocations is therefore a self-contained scenario, with no database seed. The only variable is the UUIDs that the server generates (providers created without --uuid).
Replay (conformance_capture.py replay --invocations …, adapter adapters/osc_placement.py):
- For each recorded test, run a test case derived from the original class: same
setUp, so a fresh Placement and the sameVERSION. - Run its CLI commands again, in order, with the same options.
- After each command, pair the UUIDs of the recorded responses with those of the replayed responses, by walking both JSON bodies, and substitute them in the following commands.
- Compare, command by command, the outcome (success or exception class) and the sequence of (method, route, status).
Result against the Python Placement: 928/928 invocations identical, 159 tests, 0 test failing to run.
To validate OpenKeel Placement: run the same replay with the CLI pointed at the Go endpoint instead of the in-process fixture (the PlacementFixture of the test class replaced by a fixture that resets the Go database and returns its URL). The expected outcomes and sequences are those of exchanges_stable-2026.2.json. Then compare the response bodies against response-schemas_stable-2026.2.json, and the error messages (last line of detail) against observed-errors_stable-2026.2.md.
4. Gaps
| Gap | Impact |
|---|---|
negotiation GET / → 406 not covered by the functional tests | test it separately (unit test osc_placement/tests/unit/test_http.py, or by hand with --os-placement-api-version 1 against a server < 1.29) |
| replay compares statuses and routes, not bodies field by field | body comparison through the response schemas; UUIDs and dates differ by nature |
| the Go replay needs a fixture that resets the database for every test | a reset endpoint reserved for tests, or one database per test |
POST /allocations, POST /reshaper, POST …/inventories not used | out of scope of this contract |
5. Checklist (OpenKeel Placement seen from osc-placement)
- [ ] 406 with
max_versiononGET /above the supported microversion - [ ] JSON error body
errors[0].detailon every 4xx, message on the last line - [ ]
LocationonPOST /resource_providers - [ ] empty
GET /allocations/{c}with nullconsumer_generation;PUTwith empty allocations at 1.28+ - [ ] the 928 invocations replayed identically (outcome and sequence of method, route and status)
- [ ] response bodies valid against
response-schemas_stable-2026.2.json
Source: reverse-spec-osc-placement/07-rewrite-pack_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.