On Mon, Oct 5, 2026 at 5:45 PM Mairtin O'Loingsigh via dev < [email protected]> wrote:
> Transit_Router_Port entries with an empty "chassis" column are > now instantiated as distributed router ports in every availability > zone. A single ISB port binding, created by the AZ leader, is > shared across all AZs so that they agree on the tunnel key. > > This is useful for connecting a transit router to a transit switch > without pinning the port to a specific chassis. > > Assisted-by: Claude Opus 5, Claude Code > Reported-at: https://redhat.atlassian.net/browse/FDP-2881 > Signed-off-by: Mairtin O'Loingsigh <[email protected]> > --- > NEWS | 5 +++ > ic/ovn-ic.c | 48 ++++++++++++++++++++-- > ovn-ic-nb.xml | 6 ++- > tests/ovn-ic-nbctl.at | 8 +++- > tests/ovn-ic.at | 86 ++++++++++++++++++++++++++++++++++++++++ > utilities/ovn-ic-nbctl.c | 2 +- > 6 files changed, 149 insertions(+), 6 deletions(-) > > diff --git a/NEWS b/NEWS > index 7f94d0b14..7e320d361 100644 > --- a/NEWS > +++ b/NEWS > @@ -10,6 +10,11 @@ Post v26.09.0 > (e.g. via the ovs-delete-transient-ports.service on RHEL/Fedora) will > automatically remove stale tunnel ports on reboot, preventing them > from > interfering with BFD and HA failover after a gateway chassis reboot. > + - Transit_Router_Port entries with an empty "chassis" column are now > + instantiated as distributed router ports in every availability zone, > + instead of being ignored. Such ports are useful to connect a transit > + router to a transit switch. Accordingly, "ovn-ic-nbctl trp-add" no > + longer requires a trailing network or chassis argument. > - Dynamic Routing: > * Remove the "other_config:dynamic-routing-arp-prefer-local" > option from Logical Switches. EVPN-learned MAC bindings are > diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c > index 2a2664ea3..3bbc8e1c7 100644 > --- a/ic/ovn-ic.c > +++ b/ic/ovn-ic.c > @@ -1024,6 +1024,21 @@ sync_tsp_pb(const struct > icnbrec_transit_switch_port *tsp, > } > } > > +/* Sync a transit router port's fields from ICNB->ISB. */ > +static void > +sync_trp_pb(const struct icnbrec_transit_router_port *trp, > + const struct icsbrec_port_binding *isb_pb) > +{ > + if (!isb_pb) { > + return; > + } > + > + /* Sync address to ISB. */ > + if (strcmp(trp->mac, isb_pb->address)) { > + icsbrec_port_binding_set_address(isb_pb, trp->mac); > + } > +} > + > /* For each local port: > * - Sync from NB to ISB. > * - Sync gateway from SB to ISB. > @@ -1229,7 +1244,7 @@ sync_router_port(const struct icsbrec_port_binding > *isb_pb, > nbrec_logical_router_port_update_options_setkey( > lrp, "requested-chassis", trp->chassis); > } > - } else { > + } else if (smap_get(&lrp->options, "requested-chassis")) { > nbrec_logical_router_port_update_options_delkey( > lrp, "requested-chassis"); > } > @@ -1630,7 +1645,27 @@ port_binding_run(struct ic_context *ctx) > for (size_t i = 0; i < tr->n_ports; i++) { > const struct icnbrec_transit_router_port *trp = tr->ports[i]; > > - if (chassis_is_remote(ctx, trp->chassis)) { > + if (!trp->chassis[0]) { > + /* The port is not bound to any chassis, so it is local to > + * every AZ. A single ISB port binding, created by the AZ > + * leader, is shared by all the AZs, which guarantees that > + * they all use the same tunnel key for the port. */ > + isb_pb = shash_find_and_delete(&local_pbs, trp->name); > + if (!isb_pb) { > + isb_pb = shash_find_and_delete(&remote_pbs, > trp->name); > + } > + > + if (ctx->ovnisb_txn && is_az_leader(ctx->ovnisb_txn)) { > + if (!isb_pb) { > + isb_pb = create_isb_pb(ctx->ovnisb_txn, trp->name, > + ctx->runned_az, > + tr->name, > &tr->header_.uuid, > + "transit-router-port", > + &pb_tnlids); > + } > + sync_trp_pb(trp, isb_pb); > + } > + } else if (chassis_is_remote(ctx, trp->chassis)) { > isb_pb = shash_find_and_delete(&remote_pbs, trp->name); > } else { > isb_pb = shash_find_and_delete(&local_pbs, trp->name); > @@ -1639,8 +1674,8 @@ port_binding_run(struct ic_context *ctx) > ctx->runned_az, > tr->name, &tr->header_.uuid, > "transit-router-port", > &pb_tnlids); > - icsbrec_port_binding_set_address(isb_pb, trp->mac); > } > + sync_trp_pb(trp, isb_pb); > } > > /* Don't allow remote ports to create NB LRP until ICSB entry > is > @@ -1661,6 +1696,13 @@ port_binding_run(struct ic_context *ctx) > nbrec_logical_router_update_ports_delvalue(lr, node->data); > } > > + /* Delete extra port-binding from ISB. Any local port binding > that > + * is not claimed by a transit router port above belongs to a port > + * that has been removed, or that is no longer local to this AZ. > */ > + SHASH_FOR_EACH (node, &local_pbs) { > + icsbrec_port_binding_delete(node->data); > + } > + > shash_destroy(&nb_ports); > shash_destroy(&local_pbs); > shash_destroy(&remote_pbs); > diff --git a/ovn-ic-nb.xml b/ovn-ic-nb.xml > index f6110324c..53b9f88d1 100644 > --- a/ovn-ic-nb.xml > +++ b/ovn-ic-nb.xml > @@ -165,7 +165,11 @@ > </column> > > <column name="chassis"> > - The chassis this router port should be bound to. > + The chassis this router port should be bound to. If empty, the port > + is local to all availability zones, that is, every availability zone > + instantiates it as a distributed router port of its own copy of the > + transit router. This is useful for ports that connect a transit > + router to a transit switch. > </column> > > <column name="tr_uuid"> > diff --git a/tests/ovn-ic-nbctl.at b/tests/ovn-ic-nbctl.at > index 4615f73f4..ce10e0d78 100644 > --- a/tests/ovn-ic-nbctl.at > +++ b/tests/ovn-ic-nbctl.at > @@ -172,9 +172,15 @@ AT_CHECK([ovn-ic-nbctl trp-add tr0 tr0-p0 > 00:11:22:11:22:33 192.168.10.10/24 cha > > AT_CHECK([ovn-ic-nbctl tr-add tr0]) > AT_CHECK([ovn-ic-nbctl trp-add tr0 tr0-p0], [1], [], > - [ovn-ic-nbctl: 'trp-add' command requires at least 5 arguments > + [ovn-ic-nbctl: 'trp-add' command requires at least 3 arguments > ]) > > +dnl Both the networks and the chassis are optional. > +AT_CHECK([ovn-ic-nbctl trp-add tr0 tr0-p1 00:11:22:11:22:44 > 192.168.20.10/24]) > +AT_CHECK([ovn-ic-nbctl --bare --columns=chassis list Transit_Router_Port > tr0-p1], [0], [ > +]) > +AT_CHECK([ovn-ic-nbctl trp-del tr0-p1]) > + > AT_CHECK([ovn-ic-nbctl trp-add tr0 tr0-p0 00:11:22:11:22:33 > 192.168.10.10/24 chassis=chassis]) > AT_CHECK([ovn-ic-nbctl trp-add tr0 tr0-p0 00:11:22:11:22:33 > 192.168.10.10/24 chassis=chassis], [1], [], > [ovn-ic-nbctl: tr0-p0: a port with this name already exists > diff --git a/tests/ovn-ic.at b/tests/ovn-ic.at > index 638d96f79..001f729dc 100644 > --- a/tests/ovn-ic.at > +++ b/tests/ovn-ic.at > @@ -5549,6 +5549,92 @@ OVN_CLEANUP_IC([az1], [az2]) > AT_CLEANUP > ]) > > +OVN_FOR_EACH_NORTHD([ > +AT_SETUP([ovn-ic -- Add transit router port local in all AZs]) > + > +ovn_init_ic_db > +net_add n1 > +net_add n2 > +ovn_start az1 > +ovn_start az2 > + > +OVS_WAIT_FOR_OUTPUT([ovn-ic-sbctl show], [0], [dnl > +availability-zone az1 > +availability-zone az2 > +]) > +check ovn-ic-nbctl --wait=sb sync > + > +sim_add hv1 > +as hv1 > +ovs-vsctl add-br br-phys > +ovn_az_attach az1 n1 br-phys 192.168.0.1 > +ovs-vsctl set open . external-ids:ovn-is-interconn=true > +wait_row_count Chassis 1 name=hv1 > + > +sim_add hv2 > +as hv2 > +ovs-vsctl add-br br-phys > +ovn_az_attach az2 n2 br-phys 192.168.0.2 > +ovs-vsctl set open . external-ids:ovn-is-interconn=true > +wait_row_count Chassis 1 name=hv2 > + > +# No chassis is given, so the port must be local in every AZ. > +check ovn-ic-nbctl tr-add tr0 > +check ovn-ic-nbctl trp-add tr0 tr0-p0 00:00:00:11:22:00 192.168.10.10/24 > + > +# A single IC-SB port binding is shared by all the AZs. > +wait_row_count ic-sb:Port_Binding 1 logical_port=tr0-p0 > +check_row_count ic-sb:Port_Binding 1 type=transit-router-port > +wait_column "00:00:00:11:22:00" ic-sb:Port_Binding address > logical_port=tr0-p0 > + > +ovn_as az1 > +wait_row_count Port_Binding 1 logical_port=tr0-p0 > +check_row_count sb:Port_Binding 1 logical_port=tr0-p0 type=patch > +check_column "00:00:00:11:22:00" nb:Logical_Router_Port mac name=tr0-p0 > +AT_CHECK([ovn-nbctl get Logical_Router_Port tr0-p0 > options:requested-chassis], > +[1], [], [ovn-nbctl: no key "requested-chassis" in Logical_Router_Port > record "tr0-p0" column options > +]) > +az1_tunnel_key=$(ovn-sbctl --bare --columns=tunnel_key find Port_Binding > logical_port=tr0-p0) > + > +ovn_as az2 > +wait_row_count Port_Binding 1 logical_port=tr0-p0 > +check_row_count sb:Port_Binding 1 logical_port=tr0-p0 type=patch > +check_column "00:00:00:11:22:00" nb:Logical_Router_Port mac name=tr0-p0 > +AT_CHECK([ovn-nbctl get Logical_Router_Port tr0-p0 > options:requested-chassis], > +[1], [], [ovn-nbctl: no key "requested-chassis" in Logical_Router_Port > record "tr0-p0" column options > +]) > +az2_tunnel_key=$(ovn-sbctl --bare --columns=tunnel_key find Port_Binding > logical_port=tr0-p0) > + > +# Both AZs must agree on the tunnel key of the shared port. > +check test "${az1_tunnel_key}" = "${az2_tunnel_key}" > + > +# A MAC change in IC-NB is propagated to the shared IC-SB port binding and > +# from there to the logical router port of every AZ. > +check ovn-ic-nbctl set Transit_Router_Port tr0-p0 > mac='"00:00:00:11:22:01"' > +wait_column "00:00:00:11:22:01" ic-sb:Port_Binding address > logical_port=tr0-p0 > + > +ovn_as az1 > +wait_column "00:00:00:11:22:01" nb:Logical_Router_Port mac name=tr0-p0 > + > +ovn_as az2 > +wait_column "00:00:00:11:22:01" nb:Logical_Router_Port mac name=tr0-p0 > + > +# Deleting the port removes it from the IC-SB and from every AZ. > +check ovn-ic-nbctl --wait=sb trp-del tr0-p0 > +wait_row_count ic-sb:Port_Binding 0 logical_port=tr0-p0 > + > +ovn_as az1 > +wait_row_count nb:Logical_Router_Port 0 name=tr0-p0 > +wait_row_count Port_Binding 0 logical_port=tr0-p0 > + > +ovn_as az2 > +wait_row_count nb:Logical_Router_Port 0 name=tr0-p0 > +wait_row_count Port_Binding 0 logical_port=tr0-p0 > + > +OVN_CLEANUP_IC([az1], [az2]) > +AT_CLEANUP > +]) > + > OVN_FOR_EACH_NORTHD([ > AT_SETUP([ovn-ic -- Add transit router remote port - race condition]) > > diff --git a/utilities/ovn-ic-nbctl.c b/utilities/ovn-ic-nbctl.c > index 6f5bc0826..79acebdf9 100644 > --- a/utilities/ovn-ic-nbctl.c > +++ b/utilities/ovn-ic-nbctl.c > @@ -1570,7 +1570,7 @@ static const struct ctl_command_syntax > ic_nbctl_commands[] = { > { "tr-del", 1, 1, "ROUTER", NULL, ic_nbctl_tr_del, NULL, > "--if-exists", > RW }, > { "tr-list", 0, 0, "", NULL, ic_nbctl_tr_list, NULL, "", RO }, > - { "trp-add", 5, INT_MAX, > + { "trp-add", 3, INT_MAX, > "ROUTER PORT MAC [NETWORK]...[COLUMN[:KEY]=VALUE]...", > NULL, ic_nbctl_trp_add, NULL, "--may-exist", RW }, > { "trp-del", 1, 1, "PORT", NULL, ic_nbctl_trp_del, NULL, > "--if-exists", > -- > 2.55.0 > > _______________________________________________ > dev mailing list > [email protected] > https://mail.openvswitch.org/mailman/listinfo/ovs-dev > > Thakn you Mairtin, applied to main. Regards, Ales _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
