Provider driver API — ovn-octavia-provider — branch stable/2026.2
Branch
stable/2026.2@c56940998594(11.0.0-2-gc569409) — repository https://opendev.org/openstack/ovn-octavia-provider — analysed on 2026-10-28
ovn-octavia-provider has no REST API of its own. Its "API" is the Octavia provider-driver interface (octavia_lib.api.drivers.provider_base.ProviderDriver), implemented by OvnProviderDriver (ovn_octavia_provider/driver.py:40) and loaded by the Octavia API process through the entry point octavia.api.drivers = ovn (setup.cfg:32-33). This file describes each driver method the way a reverse-spec describes an HTTP endpoint: what the Octavia API call means, what the driver checks synchronously, what it queues, and what the OVN Northbound database looks like afterwards.
References: octavia: is Octavia stable/2026.2 @ 2900ed67cac2, octavia-lib: is octavia-lib stable/2026.2 @ a46b0ff4fa95. [inferred] marks what was not followed to the end.
1. Conventions shared by all methods
1.1 Call path
sequenceDiagram
autonumber
actor U as User
participant API as octavia-api
participant DRV as OvnProviderDriver (in octavia-api)
participant Q as request queue + helper thread
participant NB as OVN NB (ovsdb-server)
participant DA as octavia driver-agent (status socket)
U->>API: POST /v2/lbaas/... (provider=ovn)
API->>DRV: listener_create(listener) (call_provider)
DRV->>DRV: synchronous validation (protocol, algorithm, options)
alt option not supported
DRV-->>API: UnsupportedOptionError
API-->>U: 501 "Provider 'ovn' does not support a requested option"
else accepted
DRV->>Q: put {'type': 'listener_create', 'info': {...}}
DRV-->>API: return (None)
API-->>U: 201, provisioning_status PENDING_CREATE
Q->>NB: OVSDB transact (Load_Balancer external_ids, vips, protocol)
Q->>DA: update_loadbalancer_status {listeners:[ACTIVE/ONLINE], loadbalancers:[ACTIVE]}
end- Octavia instantiates a new driver object for each API call:
get_driver()builds a stevedoreDriverManagerwithinvoke_on_load=Trueevery time (octavia:octavia/api/drivers/driver_factory.py:43). Each instance opens its own OVN NB IDL connection and starts its own helper thread (ovn_octavia_provider/driver.py:51-52,ovn_octavia_provider/helper.py:55-68);__del__puts anexitrequest on the queue (ovn_octavia_provider/driver.py:54-55,ovn_octavia_provider/helper.py:109-111). - Driver methods only validate and enqueue. The actual work runs in the helper thread
request_handler(ovn_octavia_provider/helper.py:408-437), which dispatches on the requesttype(ovn_octavia_provider/helper.py:70-92) and sends the returned status dictionary to Octavia (ovn_octavia_provider/helper.py:425-427). See 05 § request queue. - Exceptions raised synchronously are translated by Octavia's
call_provider(octavia:octavia/api/drivers/utils.py:38): see 02 § translation. - Every unset field arrives as
o_datamodels.UnsetType; the driver replaces an unsetadmin_state_upbyTrue(for exampleovn_octavia_provider/driver.py:96-98). - Methods not overridden (
l7policy_*,l7rule_*,loadbalancer_failover_with_flavor, flavor and availability-zone metadata) keep the base-class behaviour, which raisesNotImplementedError(octavia-lib:octavia_lib/api/drivers/provider_base.py:388) → HTTP 501.
1.2 How Octavia objects are stored in OVN
The provider keeps no database. All its state lives in OVN NB rows, mainly in Load_Balancer.external_ids. A rewrite must reproduce these keys exactly, because Neutron, the maintenance tasks and the sync tool read them.
| OVN NB location | Key / value format | Meaning | Written at |
|---|---|---|---|
Load_Balancer.name | <octavia lb id> | one row per L4 protocol of the Octavia LB (TCP, UDP, SCTP), all with the same name | ovn_octavia_provider/helper.py:1654-1662, ovn_octavia_provider/helper.py:849-915 |
Load_Balancer.protocol | tcp/udp/sctp or empty | protocol of the row; empty for an LB with no listener/pool yet | ovn_octavia_provider/helper.py:1616, ovn_octavia_provider/helper.py:2090-2095 |
external_ids['neutron:vip'] | VIP IP | main VIP | ovn_octavia_provider/helper.py:1619-1623 |
external_ids['neutron:vip_port_id'] | Neutron port UUID | VIP port | same |
external_ids['enabled'] | True/False | LB admin_state_up | same, ovn_octavia_provider/helper.py:2025-2033 |
external_ids['neutron:additional_vips'], ['neutron:additional_vip_port_ids'] | comma-separated | additional VIPs | ovn_octavia_provider/helper.py:1626-1636 |
external_ids['neutron:vip_fip'], ['neutron:additional_vip_fips'] | IP, comma-separated | floating IPs attached to the VIP ports | ovn_octavia_provider/helper.py:3211-3226 |
external_ids['listener_<id>'] or ['listener_<id>:D'] | <protocol_port>:pool_<pool id> (or <port>: without pool) | a listener; suffix :D = disabled | ovn_octavia_provider/helper.py:2073-2082, ovn_octavia_provider/helper.py:1301-1305, ovn_octavia_provider/helper.py:1340-1341 |
external_ids['pool_<id>'] or ['pool_<id>:D'] | comma-separated member_<id>_<ip>:<port>_<subnet id> | a pool and its members | ovn_octavia_provider/helper.py:2331-2333, ovn_octavia_provider/helper.py:1325-1338 |
external_ids['neutron:member_status'] | JSON {member id: operating status} | last known member operating status | ovn_octavia_provider/helper.py:2642-2672 |
external_ids['ls_refs'] | JSON {"neutron-<net id>": refcount} | logical switches the LB is attached to, reference-counted by VIP and members | ovn_octavia_provider/helper.py:1040-1106 |
external_ids['lr_ref'] | comma-separated neutron-<router id> | logical routers the LB is attached to | ovn_octavia_provider/helper.py:1192-1201 |
external_ids['octavia:healthmonitors'] | JSON list of HM ids | health monitors of the row | ovn_octavia_provider/helper.py:3516-3531 |
Load_Balancer.vips | "<vip>:<port>": "<m1 ip>:<port>,<m2 ip>:<port>", IPv6 in [] | the actual data-plane mapping, recomputed from external_ids | ovn_octavia_provider/helper.py:1380-1447 |
Load_Balancer.selection_fields | list | hash fields from the pool algorithm | ovn_octavia_provider/common/constants.py:125-130, ovn_octavia_provider/helper.py:2362-2366 |
Load_Balancer.options['affinity_timeout'] | seconds | SOURCE_IP session persistence | ovn_octavia_provider/helper.py:2343-2359 |
Load_Balancer.health_check | refs to Load_Balancer_Health_Check | one row per VIP (and per FIP) of an HM | ovn_octavia_provider/helper.py:3498-3552 |
Load_Balancer.ip_port_mappings | "<member ip>": "<member LSP name>:<HM source ip>" | tells ovn-northd which port to probe a member through | ovn_octavia_provider/ovsdb/impl_idl_ovn.py:133-153 |
Logical_Switch.load_balancer, Logical_Router.load_balancer | refs | where the LB is applied | ovn_octavia_provider/helper.py:1068-1083, ovn_octavia_provider/helper.py:1163-1202 |
_frame_vip_ips builds vips as follows (ovn_octavia_provider/helper.py:1380-1438): nothing if enabled == 'False'; for each enabled listener with a pool that has members, the online members (status not OFFLINE in neutron:member_status) are grouped by IP family and mapped from every VIP and additional VIP of the same family, and from the VIP FIP and additional FIPs (IPv4 only). The formal algorithm is in 07 § 3.
1.3 Summary table
"Sync errors" are raised in the API call (HTTP status returned by Octavia, see 02). "Async result" is what the helper thread later reports on the status socket.
| Driver method | Handler (driver) | Queued request(s) | Handler (helper) | Sync errors | Async result (success) |
|---|---|---|---|---|---|
create_vip_port | ovn_octavia_provider/driver.py:616 | — (synchronous) | ovn_octavia_provider/helper.py:3106 | 409, 500 | returns VIP dicts |
loadbalancer_create | ovn_octavia_provider/driver.py:195 | lb_create (+ listener/pool/member requests for a full graph) | ovn_octavia_provider/helper.py:1561 | 501 | LB ACTIVE/ONLINE (OFFLINE if disabled) |
loadbalancer_update | ovn_octavia_provider/driver.py:229 | lb_update | ovn_octavia_provider/helper.py:2011 | — | LB ACTIVE/ONLINE/OFFLINE |
loadbalancer_delete | ovn_octavia_provider/driver.py:216 | lb_delete | ovn_octavia_provider/helper.py:1763 | — | LB DELETED/OFFLINE (+ children if cascade) |
loadbalancer_failover | ovn_octavia_provider/driver.py:223 | — | — | 501 always | — |
listener_create | ovn_octavia_provider/driver.py:298 | listener_create | ovn_octavia_provider/helper.py:2066 | 501 | listener ACTIVE/ONLINE, LB ACTIVE |
listener_update | ovn_octavia_provider/driver.py:323 | listener_update | ovn_octavia_provider/helper.py:2218 | 501 | listener ACTIVE (+ ONLINE/OFFLINE) |
listener_delete | ovn_octavia_provider/driver.py:314 | listener_delete | ovn_octavia_provider/helper.py:2166 | — | listener DELETED/OFFLINE |
pool_create | ovn_octavia_provider/driver.py:239 | pool_create (+ hm_create) | ovn_octavia_provider/helper.py:2323 | 501 | pool ACTIVE, ONLINE with a listener else OFFLINE |
pool_update | ovn_octavia_provider/driver.py:277 | pool_update | ovn_octavia_provider/helper.py:2505 | 501 | pool ACTIVE/ONLINE/OFFLINE |
pool_delete | ovn_octavia_provider/driver.py:263 | hm_delete, member_delete, handle_member_dvr, pool_delete | ovn_octavia_provider/helper.py:2429 | 501 (member without subnet) | pool DELETED |
member_create | ovn_octavia_provider/driver.py:380 | member_create, handle_member_dvr | ovn_octavia_provider/helper.py:2805 | 501 | member NO_MONITOR/ONLINE/ERROR/OFFLINE + recomputed tree |
member_update | ovn_octavia_provider/driver.py:485 | member_update | ovn_octavia_provider/helper.py:2954 | 501 | member ACTIVE + recomputed tree |
member_delete | ovn_octavia_provider/driver.py:436 | member_delete, handle_member_dvr | ovn_octavia_provider/helper.py:2906 | 501 | member DELETED + recomputed tree |
member_batch_update | ovn_octavia_provider/driver.py:501 | member_create, member_update, member_delete, handle_member_dvr | see above | 501, 500 | per member |
health_monitor_create | ovn_octavia_provider/driver.py:690 | hm_create | ovn_octavia_provider/helper.py:3941 | 501 | HM ACTIVE/ONLINE, members probed |
health_monitor_update | ovn_octavia_provider/driver.py:707 | hm_update | ovn_octavia_provider/helper.py:4051 | 501 | HM ACTIVE |
health_monitor_delete | ovn_octavia_provider/driver.py:723 | hm_delete | ovn_octavia_provider/helper.py:4107 | — | HM DELETED/NO_MONITOR |
l7policy_*, l7rule_*, flavors, AZ | base class | — | — | 501 always | — |
do_sync (not an Octavia call) | ovn_octavia_provider/driver.py:909 | — (synchronous) | ovn_octavia_provider/driver.py:730 | — | statuses re-sent |
2. Load balancer
2.1 create_vip_port(lb_id, project_id, vip_dict, additional_vip_dicts)
Functional description
- Purpose: allocate the Neutron port(s) that carry the VIP before the LB itself is created. Octavia calls the provider instead of allocating the port itself because the driver implements
create_vip_port. - Actor: Octavia API, on
POST /v2/lbaas/loadbalancers, with the Octavia service credentials ([neutron]/[service_auth], see 03 § Neutron). - Business rules:
- if the user passed
vip_port_id, the port is only read, not created or modified (ovn_octavia_provider/helper.py:3122-3124); - otherwise a port
ovn-lb-vip-<lb_id>is created onvip_network_id/vip_subnet_id, with the requestedvip_addressif any,device_owner = ovn-lb:vipanddevice_id = lb-<lb_id>(ovn_octavia_provider/helper.py:3119-3134). The owner value deliberately has no:distributedsuffix so that Neutron does not turn it into alocalport(ovn_octavia_provider/common/constants.py:40-44); the device id blocks a Novaattach-interfaceon the VIP port (ovn_octavia_provider/maintenance.py:130-145); - each additional VIP gets a port
ovn-lb-vip-additional-<n>-<lb_id>,nfrom 1 (ovn_octavia_provider/helper.py:3135-3146); - if Neutron answers 409 (address in use, or port already created by a previous attempt), the driver looks for a port with the same name on the network and reuses it (
ovn_octavia_provider/helper.py:3090-3102); - on any other Neutron error the ports created during this call are deleted before re-raising (
ovn_octavia_provider/helper.py:3149-3161).
- if the user passed
- Outcome: returns
vip_dictwithvip_port_idandvip_address(first fixed IP of the port) and a list of additional VIP dicts (port_id,network_id,subnet_id,ip_address) (ovn_octavia_provider/driver.py:634-647).
Technical sheet
- Errors: Neutron
ConflictException→driver_exceptions.Conflict(ovn_octavia_provider/driver.py:648-653) → Octavia 409VIPAddressConflict(octavia:octavia/api/drivers/utils.py:54-57); any other exception →DriverErrorwith the Neutron message (ovn_octavia_provider/driver.py:654-663) → 500. - Effects: Neutron
POST /v2.0/ports(one per VIP),GET /v2.0/ports/{id}for a user port.
2.2 loadbalancer_create(loadbalancer)
Functional description
- Purpose: create the OVN load balancer for an Octavia LB whose VIP port exists.
- Preconditions: the VIP port exists in Neutron; its network has an OVN
Logical_Switchnamedneutron-<network id>. - Business rules:
- the request carries
id,vip_address,vip_network_id,admin_state_upand the additional VIPs (ovn_octavia_provider/driver.py:95-108); note thatvip_port_idis not passed, so the helper finds the port by listing the network's ports and matching the VIP address (ovn_octavia_provider/helper.py:1469-1491); - when the API request contains a full graph (listeners, pools, members), the driver queues the child requests in the same call; members without a subnet get the VIP subnet (
ovn_octavia_provider/driver.py:201-214); - an LB on a provider network (logical switch with
neutron:provnet-physical-network) is not attached to its logical switch (ovn_octavia_provider/helper.py:141-161,ovn_octavia_provider/helper.py:1689-1696).
- the request carries
- Flow:
- (synchronous) build the request, enqueue
lb_create(ovn_octavia_provider/driver.py:195-199). - (asynchronous) read the VIP port and subnet from Neutron (
ovn_octavia_provider/helper.py:1565-1581). On failure, delete the provider-created VIP ports and reportERROR(ovn_octavia_provider/helper.py:1582-1613). - create the
Load_Balancerrow:name = lb id,protocol = [],external_ids(VIP, port,enabled, additional VIPs, FIPs,lr_refcarried over),selection_fieldsfrom the algorithm (defaultSOURCE_IP_PORT) (ovn_octavia_provider/helper.py:1619-1662). - attach it to each logical router connected to the VIP subnet, and to the logical switches of that router (
ovn_octavia_provider/helper.py:1668-1685,ovn_octavia_provider/helper.py:1163-1202). - attach it to the VIP logical switch and set
ls_refs(ovn_octavia_provider/helper.py:1689-1714). - report LB
ACTIVEwithONLINE, orOFFLINEwhen disabled (ovn_octavia_provider/helper.py:1716-1725).
- (synchronous) build the request, enqueue
- Outcome:
PENDING_CREATEin the API response,ACTIVEafter the status update. The OVN LB has novipsyet: traffic flows only once a listener with a populated pool exists. - Limitations: an exception after the row was created (step 4–5) reports
ERRORand deletes the VIP port, but **does not delete theLoad_Balancerrow** that was already committed (ovn_octavia_provider/helper.py:1732-1760) [inferred: the row stays until a delete or a sync].
2.3 loadbalancer_update(old, new)
- Purpose: enable or disable the whole LB. Only
admin_state_upis used (ovn_octavia_provider/driver.py:229-236); a name or description change produces an immediateACTIVE(ovn_octavia_provider/helper.py:2015-2016). - Flow: for each OVN row of the LB whose
enableddiffers, setexternal_ids['enabled']and recomputevips(empty when disabled,ovn_octavia_provider/helper.py:1383-1384) in one transaction (ovn_octavia_provider/helper.py:2024-2036). - Outcome: LB
ACTIVE,ONLINEorOFFLINE;ERROR/ERRORon any exception (ovn_octavia_provider/helper.py:2042-2045).
2.4 loadbalancer_delete(loadbalancer, cascade)
Functional description
- Purpose: remove the OVN load balancer(s) of an Octavia LB, and with
cascadeall its children. - Flow:
- (asynchronous) find all OVN rows named after the LB (
ovn_octavia_provider/helper.py:1776-1777). - Not found: report
DELETEDanyway; read the LB from Octavia through the driver-libgetsocket and delete its VIP port if it was created by the provider (name prefixovn-lb-vip-), keep a user-provided one; delete additional VIP ports (ovn_octavia_provider/helper.py:1778-1833). - Found, for each row (
ovn_octavia_provider/helper.py:1840-1841,ovn_octavia_provider/helper.py:1870-2009):- with
cascade: for every pool, delete its members (and un-centralise member FIPs), and report members, pools, listeners and health monitorsDELETED(ovn_octavia_provider/helper.py:1874-1936); - clear
health_check, remove the LB from every logical switch inls_refsand every switch or router that references it, thenlb_del, in one transaction; if a referenced switch or router disappeared meanwhile, retry command by command and ignore missing switches/routers (ovn_octavia_provider/helper.py:1938-2001); - delete the health-monitor ports that are no longer used (
ovn_octavia_provider/helper.py:2005-2007).
- with
- delete the VIP port if its name starts with
ovn-lb-vip-, else keep it (user-provided,ovn_octavia_provider/helper.py:1848-1858); delete every additional VIP port (ovn_octavia_provider/helper.py:1860-1862).
- (asynchronous) find all OVN rows named after the LB (
- Outcome: LB
DELETED/OFFLINE;ERROR/ERRORon an unexpected exception (ovn_octavia_provider/helper.py:1863-1866). Octavia deletes the record when it receivesDELETED(octavia:octavia/api/drivers/driver_agent/driver_updater.py:99-113; a load balancer is only markedDELETED). - Limitations: unlike the main VIP port, additional VIP ports are deleted without checking their name (
ovn_octavia_provider/helper.py:1860-1862).
2.5 loadbalancer_failover(lb_id)
Always raises UnsupportedOptionError('OVN provider does not support loadbalancer failover') (ovn_octavia_provider/driver.py:223-227) → 501. OVN load balancing is distributed and has no amphora to fail over.
3. Listener
3.1 listener_create(listener)
- Purpose: expose a port of the VIP for one L4 protocol.
- Synchronous rules: protocol in
TCP,UDP,SCTP(ovn_octavia_provider/common/constants.py:105-108,ovn_octavia_provider/driver.py:61-66);allowed_cidrsmust be empty (ovn_octavia_provider/driver.py:85-93). - Flow (asynchronous):
- find the OVN row of the LB for this protocol, or create it (
_get_or_create_ovn_lb,ovn_octavia_provider/helper.py:849-915): reuse a row with that protocol; else give the protocol to a row that has none; else create a new row copying the VIP, port,lr_ref,ls_refs, additional VIPs and FIPs of the first row, throughlb_create(ovn_octavia_provider/helper.py:879-912). - add
listener_<id>(orlistener_<id>:Dif disabled) =<protocol_port>:pool_<default pool id>toexternal_ids, set the row protocol if it had no listener, recomputevips(ovn_octavia_provider/helper.py:2072-2097). - if the default pool has health checks, rewrite their
vipwith the listener port (ovn_octavia_provider/helper.py:2114-2121,ovn_octavia_provider/helper.py:3739-3756).
- find the OVN row of the LB for this protocol, or create it (
- Outcome: listener
ACTIVE/ONLINE(OFFLINEif disabled), LBACTIVE; on failure listenerERROR/ERROR, LBACTIVE(ovn_octavia_provider/helper.py:2098-2108). - Limitations: one Octavia LB with listeners of two protocols becomes two OVN
Load_Balancerrows sharing the same name and VIP (doc/source/admin/driver.rst:34).
3.2 listener_update(old, new)
- Synchronous rules:
allowed_cidrsmust be empty (ovn_octavia_provider/driver.py:324). A protocol or port change is not possible in Octavia; the driver passes the old protocol and port (ovn_octavia_provider/driver.py:326-329). - Flow: if
admin_state_upchanges, rename the key betweenlistener_<id>andlistener_<id>:D; ifdefault_pool_idis given, rewrite the value<port>:pool_<id>; recomputevips(ovn_octavia_provider/helper.py:2244-2311). Nothing to do when neither field is present (ovn_octavia_provider/helper.py:2249-2251). - Outcome: listener
ACTIVE(+ONLINE/OFFLINE), LBACTIVE, new default poolACTIVE. OVN row missing → listener and LBERROR(ovn_octavia_provider/helper.py:2237-2242).
3.3 listener_delete(listener)
- Flow: remove the listener key; if the row has no listener and no pool left, either clear its protocol (only row of the LB) or delete the row (another protocol row exists) (
ovn_octavia_provider/helper.py:223-242); recomputevipsunless deleted (ovn_octavia_provider/helper.py:2183-2205). - Outcome: listener
DELETED/OFFLINE, LBACTIVE; OVN row already missing → same statuses (ovn_octavia_provider/helper.py:2175-2181).
4. Pool
4.1 pool_create(pool)
- Synchronous rules: protocol in
TCP/UDP/SCTP;lb_algorithminSOURCE_IP_PORT,SOURCE_IP(ovn_octavia_provider/common/constants.py:110-112,ovn_octavia_provider/driver.py:68-73); session persistence onlySOURCE_IP(ovn_octavia_provider/driver.py:75-83). - Flow (asynchronous):
- find or create the OVN row for the pool protocol (
ovn_octavia_provider/helper.py:2324-2328). - add
pool_<id>(orpool_<id>:D) =''; append the pool key to the listener value when the pool has a listener (ovn_octavia_provider/helper.py:2330-2342). - with session persistence, set
options:affinity_timeouttopersistence_timeout(default360) (ovn_octavia_provider/helper.py:2343-2359). - set
selection_fieldsfrom the algorithm:SOURCE_IP_PORT→ip_dst, ip_src, tp_dst, tp_src;SOURCE_IP→ip_src, ip_dst(ovn_octavia_provider/common/constants.py:125-130,ovn_octavia_provider/helper.py:2362-2366). - if the API request carries a health monitor, the driver also queues
hm_create(ovn_octavia_provider/driver.py:259-261).
- find or create the OVN row for the pool protocol (
- Outcome: pool
ACTIVE,ONLINEif attached to a listener, elseOFFLINE; LB and listenerACTIVE(ovn_octavia_provider/helper.py:2372-2388). Failure → poolERROR(ovn_octavia_provider/helper.py:2389-2402). - Limitations:
selection_fieldsandaffinity_timeoutare row-level in OVN. Two pools of the same protocol with different algorithms or persistence timeouts overwrite each other on the shared row (ovn_octavia_provider/helper.py:2364-2366). The documentation still says onlySOURCE_IP_PORTis supported (doc/source/admin/driver.rst:44), whereas the code acceptsSOURCE_IPtoo.
4.2 pool_update(old, new)
- Synchronous rules: protocol and algorithm are validated when present (
ovn_octavia_provider/driver.py:278-281), but the new algorithm is not sent to the helper:request_infoonly containsid,protocol,loadbalancer_id,admin_state_upandsession_persistence(ovn_octavia_provider/driver.py:282-295). Changing the algorithm of an existing pool is therefore accepted and silently ignored. - Flow: rename
pool_<id>↔pool_<id>:Dforadmin_state_upand recomputevips; updateaffinity_timeoutwhen session persistence is set (ovn_octavia_provider/helper.py:2537-2581). Removing session persistence (null) does not removeaffinity_timeout(ovn_octavia_provider/helper.py:2572) [inferred from the condition]. - Outcome: pool
ACTIVEwithONLINE/OFFLINE, LB and listenersACTIVE.
4.3 pool_delete(pool)
- Flow: the driver first queues
hm_deleteif the pool has a monitor, thenmember_delete+handle_member_dvrfor its members, thenpool_delete(ovn_octavia_provider/driver.py:263-275). The helper removespool_<id>andpool_<id>:D, empties the pool part of listeners that referenced it, removesaffinity_timeout, recomputesvipsand cleans an empty row (ovn_octavia_provider/helper.py:2445-2484). - Outcome: pool
DELETED, LB and former listenersACTIVE. - Limitation: removing
affinity_timeoutis unconditional, even if another pool of the same row still uses persistence (ovn_octavia_provider/helper.py:2476-2480).
5. Member
5.1 member_create(member) / _members_create
Functional description
- Purpose: add a backend IP:port to a pool.
- Synchronous rules (
ovn_octavia_provider/driver.py:383-434):monitor_address/monitor_portare refused (ovn_octavia_provider/driver.py:343-357);- the member's IP family must match the VIP's, unless the LB already mixes families through additional VIPs (
ovn_octavia_provider/driver.py:359-378) — raised asIPVersionsMixingNotSupportedError; - without
subnet_id, the driver takes the LB VIP subnet (read through the driver-lib socket and Neutron) and requires the member address to be inside it (ovn_octavia_provider/helper.py:939-954,ovn_octavia_provider/driver.py:393-407). That subnet id is not returned to Octavia, which is why delete has to derive it again (ovn_octavia_provider/driver.py:440-443).
- Flow (asynchronous):
- find the OVN row that owns
pool_<id>(orpool_<id>:D), skipping Neutron port-forwarding LBs (ovn_octavia_provider/helper.py:917-934). - append
member_<id>_<ip>:<port>_<subnet>to the pool value; recomputevipswhen the member is enabled (ovn_octavia_provider/helper.py:2742-2771). - attach the LB to the member's logical switch (incrementing
ls_refs) and to the routers of the member subnet (ovn_octavia_provider/helper.py:2773-2801). - if the row has health checks, register the member in
ip_port_mappingsthrough the HM port of its subnet; the member isONLINEon success,ERRORotherwise (ovn_octavia_provider/helper.py:2838-2847). - store the operating status in
neutron:member_statusand send the whole recomputed status tree of the LB (ovn_octavia_provider/helper.py:2853-2859,ovn_octavia_provider/helper.py:4284-4407). - the separate
handle_member_dvrrequest centralises the member's floating IP when the LB VIP has a FIP: it clearsexternal_macandlogical_portof the member'sNATrow so that traffic to the member goes through the gateway chassis (ovn_octavia_provider/helper.py:3325-3385).
- find the OVN row that owns
- Outcome: member
NO_MONITORwithout HM,ONLINE/ERRORwith HM,OFFLINEif disabled; pool/listener/LB operating statuses recomputed.
5.2 member_update(old, new)
- Synchronous rules: monitor options refused; IP family check (
ovn_octavia_provider/driver.py:485-489). - Flow: only
admin_state_upmatters. The new operating status isOFFLINE(disabled),ONLINE/ERROR(enabled with HM, keeping a previousERROR), orNO_MONITOR;vipsare recomputed when the member goes from or toOFFLINE, or is enabled; the member is added to or removed fromip_port_mappings(ovn_octavia_provider/helper.py:2975-3036). - Outcome: member
ACTIVE+ recomputed tree. - Limitations: a weight, name or backup flag change is accepted but has no effect in OVN.
5.3 member_delete(member)
- Flow: remove the member from the pool value and recompute
vips, decrementls_refs(removing the LB from the switch at 0), remove it fromip_port_mappingsunless another pool still monitors the same IP, delete the HM port of the subnet when no member uses it any more, drop it fromneutron:member_status, and un-centralise its FIP by sending an empty update of the floating IP to Neutron so that Neutron recomputes the NAT entry (ovn_octavia_provider/helper.py:2861-2952,ovn_octavia_provider/helper.py:3386-3410). - Outcome: member
DELETED+ recomputed tree; a member not in OVN is only logged (ovn_octavia_provider/helper.py:2892-2894).
5.4 member_batch_update(pool_id, members)
- Purpose: replace the member list of a pool (
PUT /v2/lbaas/pools/{id}/members). - Rules: monitor options and IP family mixing make the whole batch fail synchronously, so the Octavia DB stays consistent (
ovn_octavia_provider/driver.py:515-524); a member without a subnet outside the VIP subnet fails the batch (ovn_octavia_provider/driver.py:525-545). - Flow: compare the new list with the pool value in OVN: new member info → create; same
member_<id>_<ip>:<port>_<subnet>string → update; remaining → delete + DVR cleanup (ovn_octavia_provider/driver.py:551-614). - Limitations: the driver reads
ovn_lb.external_idswithout checking that the OVN row exists; an unknown pool raisesAttributeError→ Octavia 500 (ovn_octavia_provider/driver.py:503-505) [inferred]. Changing a member's port or address produces a delete + create, because the comparison key contains them.
6. Health monitor
6.1 health_monitor_create(healthmonitor)
Functional description
- Purpose: let ovn-northd/ovn-controller probe the members and remove failed ones from the data path.
- Synchronous rules (
ovn_octavia_provider/driver.py:666-688): the OVN NB schema must haveLoad_Balancer.health_check(OVN ≥ 2.12);typeis required and must beTCPorUDP-CONNECT(ovn_octavia_provider/common/constants.py:114-116). - Mapping of the Octavia fields:
delay→interval,timeout→timeout,max_retries_down→failure_count,max_retries→success_count(ovn_octavia_provider/driver.py:695-702). HTTP/HTTPS/PING monitors are refused. - Flow (asynchronous):
- find the row owning the pool; report pool
OFFLINEif the pool key is missing (ovn_octavia_provider/helper.py:3948-3965). - create one
Load_Balancer_Health_Checkper VIP and additional VIP, withvip = "<vip>:<listener port>"(''when the pool has no listener yet) and the options, plus one per VIP FIP; reference them fromLoad_Balancer.health_checkand add the HM id tooctavia:healthmonitors, in one transaction (ovn_octavia_provider/helper.py:3453-3559). - for each member: find its LSP, ensure the HM source port
ovn-lb-hm-<subnet id>exists in Neutron (create it withdevice_owner = ovn-lb-hm:distributed, port security off), and setip_port_mappings[member ip] = "<member LSP>:<HM port ip>"(ovn_octavia_provider/helper.py:3851-3901,ovn_octavia_provider/helper.py:123-139,ovn_octavia_provider/helper.py:190-201). If a member fails, the HM isERRORand the pool's mappings are cleaned (ovn_octavia_provider/helper.py:3992-4001).
- find the row owning the pool; report pool
- Outcome: HM
ACTIVE/ONLINE(OFFLINEif disabled), pool/listenersONLINE, membersONLINE; the real member statuses arrive later throughService_Monitorevents (05 § ServiceMonitorUpdateEvent). - Limitations: the HM
admin_state_uponly changes the reported status: a disabled HM still has active health checks in OVN (ovn_octavia_provider/helper.py:3488-3490) [inferred: no other use of the flag].
6.2 health_monitor_update(old, new)
Updates the four options of every Load_Balancer_Health_Check of the HM; only non-empty values are written (ovn_octavia_provider/helper.py:4073-4089). Reports LB, pool and HM ACTIVE (OFFLINE if disabled). Missing health checks → HM ERROR/ERROR (ovn_octavia_provider/helper.py:4061-4064).
6.3 health_monitor_delete(healthmonitor)
Sets the pool's members back to NO_MONITOR, removes their ip_port_mappings, destroys the health-check rows, updates or removes octavia:healthmonitors, deletes the unused HM ports (ovn_octavia_provider/helper.py:4107-4206). Reports HM DELETED/NO_MONITOR, even if nothing was found in OVN (ovn_octavia_provider/helper.py:4116-4122).
7. Synchronisation entry points (outside the Octavia API)
do_sync(**lb_filters) (ovn_octavia_provider/driver.py:909-952) lists the Octavia LBs through the Octavia REST API (provider=ovn), reads each full LB through the driver-lib get socket, and calls _ensure_loadbalancer and _fip_sync:
- LB missing in OVN → create LB, listeners, pools, members, HMs synchronously and send the statuses (
ovn_octavia_provider/driver.py:734-776); - LB present →
lb_sync,listener_sync,pool_sync,member_sync(extra OVN members are deleted),hm_sync, thenhm_purgeof orphan health checks and a full status update (ovn_octavia_provider/driver.py:778-854); - FIP: compares the Neutron FIP of the VIP port with
neutron:port_fipon the VIP LSP and re-applies it, or only warns and asks to runneutron-ovn-db-sync-utilfirst (ovn_octavia_provider/driver.py:856-907).
It is called by octavia-ovn-db-sync-util (ovn_octavia_provider/cmd/octavia_ovn_db_sync_util.py:37-59) and by the Neutron sync plugin octavia_ovn_sync in repair/migrate mode (ovn_octavia_provider/ovn/db_sync.py:154-187); see 05 § sync.
8. Lifecycles
Statuses are those of Octavia; the provider only reports them. PENDING_* statuses are set by the Octavia API before the driver call [inferred: Octavia behaviour, not read in detail].
Load balancer
stateDiagram-v2 [*] --> PENDING_CREATE: POST loadbalancers (API) PENDING_CREATE --> ACTIVE: lb_create OK (status socket) PENDING_CREATE --> ERROR: Neutron/OVN failure in lb_create ACTIVE --> PENDING_UPDATE: PUT / child create-update-delete (API) PENDING_UPDATE --> ACTIVE: handler status (LB ACTIVE) PENDING_UPDATE --> ERROR: lb_update exception ACTIVE --> PENDING_DELETE: DELETE (API) PENDING_DELETE --> DELETED: lb_delete (always DELETED unless exception) PENDING_DELETE --> ERROR: exception in lb_delete DELETED --> [*]
Member operating status
stateDiagram-v2 [*] --> NO_MONITOR: member_create, pool without HM [*] --> ONLINE: member_create, pool with HM, mapping OK [*] --> ERROR: member_create, pool with HM, LSP/HM port missing [*] --> OFFLINE: member_create, admin_state_up=false NO_MONITOR --> ONLINE: hm_create (all members set ONLINE) ONLINE --> ERROR: Service_Monitor status=offline (OVSDB event) ERROR --> ONLINE: Service_Monitor status=online (OVSDB event) ONLINE --> OFFLINE: Service_Monitor row deleted / member disabled OFFLINE --> ONLINE: member_update admin_state_up=true (with HM) ONLINE --> NO_MONITOR: hm_delete ERROR --> NO_MONITOR: hm_delete
Pool, listener and LB operating statuses are derived from member statuses on each update (ovn_octavia_provider/helper.py:4337-4405): a pool is OFFLINE if disabled or if no member status is known, ERROR if all members are ERROR, DEGRADED if some are, else ONLINE; a listener is ERROR/DEGRADED if one of its pools is; the LB is ERROR/DEGRADED if one of its listeners is.
Source: reverse-spec-ovn-octavia-provider/01-driver-api_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.