Asynchronous processing — ovn-bgp-agent — branch stable/2026.2
Branch
stable/2026.2@f770768b41dc(7.0.0-2-gf770768) — repository https://opendev.org/openstack/ovn-bgp-agent — analysed on 2026-10-28 External references:ovsdbapp:= ovsdbappstable/2026.2@0fb5a1b9a675;neutron-lib:= neutron-libstable/2026.2@1ee72052251a.
ovn-bgp-agent has no API. Everything it does is triggered by one of three things:
- OVSDB events — row create/update/delete notifications received from the OVN Southbound (SB) or Northbound (NB) database through an ovsdbapp IDL (§ 2 to § 5);
- Two periodic loops — a full resynchronisation (
sync, everyreconcile_intervals, default 300) and an FRR base-configuration check (frr_sync, everyfrr_reconcile_intervals, default 15) (§ 1); - Reconnection — a re-created
Chassis/Chassis_Privaterow after an OVSDB reconnection forces a fullsync(§ 1.3).
The effects are always local to the node: kernel addresses, routes, rules and neighbours (netlink through privsep), sysctl flags, OVS flows and ports (ovs-vsctl/ovs-ofctl/ovs-appctl through privsep), FRR configuration (vtysh through privsep) and, with exposing_method = ovn, a node-local OVN NB database. The agent never writes to the OpenStack OVN databases; it only reads them. See 03-interactions.
0. Summary of triggers
Which events are registered depends on the driver ([DEFAULT] driver) and on configuration.
| Driver | Trigger | Type | Purpose | Reads | Writes / emits | Defined at |
|---|---|---|---|---|---|---|
| all | BGPAgent.sync | timer, reconcile_interval (300 s) | full reconciliation of the node state with OVN | OVS, OVN SB or NB, kernel | kernel, OVS, FRR | ovn_bgp_agent/agent.py:49 |
| all | BGPAgent.frr_sync | timer, frr_reconcile_interval (15 s) | re-apply the FRR VRF-leak configuration (FRR restarts) | FRR (show ip bgp summary json if no router-id) | vtysh -f, VRF and dummy NIC | ovn_bgp_agent/agent.py:57 |
| SB drivers | ChassisCreateEvent / ChassisPrivateCreateEvent | OVSDB SB Chassis / Chassis_Private CREATE, name == <own chassis> | full sync after a reconnection (not on the first notification) | — | as sync | ovn_bgp_agent/drivers/openstack/watchers/base_watcher.py:78 |
ovn_bgp_driver | PortBindingChassisCreatedEvent | SB Port_Binding UPDATE | a VM/virtual/cr-lrp port gets bound here or goes up → expose its IPs | row | IP on bgp_nic, ip rule/route, ARP/NDP proxy | ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:29 |
ovn_bgp_driver | PortBindingChassisDeletedEvent | SB Port_Binding UPDATE/DELETE | port leaves this chassis, goes down or is deleted → withdraw | row | removes the above | ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:62 |
ovn_bgp_driver | FIPSetEvent / FIPUnsetEvent | SB Port_Binding UPDATE of nat_addresses on a patch port | FIP associated/disassociated on a router whose gateway is here | row, old | expose/withdraw FIP | ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:99, ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:137 |
ovn_bgp_driver | OVNLBMemberCreateEvent / OVNLBMemberDeleteEvent | SB Load_Balancer CREATE/DELETE | OVN (Octavia) LB with VIP on a provider network behind a local gateway | SB Port_Binding | expose/withdraw VIP | ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:382, ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:450 |
ovn_bgp_driver | LocalnetCreateDeleteEvent | SB Port_Binding CREATE/DELETE, type == localnet | provider network created/deleted → full sync | — | as sync | ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:472 |
ovn_bgp_driver (+ tenant exposure) | SubnetRouterAttachedEvent / SubnetRouterDetachedEvent | SB Port_Binding CREATE/DELETE of an lrp-* patch port | tenant subnet attached/detached to a router whose gateway is here | SB | ip rule + route via cr-lrp; tenant VM IPs | ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:176, ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:235 |
ovn_bgp_driver (+ tenant exposure) | TenantPortCreatedEvent / TenantPortDeletedEvent | SB Port_Binding UPDATE/DELETE (chassis set/unset) | tenant VM bound/unbound anywhere, on a subnet exposed here | SB | IP on bgp_nic | ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:260, ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:299 |
ovn_bgp_driver (+ tenant exposure) | OVNLBVIPPortEvent | SB Port_Binding CREATE/DELETE of an unbound, MAC-less port | OVN LB VIP on a tenant network | row | IP on bgp_nic | ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:345 |
nb_ovn_bgp_driver | LogicalSwitchPortProviderCreateEvent / …DeleteEvent | NB Logical_Switch_Port UPDATE/DELETE | VM on a provider network bound/up here, or leaving | row | expose/withdraw IP | ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:33, ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:96 |
nb_ovn_bgp_driver (distributed FIPs) | LogicalSwitchPortFIPCreateEvent / …DeleteEvent, NATMACAddedEvent | NB Logical_Switch_Port UPDATE/DELETE; NB NAT UPDATE | FIP of a VM hosted here (distributed NAT) | NB NAT | expose/withdraw FIP | ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:159, ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:260, ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:882 |
nb_ovn_bgp_driver (centralized FIPs) | ExposeFIPOnCRLRP / WithdrawFIPOnCRLRP, CrLrpChassisChangeExposeEvent / …WithdrawEvent | NB NAT CREATE/DELETE; NB Logical_Router_Port UPDATE of status:hosting-chassis | FIPs handled on the node hosting the router gateway | NB NAT, Logical_Router_Port | expose/withdraw FIP | ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:950, ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:995 |
nb_ovn_bgp_driver | ChassisRedirectCreateEvent / …DeleteEvent | NB Logical_Router_Port UPDATE/DELETE, status:hosting-chassis | router gateway (cr-lrp) moves to/away from this node | row | expose/withdraw gateway IP, its subnets and LBs | ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:401, ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:435 |
nb_ovn_bgp_driver (+ tenant exposure) | LogicalSwitchPortSubnetAttachEvent / …DetachEvent | NB Logical_Switch_Port UPDATE/DELETE of type=router, device_owner=network:router_interface | subnet attached to/detached from a local gateway router | NB | ip rule/route, tenant IPs | ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:469, ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:521 |
nb_ovn_bgp_driver (+ tenant exposure, host method) | LogicalSwitchPortTenantCreateEvent / …DeleteEvent | NB Logical_Switch_Port UPDATE/DELETE | tenant VM up/down on an exposed subnet | — | IP on bgp_nic or VRF route | ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:610, ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:663 |
nb_ovn_bgp_driver | OVNLBCreateEvent / OVNLBDeleteEvent | NB Load_Balancer UPDATE/DELETE | LB VIP or VIP-FIP added/removed, router external gateway set/unset | NB Logical_Switch_Port, NAT | expose/withdraw VIP/FIP | ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:712, ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:769 |
nb_ovn_bgp_driver | OVNPFCreateEvent / OVNPFDeleteEvent | NB Load_Balancer CREATE/DELETE, name pf-floatingip-* | port-forwarding FIP | — | expose/withdraw FIP | ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:864, ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:873 |
nb_ovn_bgp_driver | LocalnetCreateDeleteEvent (non-vrf) / LogicalSwitchUpdateEvent (vrf) | NB Logical_Switch_Port CREATE/DELETE type=localnet; NB Logical_Switch UPDATE/DELETE of neutron_bgpvpn:type/vni | provider network or EVPN settings changed → full sync | — | as sync | ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:385, ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:348 |
nb_ovn_bgp_driver | DistributedFlagChangedEvent | NB NB_Global UPDATE of external_ids:neutron:fip-distributed | switch distributed ↔ centralized FIP handling | row | re-register events, sync + frr_sync | ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:1049 |
ovn_evpn_driver | PortBindingChassisCreatedEvent / …DeletedEvent (EVPN) | SB Port_Binding UPDATE/DELETE of type=chassisredirect | router gateway with EVPN annotations bound here → build the VRF/VXLAN plumbing | SB | VRF, bridge, VXLAN, veth/VLAN, FRR VRF, OVS flows | ovn_bgp_agent/drivers/openstack/watchers/evpn_watcher.py:28, ovn_bgp_agent/drivers/openstack/watchers/evpn_watcher.py:53 |
ovn_evpn_driver | SubnetRouterAttachedEvent / …DetachedEvent (EVPN) | SB Port_Binding CREATE/UPDATE/DELETE of a patch port carrying neutron_bgpvpn:vni/as | subnet or gateway annotated for EVPN | SB | VRF routes, OVS flows | ovn_bgp_agent/drivers/openstack/watchers/evpn_watcher.py:81, ovn_bgp_agent/drivers/openstack/watchers/evpn_watcher.py:116 |
ovn_evpn_driver | TenantPortCreatedEvent / …DeletedEvent, LocalnetCreateDeleteEvent (EVPN) | SB Port_Binding | tenant VM IP on lo-<vni>; sync on localnet changes | SB | IP on dummy | ovn_bgp_agent/drivers/openstack/watchers/evpn_watcher.py:151, ovn_bgp_agent/drivers/openstack/watchers/evpn_watcher.py:204 |
ovn_stretched_l2_bgp_driver | SubnetRouterAttachedEvent / SubnetRouterUpdateEvent / SubnetRouterDetachedEvent, PortBindingChassisCreatedEvent / …DeletedEvent | SB Port_Binding (bgp_watcher classes) | route tenant subnets via the gateway IP in the BGP VRF table | SB | routes in table bgp_vrf_table_id | ovn_bgp_agent/drivers/openstack/ovn_stretched_l2_bgp_driver.py:103 |
Totals: 2 timers; 55 event classes in ovn_bgp_agent/drivers/openstack/watchers/ (11 base or abstract, 44 concrete), of which a given driver registers between 5 (ovn_stretched_l2_bgp_driver) and 18 (nb_ovn_bgp_driver).
1. Process-wide mechanisms
1.1 Start-up and the two loops (ovn-bgp-agent)
- Entry point: console script
ovn-bgp-agent = ovn_bgp_agent.cmd.agent:start(setup.cfg), which callsovn_bgp_agent/agent.py:75: register options (ovn_bgp_agent/config.py:236), parse the command line withproject='bgp-agent'(ovn_bgp_agent/config.py:243), set up logging, initialise privsep with[agent] root_helper(ovn_bgp_agent/config.py:258), thenoslo_service.service.launch(CONF, BGPAgent()). - Driver choice:
BGPAgent.__init__loads[DEFAULT] driverfrom the stevedore namespaceovn_bgp_agent.drivers(ovn_bgp_agent/drivers/driver_api.py:25,ovn_bgp_agent/agent.py:35). Four drivers exist:ovn_bgp_driver(default),nb_ovn_bgp_driver,ovn_evpn_driver,ovn_stretched_l2_bgp_driver(ovn_bgp_agent/config.py:64). - Start:
driver.start()(ovn_bgp_agent/agent.py:41) connects to the local OVS database, reads the chassis identity, applies the base FRR/VRF configuration and opens the OVN IDL with its event set (per driver, § 2 to § 5). **No initialsyncis called explicitly**: the first full sync runs when the first timer tick fires, whichFixedIntervalLoopingCall.start(interval=…)does immediately (noinitial_delay) [inferred from oslo.service defaults]. - Loops: two
FixedIntervalLoopingCall(ovn_bgp_agent/agent.py:44-47):synceveryreconcile_intervalseconds (default 300,ovn_bgp_agent/config.py:26);frr_synceveryfrr_reconcile_intervalseconds (default 15,ovn_bgp_agent/config.py:29). Both wrappers catch every exception, logUnexpected exception while running the sync: %s/…the frr sync: %sand keep the loop alive (ovn_bgp_agent/agent.py:52-64).
1.2 Concurrency model
- One notification thread per IDL: ovsdbapp's
RowEventHandlerevaluatesmatch_fnin the IDL connection thread under a lock, queues the matches and runs them one by one in a singlenotify_loopthread (ovsdbapp:ovsdbapp/event.py:153,ovsdbapp:ovsdbapp/event.py:186). Events of one IDL are therefore processed sequentially, in arrival order. - Driver lock: every public driver method that touches the node (
expose_*,withdraw_*,sync,frr_sync) is decorated@lockutils.synchronized('bgp')(SB and stretched drivers,ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:128),'nbbgp'(NB driver,ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:286) or'evpn'(ovn_bgp_agent/drivers/openstack/ovn_evpn_driver.py:100). These are in-process semaphores (noexternal=True): they serialise the timer threads with the event thread inside one agent, nothing more. Running two agents on one node is not protected. - **
_SYNC_STATE_LOCK**: each watcher module declares aReaderWriterLockand every_runtakes its read lock (ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:26,ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:30). No code ever takes the write lock, so this lock never excludes anything; the effective serialisation is the driver lock. - Events that re-enter the lock:
LocalnetCreateDeleteEvent,LogicalSwitchUpdateEvent,ChassisCreateEventBaseandDistributedFlagChangedEventcallagent.sync(), which takes the driver lock; a longsynctherefore blocks all other events of that IDL. - Matching runs on cached state: many
match_fnread driver caches (ovn_local_cr_lrps,ovn_local_lrps,_exposed_ips,provider_ovn_lbs) without the driver lock. A match can be evaluated against a cache that a runningsyncis rebuilding (syncstarts by clearing all caches,ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:137-143,ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:293). Events missed this way are repaired by the nextsync(300 s by default) [inferred].
1.3 Reconnection: ChassisCreateEvent / ChassisPrivateCreateEvent
- Trigger: SB
ChassisorChassis_PrivateCREATE with conditionname == <own chassis>(ovn_bgp_agent/drivers/openstack/watchers/base_watcher.py:78-101). The SB IDL is also restricted server-side to this chassis's row (ovn_bgp_agent/drivers/openstack/utils/ovn.py:111-114). - Purpose: after an OVSDB reconnection the IDL replays all rows as CREATE; the agent uses its own chassis row as the signal and runs a full
sync. - Flow: the first notification (initial dump) only clears
first_time; any later one logsConnection to OVSDB established, doing a full syncand callsagent.sync()(ovn_bgp_agent/drivers/openstack/watchers/base_watcher.py:89-94). - Registered by:
ovn_bgp_driverandovn_evpn_driver. Not byovn_stretched_l2_bgp_drivernornb_ovn_bgp_driver: the NB driver has no reconnection hook and relies on the 300 s timer.
1.4 Error handling common to all events
Event.runwraps_runand logsUnexpected exception while running the event action(ovn_bgp_agent/drivers/openstack/watchers/base_watcher.py:31-36).- Exceptions raised in
match_fnare caught by ovsdbapp, logged asEvent not matched due to this exception:and treated as "no match" (ovsdbapp:ovsdbapp/event.py:153-158). - Five NB events override
runinstead of_run(NATMACAddedEvent,ExposeFIPOnCRLRP,WithdrawFIPOnCRLRP,CrLrpChassisChangeExposeEvent,CrLrpChassisChangeWithdrawEvent,DistributedFlagChangedEvent:ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:940,ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:958,ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:971,ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:1014,ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:1042,ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:1081). Their exceptions bypass the agent's handler and are logged by ovsdbapp asUnexpected exception in notify_loop(ovsdbapp:ovsdbapp/event.py:205-208); the event is lost, the loop survives. - No event is retried. Missed or failed work is recovered only by the next
sync.
2. ovn_bgp_driver (SB driver, default)
2.1 Start (OVNBGPDriver.start)
ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:77:
- OVS IDL on
ovsdb_connection; chassis =Open_vSwitch.external_ids:system-id; SB remote =[ovn] ovn_sb_connectionorexternal_ids:ovn-remote(ovn_bgp_agent/drivers/openstack/utils/ovs.py:290,ovn_bgp_agent/drivers/openstack/utils/ovs.py:307). ensure_base_bgp_configuration()(§ 6.1).- If
clear_vrf_routes_on_startup, delete every non-local, non-FRR route of tablebgp_vrf_table_id(ovn_bgp_agent/utils/linux_net.py:607). - SB IDL on tables
Port_Binding, Chassis, Datapath_Binding, Load_Balancer, Chassis_Private, Logical_DP_Group(ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:42) with the event set of_get_events(ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:109): 9 base events, plus 5 whenexpose_tenant_networksorexpose_ipv6_gua_tenant_networksis set.
2.2 sync (SB)
ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:134. Purpose: make the node state match OVN SB, adding what is missing and removing leftovers.
- Reset all caches (routing tables, bridge mappings, local cr-lrps/lrps, LBs, flows).
- For each
ovn-bridge-mappingsentrynetwork:bridgeof the local OVS (ovn_bgp_agent/drivers/openstack/utils/ovs.py:317), with a 1-based index:- ensure the bridge exists as a kernel link and is up (
ovn_bgp_agent/utils/linux_net.py:109); - ensure a routing table named after the bridge in
/etc/iproute2/rt_tables(random free number in 1–252,ovn_bgp_agent/utils/linux_net.py:232) with IPv4 and IPv6 default routes out of the bridge (ovn_bgp_agent/utils/linux_net.py:281); collect the routes of that table that are not the expected defaults; - for each VLAN tag of the localnet ports of that network (SB
Port_Binding type=localnet,options:network_name): ensure a VLAN device<bridge>.<tag>and its ARP/NDP addresses (ovn_bgp_agent/utils/linux_net.py:420,ovn_bgp_agent/utils/linux_net.py:139); - put
169.254.<idx>.<idx>andfd53:d91e:400:7f17::<idx>:<idx>on the bridge and enableproxy_arp/proxy_ndp(ovn_bgp_agent/utils/linux_net.py:139-170); - read the bridge MAC and the OpenFlow ports of its
patch-provnet-*ports, ensure the cookie-999 "MAC tweak" flows and delete other cookie-999 flows (ovn_bgp_agent/drivers/openstack/utils/ovs.py:94,ovn_bgp_agent/drivers/openstack/utils/ovs.py:123).
- ensure the bridge exists as a kernel link and is up (
- Read the /32 and /128 addresses on
bgp_nicand the ip rules pointing to the bridge tables (ovn_bgp_agent/utils/linux_net.py:472,ovn_bgp_agent/utils/linux_net.py:539). - Ports bound here: all SB
Port_Bindingrows whosechassisis this chassis (full table scan,ovn_bgp_agent/drivers/openstack/utils/ovn.py:542) →_ensure_port_exposed(ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:270) →_expose_ip(§ 2.3). - Gateways here: SB
chassisredirectports bound here → FIPs found in thenat_addressesof the matching patch port whose VM is on this chassis (ovn_bgp_agent/drivers/openstack/utils/ovn.py:559). - For every local cr-lrp: process each
lrp-*patch port of its router (tenant subnets, § 2.5), then the OVN LBs whose VIP is on the provider network and whose members sit behind this router (ovn_bgp_agent/drivers/openstack/utils/ovn.py:705). - Cleanup: delete leftover IPs on
bgp_nic, leftover ip rules, leftover routes of the bridge tables (ovn_bgp_agent/utils/linux_net.py:567) and VLAN devices whose tag no longer exists in OVN (ovn_bgp_agent/drivers/openstack/utils/wire.py:598). - Cost: several full scans of SB
Port_Bindingper run (bound ports, cr-lrps, patch ports for every FIP lookup throughget_fip_associated,ovn_bgp_agent/drivers/openstack/utils/ovn.py:517), one netlink dump per check, oneovs-ofctl dump-flowsper bridge. On a large SB this is the main CPU cost of the agent [inferred]. - Idempotency: every write uses "replace"/"may-exist" semantics or ignores
EEXIST(§ 6.3); a replayedsyncconverges.
2.3 Port bound here: PortBindingChassisCreatedEvent / PortBindingChassisDeletedEvent
- Trigger: SB
Port_BindingUPDATE (create event); UPDATE or DELETE (delete event) (ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:29-96). - Match (expose):
mac[0]contains an IP ("<mac> <ip>…"),up[0]is true,chassisis this chassis, and either the chassis just changed/was empty orupjust went from false to true. - Match (withdraw):
mac[0]contains an IP and either the row is deleted while bound here, or the old chassis was this one and the new is different/empty, orupwent true → false while still bound here. - Action: only for
typein"", chassisredirect, virtual(ovn_bgp_agent/constants.py:20):expose_ip(ips, row)/withdraw_ip(ips, row)whereips = mac[0].split(' ')[1:]. - **
_expose_ip** (ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:548), by port kind:- VM or virtual port on a provider network (the datapath has a
localnetport,ovn_bgp_agent/drivers/openstack/utils/ovn.py:494): find the bridge/VLAN of the network, wire the IPs (ip ruleto <ip> lookup <bridge table>, route<ip> dev <bridge>[.<vlan>]in that table; for virtual ports also an NDP proxy for the IPv6neutron:cidrs), then add the IPs as /32 or /128 onbgp_nic(§ 6.2). Returns the exposed IPs (ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:562-586). - VM on a tenant network with a FIP: find the FIP by scanning patch ports'
nat_addressesfor the port name (ovn_bgp_agent/drivers/openstack/utils/ovn.py:517); expose the FIP on the provider network of the patch port (ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:588-606). IPv4 only (comment atovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:589). - cr-lrp (router gateway): provider datapath = datapath of the patch port named after the cr-lrp; record the gateway in
ovn_local_cr_lrps; wire the gateway IPs with a permanent ARP/NDP neighbour entry for the router MAC and an NDP proxy; announce them; then expose the router's FIPs, attached tenant subnets and provider LBs (ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:624-657,ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:898).
- VM or virtual port on a provider network (the datapath has a
- **
withdraw_ip** (ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:660) mirrors this. Notable rules:- if the datapath no longer exists, the port is assumed to be on a provider network to avoid leaking an announcement (
ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:681-693); - for a virtual port, the NDP proxy for the provider CIDR is removed only if no other virtual port of that datapath is bound here and no local cr-lrp uses that provider (
ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:700-714); - for a cr-lrp, the IPv6 NDP proxy is removed only if it is the last local cr-lrp on that provider, then its subnets and LBs are withdrawn and the cache entry dropped (
ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:936-979).
- if the datapath no longer exists, the port is assumed to be on a provider network to avoid leaking an announcement (
- Ordering guarantee: on withdraw, the IP is removed from
bgp_nic(BGP withdraw) before the rule/route is removed (ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:408-409); on expose, the IP is added after the wiring succeeded (ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:341-346). This avoids black-holing traffic.
2.4 FIP association on a gateway: FIPSetEvent / FIPUnsetEvent
- Trigger: SB
Port_BindingUPDATE wherechassisis empty,nat_addresseschanged and the port is not anlrp-*port; acts only ontype=patch(ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:99-173). - Data: each
nat_addressesentry has the form"<mac> <ip1> <ip2>… is_chassis_resident(\"cr-lrp-<id>\")"; the agent groups IPs by cr-lrp and computes the IPs added (set) or removed (unset). - Action:
expose_ip(ips, row, associated_port=<cr-lrp>)→ exposes the FIPs only if the cr-lrp is bound here and the patch port's datapath is a provider network (ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:609-621). Withdraw also proceeds when the cr-lrp has no chassis or was deleted (ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:749-765).
2.5 Tenant subnets (only with expose_tenant_networks or expose_ipv6_gua_tenant_networks)
- **
SubnetRouterAttachedEvent** (SBPort_BindingCREATE of an unboundlrp-*patch port with an IP, not achassis-redirect-port) →expose_subnet(<first CIDR of mac>, row)(ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:176-198). - **
expose_subnet** (ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:1087): proceed only if the router's cr-lrp is bound here and known, the port has apeer, and the address scope is allowed (§ 2.7). Then_expose_lrp_port(ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:981):- skip if the CIDR is the gateway's own (gateway port) or if only GUA exposure is configured and the CIDR is not a global IPv6;
- record the subnet in the cr-lrp cache and
ovn_local_lrps; - wire it: ip rule
to <cidr> lookup <bridge table>and route<cidr> via <cr-lrp ip> dev <bridge>[.<vlan>]in that table; withadvertisement_method_tenant_networks = subnet, also a route<cidr> via <cr-lrp ip> dev <bgp_nic>in tablebgp_vrf_table_id(ovn_bgp_agent/drivers/openstack/utils/wire.py:831-872); - announce every VM IP of that subnet already bound to a chassis (adds /32 or /128 on
bgp_nic) (ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:1023-1028,ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:354).
- **
SubnetRouterDetachedEvent** (DELETE) →withdraw_subnet(ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:1113): removes the VM IPs of that network frombgp_nic, then unwires the subnet (ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:1030-1084). If the router datapath is already gone (router deleted with its gateway), it only checks whether the port belongs to a local cr-lrp and stops. - **
TenantPortCreatedEvent/TenantPortDeletedEvent** (SBPort_BindingUPDATE chassis empty → set, or set → empty / DELETE, on VM/virtual ports; MACunknownhandled throughneutron:cidrs) →expose_remote_ip/withdraw_remote_ip(ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:785-867): if the port's network is not a provider network and one of its router ports is inovn_local_lrps, add/remove the IPs onbgp_nic(filtered by GUA mode and address scopes). The VM may be on any chassis: the node hosting the gateway announces it. - **
OVNLBVIPPortEvent** (SBPort_BindingCREATE/DELETE of a port without MAC, chassis andup, matched only if a cr-lrp is local) →expose_ovn_lb/withdraw_ovn_lbwith the first IP ofneutron:cidrs: same path as a tenant IP (ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:345-379,ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:445). - Note: the match functions compare
self.agent.ovn_local_lrps != [](ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:279), butovn_local_lrpsis a dict, so the comparison is always true; the real filter happens later in the driver.
2.6 OVN load balancers on provider networks: OVNLBMemberCreateEvent / OVNLBMemberDeleteEvent
- Trigger: SB
Load_BalancerCREATE (matched only if a cr-lrp is local) / DELETE (matched only if the LB was exposed here) (ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:382-469). - Create flow: read the LB datapaths (
ls_datapath_group/datapath_groupfor switches,lr_datapath_groupfor routers,ovn_bgp_agent/utils/helpers.py:40); find the VIP port (SB port without MAC/chassis whoseneutron:port_nameisovn-lb-vip-<lb name>,ovn_bgp_agent/drivers/openstack/utils/ovn.py:751); pick the local cr-lrp whose provider datapath is the VIP's and whose subnets (or router) contain the LB members; then expose the VIP like a provider IP via that gateway (ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:472-503). - Delete flow: withdraw every VIP recorded for that LB name through its gateway (
ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:505-528).
2.7 Address-scope filter
When [DEFAULT] address_scopes is set, an IP is exposed only if the port's neutron:subnet_pool_addr_scope4/6 for its IP version is in that list; a port with no scope is never exposed (ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:1148-1167, ovn_bgp_agent/drivers/openstack/utils/driver_utils.py:34).
2.8 LocalnetCreateDeleteEvent (SB)
SB Port_Binding CREATE/DELETE with type=localnet → full sync (ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:472-483). Creating a provider network therefore costs one full resynchronisation on every node.
3. nb_ovn_bgp_driver (NB driver)
3.1 Start
ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:207:
- OVS IDL;
chassis=external_ids:hostname(fallback: the system hostname) andchassis_id=external_ids:system-id(ovn_bgp_agent/drivers/openstack/utils/ovs.py:296). The hostname is compared with LSPoptions:requested-chassis/neutron:host_id; the system-id with LRPstatus:hosting-chassis. - NB remote =
[ovn] ovn_nb_connectionorexternal_ids:ovn-nb-remote(ovn_bgp_agent/drivers/openstack/utils/ovs.py:311-313). - Base FRR configuration and optional VRF table cleanup, as for SB.
- NB IDL on
Logical_Switch_Port, NAT, Logical_Switch, Logical_Router, Logical_Router_Port, Load_Balancer, DHCP_Options, NB_Global(ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:44) with the base events (ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:253). - With
exposing_method = ovn: a second NB IDL on the node-local OVN cluster[local_ovn_cluster] ovn_nb_connection, leader only, no events (ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:240-245). - Reads
NB_Global.external_ids:neutron:fip-distributed(missing → distributed, with a warning) and registers the distributed or centralized FIP events (ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:127-138,ovn_bgp_agent/drivers/openstack/utils/ovn.py:436). Centralized mode requires theNAT.gateway_portcolumn (OVN ≥ 23.09), otherwiseRuntimeErrorat start (ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:54-58).
3.2 sync (NB)
ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:292. Warning — defect D16 (02 § 4): on this branch, with exposing_method = underlay, the per-bridge routing tables are never recorded in the driver (ovn_bgp_agent/drivers/openstack/utils/wire.py:34-38), so the wiring of step 5 fails with KeyError and the whole sync fails at step 7. The steps below describe the intended behaviour, recorded with the defect corrected in the harness (07 § 8).
- Reset caches.
- Base wiring per
exposing_method(ovn_bgp_agent/drivers/openstack/utils/wire.py:34):underlay(as SB step 2),vrf(EVPN type-5 devices, § 6.4),ovn(node-local OVN router, § 6.5). **l2vnianddynamicraiseUnsupportedWiringConfig** (ovn_bgp_agent/drivers/openstack/utils/wire.py:44), so everysyncfails with these values although the option accepts them (ovn_bgp_agent/config.py:130-145). - Router gateways hosted here: every
Logical_Router_Portwhosestatus:hosting-chassisis this chassis (full list,ovn_bgp_agent/drivers/openstack/utils/ovn.py:364) → exposed if itsneutron:network_nameis a provider network wired on this node (ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:375). - Subnets of those routers: LSPs
up=true, type=router, neutron:device_owner=network:router_interfacewhoseneutron:device_idis a local router (ovn_bgp_agent/drivers/openstack/utils/ovn.py:379) →_expose_subnet(§ 3.5). - VM ports up and bound here (
up=true,requested-chassisorneutron:host_id= hostname,ovn_bgp_agent/drivers/openstack/utils/ovn.py:354): with a FIP (neutron:port_fip) and distributed mode → expose the FIP; otherwise, if on a provider network → expose the IPs. In centralized mode, all FIPs whoseNAT.gateway_portis hosted here are exposed (ovn_bgp_agent/drivers/openstack/utils/ovn.py:256). - LBs whose
lr_reforneutron:router_nameis a local router (ovn_bgp_agent/drivers/openstack/utils/ovn.py:408): port-forwarding LBs expose their FIP; others their VIP, and their VIP-FIP if any. - Cleanup per method (
ovn_bgp_agent/drivers/openstack/utils/wire.py:526): IPs onbgp_nicthat are not in_exposed_ips, rules, routes, VLAN devices, flows (underlay); excess routes of each EVPN VLAN device (vrf); onlybgp_nicIPs (ovn — local NB policies/routes/ARP proxies/static MAC bindings are not cleaned,ovn_bgp_agent/drivers/openstack/utils/wire.py:558-560).
3.3 Provider VM ports: LogicalSwitchPortProviderCreateEvent / …DeleteEvent
- Create match (
ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:39-84): VM/virtual LSP with an IP inaddresses[0]; itsneutron:network_nameis not a known tenant network; its chassis (requested-chassis, orneutron:host_idfor virtual ports,ovn_bgp_agent/drivers/openstack/utils/driver_utils.py:43) is this host; it is up; the switch is a provider network wired here; and none of its IPs is exposed yet. - Delete match (
ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:102-150): the IPs are exposed here and the port is deleted, or went down without an additional binding (requested-chassiswithout a comma), or is no longer bound here. - Action:
expose_ip/withdraw_ip(ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:514,ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:578) → wiring + announcement as SB; the exposure is recorded in_exposed_ips[<switch>][<ip>], which drives later matches and the cleanup. - Live migration:
requested-chassisholdssrc,dstduring migration; the destination matches as soon as it is listed (ovn_bgp_agent/drivers/openstack/utils/driver_utils.py:57-64), the source keeps the announcement until the port goes down without an additional binding.
3.4 Floating IPs
- Distributed mode (
NB_Global external_ids:neutron:fip-distributed = "True"or absent):LogicalSwitchPortFIPCreateEvent(ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:159): VM/virtual LSP bound here, up, withneutron:port_fip; matches when it just came up, or the FIP changed and is not exposed yet, or the chassis changed. The class docstring documents the 9 NB updates of a live migration and why only the "port up" one is used (ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:172-195). Action: read the port'sNATrow forexternal_ip,external_macandneutron:fip_network_id(ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:673-694), then expose the FIP on provider switchneutron-<fip_network_id>.LogicalSwitchPortFIPDeleteEvent(ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:260): the exposed FIP's port is deleted, goes down, changes FIP or leaves this host → withdraw the old FIP (or the current one).NATMACAddedEvent(ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:882):dnat_and_snatNAT UPDATE whoseexternal_macjust changed, whose LSP is a VM port requested on this host (or a virtual port whoseneutron:host_idis this host) → expose the FIP. This covers the case where Neutron sets the external MAC after the port is up.
- Centralized mode (
"False"; requires OVN ≥ 23.09):ExposeFIPOnCRLRP/WithdrawFIPOnCRLRP:dnat_and_snatNAT CREATE/DELETE whose LSP is a VM/virtual port and whosegateway_portis hosted on this chassis → expose the FIP with the gateway port's MAC (ovn_bgp_agent/drivers/openstack/watchers/base_watcher.py:117-149,ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:89-112).CrLrpChassisChangeExposeEvent/…WithdrawEvent: LRPstatus:hosting-chassismoved to / away from this chassis → expose/withdraw everydnat_and_snatNAT of that gateway port (ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:975-1046,ovn_bgp_agent/drivers/openstack/utils/ovn.py:457).
- Switching modes:
DistributedFlagChangedEvent(NB_Global UPDATE where the flag changes and differs from the current mode) setsagent.distributed, which validates the OVN version, swaps the NAT exposer methods, unwatches the old FIP events and watches the new ones, then runssyncandfrr_sync(ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:1049-1091,ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:173-187).
3.5 Router gateways and tenant subnets
- **
ChassisRedirectCreateEvent/…DeleteEvent** (ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:401-466): LRP UPDATE withnetworks, wherestatus:hosting-chassischanged to this chassis (create) or from this chassis / the row is deleted while hosted here (delete). Action:expose_ip/withdraw_ipwith an LRP description (mac,networks,neutron:network_name,neutron:router_name) (ovn_bgp_agent/drivers/openstack/utils/port.py:74). For a gateway, after wiring the IPs the driver recordsovn_local_cr_lrps[<router>], exposes the router's subnets and LBs (ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:549-572); withdraw does the reverse and drops the entry (ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:611-633). - **
LogicalSwitchPortSubnetAttachEvent/…DetachEvent** (ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:469-607): router-interface LSP (type=router,neutron:device_owner=network:router_interface) that becomes up / changes router to a local one (attach), or goes down / is deleted / moves to another router while its router is local (detach). Action:expose_subnet/withdraw_subnetwithneutron:cidrs,neutron:device_id, network name and address scopes. - **
_expose_subnet** (ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:861):- needs a local gateway for the router;
- with
require_snat_disabled_for_tenant_networks, drops CIDRs covered by ansnatNAT entry of the router (ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:873-895); _expose_router_lsp(ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:981):subnetmethod → network prefixes;vrfmethod → host addresses; GUA filter; address-scope filter (all filtered out →ExposeDeniedForAddressScope); wire each prefix (WireFailureon failure), record it in_exposed_ips[<provider switch>]withvia = <gateway IPs>and inovn_local_lrps[<network>];- with the
hostmethod, expose every up VM/virtual LSP of that network (ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:909-934).ExposeDeniedForAddressScopeandWireFailureare caught and logged at DEBUG (ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:897-902).
- **
LogicalSwitchPortTenantCreateEvent/…DeleteEvent** (only withadvertisement_method_tenant_networks = host): tenant LSP up/down on a network inovn_local_lrps→_expose_remote_ip/_withdraw_remote_ip:underlay→ /32 or /128 onbgp_nic;vrf→ route in the EVPN VRF via the gateway (ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:783-851,ovn_bgp_agent/drivers/openstack/utils/bgp.py:29).
3.6 Load balancers and port forwarding
- **
OVNLBCreateEvent** (ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:712): LB UPDATE withvips, linked (lr_ref) to a local router, wherevipsgrew (old keys ⊂ new keys) or the router reference appeared. For each new IP (vipskeys without the port): equal toneutron:vip→expose_ovn_lb_vip; equal toneutron:vip_fip→expose_ovn_lb_fip. A VIP on a tenant network is announced as a tenant IP; on a provider network it is wired and announced (ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:1138-1159). - **
OVNLBDeleteEvent** (ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:769): LB DELETE linked to a local router, or UPDATE where the router reference changed orvipsshrank → withdraw the corresponding VIP/FIP. - **
OVNPFCreateEvent/OVNPFDeleteEvent** (ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:841-879): LB namedpf-floatingip-*withvips, whoseneutron:router_nameis local → expose/withdraw the FIP (firstvipskey) on the gateway's provider network.
3.7 LocalnetCreateDeleteEvent (NB) and LogicalSwitchUpdateEvent
- Non-
vrfmethods: LSP CREATE/DELETE withtype=localnet→ fullsync(ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:385-398). vrfmethod:Logical_SwitchUPDATE whereneutron_bgpvpn:type::neutron_bgpvpn:vnichanged, or DELETE of a switch that had them → fullsync(ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:348-382,ovn_bgp_agent/drivers/openstack/utils/driver_utils.py:138).
4. ovn_evpn_driver (SB, EVPN type-5 per router)
Declared "not supported" in the project's support matrix (doc/source/bgp_supportability_matrix.rst). Depends on neutron_bgpvpn:vni and neutron_bgpvpn:as annotations on SB Port_Binding.external_ids (written by networking-bgpvpn or by hand).
- Start (
ovn_bgp_agent/drivers/openstack/ovn_evpn_driver.py:69): OVS and SB IDLs onPort_Binding, Chassis, Datapath_Binding, Chassis_Private; no FRR base configuration;frr_syncis a no-op (ovn_bgp_agent/drivers/openstack/ovn_evpn_driver.py:100). - **
sync** (ovn_bgp_agent/drivers/openstack/ovn_evpn_driver.py:107): ARP/NDP addresses on each mapped bridge; expose everychassisredirectport bound here; then remove extra IPs onlo-<vni>, extra routes in the VNI tables, extra cookie-998 flows and extra EVPN devices (vrf-*,lo-*,br-*,vxlan-*,veth-vrf-*,vlan-*) (ovn_bgp_agent/drivers/openstack/ovn_evpn_driver.py:705-828). Beware: thebr-prefix also matches ordinary OVS bridges; onlybr-intand the mapped bridges are spared (ovn_bgp_agent/drivers/openstack/ovn_evpn_driver.py:727-731). - Gateway bound here (EVPN
PortBindingChassisCreatedEvent, cr-lrp UPDATE withchassisgoing from empty to this chassis,ovn_bgp_agent/drivers/openstack/watchers/evpn_watcher.py:28-50) →_expose_ip(ovn_bgp_agent/drivers/openstack/ovn_evpn_driver.py:187):- provider datapath found through the FIP
nat_addressesthat mention the cr-lrp; no FIP/NAT → nothing is exposed; - VNI and AS read from the patch port's annotations; missing → DEBUG log, nothing exposed;
- create
vrf-<vni>(routing table = VNI),br-<vni>(in the VRF),vxlan-<vni>(local IP =evpn_local_ip, else first /32 oflo, else first IP ofevpn_nic; UDP portevpn_udp_dstport),lo-<vni>dummy, and either avlan-<vni>OVS internal port (VLAN provider) or aveth-vrf-<vni>/veth-ovs-<vni>pair (flat) (ovn_bgp_agent/drivers/openstack/ovn_evpn_driver.py:562-642); - FRR
add-vrfconfiguration (§ 6.1); - route the gateway IPs in the VNI table out of the VLAN/veth device, NDP proxy for IPv6, unreachable default routes in the VRF (
ovn_bgp_agent/drivers/openstack/ovn_evpn_driver.py:656-686); permanent neighbour entries for the router MAC; - expose the router's attached subnets (
_ensure_network_exposed).
- provider datapath found through the FIP
- Subnet annotated (EVPN
SubnetRouterAttachedEvent, patch port CREATE, or UPDATE whereneutron_bgpvpn:vni/asappear) →expose_subnet(no NAT) orexpose_ip(with NAT) (ovn_bgp_agent/drivers/openstack/watchers/evpn_watcher.py:81-113)._expose_subnetaddsroute <cidr> via <gateway ip>in the VNI table, a cookie-998 flow that sends traffic from the router MAC and that source network to the VRF device, and puts the bound VM IPs onlo-<vni>(ovn_bgp_agent/drivers/openstack/ovn_evpn_driver.py:420-492). A VNI/AS mismatch between subnet and gateway is logged at ERROR and ignored (ovn_bgp_agent/drivers/openstack/ovn_evpn_driver.py:404-410). - Tenant VM (EVPN
TenantPortCreatedEvent/…DeletedEvent): IP added/removed onlo-<vni>, with the kernel's automatic local route deleted from the VNI table (ovn_bgp_agent/drivers/openstack/ovn_evpn_driver.py:338-372,ovn_bgp_agent/utils/linux_net.py:663-681). - Gateway leaves (
…DeletedEvent) →withdraw_ipdeletes the devices, routes, neighbours and flows and sends FRRdel-vrf(ovn_bgp_agent/drivers/openstack/ovn_evpn_driver.py:269-335).
5. ovn_stretched_l2_bgp_driver (SB)
Purpose: when the provider network is stretched at L2 between racks, every node announces the tenant subnets with the router gateway IP as next hop, instead of host routes.
- Start (
ovn_bgp_agent/drivers/openstack/ovn_stretched_l2_bgp_driver.py:70): FRR redistribution set tokernelonly (ovn_bgp_agent/drivers/openstack/ovn_stretched_l2_bgp_driver.py:76), base configuration, SB IDL on 4 tables, 5 events (ovn_bgp_agent/drivers/openstack/ovn_stretched_l2_bgp_driver.py:103-110). No reconnection event. - **
sync** (ovn_bgp_agent/drivers/openstack/ovn_stretched_l2_bgp_driver.py:120): reads the routes of tablebgp_vrf_table_id; for every cr-lrp of the SB (not only local ones,ovn_bgp_agent/drivers/openstack/utils/ovn.py:546) addsroute <gateway network> dev <bgp_nic>androute <subnet> via <gateway ip> dev <bgp_nic>in that table for each attached subnet of the same IP version (address scopes must match both sides when filtering is on); deletes the other routes of the table. - Events:
PortBindingChassisCreatedEvent/…DeletedEventact only on cr-lrps (ovn_bgp_agent/drivers/openstack/ovn_stretched_l2_bgp_driver.py:294-328) and only fire for cr-lrps bound here, whilesyncinstalls routes for gateways on all chassis: between two syncs, a gateway that appears on another node is not routed [inferred].SubnetRouterAttachedEvent/SubnetRouterUpdateEvent/SubnetRouterDetachedEventadd/update/remove the subnet routes (ovn_bgp_agent/drivers/openstack/ovn_stretched_l2_bgp_driver.py:210-291). expose_remote_ip/withdraw_remote_ipraiseNotImplementedError(ovn_bgp_agent/drivers/openstack/ovn_stretched_l2_bgp_driver.py:534-539); no event calls them.
6. Side effects shared by the drivers
6.1 FRR base configuration (frr_sync, start)
ensure_base_bgp_configuration (ovn_bgp_agent/drivers/openstack/utils/bgp.py:70), only for exposing_method in underlay, dynamic, ovn:
- ensure VRF device
bgp_vrf(defaultbgp-vrf) with tablebgp_vrf_table_id(default 10), up; - with
advertisement_method_tenant_networks = subnet, redistributeconnectedandkernel(otherwise onlyconnected; the stretched driver forceskernel); - render
LEAK_VRF_TEMPLATEand apply it withvtysh --vty_socket /run/frr/ -f <tmpfile>(ovn_bgp_agent/drivers/openstack/utils/frr.py:89-113,ovn_bgp_agent/drivers/openstack/utils/frr.py:180-192); ifbgp_router_idis not set, it is read fromshow ip bgp summary json→ipv4Unicast.routerId; if that is empty, ERRORUnknown router-id, needed for route leakingand no configuration; - ensure dummy device
bgp_nic(defaultbgp-nic), up, enslaved to the VRF (ovn_bgp_agent/utils/linux_net.py:130). - Cost: one
vtysh -f(and possibly onevtysh -c) every 15 s, forever, even if nothing changed.
6.2 Exposing = adding an address
In underlay/ovn/dynamic modes, BGP announcement is a side effect of Zebra: adding /32 or /128 addresses to the dummy bgp_nic inside the VRF makes them "connected" routes in the VRF, redistributed into BGP and leaked into the default BGP instance (import vrf) (ovn_bgp_agent/drivers/openstack/utils/bgp.py:29-67). Withdrawing = deleting the address. In vrf mode, a route is added in the EVPN VRF instead (ovn_bgp_agent/drivers/openstack/utils/evpn.py:357).
6.3 Idempotency of kernel and OVS writes
- Netlink: routes use
replace;EEXIST/ENOENT/ESRCHon routes and rules,EEXISTon addresses (raised asIpAddressAlreadyExists, then ignored by callers) andEADDRNOTAVAILon deletes are tolerated (ovn_bgp_agent/privileged/linux_net.py:363-390). - Devices:
ensure_*set the link up and create it only onNetworkInterfaceNotFound(ovn_bgp_agent/privileged/linux_net.py:59-119). - OVS:
--may-exist add-port,--if-exists del-port; flows are compared withdump-flowsbeforeadd-flow(ovn_bgp_agent/drivers/openstack/utils/ovs.py:94-147). - Netlink dumps interrupted by concurrent changes are retried with exponential backoff for up to 8 s (
ovn_bgp_agent/utils/linux_net.py:49-53).
6.4 EVPN type-5 wiring for the NB driver (exposing_method = vrf)
_ensure_base_wiring_config_evpn (ovn_bgp_agent/drivers/openstack/utils/wire.py:87): for every bridge mapping, ovs-vsctl add-br through the IDL; for every provider Logical_Switch with neutron_bgpvpn:type = l3 and neutron_bgpvpn:vni, build an EvpnBridge (br-<vni>, vxlan-<vni>, vrf-<vni> with table = VNI, FRR add-vrf with RT/RD lists parsed with ast.literal_eval from neutron_bgpvpn:rt/rd/it/et) and a VlanDev per localnet port (veth vrf<uuid[:11]>/ovs<uuid[:11]>, anycast MAC derived from VNI and VLAN, 169.254.x.x addresses, forwarding sysctls, cookie-999 MAC-tweak flow, gateway IPs from DHCP_Options.router, IPv6 RA through FRR ipv6 nd) (ovn_bgp_agent/drivers/openstack/utils/evpn.py:39-355). evpn_local_ip is mandatory here (ConfOptionRequired, ovn_bgp_agent/drivers/openstack/utils/evpn.py:43-46). Devices are torn down when their last route goes (ovn_bgp_agent/drivers/openstack/utils/evpn.py:286-310, ovn_bgp_agent/drivers/openstack/utils/evpn.py:100-131).
6.5 Node-local OVN cluster (exposing_method = ovn, NB driver only)
_ensure_base_wiring_config_ovn (ovn_bgp_agent/drivers/openstack/utils/wire.py:186), at each sync, against the local NB:
- egress flows on each provider bridge: IP/IPv6 from each
patch-provnet-*port → set destination MAC40:44:00:00:00:06(ovn_bgp_agent/drivers/openstack/utils/wire.py:505-523); - router
bgp-router; policy priority 10inport=="bgp-router-openstack"→reroutetopeer_ips(ECMP when several) (ovn_bgp_agent/drivers/openstack/utils/wire.py:428-439); - one BFD session per peer IP on port
bgp-router-<network>and an ECMP default route per peer with that BFD (ovn_bgp_agent/drivers/openstack/utils/wire.py:281-293,ovn_bgp_agent/drivers/openstack/utils/wire.py:442-449); - for each
ovn-bridge-mappings-<bgp_chassis_id>mapping: switch + localnet port; internal side (bgp-router-openstackLRP withprovider_networks_pool_prefixes, ARP proxy on the switch port) or external side (LRP with the NIC IP/MAC, ingress flow from the external NIC to the patch port for each provider prefix, static FDB entry); gateway chassisbgp_chassis_idpriority 1 (ovn_bgp_agent/drivers/openstack/utils/wire.py:317-414,ovn_bgp_agent/drivers/openstack/utils/wire.py:452-502). Per exposed IP: aStatic_MAC_Bindingonbgp-router-openstack(ovn_bgp_agent/drivers/openstack/utils/wire.py:731-740). Tenant subnets are not wired in this mode (ovn_bgp_agent/drivers/openstack/utils/wire.py:820-828returnsNone), so_expose_router_lspraisesWireFailureand tenant networks are never exposed — consistent with "Expose Tenants: Yes (Not implemented)" in the support matrix.
Source: reverse-spec-ovn-bgp-agent/05-async-processing_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.