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

Reverse-spec · Services

octavia — REST API

REST API — octavia — branch stable/2026.2

Branch stable/2026.2 @ 2900ed67cac2 (19.0.0-19-g2900ed67) — repository https://opendev.org/openstack/octavia (local clone ../octavia) — analysed on 2026-11-01

This file is the contract of the Octavia v2 API as served by the Python code. "[probe]" marks behaviour observed on the real application by _rewrite/tools/octavia_probe.py (results in _rewrite/route-probe_stable-2026.2.json, _rewrite/http-probes_stable-2026.2.json); test evidence is in _rewrite/exchanges_stable-2026.2.json. Errors are detailed in 02, what happens behind the provider call in 03.

1. Request path

1.1 WSGI pipeline

Outermost first (octavia/api/app.py:85-117):

OrderLayerEffectWhere
1RequestBodySizeLimiter (oslo.middleware)body larger than [oslo_middleware] max_request_body_size → 413octavia/api/app.py:115
2CORS (oslo.middleware)CORS headers, also on errors of the inner layersoctavia/api/app.py:113
3HTTPProxyToWSGIhonours X-Forwarded-* when [oslo_middleware] enable_proxy_headers_parsingoctavia/api/app.py:105
4SkippingAuthProtocol (only auth_strategy=keystone)keystonemiddleware auth_token, skipped for /, /load-balancer/, /healthcheck, /load-balancer/healthcheckoctavia/api/app.py:102-103, octavia/common/keystone.py:25-26, octavia/common/keystone.py:112
5AuditMiddleware (only [audit] enabled)CADF auditoctavia/api/app.py:89-100
6RequestIdadds x-openstack-request-id to every responseoctavia/api/app.py:87
7Pecan hooksContentTypeHook, ContextHook (on_route), QueryParametersHook (before, GET only)octavia/api/config.py:21-26, octavia/api/common/hooks.py:27
8Pecan routing, then WSME wsexposebody/argument conversion, handler, serialisationoctavia/api/root_controller.py:30

Consequences for the order of errors: 401 (Keystone) comes before 406; 406 (hook) and invalid limit/sort (hook) come before the policy check; _lookup 404s (parent pool/L7 policy missing) come before WSME validation and use a different body format (02 § 1).

1.2 Path families

Pecan routes by object traversal (octavia/api/root_controller.py:35-36, octavia/api/v2/controllers/__init__.py:33-80). Each v2 controller is mounted twice, and the whole tree twice:

PrefixServesExample
/v2/lbaas/ and /v2.0/lbaas/all resources/v2/lbaas/loadbalancers
/v2/ and /v2.0/the same resources without lbaas: /v2/flavors, /v2/loadbalancers [probe]what the functional tests use for flavors and availability zones (octavia/tests/functional/api/v2/base.py:37-50)
/v2/octavia/, /v2.0/octavia/only amphorae/v2/octavia/amphorae

The API reference documents flavors and availability zones as /v2.0/lbaas/flavors… and everything else as /v2/lbaas/…. python-octaviaclient and openstacksdk use these. All spellings must keep working: 297 route entries = 75 resource operations × 4 prefixes (amphora: × 2) + discovery (_rewrite/routes_stable-2026.2.json; the field alias_of gives the canonical path).

Other routing facts [probe]:

  • a trailing slash is accepted (/v2/lbaas/loadbalancers/ = 200);
  • a .json suffix is accepted; **.xml returns XML** with status 200 (/v2/lbaas/loadbalancers.xml → <result><loadbalancers /><loadbalancers_links /></result>), because WSME picks the renderer from the extension. No client uses this; a rewrite should answer it as JSON or 404 and document the difference;
  • HEAD on a collection → 405; OPTIONS → 200 without body; PATCH → 405 with the Pecan JSON body; DELETE or PUT on a collection → 400 Missing argument: "id"; POST on an item → 400 (the id is parsed as the body argument);
  • unknown path → 404 with {"code": 404, "description": …, "title": "Not Found"}; /v3, /v2.1 → 404;
  • in sub-paths, GET /v2/lbaas/quotas/{id}/default/anything still answers 200 because _lookup returns '' as the remainder (octavia/api/v2/controllers/quotas.py:123); the Go router need not reproduce this, it is not used by clients [inferred].

1.3 Identity

ContextHook builds octavia.common.context.RequestContext.from_environ for every routed request (octavia/api/common/hooks.py:61-64). is_admin is true when the policy rule context_is_admin passes or auth_strategy == noauth (octavia/common/context.py:29-32). With noauth every request is admin, project_id comes from the X-Project-Id-style headers if present, and quotas are skipped (octavia/db/repositories.py:455-457).

auth_strategy values: keystone (default), noauth, testing (octavia/common/constants.py:739-741, octavia/common/config.py:51). testing only means "no auth_token middleware, policy enforced"; the tests pair it with forced credentials. There is no X-Test-* header: the capture records the forced identity per exchange (harness.identity, 07 § 7).

1.4 Discovery

RequestAnswerWhere
GET /{"versions": [...]}: 31 entries v2.0 … v2.30, each {"id","status","updated","links":[{"href": <request URL>/v2, "rel":"self"}]}; status is SUPPORTED, and CURRENT for v2.30; no authenticationoctavia/api/root_controller.py:64-160
GET /v2, /v2.0, /v2/lbaas, /v2/octaviaJSON string "v2" with Content-Type: application/json, not an object [probe]octavia/api/v2/controllers/__init__.py:58, octavia/api/v2/controllers/__init__.py:70
GET/HEAD /healthcheck404 unless [api_settings] healthcheck_enabled; then oslo.middleware's healthcheck with the octavia_db_check backend, cached healthcheck_refresh_interval s. Other methods → 405. Success 200 (HEAD 204), failure 503; bypasses the content-type hookoctavia/api/root_controller.py:37-49, octavia/api/healthcheck/healthcheck_plugins.py:25-65, octavia/api/common/hooks.py:24

The minor versions are documentation only: there is no microversion negotiation, /v2 serves everything. The dates in updated are a client-visible constant list: copy them from octavia/api/root_controller.py:71-159. octavia-dashboard, openstacksdk and gophercloud read the highest version to enable features (tags ≥ 2.5, vip_sg_ids ≥ 2.29…) [inferred].

2. Route table

Canonical paths only. Handler lines are the method definition; policy rule names omit the prefix os_load-balancer_api:.

