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

Reverse-spec · Services

ovn-octavia-provider — Async processing

Asynchronous processing — 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

Everything the provider does after the Octavia API call returns happens here: the request queue consumed by a helper thread, the OVSDB events watched by the driver agent, and the maintenance tasks. There is no RabbitMQ consumer and no SQL polling.

ProcessTriggerTypePurposeReadsWrites / emitsDefined at
octavia-api (driver) and driver agentrequest_handlerin-process queue (queue.Queue), consumed by one daemon thread per OvnProviderHelperexecute the queued create/update/delete requests in orderqueue items, NB replica, Neutron, get socketNB transactions, status socketovn_octavia_provider/helper.py:408-437
driver agentLogicalRouterPortEventOVSDB event, NB Logical_Router_Port, create + deleteattach/detach LBs when a router interface is added to/removed from a networkexternal_ids of the LRP, router and switch rowsqueue lb_create_lrp_assoc / lb_delete_lrp_assocovn_octavia_provider/event.py:25-42
driver agentLogicalSwitchPortUpdateEventOVSDB event, NB Logical_Switch_Port, update, filter: port name ovn-lb-vip-* and external_ids changedfollow floating-IP association on the VIP portneutron:port_fip old/newqueue handle_vip_fipovn_octavia_provider/event.py:45-76
driver agentServiceMonitorUpdateEventOVSDB event, SB Service_Monitor, update + deleteturn OVN health-check results into member/pool/listener/LB operating statusesip, port, src_ip, logical_port, protocol, statusqueue hm_update_event → status socketovn_octavia_provider/event.py:79-96
driver agentchange_device_owner_lb_hm_portsperiodic, every 600 s, runs at start, stops itself when donemigrate HM ports from network:distributed to ovn-lb-hm:distributedNeutron ports, NB Logical_Switch_Port.typeNeutron PUT /portsovn_octavia_provider/maintenance.py:72-126
driver agentadd_device_owner_lb_vip_portsperiodic, 600 s, runs at start, one run onlybackfill device_owner=ovn-lb:vip, device_id=lb-<id> on legacy VIP portsNB Load_Balancer, Neutron portsNeutron PUT /portsovn_octavia_provider/maintenance.py:128-226
driver agentformat_ip_port_mappings_ipv6periodic, 600 s, runs at start, one run onlybracket IPv6 addresses in ip_port_mappingsNB Load_Balancer.ip_port_mappingsNB transactionsovn_octavia_provider/maintenance.py:228-266
operator (CLI) / neutron-ovn-db-sync-utildo_syncon demandre-create or repair OVN LBs from the Octavia databaseOctavia REST + get socket, NB, NeutronNB, status socketovn_octavia_provider/driver.py:909-952

1. Processes and threads

flowchart LR
  subgraph api[octavia-api process]
    D1[OvnProviderDriver per API call] --> Q1[(queue)] --> T1[helper thread]
  end
  subgraph agent[octavia-driver-agent: provider agent 'ovn']
    EV[NB IDL with event lock] --> Q2[(queue)]
    SB[SB IDL with event lock] --> Q2
    Q2 --> T2[helper thread]
    M[MaintenanceThread: futurist PeriodicWorker]
  end
  T1 -->|OVSDB transact| NB[(OVN NB)]
  T2 -->|OVSDB transact| NB
  M -->|OVSDB / Neutron REST| NB
  NB -.monitor.-> EV
  SBDB[(OVN SB)] -.monitor.-> SB
  T1 -->|status.sock| DA[driver agent sockets]
  T2 -->|status.sock| DA
  • The provider agent is started by Octavia's driver agent through the entry point octavia.driver_agent.provider_agents = ovn (setup.cfg:38-39) when ovn is in [driver_agent] enabled_provider_agents (octavia:octavia/common/config.py:914). OvnProviderAgent(exit_event) creates one helper, the event IDLs, the maintenance thread, then waits on exit_event and stops everything (ovn_octavia_provider/agent.py:31-74).
  • The two event IDLs share the lock name neutron_ovn_octavia_event_lock (ovn_octavia_provider/agent.py:28); only the agent holding the lock on each database handles events (ovn_octavia_provider/ovsdb/ovsdb_monitor.py:68-76).
  • The maintenance thread runs the three periodic tasks in a futurist.periodics.PeriodicWorker on a daemon thread (ovn_octavia_provider/maintenance.py:36-61); the tasks take no lock, so every agent runs them.

