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

Reverse-spec · Clients

python-octaviaclient — Rewrite pack

Client contract (rewrite pack) — python-octaviaclient — branch stable/2026.2

Branch stable/2026.2 @ b37975ec8f2d (tag 3.15.0 + 2 commits) — repository https://opendev.org/openstack/python-octaviaclient — analysed on 2026-11-01

This pack states **what openstack loadbalancer … requires from a load-balancer service**, so that an OpenKeel replacement keeps working with the unmodified plugin. It is a client contract, not a plan to rewrite the plugin.

No real service was contacted. The CLI ran against a local stub on 127.0.0.1. Octavia's code and OpenAPI were read, not run. See § 5.

1. Contents of _rewrite/

FileContentUse
[cli-commands_stable-2026.2.json](_rewrite/cli-commands_stable-2026.2.json)the 82 commands: class, purpose, each of the 491 arguments (names, type, default, choices, required, exclusive groups, help), client calls with file:line, error sitescheck argument names; generate a parser
[client-contract_stable-2026.2.json](_rewrite/client-contract_stable-2026.2.json)per command: client methods, observed requests, typed sequence with bodies, the effect of each option on the wire, vectors, probe exits; the method-to-route tablerequirements per command
[api-surface_stable-2026.2.md](_rewrite/api-surface_stable-2026.2.md)73 Octavia, 5 Neutron and 2 Keystone operations, with client method, presence in Octavia's OpenAPI, commandsgap analysis of an OpenKeel service
[unit-vectors_stable-2026.2.json](_rewrite/unit-vectors_stable-2026.2.json)287 vectors: argv → ordered API method calls, normalised ({uuid})test argument mapping without Python
[wire-probe-404_stable-2026.2.json](_rewrite/wire-probe-404_stable-2026.2.json)stub answering 404 with a fault bodyreaction to absence
[wire-probe-200_stable-2026.2.json](_rewrite/wire-probe-200_stable-2026.2.json)stub answering 200 {}reaction to an empty object
[wire-probe-typed_stable-2026.2.json](_rewrite/wire-probe-typed_stable-2026.2.json)stub answering shaped resources, with required argumentsfull request sequences; added because modes 404 and 200 stop at the first lookup
[wire-probe-options_stable-2026.2.json](_rewrite/wire-probe-options_stable-2026.2.json)one run per optional argument: 475 runs, all exit 0the wire effect of each option
[defect-repro_stable-2026.2.json](_rewrite/defect-repro_stable-2026.2.json)scenarios of 02 § 5, and the sweep with no network in the catalogevidence for defects
ScriptRoleChange from the shared tool
[extract_plugin_stable-2026.2.py](_rewrite/extract_plugin_stable-2026.2.py)imports the entry points, dumps parsers, walks codeunchanged
[probe_plugin_stable-2026.2.py](_rewrite/probe_plugin_stable-2026.2.py)stub and shell runnerOctavia fault body; typed mode; --options; logging kept, so that error text is recorded; ClientCache reset; status and headers recorded
[repro_defects_stable-2026.2.py](_rewrite/repro_defects_stable-2026.2.py)defect scenariosnew
[capture_plugin_stable-2026.2.py](_rewrite/capture_plugin_stable-2026.2.py)unit tests → vectorstest-ID prefix
[build_contract_stable-2026.2.py](_rewrite/build_contract_stable-2026.2.py)merges everythingrewritten for this plugin

Versions: python-octaviaclient 3.15.1.dev2 (installed files identical to the repository), osc-lib 4.7.0, cliff 4.16.0, python-openstackclient 10.3.1, openstacksdk 4.20.0, keystoneauth1 5.17.0, Python 3.13.

To regenerate, with HOME set to an empty directory and OS_* unset:

python -I -B _rewrite/extract_plugin_stable-2026.2.py <repo> python-octaviaclient octaviaclient _rewrite
for m in 404 200 typed; do python -I -B _rewrite/probe_plugin_stable-2026.2.py _rewrite/cli-commands_stable-2026.2.json <dir-$m> --mode $m; done
python -I -B _rewrite/probe_plugin_stable-2026.2.py _rewrite/cli-commands_stable-2026.2.json <dir-options> --mode typed --options
python -I -B _rewrite/repro_defects_stable-2026.2.py <repro.json>
python -I -B _rewrite/capture_plugin_stable-2026.2.py <repo> _rewrite/cli-commands_stable-2026.2.json <run-1>/c.json <repo>/octaviaclient/tests/unit
python -I -B _rewrite/capture_plugin_stable-2026.2.py <repo> _rewrite/cli-commands_stable-2026.2.json <run-2>/c.json <repo>/octaviaclient/tests/unit
python -I -B _rewrite/build_contract_stable-2026.2.py . <repo> <dir-404>/wire-probe_stable-2026.2.json <dir-200>/wire-probe_stable-2026.2.json <dir-typed>/wire-probe_stable-2026.2.json <dir-options>/wire-probe_stable-2026.2.json <repro.json> <run-1>/c.json <run-2>/c.json <octavia-openapi.json> <tables-dir>