MethodPathHandlerPolicy rule(s)SuccessStatuses seen in the capture (count)
GET/v2/lbaas/loadbalancersoctavia/api/v2/controllers/load_balancer.py:84 LoadBalancersController.get_allloadbalancer:get_all
loadbalancer:get_all-global
200200 (32), 403 (2)
POST/v2/lbaas/loadbalancersoctavia/api/v2/controllers/load_balancer.py:500 LoadBalancersController.postloadbalancer:post
loadbalancer:post:vip_sg_ids
201201 (1072), 400 (44), 403 (7), 409 (1), 500 (3), 501 (3)
GET/v2/lbaas/loadbalancers/{loadbalancer_id}octavia/api/v2/controllers/load_balancer.py:57 LoadBalancersController.get_oneloadbalancer:get_one200200 (3289), 403 (1), 404 (2)
PUT/v2/lbaas/loadbalancers/{loadbalancer_id}octavia/api/v2/controllers/load_balancer.py:797 LoadBalancersController.putloadbalancer:put
loadbalancer:put:vip_sg_ids
200200 (24), 400 (3), 403 (1), 404 (2), 409 (3), 500 (1), 501 (2)
DELETE/v2/lbaas/loadbalancers/{loadbalancer_id}octavia/api/v2/controllers/load_balancer.py:877 LoadBalancersController.deleteloadbalancer:delete204204 (23), 400 (2), 403 (1), 404 (2), 409 (3), 500 (1), 501 (2)
GET/v2/lbaas/loadbalancers/{loadbalancer_id}/statusoctavia/api/v2/controllers/load_balancer.py:944 StatusController.getloadbalancer:get_status200200 (9), 403 (1), 404 (1)
GET/v2/lbaas/loadbalancers/{loadbalancer_id}/statusesoctavia/api/v2/controllers/load_balancer.py:944 StatusController.getloadbalancer:get_status200200 (1)
GET/v2/lbaas/loadbalancers/{loadbalancer_id}/statsoctavia/api/v2/controllers/load_balancer.py:979 StatisticsController.getloadbalancer:get_stats200200 (2), 403 (1), 404 (1)
PUT/v2/lbaas/loadbalancers/{loadbalancer_id}/failoveroctavia/api/v2/controllers/load_balancer.py:1009 FailoverController.putloadbalancer:put_failover202202 (4), 403 (2), 404 (1), 409 (1)
GET/v2/lbaas/listenersoctavia/api/v2/controllers/listener.py:82 ListenersController.get_alllistener:get_all
listener:get_all-global
200200 (21), 403 (2)
POST/v2/lbaas/listenersoctavia/api/v2/controllers/listener.py:377 ListenersController.postlistener:post201201 (951), 400 (56), 403 (2), 404 (1), 409 (4), 500 (1)
GET/v2/lbaas/listeners/{listener_id}octavia/api/v2/controllers/listener.py:54 ListenersController.get_onelistener:get_one200200 (209), 403 (1), 404 (2)
PUT/v2/lbaas/listeners/{listener_id}octavia/api/v2/controllers/listener.py:627 ListenersController.putlistener:put200200 (35), 400 (33), 403 (1), 404 (3), 409 (2), 500 (1)
DELETE/v2/lbaas/listeners/{listener_id}octavia/api/v2/controllers/listener.py:694 ListenersController.deletelistener:delete204204 (3), 403 (1), 404 (2), 409 (2), 500 (1)
GET/v2/lbaas/listeners/{listener_id}/statsoctavia/api/v2/controllers/listener.py:747 StatisticsController.getlistener:get_stats200200 (2), 403 (1), 404 (1)
GET/v2/lbaas/poolsoctavia/api/v2/controllers/pool.py:76 PoolsController.get_allpool:get_all
pool:get_all-global
200200 (24), 403 (2)
POST/v2/lbaas/poolsoctavia/api/v2/controllers/pool.py:218 PoolsController.postpool:post201201 (1022), 400 (53), 403 (2), 409 (3), 500 (1)
GET/v2/lbaas/pools/{pool_id}octavia/api/v2/controllers/pool.py:53 PoolsController.getpool:get_one200200 (155), 403 (1), 404 (2)
PUT/v2/lbaas/pools/{pool_id}octavia/api/v2/controllers/pool.py:453 PoolsController.putpool:put200200 (21), 400 (17), 403 (1), 404 (1), 409 (2), 500 (2)
DELETE/v2/lbaas/pools/{pool_id}octavia/api/v2/controllers/pool.py:521 PoolsController.deletepool:delete204204 (4), 403 (1), 404 (2), 409 (3), 500 (1)
GET/v2/lbaas/pools/{pool_id}/membersoctavia/api/v2/controllers/member.py:74 MemberController.get_allmember:get_all200200 (26), 403 (2)
POST/v2/lbaas/pools/{pool_id}/membersoctavia/api/v2/controllers/member.py:154 MemberController.postmember:post201201 (77), 400 (6), 403 (2), 409 (4), 500 (1)
GET/v2/lbaas/pools/{pool_id}/members/{member_id}octavia/api/v2/controllers/member.py:50 MemberController.getmember:get_one200200 (26), 403 (1), 404 (4)
PUT/v2/lbaas/pools/{pool_id}/members/{member_id}octavia/api/v2/controllers/member.py:297 MemberController.putmember:put200200 (4), 400 (1), 403 (1), 404 (1), 409 (2), 500 (1)
DELETE/v2/lbaas/pools/{pool_id}/members/{member_id}octavia/api/v2/controllers/member.py:356 MemberController.deletemember:delete204204 (2), 403 (1), 404 (3), 409 (2), 500 (1)
PUT/v2/lbaas/pools/{pool_id}/membersoctavia/api/v2/controllers/member.py:400 MembersController.putmember:post
member:put
member:delete
202202 (8), 400 (3), 404 (1)
GET/v2/lbaas/healthmonitorsoctavia/api/v2/controllers/health_monitor.py:67 HealthMonitorController.get_allhealthmonitor:get_all
healthmonitor:get_all-global
200200 (25), 403 (2)
POST/v2/lbaas/healthmonitorsoctavia/api/v2/controllers/health_monitor.py:210 HealthMonitorController.posthealthmonitor:post201201 (100), 400 (16), 403 (2), 404 (1), 409 (4), 500 (1)
GET/v2/lbaas/healthmonitors/{healthmonitor_id}octavia/api/v2/controllers/health_monitor.py:49 HealthMonitorController.get_onehealthmonitor:get_one200200 (40), 403 (1), 404 (2)
PUT/v2/lbaas/healthmonitors/{healthmonitor_id}octavia/api/v2/controllers/health_monitor.py:356 HealthMonitorController.puthealthmonitor:put200200 (11), 400 (11), 403 (1), 404 (1), 409 (2), 500 (1)
DELETE/v2/lbaas/healthmonitors/{healthmonitor_id}octavia/api/v2/controllers/health_monitor.py:421 HealthMonitorController.deletehealthmonitor:delete204204 (2), 403 (1), 404 (2), 409 (2), 500 (1)
GET/v2/lbaas/l7policiesoctavia/api/v2/controllers/l7policy.py:72 L7PolicyController.get_alll7policy:get_all
l7policy:get_all-global
200200 (23), 403 (2)
POST/v2/lbaas/l7policiesoctavia/api/v2/controllers/l7policy.py:128 L7PolicyController.postl7policy:post201201 (181), 400 (20), 403 (2), 404 (1), 409 (2), 500 (1)
GET/v2/lbaas/l7policies/{l7policy_id}octavia/api/v2/controllers/l7policy.py:50 L7PolicyController.getl7policy:get_one200200 (39), 403 (1), 404 (2)
PUT/v2/lbaas/l7policies/{l7policy_id}octavia/api/v2/controllers/l7policy.py:223 L7PolicyController.putl7policy:put200200 (24), 400 (19), 403 (1), 404 (2), 409 (2), 500 (1)
DELETE/v2/lbaas/l7policies/{l7policy_id}octavia/api/v2/controllers/l7policy.py:302 L7PolicyController.deletel7policy:delete204204 (2), 403 (1), 404 (2), 409 (2), 500 (1)
GET/v2/lbaas/l7policies/{l7policy_id}/rulesoctavia/api/v2/controllers/l7rule.py:68 L7RuleController.get_alll7rule:get_all200200 (19), 403 (2)
POST/v2/lbaas/l7policies/{l7policy_id}/rulesoctavia/api/v2/controllers/l7rule.py:139 L7RuleController.postl7rule:post201201 (64), 400 (15), 403 (2), 409 (4), 500 (2)
GET/v2/lbaas/l7policies/{l7policy_id}/rules/{l7rule_id}octavia/api/v2/controllers/l7rule.py:46 L7RuleController.getl7rule:get_one200200 (26), 403 (1), 404 (3)
PUT/v2/lbaas/l7policies/{l7policy_id}/rules/{l7rule_id}octavia/api/v2/controllers/l7rule.py:216 L7RuleController.putl7rule:put200200 (6), 400 (11), 403 (1), 404 (1), 409 (2), 500 (1)
DELETE/v2/lbaas/l7policies/{l7policy_id}/rules/{l7rule_id}octavia/api/v2/controllers/l7rule.py:286 L7RuleController.deletel7rule:delete204204 (2), 403 (1), 404 (2), 409 (2), 500 (1)
GET/v2/lbaas/quotasoctavia/api/v2/controllers/quotas.py:51 QuotasController.get_allquota:get_all
quota:get_all-global
200200 (10), 403 (3)
GET/v2/lbaas/quotas/{project_id}octavia/api/v2/controllers/quotas.py:37 QuotasController.getquota:get_one200200 (15), 403 (3)
PUT/v2/lbaas/quotas/{project_id}octavia/api/v2/controllers/quotas.py:77 QuotasController.putquota:put202202 (42), 403 (1)
DELETE/v2/lbaas/quotas/{project_id}octavia/api/v2/controllers/quotas.py:101 QuotasController.deletequota:delete202202 (2), 403 (1), 404 (1)
GET/v2/lbaas/quotas/{project_id}/defaultoctavia/api/v2/controllers/quotas.py:135 QuotasDefaultController.getquota:get_defaults200200 (2), 403 (1)
GET/v2/lbaas/providersoctavia/api/v2/controllers/provider.py:39 ProviderController.get_allprovider:get_all200200 (2)
GET/v2/lbaas/providers/{provider}/flavor_capabilitiesoctavia/api/v2/controllers/provider.py:83 FlavorCapabilitiesController.get_allprovider-flavor:get_all200200 (7), 400 (1), 403 (1), 501 (1)
GET/v2/lbaas/providers/{provider}/availability_zone_capabilitiesoctavia/api/v2/controllers/provider.py:133 AvailabilityZoneCapabilitiesController.get_allprovider-availability-zone:get_all200200 (8), 400 (1), 403 (1), 501 (1)
GET/v2/lbaas/flavorsoctavia/api/v2/controllers/flavors.py:61 FlavorsController.get_allflavor:get_all200200 (4), 403 (1)
POST/v2/lbaas/flavorsoctavia/api/v2/controllers/flavors.py:86 FlavorsController.postflavor:post201201 (33), 400 (5), 403 (1), 409 (1), 500 (1)
GET/v2/lbaas/flavors/{flavor_id}octavia/api/v2/controllers/flavors.py:43 FlavorsController.get_oneflavor:get_one200200 (10), 403 (1), 404 (3)
PUT/v2/lbaas/flavors/{flavor_id}octavia/api/v2/controllers/flavors.py:119 FlavorsController.putflavor:put200200 (3), 400 (1), 403 (1), 404 (1), 500 (1)
DELETE/v2/lbaas/flavors/{flavor_id}octavia/api/v2/controllers/flavors.py:149 FlavorsController.deleteflavor:delete204204 (2), 403 (1), 404 (2), 409 (1), 500 (1)
GET/v2/lbaas/flavorprofilesoctavia/api/v2/controllers/flavor_profiles.py:66 FlavorProfileController.get_allflavor-profile:get_all200200 (4), 403 (1)
POST/v2/lbaas/flavorprofilesoctavia/api/v2/controllers/flavor_profiles.py:94 FlavorProfileController.postflavor-profile:post201201 (69), 400 (7), 403 (1), 409 (1), 500 (1)
GET/v2/lbaas/flavorprofiles/{flavorprofile_id}octavia/api/v2/controllers/flavor_profiles.py:44 FlavorProfileController.get_oneflavor-profile:get_one200200 (13), 403 (1), 404 (3)
PUT/v2/lbaas/flavorprofiles/{flavorprofile_id}octavia/api/v2/controllers/flavor_profiles.py:153 FlavorProfileController.putflavor-profile:put200200 (5), 400 (3), 403 (1), 404 (1), 409 (2)
DELETE/v2/lbaas/flavorprofiles/{flavorprofile_id}octavia/api/v2/controllers/flavor_profiles.py:215 FlavorProfileController.deleteflavor-profile:delete204204 (2), 403 (1), 404 (2), 409 (1)
GET/v2/lbaas/availabilityzonesoctavia/api/v2/controllers/availability_zones.py:68 AvailabilityZonesController.get_allavailability-zone:get_all200200 (4), 403 (1)
POST/v2/lbaas/availabilityzonesoctavia/api/v2/controllers/availability_zones.py:97 AvailabilityZonesController.postavailability-zone:post201201 (29), 400 (5), 403 (1), 409 (1), 500 (1)
GET/v2/lbaas/availabilityzones/{availability_zone_name}octavia/api/v2/controllers/availability_zones.py:41 AvailabilityZonesController.get_oneavailability-zone:get_one200200 (10), 403 (1), 404 (3)
PUT/v2/lbaas/availabilityzones/{availability_zone_name}octavia/api/v2/controllers/availability_zones.py:135 AvailabilityZonesController.putavailability-zone:put200200 (3), 400 (1), 403 (1), 404 (1), 500 (1)
DELETE/v2/lbaas/availabilityzones/{availability_zone_name}octavia/api/v2/controllers/availability_zones.py:175 AvailabilityZonesController.deleteavailability-zone:delete204204 (2), 403 (1), 404 (2), 409 (1), 500 (1)
GET/v2/lbaas/availabilityzoneprofilesoctavia/api/v2/controllers/availability_zone_profiles.py:70 AvailabilityZoneProfileController.get_allavailability-zone-profile:get_all200200 (4), 403 (1)
POST/v2/lbaas/availabilityzoneprofilesoctavia/api/v2/controllers/availability_zone_profiles.py:98 AvailabilityZoneProfileController.postavailability-zone-profile:post201201 (65), 400 (7), 403 (1), 409 (1), 500 (1)
GET/v2/lbaas/availabilityzoneprofiles/{availability_zone_profile_id}octavia/api/v2/controllers/availability_zone_profiles.py:43 AvailabilityZoneProfileController.get_oneavailability-zone-profile:get_one200200 (13), 403 (1), 404 (3)
PUT/v2/lbaas/availabilityzoneprofiles/{availability_zone_profile_id}octavia/api/v2/controllers/availability_zone_profiles.py:168 AvailabilityZoneProfileController.putavailability-zone-profile:put200200 (5), 400 (3), 403 (1), 404 (1), 409 (2)
DELETE/v2/lbaas/availabilityzoneprofiles/{availability_zone_profile_id}octavia/api/v2/controllers/availability_zone_profiles.py:243 AvailabilityZoneProfileController.deleteavailability-zone-profile:delete204204 (2), 403 (1), 404 (2), 409 (1)
GET/v2/octavia/amphoraeoctavia/api/v2/controllers/amphora.py:68 AmphoraController.get_allamphora:get_all200200 (16), 403 (1)
GET/v2/octavia/amphorae/{amphora_id}octavia/api/v2/controllers/amphora.py:50 AmphoraController.get_oneamphora:get_one200200 (5), 403 (1), 404 (2)
DELETE/v2/octavia/amphorae/{amphora_id}octavia/api/v2/controllers/amphora.py:94 AmphoraController.deleteamphora:delete204204 (2), 403 (1), 404 (1), 409 (1)
PUT/v2/octavia/amphorae/{amphora_id}/configoctavia/api/v2/controllers/amphora.py:188 AmphoraUpdateController.putamphora:put_config202202 (2), 403 (1), 404 (2), 500 (1)
PUT/v2/octavia/amphorae/{amphora_id}/failoveroctavia/api/v2/controllers/amphora.py:145 FailoverController.putamphora:put_failover202202 (2), 403 (1), 404 (2)
GET/v2/octavia/amphorae/{amphora_id}/statsoctavia/api/v2/controllers/amphora.py:220 AmphoraStatsController.getamphora:get_stats200200 (1), 403 (1), 404 (2)
GET/octavia/api/root_controller.py:64 RootController.indexnone200200 (11), 406 (2)
GET/healthcheckoctavia/api/root_controller.py:44 RootController.healthchecknone200200 (18), 404 (1), 503 (7)
HEAD/healthcheckoctavia/api/root_controller.py:44 RootController.healthchecknone200204 (1), 404 (1), 503 (1)
GET/v2octavia/api/v2/controllers/__init__.py:58 BaseV2Controller.getnone200—

GET /v2 is the only canonical route with no test (covered by the probes). Coverage of the capture per route is the last column. Documented in the API reference but different in the code: GET /v2/lbaas/quotas/defaults (api-ref/source/v2/quota.inc), used by openstacksdk; there is no such route, it is served as GET /quotas/{project_id} with project_id = "defaults" and returns the defaults only because that project has no record, with the policy target defaults, so a non-admin gets 403 [inferred]; the real default route is GET /quotas/{project_id}/default (octavia/api/v2/controllers/quotas.py:119-124). Not documented: /loadbalancers/{id}/statuses is documented as /status only.

3. Cross-cutting rules

