Interactions — 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
The provider talks to four systems. It has no SQL database, no RabbitMQ and no cache: its state is the OVN Northbound database, and it learns Octavia's state through the driver-agent sockets.
| System | Protocol | Library | Direction | Sync/async | Configuration |
|---|---|---|---|---|---|
| OVN Northbound | OVSDB JSON-RPC (RFC 7047) over TCP, SSL or Unix socket | ovsdbapp + ovs IDL | read (in-memory replica) + write (transact) + monitor | transactions synchronous inside the helper thread | [ovn] ovn_nb_connection (ovn_octavia_provider/common/config.py:24-32) |
| OVN Southbound | OVSDB JSON-RPC | ovsdbapp | monitor only (Service_Monitor) | events | [ovn] ovn_sb_connection (ovn_octavia_provider/common/config.py:45-53) |
| Octavia driver agent | JSON over Unix stream sockets | octavia_lib.api.drivers.driver_lib | out: status updates; out: object reads | synchronous request/response | sockets /var/run/octavia/{status,get}.sock (octavia-lib:octavia_lib/api/drivers/driver_lib.py:26-28) |
| Neutron | REST (HTTPS) | openstacksdk network proxy, keystoneauth1 session | out | synchronous | [neutron] (+ fallback [service_auth]) (ovn_octavia_provider/common/clients.py:115-149) |
| Octavia API | REST | openstacksdk load-balancer proxy | out (sync tool only) | synchronous | [service_auth] (ovn_octavia_provider/common/clients.py:152-183) |
| Keystone | REST | keystoneauth1 | out (token) | synchronous | [neutron] / [service_auth] auth options (ovn_octavia_provider/common/config.py:144-163) |
References: octavia: is Octavia stable/2026.2 @ 2900ed67cac2, octavia-lib: is octavia-lib stable/2026.2 @ a46b0ff4fa95.
1. OVN Northbound database
1.1 Connections
| Who opens it | Purpose | Tables replicated | Lock | Where |
|---|---|---|---|---|
each OvnProviderDriver instance (one per Octavia API call, see 01 § 1.1) | read + transact for its helper thread; notifier=False (no events) | Logical_Switch, Load_Balancer, Load_Balancer_Health_Check, Logical_Router, Logical_Switch_Port, Logical_Router_Port, Gateway_Chassis, NAT, HA_Chassis_Group | none | ovn_octavia_provider/driver.py:52, ovn_octavia_provider/helper.py:63-66, ovn_octavia_provider/ovsdb/impl_idl_ovn.py:208-225 |
| driver agent, helper | read + transact for the agent's own helper thread | same | none | ovn_octavia_provider/agent.py:40 |
| driver agent, events | monitor for LogicalRouterPortEvent, LogicalSwitchPortUpdateEvent | same | OVSDB lock neutron_ovn_octavia_event_lock | ovn_octavia_provider/agent.py:28, ovn_octavia_provider/agent.py:46-51 |
| driver agent, maintenance | read + transact for the periodic tasks | same | none | ovn_octavia_provider/maintenance.py:66-70 |
octavia_ovn_sync Neutron plugin | reuses Neutron's own NB API object (_nb_idl override) **[inferred: the helper uses ovn_nbdb_api, not _nb_idl, so the attribute set is probably unused]** | — | — | ovn_octavia_provider/ovn/db_sync.py:85-87 |
Connection parameters:
ovn_nb_connectionis a list joined by commas, so several cluster members can be given (ovn_octavia_provider/common/config.py:193-194);- SSL key, certificate and CA are set process-wide through
ovs.stream.Stream.ssl_set_*(ovn_octavia_provider/ovsdb/ovsdb_monitor.py:89-118); NB and SB therefore cannot use different client certificates in the same process [inferred: the last call wins]; - the inactivity probe is
[ovn] ovsdb_probe_intervalms (default 60000) (ovn_octavia_provider/ovsdb/impl_idl_ovn.py:156-160,ovn_octavia_provider/common/config.py:74-81); - every transaction has
[ovn] ovsdb_connection_timeouts (default 180) (ovn_octavia_provider/helper.py:64,ovn_octavia_provider/common/config.py:66-69); - fetching the schema at start-up is retried with exponential back-off capped at
[ovn] ovsdb_retry_max_interval(default 180 s) (ovn_octavia_provider/ovsdb/impl_idl_ovn.py:227-229,ovn_octavia_provider/common/utils.py:76-83).
The event lock is the OVSDB lock method: only the agent holding it handles events, and the others drop notifications while is_lock_contended (ovn_octavia_provider/ovsdb/ovsdb_monitor.py:68-76). Several driver agents can therefore run for high availability, but only one processes OVSDB events at a time. The lock does not cover the request queues, the periodic tasks or the API-side helpers.
1.2 Tables and columns written
| Table | Columns written | Written by | Read by |
|---|---|---|---|
Load_Balancer | name, protocol, external_ids (see 01 § 1.2), vips, selection_fields, options:affinity_timeout, health_check, ip_port_mappings | every handler | every handler, maintenance, Neutron (port-forwarding LBs share the table and are skipped through external_ids['neutron:device_owner'], ovn_octavia_provider/helper.py:921-924) |
Load_Balancer_Health_Check | vip, options (interval, timeout, success_count, failure_count), external_ids (octavia:healthmonitor, octavia:pool_id, octavia:vip) | hm_create, hm_sync, hm_update, hm_delete, handle_vip_fip, listener_create | same |
Logical_Switch | load_balancer (ls_lb_add / ls_lb_del) | LB/member create and delete, LRP events | — |
Logical_Router | load_balancer (lr_lb_add / lr_lb_del) | same | — |
NAT | external_mac, logical_port cleared | handle_member_dvr (ovn_octavia_provider/helper.py:3382-3385) | handle_member_dvr (lookup by external_ids['neutron:fip_port_id'], ovn_octavia_provider/helper.py:3361-3364) |
Logical_Switch_Port | none | — | member LSP lookup by address (ovn_octavia_provider/helper.py:3424-3442), VIP LSP FIP (ovn_octavia_provider/driver.py:868-872), maintenance (ovn_octavia_provider/maintenance.py:103-105) |
Logical_Router_Port, Gateway_Chassis, HA_Chassis_Group | none | — | finding the switches behind a router, skipping gateway ports (ovn_octavia_provider/helper.py:1245-1258) |
The provider never writes Logical_Switch_Port or NAT rows it does not own except the two NAT columns above; Neutron's OVN mechanism driver owns them.
1.3 Transactions
Handlers accumulate ovsdbapp commands and commit them in one transaction (_execute_commands, ovn_octavia_provider/helper.py:956-960). Exceptions:
_get_or_create_ovn_lbsets the protocol of an empty row immediately (ovn_octavia_provider/helper.py:876-878);neutron:member_statusupdates are separate one-command transactions (ovn_octavia_provider/helper.py:2661-2672);ip_port_mappingschanges are separate one-command transactions (ovn_octavia_provider/helper.py:3802-3808);- LB ↔ router associations fall back to one transaction per command after
RowNotFound(ovn_octavia_provider/helper.py:1218-1235).
There is no optimistic locking: reads come from the local IDL replica and writes use db_set on map keys, so two writers updating the same external_ids key (two Octavia API processes, or API and agent) can lose an update [inferred]. ovsdb-server serialises transactions, and the vips column is rewritten from the writer's view of external_ids (db_clear + db_set, ovn_octavia_provider/helper.py:1444-1447).
1.4 Sample frame: listener creation
listener_create for listener L on port 80 with default pool P, on an LB row that has one member 10.0.0.10:8080, produces one transact (shape reconstructed from the ovsdbapp commands; the exact mutate/update encoding is chosen by the ovs IDL [inferred]):
{"id": 42, "method": "transact", "params": ["OVN_Northbound",
{"op": "mutate", "table": "Load_Balancer", "where": [["_uuid", "==", ["uuid", "7d3e…"]]],
"mutations": [["external_ids", "insert", ["map", [["listener_L", "80:pool_P"]]]]]},
{"op": "update", "table": "Load_Balancer", "where": [["_uuid", "==", ["uuid", "7d3e…"]]],
"row": {"protocol": ["set", ["tcp"]]}},
{"op": "update", "table": "Load_Balancer", "where": [["_uuid", "==", ["uuid", "7d3e…"]]],
"row": {"vips": ["map", []]}},
{"op": "update", "table": "Load_Balancer", "where": [["_uuid", "==", ["uuid", "7d3e…"]]],
"row": {"vips": ["map", [["172.24.4.5:80", "10.0.0.10:8080"]]]}}
]}
Resulting row (the contract a rewrite must produce):
{"name": "a5f1…lb-id", "protocol": ["tcp"],
"external_ids": {"neutron:vip": "172.24.4.5", "neutron:vip_port_id": "9c0b…", "enabled": "True",
"listener_L": "80:pool_P", "pool_P": "member_M_10.0.0.10:8080_<subnet>",
"ls_refs": "{\"neutron-<net>\": 2}", "lr_ref": "neutron-<router>",
"neutron:member_status": "{\"M\": \"NO_MONITOR\"}"},
"vips": {"172.24.4.5:80": "10.0.0.10:8080"},
"selection_fields": ["ip_dst", "ip_src", "tp_dst", "tp_src"]}
1.5 Monitored events
See 05. NB: Logical_Router_Port create/delete, Logical_Switch_Port update. SB: Service_Monitor update/delete (ovn_octavia_provider/agent.py:41-56, ovn_octavia_provider/ovsdb/impl_idl_ovn.py:232-234).
2. Octavia driver-agent sockets
The driver agent is an Octavia process (octavia-driver-agent) that serves three Unix sockets and runs the provider agents (octavia:octavia/api/drivers/driver_agent/driver_listener.py:134-185). The provider uses two of them; it never sends statistics.
2.1 Framing
Each call opens a new AF_UNIX/SOCK_STREAM connection (send timeout 5 s), sends <decimal length>\n then the JSON payload, and reads the reply with the same framing, giving up after 30 s (octavia-lib:octavia_lib/api/drivers/driver_lib.py:28-108).
2.2 Status socket — update_loadbalancer_status
Sent after every handler that returns a status (ovn_octavia_provider/helper.py:425-427, ovn_octavia_provider/helper.py:448-451). The :D suffix of disabled keys is removed from all values first (ovn_octavia_provider/helper.py:100-107).
187\n{"loadbalancers": [{"id": "a5f1…", "provisioning_status": "ACTIVE"}],
"listeners": [{"id": "L", "provisioning_status": "ACTIVE", "operating_status": "ONLINE"}],
"pools": [{"id": "P", "provisioning_status": "ACTIVE"}]}
Reply: {"status_code": 200}, or {"status_code": 500, "fault_string": …, "status_object": …, "status_object_id": …} (octavia:octavia/api/drivers/driver_agent/driver_updater.py:180-188, octavia-lib:octavia_lib/common/constants.py:16-24). The agent processes members, health monitors, pools, L7 rules and policies, listeners, then load balancers, and deletes the database record of any non-LB object reported DELETED (octavia:octavia/api/drivers/driver_agent/driver_updater.py:142-179, octavia:octavia/api/drivers/driver_agent/driver_updater.py:99-113). Keys: loadbalancers, listeners, pools, members, healthmonitors; statuses: provisioning ACTIVE, ERROR, DELETED; operating ONLINE, OFFLINE, ERROR, DEGRADED, NO_MONITOR.
Retries: 15 attempts with exponential back-off up to 75 s (ovn_octavia_provider/helper.py:448-451).
2.3 Get socket — object reads
Request {"object": "loadbalancers" | "pools" | "listeners", "id": "<uuid>"}, reply the provider data model as a dict (with render_unsets), or null when not found (octavia-lib:octavia_lib/api/drivers/driver_lib.py:163-185, octavia:octavia/api/drivers/driver_agent/driver_get.py:23-50).
| Call | Why | Where |
|---|---|---|
get_pool, get_loadbalancer | find the VIP subnet of a member without subnet_id (synchronous, in the Octavia API process) | ovn_octavia_provider/helper.py:939-954 |
get_loadbalancer | VIP port and additional VIPs of an LB missing from OVN (delete) or to copy into a new protocol row | ovn_octavia_provider/helper.py:2048-2064 |
get_loadbalancer, get_pool, get_listener | admin_state_up of every pool and listener when recomputing operating statuses — one to three socket round-trips per status computation | ovn_octavia_provider/helper.py:4325-4376 |
get_loadbalancer | full graph for the sync | ovn_octavia_provider/driver.py:916-918 |
The driver-lib object is created in each OvnProviderHelper, and the constructor fails when the sockets do not exist (octavia-lib:octavia_lib/api/drivers/driver_lib.py:40-55): the Octavia API host must share /var/run/octavia with a running driver agent.
3. Neutron REST calls
Client: openstack.connection.Connection(session=…).network, created once per process (singleton, ovn_octavia_provider/common/clients.py:105-138). Region [neutron] region_name, interface [neutron] valid_interfaces[0], endpoint [neutron] endpoint_override (with insecure/cafile for HTTPS). Missing auth options in [neutron] are copied from [service_auth] (ovn_octavia_provider/common/clients.py:49-102); deprecated endpoint, endpoint_type, ca_certificates_file are mapped to their keystoneauth equivalents (ovn_octavia_provider/common/config.py:109-131).
| Method + path | When | Retry | Errors handled | Defined at |
|---|---|---|---|---|
POST /v2.0/ports | VIP and additional VIP ports (create_vip_port) | none | 409 → reuse same-name port; other → clean-up + DriverError | ovn_octavia_provider/helper.py:3068-3104 |
POST /v2.0/ports | HM source port ovn-lb-hm-<subnet> (port security off, device_owner ovn-lb-hm:distributed) | none | any HttpException → clean-up, None | ovn_octavia_provider/helper.py:123-139 |
GET /v2.0/ports/{id} | VIP port (create, delete, FIP events), user-provided VIP port, maintenance | none | 404 handled on delete | ovn_octavia_provider/helper.py:1474, ovn_octavia_provider/helper.py:371, ovn_octavia_provider/helper.py:3124, ovn_octavia_provider/helper.py:1850, ovn_octavia_provider/maintenance.py:182 |
GET /v2.0/ports?network_id= | find the VIP port by address in lb_create | 10 s | — | ovn_octavia_provider/helper.py:1480-1490 |
GET /v2.0/ports?name=ovn-lb-hm-<subnet> | HM port clean-up | 10 s | — | ovn_octavia_provider/helper.py:169-171 |
find_port(name_or_id, network_id) (GET /v2.0/ports/{name} then GET /v2.0/ports?name=…) **[inferred: openstacksdk find_* behaviour]** | HM port and reused VIP port | 10 s | — | ovn_octavia_provider/helper.py:195-198, ovn_octavia_provider/helper.py:3093-3096 |
GET /v2.0/ports?device_owner=network:distributed | maintenance (HM port owner migration) | none | — | ovn_octavia_provider/maintenance.py:85-86 |
PUT /v2.0/ports/{id} (device_owner, device_id) | maintenance tasks | none | 404 skipped (VIP task) | ovn_octavia_provider/maintenance.py:93-95, ovn_octavia_provider/maintenance.py:219-222 |
DELETE /v2.0/ports/{id} | VIP ports on LB delete or create failure, unused HM ports | 15 attempts, ≤ 75 s back-off | 404 logged | ovn_octavia_provider/helper.py:3169-3176 |
GET /v2.0/subnets/{id} | VIP subnet CIDR, member subnet → network, router lookup | none | 404 logged | ovn_octavia_provider/helper.py:1478, ovn_octavia_provider/helper.py:795, ovn_octavia_provider/helper.py:947, ovn_octavia_provider/helper.py:1002, ovn_octavia_provider/helper.py:2784, ovn_octavia_provider/helper.py:3341, ovn_octavia_provider/helper.py:3427 |
GET /v2.0/networks/{id} | sync: is the VIP on a provider network | none | — | ovn_octavia_provider/helper.py:594 |
GET /v2.0/floatingips?port_id= | sync: FIP of the VIP port | none | HttpException logged | ovn_octavia_provider/helper.py:3444-3451 |
GET + PUT /v2.0/floatingips/{id} (description unchanged) | member deleted while LB and member have FIPs: forces Neutron to recompute the member's NAT (re-distribute it under DVR) | none | 404 logged | ovn_octavia_provider/helper.py:3398-3410 |
All calls use the Octavia service user. Calls made inside driver methods (VIP ports, subnet lookup for members) run synchronously in the Octavia API request; the others run in helper threads.
4. Octavia REST (sync tool only)
GET /v2/lbaas/loadbalancers?provider=ovn (paginated by openstacksdk), retried for 10 s on HttpException (ovn_octavia_provider/helper.py:485-486, ovn_octavia_provider/driver.py:911-913), with the [service_auth] credentials (ovn_octavia_provider/common/clients.py:152-172).
5. Other
- Configuration files: the Neutron sync plugin reads Octavia's configuration from
/etc/octavia/octavia.conf,~/octavia.confor./octavia.conf(first found) and temporarily replacescfg.CONFwhile calling the driver (ovn_octavia_provider/ovn/db_sync.py:89-152). - No subprocess, no privsep, no files written.
Source: reverse-spec-ovn-octavia-provider/03-interactions_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.