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

Reverse-spec · Clients

python-openstackclient — Rewrite pack

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

Branch stable/2026.2 @ 8bb083b83829 — repository https://opendev.org/openstack/python-openstackclient — analysed on 2026-11-01

This pack serves two purposes:

  • **Keep openstack working against OpenKeel's Go services**, by stating what the CLI requires from them.
  • Re-implement the CLI, thanks to the machine-readable catalogue of its commands and arguments.

It differs from the Placement and osc-placement packs on one point: no replay against the original Python services was possible. The CLI ran against a stub and against the OpenKeel lab. See § 5 for what was proved and what was not.

1. Contents of _rewrite/

FileContentUse
[cli-commands_stable-2026.2.json](_rewrite/cli-commands_stable-2026.2.json)the 799 commands: group, class, purpose, each of the 3,471 arguments (names, type, default, choices, required, exclusive groups, help), client calls with file:line, microversion gates, error sitesgenerate the parser of a rewrite; check compatibility of argument names
[client-contract_stable-2026.2.json](_rewrite/client-contract_stable-2026.2.json)per command: client methods, observed requests, static requests, gates, number of unit vectors, probe exit codesrequirements per command
[api-surface_stable-2026.2.md](_rewrite/api-surface_stable-2026.2.md)the 815 operations per service, with source, microversions and commandsgap analysis of an OpenKeel service
[unit-vectors_stable-2026.2.json](_rewrite/unit-vectors_stable-2026.2.json)3,109 vectors: argv → ordered client calls with their arguments, normalised ({uuid})test argument mapping without Python
[lab-compat_stable-2026.2.md](_rewrite/lab-compat_stable-2026.2.md), [wire-lab_stable-2026.2.json](_rewrite/wire-lab_stable-2026.2.json)outcome per command on the OpenKeel lab, and the 730 recorded runscompatibility status
[wire-probe-404_stable-2026.2.json](_rewrite/wire-probe-404_stable-2026.2.json), [wire-probe-200_stable-2026.2.json](_rewrite/wire-probe-200_stable-2026.2.json)for each of the 661 commands: argv, exit code, up to 12 requests with path, microversion, bodywire reference
ScriptRole
[extract_cli_stable-2026.2.py](_rewrite/extract_cli_stable-2026.2.py)imports the entry points, dumps parsers, walks the code with ast, resolves SDK methods to routes
[probe_wire_stable-2026.2.py](_rewrite/probe_wire_stable-2026.2.py)local stub of Keystone and the version documents; runs each command through the real shell
[probe_lab_stable-2026.2.py](_rewrite/probe_lab_stable-2026.2.py)same commands against a lab on 127.0.0.1, recording every exchange; masks secrets, stores neither tokens nor stdout
[capture_unit_stable-2026.2.py](_rewrite/capture_unit_stable-2026.2.py)runs the unit tests with a recorder
[build_contract_stable-2026.2.py](_rewrite/build_contract_stable-2026.2.py)merges everything

The generic rewrite_pack.py and conformance_capture.py were not used: they expect a service or functional tests running in-process, while the OSC functional tests need a live cloud (openstackclient/tests/functional/base.py:1).

Versions: python-openstackclient 10.3.1, osc-lib 4.7.0, cliff 4.16.0, openstacksdk 4.20.0, keystoneauth1 5.17.0, Python 3.13.

To regenerate:

python -I -B _rewrite/extract_cli_stable-2026.2.py <repo> _rewrite
python -I _rewrite/probe_wire_stable-2026.2.py _rewrite/cli-commands_stable-2026.2.json <dir-404> --mode 404
python -I _rewrite/probe_wire_stable-2026.2.py _rewrite/cli-commands_stable-2026.2.json <dir-200> --mode 200
python -I -B _rewrite/capture_unit_stable-2026.2.py <repo> _rewrite/cli-commands_stable-2026.2.json <run-1>/c.json
python -I -B _rewrite/capture_unit_stable-2026.2.py <repo> _rewrite/cli-commands_stable-2026.2.json <run-2>/c.json
python -I -B _rewrite/build_contract_stable-2026.2.py . <dir-404>/wire-probe_stable-2026.2.json <dir-200>/wire-probe_stable-2026.2.json <run-1>/c.json <run-2>/c.json

Set HOME to an empty directory and unset OS_*, so that no real clouds.yaml is read. The probe targets 127.0.0.1 only and never runs server ssh.

2. What the CLI requires from a service

These apply to every OpenKeel service that must work with an unmodified openstack command.