3.1 Bodies and types

  • Every body has a root key (loadbalancer, listener, pool, member, healthmonitor, l7policy, rule, quota, flavor, flavorprofile, availability_zone, availability_zone_profile, and members for batch).
  • Unknown attributes are rejected with 400: Unknown attribute for argument load_balancer.loadbalancer: bogus (or Unknown attribute for argument load_balancer: bogus at root level) [probe]. The argument name in the message is the Python parameter (load_balancer, listener_, pool_, member_, health_monitor_, l7policy_, rule_, quotas, flavor_, flavor_profile_, availability_zone_, availability_zone_profile_); it is part of the observable text (_rewrite/observed-errors_stable-2026.2.md).
  • Missing root object ({}) is not validated: .listener on Unset raises AttributeError → 500 'UnsetType' object has no attribute 'project_id' on the POST of LB, listener, pool, HM, L7 policy, flavor, flavor profile, AZ, AZ profile and the PUT of quota, flavor, flavor profile, AZ, AZ profile; PUT /quotas/{id} with {} → 500 'quota' [probe, 56 route entries in _rewrite/route-probe_stable-2026.2.json]. The Go API should answer 400 and list the difference: no client depends on a 500 [inferred].
  • Content types [probe]: application/json is decoded. text/plain → 415 Unknown mimetype: text/plain. Without Content-Type, or form-encoded, the body is taken as form parameters → 400 Unknown argument: "<body>". A JSON array as body → 500 'list' object has no attribute 'keys'. Malformed JSON → 400 Request is not in valid JSON format. Empty POST body → 400 Missing argument: "load_balancer".
  • Accept (octavia/api/common/hooks.py:30-55): absent, empty, or matching JSON → JSON. Not matching → 406 with Pecan's body {"code":406,"description":"Only content type application/json is accepted.","title":"Not Acceptable"}. text/plain;q=0.1, application/json → 200 [probe].
  • Query parameters on POST/PUT/DELETE are WSME arguments: unknown → 400 Unknown argument: "fields" [probe]. GET handlers declare ignore_extra_args=True.
  • Booleans: WSME accepts true/false and strings "yes", "true", "1"… (bool_as_string probes reach the next validation step); integers are validated against the range; null is accepted for any non-mandatory field and means "clear" on PUT (§ 3.2).
  • Messages of type errors are WSME's: Invalid input for field/attribute <attr>. Value: '<value>'. <reason>, where <reason> is Value should be UUID format, Value should be one of: <values in Python set order>, Value should have a maximum character requirement of 255, Mandatory field missing., etc. When the attribute is the whole root, <value> is the Python repr of the request dictionary. Replay compares these two forms loosely (set order, repr).
  • tenant_id is a read-write alias of project_id on every type having project_id (octavia/api/common/types.py:102-129, octavia/api/common/types.py:215-219); it is always echoed in responses but rejected as a list filter (§ 3.3) [probe].
  • name/description read as null from the database are returned as "" (octavia/api/common/types.py:167-168).
  • admin_state_up: null on POST or PUT means false (octavia/api/common/types.py:220-225).
  • API → DB field names are per type (_type_to_db_map), e.g. admin_state_up→enabled, vip_address→vip.ip_address, member address→ip_address, default_tls_container_ref→tls_certificate_id, loadbalancer_id→load_balancer_id (octavia/api/common/types.py:182-193). They are listed with the field tables in § 5.

3.2 Unset, null, default

WSME distinguishes absent (Unset) from null. Rules:

  • POST: absent → type default (default= in the tables of § 5), else stored as NULL (to_dict(render_unsets=True) turns Unset into None) except LB (render_unsets=False, octavia/api/v2/controllers/load_balancer.py:545-546).
  • PUT: absent → untouched; null → cleared or reset to its default by _set_default_on_none (listener: octavia/api/v2/controllers/listener.py:585-623; member: octavia/api/v2/controllers/member.py:278-292; HM: octavia/api/v2/controllers/health_monitor.py:331-352; L7 rule invert: octavia/api/v2/controllers/l7rule.py:234-235; pool tls_versions, tls_ciphers: octavia/api/v2/controllers/pool.py:466-469).
  • The provider driver receives only the fields that are set: from_dict on a dict built with render_unsets=False. Absent ≠ null there too (03 § 2).
  • PUT with an empty object is a valid call: it still locks the parents, calls the driver and leaves them in PENDING_UPDATE [probe put_empty_object].

3.3 Lists: pagination, sorting, filtering, field selection

PaginationHelper is created by the before hook on every GET (octavia/api/common/hooks.py:69-75), even for item GETs, which is why a bad limit fails them too. Reserved names: limit, marker, sort, sort_key, sort_dir, fields, page_reverse (octavia/api/common/pagination.py:40-43).

ParameterRuleWhere
limitdefault and maximum [api_settings] pagination_max_limit (default 1000; infinite = none). 0, negative or infinite → no limit, not an error. Non-integer → 400 Supplied pagination limit 'abc' is not valid. (webob format, raised in the hook)octavia/api/common/pagination.py:63-82, octavia/common/constants.py:755
markerid of the last row of the previous page; unknown → 400 Supplied pagination marker '<x>' is not valid.octavia/api/common/pagination.py:336-339
sortk1:dir,k2; default dir asc; 3 parts → 400 Supplied sort key '['name', 'asc', 'x']' is not valid. (Python list repr, webob format); bad direction → 400 Supplied sort direction 'bogus' is not valid. (webob format)octavia/api/common/pagination.py:84-112
sort_key + sort_dircomma lists; missing dirs take the default; ignored if sort is presentoctavia/api/common/pagination.py:98-110
unknown sort key400 Supplied sort key 'bogus' is not valid. (WSME format, raised in apply)octavia/api/common/pagination.py:328-330
default ordercreated_at, id appended when the model has them and they are not already keysoctavia/api/common/pagination.py:301-305, octavia/common/constants.py:754
nested sorta key mapped to vip.ip_address joins the child tableoctavia/api/common/pagination.py:321-327
page_reverseexactly "True" flips the marker comparison, not the order of the resultoctavia/api/common/pagination.py:368-376
filtersany other parameter: translated with the type map, must be an attribute of the data model, else 400 One or more arguments are either duplicate or invalid (this includes tenant_id). Equality only; booleans go through bool_from_string (admin_state_up=bogus → false); vip_* filter on the VIP childoctavia/api/common/pagination.py:248-291, octavia/db/base_models.py:131-160
tagstags (all), tags-any, not-tags, not-tags-any; comma lists and repeats are mergedoctavia/api/common/pagination.py:178-221, octavia/api/common/pagination.py:269-280
fieldsrepeated parameter, keeps those attributes in each object; unknown names ignored; **fields=id,name is one unknown name → {}** [probe]; root links unaffected. Honoured only when allow_field_selection. Not on /quotas, ignored (ignore_extra_args) on POST responsesoctavia/api/v2/controllers/base.py:234-241
project_idhandler argument, not a filter: goes through _auth_get_all (§ 3.5)octavia/api/v2/controllers/base.py:205-226

Result: {"<plural>": [...], "<plural>_links": [...]}. Link next appears when the page is full (len >= limit) or when no limit applies and the list is non-empty; previous appears when a marker was given; both carry limit, sort, sort_key and marker (plus page_reverse=True for previous), but not filters nor sort_dir. The base URL is [api_settings] api_base_uri when set, else the request URL (octavia/api/common/pagination.py:143-176). With limit absent and the default maximum 1000, an empty list gives no links. Flavor profile links are in flavorprofile_links and AZ profile links in availability_zone_profile_links (singular, octavia/api/v2/controllers/flavor_profiles.py:89, octavia/api/v2/controllers/availability_zone_profiles.py:93).

The kill switches allow_pagination, allow_sorting, allow_filtering, allow_field_selection default to true (octavia/common/config.py:56-63).

3.4 Locking: immutability and PENDING_*

Writes follow one pattern: test-and-set on the load balancer row inside the request transaction, call the provider, commit.

  • test_and_set_provisioning_status takes SELECT … FOR UPDATE on the LB; accepted states are MUTABLE_STATUSES = (ACTIVE,), or DELETABLE_STATUSES = (ACTIVE, ERROR) when the target is PENDING_DELETE (octavia/db/repositories.py:794-825, octavia/common/constants.py:247-249).
  • Failover uses FAILOVERABLE_STATUSES = (ACTIVE, ERROR) (octavia/db/repositories.py:827-853).
  • Child operations set LB → PENDING_UPDATE, affected listeners → PENDING_UPDATE (or PENDING_DELETE for the listener being deleted), the parent pool or L7 policy → PENDING_UPDATE, without re-checking listeners (octavia/db/repositories.py:392-436).
  • Before that, member and HM operations reject a parent pool not ACTIVE with ImmutableObject(Pool), L7 rules a parent policy not ACTIVE (octavia/api/v2/controllers/member.py:118-119, octavia/api/v2/controllers/health_monitor.py:105-106, octavia/api/v2/controllers/l7rule.py:105-107).
  • The object itself: created rows start PENDING_CREATE/OFFLINE (NO_MONITOR for a member of a pool without HM: octavia/db/prepare.py:176-179), updated rows get PENDING_UPDATE, deleted rows PENDING_DELETE.
  • The transaction, hence the row lock, stays open across the provider call, so the driver must be fast and must never call back into the API synchronously. A driver exception rolls everything back: statuses, new rows and quota counts.
  • A 409 is ImmutableObject (<resource> <id> is immutable and cannot be updated.) except on LB PUT, DELETE and failover, which raise LBPendingStateError (Invalid state <status> of loadbalancer resource <id>) (octavia/api/v2/controllers/load_balancer.py:113-132).
  • Nothing in the API moves a resource out of PENDING_*: only a provider status message does (03 § 2.3). With the OVN provider each child operation reports the LB ACTIVE when it is done.

3.5 Authorisation

Every operation calls _auth_validate_action(context, project_id, action): action = RBAC_TYPE + suffix, target = {"project_id": <owner>} (octavia/api/v2/controllers/base.py:228-232). The owner comes from the database for existing objects: the load balancer's project_id, or the object's own. A project-less check uses context.project_id as target (flavors, providers, AZ, amphora GET: any authenticated project passes the owner part). Lists use _auth_get_all (octavia/api/v2/controllers/base.py:205-226):

  1. <type>:get_all-global with target {project_id: <query value or None>}: if allowed, no project filter unless project_id was given;
  2. otherwise project_id falls back to the context's, None → 403, and <type>:get_all is enforced with that target, which is how another project's project_id gets a 403.

Member and L7 rule lists enforce the parent's owner without a global variant. PolicyForbidden → 403 Policy does not allow this request to be performed. (octavia/common/policy.py:71-121). Rule list: § 6.

3.6 Response envelope on write

POST → 201 with the object; LB POST uses LoadBalancerFullResponse, so listeners and pools are complete objects on a graph create (octavia/api/v2/types/load_balancer.py:117-123), but IDs only on GET. PUT → 200 with the object re-read (statuses already PENDING_UPDATE), except quota PUT/DELETE → 202 with body (DELETE too: body with the defaults although documented as 202, octavia/api/v2/controllers/quotas.py:100-117). DELETE → 204. LB failover, amphora failover/config, member batch → 202 without body. tenant_id always echoed. Timestamps are naive UTC, ISO 8601 without zone (2026-10-31T23:57:54.439059), updated_at null until first update.

4. Resources

4.1 Load balancers

  • Purpose: declare a virtual IP served by a provider; optionally the whole tree of listeners, pools, members, monitors and L7 objects in one call.
  • Actor: project member (or admin creating for a project through project_id).

POST (octavia/api/v2/controllers/load_balancer.py:500-684). Order of checks, all before any write unless stated:

  1. project_id: body value, else context; none → 400 Validation failure: Missing project ID in request where one is required. An administrator should check the keystone settings in the Octavia configuration. (octavia/api/v2/controllers/load_balancer.py:505-512).
  2. Policy loadbalancer:post on that project, and post:vip_sg_ids if vip_sg_ids is in the body (octavia/api/v2/controllers/load_balancer.py:514-519).
  3. VIP request object (octavia/api/v2/controllers/load_balancer.py:284-344):
    • allow_vip_port_id, allow_vip_network_id, allow_vip_subnet_id false → 400 Validation failure: use of vip_port_id is disallowed by this deployment's configuration. …;
    • none of the three → VIPValidationException 400 Validation failure: VIP must contain one of: vip_port_id, vip_network_id, vip_subnet_id.;
    • port given (_validate_port_and_fill_or_validate_subnet, octavia/api/v2/controllers/load_balancer.py:203-252): port must exist (InvalidSubresource 400 Port <id> not found.), must have no device_id (400 Port <id> is already used by device <d> with trailing space, octavia/common/validate.py:345-348); network taken from the port; the port's qos_policy_id fills vip_qos_policy_id if absent; then with a subnet: subnet exists, first fixed IP in that subnet gives the address, none → No VIP address found on the specified VIP port within the specified subnet.; with an address: matches a fixed IP (compressed form), else Specified VIP address not found on the specified VIP port.; alone: exactly one fixed IP, else VIP port's subnet could not be determined. Please specify either a VIP subnet or address.;
    • network given (octavia/api/v2/controllers/load_balancer.py:135-200): must exist; a given subnet must be in network.subnets, else Subnet <id> not found.; no subnet + address: first subnet whose CIDR holds the address, else Supplied network does not contain a subnet for VIP address specified.; no subnet, no address: get_network_ip_availability; subnets with at least 2 free IPs (3 under ACTIVE_STANDBY topology), prefer the first IPv4, else the first; messages Supplied network does not contain a subnet., Could not verify IP availability for network <id>., Subnet(s) in the supplied network do not contain enough available IPs.;
    • subnet only: must exist, gives the network;
    • vip_qos_policy_id: QoS extension enabled (VIP QoS policy is not allowed in this deployment.) and policy exists (octavia/common/validate.py:363-378);
    • additional VIPs: subnets unique including the main one (Duplicate VIP subnet(s) specified. Only one IP can be bound per subnet.), all exist, all in one network (All VIP subnets must belong to the same network.), network_id filled (octavia/api/v2/controllers/load_balancer.py:255-282);
    • vip_sg_ids: each must exist (Security Group <id> not found.). Network lookups use the user's token unless [networking] allow_invisible_resource_usage (03 § 3).
  4. Flavor: exists (Validation failure: Invalid flavor_id.), enabled (DisabledOption 400 The selected flavor is not allowed in this deployment: <id>) (octavia/api/v2/controllers/load_balancer.py:443-455).
  5. Availability zone: exists (Invalid availability zone.), enabled (octavia/api/v2/controllers/load_balancer.py:485-496).
  6. Provider (octavia/api/v2/controllers/load_balancer.py:388-416): flavor's provider, else body provider, else default_provider_driver; both and different → ProviderFlavorMismatchError 400. get_driver: not in enabled_provider_drivers → ProviderNotEnabled 400; cannot load → ProviderNotFound 501 (octavia/api/drivers/driver_factory.py:30-52).
  7. Transaction opens. Quota (§ 4.8): over → QuotaException 403 Quota has been met for resources: load_balancer… (octavia/api/v2/controllers/load_balancer.py:536-541).
  8. create_load_balancer prepares id, PENDING_CREATE, OFFLINE, topology (octavia/db/prepare.py:26-37); provider = driver name.
  9. Flavor metadata: validate_flavor on the driver if the flavor has data; loadbalancer_topology from it, else config (octavia/api/v2/controllers/load_balancer.py:418-441).
  10. AZ metadata: validate_availability_zone if the AZ has data; NotImplementedError → 501 … This provider does not support availability zones. (octavia/api/v2/controllers/load_balancer.py:457-483).
  11. network_allowed_by_config (AZ valid_vip_networks, else [networking] valid_vip_networks), network_denylist_by_config → 400 Supplied VIP network_id is not allowed by the configuration of this deployment. (octavia/common/validate.py:412-430).
  12. vNIC: direct when the flavor sets sriov_vip, else normal; direct + vip_sg_ids → VIP Security Groups are not allowed with VNIC direct type (octavia/api/v2/controllers/load_balancer.py:570-578, octavia/api/v2/controllers/load_balancer.py:346-352).
  13. Rows load_balancer, vip, additional_vip inserted (octavia/db/repositories.py:299-335).
  14. **create_vip_port** on the driver with the provider VIP dictionary and additional VIPs. The driver may return a complete VIP (the OVN provider does). ProviderNotImplementedError → the API creates the port itself with allocate_vip (octavia/api/v2/controllers/load_balancer.py:603-625). octavia_owned true if the API made the port or the driver says so.
  15. Ports created in this request and not given by the user are remembered, so that any later failure deletes them (octavia/api/v2/controllers/load_balancer.py:582-585, octavia/api/v2/controllers/load_balancer.py:371-386); this is the recent fix at HEAD (2900ed67).
  16. vip/additional_vip rows updated from the result.
  17. Graph (below), if listeners or pools.
  18. loadbalancer_create on the driver with the full provider object, then commit; a duplicate → IDAlreadyExists 409; response re-read with LoadBalancerFullResponse (octavia/api/v2/controllers/load_balancer.py:658-684).

