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.
| Process | Trigger | Type | Purpose | Reads | Writes / emits | Defined at |
|---|---|---|---|---|---|---|
| octavia-api (driver) and driver agent | request_handler | in-process queue (queue.Queue), consumed by one daemon thread per OvnProviderHelper | execute the queued create/update/delete requests in order | queue items, NB replica, Neutron, get socket | NB transactions, status socket | ovn_octavia_provider/helper.py:408-437 |
| driver agent | LogicalRouterPortEvent | OVSDB event, NB Logical_Router_Port, create + delete | attach/detach LBs when a router interface is added to/removed from a network | external_ids of the LRP, router and switch rows | queue lb_create_lrp_assoc / lb_delete_lrp_assoc | ovn_octavia_provider/event.py:25-42 |
| driver agent | LogicalSwitchPortUpdateEvent | OVSDB event, NB Logical_Switch_Port, update, filter: port name ovn-lb-vip-* and external_ids changed | follow floating-IP association on the VIP port | neutron:port_fip old/new | queue handle_vip_fip | ovn_octavia_provider/event.py:45-76 |
| driver agent | ServiceMonitorUpdateEvent | OVSDB event, SB Service_Monitor, update + delete | turn OVN health-check results into member/pool/listener/LB operating statuses | ip, port, src_ip, logical_port, protocol, status | queue hm_update_event → status socket | ovn_octavia_provider/event.py:79-96 |
| driver agent | change_device_owner_lb_hm_ports | periodic, every 600 s, runs at start, stops itself when done | migrate HM ports from network:distributed to ovn-lb-hm:distributed | Neutron ports, NB Logical_Switch_Port.type | Neutron PUT /ports | ovn_octavia_provider/maintenance.py:72-126 |
| driver agent | add_device_owner_lb_vip_ports | periodic, 600 s, runs at start, one run only | backfill device_owner=ovn-lb:vip, device_id=lb-<id> on legacy VIP ports | NB Load_Balancer, Neutron ports | Neutron PUT /ports | ovn_octavia_provider/maintenance.py:128-226 |
| driver agent | format_ip_port_mappings_ipv6 | periodic, 600 s, runs at start, one run only | bracket IPv6 addresses in ip_port_mappings | NB Load_Balancer.ip_port_mappings | NB transactions | ovn_octavia_provider/maintenance.py:228-266 |
| operator (CLI) / neutron-ovn-db-sync-util | do_sync | on demand | re-create or repair OVN LBs from the Octavia database | Octavia REST + get socket, NB, Neutron | NB, status socket | ovn_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) whenovnis 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 onexit_eventand 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.PeriodicWorkeron 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;puthas a 5 s timeout that can only fire on a full queue (ovn_octavia_provider/common/constants.py:136-137). - Loop:
getwith a 5 s timeout,continueon empty;exitends the thread; otherwise look up the handler bytype(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__enqueuesexit(ovn_octavia_provider/driver.py:54-55), so the thread ends after it drained the requests queued before. Its NB connection is stopped byatexitonly (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_Portrow 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:
- 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). - queue
lb_create_lrp_assocwithis_gw_portfromexternal_ids['neutron:is_ext_gw'](ovn_octavia_provider/helper.py:298-304) orlb_delete_lrp_assoc. - 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). - delete: remove from the router the LBs whose
ls_refscontain this switch; remove from the switch the router's other LBs (ovn_octavia_provider/helper.py:255-290).
- resolve the router (
- Concurrency and idempotency:
may_exist/if_existson 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_Portupdate where the new row'sneutron:port_namestarts withovn-lb-vip-andexternal_idsis 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 →disassociatewith the old FIP, elseassociatewith 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 onehandle_vip_fipper OVN row of the LB (ovn_octavia_provider/helper.py:341-384).handle_vip_fipstores or removesneutron:vip_fip/neutron:additional_vip_fips, creates or destroys the matchingLoad_Balancer_Health_Checkfor the FIP, and recomputesvipsso 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
vipsentry 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_Monitorrow updated or deleted (ovn_octavia_provider/event.py:81-86). ovn-northd creates one row perip_port_mappingsbackend of an LB with health checks, and ovn-controller updates itsstatus(online/offline). - Flow:
- build the mapping
{member ip: "<logical_port>:<src_ip>"}(IPv6 bracketed) and find the NBLoad_Balancerrows whoseip_port_mappingscontain it with the same protocol (ovn_octavia_provider/helper.py:4208-4238). - queue
hm_update_eventwithip,port,statusand a delete flag (ovn_octavia_provider/helper.py:4240-4264). - for each LB row, find the member with that IP and port in the pools; set its status to
OFFLINE(row deleted),ERROR(offline) orONLINE; store it inneutron:member_status; recompute the status tree (ovn_octavia_provider/helper.py:4409-4461). - merge the trees of all rows and send them on the status socket.
- build the mapping
- Effect on traffic: none from the provider: OVN itself removes the failed backend from the data path; the provider only reports statuses.
vipsis not recomputed on a health event (onlyOFFLINEmembers set by admin state or deletion are excluded fromvips,ovn_octavia_provider/helper.py:1410-1417). - Cost:
db_find_rowsoverLoad_Balancerper 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:distributedowner toovn-lb-hm:distributedwithdevice_id = <port name>, so that Neutron still treats them aslocalportbut can tell them apart from metadata ports. - Data read: Neutron
GET /ports?device_owner=network:distributed, filtered on names starting withovn-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 itsneutron:vip_port_idandneutron:additional_vip_port_ids: read the port; skip ports already correct, user-provided ports (name notovn-lb-vip-*), and ports with anotherdevice_id(warning); setdevice_owner=ovn-lb:vip,device_id=lb-<lb id>(ovn_octavia_provider/maintenance.py:159-222). Runs once. - Cost: one Neutron
GETper 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_mappingswritten 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 andport:[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; orneutron-ovn-db-sync-util --sync_plugin octavia_ovn_syncthrough the entry pointneutron.ovn.db_sync(setup.cfg:44-45), which requires theovn-syncmechanism driver, skipslogmode and re-raises errors inrepairmode (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.