The generic rewrite_pack.py and conformance_capture.py were not used, because the plugin has no functional tests to record (octaviaclient/tests/functional/base.py:45).

2. What the CLI requires from a service

"Hard" means that the command fails without it. Items marked [observed] were seen on the stub, which shows the client's demand, not a service's answer.

Catalog and URL

#RequirementHardSource
1a public endpoint of type load-balanceryesoctaviaclient/osc/plugin.py:32-36 [observed]
2also an endpoint of type network, or 13 commands fail before any requestyesoctaviaclient/osc/v2/utils.py:273 [observed]
3the catalog URL must not end in /v2; bare or /v2.0 are fineyesoctaviaclient/api/v2/octavia.py:104-106 [observed]
4routes live under /v2.0/lbaas/… and /v2.0/octavia/amphorae…; /v2 alone is not usedyesoctaviaclient/api/constants.py:32-33 [observed]
5no version document or microversion is neededn/aoctaviaclient/osc/plugin.py:37-41 [observed]

Lookup

#RequirementHardSource
6list routes honour ?id= and ?name=, including amphorae, members and rules, and return only matches. An ignored filter gives found with name or ID as soon as 2 rows existyesoctaviaclient/osc/v2/utils.py:108-145
7availability zones honour ?name=, and their rows have name; no id is requiredyesoctaviaclient/osc/v2/utils.py:110, octaviaclient/osc/v2/utils.py:168-169
8GET /resource/{id} for an unknown ID answers 404 (400 is treated alike)yes, for the fallbackosc-lib:osc_lib/api/api.py:411-425 [observed]
9list rows have id, and name where looked upyesoctaviaclient/osc/v2/utils.py:193

Response shape

#RequirementHardSource
10lists always hold the collection key, even when empty; otherwise a bare key name and exit 1yesoctaviaclient/api/v2/octavia.py:92 [observed]
11a single-resource object has its wrapper as the first JSON key. Check the encoder of a Go service: Go map encoding sorts keys, so any added key must sort after the wrapper; a struct keeps field orderyesosc-lib:osc_lib/api/api.py:409 [observed]
12POST bodies are {"<wrapper>": {...}}, valid JSONyesoctaviaclient/osc/v2/load_balancer.py:178 [observed]
13all bodies decoded by the client are valid JSON; a proxy page gives raw Python errorsyes02 § 5 items 5, 6 [observed]
14wrappers and keys as in octaviaclient/api/constants.py:14-30: loadbalancer, listener, pool, member, healthmonitor, l7policy, rule, amphora, flavor, flavorprofile, availability_zone, availability_zone_profile, quotayesoctaviaclient/osc/v2/l7rule.py:111, octaviaclient/osc/v2/availabilityzone.py:71
15stats holds active_connections, bytes_in, bytes_out, request_errors, total_connections; amphora_stats is a list of rows with these as numbers and listener_idmissing: blank; non-number: failureoctaviaclient/osc/v2/constants.py:50-55, octaviaclient/osc/v2/amphora.py:253-256
16tags is present on every taggable resource, or set --tag and unset --tag give 'tags'yes for those optionsoctaviaclient/osc/v2/utils.py:827, octaviaclient/osc/v2/utils.py:839 [observed]
17other fields are read by column list; a missing one shows blank. Lists: octaviaclient/osc/v2/constants.py:15-380. Each listed field exists in Octavia's observed response schemas (checked against reverse-spec-octavia/_rewrite/response-schemas_stable-2026.2.json, read only)noosc-lib:osc_lib/utils/__init__.py:513
18flavor_capabilities and availability_zone_capabilities are lists of name, descriptionkey: yesoctaviaclient/osc/v2/provider.py:84, octaviaclient/osc/v2/provider.py:92, octaviaclient/osc/v2/constants.py:321-325
19GET /lbaas/quotas/defaults answers {"quota": {...}}, keys load_balancer, health_monitor, etc.yesoctaviaclient/osc/v2/quota.py:95, octaviaclient/osc/v2/constants.py:251-259