Graph creation (octavia/api/v2/controllers/load_balancer.py:686-792). Same transaction; no status test because the LB is new:

  • a listener's default_pool or a policy's redirect_pool with more than 3 keys (name + the two defaulted fields admin_state_up, tls_enabled) is a full definition moved to the top-level pool list; otherwise it is a reference by name. No name → Pools must be named when creating a fully populated loadbalancer.;
  • pool names unique: Pool names must be unique when creating a fully populated loadbalancer.; reference without definition: Pool '<name>' was referenced but no full definition was found.; the mandatory protocol, lb_algorithm are checked by hand: Pool definition for '<name>' missing required attribute: <attr>;
  • quotas are consumed per kind, with count (pools, then for each pool HM and members; listeners; L7 policies and rules are not quota-checked in the graph);
  • pools → members and HM → listeners → L7 policies → rules; the same per-object validators as the single calls, except that no driver call happens per object: the driver gets one loadbalancer_create with nested objects.
  • PROMETHEUS listeners cannot have a default pool or L7 policies: ListenerNoChildren.
  • Evidence: TestLoadBalancerGraph exchanges.

PUT (octavia/api/v2/controllers/load_balancer.py:797-874): editable fields name, description, admin_state_up, tags, vip_qos_policy_id, vip_sg_ids. Policy put, put:vip_sg_ids; QoS validated if changed; vip_sg_ids: null → []; _validate_vnic_type. Status test → PENDING_UPDATE. loadbalancer_update(old, new): old is the full object from the database with for_delete=True, new only the changed fields and the id. DB update after the driver accepted.

DELETE (octavia/api/v2/controllers/load_balancer.py:877-904): ?cascade= parsed by bool_from_string. Children (listeners or pools) without cascade → 400 Validation failure: Cannot delete Load Balancer <id> - it has children. Status → PENDING_DELETE; loadbalancer_delete(full_object, cascade). Children statuses are not touched by the API on a cascade: the driver reports every object DELETED.

Status tree GET …/status and …/statuses (octavia/api/v2/controllers/load_balancer.py:944-967): {"statuses": {"loadbalancer": {id, name, provisioning_status, operating_status, listeners: [{…, l7policies:[{…, rules:[…]}], pools:[{…, health_monitor:{…}, members:[{…, address, protocol_port}]}]}], pools: […]}}}. Pools appear under the listeners using them and at LB level. Policy get_status.

Stats GET …/stats (octavia/api/v2/controllers/load_balancer.py:979-999): sum of the listeners (03 § 2.5); {"stats": {active_connections, bytes_in, bytes_out, request_errors, total_connections}}.

Failover PUT …/failover (octavia/api/v2/controllers/load_balancer.py:1009-1027): 202, body ignored (**kwargs), status ACTIVE or ERROR → PENDING_UPDATE, loadbalancer_failover(id). The OVN provider raises UnsupportedOptionError → 501 Provider 'ovn' does not support a requested option: OVN provider does not support loadbalancer failover and rollback (provider spec 02).

Soft delete: GET-style lookups use show_deleted=False, the LB row stays with status DELETED until housekeeping purges it after load_balancer_expiry_age (octavia/db/repositories.py:126-135, octavia/controller/housekeeping/house_keeping.py:66). Non-LB rows are removed by the status path (03 § 2.3).

4.2 Listeners

POST (octavia/api/v2/controllers/listener.py:377-445). Project and provider from the LB (NotFound if unknown); policy; driver; quota; default pool: PROMETHEUS → ListenerNoChildren 400, must belong to the LB (404 Pool <id> not found.) and be protocol-compatible (octavia/common/constants.py:210-218: The pool protocol 'X' is invalid while the listener protocol is 'Y'.); LB test-and-set; then _validate_create_listener (octavia/api/v2/controllers/listener.py:193-373):

RuleMessage (faultstring)
insert_headers only on HTTP, TERMINATED_HTTPSinsert-headers is not a valid option for a <P> protocol listener.
header name known; SSL headers only with TERMINATED_HTTPS<list repr> is not a valid option for insert_headers
UDP, SCTP + TLS or headersValidation failure: <P> protocol listener does not support TLS.
allow_tls_terminated_listeners offThe selected protocol is not allowed in this deployment: TERMINATED_HTTPS
allow_prometheus_listeners offThe selected protocol is not allowed in this deployment: PROMETHEUS
certificate refs on non-TLSCertificate container references are not allowed on <P> protocol listeners.
TLS without certificateAn SNI or default certificate container reference must be provided for <P> protocol listeners.
client CA without base cert; client auth without CA; CRL without CAAn SNI or default…, Client authentication setting <X> requires a client CA container reference., A client authentication CA reference is required to specify a client authentication revocation list.
ciphersInvalid characters in cipher string.…, The following ciphers have been prohibited by an administrator: …
certificates readableCould not retrieve certificate: <refs>; PEM errors for CA and CRL (octavia/api/v2/controllers/base.py:249-316)
same L4 port on the LB409 Another Listener on this Load Balancer is already using protocol <P> and protocol_port <n> (L4 map octavia/common/constants.py:866-875)
allowed_cidrsAllowed CIDRs are not allowed when using custom VIP Security Groups.; CIDR <c> IP version incompatible with all VIPs <list repr> IP version.
TLS versions, ALPN, HSTS (octavia/common/validate.py:512-597)Empty TLS version list.…, Invalid TLS versions: …, Requested TLS versions are less than the minimum: …, Empty ALPN protocol list.…, Invalid ALPN protocol: …, HSTS configuration options…, The HSTS feature can only be used…

Defaults: timeout_* from [haproxy_amphora] (timeout_client_data 50000, timeout_member_connect 5000, timeout_member_data 50000, timeout_tcp_inspect 0), tls_ciphers, tls_versions, alpn_protocols from [api_settings] for TERMINATED_HTTPS (octavia/db/prepare.py:40-97). A listener with protocol PROMETHEUS reports protocol_port-based stats on the amphora only, out of OVN scope. The driver call receives secrets read from the certificate manager: default_tls_container_data, sni_container_data, client_ca_tls_container_data, client_crl_container_data (octavia/api/drivers/utils.py:227-295).

PUT (octavia/api/v2/controllers/listener.py:627-691): _validate_listener_PUT is the same list adapted (octavia/api/v2/controllers/listener.py:484-583), with one text difference: … does not support TLS or header insertion.; default_pool_id re-validated, null removes it; LB and listener → PENDING_UPDATE; listener_update(old, new); insert_headers, allowed_cidrs, sni_container_refs are replaced as a whole. DELETE (octavia/api/v2/controllers/listener.py:694-724): LB PENDING_UPDATE, listener PENDING_DELETE. Stats octavia/api/v2/controllers/listener.py:747-772.

4.3 Pools

POST (octavia/api/v2/controllers/pool.py:218-318). Requires loadbalancer_id or listener_id (Must provide at least one of: loadbalancer_id, listener_id; a bad listener_id raises an internal error → 500, AttributeError on None **[inferred from octavia/api/v2/controllers/pool.py:238-240]**). Rules: listener protocol compatibility; PROMETHEUS parent; UDP/SCTP session persistence: only SOURCE_IP with persistence_timeout and persistence_granularity, messages at octavia/api/v2/controllers/pool.py:188-214; non-UDP: persistence_timeout and persistence_granularity is only for UDP and SCTP protocol pools. (POST) or … only for UDP protocol pools. (PUT, octavia/api/v2/controllers/pool.py:386-388); check_session_persistence (octavia/common/validate.py:439-462); listener already having a default pool → DuplicatePoolEntry 409; TLS refs, CA, CRL, ciphers, versions, ALPN (only when tls_enabled) (octavia/api/v2/controllers/pool.py:118-170). listener_id sets the listener's default_pool_id in the same transaction (octavia/db/repositories.py:337-364). lb_algorithm SOURCE_IP_PORT… is free for the API; the OVN provider accepts SOURCE_IP_PORT and SOURCE_IP only (others → 501 from the driver). PUT octavia/api/v2/controllers/pool.py:453-518. DELETE octavia/api/v2/controllers/pool.py:521-556: used by an L7 policy → PoolInUseByL7Policy 409; affects every listener using it (_get_affected_listener_ids).

4.4 Members

Sub-resource of a pool: _lookup checks the pool exists (webob-format 404, title "Bad Request") and returns MemberController for an item, MembersController for the collection (octavia/api/v2/controllers/pool.py:559-583).

POST (octavia/api/v2/controllers/member.py:154-246): ip_not_reserved ([networking] reserved_ips: <IP upper exploded> is not a valid option for member address), subnet exists, quota, request_sriov needs flavor allow_member_sriov (MemberSRIOVDisabled 400), operating status NO_MONITOR without HM else OFFLINE; duplicate (pool, address, port) → 409 Another member on this pool is already using ip <a> on protocol_port <p>. PUT octavia/api/v2/controllers/member.py:297-353: name, weight, admin_state_up, backup, monitor_*, tags. DELETE octavia/api/v2/controllers/member.py:356-390. A member of another pool → 404 Member <id> not found. (octavia/api/v2/controllers/member.py:148-150).

Batch PUT /pools/{id}/members (octavia/api/v2/controllers/member.py:400-546, 202 without body). additive_only query parameter. Policies post, put, and delete unless additive. Algorithm:

  1. Key = (address, port). In the body and not in the DB: new (ip_not_reserved). In both: update; the same key twice → Member must be updated only once in the same request.. In the DB and not in the body: delete, unless additive_only (then passed through unchanged).
  2. All empty → logged, rollback, return 202 without driver call and with statuses unchanged.
  3. Quota on new - deleted (new if additive) when positive.
  4. New subnets validated once each. New members via _graph_create; updates → PENDING_UPDATE; deletes → PENDING_DELETE.
  5. member_batch_update(pool_id, provider_members): the list is the target state: new + updated + (when additive) untouched; deleted ones are simply absent. **In the batch, request_sriov on an update is silently ignored**.

4.5 Health monitors