2. The request queue (request_handler)

  • Trigger: any add_request() (ovn_octavia_provider/helper.py:439-440): driver methods in the Octavia API process, event handlers in the agent. The queue is unbounded; put has a 5 s timeout that can only fire on a full queue (ovn_octavia_provider/common/constants.py:136-137).
  • Loop: get with a 5 s timeout, continue on empty; exit ends the thread; otherwise look up the handler by type (20 types, ovn_octavia_provider/helper.py:70-92), call it, send its status if not empty (ovn_octavia_provider/helper.py:408-437).
  • Ordering: strictly FIFO per helper, one request at a time. A driver call that queues several requests (LB with full graph, pool delete, member create + DVR) has them executed in that order (ovn_octavia_provider/driver.py:195-214, ovn_octavia_provider/driver.py:263-275, ovn_octavia_provider/driver.py:428-434).
  • Concurrency: the API process creates a new driver, hence a new queue and thread, per Octavia call, so requests of different API calls run in parallel threads against the same NB rows, and are not ordered between themselves. Octavia's own locking (immutable PENDING_* load balancer) prevents most conflicting calls on one LB [inferred: Octavia API behaviour]; the agent's thread (events) is not covered by that lock.
  • Lifetime: the driver's __del__ enqueues exit (ovn_octavia_provider/driver.py:54-55), so the thread ends after it drained the requests queued before. Its NB connection is stopped by atexit only (ovn_octavia_provider/helper.py:66) [inferred: one IDL connection per API call stays open until garbage collection closes it].
  • Error behaviour: handler exceptions are logged and the loop continues (ovn_octavia_provider/helper.py:429-437); see 02 § 3.
  • Cost: status computation (_get_current_operating_statuses) makes one get-socket call for the LB plus one per pool and listener missing from the LB graph (ovn_octavia_provider/helper.py:4325-4376).

3. OVSDB events (driver agent)

