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

Reverse-spec · Services

manila — Rewrite pack

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-manilaclient stable/2026.2 @ d18a7e96667b (prefix python-manilaclient:), manila-ui stable/2026.2 @ f4dfaacb5658 (manila-ui:), skyline-console stable/2026.2 @ 806d200392ee (skyline-console:), skyline-apiserver stable/2026.2 @ 4af72bfd4577 (skyline-apiserver:), cloud-provider-openstack v1.37.1 (Manila CSI driver, cloud-provider-openstack:), gophercloud v2.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/

FileUse 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:

  • faultwrap is outside authtoken: a crash in keystonemiddleware becomes a Manila computeFault 500, but a missing/invalid token gives keystonemiddleware's 401 body {"error": {"code": 401, "title": "Unauthorized", "message": …}} with WWW-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 outside authtoken; /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, or project_id_regex must be widened.
  • Body actions: exactly one key; os- spellings until 2.6 (01 § 1.4).
  • Statuses are not uniform: POST /shares and POST /shares/manage answer 200, POST /snapshots, transfers, replicas and most actions 202, allow_access 200, 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>_links with marker only when limit is reached; admin all_tenants=1 silently 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_protocol defaults 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 iff r == 0 and S <= F; T <= 0 → fails;
  • free = floor(F − T·r);
  • thin provisioning (share type thin_provisioning / pool thin_provisioning true) with max_over_subscription_ratio R ≥ 1: fails if (provisioned_capacity_gb + S) / T > R, else passes iff free · 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 by capacity_weight_multiplier (1.0; negative stacks instead of spreading).

3.3 Algorithm

  1. Host states: every manila-share service of the topic that is up (updated_at within service_down_time) and not disabled (unless an admin only_host hint), 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).
  2. 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_function evaluated), 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).
  3. 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).
  4. Best = first; consume_from_share decrements its free capacity in memory (manila/scheduler/host_manager.py:429-450); write host@backend#pool; cast to the share host.
  5. Retry: scheduler_max_attempts (3, manila/scheduler/drivers/base.py:35) is tracked in filter_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, optionally krb5 with 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, so 10.0.0.5 is 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 pointRequired behaviour (Manila side)OpenKeel mapping [inferred]
driver_handles_share_serversTrue. 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 groupconstant
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-allocatedcreate 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_idthe manager rejects inconsistent segmentation for flat, vlan, vxlan, gre (manila/share/manager.py:4852-4890). Other types (OVN geneve) pass uncheckedKube-OVN/OVN subnets report geneve (or vlan for underlay subnets)
security_servicesLDAP / 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_sizecapabilities 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_detailsdelete 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_serveras 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_statspools 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_infore-export after a restart (05)check gateway pods and exports; recreate a missing export from the database
share server statusescreating → 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-networks with neutron_net_id / neutron_subnet_id of their own network; subnets per availability zone from 2.51) and pass share_network_id on POST /shares. Skyline and manila-ui already expose share networks. The Manila CSI driver takes it from the StorageClass parameter shareNetworkID (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_server shares. 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 ports device_owner: manila:share in 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 is preferred), plus the admin-network one (is_admin_only: true), visible to admins only. The server's backend_details hold what _setup_server returned (server_id, service_ip, subnet_allocations, username), and identifier is 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_share is False by default). delete_free_share_servers removes it once it has been unused for longer than unused_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.5x addresses 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) → share error, server error, user message action 002 / detail 027 SHARE_NETWORK_PORT_QUOTA_LIMIT_EXCEEDED, ports rolled back. **A full subnet (409 IpAddressGenerationFailure) gives the same detail 027, not 033 NEUTRON_SUBNET_FULL**: IpAddressGenerationFailureClient is a subclass of Conflict in python-neutronclient 14.0.0, so the first except NeutronClientException with status_code == 409 turns it into PortLimitExceeded, 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):

RequestWhenWhat Manila reads or sendsDefined at
GET /v2.0/extensionsbefore each allocationthe extension names Provider Network (mandatory, otherwise NetworkBadConfigurationException "Provider Network extension required") and Port Binding — not the aliasesmanila/network/neutron/neutron_network_plugin.py:418-420, manila/network/neutron/api.py:278-288
GET /v2.0/networks/{id}allocationprovider:network_type, provider:segmentation_id, mtu, router:external, or segments[] for multi-segment networksmanila/network/neutron/neutron_network_plugin.py:428-470
GET /v2.0/subnets/{id}allocationcidr, gateway_ip, ip_versionmanila/network/neutron/neutron_network_plugin.py:428-470
POST /v2.0/portsone 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 → PortLimitExceededmanila/network/neutron/api.py:139-217
GET /v2.0/ports?device_id=&device_owner=manila:share&network_id=de-allocationports of a share servermanila/network/neutron/neutron_network_plugin.py:310-345
DELETE /v2.0/ports/{id}de-allocation404 toleratedmanila/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

ExpectationWhere the client relies on it
GET /v2/ (authenticated) returns {"versions": [ … ]} with exactly one element carrying min_version, version (max), status, updated in RFC 3339gophercloud: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_hintscloud-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 drivergophercloud-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 202gophercloud: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:exportcloud-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 413python-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, sharepython-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 zonesskyline-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-onlyscenario 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 that manila-manage db sync deployments 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 have deleted INTEGER (0 / id); unique constraints include deleted. Filters use deleted = '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 UPDATE with 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 → headers X-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 of policies_stable-2026.2.json; enforce at the same two levels (handler and business layer). Defaults use enforce_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 call db sync and service list.

7. Conformance strategy

  • Suite: there is no gabbi suite and no in-process functional suite upstream; Tempest's manila-tempest-plugin needs 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 of tools/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 real NeutronNetworkPlugin) (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.json in order per scenario, substituting server-generated ids by walking recorded and replayed bodies; compare status, fault name, message texts of § 4, and validate bodies against response-schemas_stable-2026.2.json. Asynchronous steps are deterministic in the recording because casts are delivered after each request and report() (capability report + heartbeats) is called between steps: against a real deployment, poll instead (the scenarios already poll with wait_share).
  • RPC frames: rpc-frames_stable-2026.2.json gives 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 of manila.tests.api, 0 failures, 119 exchanges through manila.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:get never enforced, fail_reason set to an exception object (02 § 4); model String without 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 in routes_*.json and 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 policy field of routes_*.json is 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, export is_admin_only, share_server_id) hidden from members; auth_key of 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 safe message exposure; per-handler status conversions of 02 § 1
  • [ ] Policy engine with the 230 defaults, checked in handler and business layer; share_transfer:get enforced
  • [ ] 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_access per-rule errors with export client lists and OVN ACLs, shrink refused cleanly, snapshots via VolumeSnapshot, delete_free_share_servers cleanup
  • [ ] 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.* and scheduler.* if metering is needed
  • [ ] manila-manage db sync / service list equivalents
  • [ ] 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.