POST octavia/api/v2/controllers/health_monitor.py:210-286. PING can be disabled (allow_ping_health_monitors); pool UDP/SCTP: only UDP-CONNECT, SCTP, TCP, HTTP; UDP-CONNECT delay ≥ udp_connect_min_interval_health_monitor (default 3); other pools refuse UDP-CONNECT, SCTP. Non-HTTP types refuse http_method, url_path, expected_codes: http_method is not a valid option for health monitors of type TCP; HTTP(S) defaults GET, /, 200 (octavia/api/v2/controllers/health_monitor.py:129-152). domain_name requires http_version ≥ 1.1. One per pool: DuplicateHealthMonitor 409. max_retries is stored as rise_threshold, max_retries_down as fall_threshold (default 3). Member operating statuses are not changed by HM creation [inferred: not in the controller]. PUT octavia/api/v2/controllers/health_monitor.py:356-418: type not editable; delay, timeout, max_retries can't be null (None is not a valid option for delay). DELETE octavia/api/v2/controllers/health_monitor.py:421-450.

4.6 L7 policies and rules

Policy POST octavia/api/v2/controllers/l7policy.py:128-200: listener exists; PROMETHEUS parent refused; redirect_pool_id exists and is protocol-compatible; sanitize_l7policy_api_args makes action and arguments consistent (octavia/common/validate.py:250-330): REJECT clears redirects, REDIRECT_TO_URL, REDIRECT_PREFIX, REDIRECT_TO_POOL need their argument (redirect_url must not be None…), pool and URL together → Cannot specify redirect_pool_id and redirect_url or redirect_prefix at the same time; and the argument **overrides action**. redirect_http_code defaults to 302 for URL and prefix. position 1..2147483647 (default last): the list is renumbered from 1 without holes, a too-large position means last (octavia/db/repositories.py:1920-1960, octavia/db/models.py:597). PUT octavia/api/v2/controllers/l7policy.py:223-299; a moved position renumbers siblings (octavia/db/repositories.py:1905-1916). DELETE octavia/api/v2/controllers/l7policy.py:302-337.

Rule (under /l7policies/{id}/rules, _lookup as members): POST octavia/api/v2/controllers/l7rule.py:139-201, PUT octavia/api/v2/controllers/l7rule.py:216-283 merges the row with the body and validates the result, DELETE octavia/api/v2/controllers/l7rule.py:286-324. Maximum 50 rules per policy: TooManyL7RulesOnL7Policy 409 Too many rules on L7 policy <id> (octavia/common/constants.py:270). validate.l7rule_data (octavia/common/validate.py:129-247), wrapped in L7RuleValidation 400 Error parsing L7Rule: Invalid L7 Rule: <msg>; header and cookie regexes in octavia/common/constants.py (HTTP_HEADER_NAME_REGEX…). In the OVN provider, all L7 calls are NotImplementedError → 501 after the same validation.

4.7 Amphorae (read mostly; empty in OpenKeel)

Request when no amphora exists [probe, _rewrite/amphora-empty_stable-2026.2.json]StatusBody
GET /v2/octavia/amphorae200{"amphorae": [], "amphorae_links": []}
GET /v2/octavia/amphorae/{id}404faultstring: Amphora <id> not found.
DELETE /v2/octavia/amphorae/{id}404same (octavia/api/v2/controllers/amphora.py:101-106)
PUT …/{id}/failover, PUT …/{id}/config404same
GET …/{id}/stats404faultstring: Amphora stats for <id> not found. (octavia/api/v2/controllers/amphora.py:229-231)

