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

Reverse-spec · Services

ovn-octavia-provider — Driver api

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 stevedore DriverManager with invoke_on_load=True every 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 an exit request 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 request type (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 unset admin_state_up by True (for example ovn_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 raises NotImplementedError (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 locationKey / value formatMeaningWritten at
Load_Balancer.name<octavia lb id>one row per L4 protocol of the Octavia LB (TCP, UDP, SCTP), all with the same nameovn_octavia_provider/helper.py:1654-1662, ovn_octavia_provider/helper.py:849-915
Load_Balancer.protocoltcp/udp/sctp or emptyprotocol of the row; empty for an LB with no listener/pool yetovn_octavia_provider/helper.py:1616, ovn_octavia_provider/helper.py:2090-2095
external_ids['neutron:vip']VIP IPmain VIPovn_octavia_provider/helper.py:1619-1623
external_ids['neutron:vip_port_id']Neutron port UUIDVIP portsame
external_ids['enabled']True/FalseLB admin_state_upsame, ovn_octavia_provider/helper.py:2025-2033
external_ids['neutron:additional_vips'], ['neutron:additional_vip_port_ids']comma-separatedadditional VIPsovn_octavia_provider/helper.py:1626-1636
external_ids['neutron:vip_fip'], ['neutron:additional_vip_fips']IP, comma-separatedfloating IPs attached to the VIP portsovn_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 = disabledovn_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 membersovn_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 statusovn_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 membersovn_octavia_provider/helper.py:1040-1106
external_ids['lr_ref']comma-separated neutron-<router id>logical routers the LB is attached toovn_octavia_provider/helper.py:1192-1201
external_ids['octavia:healthmonitors']JSON list of HM idshealth monitors of the rowovn_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_idsovn_octavia_provider/helper.py:1380-1447
Load_Balancer.selection_fieldslisthash fields from the pool algorithmovn_octavia_provider/common/constants.py:125-130, ovn_octavia_provider/helper.py:2362-2366
Load_Balancer.options['affinity_timeout']secondsSOURCE_IP session persistenceovn_octavia_provider/helper.py:2343-2359
Load_Balancer.health_checkrefs to Load_Balancer_Health_Checkone row per VIP (and per FIP) of an HMovn_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 throughovn_octavia_provider/ovsdb/impl_idl_ovn.py:133-153
Logical_Switch.load_balancer, Logical_Router.load_balancerrefswhere the LB is appliedovn_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 methodHandler (driver)Queued request(s)Handler (helper)Sync errorsAsync result (success)
create_vip_portovn_octavia_provider/driver.py:616— (synchronous)ovn_octavia_provider/helper.py:3106409, 500returns VIP dicts
loadbalancer_createovn_octavia_provider/driver.py:195lb_create (+ listener/pool/member requests for a full graph)ovn_octavia_provider/helper.py:1561501LB ACTIVE/ONLINE (OFFLINE if disabled)
loadbalancer_updateovn_octavia_provider/driver.py:229lb_updateovn_octavia_provider/helper.py:2011—LB ACTIVE/ONLINE/OFFLINE
loadbalancer_deleteovn_octavia_provider/driver.py:216lb_deleteovn_octavia_provider/helper.py:1763—LB DELETED/OFFLINE (+ children if cascade)
loadbalancer_failoverovn_octavia_provider/driver.py:223——501 always—
listener_createovn_octavia_provider/driver.py:298listener_createovn_octavia_provider/helper.py:2066501listener ACTIVE/ONLINE, LB ACTIVE
listener_updateovn_octavia_provider/driver.py:323listener_updateovn_octavia_provider/helper.py:2218501listener ACTIVE (+ ONLINE/OFFLINE)
listener_deleteovn_octavia_provider/driver.py:314listener_deleteovn_octavia_provider/helper.py:2166—listener DELETED/OFFLINE
pool_createovn_octavia_provider/driver.py:239pool_create (+ hm_create)ovn_octavia_provider/helper.py:2323501pool ACTIVE, ONLINE with a listener else OFFLINE
pool_updateovn_octavia_provider/driver.py:277pool_updateovn_octavia_provider/helper.py:2505501pool ACTIVE/ONLINE/OFFLINE
pool_deleteovn_octavia_provider/driver.py:263hm_delete, member_delete, handle_member_dvr, pool_deleteovn_octavia_provider/helper.py:2429501 (member without subnet)pool DELETED
member_createovn_octavia_provider/driver.py:380member_create, handle_member_dvrovn_octavia_provider/helper.py:2805501member NO_MONITOR/ONLINE/ERROR/OFFLINE + recomputed tree
member_updateovn_octavia_provider/driver.py:485member_updateovn_octavia_provider/helper.py:2954501member ACTIVE + recomputed tree
member_deleteovn_octavia_provider/driver.py:436member_delete, handle_member_dvrovn_octavia_provider/helper.py:2906501member DELETED + recomputed tree
member_batch_updateovn_octavia_provider/driver.py:501member_create, member_update, member_delete, handle_member_dvrsee above501, 500per member
health_monitor_createovn_octavia_provider/driver.py:690hm_createovn_octavia_provider/helper.py:3941501HM ACTIVE/ONLINE, members probed
health_monitor_updateovn_octavia_provider/driver.py:707hm_updateovn_octavia_provider/helper.py:4051501HM ACTIVE
health_monitor_deleteovn_octavia_provider/driver.py:723hm_deleteovn_octavia_provider/helper.py:4107—HM DELETED/NO_MONITOR
l7policy_*, l7rule_*, flavors, AZbase 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 on vip_network_id/vip_subnet_id, with the requested vip_address if any, device_owner = ovn-lb:vip and device_id = lb-<lb_id> (ovn_octavia_provider/helper.py:3119-3134). The owner value deliberately has no :distributed suffix so that Neutron does not turn it into a localport (ovn_octavia_provider/common/constants.py:40-44); the device id blocks a Nova attach-interface on the VIP port (ovn_octavia_provider/maintenance.py:130-145);
    • each additional VIP gets a port ovn-lb-vip-additional-<n>-<lb_id>, n from 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).
  • Outcome: returns vip_dict with vip_port_id and vip_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 409 VIPAddressConflict (octavia:octavia/api/drivers/utils.py:54-57); any other exception → DriverError with 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_Switch named neutron-<network id>.
  • Business rules:
    • the request carries id, vip_address, vip_network_id, admin_state_up and the additional VIPs (ovn_octavia_provider/driver.py:95-108); note that vip_port_id is 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).
  • Flow:
    1. (synchronous) build the request, enqueue lb_create (ovn_octavia_provider/driver.py:195-199).
    2. (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 report ERROR (ovn_octavia_provider/helper.py:1582-1613).
    3. create the Load_Balancer row: name = lb id, protocol = [], external_ids (VIP, port, enabled, additional VIPs, FIPs, lr_ref carried over), selection_fields from the algorithm (default SOURCE_IP_PORT) (ovn_octavia_provider/helper.py:1619-1662).
    4. 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).
    5. attach it to the VIP logical switch and set ls_refs (ovn_octavia_provider/helper.py:1689-1714).
    6. report LB ACTIVE with ONLINE, or OFFLINE when disabled (ovn_octavia_provider/helper.py:1716-1725).
  • Outcome: PENDING_CREATE in the API response, ACTIVE after the status update. The OVN LB has no vips yet: traffic flows only once a listener with a populated pool exists.
  • Limitations: an exception after the row was created (step 4–5) reports ERROR and deletes the VIP port, but **does not delete the Load_Balancer row** 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_up is used (ovn_octavia_provider/driver.py:229-236); a name or description change produces an immediate ACTIVE (ovn_octavia_provider/helper.py:2015-2016).
  • Flow: for each OVN row of the LB whose enabled differs, set external_ids['enabled'] and recompute vips (empty when disabled, ovn_octavia_provider/helper.py:1383-1384) in one transaction (ovn_octavia_provider/helper.py:2024-2036).
  • Outcome: LB ACTIVE, ONLINE or OFFLINE; ERROR/ERROR on 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 cascade all its children.
  • Flow:
    1. (asynchronous) find all OVN rows named after the LB (ovn_octavia_provider/helper.py:1776-1777).
    2. Not found: report DELETED anyway; read the LB from Octavia through the driver-lib get socket and delete its VIP port if it was created by the provider (name prefix ovn-lb-vip-), keep a user-provided one; delete additional VIP ports (ovn_octavia_provider/helper.py:1778-1833).
    3. 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 monitors DELETED (ovn_octavia_provider/helper.py:1874-1936);
      • clear health_check, remove the LB from every logical switch in ls_refs and every switch or router that references it, then lb_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).
    4. 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).
  • Outcome: LB DELETED/OFFLINE; ERROR/ERROR on an unexpected exception (ovn_octavia_provider/helper.py:1863-1866). Octavia deletes the record when it receives DELETED (octavia:octavia/api/drivers/driver_agent/driver_updater.py:99-113; a load balancer is only marked DELETED).
  • 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_cidrs must be empty (ovn_octavia_provider/driver.py:85-93).
  • Flow (asynchronous):
    1. 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, through lb_create (ovn_octavia_provider/helper.py:879-912).
    2. add listener_<id> (or listener_<id>:D if disabled) = <protocol_port>:pool_<default pool id> to external_ids, set the row protocol if it had no listener, recompute vips (ovn_octavia_provider/helper.py:2072-2097).
    3. if the default pool has health checks, rewrite their vip with the listener port (ovn_octavia_provider/helper.py:2114-2121, ovn_octavia_provider/helper.py:3739-3756).
  • Outcome: listener ACTIVE/ONLINE (OFFLINE if disabled), LB ACTIVE; on failure listener ERROR/ERROR, LB ACTIVE (ovn_octavia_provider/helper.py:2098-2108).
  • Limitations: one Octavia LB with listeners of two protocols becomes two OVN Load_Balancer rows sharing the same name and VIP (doc/source/admin/driver.rst:34).

