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