#RequirementWhySource
1the Keystone token holds a catalog with public endpoints, under the service types the CLI asks forotherwise public endpoint for … not found[observed], openstackclient/object/client.py:32
2block storage may register as block-storage, volume, volumev3, volumev2 or block-storeavailability checks try all fiveopenstackclient/common/clientmanager.py:171
3compute and volume version documents hold version, min_version, status, linksthe SDK reads the range before any call[observed] with the stub
4network and image answer version discovery at the catalog URL; image and identity may use 300discovery[observed]
5OpenStack-API-Version: compute <v> and volume <v> are honoured from the minimum up to the announced maximumthe SDK sends its own maximum per resource, and users can pin 2.1[observed], 03
6announcing a microversion means implementing itgates call supports_microversion and then send the matching fields; 55 compute and 29 volume values are checkedopenstacksdk:openstack/utils.py:242
7GET /resource/{name} answers 404 for an unknown ID or a name, never 400 or 500this triggers the name lookup[observed], osc-lib:osc_lib/utils/__init__.py:204
8the filtered list works: ?name= on the list of network and identity, and on /detail for compute and block storagesecond step of the lookup[observed]
9list responses always hold the collection key, even when emptyotherwise the SDK raises KeyError[observed]
10POST /servers/{id}/action and POST /volumes/{id}/action accept the action in the body keyserver and volume actions[observed] for the route, unit vectors for the arguments
11Nova keeps os-agents, os-hosts, os-security-groups, os-networks and os-floating-ips at microversion 2.1, or the commands that use them faillegacy calls pinned to 2.1openstackclient/api/compute_v2.py:68
12Neutron exposes GET /extensions and GET /extensions/{alias}feature detection, for example external-gateway-multihoming[observed]
13Swift keeps AUTH_<id> as the last URL segment, and sends Etag and X-Trans-Idthe CLI displays themopenstackclient/api/object_store_v1.py:532
14error bodies keep the shape of the original servicethe SDK extracts the text shown to the user[inferred]
15GET / of each service answers version dataversions show[observed], openstackclient/common/versions.py:82

Which requests a command sends

Read observed_requests first, then static_requests. Static routes follow the SDK source, and are accurate for the method and base path, but:

  • the fallback of find_* is written ?name=, while compute and block storage use /detail?name=;
  • lists on compute, block storage and flavors really go to /detail;
  • 1,562 of the 1,948 observed requests match a static route exactly; the other 386 are those variants, discovery of extensions, and SDK-internal calls such as project cleanup.

3. Coverage of OpenKeel services

Operations used by the CLI, from [api-surface_stable-2026.2.md](_rewrite/api-surface_stable-2026.2.md):

Service typeOpenKeel serviceOperationsNote
networkopenkeel-neutron293including the 100 extension commands
block-storageopenkeel-cinder151
identityopenkeel-keystone150includes keystoneclient calls
computeopenkeel-nova136
imageopenkeel-glance68
object-storeSwift replacement13

Result on the OpenKeel lab

Run on 2026-11-01 against the core profile of openkeel-lab, with images built from the local working trees, the compute agent enabled by labelling the node (openkeel-lab:components/nova/kustomization.yaml:3), fake compute and Cinder drivers, and no object store. 657 commands ran; project cleanup, user password set and server ssh were excluded. Details are in the compatibility table.

Outcome of the last runCommandsReading
Succeeded219the CLI works end to end on these
501 from the service28declared gaps: subnet pool, address scope, network qos policy, network trunk, network segment, host, usage, console access, server add port and server add security group, volume group types, consistency groups
HTML 404 from Keystone27routes absent, not declared: federation, endpoint groups, limits, policies, mappings
404 from a service173inconclusive; includes the 97 Neutron extension commands (bgpvpn, fwaas, vpnaas, taas, dynamic routing)
Other210mostly the probe: missing parent resources, resource state, or no object store

Requirements of § 2 confirmed on the lab: discovery of every deployed service (1, 3, 4), microversions up to compute 2.100 and volume 3.71 (5), 404 then name lookup, including /detail (7, 8), list keys (9), and server actions (10). A real cycle server create --wait reached ACTIVE, then server delete --wait succeeded. No 5xx other than 501, and no pod restart.

Findings for OpenKeel:

  1. Keystone is inconsistent with the other services: an unimplemented operation gives an HTML 404 there (openkeel-keystone:internal/api/web.go:243), where Nova, Neutron and Cinder give an explicit 501. The user reads "not found" rather than "not implemented", and identity provider create fails the same way.
  2. **Neutron extensions answer The resource could not be found**, which is what Neutron does for an extension that is not loaded [inferred], so the absence is visible only through GET /extensions.
  3. **server add port and server add security group are 501**, although they are common day-to-day operations.
  4. **GET /v3/ on Cinder answers 401 without a token** [lab]; the CLI copes with it. Whether the Python Cinder does the same was not checked.

This covers the lab's fake drivers only, and says nothing about real VMs or volumes.

4. For a rewrite of the CLI itself