3.2 listener_update(old, new)

  • Synchronous rules: allowed_cidrs must 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_up changes, rename the key between listener_<id> and listener_<id>:D; if default_pool_id is given, rewrite the value <port>:pool_<id>; recompute vips (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), LB ACTIVE, new default pool ACTIVE. OVN row missing → listener and LB ERROR (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); recompute vips unless deleted (ovn_octavia_provider/helper.py:2183-2205).
  • Outcome: listener DELETED/OFFLINE, LB ACTIVE; 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_algorithm in SOURCE_IP_PORT, SOURCE_IP (ovn_octavia_provider/common/constants.py:110-112, ovn_octavia_provider/driver.py:68-73); session persistence only SOURCE_IP (ovn_octavia_provider/driver.py:75-83).
  • Flow (asynchronous):
    1. find or create the OVN row for the pool protocol (ovn_octavia_provider/helper.py:2324-2328).
    2. add pool_<id> (or pool_<id>:D) = ''; append the pool key to the listener value when the pool has a listener (ovn_octavia_provider/helper.py:2330-2342).
    3. with session persistence, set options:affinity_timeout to persistence_timeout (default 360) (ovn_octavia_provider/helper.py:2343-2359).
    4. set selection_fields from 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).
    5. if the API request carries a health monitor, the driver also queues hm_create (ovn_octavia_provider/driver.py:259-261).
  • Outcome: pool ACTIVE, ONLINE if attached to a listener, else OFFLINE; LB and listener ACTIVE (ovn_octavia_provider/helper.py:2372-2388). Failure → pool ERROR (ovn_octavia_provider/helper.py:2389-2402).
  • Limitations: selection_fields and affinity_timeout are 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 only SOURCE_IP_PORT is supported (doc/source/admin/driver.rst:44), whereas the code accepts SOURCE_IP too.

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_info only contains id, protocol, loadbalancer_id, admin_state_up and session_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>:D for admin_state_up and recompute vips; update affinity_timeout when session persistence is set (ovn_octavia_provider/helper.py:2537-2581). Removing session persistence (null) does not remove affinity_timeout (ovn_octavia_provider/helper.py:2572) [inferred from the condition].
  • Outcome: pool ACTIVE with ONLINE/OFFLINE, LB and listeners ACTIVE.