The policy is checked first on list, DELETE and stats (target: the caller's own project), after the lookup on GET one, failover and config: under keystone, a non-admin sees 403 on list, DELETE and stats, and 404 elsewhere for an unknown id (octavia/api/v2/controllers/amphora.py:50-58, octavia/api/v2/controllers/amphora.py:73-74, octavia/api/v2/controllers/amphora.py:98-99, octavia/api/v2/controllers/amphora.py:150-155). With amphorae present these routes cast RPC to the worker, out of scope; the Go API serves only the table above. Amphora rows filter project_id through the LB (octavia/api/common/pagination.py:253-254, octavia/api/common/pagination.py:292-294).

4.8 Quotas

GET /quotas lists explicit records only (not defaults); GET /quotas/{project} merges the record with [quotas] default_*_quota (default -1, unlimited) (octavia/api/v2/controllers/base.py:163-203, octavia/common/config.py:834-852). GET /quotas/{project}/default is config only. PUT (202): body fields loadbalancer/load_balancer, healthmonitor/health_monitor, listener, member, pool, l7policy, l7rule, range -1..2000000000; null means "use the default". DELETE (202 with body) nulls the limits, keeps in_use_* (404 if no record, octavia/db/repositories.py:2014-2030). Responses carry both spellings (loadbalancer and load_balancer; healthmonitor and health_monitor).

Accounting (octavia/db/repositories.py:438-624): check_quota_met runs inside the create transaction: skipped under noauth; ensure_project_exists commits a blank record in its own transaction with deadlock retries (octavia/db/repositories.py:2005-2012); SELECT … FOR UPDATE on the project's row; limit = record or config default; if in_use_x is 0 or NULL, it is recomputed by COUNT(*) of non-DELETED rows ("upgrade case"); else in_use + count; allowed if <= limit or limit = -1, then stored. DBDeadlock → ProjectBusyException 503. The counter is decremented only by a provider status DELETED (03 § 2.3): delete-then-create immediately may be refused while the provider has not reported.

4.9 Providers

GET /providers lists [api_settings] enabled_provider_drivers as {name, description}, minus drivers that failed to load at start-up (octavia/api/v2/controllers/provider.py:39-53, octavia/api/app.py:45-60). GET /providers/{p}/flavor_capabilities, …/availability_zone_capabilities: get_supported_*_metadata() as {name, description} list, filters name, description (exact), fields; NotImplementedError → 501 (octavia/api/v2/controllers/provider.py:83-120, octavia/api/v2/controllers/provider.py:133-172). A direct library exception bypasses call_provider here: other driver exceptions → 500 with their text.

4.10 Flavors, flavor profiles, availability zones, AZ profiles

Administrator-managed catalogues with the same shape.

  • Reads for everybody (rule:load-balancer:read-style, see § 6); profiles admin-only.
  • **The placeholder rows with id or name 00000000-0000-0000-0000-000000000000 are hidden from every list and refused as id** with 404 (octavia/db/repositories.py:2033-2097, octavia/api/v2/controllers/flavors.py:48-49).
  • Flavor: POST takes an existing flavor_profile_id (FK; a bad id → 500 from the DB error, no explicit validation, octavia/api/v2/controllers/flavors.py:93; name unique → RecordAlreadyExists 409 A flavor of <name> already exists.). PUT: only name, description, enabled. DELETE (serialisable transaction, 5 deadlock retries): re-points DELETED LBs to the NIL id, then deletes; FK from live LBs → ObjectInUse 409 Flavor <id> is in use and cannot be modified. (octavia/api/v2/controllers/flavors.py:147-176, octavia/db/repositories.py:2119-2135). Note PUT with the Python argument named id and DELETE with flavor_id: invisible for clients.
  • Flavor profile: flavor_data must be JSON (<value> is not a valid option for flavor_data), validate_flavor on the driver; PUT refuses changing flavor_data or provider_name while in use; null refused (None is not a valid option for name); DELETE refused while used (octavia/api/v2/controllers/flavor_profiles.py:94-237). The flavor_data is returned as a string.
  • AZ: key is name (not a UUID), availability_zone_profile_id; PUT only description, enabled; DELETE same as flavor (octavia/api/v2/controllers/availability_zones.py:175-206, octavia/db/repositories.py:2177-2197).
  • AZ profile: same as flavor profile; a bad JSON on PUT reports flavor_data, not availability_zone_data (octavia/api/v2/controllers/availability_zone_profiles.py:190-192), a client-visible typo to keep.

5. Field tables

Generated from the WSME classes (_rewrite/request-schemas_stable-2026.2.json). uuid, ip, cidr, url, url-path are the validators of octavia/api/common/types.py.

Load balancer

**LoadBalancerPOST** (octavia/api/v2/types/load_balancer.py:139)

FieldTypeReq.Default
additional_vips[AdditionalVipsType][]
admin_state_upbooleantrue
availability_zonestring ≤255
descriptionstring ≤255
flavor_iduuid
listeners[ListenerSingleCreate][]
namestring ≤255
pools[PoolSingleCreate][]
project_idstring ≤36
providerstring ≤64
tags[string ≤255]
tenant_idstring ≤36
vip_addressip
vip_network_iduuid
vip_port_iduuid
vip_qos_policy_iduuid
vip_sg_ids[uuid]
vip_subnet_iduuid

API → DB names: admin_state_up→enabled, vip_address→vip.ip_address, vip_network_id→vip.network_id, vip_port_id→vip.port_id, vip_qos_policy_id→vip.qos_policy_id, vip_sg_ids→vip.sg_ids, vip_subnet_id→vip.subnet_id, vip_vnic_type→vip.vnic_type

**LoadBalancerPUT** (octavia/api/v2/types/load_balancer.py:166)

FieldTypeReq.Default
admin_state_upboolean
descriptionstring ≤255
namestring ≤255
tags[string ≤255]
vip_qos_policy_iduuid
vip_sg_ids[uuid]

API → DB names: admin_state_up→enabled, vip_address→vip.ip_address, vip_network_id→vip.network_id, vip_port_id→vip.port_id, vip_qos_policy_id→vip.qos_policy_id, vip_sg_ids→vip.sg_ids, vip_subnet_id→vip.subnet_id, vip_vnic_type→vip.vnic_type

**AdditionalVipsType** (octavia/api/v2/types/load_balancer.py:41)

FieldTypeReq.Default
ip_addressip
port_iduuid
subnet_iduuidyes

**LoadBalancerResponse** (octavia/api/v2/types/load_balancer.py:48)

FieldTypeReq.Default
additional_vips[AdditionalVipsType]
admin_state_upboolean
availability_zonestring
created_atdate-time
descriptionstring
flavor_iduuid
iduuid
listeners[IdOnlyType]
namestring
operating_statusstring
pools[IdOnlyType]
project_idstring
providerstring
provisioning_statusstring
tags[string]
tenant_idstring ≤36
updated_atdate-time
vip_addressip
vip_network_iduuid
vip_port_iduuid
vip_qos_policy_iduuid
vip_sg_ids[uuid]
vip_subnet_iduuid
vip_vnic_typestring

API → DB names: admin_state_up→enabled, vip_address→vip.ip_address, vip_network_id→vip.network_id, vip_port_id→vip.port_id, vip_qos_policy_id→vip.qos_policy_id, vip_sg_ids→vip.sg_ids, vip_subnet_id→vip.subnet_id, vip_vnic_type→vip.vnic_type

**LoadBalancerStatisticsResponse** (octavia/api/v2/types/load_balancer.py:212)

FieldTypeReq.Default
active_connectionsinteger
bytes_ininteger
bytes_outinteger
request_errorsinteger
total_connectionsinteger

API → DB names: admin_state_up→enabled, vip_address→vip.ip_address, vip_network_id→vip.network_id, vip_port_id→vip.port_id, vip_qos_policy_id→vip.qos_policy_id, vip_sg_ids→vip.sg_ids, vip_subnet_id→vip.subnet_id, vip_vnic_type→vip.vnic_type

Listener

**ListenerPOST** (octavia/api/v2/types/listener.py:119)

FieldTypeReq.Default
admin_state_upbooleantrue
allowed_cidrs[cidr]
alpn_protocols[string]
client_authenticationMANDATORY | NONE | OPTIONAL"NONE"
client_ca_tls_container_refstring ≤255
client_crl_container_refstring ≤255
connection_limitinteger [-1..]-1
default_poolPoolSingleCreate
default_pool_iduuid
default_tls_container_refstring ≤255
descriptionstring ≤255
hsts_include_subdomainsbooleanfalse
hsts_max_ageinteger [0..]
hsts_preloadbooleanfalse
insert_headersmap
l7policies[L7PolicySingleCreate][]
loadbalancer_iduuidyes
namestring ≤255
project_idstring ≤36
protocolHTTP | HTTPS | PROMETHEUS | SCTP | TCP | TERMINATED_HTTPS | UDPyes
protocol_portinteger [1..65535]yes
sni_container_refs[string ≤255]
tags[string ≤255]
tenant_idstring ≤36
timeout_client_datainteger [0..2073600000]
timeout_member_connectinteger [0..2073600000]
timeout_member_datainteger [0..2073600000]
timeout_tcp_inspectinteger [0..2073600000]
tls_ciphersstring ≤2048
tls_versions[string ≤32]

API → DB names: admin_state_up→enabled, client_ca_tls_container_ref→client_ca_tls_certificate_id, client_crl_container_ref→client_crl_container_id, default_tls_container_ref→tls_certificate_id, sni_container_refs→sni_containers

**ListenerPUT** (octavia/api/v2/types/listener.py:177)

FieldTypeReq.Default
admin_state_upboolean
allowed_cidrs[cidr]
alpn_protocols[string]
client_authenticationMANDATORY | NONE | OPTIONAL
client_ca_tls_container_refstring ≤255
client_crl_container_refstring ≤255
connection_limitinteger [-1..]
default_pool_iduuid
default_tls_container_refstring ≤255
descriptionstring ≤255
hsts_include_subdomainsboolean
hsts_max_ageinteger [0..]
hsts_preloadboolean
insert_headersmap
namestring ≤255
sni_container_refs[string ≤255]
tags[string ≤255]
timeout_client_datainteger [0..2073600000]
timeout_member_connectinteger [0..2073600000]
timeout_member_datainteger [0..2073600000]
timeout_tcp_inspectinteger [0..2073600000]
tls_ciphersstring ≤2048
tls_versions[string ≤32]

API → DB names: admin_state_up→enabled, client_ca_tls_container_ref→client_ca_tls_certificate_id, client_crl_container_ref→client_crl_container_id, default_tls_container_ref→tls_certificate_id, sni_container_refs→sni_containers

**ListenerResponse** (octavia/api/v2/types/listener.py:34)

FieldTypeReq.Default
admin_state_upboolean
allowed_cidrs[cidr]
alpn_protocols[string]
client_authenticationstring
client_ca_tls_container_refstring
client_crl_container_refstring
connection_limitinteger
created_atdate-time
default_pool_iduuid
default_tls_container_refstring
descriptionstring
hsts_include_subdomainsboolean
hsts_max_ageinteger
hsts_preloadboolean
iduuid
insert_headersmap
l7policies[IdOnlyType]
loadbalancers[IdOnlyType]
namestring
operating_statusstring
project_idstring
protocolstring
protocol_portinteger
provisioning_statusstring
sni_container_refs[string]
tags[string]
tenant_idstring ≤36
timeout_client_datainteger
timeout_member_connectinteger
timeout_member_datainteger
timeout_tcp_inspectinteger
tls_ciphersstring
tls_versions[string]
updated_atdate-time

API → DB names: admin_state_up→enabled, client_ca_tls_container_ref→client_ca_tls_certificate_id, client_crl_container_ref→client_crl_container_id, default_tls_container_ref→tls_certificate_id, sni_container_refs→sni_containers

**ListenerStatisticsResponse** (octavia/api/v2/types/listener.py:295)

FieldTypeReq.Default
active_connectionsinteger
bytes_ininteger
bytes_outinteger
request_errorsinteger
total_connectionsinteger

API → DB names: admin_state_up→enabled, client_ca_tls_container_ref→client_ca_tls_certificate_id, client_crl_container_ref→client_crl_container_id, default_tls_container_ref→tls_certificate_id, sni_container_refs→sni_containers

Pool and session persistence

**PoolPOST** (octavia/api/v2/types/pool.py:161)

FieldTypeReq.Default
admin_state_upbooleantrue
alpn_protocols[string]
ca_tls_container_refstring ≤255
crl_container_refstring ≤255
descriptionstring ≤255
healthmonitorHealthMonitorSingleCreate
lb_algorithmLEAST_CONNECTIONS | ROUND_ROBIN | SOURCE_IP | SOURCE_IP_PORTyes
listener_iduuid
loadbalancer_iduuid
members[MemberSingleCreate]
namestring ≤255
project_idstring ≤36
protocolHTTP | HTTPS | PROXY | PROXYV2 | SCTP | TCP | UDPyes
session_persistenceSessionPersistencePOST
tags[string ≤255]
tenant_idstring ≤36
tls_ciphersstring ≤2048
tls_container_refstring ≤255
tls_enabledbooleanfalse
tls_versions[string ≤32]

API → DB names: admin_state_up→enabled, ca_tls_container_ref→ca_tls_certificate_id, crl_container_ref→crl_container_id, healthmonitor→health_monitor, healthmonitor_id→health_monitor.id, tls_container_ref→tls_certificate_id

**PoolPUT** (octavia/api/v2/types/pool.py:196)

FieldTypeReq.Default
admin_state_upboolean
alpn_protocols[string]
ca_tls_container_refstring ≤255
crl_container_refstring ≤255
descriptionstring ≤255
lb_algorithmLEAST_CONNECTIONS | ROUND_ROBIN | SOURCE_IP | SOURCE_IP_PORT
namestring ≤255
session_persistenceSessionPersistencePUT
tags[string ≤255]
tls_ciphersstring ≤2048
tls_container_refstring ≤255
tls_enabledboolean
tls_versions[string ≤32]

API → DB names: admin_state_up→enabled, ca_tls_container_ref→ca_tls_certificate_id, crl_container_ref→crl_container_id, healthmonitor→health_monitor, healthmonitor_id→health_monitor.id, tls_container_ref→tls_certificate_id

**SessionPersistencePOST** (octavia/api/v2/types/pool.py:32)

FieldTypeReq.Default
cookie_namestring ≤255 pattern ^[^\s,;\\]+$null
persistence_granularityipnull
persistence_timeoutintegernull
typeAPP_COOKIE | HTTP_COOKIE | SOURCE_IPyes

**SessionPersistencePUT** (octavia/api/v2/types/pool.py:46)

FieldTypeReq.Default
cookie_namestring ≤255 pattern ^[^\s,;\\]+$null
persistence_granularityipnull
persistence_timeoutintegernull
typeAPP_COOKIE | HTTP_COOKIE | SOURCE_IP

**PoolResponse** (octavia/api/v2/types/pool.py:70)

FieldTypeReq.Default
admin_state_upboolean
alpn_protocols[string]
ca_tls_container_refstring
created_atdate-time
crl_container_refstring
descriptionstring
healthmonitor_iduuid
iduuid
lb_algorithmstring
listeners[IdOnlyType]
loadbalancers[IdOnlyType]
members[IdOnlyType]
namestring
operating_statusstring
project_idstring
protocolstring
provisioning_statusstring
session_persistenceSessionPersistenceResponse
tags[string]
tenant_idstring ≤36
tls_ciphersstring
tls_container_refstring
tls_enabledboolean
tls_versions[string]
updated_atdate-time

API → DB names: admin_state_up→enabled, ca_tls_container_ref→ca_tls_certificate_id, crl_container_ref→crl_container_id, healthmonitor→health_monitor, healthmonitor_id→health_monitor.id, tls_container_ref→tls_certificate_id

Member

**MemberPOST** (octavia/api/v2/types/member.py:63)

FieldTypeReq.Default
addressipyes
admin_state_upbooleantrue
backupbooleanfalse
monitor_addressipnull
monitor_portinteger [1..65535]null
namestring ≤255
project_idstring ≤36
protocol_portinteger [1..65535]yes
request_sriovbooleanfalse
subnet_iduuid
tags[string ≤255]
tenant_idstring ≤36
weightinteger [0..256]1

API → DB names: address→ip_address, admin_state_up→enabled

**MemberPUT** (octavia/api/v2/types/member.py:90)

FieldTypeReq.Default
admin_state_upboolean
backupboolean
monitor_addressip
monitor_portinteger [1..65535]
namestring ≤255
tags[string ≤255]
weightinteger [0..256]

API → DB names: address→ip_address, admin_state_up→enabled

**MembersRootPUT** (octavia/api/v2/types/member.py:107)

FieldTypeReq.Default
members[MemberPOST]

**MemberResponse** (octavia/api/v2/types/member.py:27)

FieldTypeReq.Default
addressip
admin_state_upboolean
backupboolean
created_atdate-time
iduuid
monitor_addressip
monitor_portinteger
namestring
operating_statusstring
project_idstring
protocol_portinteger
provisioning_statusstring
subnet_iduuid
tags[string]
tenant_idstring ≤36
updated_atdate-time
vnic_typestring
weightinteger

API → DB names: address→ip_address, admin_state_up→enabled

Health monitor

**HealthMonitorPOST** (octavia/api/v2/types/health_monitor.py:76)

FieldTypeReq.Default
admin_state_upbooleantrue
delayinteger [0..]yes
domain_namestring ≤255 ≥1 pattern ^(?=.{1,253}\.?$)(?:(?!-|[^.]+_)[A-Za-z0-9-_]{1,63}(?<!-)(?:\.|$))+$
expected_codesstring pattern ^(\d{3}(\s*,\s*\d{3})*)$|^(\d{3}-\d{3})$
http_methodCONNECT | DELETE | GET | HEAD | OPTIONS | PATCH | POST | PUT | TRACE
http_version1.0 | 1.1
max_retriesinteger [1..10]yes
max_retries_downinteger [1..10]3
namestring ≤255
pool_iduuidyes
project_idstring ≤36
tags[string ≤255]
tenant_idstring ≤36
timeoutinteger [0..]yes
typeHTTP | HTTPS | PING | SCTP | TCP | TLS-HELLO | UDP-CONNECTyes
url_pathurl-path

API → DB names: admin_state_up→enabled, max_retries→rise_threshold, max_retries_down→fall_threshold

**HealthMonitorPUT** (octavia/api/v2/types/health_monitor.py:114)

FieldTypeReq.Default
admin_state_upboolean
delayinteger [0..]
domain_namestring ≤255 ≥1 pattern ^(?=.{1,253}\.?$)(?:(?!-|[^.]+_)[A-Za-z0-9-_]{1,63}(?<!-)(?:\.|$))+$
expected_codesstring pattern ^(\d{3}(\s*,\s*\d{3})*)$|^(\d{3}-\d{3})$
http_methodCONNECT | DELETE | GET | HEAD | OPTIONS | PATCH | POST | PUT | TRACE
http_version1.0 | 1.1
max_retriesinteger [1..10]
max_retries_downinteger [1..10]
namestring ≤255
tags[string ≤255]
timeoutinteger [0..]
url_pathurl-path

API → DB names: admin_state_up→enabled, max_retries→rise_threshold, max_retries_down→fall_threshold

**HealthMonitorResponse** (octavia/api/v2/types/health_monitor.py:28)

FieldTypeReq.Default
admin_state_upboolean
created_atdate-time
delayinteger
domain_namestring
expected_codesstring
http_methodstring
http_versionnumber
iduuid
max_retriesinteger
max_retries_downinteger
namestring
operating_statusstring
pools[IdOnlyType]
project_idstring
provisioning_statusstring
tags[string]
tenant_idstring ≤36
timeoutinteger
typestring
updated_atdate-time
url_pathstring

API → DB names: admin_state_up→enabled, max_retries→rise_threshold, max_retries_down→fall_threshold

L7 policy and rule

**L7PolicyPOST** (octavia/api/v2/types/l7policy.py:84)

FieldTypeReq.Default
actionREDIRECT_PREFIX | REDIRECT_TO_POOL | REDIRECT_TO_URL | REJECTyes
admin_state_upbooleantrue
descriptionstring ≤255
listener_iduuidyes
namestring ≤255
positioninteger [1..2147483647]2147483647
project_idstring ≤36
redirect_http_code301 | 302 | 303 | 307 | 308
redirect_pool_iduuid
redirect_prefixurl
redirect_urlurl
rules[L7RuleSingleCreate]
tags[string ≤255]
tenant_idstring ≤36

API → DB names: admin_state_up→enabled

**L7PolicyPUT** (octavia/api/v2/types/l7policy.py:112)

FieldTypeReq.Default
actionREDIRECT_PREFIX | REDIRECT_TO_POOL | REDIRECT_TO_URL | REJECT
admin_state_upboolean
descriptionstring ≤255
namestring ≤255
positioninteger [1..2147483647]
redirect_http_code301 | 302 | 303 | 307 | 308
redirect_pool_iduuid
redirect_prefixurl
redirect_urlurl
tags[string ≤255]

API → DB names: admin_state_up→enabled

**L7PolicyResponse** (octavia/api/v2/types/l7policy.py:28)

FieldTypeReq.Default
actionstring
admin_state_upboolean
created_atdate-time
descriptionstring
iduuid
listener_iduuid
namestring
operating_statusstring
positioninteger
project_idstring
provisioning_statusstring
redirect_http_codeinteger
redirect_pool_iduuid
redirect_prefixstring
redirect_urlstring
rules[IdOnlyType]
tags[string]
tenant_idstring ≤36
updated_atdate-time

API → DB names: admin_state_up→enabled

**L7RulePOST** (octavia/api/v2/types/l7rule.py:58)

FieldTypeReq.Default
admin_state_upbooleantrue
compare_typeCONTAINS | ENDS_WITH | EQUAL_TO | REGEX | STARTS_WITHyes
invertbooleanfalse
keystring ≤255 pattern ^[^\r\n]*$
project_idstring ≤36
tags[string ≤255]
tenant_idstring ≤36
typeCOOKIE | FILE_TYPE | HEADER | HOST_NAME | PATH | SSL_CONN_HAS_CERT | SSL_DN_FIELD | SSL_VERIFY_RESULTyes
valuestring ≤255 pattern ^[^\r\n]*$yes

API → DB names: admin_state_up→enabled

**L7RulePUT** (octavia/api/v2/types/l7rule.py:82)

FieldTypeReq.Default
admin_state_upboolean
compare_typeCONTAINS | ENDS_WITH | EQUAL_TO | REGEX | STARTS_WITH
invertboolean
keystring ≤255 pattern ^[^\r\n]*$
tags[string ≤255]
typeCOOKIE | FILE_TYPE | HEADER | HOST_NAME | PATH | SSL_CONN_HAS_CERT | SSL_DN_FIELD | SSL_VERIFY_RESULT
valuestring ≤255 pattern ^[^\r\n]*$

API → DB names: admin_state_up→enabled

**L7RuleResponse** (octavia/api/v2/types/l7rule.py:26)

FieldTypeReq.Default
admin_state_upboolean
compare_typestring
created_atdate-time
iduuid
invertboolean
keystring
operating_statusstring
project_idstring
provisioning_statusstring
tags[string]
tenant_idstring ≤36
typestring
updated_atdate-time
valuestring

API → DB names: admin_state_up→enabled

Quota

**QuotaPUT** (octavia/api/v2/types/quotas.py:60)

FieldTypeReq.Default
health_monitorinteger [-1..2000000000]
healthmonitorinteger [-1..2000000000]
l7policyinteger [-1..2000000000]
l7ruleinteger [-1..2000000000]
listenerinteger [-1..2000000000]
load_balancerinteger [-1..2000000000]
loadbalancerinteger [-1..2000000000]
memberinteger [-1..2000000000]
poolinteger [-1..2000000000]

API → DB names: healthmonitor→health_monitor, loadbalancer→load_balancer

**QuotaResponse** (octavia/api/v2/types/quotas.py:27)

FieldTypeReq.Default
health_monitorinteger [-1..2000000000]
healthmonitorinteger [-1..2000000000]
l7policyinteger [-1..2000000000]
l7ruleinteger [-1..2000000000]
listenerinteger [-1..2000000000]
load_balancerinteger [-1..2000000000]
loadbalancerinteger [-1..2000000000]
memberinteger [-1..2000000000]
poolinteger [-1..2000000000]
project_idstring
tenant_idstring ≤36

API → DB names: healthmonitor→health_monitor, loadbalancer→load_balancer

Flavor and profile

**FlavorPOST** (octavia/api/v2/types/flavors.py:43)

FieldTypeReq.Default
descriptionstring ≤255
enabledbooleantrue
flavor_profile_iduuidyes
namestring ≤255yes

**FlavorPUT** (octavia/api/v2/types/flavors.py:55)

FieldTypeReq.Default
descriptionstring ≤255
enabledboolean
namestring ≤255

**FlavorResponse** (octavia/api/v2/types/flavors.py:25)

FieldTypeReq.Default
descriptionstring
enabledboolean
flavor_profile_idstring
iduuid
namestring

**FlavorProfilePOST** (octavia/api/v2/types/flavor_profile.py:42)

FieldTypeReq.Default
flavor_datastring ≤4096yes
namestring ≤255yes
provider_namestring ≤255yes

**FlavorProfilePUT** (octavia/api/v2/types/flavor_profile.py:55)

FieldTypeReq.Default
flavor_datastring ≤4096
namestring ≤255
provider_namestring ≤255

**FlavorProfileResponse** (octavia/api/v2/types/flavor_profile.py:25)

FieldTypeReq.Default
flavor_datastring
iduuid
namestring
provider_namestring

Availability zone and profile

**AvailabilityZonePOST** (octavia/api/v2/types/availability_zones.py:42)

FieldTypeReq.Default
availability_zone_profile_iduuidyes
descriptionstring ≤255
enabledbooleantrue
namestring ≤255yes

**AvailabilityZonePUT** (octavia/api/v2/types/availability_zones.py:55)

FieldTypeReq.Default
descriptionstring ≤255
enabledboolean

**AvailabilityZoneResponse** (octavia/api/v2/types/availability_zones.py:25)

FieldTypeReq.Default
availability_zone_profile_idstring
descriptionstring
enabledboolean
namestring

**AvailabilityZoneProfilePOST** (octavia/api/v2/types/availability_zone_profile.py:43)

FieldTypeReq.Default
availability_zone_datastring ≤4096yes
namestring ≤255yes
provider_namestring ≤255yes

**AvailabilityZoneProfilePUT** (octavia/api/v2/types/availability_zone_profile.py:56)

FieldTypeReq.Default
availability_zone_datastring ≤4096
namestring ≤255
provider_namestring ≤255

**AvailabilityZoneProfileResponse** (octavia/api/v2/types/availability_zone_profile.py:25)

FieldTypeReq.Default
availability_zone_datastring
iduuid
namestring
provider_namestring

Amphora

**AmphoraResponse** (octavia/api/v2/types/amphora.py:25)

FieldTypeReq.Default
cached_zonestring
cert_busyboolean
cert_expirationdate-time
compute_flavorstring
compute_iduuid
created_atdate-time
ha_ipip
ha_port_iduuid
iduuid
image_iduuid
lb_network_ipip
loadbalancer_iduuid
rolestring
statusstring
updated_atdate-time
vrrp_idinteger
vrrp_interfacestring
vrrp_ipip
vrrp_port_iduuid
vrrp_priorityinteger

API → DB names: loadbalancer_id→load_balancer_id

**AmphoraStatisticsResponse** (octavia/api/v2/types/amphora.py:58)

FieldTypeReq.Default
active_connectionsinteger
bytes_ininteger
bytes_outinteger
iduuid
listener_iduuid
loadbalancer_iduuid
request_errorsinteger
total_connectionsinteger

API → DB names: loadbalancer_id→load_balancer_id

Objects inside a graph create

**ListenerSingleCreate** (octavia/api/v2/types/listener.py:221)

FieldTypeReq.Default
admin_state_upbooleantrue
allowed_cidrs[cidr]
alpn_protocols[string]
client_authenticationMANDATORY | NONE | OPTIONAL"NONE"
client_ca_tls_container_refstring ≤255
client_crl_container_refstring ≤255
connection_limitinteger [-1..]-1
default_poolPoolSingleCreate
default_pool_iduuid
default_tls_container_refstring ≤255
descriptionstring ≤255
hsts_include_subdomainsbooleanfalse
hsts_max_ageinteger
hsts_preloadbooleanfalse
insert_headersmap
l7policies[L7PolicySingleCreate][]
namestring ≤255
protocolHTTP | HTTPS | PROMETHEUS | SCTP | TCP | TERMINATED_HTTPS | UDPyes
protocol_portinteger [1..65535]yes
sni_container_refs[string ≤255]
tags[string ≤255]
timeout_client_datainteger [0..2073600000]
timeout_member_connectinteger [0..2073600000]
timeout_member_datainteger [0..2073600000]
timeout_tcp_inspectinteger [0..2073600000]
tls_ciphersstring ≤2048
tls_versions[string ≤32]

API → DB names: admin_state_up→enabled, client_ca_tls_container_ref→client_ca_tls_certificate_id, client_crl_container_ref→client_crl_container_id, default_tls_container_ref→tls_certificate_id, sni_container_refs→sni_containers

**PoolSingleCreate** (octavia/api/v2/types/pool.py:219)

FieldTypeReq.Default
admin_state_upbooleantrue
alpn_protocols[string]
ca_tls_container_refstring ≤255
crl_container_refstring ≤255
descriptionstring ≤255
healthmonitorHealthMonitorSingleCreate
lb_algorithmLEAST_CONNECTIONS | ROUND_ROBIN | SOURCE_IP | SOURCE_IP_PORT
members[MemberSingleCreate]
namestring ≤255
protocolHTTP | HTTPS | PROXY | PROXYV2 | SCTP | TCP | UDP
session_persistenceSessionPersistencePOST
tags[string ≤255]
tls_ciphersstring ≤2048
tls_container_refstring ≤255
tls_enabledbooleanfalse
tls_versions[string ≤32]

API → DB names: admin_state_up→enabled, ca_tls_container_ref→ca_tls_certificate_id, crl_container_ref→crl_container_id, healthmonitor→health_monitor, healthmonitor_id→health_monitor.id, tls_container_ref→tls_certificate_id

**MemberSingleCreate** (octavia/api/v2/types/member.py:111)

FieldTypeReq.Default
addressipyes
admin_state_upbooleantrue
backupbooleanfalse
monitor_addressip
monitor_portinteger [1..65535]
namestring ≤255
protocol_portinteger [1..65535]yes
request_sriovbooleanfalse
subnet_iduuid
tags[string ≤255]
weightinteger [0..256]1

API → DB names: address→ip_address, admin_state_up→enabled

**HealthMonitorSingleCreate** (octavia/api/v2/types/health_monitor.py:143)

FieldTypeReq.Default
admin_state_upbooleantrue
delayinteger [0..]yes
domain_namestring ≤255 ≥1 pattern ^(?=.{1,253}\.?$)(?:(?!-|[^.]+_)[A-Za-z0-9-_]{1,63}(?<!-)(?:\.|$))+$
expected_codesstring pattern ^(\d{3}(\s*,\s*\d{3})*)$|^(\d{3}-\d{3})$
http_methodCONNECT | DELETE | GET | HEAD | OPTIONS | PATCH | POST | PUT | TRACE
http_version1.0 | 1.1
max_retriesinteger [1..10]yes
max_retries_downinteger [1..10]3
namestring ≤255
tags[string ≤255]
timeoutinteger [0..]yes
typeHTTP | HTTPS | PING | SCTP | TCP | TLS-HELLO | UDP-CONNECTyes
url_pathurl-path

API → DB names: admin_state_up→enabled, max_retries→rise_threshold, max_retries_down→fall_threshold

**L7PolicySingleCreate** (octavia/api/v2/types/l7policy.py:134)

FieldTypeReq.Default
actionREDIRECT_PREFIX | REDIRECT_TO_POOL | REDIRECT_TO_URL | REJECTyes
admin_state_upbooleantrue
descriptionstring ≤255
namestring ≤255
positioninteger [1..2147483647]2147483647
redirect_http_code301 | 302 | 303 | 307 | 308
redirect_poolPoolSingleCreate
redirect_prefixurl
redirect_urlurl
rules[L7RuleSingleCreate]
tags[string ≤255]

API → DB names: admin_state_up→enabled

**L7RuleSingleCreate** (octavia/api/v2/types/l7rule.py:103)

FieldTypeReq.Default
admin_state_upbooleantrue
compare_typeCONTAINS | ENDS_WITH | EQUAL_TO | REGEX | STARTS_WITHyes
invertbooleanfalse
keystring ≤255
tags[string ≤255]
typeCOOKIE | FILE_TYPE | HEADER | HOST_NAME | PATH | SSL_CONN_HAS_CERT | SSL_DN_FIELD | SSL_VERIFY_RESULTyes
valuestring ≤255yes

API → DB names: admin_state_up→enabled

6. Policies

97 default rules: 81 operation rules on the API and 16 base rules (octavia/policies/__init__.py:23-27). The rule evaluated by each route is in § 2; _rewrite/policies_stable-2026.2.json, .yaml have them in machine form.

RuleDefault check stringDescription
load-balancer:readrule:load-balancer:observer_and_owner or rule:load-balancer:global_observer or rule:load-balancer:member_and_owner or rule:load-balancer:admin
load-balancer:read-globalrule:load-balancer:global_observer or rule:load-balancer:admin
load-balancer:writerule:load-balancer:member_and_owner or rule:load-balancer:admin
load-balancer:read-quotarule:load-balancer:observer_and_owner or rule:load-balancer:global_observer or rule:load-balancer:member_and_owner or rule:load-balancer:quota-admin or rule:load-balancer:admin
load-balancer:read-quota-globalrule:load-balancer:global_observer or rule:load-balancer:quota-admin or rule:load-balancer:admin
load-balancer:write-quotarule:load-balancer:quota-admin or rule:load-balancer:admin
project-memberrole:member and project_id:%(project_id)s
project-readerrole:reader and project_id:%(project_id)s
context_is_adminrole:admin
load-balancer:adminis_admin:True or role:admin
servicerole:service
load-balancer:global_observerrole:admin
load-balancer:member_and_ownerrule:project-member
load-balancer:observer_and_ownerrule:project-reader
load-balancer:quota-adminrole:admin
load-balancer:ownerproject_id:%(project_id)s
os_load-balancer_api:flavor:get_allrule:load-balancer:readList Flavors
os_load-balancer_api:flavor:postrule:load-balancer:adminCreate a Flavor
os_load-balancer_api:flavor:putrule:load-balancer:adminUpdate a Flavor
os_load-balancer_api:flavor:get_onerule:load-balancer:readShow Flavor details
os_load-balancer_api:flavor:deleterule:load-balancer:adminRemove a Flavor
os_load-balancer_api:flavor-profile:get_allrule:load-balancer:adminList Flavor Profiles
os_load-balancer_api:flavor-profile:postrule:load-balancer:adminCreate a Flavor Profile
os_load-balancer_api:flavor-profile:putrule:load-balancer:adminUpdate a Flavor Profile
os_load-balancer_api:flavor-profile:get_onerule:load-balancer:adminShow Flavor Profile details
os_load-balancer_api:flavor-profile:deleterule:load-balancer:adminRemove a Flavor Profile
os_load-balancer_api:availability-zone:get_allrule:load-balancer:readList Availability Zones
os_load-balancer_api:availability-zone:postrule:load-balancer:adminCreate an Availability Zone
os_load-balancer_api:availability-zone:putrule:load-balancer:adminUpdate an Availability Zone
os_load-balancer_api:availability-zone:get_onerule:load-balancer:readShow Availability Zone details
os_load-balancer_api:availability-zone:deleterule:load-balancer:adminRemove an Availability Zone
os_load-balancer_api:availability-zone-profile:get_allrule:load-balancer:adminList Availability Zones
os_load-balancer_api:availability-zone-profile:postrule:load-balancer:adminCreate an Availability Zone
os_load-balancer_api:availability-zone-profile:putrule:load-balancer:adminUpdate an Availability Zone
os_load-balancer_api:availability-zone-profile:get_onerule:load-balancer:adminShow Availability Zone details
os_load-balancer_api:availability-zone-profile:deleterule:load-balancer:adminRemove an Availability Zone
os_load-balancer_api:healthmonitor:get_allrule:load-balancer:readList Health Monitors of a Pool
os_load-balancer_api:healthmonitor:get_all-globalrule:load-balancer:read-globalList Health Monitors including resources owned by others
os_load-balancer_api:healthmonitor:postrule:load-balancer:writeCreate a Health Monitor
os_load-balancer_api:healthmonitor:get_onerule:load-balancer:readShow Health Monitor details
os_load-balancer_api:healthmonitor:putrule:load-balancer:writeUpdate a Health Monitor
os_load-balancer_api:healthmonitor:deleterule:load-balancer:writeRemove a Health Monitor
os_load-balancer_api:l7policy:get_allrule:load-balancer:readList L7 Policys
os_load-balancer_api:l7policy:get_all-globalrule:load-balancer:read-globalList L7 Policys including resources owned by others
os_load-balancer_api:l7policy:postrule:load-balancer:writeCreate a L7 Policy
os_load-balancer_api:l7policy:get_onerule:load-balancer:readShow L7 Policy details
os_load-balancer_api:l7policy:putrule:load-balancer:writeUpdate a L7 Policy
os_load-balancer_api:l7policy:deleterule:load-balancer:writeRemove a L7 Policy
os_load-balancer_api:l7rule:get_allrule:load-balancer:readList L7 Rules
os_load-balancer_api:l7rule:postrule:load-balancer:writeCreate a L7 Rule
os_load-balancer_api:l7rule:get_onerule:load-balancer:readShow L7 Rule details
os_load-balancer_api:l7rule:putrule:load-balancer:writeUpdate a L7 Rule
os_load-balancer_api:l7rule:deleterule:load-balancer:writeRemove a L7 Rule
os_load-balancer_api:listener:get_allrule:load-balancer:readList Listeners
os_load-balancer_api:listener:get_all-globalrule:load-balancer:read-globalList Listeners including resources owned by others
os_load-balancer_api:listener:postrule:load-balancer:writeCreate a Listener
os_load-balancer_api:listener:get_onerule:load-balancer:readShow Listener details
os_load-balancer_api:listener:putrule:load-balancer:writeUpdate a Listener
os_load-balancer_api:listener:deleterule:load-balancer:writeRemove a Listener
os_load-balancer_api:listener:get_statsrule:load-balancer:readShow Listener statistics
os_load-balancer_api:loadbalancer:get_allrule:load-balancer:readList Load Balancers
os_load-balancer_api:loadbalancer:get_all-globalrule:load-balancer:read-globalList Load Balancers including resources owned by others
os_load-balancer_api:loadbalancer:postrule:load-balancer:writeCreate a Load Balancer
os_load-balancer_api:loadbalancer:post:vip_sg_idsrule:load-balancer:writeCreate a Load Balancer with VIP Security Groups
os_load-balancer_api:loadbalancer:get_onerule:load-balancer:readShow Load Balancer details
os_load-balancer_api:loadbalancer:putrule:load-balancer:writeUpdate a Load Balancer
os_load-balancer_api:loadbalancer:put:vip_sg_idsrule:load-balancer:writeUpdate the VIP Security Groups of a Load Balancer
os_load-balancer_api:loadbalancer:deleterule:load-balancer:writeRemove a Load Balancer
os_load-balancer_api:loadbalancer:get_statsrule:load-balancer:readShow Load Balancer statistics
os_load-balancer_api:loadbalancer:get_statusrule:load-balancer:readShow Load Balancer status
os_load-balancer_api:loadbalancer:put_failoverrule:load-balancer:adminFailover a Load Balancer
os_load-balancer_api:member:get_allrule:load-balancer:read or rule:serviceList Members of a Pool
os_load-balancer_api:member:postrule:load-balancer:writeCreate a Member
os_load-balancer_api:member:get_onerule:load-balancer:readShow Member details
os_load-balancer_api:member:putrule:load-balancer:writeUpdate a Member
os_load-balancer_api:member:deleterule:load-balancer:writeRemove a Member
os_load-balancer_api:pool:get_allrule:load-balancer:readList Pools
os_load-balancer_api:pool:get_all-globalrule:load-balancer:read-globalList Pools including resources owned by others
os_load-balancer_api:pool:postrule:load-balancer:writeCreate a Pool
os_load-balancer_api:pool:get_onerule:load-balancer:readShow Pool details
os_load-balancer_api:pool:putrule:load-balancer:writeUpdate a Pool
os_load-balancer_api:pool:deleterule:load-balancer:writeRemove a Pool
os_load-balancer_api:provider:get_allrule:load-balancer:readList enabled providers
os_load-balancer_api:quota:get_allrule:load-balancer:read-quotaList Quotas
os_load-balancer_api:quota:get_all-globalrule:load-balancer:read-quota-globalList Quotas including resources owned by others
os_load-balancer_api:quota:get_onerule:load-balancer:read-quotaShow Quota details
os_load-balancer_api:quota:putrule:load-balancer:write-quotaUpdate a Quota
os_load-balancer_api:quota:deleterule:load-balancer:write-quotaReset a Quota
os_load-balancer_api:quota:get_defaultsrule:load-balancer:read-quotaShow Default Quota for a Project
os_load-balancer_api:amphora:get_allrule:load-balancer:adminList Amphorae
os_load-balancer_api:amphora:get_onerule:load-balancer:adminShow Amphora details
os_load-balancer_api:amphora:deleterule:load-balancer:adminDelete an Amphora
os_load-balancer_api:amphora:put_configrule:load-balancer:adminUpdate Amphora Agent Configuration
os_load-balancer_api:amphora:put_failoverrule:load-balancer:adminFailover Amphora
os_load-balancer_api:amphora:get_statsrule:load-balancer:adminShow Amphora statistics
os_load-balancer_api:provider-flavor:get_allrule:load-balancer:adminList the provider flavor capabilities.
os_load-balancer_api:provider-availability-zone:get_allrule:load-balancer:adminList the provider availability zone capabilities.

Policy modes

Four things decide who can do what: the code defaults, oslo.policy's [oslo_policy] enforce_new_defaults and enforce_scope (both default true in the oslo.policy shipped by the 2026.2 constraints; Octavia does not set them), and a policy file.

  • Code defaults: member + owner writes, reader + owner reads, admin everything. **The load-balancer_* roles grant nothing.** Quota-related admin is admin. All rules are scope_types=[project], so system-scoped tokens are refused (InvalidScope). (octavia/policies/keystone_default_roles.py:18-82)
  • **enforce_new_defaults=False**: each deprecated rule (role:load-balancer_member and rule:load-balancer:owner, role:load-balancer_observer…, load-balancer_global_observer, load-balancer_quota_admin, load-balancer_admin) is OR-ed with the new one: legacy advanced RBAC (octavia/policies/advanced_rbac.py:35-84).
  • **etc/policy/octavia-advanced-rbac-policy.yaml**: only the load-balancer_* roles (and admin).
  • **etc/policy/keystone_default_roles-policy.yaml**: Keystone personas.
  • **etc/policy/admin_or_owner-policy.yaml**: anyone in the project; admin everything.

is_admin:True in a check string is true when context_is_admin passes, or always under noauth (octavia/common/context.py:31-32, octavia/common/policy.py:103-104, octavia/common/policy.py:143-157).

Samples from _rewrite/rbac-matrix_stable-2026.2.json (all 81 rules × 9 personas × 5 modes, computed by the real enforcer; "own" = target in the caller's project):

code defaults (enforce_new_defaults=True, enforce_scope=True)

Ruleadminlb_adminlb_global_observerlb_memberlb_observerlb_quota_adminmemberother_rolereader
loadbalancer:get_oneown+other—————own—own
loadbalancer:get_all-globalown+other————————
loadbalancer:postown+other—————own——
quota:get_oneown+other—————own—own
quota:putown+other————————
flavor:get_allown+other—————own—own
flavor:postown+other————————
amphora:get_allown+other————————

code defaults with [oslo_policy] enforce_new_defaults=False (deprecated rules OR-ed)

Ruleadminlb_adminlb_global_observerlb_memberlb_observerlb_quota_adminmemberother_rolereader
loadbalancer:get_oneown+otherown+otherown+otherownown—own—own
loadbalancer:get_all-globalown+otherown+otherown+other——————
loadbalancer:postown+otherown+other—own——own——
quota:get_oneown+otherown+otherown+otherownownown+otherown—own
quota:putown+otherown+other———own+other———
flavor:get_allown+otherown+otherown+otherownown—own—own
flavor:postown+otherown+other———————
amphora:get_allown+otherown+other———————

etc/policy/admin_or_owner-policy.yaml

Ruleadminlb_adminlb_global_observerlb_memberlb_observerlb_quota_adminmemberother_rolereader
loadbalancer:get_oneown+otherown+otherownownownownownownown
loadbalancer:get_all-globalown+otherown+other———————
loadbalancer:postown+otherown+otherownownownownownownown
quota:get_oneown+otherown+otherownownownownownownown
quota:putown+otherown+other———————
flavor:get_allown+otherown+otherownownownownownownown
flavor:postown+otherown+other———————
amphora:get_allown+otherown+other———————

etc/policy/keystone_default_roles-policy.yaml

Ruleadminlb_adminlb_global_observerlb_memberlb_observerlb_quota_adminmemberother_rolereader
loadbalancer:get_oneown+other———————own
loadbalancer:get_all-globalown+other————————
loadbalancer:postown+other—————own——
quota:get_oneown+other———————own
quota:putown+other————————
flavor:get_allown+other———————own
flavor:postown+otherown+other———————
amphora:get_allown+otherown+other———————

etc/policy/octavia-advanced-rbac-policy.yaml

Ruleadminlb_adminlb_global_observerlb_memberlb_observerlb_quota_adminmemberother_rolereader
loadbalancer:get_oneown+otherown+otherown+otherownown————
loadbalancer:get_all-globalown+otherown+otherown+other——————
loadbalancer:postown+otherown+other—own—————
quota:get_oneown+otherown+otherown+otherownownown+other———
quota:putown+otherown+other———own+other———
flavor:get_allown+otherown+otherown+otherownown————
flavor:postown+otherown+other———————
amphora:get_allown+otherown+other———————

7. Lifecycles

stateDiagram-v2
  [*] --> PENDING_CREATE: POST (API, in the request transaction)
  PENDING_CREATE --> ACTIVE: provider status message ACTIVE
  PENDING_CREATE --> ERROR: provider status message ERROR
  ACTIVE --> PENDING_UPDATE: PUT, any child write, failover (API)
  ERROR --> PENDING_UPDATE: failover only (LB)
  PENDING_UPDATE --> ACTIVE: provider status ACTIVE
  PENDING_UPDATE --> ERROR: provider status ERROR
  ACTIVE --> PENDING_DELETE: DELETE (API)
  ERROR --> PENDING_DELETE: DELETE (LB)
  PENDING_DELETE --> DELETED: provider status DELETED (LB row kept)
  PENDING_DELETE --> [*]: provider status DELETED (child row removed)
  DELETED --> [*]: housekeeping after load_balancer_expiry_age (absent from Go unless ported)

Child objects use the same states, but ERROR children can only be deleted through the parent's rules: for example a member in ERROR is writable as long as the LB and pool are ACTIVE, since the test is on the parents (octavia/api/v2/controllers/member.py:112-129). Operating status is free-form from the provider: ONLINE, OFFLINE, DEGRADED, ERROR, DRAINING, NO_MONITOR (FK to operating_status).

Source: reverse-spec-octavia/01-rest-api_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.