3.1 LogicalRouterPortEvent — router interface added or removed

  • Trigger: NB Logical_Router_Port row created or deleted, no condition (ovn_octavia_provider/event.py:27-32).
  • Emitter: Neutron's OVN mechanism driver, on router interface add/remove (and gateway set/clear).
  • Purpose: make LBs reachable from every network attached to the router of their VIP or members.
  • Flow:
    1. resolve the router (neutron-<external_ids['neutron:router_name']>) and network (external_ids['neutron:network_name']) of the LRP; missing → ignored (ovn_octavia_provider/helper.py:203-221, ovn_octavia_provider/helper.py:244-253, ovn_octavia_provider/helper.py:292-304).
    2. queue lb_create_lrp_assoc with is_gw_port from external_ids['neutron:is_ext_gw'] (ovn_octavia_provider/helper.py:298-304) or lb_delete_lrp_assoc.
    3. create: add to the router every LB of the switch it does not already have (and to the router's other switches); unless the port is a gateway port, add to the switch every LB of the router it does not have, without touching ls_refs (ovn_octavia_provider/helper.py:306-339).
    4. delete: remove from the router the LBs whose ls_refs contain this switch; remove from the switch the router's other LBs (ovn_octavia_provider/helper.py:255-290).
  • Concurrency and idempotency: may_exist/if_exists on the add/del commands make a replay harmless (ovn_octavia_provider/helper.py:1172-1174, ovn_octavia_provider/helper.py:1159-1160).
  • Error behaviour: RowNotFound → step-by-step retry, then logged and skipped.

3.2 LogicalSwitchPortUpdateEvent — floating IP on the VIP

  • Trigger: NB Logical_Switch_Port update where the new row's neutron:port_name starts with ovn-lb-vip- and external_ids is in the changed columns (ovn_octavia_provider/event.py:54-60). Additional VIP ports (ovn-lb-vip-additional-…) match the same prefix.
  • Emitter: Neutron, which writes external_ids['neutron:port_fip'] when a FIP is associated with the port.
  • Flow: compare old and new neutron:port_fip: removed → disassociate with the old FIP, else associate with the new one (ovn_octavia_provider/event.py:62-76). The handler extracts the LB id from the port name, reads the port from Neutron, and queues one handle_vip_fip per OVN row of the LB (ovn_octavia_provider/helper.py:341-384). handle_vip_fip stores or removes neutron:vip_fip / neutron:additional_vip_fips, creates or destroys the matching Load_Balancer_Health_Check for the FIP, and recomputes vips so that the FIP itself becomes a VIP (ovn_octavia_provider/helper.py:3199-3323).
  • Why: in OVN, traffic arriving on the FIP must match a vips entry for the FIP address; NAT alone is not enough for distributed load balancing **[inferred from the docstring, ovn_octavia_provider/helper.py:342-351]**.
  • Cost: one Neutron GET /ports/{id} per event and per OVN row (ovn_octavia_provider/helper.py:370-371).

3.3 ServiceMonitorUpdateEvent — health-check results

  • Trigger: SB Service_Monitor row updated or deleted (ovn_octavia_provider/event.py:81-86). ovn-northd creates one row per ip_port_mappings backend of an LB with health checks, and ovn-controller updates its status (online/offline).
  • Flow:
    1. build the mapping {member ip: "<logical_port>:<src_ip>"} (IPv6 bracketed) and find the NB Load_Balancer rows whose ip_port_mappings contain it with the same protocol (ovn_octavia_provider/helper.py:4208-4238).
    2. queue hm_update_event with ip, port, status and a delete flag (ovn_octavia_provider/helper.py:4240-4264).
    3. for each LB row, find the member with that IP and port in the pools; set its status to OFFLINE (row deleted), ERROR (offline) or ONLINE; store it in neutron:member_status; recompute the status tree (ovn_octavia_provider/helper.py:4409-4461).
    4. merge the trees of all rows and send them on the status socket.
  • Effect on traffic: none from the provider: OVN itself removes the failed backend from the data path; the provider only reports statuses. vips is not recomputed on a health event (only OFFLINE members set by admin state or deletion are excluded from vips, ovn_octavia_provider/helper.py:1410-1417).
  • Cost: db_find_rows over Load_Balancer per event, then 1–3 get-socket calls per affected row.

4. Maintenance tasks (driver agent)

All three are @periodics.periodic(spacing=600, run_immediately=True) and raise NeverAgain to stop (ovn_octavia_provider/maintenance.py:72, ovn_octavia_provider/maintenance.py:128, ovn_octavia_provider/maintenance.py:229). The spacing is hard-coded, with no configuration option.

4.1 change_device_owner_lb_hm_ports

  • Purpose: move health-monitor ports from the generic network:distributed owner to ovn-lb-hm:distributed with device_id = <port name>, so that Neutron still treats them as localport but can tell them apart from metadata ports.
  • Data read: Neutron GET /ports?device_owner=network:distributed, filtered on names starting with ovn-lb-hm (ovn_octavia_provider/maintenance.py:84-90).
  • Flow: update each port; after the first one, check in NB that its LSP is still type=localport; if not, the running Neutron does not know the new owner yet: roll that port back and retry in 600 s (ovn_octavia_provider/maintenance.py:91-119). When the list is empty, stop for good (ovn_octavia_provider/maintenance.py:120-124).
  • Idempotency: safe to replay; several agents may update the same ports concurrently with the same values.

4.2 add_device_owner_lb_vip_ports

  • Purpose: protect VIP ports created before bug LP#2150682 was fixed, so that Nova cannot attach them to an instance (ovn_octavia_provider/maintenance.py:130-155).
  • Flow: for each NB Load_Balancer, for its neutron:vip_port_id and neutron:additional_vip_port_ids: read the port; skip ports already correct, user-provided ports (name not ovn-lb-vip-*), and ports with another device_id (warning); set device_owner=ovn-lb:vip, device_id=lb-<lb id> (ovn_octavia_provider/maintenance.py:159-222). Runs once.
  • Cost: one Neutron GET per VIP port of every OVN LB row (rows of the same LB repeat the same ports), once per agent start.

4.3 format_ip_port_mappings_ipv6

  • Purpose: fix ip_port_mappings written by older versions with unbracketed IPv6 (ovn_octavia_provider/maintenance.py:230-238).
  • Flow: for LBs with a non-empty mapping, stop at the first IPv4 or already-bracketed key; rewrite the whole map with [ip] keys and port:[src] values (ovn_octavia_provider/maintenance.py:241-262). Runs once. Marked for removal (ovn_octavia_provider/maintenance.py:228).
  • Limitation: the first key decides for the whole map (break), and a map mixing fixed and unfixed IPv6 entries is rewritten only up to the first fixed one [inferred].

5. Synchronisation (do_sync)

  • Triggers: octavia-ovn-db-sync-util [--debug] (setup.cfg:35-36, ovn_octavia_provider/cmd/octavia_ovn_db_sync_util.py:37-59) with Octavia's configuration; or neutron-ovn-db-sync-util --sync_plugin octavia_ovn_sync through the entry point neutron.ovn.db_sync (setup.cfg:44-45), which requires the ovn-sync mechanism driver, skips log mode and re-raises errors in repair mode (ovn_octavia_provider/ovn/db_sync.py:54-66, ovn_octavia_provider/ovn/db_sync.py:154-187).
  • Flow: see 01 § 7. It runs synchronously (no queue) and sends one status update per LB.
  • Limitations: the sync re-creates LB, listeners, pools, members and HMs, but when an LB is missing in OVN a status[MEMBERS] list is reset for each member (ovn_octavia_provider/driver.py:755-762), so only the last member of each pool is reported [inferred: the list is re-assigned inside the loop]; L7 objects are not synchronised (not supported).

Source: reverse-spec-ovn-octavia-provider/05-async-processing_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.