4.3 pool_delete(pool)

  • Flow: the driver first queues hm_delete if the pool has a monitor, then member_delete + handle_member_dvr for its members, then pool_delete (ovn_octavia_provider/driver.py:263-275). The helper removes pool_<id> and pool_<id>:D, empties the pool part of listeners that referenced it, removes affinity_timeout, recomputes vips and cleans an empty row (ovn_octavia_provider/helper.py:2445-2484).
  • Outcome: pool DELETED, LB and former listeners ACTIVE.
  • Limitation: removing affinity_timeout is 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_port are 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 as IPVersionsMixingNotSupportedError;
    • 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):
    1. find the OVN row that owns pool_<id> (or pool_<id>:D), skipping Neutron port-forwarding LBs (ovn_octavia_provider/helper.py:917-934).
    2. append member_<id>_<ip>:<port>_<subnet> to the pool value; recompute vips when the member is enabled (ovn_octavia_provider/helper.py:2742-2771).
    3. 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).
    4. if the row has health checks, register the member in ip_port_mappings through the HM port of its subnet; the member is ONLINE on success, ERROR otherwise (ovn_octavia_provider/helper.py:2838-2847).
    5. store the operating status in neutron:member_status and send the whole recomputed status tree of the LB (ovn_octavia_provider/helper.py:2853-2859, ovn_octavia_provider/helper.py:4284-4407).
    6. the separate handle_member_dvr request centralises the member's floating IP when the LB VIP has a FIP: it clears external_mac and logical_port of the member's NAT row so that traffic to the member goes through the gateway chassis (ovn_octavia_provider/helper.py:3325-3385).
  • Outcome: member NO_MONITOR without HM, ONLINE/ERROR with HM, OFFLINE if 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_up matters. The new operating status is OFFLINE (disabled), ONLINE/ERROR (enabled with HM, keeping a previous ERROR), or NO_MONITOR; vips are recomputed when the member goes from or to OFFLINE, or is enabled; the member is added to or removed from ip_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, decrement ls_refs (removing the LB from the switch at 0), remove it from ip_port_mappings unless another pool still monitors the same IP, delete the HM port of the subnet when no member uses it any more, drop it from neutron: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_ids without checking that the OVN row exists; an unknown pool raises AttributeError → 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 have Load_Balancer.health_check (OVN ≥ 2.12); type is required and must be TCP or UDP-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):
    1. find the row owning the pool; report pool OFFLINE if the pool key is missing (ovn_octavia_provider/helper.py:3948-3965).
    2. create one Load_Balancer_Health_Check per VIP and additional VIP, with vip = "<vip>:<listener port>" ('' when the pool has no listener yet) and the options, plus one per VIP FIP; reference them from Load_Balancer.health_check and add the HM id to octavia:healthmonitors, in one transaction (ovn_octavia_provider/helper.py:3453-3559).
    3. for each member: find its LSP, ensure the HM source port ovn-lb-hm-<subnet id> exists in Neutron (create it with device_owner = ovn-lb-hm:distributed, port security off), and set ip_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 is ERROR and the pool's mappings are cleaned (ovn_octavia_provider/helper.py:3992-4001).
  • Outcome: HM ACTIVE/ONLINE (OFFLINE if disabled), pool/listeners ONLINE, members ONLINE; the real member statuses arrive later through Service_Monitor events (05 § ServiceMonitorUpdateEvent).
  • Limitations: the HM admin_state_up only 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, then hm_purge of 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_fip on the VIP LSP and re-applies it, or only warns and asks to run neutron-ovn-db-sync-util first (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.