Status, for --wait

#RequirementHardSource
20provisioning_status on load balancers, listeners, pools, members, health monitors, policies and rulesyes with --waitoctaviaclient/osc/v2/utils.py:792 [observed]
21the status leaves ACTIVE before the answer to a write, or --wait returns at onceyes for correctnessosc-lib:osc_lib/utils/__init__.py:806-810 [observed on a stub that stays ACTIVE]
22failure ends in ERROR, otherwise the wait for ACTIVE never endsyesosc-lib:osc_lib/utils/__init__.py:811-813
23a finished deletion answers 404 on GETyes with delete --waitoctaviaclient/osc/v2/utils.py:808-821
24parent references: listener.loadbalancers[0].id, pool.loadbalancers[0].id, healthmonitor.pools[0].id, l7policy.listener_id, amphora.loadbalancer_id (may be null)yes with --waitoctaviaclient/osc/v2/listener.py:253, octaviaclient/osc/v2/pool.py:176, octaviaclient/osc/v2/health_monitor.py:159, octaviaclient/osc/v2/l7policy.py:133, octaviaclient/osc/v2/amphora.py:161

What the service must accept

#RequirementSource
25connection_limit as a JSON string on listener create and set. A strict Go decoder into int rejects this. wsme converts it [inferred, octavia:octavia/api/v2/types/listener.py:130]octaviaclient/osc/v2/utils.py:337 [observed]
26query booleans True and False, capitalised: admin_state_up, enabled, cascade. Octavia parses cascade with bool_from_string (octavia:octavia/api/v2/controllers/load_balancer.py:880)octaviaclient/osc/v2/utils.py:291-293 [observed]
27repeated query parameters: tags=a&tags=b, and tags-any, not-tags, not-tags-anyoctaviaclient/osc/v2/utils.py:200-207 [observed]
28null on PUT means back to default, for all fields of unset commandsoctaviaclient/osc/v2/utils.py:774-781 [observed]
29an empty object on PUT: {"loadbalancer": {}}octaviaclient/osc/v2/load_balancer.py:486-489 [observed]
30quota PUT with both spellings: load_balancer, health_monitor (from set) and loadbalancer, healthmonitor (from unset)octaviaclient/osc/v2/utils.py:589-591, octaviaclient/osc/v2/quota.py:262 [observed]; octavia:octavia/api/v2/types/quotas.py:61-64
31flavor_data and availability_zone_data as strings holding JSON; http_version as a numberoctaviaclient/osc/v2/utils.py:674, octaviaclient/osc/v2/utils.py:716, octaviaclient/osc/v2/utils.py:576 [observed]
32a tags array in any order, treated as a setoctaviaclient/osc/v2/utils.py:827-829 [observed]
33failover and configure PUT with no bodyoctaviaclient/api/v2/octavia.py:221, octaviaclient/api/v2/octavia.py:817 [observed]

Errors and paging

#RequirementSource
34error bodies hold faultstring, or the user sees canned text; faultcode and debuginfo are not readoctaviaclient/api/v2/octavia.py:44-46 [observed]
35x-openstack-request-id is shown in errorsoctaviaclient/api/exceptions.py:24-27 [observed]
36<key>_links is optional. Its next link must stop at the end, since the client has no page limit. Its query replaces all parameters, so a compatible service should put the filters in the link; this improves on Octavia, which does not (octavia:octavia/api/common/pagination.py:151-172)octaviaclient/api/v2/octavia.py:94-101 [observed]

3. Coverage of OpenKeel services

Service typeOperationsNote
load-balancer73Octavia replacement; see reverse-spec-octavia/
network5, read onlysubnets, ports, networks, qos/policies, security-groups, each filtered by id or name
identity2, read onlyprojects/{value}, projects?name=

No OpenKeel service was probed: the request allowed only the local stub.

4. Rewriting the plugin itself

Not recommended now. The plugin is thin, and scripts depend on command and argument names, which cli-commands_stable-2026.2.json holds. A short list to keep, and to fix:

TopicRuleSource
Namesloadbalancer_listener_create → loadbalancer listener createsetup.cfg:33-115
Exit codes0, 1, 2, as in 02 § 1cliff:cliff/app.py:428
Fixfilters kept across pages; lazy network client; a wait timeout; error text instead of raw Python02 § 5
RiskBaseAPI, wait_for_status, wait_for_delete and ClientCache are deprecated in osc-lib, and a removal breaks the pluginosc-lib:osc_lib/api/api.py:69

