Client contract (rewrite pack) — python-manilaclient — branch stable/2026.2
Branch
stable/2026.2@d18a7e966(6.3.0+ 2 maintenance commits) — repository https://opendev.org/openstack/python-manilaclient — analysed on 2026-11-01
This pack has two uses:
- **Keep
openstack shareworking against the OpenKeel Manila service in Go**, by stating what the client requires. - Re-implement the plugin, thanks to the machine-readable catalogue.
No Manila ran. Everything observed was against a local stub on 127.0.0.1, which answers version discovery, a Keystone token, and fixed bodies. It shows what the client sends and how it reacts. It says nothing about what a real service does, and no OpenKeel or Python Manila was contacted. See § 5.
1. Contents of _rewrite/
| File | Content | Use |
|---|---|---|
[cli-commands_stable-2026.2.json](_rewrite/cli-commands_stable-2026.2.json) | 154 commands: class, purpose, 603 arguments (names, type, default, choices, required, exclusive groups, help), client calls with file:line, gates, error sites | generate a parser; compare argument names |
[client-contract_stable-2026.2.json](_rewrite/client-contract_stable-2026.2.json) | per command: client methods, observed requests with query and body keys, static requests with version ranges, gates, unit vector count, exit codes per probe, lowest working version | requirements per command |
[api-surface_stable-2026.2.md](_rewrite/api-surface_stable-2026.2.md) | 232 operations per service, with source, version range, experimental flag, keys, commands | gap analysis of a service |
[manager-routes_stable-2026.2.json](_rewrite/manager-routes_stable-2026.2.json) | 288 variants of 187 manager methods: range, file:line, method, route, body sent with synthetic arguments | the routes of every microversion, also those the probe does not reach at 2.98 |
[unit-vectors_stable-2026.2.json](_rewrite/unit-vectors_stable-2026.2.json) | 573 vectors: argv → ordered manager calls with arguments, normalised to {uuid} | test argument mapping without Python |
[wire-probe-404_stable-2026.2.json](_rewrite/wire-probe-404_stable-2026.2.json) | each command against a stub answering 404 with a fault body | error path |
[wire-probe-200_stable-2026.2.json](_rewrite/wire-probe-200_stable-2026.2.json) | each command against a stub answering 200 {} | what breaks when keys are missing |
[wire-probe-shaped_stable-2026.2.json](_rewrite/wire-probe-shaped_stable-2026.2.json) | each command against a stub answering a body holding every key, with options added so that set commands send something | real routes and bodies at 2.98 |
[wire-probe-all-options_stable-2026.2.json](_rewrite/wire-probe-all-options_stable-2026.2.json) | same, with every optional argument at once | query keys, body keys, keyword mismatches |
[version-sweep_stable-2026.2.json](_rewrite/version-sweep_stable-2026.2.json) | exit code per command and per microversion 2.0 to 2.98, on the shaped stub | where a command stops working |
[defects-observed_stable-2026.2.json](_rewrite/defects-observed_stable-2026.2.json) | 60 targeted cases: argv, stub setting, exit, stderr, requests, and one sample of headers | reproducible evidence for 02 |
[manila-route-check_stable-2026.2.md](_rewrite/manila-route-check_stable-2026.2.md) | client routes against Manila's declared routes and actions | static cross-check |
| Script | Role |
|---|---|
[extract_plugin_stable-2026.2.py](_rewrite/extract_plugin_stable-2026.2.py) | imports the entry points, dumps parsers, walks take_action. Adapted for api_version comparisons, matches, and methods called on a Resource |
[manager_routes_stable-2026.2.py](_rewrite/manager_routes_stable-2026.2.py) | new: calls each manager variant on a fake HTTP client |
[probe_plugin_stable-2026.2.py](_rewrite/probe_plugin_stable-2026.2.py) | the stub and the probe. Adapted: Manila version document, shaped mode, --api-version, PROBE_ALL_OPTIONS, reset of the client cache |
[capture_plugin_stable-2026.2.py](_rewrite/capture_plugin_stable-2026.2.py) | runs the OSC unit tests with a recorder |
[defects_stable-2026.2.py](_rewrite/defects_stable-2026.2.py) | targeted cases on the same stub |
[static_scan_stable-2026.2.py](_rewrite/static_scan_stable-2026.2.py) | return values of command.Command, unknown parsed_args attributes |
[compare_manila_stable-2026.2.py](_rewrite/compare_manila_stable-2026.2.py) | static comparison with the Manila code |
[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. The functional tests here need a live cloud (manilaclient/tests/functional/osc/base.py:24).
Versions: python-manilaclient 6.3.1.dev2 at this commit (the installed copy was compared with the clone: identical), python-openstackclient 10.3.1, osc-lib 4.7.0, cliff 4.16.0, keystoneauth1 5.17.0, openstacksdk 4.20.0, Python 3.13.
To regenerate (HOME set to an empty directory, OS_* unset):
python -I -B _rewrite/extract_plugin_stable-2026.2.py <repo> python-manilaclient manilaclient _rewrite
python -I -B _rewrite/manager_routes_stable-2026.2.py <repo> <mr.json>
python -I -B _rewrite/probe_plugin_stable-2026.2.py _rewrite/cli-commands_stable-2026.2.json <dir> --mode 404 # then 200, shaped
PROBE_ALL_OPTIONS=1 python -I -B _rewrite/probe_plugin_stable-2026.2.py _rewrite/cli-commands_stable-2026.2.json <dir> --mode shaped
python -I -B _rewrite/probe_plugin_stable-2026.2.py _rewrite/cli-commands_stable-2026.2.json <dir> --mode shaped --api-version 2.N # for N in 0..98
python -I -B _rewrite/capture_plugin_stable-2026.2.py <repo> _rewrite/cli-commands_stable-2026.2.json <run-N>/c.json <repo>/manilaclient/tests/unit/osc/v2 <repo> # twice
python -I -B _rewrite/defects_stable-2026.2.py _rewrite/probe_plugin_stable-2026.2.py <defects.json>
python -I -B _rewrite/build_contract_stable-2026.2.py . <gen-dir> <404> <200> <shaped> <all-options> <run-1> <run-2> <mr.json> <sweep-dir> <defects.json>
2. What the plugin requires from a service
| # | Requirement | Why | Evidence |
|---|---|---|---|
| 1 | The Keystone catalog holds shared-file-system or sharev2, with a public endpoint | endpoint lookup | [observed] catalog-*, manilaclient/osc/plugin.py:41-55 |
| 2 | The endpoint path has /vN, and the path before it answers version discovery | _get_base_url; without /vN, discovery goes to the host root | [observed] override-unversioned, manilaclient/common/httpclient.py:70-83 |
| 3 | Discovery answers {"versions": [{"status": "CURRENT", "version": "2.x", "min_version": "2.y", ...}]}, with a status 2xx. Authenticated requests are sent to it | without it, every command fails | [observed] disc-*, manilaclient/api_versions.py:244-261; shape of manila:manila/api/versions.py:42 |
| 4 | X-OpenStack-Manila-API-Version selects the microversion, from the minimum up to the maximum announced. OpenStack-API-Version: shared-file-system is also sent, and can be ignored | the client asks 2.98 by default and pins lower values on request | [observed] headers-sample, manila:manila/api/openstack/wsgi.py:226 |
| 5 | Announcing a microversion means implementing it | 86 gate sites and every wraps variant trust it | manilaclient/api_versions.py:444-460 |
| 6 | Accept X-OpenStack-Manila-API-Experimental: true on any route, sticky once sent | a later request in the same process keeps it | [observed], manilaclient/api_versions.py:413-414 |
| 7 | GET /<resource>/{name} answers 404 for an unknown ID or name | triggers the name lookup; for osc_lib lookups, any other error also falls to the listing | [observed], osc-lib:osc_lib/utils/__init__.py:234 |
| 8 | Lists accept all_tenants=1, all_tenants=True, is_public=True, and honour is_soft_deleted=True | a service that ignores the filter breaks name lookup from 2.69 | [observed] name-lookup, name-lookup-nofilter |
| 9 | Lists always hold the collection key, also when empty; shows hold the resource key | otherwise a bare key name is printed | [observed] list-no-key, show-no-key |
| 10 | Answers hold the fields of 02 § 5: size, required_extra_specs, extra_specs, is_public, backend_details, share_type_access:is_public | client code reads them | [observed] |
| 11 | Error bodies: JSON, Content-Type: application/json, first key holds message; never {} | the message shown to users, and {} crashes from_response | [observed] error-*, manilaclient/common/apiclient/exceptions.py:505 |
| 12 | x-openstack-request-id on errors | printed as Request-ID | manilaclient/common/apiclient/exceptions.py:485 |
| 13 | POST /<resource>/{id}/action takes the action in the body key; both os- names below 2.7 and plain names | the managers can send 35 action names, of which 18 were seen through commands at 2.98 | [observed], [manila-route-check](_rewrite/manila-route-check_stable-2026.2.md) |
| 14 | allow_access answers {"access": {...}} with id | the client reads it directly (manilaclient/v2/shares.py:968) | [observed] |
| 15 | metadata, extra_specs and group_specs filters arrive as a Python dict repr in the query string, and must be parsed as such | {'k': 'v'}, URL-encoded | [observed] access-list-filters, manila:manila/api/v2/shares.py:1526 |
| 16 | Query values are never False, 0 or empty, since the client drops them | a filter on is_public=False is impossible through this client | manilaclient/base.py:229 |
| 17 | migration_check always answers a non-empty body | an empty one starts a migration | [observed] server-migration-check-empty |
| 18 | Strings "True" and "False" are accepted for the boolean fields of migrations | the options are choices=['True', 'False'] | [observed], manilaclient/osc/v2/share_servers.py:684-687 |
| 19 | reset_task_state with null is accepted for admins, and a 403 for others is expected on every share set | defect 1 | [observed], [inferred] for the 403 |
| 20 | Routes keep their odd names: /share_instances, /shares/{id}/export_locations, /snapshot-instances, /types/{id}/share_type_access, /share-access-rules | no aliasing in the client | [observed] |
3. Microversion coverage
- Range: client 2.0 to 2.98. Manila on this branch goes to 2.99 (
manila:manila/api/openstack/api_version_request.py:229). The fieldsnapshot_inherit_share_access_supportof 2.99 is not requested by this client, and the CLI refuses--os-share-api-version 2.99. - Gates: 86 sites in the commands, on 43 microversions. Their list is in
client-contract_stable-2026.2.json. - Variants: 288 across 187 manager methods.
- Minimum for a useful client: shares, types, and snapshots mostly work from 2.0 to 2.9. Backups need 2.80, locks 2.81, transfers 2.77, QoS 2.94, groups 2.31, snapshot export locations 2.32. The version sweep gives this per command.
OpenKeel: openkeel-chart, openkeel-lab and the OpenKeel Manila service were not read or contacted for this pack. Whether a Go Manila exists at the announced version must be checked separately, starting from [api-surface_stable-2026.2.md](_rewrite/api-surface_stable-2026.2.md).
4. For a rewrite of the plugin itself
| Topic | Rule | Source |
|---|---|---|
| Names | share_access_create → share access create; scripts depend on these and on argument names | pyproject.toml:60 |
| Aliases | many options have two spellings, such as --resource-action and --resource_action | manilaclient/osc/v2/resource_locks.py:70 |
| Name resolution | keep the behaviour, not the two implementations | 03 § 5 |
| Exit codes | 0, 1, 2 as in 02 § 1; do not copy the defects of its § 4 | |
| Formats | table strings differ from JSON structures | manilaclient/osc/v2/share.py:828-832 |
| Plugin form | a non-Python rewrite cannot load through OSC entry points; it would replace openstack share | pyproject.toml:36-37 |
Recommendation: keep the Python plugin, make the service compatible, and propose defects 1 to 12 upstream. The surface is 154 commands with 288 manager variants, and the OSC shell cannot host a non-Python plugin.
5. Conformance: what was proved
| Evidence | Result | What it proves | What it does not prove |
|---|---|---|---|
| Parser dump | 154 of 154 classes imported, 0 failures | the catalogue is complete for the declared entry points | |
| OSC unit tests with recorder | 573 of 573 pass, 573 vectors, in two independent runs | argv → manager calls for 153 of 154 classes | anything about a service: the clients are mocks |
| Comparison of the two runs | 573 of 573 vectors have the same test, call sequence and status | sequences are deterministic | argument values were not compared |
| Other unit tests of the repository | 1,433 tests in total, including those 573: 0 failures, 0 errors, 0 skipped | manager code against a fake HTTP layer (manilaclient/tests/unit/fakes.py) | a real service |
Wire probe, 404 and {} | 154 of 154 commands send at least one request; 269 and 286 requests | routes, headers and negotiation up to the first failure | later steps |
| Wire probe, shaped | 154 of 154 send a request, 154 exit 0; 348 requests | the client's path to the end, against a body it accepts | that Manila answers this way |
| All options | 149 of 154 send a request; 141 exit 0; of the 13 others, 2 are defects (8, 9), and the rest are synthetic values refused locally | query and body keys | combinations |
| Version sweep | 99 versions × 154 commands | lowest working version; defects 4, 6, 7 | versions on a real service |
| Route check | 134 of 139 routes and 35 of 35 actions found in Manila's declarations | names agree | bodies, status codes, behaviour |
| Functional tests | not run: they need a cloud |
The usual "replay is mandatory" bar is not met for service behaviour. No vector was replayed against a rewrite, and nothing was compared with a running Python Manila. The mocks also hide real defects: defects 1 to 12 of 02 § 4 all pass the unit tests, except defect 11, which has none.
To close the gap: run the probe's argv from wire-probe-shaped_stable-2026.2.json against a devstack on stable/2026.2 with real resources, or run manilaclient/tests/functional/osc against both implementations.
Class without a unit vector (1): manilaclient.osc.v2.share:RestoreShare.
6. Gaps found while building the pack
- 55 of the 232 operations are static only. Most are variants below 2.98, such as
/os-services, or manager methods no command reaches. - 8 static client methods do not resolve, because they are
Resourcemethods orto_dict:set_keys,unset_keys,to_dict,share_access_rules.list. The wire probe shows the first ones, for examplePOST /types/{id}/extra_specs. manager_routescalls each method with its required arguments only, so optional body fields are not all inmanager-routes.wire-probe-all-optionscovers them at 2.98 only.- The extractor's static call list follows variables assigned from
find_resourceandget, not attributes passed through helpers. The unit vectors cover that. - The route check uses Manila's generated OpenAPI. Its 5 misses are
/services/enableand/services/disable, which it stores as/services/{id};/os-services/*likewise; and/types/{id}/os-share-type-accessbelow 2.7.
7. Non-functional requirements
- Latency: each invocation authenticates and reads discovery. Keystone and
GET /of Manila are on the hot path. - Listing cost: a name lookup lists the collection four times for shares from 2.69, twice elsewhere. A service must keep
GET /shares/detailfast withall_tenants=1. - Polling: 1
GETevery 5 s per waiting command, without a limit for status waits.
8. Checklist for an OpenKeel Manila service
- [ ] Catalog entry
shared-file-systemorsharev2, withpublic, and/v2in the URL path. - [ ] Version document at the base URL, with a
CURRENTentry,version,min_version. - [ ]
X-OpenStack-Manila-API-Versionhonoured; every announced microversion implemented. - [ ] Experimental header accepted on every route.
- [ ]
GET /<resource>/{name}gives 404;all_tenants,is_public,is_soft_deletedhonoured on lists. - [ ] Empty lists keep their key; required fields present; errors in
{"<key>": {"code": n, "message": "…"}}, never{}. - [ ] Dict filters parsed from a Python repr.
- [ ]
migration_checknever answers empty. - [ ]
reset_task_statewithnullhandled as Manila does, since everyshare setsends it. - [ ] Each operation of
api-surface_stable-2026.2.mdimplemented, or its absence documented. - [ ] The argv of
wire-probe-shaped_stable-2026.2.json, pointed at the service with real resources, gives the same routes and exit 0.
Source: reverse-spec-python-manilaclient/07-rewrite-pack_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.