TopicRuleSource
Namesserver_add_fixed_ip → server add fixed ip; scripts depend on these and on argument namespyproject.toml:61
Exit codes0, 1, 2 as in 02; do not copy the defects in its § 4cliff:cliff/command.py:196
Unknown commandmessage on stdout, exit 2, with fuzzy suggestionscliff:cliff/app.py:363
Version selectioncommands depend on the major API versionopenstackclient/shell.py:150
Pluginsexternal Python plugins cannot be loaded by a non-Python rewrite; about 20 known plugins would need replacingopenstackclient/common/clientmanager.py:273
Formatterstable, json, yaml, csv, value, -c, --sort-columncliff
Configurationclouds.yaml, secure.yaml, OS_*, with the precedence of openstack.configosc-lib:osc_lib/shell.py:217
Authenticationevery keystoneauth plugin (password, v3applicationcredential, v3oidc…, admin_token)openstackclient/shell.py:81
Prompts--password-prompt, project cleanup confirmationopenstackclient/identity/v3/user.py:303, openstackclient/common/project_cleanup.py:33

Recommendation: keep the Python CLI and make the services compatible, rather than rewriting it. The surface is large (799 commands), most behaviour sits in openstacksdk, and plugins are Python.

5. Conformance: what was proved

EvidenceResultWhat it provesWhat it does not prove
Parser dump799 of 799 classes imported, 0 failuresthe catalogue is complete for the declared entry pointsruntime plugin commands
Unit tests with recorder3,250 of 3,250 pass, 3,109 vectors, in two independent runsargv → client calls, for 765 of 799 classesanything about a service, since the clients are mocks
Comparison of the two runs3,109 of 3,109 vectors have the same test, call sequence and statusmethod sequences are deterministicargument values holding random IDs were not compared
Lab probe657 commands, 730 runs, 2,795 requests; 219 succeedthe CLI works against the Go services on those commandsconformance with the Python services; anything needing real drivers
Wire probe on the stub661 commands run; 615 sent a request; 1,948 requests recorded across both modes; 441 exit 0 in {} modereal routes, headers and negotiation up to the first errorlater steps; behaviour with real data

The unit vectors were not replayed against a rewrite of the CLI, since none exists, and nothing was compared with a Python OpenStack. The usual "replay is mandatory" bar is therefore not met for service behaviour, and the lab run shows compatibility, not identical behaviour. The mocks also hide real defects: the first 4 defects of 02 § 4 all pass their unit tests. This is the main limit of the pack.

To close the gap: run probe_lab_stable-2026.2.py against a devstack on stable/2026.2 and compare it with wire-lab_stable-2026.2.json, or run openstackclient/tests/functional against both.

Classes without a unit vector (34)

EC2 credentials (8, v2 and v3); dynamic routing bgp_dragent, bgp_peer, bgp_speaker (8); object and container set, unset, save and account (9, of which the API wrapper has its own tests); BGPVPN router associations (3); ShowVersions, SetServerVolume, ShowEndpointGroup, ShowVolumeAttachment, ShowVolumeGroup, ShowVolumeGroupSnapshot. The list is printed by build_contract_stable-2026.2.py.

6. Gaps found while building the pack

  • The stub stops multi-step commands early; 337 of the 815 operations are static only.
  • 46 commands sent nothing, because of synthetic arguments, or because they make no calls (token issue, catalog list).
  • Non-default groups (identity.v2, image.v1, volume.v2) were not probed.
  • share.v2 code exists, is unit-tested, and is never loaded (openstackclient/shell.py:43). It should not be counted as user-facing surface.
  • Identity is in transition: 144 calls still go through keystoneclient, against 562 through the SDK.

7. Non-functional requirements

  • Latency: each invocation authenticates and reads discovery documents, so Keystone and the version endpoints are on the hot path. A script of N commands sends N token requests.
  • Payload size: object create and image create stream; services must not buffer the body in memory.
  • Listing cost: server list also lists images and flavors unless told otherwise (openstackclient/compute/v2/server.py:3142).

8. Checklist for an OpenKeel service

  • [ ] The service is in the Keystone catalog under the expected type, with a public endpoint.
  • [ ] Version discovery answers at the catalog URL, with min_version and version where microversions apply.
  • [ ] Every announced microversion is implemented; the oldest pinned ones (2.1) still work.
  • [ ] GET /resource/{name} gives 404; the name filter works, on /detail where applicable.
  • [ ] Empty lists keep their collection key.
  • [ ] Error bodies match the original.
  • [ ] Each operation of api-surface_stable-2026.2.md for the service is implemented, or its absence is documented.
  • [ ] An unimplemented operation answers 501 with the service's error body, not a 404.
  • [ ] The probe, pointed at the lab, gives the same routes and an exit code of 0 with real resources.

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