5. Conformance: what was proved

EvidenceResultWhat it provesWhat it does not prove
Parser dump82 of 82 classes imported, 0 failuresthe catalogue is complete for the declared entry pointsnothing beyond them
Unit tests with recorder421 of 421 pass, in two runs; 287 vectors; 82 of 82 classesargv → API method callsanything about a service: the API is mocked
Comparison of runs287 of 287 vectors identical over 4 runs, sequences and arguments, with Python's default hash randomisationdeterministic in these testsreal order of tags, which does vary (below)
Wire probe 40472 commands sent a request, 75 requests; exits: 72 with 1, 10 with 0lookup behaviour, fault handlinganything past the first 404
Wire probe 200 {}72 commands sent a request, 78 requests; exits: 71 with 1, 11 with 0reaction to an empty object
Wire probe typed72 commands sent a request, 139 requests; all 82 exit 0every default sequence reaches its real write routecorrectness on a real service; the stub is built from the client's own columns, so the exit 0 is partly circular
Option sweep475 runs, all exit 0, 82 of 82 commands sent requests, 983 requestseach option's wire form; all 73 Octavia routes seencombinations of options
Defect scenarios66 scenarios and an 82-command sweep02 § 5that a real service triggers them, unless stated
Route cross-check72 of 73 routes in Octavia's OpenAPI; the other is quotas/defaultsroutes exist on paperbehaviour; read only

Replay against the original service: not done, and not possible under this request's rules. The usual "replay is mandatory" bar is not met. The vectors were not replayed against any rewrite either, since none exists.

Tag order: the PUT body varies from run to run (["t3","t4","t2","t1"], then ["t2","t4","t1","t3"]) [observed], so wire files are not byte-stable across runs on set --tag and unset --tag. The unit vectors were identical over 4 runs, but this does not show that the order is stable: their merged tag lists hold only 2 items.

To close the gap: point probe_plugin at a devstack on stable/2026.2, or at the OpenKeel lab, with the stub removed, and compare request sequences and exit codes with wire-probe-typed.

6. Gaps found while building the pack

  • The shared probe's modes stop at the first lookup, since {} has no collection key and 404 ends the command. Hence typed mode. Its 10 unset commands send nothing with no option; they were reached through --options.
  • The shared probe disabled logging, which hid every error text; this copy keeps it.
  • ClientCache holds clients on the class (osc-lib:osc_lib/clientmanager.py:47), so in one process later commands kept the first endpoint. This made two catalog scenarios pass falsely until the probe reset it. Results before the reset were discarded.
  • The extractor resolves SDK calls to routes only: with_rest is 3 of 197 calls. Octavia routes come from the OctaviaAPI table that build_contract reads, and from the wire.
  • Client calls in cli-commands are static and over-approximate: amphora configure lists load_balancer_show, reached through a status_f argument. For routes, the wire wins.
  • No functional tests exist in the repository, so no suite could be replayed.

7. Non-functional requirements

  • Latency: each invocation authenticates, and each name adds one list request, or two for a child with a parent [observed]. A script of N commands sends N token requests.
  • Polling: one GET per 3 s per --wait (octaviaclient/osc/v2/utils.py:793).
  • Paging: the client requests no page size, so the service's limit decides. Its next page is requested at once.

8. Checklist for an OpenKeel service

  • [ ] Types load-balancer and network are in the catalog; the URL does not end in /v2.
  • [ ] Routes of api-surface_stable-2026.2.md exist under /v2.0, including GET /lbaas/quotas/defaults.
  • [ ] Lists keep their key when empty; filters id and name work on every list.
  • [ ] The wrapper is the first JSON key of a single-resource body.
  • [ ] Decoding accepts connection_limit as a string, True/False in queries, repeated tag parameters, null as reset, and empty PUT objects.
  • [ ] Both quota spellings are accepted.
  • [ ] Writes leave ACTIVE before answering; failures end in ERROR; finished deletes give 404.
  • [ ] Errors hold faultstring; responses carry x-openstack-request-id.
  • [ ] next links carry the filters and stop at the end.
  • [ ] Run probe_plugin against the service and compare with wire-probe-typed.

Source: reverse-spec-python-octaviaclient/07-rewrite-pack_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.