REST API — neutron — branch stable/2026.2
Branch
stable/2026.2@eef1a0f7ee36— repository https://opendev.org/openstack/neutron — analysed on 2026-10-28 Library: neutron-libstable/2026.2@1ee72052(cited asneutron-lib:)
Scope: the Networking API v2.0 as served by neutron-server with the ML2/OVN reference profile of this specification: core plugin ml2, mechanism driver ovn, service plugins ovn-router, trunk, qos, segments, port_forwarding, log, network_segment_range, placement plus Neutron's default service plugins (auto_allocate, tag, timestamp, network_ip_availability, flavors, revisions, neutron/plugins/common/constants.py:30). The configuration is _rewrite/tools/neutron-ovn.conf. The route table below was extracted from the running application built with that profile (_rewrite/tools/neutron_routes.py), not from the documentation: Neutron has no route table in its code. Another set of plugins gives another API; the extensions that this profile does not load (BGP, EVPN, local IP, NDP proxy, conntrack helpers, metering, VPN, FWaaS…) are out of scope.
Error statuses are only summarised here; the catalogue with the situations is in 02-errors_stable-2026.2.md. What happens in OVN after each call is in 05-async-processing_stable-2026.2.md and 04-scenarios_stable-2026.2.md.
1. How a request reaches the code
1.1 Paste pipeline
neutron-server (or the WSGI script neutron/wsgi/api.py) loads etc/neutron/api-paste.ini:
| Path | Pipeline (outermost first), auth_strategy = keystone | Defined at |
|---|---|---|
/ | cors → http_proxy_to_wsgi → neutronversions (pecan RootController) | etc/neutron/api-paste.ini:12 |
/healthcheck | oslo.middleware Healthcheck, backend disable_by_file (/var/lib/neutron/healthcheck_disable) | etc/neutron/api-paste.ini:55 |
/v2.0 | cors → http_proxy_to_wsgi → request_id → catch_errors → osprofiler → authtoken (keystonemiddleware) → keystonecontext → extensions → neutronapiapp_v2_0 (pecan) | etc/neutron/api-paste.ini:7-10 |
The pipeline is chosen by [DEFAULT]/auth_strategy (neutron/auth.py:57). With noauth, authtoken and keystonecontext are replaced by fake_project_id, which gives the request an admin context with project fake_project_id (neutron/auth.py:42-54).
keystonecontextbuilds theneutron_lib.context.Contextfrom theX-*headers set by keystonemiddleware and answers 401 when there is no user ID (neutron/auth.py:29-39).catch_errorsturns any exception that escapes the pipeline into a 500 with a plain body.
1.2 Two routing layers
Point of attention for a rewrite. Two different web stacks serve the API, and they do not behave identically:
- Extension middleware (
extensionsfilter,neutron/api/extensions.py:138). At start-up it builds aroutes.Mapperfrom everyResourceExtensionreturned by the loaded extensions (neutron/api/extensions.py:147-184): onemapper.resource()per collection (index, create, show, update, delete), plus the member actions (PUT /routers/{id}/add_router_interface…), collection actions (GET /quotas/project…) and collection methods (PUT /networks/{id}/tags…). A matched request is handed toneutron.api.v2.resource.Resource(neutron/api/v2/resource.py:34), which calls the legacy controllerneutron.api.v2.base.Controller(neutron/api/v2/base.py:46) or a custom controller (quotas, tags, agent schedulers). Every extension resource is served here: routers, floating IPs, security groups, QoS, trunks, segments, RBAC, quotas, agents… - pecan (
neutron/pecan_wsgi/app.py:27). It only receives what the mapper did not match (neutron/api/extensions.py:263-274): the core collectionsnetworks,subnets,ports,subnetpools(neutron/pecan_wsgi/startup.py:32-58),GET /v2.0/andGET /v2.0/extensions[/{alias}]. Pecan also builds controllers for the extension resources (neutron/pecan_wsgi/startup.py:61-120), but they are unreachable while theextensionsfilter is in the pipeline.
This was checked by instrumenting both controllers in the reference profile (_rewrite/tools/pipeline_probe.py, results in _rewrite/pipeline-probes_stable-2026.2.json): GET /v2.0/networks reaches pecan's CollectionsController.get, while GET /v2.0/routers, /qos/policies, /floatingips, /security-groups, /segments, /trunks, /address-scopes reach base.Controller.index.
Visible differences between the two layers:
| Behaviour | pecan (networks, subnets, ports, subnetpools) | extension middleware (all other resources) |
|---|---|---|
| Request processing | hooks in priority order (neutron/pecan_wsgi/app.py:31-40): user filter 90, context 95, exception translation 100, body validation 120, quota 130, notifier 135, query parameters 139, policy 140 | base.Controller methods (neutron/api/v2/base.py:373-709) |
| Invalid JSON body | 400 HTTPBadRequest "Body contains invalid data" (neutron/pecan_wsgi/hooks/body_validation.py:45-51) | 400 MalformedRequestBody "Malformed request body: Cannot understand JSON." (neutron/api/wsgi.py) |
| JSON serialisation | compact ({"networks":[]}) | with spaces ({"routers": []}) |
Unknown method on a collection (HEAD, PATCH…) | 405 (neutron/pecan_wsgi/controllers/resource.py:147-152) | 405 from Routes |
GET /v2.0/<collection>/new, GET …/{id}/edit | 404 (no such ID) | 500 for /new (Routes generates new/edit actions, the controller has no such method) |
| Policy failure on GET of one item | 404 "The resource could not be found." (neutron/pecan_wsgi/hooks/policy_enforcement.py:195-210) | same text (neutron/api/v2/base.py:396-400) |
.json suffix (/v2.0/routers.json) | accepted (guess_content_type_from_ext, neutron/pecan_wsgi/app.py:45) | accepted (neutron/api/extensions.py:166) |
1.3 Handler naming convention
Neither layer has explicit handlers: the controller derives the plugin method from the resource name (neutron/api/v2/base.py:125-137, neutron/pecan_wsgi/controllers/utils.py:163-173):
| Operation | Plugin method | Policy rule checked |
|---|---|---|
GET /<collection> | get_<collection> (sub-resource: get_<parent>_<collection>) | get_<resource> on each item (items that fail are silently dropped) |
POST /<collection> | create_<resource>, or create_<resource>_bulk for a bulk body when the plugin supports native bulk | create_<resource> per item |
GET /<collection>/{id} | get_<resource> | get_<resource>; failure → 404 |
PUT /<collection>/{id} | update_<resource> | update_<resource>; failure → 404 if the caller cannot even get it, else 403 |
DELETE /<collection>/{id} | delete_<resource> | delete_<resource>; same 404/403 rule |
PUT /<collection>/{id}/<action> | <action> (member action) | <action> after a get_<resource> check (neutron/api/v2/base.py:224-271) |
Attribute-level rules (create_port:binding:host_id, get_network:provider:segmentation_id…) are checked for each attribute present in the body or the response. In responses, attributes the caller may not see are removed rather than refused (neutron/api/v2/base.py:159-193, neutron/pecan_wsgi/hooks/policy_enforcement.py:223-261). The 375 default rules are in _rewrite/policies_stable-2026.2.yaml.
2. Cross-cutting request rules
2.1 Request body
- The body must be
{"<resource>": {...}}, or{"<collection>": [...]}for a bulk create (neutron/api/v2/base.py:712-775). Errors: 400 "Resource body required", "Bulk operation not supported", "Resources required", "Unable to find '<resource>' in request body", "Object '<resource>' contains invalid data". - Each attribute is checked against the resource's
RESOURCE_ATTRIBUTE_MAPentry (neutron-lib:neutron_lib/api/attributes.py): unknown attribute → 400 "Unrecognized attribute(s)";allow_post: Falseon create orallow_put: Falseon update → 400 "Attribute '…' not allowed in POST" / "Cannot update read-only attribute …" (neutron/api/v2/base.py:769-772); missing attribute without default → 400; failedvalidate(type:uuid,type:subnet,type:ip_address…) → 400 with the validator's message;convert_tofailure → 400. project_id/tenant_idare filled from the context on create (AttributeInfo.populate_project_id,neutron/api/v2/base.py:761). A non-admin may only set them to its own project; innoauthmode a create withoutproject_idis refused with 400 "Running without keystone AuthN requires that project_id is specified".DELETEwith a body → 400 "Request body is not supported in DELETE." (neutron/api/v2/base.py:568-570,neutron/pecan_wsgi/controllers/resource.py:78-80).
2.2 Listing: filters, fields, sorting, pagination
- Filters: any query parameter that names an attribute filters on equality; repeated parameters are OR-ed (
neutron/api/api_common.py:70-115). With thefilter-validationextension (enabled,[DEFAULT]/filter_validation = True), an unknown filter → 400 (neutron/api/api_common.py:113).ip-substring-filteringallowsfixed_ips=ip_address_substr=…on ports.empty-string-filteringallowsname=. - Fields:
fields=a&fields=brestricts the response; the attributes needed by the policy engine are fetched anyway and stripped afterwards (neutron/api/v2/base.py:215-222). - Sorting:
sort_key/sort_dir(asc|desc), several allowed; only attributes withis_sort_key: True(neutron/api/api_common.py:223-250), otherwise 400. - Pagination:
limit,marker,page_reverse.limitis capped by[DEFAULT]/pagination_max_limit(default-1, no cap) (neutron/api/api_common.py:171-215). The response then carries<collection>_linkswithnext/previoushrefs (neutron/api/v2/base.py:343-345). ML2 and most service plugins do native SQL pagination; otherwise it is emulated in memory. - A
GETon a collection also resynchronises the caller's quota usage counters for that resource (neutron/api/v2/base.py:347-348).
2.3 Optimistic concurrency: If-Match
With the revision-if-match extension, PUT/DELETE accept If-Match: revision_number=<n> (neutron/api/api_common.py:47-62). The constraint is stored in the context and checked by the revision plugin in the before_flush phase of the transaction that bumps standardattributes.revision_number (neutron/services/revisions/revision_plugin.py:301); a mismatch raises RevisionNumberConstraintFailed, a webob.exc.HTTPPreconditionFailed → 412 "Constrained to <n>, but current revision is <m>" (neutron/services/revisions/revision_plugin.py:324). Every resource with standard attributes carries revision_number, created_at, updated_at, description, tags.
2.4 Quotas
Before a create, a reservation is made per project for the number of items (neutron/api/v2/base.py:488-500, neutron/pecan_wsgi/hooks/quota_enforcement.py:36-77); it is committed after success and cancelled on failure. Exceeding the limit raises OverQuota → 409 "Quota exceeded for resources: […]". Resources with quotas in this profile: network, subnet, subnetpool, port, router, floatingip, router_route, rbac_policy, security_group, security_group_rule, trunk (GET /v2.0/quotas/{project}/default in the reference run). The driver is [quotas]/quota_driver (default DbQuotaNoLockDriver).
2.5 Notifications and callbacks emitted by every write
- AMQP notifications
<resource>.create.start/.end,.update.start/.end,.delete.start/.endon thenetworkpublisher (neutron/api/v2/base.py:439-441,511-514,571-573,605-611,627-629,701-702; pecan:neutron/pecan_wsgi/hooks/notifier.py:37-112). Payload: the request body (.start) or the API view of the result (.end); deletes add<resource>_id. See 03. - In-process callback
BEFORE_RESPONSEon the resource with anAPIEventPayload(neutron/api/v2/base.py:515-520). Member actions (add_router_interface…) emit no API notification. - All handlers are wrapped in
retry_db_errors(neutron/api/v2/base.py:373,neutron/pecan_wsgi/controllers/utils.py:73): deadlocks,StaleDataErrorandRetryRequestare retried (oslo.dbwrap_db_retry, 20 attempts with back-off,neutron-lib:neutron_lib/db/api.py).
2.6 Success statuses
POST → 201 with the created object; DELETE → 204 without body; others → 200. Exceptions: the collection methods of tags (PUT /…/tags 200, DELETE /…/tags 204), PUT /…/tags/{tag} 201, quotas DELETE 204.
2.7 Error body
{"NeutronError": {"type": "NetworkNotFound", "message": "Network 3c7… could not be found.", "detail": ""}}
type is the exception class name (neutron/api/api_common.py:520-532). The class → status mapping is neutron-lib:neutron_lib/api/faults.py:19-29; anything else is a 500 with a generic message; NotImplementedError → 501 with key NotImplementedError instead of NeutronError (neutron/api/api_common.py:500-509). Details in 02.
3. Summary of operations
Generated from _rewrite/routes_stable-2026.2.json (_rewrite/tools/table01.py). "Handler" is the plugin method that does the work, with the def line of its implementation in the class actually loaded (ML2 for core resources, OVNL3RouterPlugin for L3…). "Layer": pecan or ext (extension middleware), see § 1.2. Rows …/tags are repeated for every taggable resource (networks, subnets, ports, subnetpools, routers, floatingips, security-groups, trunks, policies, network-segment-ranges). The error statuses of each family are given in § 4 and catalogued in 02.
| Method | Path | Handler (plugin method) | Policy rule | Success | Layer |
|---|---|---|---|---|---|
| GET | / | neutron/pecan_wsgi/controllers/root.py:60 | — | 200 | pecan |
| GET | /v2.0/ | neutron/pecan_wsgi/controllers/root.py:98 | — | 200 | pecan |
| GET | /v2.0/address-groups | neutron/db/address_group_db.py:169 get_address_groups | get_address_group | 200 | ext |
| POST | /v2.0/address-groups | neutron/db/address_group_db.py:129 create_address_group | create_address_group | 201 | ext |
| GET | /v2.0/address-groups/{id} | neutron/db/address_group_db.py:165 get_address_group | get_address_group | 200 | ext |
| PUT | /v2.0/address-groups/{id} | neutron/db/address_group_db.py:151 update_address_group | update_address_group | 200 | ext |
| DELETE | /v2.0/address-groups/{id} | neutron/db/address_group_db.py:181 delete_address_group | delete_address_group | 204 | ext |
| PUT | /v2.0/address-groups/{id}/add_addresses | neutron/db/address_group_db.py:84 add_addresses | add_addresses | 200 | ext |
| PUT | /v2.0/address-groups/{id}/remove_addresses | neutron/db/address_group_db.py:108 remove_addresses | remove_addresses | 200 | ext |
| GET | /v2.0/address-scopes | neutron/db/address_scope_db.py:107 get_address_scopes | get_address_scope | 200 | ext |
| POST | /v2.0/address-scopes | neutron/db/address_scope_db.py:70 create_address_scope | create_address_scope | 201 | ext |
| GET | /v2.0/address-scopes/{id} | neutron/db/address_scope_db.py:103 get_address_scope | get_address_scope | 200 | ext |
| PUT | /v2.0/address-scopes/{id} | neutron/db/address_scope_db.py:91 update_address_scope | update_address_scope | 200 | ext |
| DELETE | /v2.0/address-scopes/{id} | neutron/db/address_scope_db.py:122 delete_address_scope | delete_address_scope | 204 | ext |
| GET | /v2.0/agents | neutron/db/agents_db.py:294 get_agents | get_agent | 200 | ext |
| POST | /v2.0/agents | neutron/extensions/agent.py:58 create_agent | create_agent | 201 | ext |
| GET | /v2.0/agents/{agent_id}/dhcp-networks | neutron/extensions/dhcpagentscheduler.py:37 index | — | 200 | ext |
| POST | /v2.0/agents/{agent_id}/dhcp-networks | neutron/extensions/dhcpagentscheduler.py:45 create | — | 201 | ext |
| DELETE | /v2.0/agents/{agent_id}/dhcp-networks/{id} | neutron/extensions/dhcpagentscheduler.py:57 delete | — | 204 | ext |
| GET | /v2.0/agents/{agent_id}/l3-routers | neutron/extensions/l3agentscheduler.py:54 index | — | 200 | ext |
| POST | /v2.0/agents/{agent_id}/l3-routers | neutron/extensions/l3agentscheduler.py:62 create | — | 201 | ext |
| PUT | /v2.0/agents/{agent_id}/l3-routers/{id} | neutron/extensions/l3agentscheduler.py:76 update | — | 200 | ext |
| DELETE | /v2.0/agents/{agent_id}/l3-routers/{id} | neutron/extensions/l3agentscheduler.py:89 delete | — | 204 | ext |
| GET | /v2.0/agents/{id} | neutron/db/agents_db.py:342 get_agent | get_agent | 200 | ext |
| PUT | /v2.0/agents/{id} | neutron/db/agentschedulers_db.py:82 update_agent | update_agent | 200 | ext |
| DELETE | /v2.0/agents/{id} | neutron/db/agents_db.py:269 delete_agent | delete_agent | 204 | ext |
| GET | /v2.0/auto-allocated-topology/{id} | neutron/services/auto_allocate/db.py:132 get_auto_allocated_topology | get_auto_allocated_topology | 200 | ext |
| DELETE | /v2.0/auto-allocated-topology/{id} | neutron/services/auto_allocate/db.py:162 delete_auto_allocated_topology | delete_auto_allocated_topology | 204 | ext |
| GET | /v2.0/availability_zones | neutron/db/agents_db.py:139 get_availability_zones | get_availability_zone | 200 | ext |
| GET | /v2.0/default-security-group-rules | neutron/db/securitygroups_db.py:693 get_default_security_group_rules | get_default_security_group_rule | 200 | ext |
| POST | /v2.0/default-security-group-rules | neutron/db/securitygroups_db.py:595 create_default_security_group_rule | create_default_security_group_rule | 201 | ext |
| GET | /v2.0/default-security-group-rules/{id} | neutron/db/securitygroups_db.py:715 get_default_security_group_rule | get_default_security_group_rule | 200 | ext |
| DELETE | /v2.0/default-security-group-rules/{id} | neutron/db/securitygroups_db.py:651 delete_default_security_group_rule | delete_default_security_group_rule | 204 | ext |
| GET | /v2.0/extensions | neutron/pecan_wsgi/controllers/extensions.py | — | 200 | pecan |
| GET | /v2.0/extensions/{alias} | neutron/pecan_wsgi/controllers/extensions.py | — | 200 | pecan |
| GET | /v2.0/flavors | neutron/db/flavors_db.py:125 get_flavors | get_flavor | 200 | ext |
| POST | /v2.0/flavors | neutron/db/flavors_db.py:93 create_flavor | create_flavor | 201 | ext |
| GET | /v2.0/flavors/{flavor_id}/next_providers/{id} | neutron/db/flavors_db.py:217 get_flavor_next_provider | get_flavor_next_provider | 200 | ext |
| POST | /v2.0/flavors/{flavor_id}/service_profiles | neutron/db/flavors_db.py:134 create_flavor_service_profile | create_flavor_service_profile | 201 | ext |
| GET | /v2.0/flavors/{flavor_id}/service_profiles/{id} | neutron/db/flavors_db.py:157 get_flavor_service_profile | get_flavor_service_profile | 200 | ext |
| DELETE | /v2.0/flavors/{flavor_id}/service_profiles/{id} | neutron/db/flavors_db.py:148 delete_flavor_service_profile | delete_flavor_service_profile | 204 | ext |
| GET | /v2.0/flavors/{id} | neutron/db/flavors_db.py:109 get_flavor | get_flavor | 200 | ext |
| PUT | /v2.0/flavors/{id} | neutron/db/flavors_db.py:101 update_flavor | update_flavor | 200 | ext |
| DELETE | /v2.0/flavors/{id} | neutron/db/flavors_db.py:113 delete_flavor | delete_flavor | 204 | ext |
| GET | /v2.0/floatingip-pools | neutron/db/l3_fip_pools_db.py:37 get_floatingip_pools | get_floatingip_pool | 200 | ext |
| GET | /v2.0/floatingips | neutron/db/l3_db.py:1783 get_floatingips | get_floatingip | 200 | ext |
| POST | /v2.0/floatingips | neutron/services/ovn_l3/plugin.py:239 create_floatingip | create_floatingip | 201 | ext |
| GET | /v2.0/floatingips/{floatingip_id}/port_forwardings | neutron/services/portforwarding/pf_plugin.py:686 get_floatingip_port_forwardings | get_floatingip_port_forwarding | 200 | ext |
| POST | /v2.0/floatingips/{floatingip_id}/port_forwardings | neutron/services/portforwarding/pf_plugin.py:352 create_floatingip_port_forwarding | create_floatingip_port_forwarding | 201 | ext |
| GET | /v2.0/floatingips/{floatingip_id}/port_forwardings/{id} | neutron/services/portforwarding/pf_plugin.py:668 get_floatingip_port_forwarding | get_floatingip_port_forwarding | 200 | ext |
| PUT | /v2.0/floatingips/{floatingip_id}/port_forwardings/{id} | neutron/services/portforwarding/pf_plugin.py:406 update_floatingip_port_forwarding | update_floatingip_port_forwarding | 200 | ext |
| DELETE | /v2.0/floatingips/{floatingip_id}/port_forwardings/{id} | neutron/services/portforwarding/pf_plugin.py:697 delete_floatingip_port_forwarding | delete_floatingip_port_forwarding | 204 | ext |
| GET | /v2.0/floatingips/{floatingip_id}/tags | neutron/extensions/tagging.py:206 index | — | 200 | ext |
| POST | /v2.0/floatingips/{floatingip_id}/tags | neutron/extensions/tagging.py:228 create | — | 200 | ext |
| PUT | /v2.0/floatingips/{floatingip_id}/tags | neutron/extensions/tagging.py:269 update_all | — | 200 | ext |
| DELETE | /v2.0/floatingips/{floatingip_id}/tags | neutron/extensions/tagging.py:306 delete_all | — | 204 | ext |
| GET | /v2.0/floatingips/{floatingip_id}/tags/{id} | neutron/extensions/tagging.py:216 show | — | 204 | ext |
| PUT | /v2.0/floatingips/{floatingip_id}/tags/{id} | neutron/extensions/tagging.py:247 update | — | 201 | ext |
| DELETE | /v2.0/floatingips/{floatingip_id}/tags/{id} | neutron/extensions/tagging.py:288 delete | — | 204 | ext |
| GET | /v2.0/floatingips/{id} | neutron/db/l3_db.py:1778 get_floatingip | get_floatingip | 200 | ext |
| PUT | /v2.0/floatingips/{id} | neutron/db/l3_db.py:1729 update_floatingip | update_floatingip | 200 | ext |
| DELETE | /v2.0/floatingips/{id} | neutron/db/l3_db.py:1774 delete_floatingip | delete_floatingip | 204 | ext |
| GET | /v2.0/log/loggable-resources | neutron/services/logapi/logging_plugin.py:141 get_loggable_resources | get_loggable_resource | 200 | ext |
| GET | /v2.0/log/logs | neutron/services/logapi/logging_plugin.py:75 get_logs | get_log | 200 | ext |
| POST | /v2.0/log/logs | neutron/services/logapi/logging_plugin.py:95 create_log | create_log | 201 | ext |
| GET | /v2.0/log/logs/{id} | neutron/services/logapi/logging_plugin.py:91 get_log | get_log | 200 | ext |
| PUT | /v2.0/log/logs/{id} | neutron/services/logapi/logging_plugin.py:115 update_log | update_log | 200 | ext |
| DELETE | /v2.0/log/logs/{id} | neutron/services/logapi/logging_plugin.py:131 delete_log | delete_log | 204 | ext |
| GET | /v2.0/network-ip-availabilities | neutron/services/network_ip_availability/plugin.py:50 get_network_ip_availabilities | get_network_ip_availability | 200 | ext |
| GET | /v2.0/network-ip-availabilities/{id} | neutron/services/network_ip_availability/plugin.py:58 get_network_ip_availability | get_network_ip_availability | 200 | ext |
| GET | /v2.0/network-segment-ranges | neutron/services/network_segment_range/plugin.py:193 get_network_segment_ranges | get_network_segment_range | 200 | ext |
| POST | /v2.0/network-segment-ranges | neutron/services/network_segment_range/plugin.py:156 create_network_segment_range | create_network_segment_range | 201 | ext |
| GET | /v2.0/network-segment-ranges/{id} | neutron/services/network_segment_range/plugin.py:187 get_network_segment_range | get_network_segment_range | 200 | ext |
| PUT | /v2.0/network-segment-ranges/{id} | neutron/services/network_segment_range/plugin.py:217 update_network_segment_range | update_network_segment_range | 200 | ext |
| DELETE | /v2.0/network-segment-ranges/{id} | neutron/services/network_segment_range/plugin.py:248 delete_network_segment_range | delete_network_segment_range | 204 | ext |
| GET | /v2.0/network_segment_ranges/{network_segment_range_id}/tags | neutron/extensions/tagging.py:206 index | — | 200 | ext |
| POST | /v2.0/network_segment_ranges/{network_segment_range_id}/tags | neutron/extensions/tagging.py:228 create | — | 200 | ext |
| PUT | /v2.0/network_segment_ranges/{network_segment_range_id}/tags | neutron/extensions/tagging.py:269 update_all | — | 200 | ext |
| DELETE | /v2.0/network_segment_ranges/{network_segment_range_id}/tags | neutron/extensions/tagging.py:306 delete_all | — | 204 | ext |
| GET | /v2.0/network_segment_ranges/{network_segment_range_id}/tags/{id} | neutron/extensions/tagging.py:216 show | — | 204 | ext |
| PUT | /v2.0/network_segment_ranges/{network_segment_range_id}/tags/{id} | neutron/extensions/tagging.py:247 update | — | 201 | ext |
| DELETE | /v2.0/network_segment_ranges/{network_segment_range_id}/tags/{id} | neutron/extensions/tagging.py:288 delete | — | 204 | ext |
| GET | /v2.0/networks | neutron/plugins/ml2/plugin.py:1393 get_networks | get_network | 200 | pecan |
| POST | /v2.0/networks | neutron/plugins/ml2/plugin.py:1296 create_network | create_network | 201 | pecan |
| GET | /v2.0/networks/{network_id}/dhcp-agents | neutron/extensions/dhcpagentscheduler.py:70 index | — | 200 | ext |
| GET | /v2.0/networks/{network_id}/tags | neutron/extensions/tagging.py:206 index | — | 200 | ext |
| POST | /v2.0/networks/{network_id}/tags | neutron/extensions/tagging.py:228 create | — | 200 | ext |
| PUT | /v2.0/networks/{network_id}/tags | neutron/extensions/tagging.py:269 update_all | — | 200 | ext |
| DELETE | /v2.0/networks/{network_id}/tags | neutron/extensions/tagging.py:306 delete_all | — | 204 | ext |
| GET | /v2.0/networks/{network_id}/tags/{id} | neutron/extensions/tagging.py:216 show | — | 204 | ext |
| PUT | /v2.0/networks/{network_id}/tags/{id} | neutron/extensions/tagging.py:247 update | — | 201 | ext |
| DELETE | /v2.0/networks/{network_id}/tags/{id} | neutron/extensions/tagging.py:288 delete | — | 204 | ext |
| GET | /v2.0/networks/{id} | neutron/plugins/ml2/plugin.py:1384 get_network | get_network | 200 | pecan |
| PUT | /v2.0/networks/{id} | neutron/plugins/ml2/plugin.py:1325 update_network | update_network | 200 | pecan |
| DELETE | /v2.0/networks/{id} | neutron/plugins/ml2/plugin.py:1424 delete_network | delete_network | 204 | pecan |
| GET | /v2.0/policies/{policy_id}/packet_rate_limit_rules | neutron/services/qos/qos_plugin.py:1065 get_policy_packet_rate_limit_rules -> get_policy_rules | get_policy_packet_rate_limit_rule | 200 | ext |
| POST | /v2.0/policies/{policy_id}/packet_rate_limit_rules | neutron/services/qos/qos_plugin.py:862 create_policy_packet_rate_limit_rule -> create_policy_rule | create_policy_packet_rate_limit_rule | 201 | ext |
| GET | /v2.0/policies/{policy_id}/packet_rate_limit_rules/{id} | neutron/services/qos/qos_plugin.py:1024 get_policy_packet_rate_limit_rule -> get_policy_rule | get_policy_packet_rate_limit_rule | 200 | ext |
| PUT | /v2.0/policies/{policy_id}/packet_rate_limit_rules/{id} | neutron/services/qos/qos_plugin.py:901 update_policy_packet_rate_limit_rule -> update_policy_rule | update_policy_packet_rate_limit_rule | 200 | ext |
| DELETE | /v2.0/policies/{policy_id}/packet_rate_limit_rules/{id} | neutron/services/qos/qos_plugin.py:975 delete_policy_packet_rate_limit_rule -> delete_policy_rule | delete_policy_packet_rate_limit_rule | 204 | ext |
| GET | /v2.0/policies/{policy_id}/tags | neutron/extensions/tagging.py:206 index | — | 200 | ext |
| POST | /v2.0/policies/{policy_id}/tags | neutron/extensions/tagging.py:228 create | — | 200 | ext |
| PUT | /v2.0/policies/{policy_id}/tags | neutron/extensions/tagging.py:269 update_all | — | 200 | ext |
| DELETE | /v2.0/policies/{policy_id}/tags | neutron/extensions/tagging.py:306 delete_all | — | 204 | ext |
| GET | /v2.0/policies/{policy_id}/tags/{id} | neutron/extensions/tagging.py:216 show | — | 204 | ext |
| PUT | /v2.0/policies/{policy_id}/tags/{id} | neutron/extensions/tagging.py:247 update | — | 201 | ext |
| DELETE | /v2.0/policies/{policy_id}/tags/{id} | neutron/extensions/tagging.py:288 delete | — | 204 | ext |
| GET | /v2.0/ports | neutron/db/db_base_plugin_v2.py:1733 get_ports | get_port | 200 | pecan |
| POST | /v2.0/ports | neutron/plugins/ml2/plugin.py:1675 create_port | create_port | 201 | pecan |
| GET | /v2.0/ports/{port_id}/bindings | neutron/plugins/ml2/plugin.py:2847 get_port_bindings | get_port_binding | 200 | ext |
| POST | /v2.0/ports/{port_id}/bindings | neutron/plugins/ml2/plugin.py:2800 create_port_binding | create_port_binding | 201 | ext |
| GET | /v2.0/ports/{port_id}/bindings/{id} | neutron/plugins/ml2/plugin.py:2864 get_port_binding | get_port_binding | 200 | ext |
| PUT | /v2.0/ports/{port_id}/bindings/{id} | neutron/plugins/ml2/plugin.py:2882 update_port_binding | update_port_binding | 200 | ext |
| DELETE | /v2.0/ports/{port_id}/bindings/{id} | neutron/plugins/ml2/plugin.py:2970 delete_port_binding | delete_port_binding | 204 | ext |
| PUT | /v2.0/ports/{port_id}/bindings/{id}/activate | neutron/plugins/ml2/plugin.py:2917 activate | activate | 200 | ext |
| GET | /v2.0/ports/{port_id}/tags | neutron/extensions/tagging.py:206 index | — | 200 | ext |
| POST | /v2.0/ports/{port_id}/tags | neutron/extensions/tagging.py:228 create | — | 200 | ext |
| PUT | /v2.0/ports/{port_id}/tags | neutron/extensions/tagging.py:269 update_all | — | 200 | ext |
| DELETE | /v2.0/ports/{port_id}/tags | neutron/extensions/tagging.py:306 delete_all | — | 204 | ext |
| GET | /v2.0/ports/{port_id}/tags/{id} | neutron/extensions/tagging.py:216 show | — | 204 | ext |
| PUT | /v2.0/ports/{port_id}/tags/{id} | neutron/extensions/tagging.py:247 update | — | 201 | ext |
| DELETE | /v2.0/ports/{port_id}/tags/{id} | neutron/extensions/tagging.py:288 delete | — | 204 | ext |
| GET | /v2.0/ports/{id} | neutron/db/db_base_plugin_v2.py:1691 get_port | get_port | 200 | pecan |
| PUT | /v2.0/ports/{id} | neutron/plugins/ml2/plugin.py:1966 update_port | update_port | 200 | pecan |
| DELETE | /v2.0/ports/{id} | neutron/plugins/ml2/plugin.py:2228 delete_port | delete_port | 204 | pecan |
| GET | /v2.0/qos/alias-bandwidth-limit-rules | neutron/services/qos/qos_plugin.py:1065 get_alias_bandwidth_limit_rules -> get_policy_rules | get_alias_bandwidth_limit_rule | 200 | ext |
| POST | /v2.0/qos/alias-bandwidth-limit-rules | neutron/services/qos/qos_plugin.py:862 create_alias_bandwidth_limit_rule -> create_policy_rule | create_alias_bandwidth_limit_rule | 201 | ext |
| GET | /v2.0/qos/alias-bandwidth-limit-rules/{id} | neutron/services/qos/qos_plugin.py:1024 get_alias_bandwidth_limit_rule -> get_policy_rule | get_alias_bandwidth_limit_rule | 200 | ext |
| PUT | /v2.0/qos/alias-bandwidth-limit-rules/{id} | neutron/services/qos/qos_plugin.py:901 update_alias_bandwidth_limit_rule -> update_policy_rule | update_alias_bandwidth_limit_rule | 200 | ext |
| DELETE | /v2.0/qos/alias-bandwidth-limit-rules/{id} | neutron/services/qos/qos_plugin.py:975 delete_alias_bandwidth_limit_rule -> delete_policy_rule | delete_alias_bandwidth_limit_rule | 204 | ext |
| GET | /v2.0/qos/alias-dscp-marking-rules | neutron/services/qos/qos_plugin.py:1065 get_alias_dscp_marking_rules -> get_policy_rules | get_alias_dscp_marking_rule | 200 | ext |
| POST | /v2.0/qos/alias-dscp-marking-rules | neutron/services/qos/qos_plugin.py:862 create_alias_dscp_marking_rule -> create_policy_rule | create_alias_dscp_marking_rule | 201 | ext |
| GET | /v2.0/qos/alias-dscp-marking-rules/{id} | neutron/services/qos/qos_plugin.py:1024 get_alias_dscp_marking_rule -> get_policy_rule | get_alias_dscp_marking_rule | 200 | ext |
| PUT | /v2.0/qos/alias-dscp-marking-rules/{id} | neutron/services/qos/qos_plugin.py:901 update_alias_dscp_marking_rule -> update_policy_rule | update_alias_dscp_marking_rule | 200 | ext |
| DELETE | /v2.0/qos/alias-dscp-marking-rules/{id} | neutron/services/qos/qos_plugin.py:975 delete_alias_dscp_marking_rule -> delete_policy_rule | delete_alias_dscp_marking_rule | 204 | ext |
| GET | /v2.0/qos/alias-minimum-bandwidth-rules | neutron/services/qos/qos_plugin.py:1065 get_alias_minimum_bandwidth_rules -> get_policy_rules | get_alias_minimum_bandwidth_rule | 200 | ext |
| POST | /v2.0/qos/alias-minimum-bandwidth-rules | neutron/services/qos/qos_plugin.py:862 create_alias_minimum_bandwidth_rule -> create_policy_rule | create_alias_minimum_bandwidth_rule | 201 | ext |
| GET | /v2.0/qos/alias-minimum-bandwidth-rules/{id} | neutron/services/qos/qos_plugin.py:1024 get_alias_minimum_bandwidth_rule -> get_policy_rule | get_alias_minimum_bandwidth_rule | 200 | ext |
| PUT | /v2.0/qos/alias-minimum-bandwidth-rules/{id} | neutron/services/qos/qos_plugin.py:901 update_alias_minimum_bandwidth_rule -> update_policy_rule | update_alias_minimum_bandwidth_rule | 200 | ext |
| DELETE | /v2.0/qos/alias-minimum-bandwidth-rules/{id} | neutron/services/qos/qos_plugin.py:975 delete_alias_minimum_bandwidth_rule -> delete_policy_rule | delete_alias_minimum_bandwidth_rule | 204 | ext |
| GET | /v2.0/qos/alias-minimum-packet-rate-rules | neutron/services/qos/qos_plugin.py:1065 get_alias_minimum_packet_rate_rules -> get_policy_rules | get_alias_minimum_packet_rate_rule | 200 | ext |
| POST | /v2.0/qos/alias-minimum-packet-rate-rules | neutron/services/qos/qos_plugin.py:862 create_alias_minimum_packet_rate_rule -> create_policy_rule | create_alias_minimum_packet_rate_rule | 201 | ext |
| GET | /v2.0/qos/alias-minimum-packet-rate-rules/{id} | neutron/services/qos/qos_plugin.py:1024 get_alias_minimum_packet_rate_rule -> get_policy_rule | get_alias_minimum_packet_rate_rule | 200 | ext |
| PUT | /v2.0/qos/alias-minimum-packet-rate-rules/{id} | neutron/services/qos/qos_plugin.py:901 update_alias_minimum_packet_rate_rule -> update_policy_rule | update_alias_minimum_packet_rate_rule | 200 | ext |
| DELETE | /v2.0/qos/alias-minimum-packet-rate-rules/{id} | neutron/services/qos/qos_plugin.py:975 delete_alias_minimum_packet_rate_rule -> delete_policy_rule | delete_alias_minimum_packet_rate_rule | 204 | ext |
| GET | /v2.0/qos/policies | neutron/services/qos/qos_plugin.py:822 get_policies | get_policy | 200 | ext |
| POST | /v2.0/qos/policies | neutron/services/qos/qos_plugin.py:733 create_policy | create_policy | 201 | ext |
| GET | /v2.0/qos/policies/{policy_id}/bandwidth_limit_rules | neutron/services/qos/qos_plugin.py:1065 get_policy_bandwidth_limit_rules -> get_policy_rules | get_policy_bandwidth_limit_rule | 200 | ext |
| POST | /v2.0/qos/policies/{policy_id}/bandwidth_limit_rules | neutron/services/qos/qos_plugin.py:862 create_policy_bandwidth_limit_rule -> create_policy_rule | create_policy_bandwidth_limit_rule | 201 | ext |
| GET | /v2.0/qos/policies/{policy_id}/bandwidth_limit_rules/{id} | neutron/services/qos/qos_plugin.py:1024 get_policy_bandwidth_limit_rule -> get_policy_rule | get_policy_bandwidth_limit_rule | 200 | ext |
| PUT | /v2.0/qos/policies/{policy_id}/bandwidth_limit_rules/{id} | neutron/services/qos/qos_plugin.py:901 update_policy_bandwidth_limit_rule -> update_policy_rule | update_policy_bandwidth_limit_rule | 200 | ext |
| DELETE | /v2.0/qos/policies/{policy_id}/bandwidth_limit_rules/{id} | neutron/services/qos/qos_plugin.py:975 delete_policy_bandwidth_limit_rule -> delete_policy_rule | delete_policy_bandwidth_limit_rule | 204 | ext |
| GET | /v2.0/qos/policies/{policy_id}/dscp_marking_rules | neutron/services/qos/qos_plugin.py:1065 get_policy_dscp_marking_rules -> get_policy_rules | get_policy_dscp_marking_rule | 200 | ext |
| POST | /v2.0/qos/policies/{policy_id}/dscp_marking_rules | neutron/services/qos/qos_plugin.py:862 create_policy_dscp_marking_rule -> create_policy_rule | create_policy_dscp_marking_rule | 201 | ext |
| GET | /v2.0/qos/policies/{policy_id}/dscp_marking_rules/{id} | neutron/services/qos/qos_plugin.py:1024 get_policy_dscp_marking_rule -> get_policy_rule | get_policy_dscp_marking_rule | 200 | ext |
| PUT | /v2.0/qos/policies/{policy_id}/dscp_marking_rules/{id} | neutron/services/qos/qos_plugin.py:901 update_policy_dscp_marking_rule -> update_policy_rule | update_policy_dscp_marking_rule | 200 | ext |
| DELETE | /v2.0/qos/policies/{policy_id}/dscp_marking_rules/{id} | neutron/services/qos/qos_plugin.py:975 delete_policy_dscp_marking_rule -> delete_policy_rule | delete_policy_dscp_marking_rule | 204 | ext |
| GET | /v2.0/qos/policies/{policy_id}/minimum-packet-rate-rules | neutron/services/qos/qos_plugin.py:1065 get_policy_minimum_packet_rate_rules -> get_policy_rules | get_policy_minimum_packet_rate_rule | 200 | ext |
| POST | /v2.0/qos/policies/{policy_id}/minimum-packet-rate-rules | neutron/services/qos/qos_plugin.py:862 create_policy_minimum_packet_rate_rule -> create_policy_rule | create_policy_minimum_packet_rate_rule | 201 | ext |
| GET | /v2.0/qos/policies/{policy_id}/minimum-packet-rate-rules/{id} | neutron/services/qos/qos_plugin.py:1024 get_policy_minimum_packet_rate_rule -> get_policy_rule | get_policy_minimum_packet_rate_rule | 200 | ext |
| PUT | /v2.0/qos/policies/{policy_id}/minimum-packet-rate-rules/{id} | neutron/services/qos/qos_plugin.py:901 update_policy_minimum_packet_rate_rule -> update_policy_rule | update_policy_minimum_packet_rate_rule | 200 | ext |
| DELETE | /v2.0/qos/policies/{policy_id}/minimum-packet-rate-rules/{id} | neutron/services/qos/qos_plugin.py:975 delete_policy_minimum_packet_rate_rule -> delete_policy_rule | delete_policy_minimum_packet_rate_rule | 204 | ext |
| GET | /v2.0/qos/policies/{policy_id}/minimum_bandwidth_rules | neutron/services/qos/qos_plugin.py:1065 get_policy_minimum_bandwidth_rules -> get_policy_rules | get_policy_minimum_bandwidth_rule | 200 | ext |
| POST | /v2.0/qos/policies/{policy_id}/minimum_bandwidth_rules | neutron/services/qos/qos_plugin.py:862 create_policy_minimum_bandwidth_rule -> create_policy_rule | create_policy_minimum_bandwidth_rule | 201 | ext |
| GET | /v2.0/qos/policies/{policy_id}/minimum_bandwidth_rules/{id} | neutron/services/qos/qos_plugin.py:1024 get_policy_minimum_bandwidth_rule -> get_policy_rule | get_policy_minimum_bandwidth_rule | 200 | ext |
| PUT | /v2.0/qos/policies/{policy_id}/minimum_bandwidth_rules/{id} | neutron/services/qos/qos_plugin.py:901 update_policy_minimum_bandwidth_rule -> update_policy_rule | update_policy_minimum_bandwidth_rule | 200 | ext |
| DELETE | /v2.0/qos/policies/{policy_id}/minimum_bandwidth_rules/{id} | neutron/services/qos/qos_plugin.py:975 delete_policy_minimum_bandwidth_rule -> delete_policy_rule | delete_policy_minimum_bandwidth_rule | 204 | ext |
| GET | /v2.0/qos/policies/{id} | neutron/services/qos/qos_plugin.py:808 get_policy | get_policy | 200 | ext |
| PUT | /v2.0/qos/policies/{id} | neutron/services/qos/qos_plugin.py:760 update_policy | update_policy | 200 | ext |
| DELETE | /v2.0/qos/policies/{id} | neutron/services/qos/qos_plugin.py:786 delete_policy | delete_policy | 204 | ext |
| GET | /v2.0/qos/rule-types | neutron/services/qos/qos_plugin.py:847 get_rule_types | get_rule_type | 200 | ext |
| GET | /v2.0/qos/rule-types/{id} | neutron/services/qos/qos_plugin.py:840 get_rule_type | get_rule_type | 200 | ext |
| GET | /v2.0/quotas | neutron/extensions/quotasv2.py:96 index | — | 200 | ext |
| POST | /v2.0/quotas | neutron/extensions/quotasv2.py:92 create | — | 201 | ext |
| GET | /v2.0/quotas/project | neutron/extensions/quotasv2.py:109 project | — | 200 | ext |
| GET | /v2.0/quotas/tenant | neutron/extensions/quotasv2.py:103 tenant | — | 200 | ext |
| GET | /v2.0/quotas/{id} | neutron/extensions/quotasv2.py:121 show | — | 200 | ext |
| PUT | /v2.0/quotas/{id} | neutron/extensions/quotasv2.py:130 update | — | 200 | ext |
| DELETE | /v2.0/quotas/{id} | neutron/extensions/quotasv2.py:126 delete | — | 204 | ext |
| GET | /v2.0/quotas/{id}/default | neutron/extensions/quotasv2.py:82 default | — | 200 | ext |
| GET | /v2.0/quotas/{id}/details | neutron/extensions/quotasv2_detail.py:54 details | — | 200 | ext |
| GET | /v2.0/rbac-policies | neutron/db/rbac_db_mixin.py:134 get_rbac_policies | get_rbac_policy | 200 | ext |
| POST | /v2.0/rbac-policies | neutron/db/rbac_db_mixin.py:37 create_rbac_policy | create_rbac_policy | 201 | ext |
| GET | /v2.0/rbac-policies/{id} | neutron/db/rbac_db_mixin.py:129 get_rbac_policy | get_rbac_policy | 200 | ext |
| PUT | /v2.0/rbac-policies/{id} | neutron/db/rbac_db_mixin.py:75 update_rbac_policy | update_rbac_policy | 200 | ext |
| DELETE | /v2.0/rbac-policies/{id} | neutron/db/rbac_db_mixin.py:99 delete_rbac_policy | delete_rbac_policy | 204 | ext |
| GET | /v2.0/routers | neutron/db/l3_db.py:688 get_routers | get_router | 200 | ext |
| POST | /v2.0/routers | neutron/db/l3_db.py:312 create_router | create_router | 201 | ext |
| GET | /v2.0/routers/{router_id}/l3-agents | neutron/extensions/l3agentscheduler.py:111 index | — | 200 | ext |
| GET | /v2.0/routers/{router_id}/tags | neutron/extensions/tagging.py:206 index | — | 200 | ext |
| POST | /v2.0/routers/{router_id}/tags | neutron/extensions/tagging.py:228 create | — | 200 | ext |
| PUT | /v2.0/routers/{router_id}/tags | neutron/extensions/tagging.py:269 update_all | — | 200 | ext |
| DELETE | /v2.0/routers/{router_id}/tags | neutron/extensions/tagging.py:306 delete_all | — | 204 | ext |
| GET | /v2.0/routers/{router_id}/tags/{id} | neutron/extensions/tagging.py:216 show | — | 204 | ext |
| PUT | /v2.0/routers/{router_id}/tags/{id} | neutron/extensions/tagging.py:247 update | — | 201 | ext |
| DELETE | /v2.0/routers/{router_id}/tags/{id} | neutron/extensions/tagging.py:288 delete | — | 204 | ext |
| GET | /v2.0/routers/{id} | neutron/db/l3_db.py:682 get_router | get_router | 200 | ext |
| PUT | /v2.0/routers/{id} | neutron/db/extraroute_db.py:63 update_router | update_router | 200 | ext |
| DELETE | /v2.0/routers/{id} | neutron/db/l3_db.py:638 delete_router | delete_router | 204 | ext |
| PUT | /v2.0/routers/{id}/add_external_gateways | neutron/db/l3_extra_gws_db.py:461 add_external_gateways | add_external_gateways | 200 | ext |
| PUT | /v2.0/routers/{id}/add_extraroutes | neutron/db/extraroute_db.py:221 add_extraroutes | add_extraroutes | 200 | ext |
| PUT | /v2.0/routers/{id}/add_router_interface | neutron/db/l3_db.py:2272 add_router_interface | add_router_interface | 200 | ext |
| PUT | /v2.0/routers/{id}/remove_external_gateways | neutron/db/l3_extra_gws_db.py:478 remove_external_gateways | remove_external_gateways | 200 | ext |
| PUT | /v2.0/routers/{id}/remove_extraroutes | neutron/db/extraroute_db.py:238 remove_extraroutes | remove_extraroutes | 200 | ext |
| PUT | /v2.0/routers/{id}/remove_router_interface | neutron/db/l3_db.py:2279 remove_router_interface | remove_router_interface | 200 | ext |
| PUT | /v2.0/routers/{id}/update_external_gateways | neutron/db/l3_extra_gws_db.py:582 update_external_gateways | update_external_gateways | 200 | ext |
| GET | /v2.0/security-group-rules | neutron/db/securitygroups_db.py:1046 get_security_group_rules | get_security_group_rule | 200 | ext |
| POST | /v2.0/security-group-rules | neutron/db/securitygroups_rpc_base.py:63 create_security_group_rule | create_security_group_rule | 201 | ext |
| GET | /v2.0/security-group-rules/{id} | neutron/db/securitygroups_db.py:1086 get_security_group_rule | get_security_group_rule | 200 | ext |
| DELETE | /v2.0/security-group-rules/{id} | neutron/db/securitygroups_rpc_base.py:77 delete_security_group_rule | delete_security_group_rule | 204 | ext |
| GET | /v2.0/security-groups | neutron/db/securitygroups_db.py:166 get_security_groups | get_security_group | 200 | ext |
| POST | /v2.0/security-groups | neutron/db/securitygroups_db.py:89 create_security_group | create_security_group | 201 | ext |
| GET | /v2.0/security-groups-default-statefulness | neutron/db/security_groups_default_statefulness.py:79 get_security_groups_default_statefulness | get_security_groups_default_statefulness | 200 | ext |
| POST | /v2.0/security-groups-default-statefulness | neutron/db/security_groups_default_statefulness.py:52 create_security_groups_default_statefulness | create_security_groups_default_statefulness | 201 | ext |
| GET | /v2.0/security-groups-default-statefulness/{id} | neutron/db/security_groups_default_statefulness.py:79 get_security_groups_default_statefulness | get_security_groups_default_statefulness | 200 | ext |
| PUT | /v2.0/security-groups-default-statefulness/{id} | neutron/db/security_groups_default_statefulness.py:70 update_security_groups_default_statefulness | update_security_groups_default_statefulness | 200 | ext |
| DELETE | /v2.0/security-groups-default-statefulness/{id} | neutron/db/security_groups_default_statefulness.py:95 delete_security_groups_default_statefulness | delete_security_groups_default_statefulness | 204 | ext |
| GET | /v2.0/security-groups/{security_group_id}/tags | neutron/extensions/tagging.py:206 index | — | 200 | ext |
| POST | /v2.0/security-groups/{security_group_id}/tags | neutron/extensions/tagging.py:228 create | — | 200 | ext |
| PUT | /v2.0/security-groups/{security_group_id}/tags | neutron/extensions/tagging.py:269 update_all | — | 200 | ext |
| DELETE | /v2.0/security-groups/{security_group_id}/tags | neutron/extensions/tagging.py:306 delete_all | — | 204 | ext |
| GET | /v2.0/security-groups/{security_group_id}/tags/{id} | neutron/extensions/tagging.py:216 show | — | 204 | ext |
| PUT | /v2.0/security-groups/{security_group_id}/tags/{id} | neutron/extensions/tagging.py:247 update | — | 201 | ext |
| DELETE | /v2.0/security-groups/{security_group_id}/tags/{id} | neutron/extensions/tagging.py:288 delete | — | 204 | ext |
| GET | /v2.0/security-groups/{id} | neutron/db/securitygroups_db.py:208 get_security_group | get_security_group | 200 | ext |
| PUT | /v2.0/security-groups/{id} | neutron/db/securitygroups_db.py:303 update_security_group | update_security_group | 200 | ext |
| DELETE | /v2.0/security-groups/{id} | neutron/db/securitygroups_db.py:261 delete_security_group | delete_security_group | 204 | ext |
| GET | /v2.0/segments | neutron/services/segments/db.py:169 get_segments | get_segment | 200 | ext |
| POST | /v2.0/segments | neutron/services/segments/db.py:89 create_segment | create_segment | 201 | ext |
| GET | /v2.0/segments/{id} | neutron/services/segments/db.py:164 get_segment | get_segment | 200 | ext |
| PUT | /v2.0/segments/{id} | neutron/services/segments/db.py:154 update_segment | update_segment | 200 | ext |
| DELETE | /v2.0/segments/{id} | neutron/services/segments/db.py:192 delete_segment | delete_segment | 204 | ext |
| GET | /v2.0/service-providers | neutron/db/servicetype_db.py:48 get_service_providers | get_service_provider | 200 | ext |
| GET | /v2.0/service_profiles | neutron/db/flavors_db.py:207 get_service_profiles | get_service_profile | 200 | ext |
| POST | /v2.0/service_profiles | neutron/db/flavors_db.py:168 create_service_profile | create_service_profile | 201 | ext |
| GET | /v2.0/service_profiles/{id} | neutron/db/flavors_db.py:198 get_service_profile | get_service_profile | 200 | ext |
| PUT | /v2.0/service_profiles/{id} | neutron/db/flavors_db.py:183 update_service_profile | update_service_profile | 200 | ext |
| DELETE | /v2.0/service_profiles/{id} | neutron/db/flavors_db.py:202 delete_service_profile | delete_service_profile | 204 | ext |
| GET | /v2.0/subnetpools | neutron/db/db_base_plugin_v2.py:1430 get_subnetpools | get_subnetpool | 200 | ext |
| POST | /v2.0/subnetpools | neutron/db/db_base_plugin_v2.py:1352 create_subnetpool | create_subnetpool | 201 | ext |
| GET | /v2.0/subnetpools/{subnetpool_id}/tags | neutron/extensions/tagging.py:206 index | — | 200 | ext |
| POST | /v2.0/subnetpools/{subnetpool_id}/tags | neutron/extensions/tagging.py:228 create | — | 200 | ext |
| PUT | /v2.0/subnetpools/{subnetpool_id}/tags | neutron/extensions/tagging.py:269 update_all | — | 200 | ext |
| DELETE | /v2.0/subnetpools/{subnetpool_id}/tags | neutron/extensions/tagging.py:306 delete_all | — | 204 | ext |
| GET | /v2.0/subnetpools/{subnetpool_id}/tags/{id} | neutron/extensions/tagging.py:216 show | — | 204 | ext |
| PUT | /v2.0/subnetpools/{subnetpool_id}/tags/{id} | neutron/extensions/tagging.py:247 update | — | 201 | ext |
| DELETE | /v2.0/subnetpools/{subnetpool_id}/tags/{id} | neutron/extensions/tagging.py:288 delete | — | 204 | ext |
| GET | /v2.0/subnetpools/{id} | neutron/db/db_base_plugin_v2.py:1425 get_subnetpool | get_subnetpool | 200 | ext |
| PUT | /v2.0/subnetpools/{id} | neutron/db/db_base_plugin_v2.py:1387 update_subnetpool | update_subnetpool | 200 | ext |
| DELETE | /v2.0/subnetpools/{id} | neutron/db/db_base_plugin_v2.py:1452 delete_subnetpool | delete_subnetpool | 204 | ext |
| PUT | /v2.0/subnetpools/{id}/add_prefixes | neutron/db/db_base_plugin_v2.py:1792 add_prefixes | add_prefixes | 200 | ext |
| PUT | /v2.0/subnetpools/{id}/onboard_network_subnets | neutron/db/db_base_plugin_v2.py:1461 onboard_network_subnets | onboard_network_subnets | 200 | ext |
| PUT | /v2.0/subnetpools/{id}/remove_prefixes | neutron/db/db_base_plugin_v2.py:1810 remove_prefixes | remove_prefixes | 200 | ext |
| GET | /v2.0/subnets | neutron/db/db_base_plugin_v2.py:1214 get_subnets | get_subnet | 200 | pecan |
| POST | /v2.0/subnets | neutron/plugins/ml2/plugin.py:1491 create_subnet | create_subnet | 201 | pecan |
| GET | /v2.0/subnets/{subnet_id}/tags | neutron/extensions/tagging.py:206 index | — | 200 | ext |
| POST | /v2.0/subnets/{subnet_id}/tags | neutron/extensions/tagging.py:228 create | — | 200 | ext |
| PUT | /v2.0/subnets/{subnet_id}/tags | neutron/extensions/tagging.py:269 update_all | — | 200 | ext |
| DELETE | /v2.0/subnets/{subnet_id}/tags | neutron/extensions/tagging.py:306 delete_all | — | 204 | ext |
| GET | /v2.0/subnets/{subnet_id}/tags/{id} | neutron/extensions/tagging.py:216 show | — | 204 | ext |
| PUT | /v2.0/subnets/{subnet_id}/tags/{id} | neutron/extensions/tagging.py:247 update | — | 201 | ext |
| DELETE | /v2.0/subnets/{subnet_id}/tags/{id} | neutron/extensions/tagging.py:288 delete | — | 204 | ext |
| GET | /v2.0/subnets/{id} | neutron/db/db_base_plugin_v2.py:1209 get_subnet | get_subnet | 200 | pecan |
| PUT | /v2.0/subnets/{id} | neutron/plugins/ml2/plugin.py:1526 update_subnet | update_subnet | 200 | pecan |
| DELETE | /v2.0/subnets/{id} | neutron/plugins/ml2/plugin.py:1548 delete_subnet | delete_subnet | 204 | pecan |
| GET | /v2.0/trunks | neutron/services/trunk/plugin.py:226 get_trunks | get_trunk | 200 | ext |
| POST | /v2.0/trunks | neutron/services/trunk/plugin.py:236 create_trunk | create_trunk | 201 | ext |
| GET | /v2.0/trunks/{trunk_id}/tags | neutron/extensions/tagging.py:206 index | — | 200 | ext |
| POST | /v2.0/trunks/{trunk_id}/tags | neutron/extensions/tagging.py:228 create | — | 200 | ext |
| PUT | /v2.0/trunks/{trunk_id}/tags | neutron/extensions/tagging.py:269 update_all | — | 200 | ext |
| DELETE | /v2.0/trunks/{trunk_id}/tags | neutron/extensions/tagging.py:306 delete_all | — | 204 | ext |
| GET | /v2.0/trunks/{trunk_id}/tags/{id} | neutron/extensions/tagging.py:216 show | — | 204 | ext |
| PUT | /v2.0/trunks/{trunk_id}/tags/{id} | neutron/extensions/tagging.py:247 update | — | 201 | ext |
| DELETE | /v2.0/trunks/{trunk_id}/tags/{id} | neutron/extensions/tagging.py:288 delete | — | 204 | ext |
| GET | /v2.0/trunks/{id} | neutron/services/trunk/plugin.py:220 get_trunk | get_trunk | 200 | ext |
| PUT | /v2.0/trunks/{id} | neutron/services/trunk/plugin.py:276 update_trunk | update_trunk | 200 | ext |
| DELETE | /v2.0/trunks/{id} | neutron/services/trunk/plugin.py:300 delete_trunk | delete_trunk | 204 | ext |
| PUT | /v2.0/trunks/{id}/add_subports | neutron/services/trunk/plugin.py:331 add_subports | add_subports | 200 | ext |
| GET | /v2.0/trunks/{id}/get_subports | neutron/services/trunk/plugin.py:447 get_subports | get_subports | 200 | ext |
| PUT | /v2.0/trunks/{id}/remove_subports | neutron/services/trunk/plugin.py:389 remove_subports | remove_subports | 200 | ext |
3.1 Routes that exist but are not implemented
The Routes mapper registers the five standard operations for every extension collection, plus GET …/new and GET …/{id}/edit for each (mapper.resource, neutron/api/extensions.py:180-184). When the plugin or the custom controller lacks the matching method, the request fails with 500 "Request Failed: internal server error while processing your request." (AttributeError caught by neutron/api/v2/resource.py:97-110), except when an earlier step answers first (an update or delete fetches the object, so an unknown ID gives 404). Checked in the reference run: POST /v2.0/availability_zones, GET /v2.0/service-providers/x, GET /v2.0/floatingip-pools/x, GET /v2.0/routers/x/l3-agents/y, GET /v2.0/auto-allocated-topology (list), GET /v2.0/routers/new → 500; PUT /v2.0/security-group-rules/x → 404. The 54 such operations are in _rewrite/routes-unimplemented_stable-2026.2.json; among them /v2.0/qos/ports and /v2.0/qos/networks (all methods), registered by a QoS extension although no plugin serves them (GET /v2.0/qos/ports → 500 in the reference run). The alias QoS rule collections (/v2.0/qos/alias-<type>-rules) only work by ID: listing them answers 500. A rewrite should answer 404 or 405 for them; no client can depend on a 500.
Two spellings of the same collection. Collections whose name contains a dash are registered dashed in the mapper (/v2.0/network-segment-ranges, /v2.0/qos/policies/{id}/minimum-packet-rate-rules). The underscore spelling used by parts of the api-ref (/v2.0/network_segment_ranges, …/minimum_packet_rate_rules) is not matched by the mapper but answered by pecan's fallback controllers, with the same data in compact JSON (checked in the reference run). Tag sub-resources are registered with the underscore form of the parent (/v2.0/network_segment_ranges/{id}/tags). A rewrite should accept both spellings.
QoS packet-rate-limit rules are registered without the /qos prefix (/v2.0/policies/{policy_id}/packet_rate_limit_rules, and the tags of QoS policies under /v2.0/policies/{policy_id}/tags). The documented form /v2.0/qos/policies/{policy_id}/packet_rate_limit_rules also answers in the reference run: the mapper does not match it, so it falls through to the pecan controllers that neutron/pecan_wsgi/startup.py:61-120 builds for extension resources (neutron/pecan_wsgi/controllers/root.py:121-150); the probe run confirms it: the /qos/… form is served by pecan, the unprefixed form by the legacy controller, both answering QosPolicyNotFound for an unknown policy (_rewrite/pipeline-probes_stable-2026.2.json).
4. Functional description by resource
The behaviour after the response (OVN NB transactions, status changes) is described in 05; the sheets below say what each call is for, its rules and its synchronous effects. All sheets assume the reference profile.
4.1 Networks — /v2.0/networks
- Purpose: create an L2 broadcast domain. In OVN it becomes a
Logical_Switchnamedneutron-<network id>. - Actor: project member (
create_network: admin orrole:memberon its own project); provider attributes (provider:network_type,provider:physical_network,provider:segmentation_id,segments) are admin-only to set and to read (create_network:provider:network_type=rule:admin_only);sharedandrouter:externalare admin-only on create. - Business rules:
- Without provider attributes, ML2 allocates a segment of the first
[ml2]/project_network_typestype (heregeneve, VNI from[ml2_type_geneve]/vni_rangesor from the network segment ranges) (neutron/plugins/ml2/plugin.py:1296). - The OVN driver only accepts
local,flat,geneve,vxlan,vlan(neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py:608-613) and refuses a segmentation ID above OVN'sNB_Global.options:max_tunid(neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py:622-643); both → 400InvalidInput. mtuis computed from the segment type and[DEFAULT]/global_physnet_mtuminus the Geneve header ([ml2_type_geneve]/max_header_size); withnet-mtu-writablethe owner may lower it, but not below the MTU needed by its IPv6 subnets:NetworkMTUSubnetConflict(neutron/db/db_base_plugin_v2.py:561-569).shared: falseon a network that other projects use → 409InvalidSharedSetting(neutron/db/db_base_plugin_v2.py:328,376).availability_zone_hintsmust name OVN availability zones (gateway chassis withovn-cms-options=availability-zones=…), see § 4.17.
- Without provider attributes, ML2 allocates a segment of the first
- Flow: 1. (sync) precommit: segments validated, initial row in
ovn_revision_numbers(neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py:737-764); commit. 2. (sync, after commit, before the response) postcommit: one OVSDB transaction creates theLogical_Switchwithexternal_ids(name, MTU, revision, AZ hints, network type, physnet) andother_config(multicast snooping, VLAN passthrough, FDB ageing), plus alocalnetport per segment that has a physical network (neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py:2414-2494); the revision is bumped; then the metadata port (device_owner: network:distributed) is created when[ovn]/ovn_metadata_enabledis set (neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py:3196). 3. (async) nothing else; if step 2 failed, the network stays in the Neutron DB and the maintenance task re-creates the switch (05). - Outcome:
status: ACTIVEimmediately;admin_state_uphas no data-plane effect in OVN for networks. - Limitations: a postcommit failure on create deletes the network again and returns the error (
neutron/plugins/ml2/plugin.py:1301-1316); on update and delete, a postcommit failure is only logged and repaired later by the maintenance task. - Delete: refused with 409
NetworkInUsewhile non-service ports exist (neutron/db/db_base_plugin_v2.py:581); DHCP, router-gateway and distributed (metadata) ports are deleted automatically. OVN:ls_del(neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py:2496-2500).
4.2 Subnets — /v2.0/subnets
- Purpose: attach an IPv4 or IPv6 address range to a network; in OVN it becomes a
DHCP_Optionsrow (whenenable_dhcp) used byovn-controller's native DHCP responder. - Actor: owner of the network (
create_subnet: admin or member withrule:network_owner); a subnet on a shared network may be created by others only if the policy allows it. - Business rules (
neutron/db/db_base_plugin_v2.py:934,neutron/db/ipam_backend_mixin.py):cidrorsubnetpool_id(+prefixlen) required; gateway inside the CIDR and outside the allocation pools (GatewayConflictWithAllocationPools,neutron/db/ipam_backend_mixin.py:468); pools inside the CIDR and not overlapping (InvalidAllocationPool,OutOfBoundsAllocationPool,OverlappingAllocationPools,neutron/db/ipam_backend_mixin.py:326-356); at most[DEFAULT]/max_dns_nameservers(5) DNS servers and[DEFAULT]/max_subnet_host_routes(20) host routes (neutron/db/db_base_plugin_v2.py:779,793);ipv6_ra_mode/ipv6_address_modecombinations are checked (neutron/db/db_base_plugin_v2.py:721-753); all subnets of a network must use subnet pools of the same address scope (NetworkSubnetPoolAffinityError,neutron/db/ipam_backend_mixin.py:282-294); with routed networks every subnet of a multi-segment network must have asegment_id(SubnetsNotAllAssociatedWithSegments,neutron/db/ipam_backend_mixin.py:382-389); overlapping CIDRs on the same network are refused. - Flow: (sync) IPAM allocation (
internaldriver, tablesipamsubnets,ipamallocationpools), precommit revision row, commit; postcommit creates or updatesDHCP_Optionsand the metadata port's IP on that subnet (neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py:834-851,neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py:2985). An update ofgateway_ip,host_routesor DNS servers also updates routers attached to the subnet (neutron/services/ovn_l3/plugin.py:395-430). - Delete: 409
SubnetInUsewhile ports other than DHCP/metadata/router-gateway use it (neutron/db/db_base_plugin_v2.py:1151,1162).
4.3 Ports — /v2.0/ports
- Purpose: reserve a MAC and IP addresses on a network for a VM interface, a router interface, a floating IP, a load-balancer VIP… In OVN it becomes a
Logical_Switch_Port(LSP) named after the port ID. - Actor: admin, owner of the network, or any member on a shared network (
create_port); binding attributes (binding:host_id,binding:profile) are writable only by admin or theservicerole (Nova),binding:vif_detailsreadable only by them;mac_address,fixed_ipswith an explicit IP on a network not owned,device_ownervalues starting withnetwork:andport_security_enabledhave their own rules (neutron/conf/policies/port.py). - Business rules: MAC unique on the network (
MacAddressInUse,neutron/plugins/ml2/plugin.py:1723); requested IPs valid for the subnet/network (InvalidIpForSubnet,InvalidIpForNetwork,neutron/db/ipam_backend_mixin.py:437,449), free (IpAddressAlreadyAllocated), or allocation fails withIpAddressGenerationFailure(neutron/db/ipam_pluggable_backend.py:140);allowed_address_pairsand security groups requireport_security_enabled(AddressPairAndPortSecurityRequired,PortSecurityAndIPRequiredForSecurityGroups,neutron/plugins/ml2/plugin.py:1594-1602); OVN refuses allowed address pairs that reuse the IP of the network's metadata port (neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py:645-695);device_idof a router must belong to the caller (DeviceIDNotOwnedByTenant,neutron/db/db_base_plugin_v2.py:1786-1789); the device_owner of an OVN-"ignored" port (e.g.network:floatingip) cannot be changed to or from (OVNPortUpdateError, 400,neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py:952-967); fixed IPs of a port that carries a floating IP cannot be changed (neutron/db/l3_db.py:1825); unsupportedbinding:profilecontent → 400 (validate_and_get_data_from_binding_profile). - Flow (create) (
neutron/plugins/ml2/plugin.py:1675):- (sync, transaction) port row, IP allocation, security-group bindings (default SG of the project if none given), extension data; precommit of OVN: initial revision, and a provisioning block
L2when the port is bound to a host that has an OVN chassis (neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py:866-932). - (sync, after commit) postcommit: one NB transaction creates the LSP with addresses,
port_security,options(requested-chassis…),external_ids, DHCP options, the port group of each security group (or the drop group), DNS records and QoS (neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py:620-715); the DHCP provisioning entity is completed at once (neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py:943-950). - (sync) if
binding:host_idis set, ML2 binds the port (neutron/plugins/ml2/plugin.py:651, up toMAX_BIND_TRIES= 10 attempts): the OVN driver binds only if the host has a live OVN controller agent and, for flat/VLAN segments, a bridge mapping for the physical network; it choosesvif_typeovs(orvhostuserfor DPDK,agilio_ovsfor virtio-forwarder) and returns the integration bridge and datapath type invif_details(neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py:1226-1405). Otherwisebinding:vif_typestaysbinding_failed(no error is returned). - (async) when
ovn-controllerclaims the port, theLogical_Switch_Port.upcolumn becomestrue; the server's OVSDB event callsset_port_status_up, which completes the provisioning block; ML2 then setsstatus: ACTIVEand Nova receivesnetwork-vif-plugged(neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py:1453-1507,neutron/plugins/ml2/plugin.py:370). See 05 and the port lifecycle below.
- (sync, transaction) port row, IP allocation, security-group bindings (default SG of the project if none given), extension data; precommit of OVN: initial revision, and a provisioning block
- Outcome:
status: DOWNin the response;ACTIVEonce OVN reports the port up; back toDOWNwhen it goes down. - Update: re-binding when
binding:host_idchanges (live migration uses thebindingssub-resource, § 4.4); OVN LSP updated after commit, with a retry onRevisionConflictduring a live migration (neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py:969-996). - Delete: router interface and gateway ports can only be deleted through the router API (
ServicePortInUse409,neutron/plugins/ml2/plugin.py:2224,neutron/db/l3_db.py:1864); floating IPs associated with the port are disassociated (neutron/db/l3_db.py:1868); OVN LSP removed after commit (neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py:1209-1224). - Limitations:
binding:vnic_typeother thannormal,direct,direct-physical,macvtap,vdpa,remote-managed,baremetal,virtio-forwarderis not bound by OVN (neutron/common/ovn/constants.py:502-510);directports become OVNexternalports served by the HA chassis group of the network.
4.4 Port bindings — /v2.0/ports/{port_id}/bindings (extension binding-extended)
- Purpose: let Nova prepare a port on the destination host of a live migration before switching to it.
- Actor: admin or service role.
- Rules: one binding per host;
POSTfor a host that already has a binding → 409PortBindingAlreadyExists(neutron/plugins/ml2/plugin.py:2808); the new binding isINACTIVE; binding failure → 500-classPortBindingError(neutron/plugins/ml2/plugin.py:2835). - **
PUT …/bindings/{host}/activate** (neutron/plugins/ml2/plugin.py:2917): makes the host's bindingACTIVEand the old oneINACTIVE; 409PortBindingAlreadyActiveif already active (neutron/plugins/ml2/plugin.py:2929). In OVN the LSPoptions:requested-chassislists both chassis during the migration and is reduced afterwards (neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py:552). DELETE …/bindings/{host}: removes an inactive binding; deleting the active one is refused.
4.5 Subnet pools and address scopes — /v2.0/subnetpools, /v2.0/address-scopes
- Purpose: hand out subnet CIDRs automatically from a set of prefixes; an address scope groups pools whose addresses may be routed without NAT.
- Rules: prefixes of a pool may only grow (
IllegalSubnetPoolPrefixUpdate,neutron/db/db_base_plugin_v2.py:108); the pool's address scope must have the same IP version (IllegalSubnetPoolIpVersionAssociationToAddressScope,neutron/db/db_base_plugin_v2.py:1267) and its prefixes must not overlap another pool of the scope (AddressScopePrefixConflict,neutron/db/db_base_plugin_v2.py:1283); a pool in use cannot be deleted (SubnetPoolDeleteError,neutron/db/db_base_plugin_v2.py:1457);is_defaultpools are used when a subnet is created withuse_default_subnetpool.PUT /subnetpools/{id}/add_prefixesandremove_prefixes(extensionsubnetpool-prefix-ops) are member actions.POST /subnetpools/{id}/onboard_network_subnetsadopts existing subnets (subnet_onboard). - OVN effect: the address scope of the router's subnets is copied to the LRP
external_idsand controls whether the gateway SNATs [inferred,neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py:1889].
4.6 Routers — /v2.0/routers
- Purpose: route between project subnets and towards an external network, with SNAT for the private subnets. In OVN: a
Logical_Routerneutron-<router id>, oneLogical_Router_Port(LRP) per interface and gateway port,NATrows for SNAT/floating IPs,Logical_Router_Static_Routefor the default route and extra routes, gateway ports scheduled on gateway chassis. - Actor: project member;
external_gateway_info.external_fixed_ipsandenable_snatare admin-only by default;ha,distributedadmin-only. - Business rules: the gateway network must be
router:external(neutron/db/l3_db.py:419); a requested gateway IP may not be the subnet's gateway IP (neutron/db/l3_db.py:429); removing the gateway while floating IPs use it → 409RouterExternalGatewayInUseByFloatingIp(neutron/db/l3_db.py:474); a router with interfaces cannot be deleted (RouterInUse409,neutron/db/l3_db.py:638,514,634);availability_zone_hintsmust name existing OVN AZs (neutron/services/ovn_l3/plugin.py:469-482). - Flow (create): (sync) DB row, gateway port creation on the external network (
neutron/db/l3_db.py:516), precommit revision row (neutron/services/ovn_l3/service_providers/ovn.py:46); after commit theAFTER_CREATEcallback creates the logical router with its options (always_learn_from_arp_request=false,dynamic_neigh_routers=true, MAC-binding age), the gateway LRP, its HA chassis group, the default route and the SNAT rules (neutron/services/ovn_l3/service_providers/ovn.py:66-78,neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py:1647-1677,1465). - Outcome:
status: ACTIVEimmediately. OVN routers have noPENDING_*states; failures are logged and the router is fixed by the maintenance task. - **
PUT /routers/{id}/add_router_interface**{"subnet_id"}or{"port_id"}(neutron/db/l3_db.py:981): exactly one of them (neutron/db/l3_db.py:749-759); the subnet must have a gateway IP (neutron/db/l3_db.py:917), not overlap another subnet already on the router (neutron/db/l3_db.py:622), and belong to the caller unless admin (neutron/db/l3_db.py:936); a port must have at least one fixed IP (neutron/db/l3_db.py:781) and not be in use (PortInUse,neutron/db/l3_db.py:776); only one IPv4 subnet per router port (neutron/db/l3_db.py:835); IPv6 subnets of one network share one port (neutron/db/l3_db.py:899). Response:{"id", "subnet_id", "subnet_ids", "port_id", "network_id", "project_id", "tenant_id"}with 200. OVN: LRP created by theROUTER_INTERFACE AFTER_CREATEcallback (neutron/services/ovn_l3/service_providers/ovn.py:112-132) and the LSP of the port getstype: router. - **
PUT /routers/{id}/remove_router_interface**: by subnet or port; 404RouterInterfaceNotFound/RouterInterfaceNotFoundForSubnet(neutron/db/l3_db.py:1163,1207); 409RouterInterfaceInUseByRoutewhen an extra route still uses the subnet (neutron/db/extraroute_db.py:169); 409RouterInUsewhen floating IPs depend on it (neutron/db/l3_db.py:1154). - Extra routes (
routesattribute,PUT add_extraroutes/remove_extraroutesfromextraroute-atomic,neutron/db/extraroute_db.py:221): next hop must be on a connected subnet (InvalidRoutes,neutron/db/extraroute_db.py:88-93); at most[DEFAULT]/max_routes(30, and quotarouter_route). OVN: static routes (neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py:1567). - Multiple gateways (
external-gateway-multihoming):PUT add_external_gateways,update_external_gateways,remove_external_gateways(neutron/db/l3_extra_gws_db.py:461) manage theexternal_gatewayslist; OVN can spread the default route over them with ECMP and BFD (enable_default_route_ecmp,enable_default_route_bfd). - Gateway placement: the gateway port is scheduled on up to 5 gateway chassis with priorities (an
HA_Chassis_Group), chosen by[ovn]/ovn_l3_scheduleramong the chassis that haveenable-chassis-as-gwand the external network's physnet, filtered by the router's AZ hints; see 05-async-processing_stable-2026.2.md § 7. No candidate does not fail the API call: the router is created without external connectivity. - L3 agent scheduling under OVN:
GET /routers/{id}/l3-agentsand/agents/{id}/l3-routersare answered by the OVN plugin from the gateway chassis priorities of the router (neutron/services/ovn_l3/plugin.py:500-650);PUT /agents/{agent_id}/l3-routers/{router_id}withha_chassis_priority(extensionl3-agent-scheduler-ha-chassis-priority) changes the priority of a chassis.
4.7 Floating IPs — /v2.0/floatingips
- Purpose: map a public IPv4 address of an external network to a private fixed IP (1:1 NAT). OVN: a
NATrow of typednat_and_snaton the router, distributed on the compute node when[ovn]/enable_distributed_floating_ipis set, centralised on the gateway chassis otherwise. - Rules (
neutron/db/l3_db.py:1510): the floating network must be external and have an IPv4 subnet (neutron/db/l3_db.py:1528-1532); the internal port must belong to the same project unless admin (neutron/db/l3_db.py:1375), have an IPv4 fixed IP, and if it has several,fixed_ip_addressmust say which (neutron/db/l3_db.py:1395-1406);fixed_ip_addresswithoutport_id→ 400 (neutron/db/l3_db.py:1447); the port's subnet must be attached to a router that has a gateway on the floating network (ExternalGatewayForFloatingIPNotFound404, or 409 "Router … has no external gateway on network …",neutron/db/l3_db.py:1357); a port/IP pair may carry only one floating IP (FloatingIPPortAlreadyAssociated409,neutron/db/l3_db.py:1467); the address pool may be exhausted (ExternalIpAddressExhausted400,neutron/db/l3_db.py:1578). - Flow: (sync) allocate a port with
device_owner: network:floatingipon the external network, create the row (DOWN), commit; OVN: initial revision in precommit, thencreate_floatingipadds the NAT row and, if the FIP has a router, **setsstatus: ACTIVEimmediately** (neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py:1280-1301); the response ofPOSTmay still showDOWNbecause the status update happens after the object was built [inferred]. - Update:
port_id: nulldisassociates (NAT removed, statusDOWN); changingport_idre-creates the NAT.qos_policy_idapplies a FIP QoS policy (OVN: meters on the NAT,qos-fip). - Port forwardings —
/v2.0/floatingips/{fip_id}/port_forwardings(neutron/services/portforwarding/pf_plugin.py:352): DNAT of a protocol/port (or range) of a floating IP that is not associated with a port; OVN implements each as a VIP of aLoad_Balancernamedpf-floatingip-<fip id>-<tcp|udp>(one per FIP and protocol; port ranges get their own balancer suffixed-<external_port>), attached to the router and to the router's internal switches, VIP<floating ip>:<external_port>→<internal ip>:<internal_port>(neutron/services/portforwarding/drivers/ovn/driver.py:42-132). Rules (neutron/services/portforwarding/pf_plugin.py:352): the floating IP must not be associated with a port (FipInUseByPortForwarding,PortHasBindingFloatingIP,neutron/services/portforwarding/pf_plugin.py:181,345), the internal port must have an IPv4 address and the internal IP must be one of its fixed IPs (neutron/services/portforwarding/pf_plugin.py:293,316,615), the floating IP's router must reach the internal port's subnet (neutron/services/portforwarding/pf_plugin.py:595), no duplicate entry (neutron/services/portforwarding/pf_plugin.py:392,463) and no collision between port ranges of the same floating IP (neutron/services/portforwarding/pf_plugin.py:482), all 400; a port with forwardings cannot be deleted (PortHasPortForwarding,neutron/services/portforwarding/pf_plugin.py:149). With distributed floating IPs, forwardings on routers attached to provider networks are logged as not working (neutron/services/portforwarding/drivers/ovn/driver.py:134-160).
4.8 Security groups — /v2.0/security-groups, /v2.0/security-group-rules
- Purpose: stateful (or stateless,
stateful: false) per-port firewall. OVN: onePort_Groupper group (pg_<id with _>) holding the LSPs, and oneACLper rule on that port group; ports with port security but no group are in theneutron_pg_dropgroup. - Rules: each project gets a
defaultgroup created on first need, with rules copied from the default security group rules templates (/v2.0/default-security-group-rules, admin-only,neutron/db/securitygroups_db.py:595);defaultcannot be deleted (SecurityGroupCannotRemoveDefault,neutron/db/securitygroups_db.py:271) or renamed (neutron/db/securitygroups_db.py:324); a group in use by ports cannot be deleted (SecurityGroupInUse409,neutron/db/securitygroups_db.py:266); stateful cannot change while in use (neutron/db/securitygroups_db.py:314); rule checks: protocol required with ports, port range valid, ICMP type/code ranges, ethertype consistent with protocol and remote prefix, only one ofremote_ip_prefix/remote_group_id/remote_address_group_id(neutron/db/securitygroups_db.py:527,768-853), no duplicate rule (SecurityGroupRuleExists409,neutron/db/securitygroups_db.py:1014). Rules cannot be updated (noPUT, § 3.1). - Flow: (sync) DB rows; OVN port group and ACLs created in callbacks before the response (
neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py:476-606).statefulgroups giveallow-relatedACLs, stateless giveallow-stateless. - **
/v2.0/security-groups-default-statefulness** (neutron/db/security_groups_default_statefulness.py:52-95): per-project default ofstatefulfor new groups. - Address groups —
/v2.0/address-groupswithPUT add_addresses/remove_addresses(neutron/db/address_group_db.py:84-129): named lists of CIDRs used asremote_address_group_id; OVN keeps them inAddress_Setrows (neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py:3115-3165).
4.9 QoS — /v2.0/qos/policies and rule sub-resources
- Purpose: bandwidth limits, DSCP marking and minimum bandwidth guarantees for ports, networks, floating IPs and router gateways.
- Rules: the OVN driver supports
bandwidth_limit(egress and ingress),dscp_marking,minimum_bandwidth(neutron/services/qos/drivers/ovn/driver.py:25-41) onnormalports;GET /qos/rule-typeslists what the loaded drivers support (neutron/services/qos/qos_plugin.py:847); a rule type the driver cannot apply to a bound port → 409/400 at association time;minimum_bandwidthandminimum_packet_raterules feed Placement resource requests (port-resource-request-groups), so a port with such a policy carriesresource_requestthat Nova must honour; a policy in use cannot be deleted (QosPolicyInUse409);is_default(extensionqos-default) makes it the project default for new networks. - Rule paths:
/v2.0/qos/policies/{policy_id}/bandwidth_limit_rules,dscp_marking_rules,minimum_bandwidth_rules,minimum-packet-rate-rules,/v2.0/policies/{policy_id}/packet_rate_limit_rules(see § 3.1), and the alias forms/v2.0/qos/alias-<type>-rules/{id}that address a rule by ID only. All are synthesised byQoSPluginBase.__getattr__(neutron/extensions/qos.py:106-185) onto the genericcreate_policy_rule,update_policy_rule… (neutron/services/qos/qos_plugin.py:862-1065). - OVN effect (
neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/qos.py:114-207): per port and direction, at most one NBQoSrow on the port's logical switch,directionfrom-lport(Neutron egress,inport == "<port>") orto-lport(ingress,outport == "<port>"), priority 2002,bandwidthrate=max_kbpsandburst=max_burst_kbps(kbit/s),actiondscp=dscp_mark; a bandwidth-limit rule and a DSCP rule of the same direction are merged into that one row. Floating IP and router gateway policies use priority 2003 withip4.src/ip4.dst == <FIP>andis_chassis_resident("<gateway port>")(neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/qos.py:34-35). Minimum bandwidth and the egress maximum of ports on provider (physnet) networks are written as LSPoptions:qos_min_rate/qos_max_ratein bit/s (max_kbps × 1000), applied by OVN on the localnet egress queue (neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/qos.py:225-266). The effective policy of a port is its ownqos_policy_id, else its network's (neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/qos.py:310).
4.10 Trunks — /v2.0/trunks
- Purpose: carry several networks to one VM interface with VLAN tags (sub-ports).
- Rules (
neutron/services/trunk/plugin.py:236,neutron/services/trunk/rules.py): the parent port must not be in another trunk or be a sub-port (TrunkPortInUse,ParentPortInUse,neutron/services/trunk/rules.py:67-95); sub-ports needsegmentation_typeandsegmentation_id(neutron/services/trunk/rules.py:275-296), a uniquesegmentation_id(1–4094 forvlan) inside the trunk, and an MTU not greater than the parent's (SubPortMtuGreaterThanTrunkPortMtu,neutron/services/trunk/rules.py:258-273); a port used as parent or sub-port cannot be deleted (PortInUseAsTrunkParent,PortInUseAsSubPort,neutron/services/trunk/rules.py:43-58); a trunk in a transient state cannot be changed (TrunkDisabled,neutron/services/trunk/rules.py:37-40);PUT add_subports/remove_subports(neutron/services/trunk/plugin.py:331,389);GET get_subports; a trunk whose parent is bound cannot be deleted unless the driver allows it. - OVN effect: each sub-port's LSP gets
parent_name= parent port andtag= VLAN; its binding profile and status follow the parent (neutron/services/trunk/drivers/ovn/trunk_driver.py:50-120). - Status:
ACTIVEset at create and on every sub-port change by the OVN trunk driver (neutron/services/trunk/drivers/ovn/trunk_driver.py:183-215), and when the parent port becomesACTIVE(neutron/services/trunk/drivers/ovn/trunk_driver.py:217-258).
4.11 Segments and network segment ranges — /v2.0/segments, /v2.0/network-segment-ranges
- Segments (
neutron/services/segments/db.py:89-192): admin-only management of the L2 segments of a network ("routed provider networks"); each segment has its own subnets, and hosts are mapped to segments through the OVN chassis bridge mappings (neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py:1554-1594). In OVN, a network with several segments is one logical switch with onelocalnetport per segment that has a physical network (create_segment_provnet_port,neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py:720-735). With theplacementservice plugin, segments are reported to Placement as resource providers with anIPV4_ADDRESSinventory [inferred]. - Network segment ranges (
neutron/services/network_segment_range/plugin.py:156-247): admin-only pools of VLAN IDs / VNIs, shared or reserved for a project, from which project networks get their segmentation ID; ranges in use cannot shrink below allocated IDs.
4.12 RBAC — /v2.0/rbac-policies
- Purpose: share a network, QoS policy, security group, address scope, subnet pool or address group with another project (
access_as_shared), or make a network external for some projects (access_as_external) (neutron/db/rbac_db_mixin.py:37-133). - Rules: only the owner (or admin) creates entries; deleting an entry that a project still uses (ports on the shared network) → 409
RbacPolicyInUse; target*= all projects; quotarbac_policy.
4.13 Quotas — /v2.0/quotas
GET /quotas(admin: all projects with non-default quotas),GET /quotas/{project},PUT /quotas/{project}{"quota": {...}}(admin),DELETE /quotas/{project}(back to defaults),GET /quotas/{project}/default,GET /quotas/{project}/details(limit, used, reserved),GET /quotas/project(the caller's) (neutron/extensions/quotasv2.py:82-130,neutron/extensions/quotasv2_detail.py:54). Withquota-check-limit,PUTwith"check_limit": truerefuses a limit below current usage. Defaults come from[quotas]/quota_<resource>.
4.14 Agents and availability zones — /v2.0/agents, /v2.0/availability_zones
- Under OVN there are no agent rows for OVN controllers:
GET /agentsis answered by the OVN driver from the SouthboundChassis/Chassis_Privatetables (typesOVN Controller agent,OVN Controller Gateway agent,OVN Metadata agent,OVN Neutron agent) merged with the DB agents (neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py:1715-1776);aliveis computed from thenb_cfgheartbeat (05). PUT /agents/{id}only changesdescription/admin_state_up;DELETEremoves a dead chassis' agent (neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py:1730-1776).POST /agents→ 400 "Agent creation using API is not supported." (neutron/extensions/agent.py:58).GET /availability_zones: OVN AZs come from gateway chassisovn-cms-options(neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py:1640-1660).- The DHCP agent scheduler API (
/agents/{id}/dhcp-networks,/networks/{id}/dhcp-agents) is loaded but meaningless with OVN's native DHCP:GET /networks/x/dhcp-agentsanswers an empty list.
4.15 Tags — /v2.0/<collection>/{id}/tags
Replace all (PUT …/tags {"tags": [...]}), add one (PUT …/tags/{tag} → 201), check one (GET …/tags/{tag} → 204 or 404), remove one or all (neutron/extensions/tagging.py:206-306). Tags are also accepted in the create body (tag-creation) and are filterable (tags, tags-any, not-tags, not-tags-any). A tag ending in .json is truncated by the Routes suffix handling (neutron/api/v2/resource.py:74-87).
4.16 Network logging — /v2.0/log/logs, /v2.0/log/loggable-resources
Admin-only. Logs accepted/dropped packets of security groups (resource_type: security_group, the only type of the OVN driver, neutron/services/logapi/drivers/ovn/driver.py:45). OVN sets log, meter (acl_log_meter) and severity on the ACLs; ovn-controller writes the entries to its own log (no Neutron agent).
4.17 Other read-only or administrative resources
GET /v2.0/network-ip-availabilities[/{network_id}](admin): used and total IPs per subnet (neutron/services/network_ip_availability/plugin.py:50).GET /v2.0/auto-allocated-topology/{project_id}(neutron/services/auto_allocate/db.py:132): "get me a network" — creates on first call a network, subnets from the default subnet pools and a router to the default external network, and returns the network ID;?fields=dry-runonly validates.DELETEremoves it./v2.0/flavors,/v2.0/service_profiles,/v2.0/flavors/{id}/service_profiles(neutron/db/flavors_db.py:93-217): admin-defined flavours of service providers; with the OVN L3 plugin a routerflavor_idcan select a user-defined L3 provider (neutron/services/ovn_l3/service_providers/user_defined.py).GET /v2.0/service-providers(neutron/db/servicetype_db.py:48),GET /v2.0/floatingip-pools,GET /v2.0/qos/rule-types[/{type}].GET /v2.0/extensions[/{alias}]: list of the 110 loaded extensions (_rewrite/routes_stable-2026.2.json, keyextensions); clients (Nova, Octavia, Horizon) decide features from this list, so a rewrite must return the same aliases.GET /: version document{"versions": [{"id": "v2.0", "status": "CURRENT", "links": […]}]}(neutron/pecan_wsgi/controllers/root.py:60-69).
5. Lifecycles
Port (status)
stateDiagram-v2 [*] --> DOWN: POST /ports (API) DOWN --> DOWN: binding (host_id set, provisioning block L2 added) DOWN --> ACTIVE: LSP up=true (OVSDB event) → provisioning complete ACTIVE --> DOWN: LSP up=false (OVSDB event) → block re-added ACTIVE --> DOWN: binding host changed (migration) DOWN --> ACTIVE: router port: LSP up (status set directly) DOWN --> [*]: DELETE /ports ACTIVE --> [*]: DELETE /ports
BUILD and ERROR exist in the model but the OVN driver does not use them. The maintenance task re-applies the status after a resync (05).
Floating IP (status)
stateDiagram-v2 [*] --> DOWN: POST without port_id [*] --> ACTIVE: POST with port_id (OVN NAT created after commit) DOWN --> ACTIVE: PUT port_id (NAT created) ACTIVE --> DOWN: PUT port_id=null / port deleted ACTIVE --> [*]: DELETE DOWN --> [*]: DELETE
Trunk (status)
stateDiagram-v2 [*] --> ACTIVE: POST /trunks (OVN trunk driver) ACTIVE --> ACTIVE: add_subports / remove_subports DOWN --> ACTIVE: parent port becomes ACTIVE ACTIVE --> [*]: DELETE
The DOWN, BUILD, DEGRADED, ERROR statuses of the trunk model are only set by the OVS agent driver, which is out of scope.
Networks, subnets, routers, security groups and QoS policies have no asynchronous status under OVN: networks and routers are ACTIVE as soon as the call returns.
Source: reverse-spec-neutron/01-rest-api_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.