Rewrite pack — manila — branch stable/2026.2
Branch
stable/2026.2@4fbc4b4f3688— repository https://opendev.org/openstack/manila — analysed on 2026-10-29 Clients read: python-manilaclientstable/2026.2@d18a7e96667b(prefixpython-manilaclient:), manila-uistable/2026.2@f4dfaacb5658(manila-ui:), skyline-consolestable/2026.2@806d200392ee(skyline-console:), skyline-apiserverstable/2026.2@4af72bfd4577(skyline-apiserver:), cloud-provider-openstackv1.37.1(Manila CSI driver,cloud-provider-openstack:), gophercloudv2.15.0(gophercloud:), gophercloud-utils @b4759ea0529b(gophercloud-utils:).
This file is what a re-implementation (OpenKeel's openkeel-manila, Go, Kubernetes-native) needs beyond files 01–05: the machine-readable contracts of _rewrite/, the cross-cutting HTTP rules, the scheduler algorithm, the share-driver contract that a CSI-backed back end (Longhorn volumes behind per-tenant NFS gateways, DHSS=True) must satisfy, what the four clients rely on, and how to prove conformance.
1. Contents of _rewrite/
| File | Use in a rewrite |
|---|---|
[routes_stable-2026.2.json](_rewrite/routes_stable-2026.2.json) | 258 operations of the live v2 router: method, path, body action, handler file:line, microversion variants (with experimental), body/query/response schemas per variant, success code, policy rules and their source. The route table to implement. |
[routes-unimplemented_stable-2026.2.json](_rewrite/routes-unimplemented_stable-2026.2.json) | routes declared by mapper.resource without a controller method: answer 404 itemNotFound, do not implement |
[policy-operations-unmatched_stable-2026.2.json](_rewrite/policy-operations-unmatched_stable-2026.2.json) | 6 policy operations entries that match no route (documentation drift) |
[openapi_stable-2026.2.json](_rewrite/openapi_stable-2026.2.json) | OpenAPI 3.0.3, 181 paths / 259 operations; body actions as <path>#<action>; x-microversions (with experimental), x-schema-selection, x-query-schema-selection, x-response-schema-selection, x-policy, x-handler; Manila fault body as fault. Types and stubs, not the authority on behaviour |
[request-schemas_stable-2026.2.json](_rewrite/request-schemas_stable-2026.2.json) | 102 JSON schemas of manila/api/schemas/* (only part of the API uses them, 01 § 1.5) |
[api-ref_stable-2026.2.json](_rewrite/api-ref_stable-2026.2.json), [microversions_stable-2026.2.md](_rewrite/microversions_stable-2026.2.md) | 209 documented operations with parameters; 101 history entries (1.0, 2.0–2.99) |
[schema-sqlite-migrated_stable-2026.2.sql](_rewrite/schema-sqlite-migrated_stable-2026.2.sql), [schema-diff_stable-2026.2.md](_rewrite/schema-diff_stable-2026.2.md) | the database schema to use (alembic head afc3b5e1f406, 53 tables) and its 50 differences from the models |
[schema-mysql_stable-2026.2.sql](_rewrite/schema-mysql_stable-2026.2.sql), [schema-postgresql_stable-2026.2.sql](_rewrite/schema-postgresql_stable-2026.2.sql) | DDL generated from the ORM models (52 tables; one column patched, see header) — reference only |
[policies_stable-2026.2.json](_rewrite/policies_stable-2026.2.json), [policies_stable-2026.2.yaml](_rewrite/policies_stable-2026.2.yaml) | 230 default rules with check_str, scope types and operations; oslopolicy sample |
[config_stable-2026.2.conf](_rewrite/config_stable-2026.2.conf) | oslo-config-generator sample: 23 sections, 923 options (driver options included). Trim to the options of 07 § 6 |
[exchanges_stable-2026.2.json](_rewrite/exchanges_stable-2026.2.json) | 283 HTTP exchanges of 29 client scenarios against the real service (real scheduler and share managers, DHSS=False and DHSS=True), each tagged with its scenario, plus the scenario results — the differential-testing reference |
[rpc-frames_stable-2026.2.json](_rewrite/rpc-frames_stable-2026.2.json), [manager-errors_stable-2026.2.json](_rewrite/manager-errors_stable-2026.2.json) | 247 RPC frames as serialised on the bus, and the exceptions raised by casts during the scenarios |
[neutron-calls_stable-2026.2.json](_rewrite/neutron-calls_stable-2026.2.json) | 29 Neutron v2.0 requests made by manila-share for the DHSS=True back end (real NeutronNetworkPlugin, python-neutronclient, keystoneauth): what OpenKeel's Neutron must serve to Manila (§ 3.5.4) |
[response-schemas_stable-2026.2.json](_rewrite/response-schemas_stable-2026.2.json) / .md, [observed-errors_stable-2026.2.md](_rewrite/observed-errors_stable-2026.2.md) | response schemas inferred from the scenarios (80 operation/status pairs), observed errors with their messages |
[exchanges-unit-api_stable-2026.2.json](_rewrite/exchanges-unit-api_stable-2026.2.json), response-schemas-unit-api_*, observed-errors-unit-api_* | 119 exchanges recorded from 894 unit API tests (mocked share API: HTTP-layer evidence only) |
[replay-report_stable-2026.2.json](_rewrite/replay-report_stable-2026.2.json) | proof that the scenarios are reproducible (§ 7) |
[tools/](_rewrite/tools/) | manila_routes.py (route/schema/policy extraction), postprocess.py, def_lines.py, table01.py, gen_ddl.py, migrate_sqlite.py, schemadiff.py, adapter_manila.py (scenarios), adapter_manila_http.py (unit API capture) |
Library versions of the extraction venv (Python 3.12.7, upper-constraints of requirements stable/2026.2): oslo.messaging 18.3.1, oslo.db 18.1.1, oslo.policy 6.0.1, oslo.config 10.7.0, oslo.service 4.8.0, oslo.middleware 8.2.0, oslo.concurrency 7.6.1, keystonemiddleware 13.0.1, keystoneauth1 5.17.0, SQLAlchemy 2.0.52, alembic 1.19.1, WebOb 1.8.11, Routes 2.5.1, jsonschema 4.26.0, tooz 9.1.0, castellan 5.8.0, python-manilaclient 6.3.1.dev2 (local stable/2026.2).
How the pack was produced: rewrite_pack.py --routes manila_routes:ROUTES --policies manila.policies:list_rules --config-generator etc/oslo-config-generator/manila.conf --policy-generator etc/manila/manila-policy-generator.conf --api-ref api-ref/source --version-history manila/api/openstack/rest_api_version_history.rst --schemas-modules 'manila.api.schemas.*', then tools/postprocess.py. Its models extractor fails on a model defect (VARCHAR requires a length, manila/db/sqlalchemy/models.py:1471): tools/gen_ddl.py replaces it.
2. HTTP behaviour that the contracts do not show
2.1 Middleware pipeline (outermost first)
cors → request_id → faultwrap → http_proxy_to_wsgi → sizelimit → osprofiler → authtoken → keystonecontext → router (etc/manila/api-paste.ini:11-16). Consequences:
faultwrapis outsideauthtoken: a crash in keystonemiddleware becomes a ManilacomputeFault500, but a missing/invalid token gives keystonemiddleware's 401 body{"error": {"code": 401, "title": "Unauthorized", "message": …}}withWWW-Authenticate: Keystone uri=….- 401 wins over everything (version negotiation, routing, body parsing happen in the router).
- In the router: project-id regex routing (plain webob 404 when nothing matches) → microversion header parsing (400 / 406,
manila/api/openstack/wsgi.py:732-746) → body parsing (400 "Malformed request body") →{project_id}vs token check (400 "Malformed request url",manila/api/openstack/wsgi.py:812-816) → handler (policy 403, validation 400, version range 404). /(version document, 300) is outsideauthtoken;/v2/is inside (01 § 1.1).
2.2 Error body
{"<faultName>": {"code": N, "message": "…"}} (01 § 1.6, manila/api/openstack/wsgi.py:1294-1335). Fault responses also carry X-OpenStack-Manila-API-Version, Vary and, when requested, X-OpenStack-Manila-API-Experimental (manila/api/openstack/wsgi.py:1322-1331). Non-safe exceptions expose no message (02 § 1). The texts that clients parse are in § 4.
2.3 Other cross-cutting rules
- Default microversion 2.0 when the header is absent; no
latest; range 2.0–2.99; experimental header (01 § 1.3). - Every path exists with and without
/{project_id};{project_id}must match[0-9a-f\-]+by default (01 § 1.2) — OpenKeel Keystone project ids must be hex/UUID-like, orproject_id_regexmust be widened. - Body actions: exactly one key;
os-spellings until 2.6 (01 § 1.4). - Statuses are not uniform:
POST /sharesandPOST /shares/manageanswer 200,POST /snapshots, transfers, replicas and most actions 202,allow_access200,DELETE /resource-locks/{id}204, most deletes 202 (§ 3 of 01, "Observed" column). - Lists:
limit≤osapi_max_limit(1000),offset,marker,sort_key/sort_dir;<resource>_linkswithmarkeronly whenlimitis reached; adminall_tenants=1silently ignored for others. - Response-body validation is active in production and may turn a committed write into a 400 (02 § 4): do not reproduce.
3. Core algorithms
3.1 Scheduler (FilterScheduler): input and validation
Input: the request spec built by share_api.create_instance (manila/share/api.py:1268-1367): share_properties, share_instance_properties, share_type (with extra_specs), share_proto, snapshot_id, availability_zone_id / availability_zones, share_group, scheduler_hints, snapshot_host, active_replica_host. Normalisation (manila/scheduler/drivers/filter.py:134-226):
- no share type → error NO_DEFAULT_SHARE_TYPE (
manila/scheduler/drivers/filter.py:146-160); - boolean extra specs without an operator become
<is> <value>(manila/scheduler/drivers/filter.py:162-168); storage_protocoldefaults to<in> <share_proto>(manila/scheduler/drivers/filter.py:170-175);- creating from a snapshot or a replica restricts to the replication domain of the source host and drops
share_backend_name(manila/scheduler/drivers/filter.py:180-200).
3.2 Formulas
For a pool with total_capacity_gb T, free_capacity_gb F, reserved percentage r (reserved_percentage, or reserved_snapshot_percentage when creating from a snapshot, or reserved_share_extend_percentage for an extend), requested size S (manila/scheduler/filters/capacity.py:34-130):
F == "unknown"→ passes;T == "unknown"→ passes iffr == 0 and S <= F;T <= 0→ fails;free = floor(F − T·r);- thin provisioning (share type
thin_provisioning/ poolthin_provisioningtrue) withmax_over_subscription_ratioR ≥ 1: fails if(provisioned_capacity_gb + S) / T > R, else passes ifffree · R >= S(manila/scheduler/filters/capacity.py:99-119); R < 1 → fails; - otherwise passes iff
free >= S(manila/scheduler/filters/capacity.py:127). Capacity weight (manila/scheduler/weighers/capacity.py:53-96): unknown → −inf/+inf (by sign of the multiplier); thin:floor(T·R − provisioned − T·r); thick:floor(F − T·r); multiplied bycapacity_weight_multiplier(1.0; negative stacks instead of spreading).
3.3 Algorithm
- Host states: every
manila-shareservice of the topic that is up (updated_atwithinservice_down_time) and not disabled (unless an adminonly_hosthint), with the pools of its last capability report (manila/scheduler/host_manager.py:656-720). None →WillNotSchedule+ SHARE_BACKEND_NOT_READY_YET (manila/scheduler/drivers/filter.py:243-262). - Filters, in order
scheduler_default_filters(manila/scheduler/host_manager.py:40-52): OnlyHost, AvailabilityZone, Capacity, Capabilities (extra specs matched with the operators=(≥),==,!=,>=,<=,s==,s!=,s<,s>,<in>,<is>,<or>,<all-in>,manila/scheduler/filters/extra_specs_ops.py:25-80;capabilities:prefix stripped; scope keys ignored), Driver (filter_functionevaluated), ShareReplication, CreateFromSnapshot, Affinity / AntiAffinity (2.65 hints). Each filter runs over the remaining pools (manila/scheduler/filters/base.py:67-90); the last filter that removed everything is reported (NoValidHost, detail code per filter). - Weighers
scheduler_default_weighers= Capacity, Goodness (goodness_function), HostAffinity (manila/scheduler/host_manager.py:53-59): each weigher's raw weights are normalised to [0, 1] over the candidates (all equal → 0), multiplied by its multiplier and summed; candidates are sorted by total weight descending (manila/scheduler/weighers/base.py:25-142). - Best = first;
consume_from_sharedecrements its free capacity in memory (manila/scheduler/host_manager.py:429-450); writehost@backend#pool; cast to the share host. - Retry:
scheduler_max_attempts(3,manila/scheduler/drivers/base.py:35) is tracked infilter_properties['retry']with the hosts already tried (manila/scheduler/drivers/filter.py:290-345); the share manager does not reschedule on driver failure on this branch, so in practice one attempt.
3.4 Ordering and determinism
Ties after normalisation keep the order of the host-state dictionary (insertion order of service records): a rewrite must not rely on it; tests should give pools distinct capacities. Capacity numbers are only as fresh as the last capability report (periodic_interval).
3.5 Share-driver contract for a CSI-backed back end (OpenKeel, DHSS=True)
Model chosen: DHSS=True. Manila's DHSS=False mode puts every project on the same NFS endpoints and isolates tenants only by client-IP rules. With Longhorn's RWX share-manager (one NFSv4 server per volume in longhorn-system, reached through a ClusterIP Service on the pod network, exporting to every client with AUTH_SYS) this gives no network isolation at all, and ip rules become ambiguous when tenant VPCs reuse the same CIDRs. OpenKeel therefore implements the driver-handles-share-servers contract instead:
- a share server is created per share network subnet (per tenant network, per back end);
- it is an NFS gateway, attached only to the tenant's network;
- the CSI volumes sit behind it and are never exposed on the pod network.
This is what Manila does with its Generic driver (a Nova VM per share network) and with NetApp (an SVM per share network). Here the "VM" is a pod. Everything marked [inferred] is OpenKeel design, not Manila behaviour; it must be validated against the Longhorn, Kube-OVN and NFS-Ganesha releases in use.
3.5.1 Topology [inferred]
flowchart LR
subgraph tenantA[Tenant A VPC / Kube-OVN subnet 10.0.0.0/24]
vmA[KubeVirt VMs / pods of tenant A]
gwA[share server A: NFS-Ganesha pod, IP 10.0.0.50 (Neutron port device_owner manila:share)]
end
subgraph tenantB[Tenant B VPC / subnet 10.0.0.0/24 — same CIDR]
vmB[VMs of tenant B]
gwB[share server B: NFS-Ganesha pod, IP 10.0.0.50]
end
gwA -->|mounts| pvA1[(PVC share-1 RWO, Longhorn)]
gwA -->|mounts| pvA2[(PVC share-2 RWO)]
gwB -->|mounts| pvB1[(PVC share-3 RWO)]
vmA -->|NFSv4 10.0.0.50:/share-1| gwA
vmB -->|NFSv4 10.0.0.50:/share-3| gwB- Share server = one gateway pod (Deployment with one replica, or a StatefulSet if a stable identity is wanted), in a per-tenant namespace or in the Manila namespace with a tenant label. Its only data-path interface is a port on the tenant subnet. That port is the Neutron port Manila already allocates (
device_owner: manila:share,manila/network/neutron/neutron_network_plugin.py:140-251); the pod is attached to it through Kube-OVN (VPC subnet annotation, or Multus with the port's IP and MAC). It has no interface on the pod network, apart from the kubelet's management access. - Volumes: each share is a RWO PVC of the block-backed StorageClass, typically Longhorn. It is not RWX, so Longhorn's own NFS share-manager is not used. The gateway pod mounts it. Every share of a share server is therefore on the node of the gateway. Longhorn moves the attachment when the pod is rescheduled.
- Export: NFS-Ganesha (NFSv4.1,
SecType = sys, optionallykrb5with a Kerberos security service) exports/<share-id>with a per-export client list built from the access rules. Clients are resolved inside the tenant's network only, so10.0.0.5is unambiguous. - Defence in depth: in addition, a Kube-OVN/OVN ACL (or a NetworkPolicy on the gateway's tenant interface) admits only the CIDRs of the active access rules on TCP 2049. Because the gateway is in the tenant's L2/L3 domain only, a wrong export list cannot leak across tenants.
3.5.2 Entry points
The table lists what the share manager calls (Manila side), what it expects back, and the OpenKeel mapping [inferred].
| Entry point | Required behaviour (Manila side) | OpenKeel mapping [inferred] |
|---|---|---|
driver_handles_share_servers | True. Share types of this back end carry driver_handles_share_servers=True. The API refuses a share without share_network_id (400 "Share network must be set when the driver_handles_share_servers is true.", manila/api/v2/shares.py:247-251) unless it comes from a snapshot or a share group | constant |
get_network_allocations_number() | number of tenant-network ports to allocate per share server. Manila's network plugin creates them in the share network subnet before setup_server (manila/share/driver.py:928-936, manila/share/driver.py:953-962) | 1 (the gateway's NFS address) |
get_admin_network_allocations_number() | ports on an admin network (optional, manila/share/driver.py:939-940) | 0, or 1 if the operator wants a management/replication interface |
network plugin ([<backend>]/network_api_class) | NeutronNetworkPlugin by default (manila/network/__init__.py:27-30): creates Neutron ports (device_owner manila:share) and stores network_allocations rows (ip_address, mac_address, label: user) (manila/network/neutron/neutron_network_plugin.py:140-251) | keep it: OpenKeel's Neutron API (OVN / Kube-OVN) serves the ports; the gateway pod uses the port's IP and MAC |
_setup_server(network_info, metadata) | gets one network_info dict per subnet: server_id, neutron_net_id, neutron_subnet_id, network_allocations (IP, MAC…), admin_network_allocations, security_services, backend_details, network_type, segmentation_id, cidr (manila/share/manager.py:4716-4751). metadata holds request_host. Returns a dict stored in share_server_backend_details; its identifier key becomes the server's identifier (manila/share/manager.py:4786-4840). An exception marks the server error; detail_data.server_details is persisted for cleanup (manila/share/manager.py:4753-4785), and the ports are de-allocated | create the gateway pod (and its namespace / ServiceAccount) with the tenant port's IP and MAC on the tenant subnet. Wait until it is Ready and Ganesha answers. Return {"identifier": "<namespace>/<pod or statefulset name>", "pod": …, "ip": …}. On failure, put what was created in detail_data["server_details"] |
network_type / segmentation_id | the manager rejects inconsistent segmentation for flat, vlan, vxlan, gre (manila/share/manager.py:4852-4890). Other types (OVN geneve) pass unchecked | Kube-OVN/OVN subnets report geneve (or vlan for underlay subnets) |
security_services | LDAP / Kerberos / AD data of the share network are passed in network_info and copied to backend details (manila/share/manager.py:4805-4823); dhss_mandatory_security_service_association can make one mandatory per protocol (manila/share/manager.py:2311-2338) | optional: Kerberos (krb5, krb5i, krb5p) and LDAP id-mapping configured in Ganesha from the security service |
choose_share_server_compatible_with_share(context, share_servers, share, snapshot, share_group, encryption_key_ref) | pick an existing active server of the same subnet, or None to create a new one (default: the first one, manila/share/driver.py:985-1009). The manager serialises this per subnet with a lock (manila/share/manager.py:807-833) | the first active server below max_shares_per_share_server |
max_shares_per_share_server, max_share_server_size | capabilities used to stop reusing a server (manila/share/driver.py:151-160, manila/share/driver.py:356-366) | for example 50 shares / 10 TiB per gateway; tune to the gateway's CPU and memory |
_teardown_server(server_details, security_services) | delete everything setup_server created; called when a server is deleted, including by the periodic delete_free_share_servers for unused servers older than unused_share_server_cleanup_interval (manila/share/manager.py:4002-4022). Must tolerate partial server_details | delete the gateway pod and its namespace objects. The ports are released by the manager (deallocate_network) |
create_share(context, share, share_server) | create the share on that server. Return export locations server_ip:/path (strings or dicts with is_admin_only, metadata.preferred), synchronously (manila/share/manager.py:2340-2384) | create a RWO PVC (size share.size Gi, labels share id / instance id / server id); mount it in the gateway; add a Ganesha export /<share-id> with an empty client list; return ["<tenant port IP>:/<share-id>"], preferred: true |
create_share_from_snapshot(…, share_server, parent_share) | same, or the model update {"status": "creating_from_snapshot", …} finished by get_share_status (manila/share/manager.py:2352-2375, manila/share/driver.py:2967-2994) | PVC with dataSource: VolumeSnapshot, then the same export. Same share server: the snapshot stays on the parent's back end |
update_access(context, share, access_rules, add_rules, delete_rules, update_rules, share_server) | the per-rule error contract of manila/share/driver.py:817-911 (never raise for one bad rule; return {access_id: {"state": "error"}}) | rewrite the export's client list (ip rules → Clients = <cidr>, Access_Type = RW|RO) and reload Ganesha through D-Bus. Update the OVN ACL / NetworkPolicy. user / cert / cephx → error |
delete_share, extend_share, shrink_share, create_snapshot, delete_snapshot, manage_existing_with_server, unmanage_with_server | as in the generic contract (05, manila/share/driver.py:1061-1124) | remove the export, then the PVC; expand the PVC (allowVolumeExpansion) and grow the file system online; shrink → NotImplementedError; snapshots → VolumeSnapshot (crash-consistent, without quiescing the gateway) |
get_share_server_pools(share_server), _update_share_stats | pools per server (optional); stats with driver_handles_share_servers: True, max_shares_per_share_server, max_share_server_size (manila/share/driver.py:1395-1401) | one pool per StorageClass; capacities from Longhorn |
ensure_shares, get_backend_info | re-export after a restart (05) | check gateway pods and exports; recreate a missing export from the database |
| share server statuses | creating → active / error; deleting; unmanage_*; migration and network_change statuses (manila/common/constants.py:81-93) | migration and network change unsupported at first: report share_server_multiple_subnet_support: False, network_allocation_update_support: False, security_service_update_support: False |
3.5.3 Consequences for the API and the clients
- Projects must create a share network (
POST /share-networkswithneutron_net_id/neutron_subnet_idof their own network; subnets per availability zone from 2.51) and passshare_network_idonPOST /shares. Skyline and manila-ui already expose share networks. The Manila CSI driver takes it from the StorageClass parametershareNetworkID(cloud-provider-openstack:pkg/csi/manila/options/shareoptions.go:26). - The CSI driver must run where it can reach the gateway, that is in a cluster or VM attached to the same tenant network, which is the normal tenant use. The OpenKeel platform's own pods cannot mount tenant shares unless they are given an interface on that network.
- Share servers become user-visible objects: admin
GET /share-servers,reset_status,unmanage, plus the user messages NO_SHARE_SERVER, SHARE_NETWORK_PORT_QUOTA_LIMIT_EXCEEDED and NEUTRON_SUBNET_FULL (02 § 3). Every share server consumes one Neutron port of the tenant's quota. - Cost and availability: one gateway pod per (tenant subnet, back end) and per
max_shares_per_share_servershares. A gateway restart interrupts NFS I/O for its shares; NFSv4 clients recover with a grace period. Gateway HA (active/passive with a floating port) is a later step. - Single-tenant clusters can still use the DHSS=False mapping (Longhorn RWX share-manager directly) through a second back end with
driver_handles_share_servers = False, on share types restricted to trusted projects.
3.5.4 Verified behaviour and the Neutron contract
The scenarios dhss-share-network-required, dhss-share-server-lifecycle, dhss-two-tenants-same-cidr and dhss-neutron-failures run the real share manager with a DHSS=True back end (gamma, DummyDriver, get_network_allocations_number() = 2, one admin allocation from the standalone plugin) and record what happens:
- Share network required: no
share_network_id→ 400 "Share network must be set when the driver_handles_share_servers is true." - First share on a share network: share server
creating→active; two Neutron portsdevice_owner: manila:sharein the tenant subnet (10.0.0.50, 10.0.0.51), owned by the tenant's project; export locations on those IPs for members (the first ispreferred), plus the admin-network one (is_admin_only: true), visible to admins only. The server'sbackend_detailshold what_setup_serverreturned (server_id,service_ip,subnet_allocations,username), andidentifieris the server id. - Second share: the same server is reused, no new port.
- Members get 403 on
GET /share-servers(admin-only); deleting a share network still used by a server → 409. - Deleting the shares leaves the server
active(delete_share_server_with_last_shareis False by default).delete_free_share_serversremoves it once it has been unused for longer thanunused_share_server_cleanup_interval, deletes its Neutron ports, and the share network can then be deleted (202). - Two tenants with the same CIDR get two distinct share servers; both export on
10.0.0.5xaddresses of their own subnet. Another project naming a share network that is not its own gets 404. - Neutron failures: a port quota exceeded (409
OverQuota) → shareerror, servererror, user message action 002 / detail 027 SHARE_NETWORK_PORT_QUOTA_LIMIT_EXCEEDED, ports rolled back. **A full subnet (409IpAddressGenerationFailure) gives the same detail 027, not 033 NEUTRON_SUBNET_FULL**:IpAddressGenerationFailureClientis a subclass ofConflictin python-neutronclient 14.0.0, so the firstexcept NeutronClientExceptionwithstatus_code == 409turns it intoPortLimitExceeded, and the dedicated branch is unreachable (manila/network/neutron/api.py:194-201). See 02 § 4.
Requests manila-share sends to Neutron (neutron-calls_stable-2026.2.json), which OpenKeel's Neutron API must serve with the service user's token ([neutron] keystoneauth options, admin role):
| Request | When | What Manila reads or sends | Defined at |
|---|---|---|---|
GET /v2.0/extensions | before each allocation | the extension names Provider Network (mandatory, otherwise NetworkBadConfigurationException "Provider Network extension required") and Port Binding — not the aliases | manila/network/neutron/neutron_network_plugin.py:418-420, manila/network/neutron/api.py:278-288 |
GET /v2.0/networks/{id} | allocation | provider:network_type, provider:segmentation_id, mtu, router:external, or segments[] for multi-segment networks | manila/network/neutron/neutron_network_plugin.py:428-470 |
GET /v2.0/subnets/{id} | allocation | cidr, gateway_ip, ip_version | manila/network/neutron/neutron_network_plugin.py:428-470 |
POST /v2.0/ports | one per allocation | {"port": {"network_id", "fixed_ips": [{"subnet_id"}], "device_owner": "manila:share", "device_id": <share server id>, "name": "<server id>_<n>", "project_id": <tenant>, "admin_state_up": true}}; reads fixed_ips[].ip_address, mac_address, id. 409 → PortLimitExceeded | manila/network/neutron/api.py:139-217 |
GET /v2.0/ports?device_id=&device_owner=manila:share&network_id= | de-allocation | ports of a share server | manila/network/neutron/neutron_network_plugin.py:310-345 |
DELETE /v2.0/ports/{id} | de-allocation | 404 tolerated | manila/network/neutron/neutron_network_plugin.py:310-345 |
For OpenKeel the network-side requirements are: report Provider Network and Port Binding by name; create ports for another project when the caller is admin; and let the gateway pod take over the port's IP and MAC (Kube-OVN) [inferred].
Protocol scope: NFS only (enabled_share_protocols = NFS). The CSI driver's NFS adapter grants ip / rw per node and reads one export location (cloud-provider-openstack:pkg/csi/manila/shareadapters/nfs.go:40-104); its CephFS adapter (cephx) is out of scope.
3.6 Test cases
No separate algorithm vectors were recorded: the scheduler is exercised end-to-end by the scenarios (no-valid-host for the Capabilities filter, admin-views for pools_by_type, every create for capacity and weighing with two pools of identical capacity). A rewrite should add table tests from § 3.2 (unknown capacities, thin with R ≥ 1 and < 1, reserved percentages per operation).
3.7 Performance targets
Scheduling is in-memory and O(pools × filters). The costly parts in production are the DB round-trips of share_api.create (≈ 15 queries including quota FOR UPDATE) and the driver calls in manila-share, which run in the RPC worker: a CSI-backed create_share that waits for Bound must bound its wait (Manila has no RPC-side timeout) [inferred].
4. What the clients expect
| Expectation | Where the client relies on it |
|---|---|
GET /v2/ (authenticated) returns {"versions": [ … ]} with exactly one element carrying min_version, version (max), status, updated in RFC 3339 | gophercloud:openstack/sharedfilesystems/apiversions/results.go:60-77, cloud-provider-openstack:pkg/csi/manila/manilaclient/builder.go:74-110 |
The CSI driver sets microversion 2.37 for everything, 2.45 for GET /share-access-rules?share_id=, 2.65 for scheduler_hints | cloud-provider-openstack:pkg/csi/manila/manilaclient/builder.go:33, cloud-provider-openstack:pkg/csi/manila/manilaclient/client.go:79-82, cloud-provider-openstack:pkg/csi/manila/volumesource.go:49-58 |
Microversion header name X-OpenStack-Manila-API-Version (gophercloud, manilaclient, Skyline) | gophercloud:service_client.go:123-124, skyline-console:src/client/client/constants.js:107-110 |
Lookup by name: GET /shares/detail?name=<exact> and GET /snapshots/detail?name=<exact> return 0 or 1 element; > 1 is an error for the CSI driver | gophercloud-utils:openstack/sharedfilesystems/v2/shares/utils.go:1-40, cloud-provider-openstack:pkg/csi/manila/share.go:66-95 |
POST /shares answers 200 or 201 (gophercloud OkCodes); allow_access answers 200 only; deny_access 200 or 202; extend 202 | gophercloud:openstack/sharedfilesystems/v2/shares/requests.go:89-90, gophercloud:openstack/sharedfilesystems/v2/shares/requests.go:252-253, gophercloud:openstack/sharedfilesystems/v2/shares/requests.go:289-290, gophercloud:openstack/sharedfilesystems/v2/shares/requests.go:338-339 |
Share statuses creating, creating_from_snapshot, deleting, extending, available, error statuses error, error_deleting, extending_error; snapshot creating, available, error, deleting; rule states active, error (anything else = keep waiting) | cloud-provider-openstack:pkg/csi/manila/share.go:35-62, cloud-provider-openstack:pkg/csi/manila/snapshot.go:31-38, cloud-provider-openstack:pkg/csi/manila/shareadapters/accessrights.go:40-75 |
GET /messages?resource_id=&message_level=ERROR&limit=1&sort_dir=desc&sort_key=created_at works at 2.37, and detail_id codes 002, 003, 007, 008, 009 keep their meaning (mapped to gRPC codes) | cloud-provider-openstack:pkg/csi/manila/util.go:45-80, cloud-provider-openstack:pkg/csi/manila/util.go:133-141 |
Export locations: path, preferred, is_admin_only; non-admin, non-empty, preferred first; path server:export | cloud-provider-openstack:pkg/csi/manila/util/exportlocation.go:30-68, cloud-provider-openstack:pkg/csi/manila/shareadapters/util.go:24-35 |
manilaclient negotiates: GET <endpoint root> versions, picks the entry with status: CURRENT and uses min(requested, server max); client max 2.98 (one below the server) | python-manilaclient:manilaclient/api_versions.py:244-300, python-manilaclient:manilaclient/api_versions.py:32-33, python-manilaclient:manilaclient/osc/plugin.py:94-105 |
Errors are parsed as "first key of the body → its message" (faultstring fallback); x-openstack-request-id is shown; retry-after read on 413 | python-manilaclient:manilaclient/common/apiclient/exceptions.py:475-515 |
Service type sharev2 in the Keystone catalogue (manilaclient, manila-ui, Skyline); gophercloud looks for shared-file-system with aliases sharev2, share | python-manilaclient:manilaclient/common/constants.py:132, manila-ui:manila_ui/api/manila.py:31-33, skyline-apiserver:skyline_apiserver/config/openstack.py:144, gophercloud:endpoint_search.go:48 |
manila-ui pins microversion 2.87 and authenticates with the user's token against the catalogue URL (/v2/<project>) | manila-ui:manila_ui/api/manila.py:31, manila-ui:manila_ui/api/manila.py:75-100 |
Skyline pins 2.55 for every Manila call and uses /v2/<project>/… for shares, export locations, metadata, types, extra specs, share-group types (needs 2.55, not experimental), share instances, share networks and subnets, share groups, share-access-rules (2.45+), quota sets, share servers, scheduler-stats/pools/detail, availability zones | skyline-console:src/client/client/constants.js:107-110, skyline-console:src/client/manila/index.js:18-80 |
GET /types/{id}/extra_specs is admin-only by default: the CSI driver's GetExtraSpecs is not called on the provisioning path, keep it admin-only | scenario share-types; cloud-provider-openstack:pkg/csi/manila/manilaclient/client.go:139-141 |
Statuses that must not change: a share of another project → 404 (not 403); DELETE with snapshots → 403; unknown microversion → 406; version too new for a method → 404.
5. Database strategy
- Reuse the migrated schema (
schema-sqlite-migrated_stable-2026.2.sql, verify on MariaDB) so thatmanila-manage db syncdeployments and the rewrite can share a database during migration; the models are not the authority (schema-diff). - Soft delete everywhere: 33 tables have
deleted VARCHAR(36)('False', set to the row id on delete), 19 havedeleted INTEGER(0 / id); unique constraints includedeleted. Filters usedeleted = 'False'/0. - Most ids are UUID strings generated by the API;
services,quotas,quota_usages,reservations,share_access_rules_metadata… use integer auto-increment keys. - Locking: quota rows
SELECT … FOR UPDATEwith deadlock retries (03 § notable transactions); access-rule processing serialised per share instance (file lock, host-local); tooz locks for replicas and scheduler housekeeping. In Kubernetes, replace file locks by a lock that spans pods (database advisory lock or Kubernetes Lease) [inferred]. - Startup data: none required except the default share type, created by the operator (
default_share_type); quotas come from configuration defaults. - No online data migrations on this branch.
6. Authentication, policies, configuration and CLI
- Auth: keystonemiddleware
authtoken→ headersX-User-Id,X-Project-Id,X-Roles,X-Project-Domain-Id… →ManilaKeystoneContext(manila/api/middleware/auth.py:74-118). A rewrite must validate tokens the same way (OpenKeel Keystone, JWT/OIDC) and strip client-sent identity headers. - Policies: 230 oslo.policy rules (rule language
role:,project_id:%(project_id)s,rule:,is_admin:True,or/and/not), deployers may override them in the policy file ([oslo_policy]/policy_file). Implement the rule language or at least the defaults ofpolicies_stable-2026.2.json; enforce at the same two levels (handler and business layer). Defaults useenforce_new_defaults; scope is not enforced by default. - Configuration to keep (names as in
config_stable-2026.2.conf):[DEFAULT]host,enabled_share_backends,enabled_share_protocols,default_share_type,storage_availability_zone,osapi_max_limit,project_id_regex,quota_*,reservation_expire,periodic_interval,report_interval,service_down_time,scheduler_default_filters,scheduler_default_weighers,capacity_weight_multiplier,use_scheduler_creating_share_from_snapshot,is_deferred_deletion_enabled,soft_deleted_share_retention_time,transfer_retention_time,message_ttl,admin_only_metadata,transport_url;[database],[keystone_authtoken],[oslo_policy],[oslo_messaging_notifications],[coordination]; one section per back end (share_backend_name,driver_handles_share_servers,reserved_share_percentage,max_over_subscription_ratio, plus the CSI driver's own options: StorageClass, VolumeSnapshotClass, namespace, NFS endpoint form). Driver options of the 40 vendor drivers can be dropped. - CLI:
manila-manage(manila/cmd/manage.py:132-480):db sync|version|purge|stamp,service list|cleanup,share update_host|delete,share_server update_share_server_capabilities,host list,config list;manila-status upgrade check. Deployment tools calldb syncandservice list.
7. Conformance strategy
- Suite: there is no gabbi suite and no in-process functional suite upstream; Tempest's
manila-tempest-pluginneeds a deployment (it can run against OpenKeel later, with the dummy-driver configuration of the upstream CI as a model). The conformance base of this pack is the 29 client scenarios oftools/adapter_manila.py: real paste pipeline (authtoken replaced by a token→headers stand-in), SQLite migrated with Manila's migrations, in-process RPC delivering to the real scheduler, three real share services with the DummyDriver (two DHSS=False, one DHSS=True whose tenant ports come from a fake Neutron through the realNeutronNetworkPlugin) (manila/tests/share/drivers/dummy.py) and the real data service; clients: the real python-manilaclient and the exact gophercloud requests of the Manila CSI driver. - Proof: run twice from a fresh database — 29/29 scenarios as expected both times, 283/283 exchanges with identical (method, path, status) sequences, 29/29 identical scenario results, 29/29 identical Neutron calls once UUIDs, timestamps and request ids are templated ([
replay-report_stable-2026.2.json](_rewrite/replay-report_stable-2026.2.json)). 87 of the 258 operations are exercised. - How to use it against the rewrite: replay the requests of
exchanges_stable-2026.2.jsonin order per scenario, substituting server-generated ids by walking recorded and replayed bodies; compare status, fault name, message texts of § 4, and validate bodies againstresponse-schemas_stable-2026.2.json. Asynchronous steps are deterministic in the recording because casts are delivered after each request andreport()(capability report + heartbeats) is called between steps: against a real deployment, poll instead (the scenarios already poll withwait_share). - RPC frames:
rpc-frames_stable-2026.2.jsongives method names, versions, topics and argument shapes, needed only if the rewrite must interoperate with Python Manila services during a migration. - Unit API evidence:
exchanges-unit-api_stable-2026.2.json(894 tests ofmanila.tests.api, 0 failures, 119 exchanges throughmanila.tests.api.fakes.app). - Original fixtures for reference:
manila/tests/api/fakes.py:147-155(unit app),manila/tests/integrated/integrated_helpers.py:66-100(in-process services),manila/tests/conf_fixture.py:29-60.
8. Gaps found while building the pack
- Defects to fix rather than copy: response-body validation after the write (resource locks),
share_transfer:getnever enforced,fail_reasonset to an exception object (02 § 4); modelStringwithout length; 69-character constraint name. - The migrations do not run on SQLite as written (second connection in the first revision,
manila/db/migrations/alembic/versions/162a3e673105_manila_init.py:372;ALTER COLUMN; unnamed constraints): the SQLite schema was produced through alembic batch mode, and nullability / foreign-key rows of the schema diff are [inferred] until checked on MariaDB. - DHSS=True is exercised (scenarios
dhss-*: share server creation and reuse, Neutron ports, two tenants with the same CIDR, cleanup, port quota and full subnet) but against a fake Neutron and the DummyDriver; security services, share network subnets in several AZs and share server migration are not. Not exercised either: migration, share server migration, encryption, QoS types, data-manager backups to completion (no mount in the harness), snapshot access rules, network allocation updates. Their contracts are inroutes_*.jsonand the api-ref only. GET /v2/requires a token: unauthenticated version discovery (some load balancers' health checks) fails with 401.- 13 routes have their policy checked only in the share API layer (01 § 1.7); the
policyfield ofroutes_*.jsonis empty for them. - Running Go code against the in-process service was not possible (Go 1.24 installed, gophercloud v2.15.0 needs 1.25): the CSI requests are reproduced from the gophercloud source, not executed by gophercloud itself.
9. Non-functional requirements
- Throughput: API workers are synchronous; each create holds row locks on quota usages: keep quota updates short.
- Availability: several API pods and schedulers are safe (stateless, capability cache per scheduler); one active share service per back end name (host-local locks, periodic tasks without election).
- Observability: user messages are the client-facing error channel (CSI reads them); notifications for metering; request ids propagated.
- Security: identity headers from clients must be stripped; admin-only fields (
host, exportis_admin_only,share_server_id) hidden from members;auth_keyof transfers returned once.
10. Rewrite checklist
- [ ] Router with both path forms, project-id regex, body actions, unimplemented verbs → 404
- [ ] Microversion negotiation 2.0–2.99, default 2.0, experimental header, 404 outside a method's range, headers on every response
- [ ] Fault body and
safemessage exposure; per-handler status conversions of 02 § 1 - [ ] Policy engine with the 230 defaults, checked in handler and business layer;
share_transfer:getenforced - [ ] Shares, access rules, snapshots, types, quotas, messages, limits, AZs, services, pools, export locations, metadata, transfers, resource locks (scope of the CSI and Skyline flows first)
- [ ] Scheduler filters and weighers of § 3 with capability reports every
periodic_interval - [ ] DHSS=True CSI-backed share driver honouring § 3.5: share networks and Neutron ports, one NFS gateway pod per tenant subnet, export path
server:export,update_accessper-rule errors with export client lists and OVN ACLs, shrink refused cleanly, snapshots via VolumeSnapshot,delete_free_share_serverscleanup - [ ] User messages with the action/detail codes of 02 § 3 (CSI maps 002, 003, 007, 008, 009)
- [ ] Migrated schema, soft-delete semantics, quota reservations with
FOR UPDATE - [ ] Notifications
share.*andscheduler.*if metering is needed - [ ]
manila-manage db sync/service listequivalents - [ ] Replay the 29 scenarios: identical statuses and messages; python-manilaclient, OSC, manila-ui (2.87), Skyline (2.55) and Manila CSI (2.37/2.45/2.65) flows green
Source: reverse-spec-manila/07-rewrite-pack_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.