On Thu, Jul 25, 2024 at 10:05 AM Frode Nordahl <[email protected]> wrote:
>
> Introduce route-exchange module that depending on Logical Router
> Port options maintains a VRF in the system for redistribution of
> host routes to NAT addresses and LB VIPs attached to local gateway
> router datapaths.
>
> The route-exchange module requires input from both runtime_data
> and lb_data engine nodes. Consequently it needs its own I-P
> engine node.
>
> TODO:
> * E2E test together with the bgp-mirror patch.
> * E2E docs and NEWS items.
>
> Signed-off-by: Frode Nordahl <[email protected]>
Hi Frode,
Thanks for the patch series and adding this feature. Not a full review.
I've a few comments related to I-P handling. Please see below.
Can you please include a cover letter for this patch series.
Your RFC patch series had a lot of details, maybe you can include
those details in the cover letter ?
> ---
> controller/automake.mk | 9 +-
> controller/ovn-controller.c | 193 ++++++++++++++++
> controller/route-exchange-stub.c | 44 ++++
> controller/route-exchange.c | 274 ++++++++++++++++++++++
> controller/route-exchange.h | 45 ++++
> tests/system-ovn.at | 382 +++++++++++++++++++++++++++++++
> 6 files changed, 945 insertions(+), 2 deletions(-)
> create mode 100644 controller/route-exchange-stub.c
> create mode 100644 controller/route-exchange.c
> create mode 100644 controller/route-exchange.h
>
> diff --git a/controller/automake.mk b/controller/automake.mk
> index 006e884dc..3e91e97e6 100644
> --- a/controller/automake.mk
> +++ b/controller/automake.mk
> @@ -49,13 +49,18 @@ controller_ovn_controller_SOURCES = \
> controller/statctrl.h \
> controller/statctrl.c \
> controller/ct-zone.h \
> - controller/ct-zone.c
> + controller/ct-zone.c \
> + controller/route-exchange.h
>
> if HAVE_NETLINK
> controller_ovn_controller_SOURCES += \
> controller/route-exchange-netlink.h \
> controller/route-exchange-netlink-private.h \
> - controller/route-exchange-netlink.c
> + controller/route-exchange-netlink.c \
> + controller/route-exchange.c
> +else
> +controller_ovn_controller_SOURCES += \
> + controller/route-exchange-stub.c
> endif
>
> controller_ovn_controller_LDADD = lib/libovn.la
> $(OVS_LIBDIR)/libopenvswitch.la
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index 805d29c81..7bc90da31 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -87,6 +87,7 @@
> #include "statctrl.h"
> #include "lib/dns-resolve.h"
> #include "ct-zone.h"
> +#include "route-exchange.h"
>
> VLOG_DEFINE_THIS_MODULE(main);
>
> @@ -4576,6 +4577,14 @@ controller_output_mac_cache_handler(struct engine_node
> *node,
> return true;
> }
>
> +static bool
> +controller_output_route_exchange_handler(struct engine_node *node,
> + void *data OVS_UNUSED)
> +{
> + engine_set_node_state(node, EN_UPDATED);
> + return true;
> +}
> +
> /* Handles sbrec_chassis changes.
> * If a new chassis is added or removed return false, so that
> * flows are recomputed. For any updates, there is no need for
> @@ -4599,6 +4608,174 @@ pflow_lflow_output_sb_chassis_handler(struct
> engine_node *node,
> return true;
> }
>
> +struct ed_type_route_exchange {
> + /* Contains struct tracked_datapath entries for local datapaths subject
> to
> + * route exchange. */
> + struct hmap tracked_re_datapaths;
> +};
> +
> +static void
> +en_route_exchange_run(struct engine_node *node, void *data)
> +{
> + struct ed_type_route_exchange *re_data = data;
> + const struct ovsrec_open_vswitch_table *ovs_table =
> + EN_OVSDB_GET(engine_get_input("OVS_open_vswitch", node));
> + const char *chassis_id = get_ovs_chassis_id(ovs_table);
> + ovs_assert(chassis_id);
> +
> + struct ovsdb_idl_index *sbrec_chassis_by_name =
> + engine_ovsdb_node_get_index(
> + engine_get_input("SB_chassis", node),
> + "name");
> + const struct sbrec_chassis *chassis
> + = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
> + ovs_assert(chassis);
> +
> + struct ovsdb_idl_index *sbrec_port_binding_by_name =
> + engine_ovsdb_node_get_index(
> + engine_get_input("SB_port_binding", node),
> + "name");
> + struct ed_type_runtime_data *rt_data =
> + engine_get_input_data("runtime_data", node);
> +
> + const struct sbrec_load_balancer_table *lb_table =
> + EN_OVSDB_GET(engine_get_input("SB_load_balancer", node));
> + struct ed_type_lb_data *lb_data =
> + engine_get_input_data("lb_data", node);
> +
> + struct route_exchange_ctx_in r_ctx_in = {
> + .sbrec_port_binding_by_name = sbrec_port_binding_by_name,
> + .lb_table = lb_table,
> + .chassis_rec = chassis,
> + .active_tunnels = &rt_data->active_tunnels,
> + .local_datapaths = &rt_data->local_datapaths,
> + .local_lbs = &lb_data->local_lbs,
> + };
> +
> + struct route_exchange_ctx_out r_ctx_out = {
> + .tracked_re_datapaths = &re_data->tracked_re_datapaths,
> + };
> +
> +
> + route_exchange_run(&r_ctx_in, &r_ctx_out);
> +
> + engine_set_node_state(node, EN_UPDATED);
> +}
> +
> +
> +static void *
> +en_route_exchange_init(struct engine_node *node OVS_UNUSED,
> + struct engine_arg *arg OVS_UNUSED)
> +{
> + struct ed_type_route_exchange *data = xzalloc(sizeof *data);
> +
> + hmap_init(&data->tracked_re_datapaths);
> +
> + return data;
> +}
> +
> +static void
> +en_route_exchange_cleanup(void *data)
> +{
> + struct ed_type_route_exchange *re_data = data;
> +
> + tracked_datapaths_destroy(&re_data->tracked_re_datapaths);
> +}
> +
> +static bool
> +route_exchange_runtime_data_handler(struct engine_node *node, void *data)
> +{
> + struct ed_type_route_exchange *re_data = data;
> + struct ed_type_runtime_data *rt_data =
> + engine_get_input_data("runtime_data", node);
> +
> + if (!rt_data->tracked) {
> + return false;
> + }
> +
> + struct tracked_datapath *t_dp;
> + HMAP_FOR_EACH (t_dp, node, &rt_data->tracked_dp_bindings) {
> + struct tracked_datapath *re_t_dp =
> + tracked_datapath_find(&re_data->tracked_re_datapaths, t_dp->dp);
> +
> + if (re_t_dp) {
> + /* Until we get I-P support for route exchange we need to request
> + * recompute. */
> + return false;
> + }
> +
> + struct shash_node *shash_node;
> + SHASH_FOR_EACH (shash_node, &t_dp->lports) {
> + struct tracked_lport *lport = shash_node->data;
> + if (route_exchange_relevant_port(lport->pb)) {
> + /* Until we get I-P support for route exchange we need to
> + * request recompute. */
> + return false;
> + }
> + }
> + }
> +
> + return true;
> +}
> +
> +static bool
> +route_exchange_lb_data_handler(struct engine_node *node,
> + void *data)
> +{
> + struct ed_type_route_exchange *re_data = data;
> + struct ed_type_runtime_data *rt_data =
> + engine_get_input_data("runtime_data", node);
> + struct ed_type_lb_data *lb_data =
> + engine_get_input_data("lb_data", node);
> + const struct sbrec_load_balancer_table *lb_table =
> + EN_OVSDB_GET(engine_get_input("SB_load_balancer", node));
> +
> + if (!lb_data->change_tracked) {
> + return false;
> + }
> +
> + if (!rt_data->tracked) {
> + return false;
> + }
> +
> + if (hmap_is_empty(&re_data->tracked_re_datapaths)) {
> + return true;
> + }
> +
> + struct hmap *tracked_dp_bindings = &rt_data->tracked_dp_bindings;
> + if (hmap_is_empty(tracked_dp_bindings)) {
> + return true;
> + }
> +
> + struct hmap *lbs = NULL;
> +
> + struct tracked_datapath *t_dp;
> + HMAP_FOR_EACH (t_dp, node, tracked_dp_bindings) {
> + struct tracked_datapath *re_t_dp =
> + tracked_datapath_find(&re_data->tracked_re_datapaths, t_dp->dp);
> +
> + if (!re_t_dp) {
> + continue;
> + }
> +
> + if (!lbs) {
> + lbs = load_balancers_by_dp_init(&rt_data->local_datapaths,
> + lb_table);
> + }
> +
> + struct load_balancers_by_dp *lbs_by_dp =
> + load_balancers_by_dp_find(lbs, re_t_dp->dp);
> + if (lbs_by_dp) {
> + /* Until we get I-P support for route exchange we need to
> + * request recompute. */
> + load_balancers_by_dp_cleanup(lbs);
> + return false;
> + }
> + }
> + load_balancers_by_dp_cleanup(lbs);
> + return true;
> +}
The I-P handler route_exchange_lb_data_handler() should ideally handle
the tracked changes provided by the lb_data engine node
and not provided by the runtime data.
Please take a look at lflow_handle_changed_lbs() in controller/lflow.c
I thuink you need to handle 2 main scenarios for load balancer related updates.
1. When a load balancer gets updated (eg. ovn-nbctl set
load_balancer <lb1> vips:IP="BIP1, BIP2").
2. When a load balancer gets associated to or dissassociated from a
logical switch/router (eg. ovn-nbctl ls-lb-add sw0 lb1)
In both the cases, the engine node "lb_data" will get updated and the
handler route_exchange_lb_data_handler() will be
called for the route_exchange node.
Either you can have a NULL handler in route_exchange (as a first step)
for lb_data changes so that route_exchange engine node
will always recompute and handle the I-P changes in the future patch.
i.e engine_add_input(&en_route_exchange, &en_lb_data, NULL);
OR
Iterate through the changed load balancers (see 'struct
ed_type_lb_data') and return false if the changed load balancer is
local to the
chassis (see the function lb_is_local() in controller/local-data.c)
and add proper I-P handling for load balancers in the future patch.
> +
> /* Returns false if the northd internal version stored in SB_Global
> * and ovn-controller internal version don't match.
> */
> @@ -4885,6 +5062,7 @@ main(int argc, char *argv[])
> ENGINE_NODE(if_status_mgr, "if_status_mgr");
> ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lb_data, "lb_data");
> ENGINE_NODE(mac_cache, "mac_cache");
> + ENGINE_NODE(route_exchange, "route_exchange");
>
> #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
> SB_NODES
> @@ -4907,6 +5085,17 @@ main(int argc, char *argv[])
> engine_add_input(&en_lb_data, &en_runtime_data,
> lb_data_runtime_data_handler);
>
> + engine_add_input(&en_route_exchange, &en_ovs_open_vswitch, NULL);
> + engine_add_input(&en_route_exchange, &en_sb_chassis, NULL);
> + engine_add_input(&en_route_exchange, &en_sb_port_binding,
> + engine_noop_handler);
Why does route_exchange need this sb_port_binding as input ? If so,
ideally it should
handle the changes or put a comment why a noop handler is sufficient.
> + engine_add_input(&en_route_exchange, &en_runtime_data,
> + route_exchange_runtime_data_handler);
> + engine_add_input(&en_route_exchange, &en_sb_load_balancer,
> + engine_noop_handler);
I don't think there is a a need to add en_sb_load_balancer as input here
if you handle the changes provided by "en_lb_data" properly.
Thanks
Numan
> + engine_add_input(&en_route_exchange, &en_lb_data,
> + route_exchange_lb_data_handler);
> +
> engine_add_input(&en_addr_sets, &en_sb_address_set,
> addr_sets_sb_address_set_handler);
> engine_add_input(&en_port_groups, &en_sb_port_group,
> @@ -5081,6 +5270,8 @@ main(int argc, char *argv[])
> controller_output_pflow_output_handler);
> engine_add_input(&en_controller_output, &en_mac_cache,
> controller_output_mac_cache_handler);
> + engine_add_input(&en_controller_output, &en_route_exchange,
> + controller_output_route_exchange_handler);
>
> struct engine_arg engine_arg = {
> .sb_idl = ovnsb_idl_loop.idl,
> @@ -5770,6 +5961,7 @@ loop_done:
> ovsdb_idl_loop_commit_and_wait(&ovs_idl_loop);
> poll_block();
> }
> + route_exchange_cleanup();
> }
>
> free(ovn_version);
> @@ -5799,6 +5991,7 @@ loop_done:
> service_stop();
> ovsrcu_exit();
> dns_resolve_destroy();
> + route_exchange_destroy();
>
> exit(retval);
> }
> diff --git a/controller/route-exchange-stub.c
> b/controller/route-exchange-stub.c
> new file mode 100644
> index 000000000..839cbc077
> --- /dev/null
> +++ b/controller/route-exchange-stub.c
> @@ -0,0 +1,44 @@
> +/*
> + * Copyright (c) 2024 Canonical
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + * http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <config.h>
> +
> +#include <stdbool.h>
> +
> +#include "openvswitch/compiler.h"
> +#include "route-exchange.h"
> +
> +bool
> +route_exchange_relevant_port(const struct sbrec_port_binding *pb OVS_UNUSED)
> +{
> + return false;
> +}
> +
> +void
> +route_exchange_run(struct route_exchange_ctx_in *r_ctx_in OVS_UNUSED,
> + struct route_exchange_ctx_out *r_ctx_out OVS_UNUSED)
> +{
> +}
> +
> +void
> +route_exchange_cleanup(void)
> +{
> +}
> +
> +void
> +route_exchange_destroy(void)
> +{
> +}
> diff --git a/controller/route-exchange.c b/controller/route-exchange.c
> new file mode 100644
> index 000000000..d3b8f0480
> --- /dev/null
> +++ b/controller/route-exchange.c
> @@ -0,0 +1,274 @@
> +/*
> + * Copyright (c) 2024 Canonical
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + * http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <config.h>
> +
> +#include <errno.h>
> +#include <net/if.h>
> +
> +#include "openvswitch/vlog.h"
> +
> +#include "lib/ovn-sb-idl.h"
> +
> +#include "binding.h"
> +#include "ha-chassis.h"
> +#include "lb.h"
> +#include "local_data.h"
> +#include "route-exchange.h"
> +#include "route-exchange-netlink.h"
> +
> +
> +VLOG_DEFINE_THIS_MODULE(route_exchange);
> +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
> +
> +/* While the linux kernel can handle 2^32 routing tables, only so many can
> fit
> + * in the corresponding VRF interface name. */
> +#define MAX_TABLE_ID 1000000000
> +
> +static struct sset _maintained_vrfs = SSET_INITIALIZER(&_maintained_vrfs);
> +
> +bool
> +route_exchange_relevant_port(const struct sbrec_port_binding *pb) {
> + return (pb && pb->type && !strcmp(pb->type, "l3gateway") &&
> + (smap_get_bool(&pb->options, "redistribute-lb-vips", false)
> ||
> + smap_get_bool(&pb->options, "redistribute-nat", false)));
> +}
> +
> +static void
> +extract_nat_addresses(const struct sbrec_port_binding *pb,
> + struct route_exchange_ctx_in *r_ctx_in,
> + uint32_t table_id, struct hmap *host_routes)
> +{
> + if (!pb || !pb->n_nat_addresses) {
> + return;
> + }
> + VLOG_DBG("extract_nat_addresses: considering lport %s",
> pb->logical_port);
> +
> + for (size_t i = 0; i < pb->n_nat_addresses; i++) {
> + struct lport_addresses *laddrs = xzalloc(sizeof *laddrs);
> + char *lport = NULL;
> +
> + if (!extract_addresses_with_port(
> + pb->nat_addresses[i], laddrs, &lport)) {
> + VLOG_DBG("extract_nat_addresses: no addresses");
> + goto cleanup;
> + }
> + if (lport) {
> + const struct sbrec_port_binding *lport_pb = lport_lookup_by_name(
> + r_ctx_in->sbrec_port_binding_by_name, lport);
> + if (!lport_pb || !lport_pb->chassis) {
> + VLOG_DBG("extract_nat_addresses: cannot find lport %s",
> + lport);
> + goto cleanup;
> + }
> + enum en_lport_type lport_pb_type = get_lport_type(lport_pb);
> + if (((lport_pb_type == LP_VIF ||
> + lport_pb_type == LP_CHASSISREDIRECT) &&
> + lport_pb->chassis != r_ctx_in->chassis_rec) ||
> + !ha_chassis_group_is_active(lport_pb->ha_chassis_group,
> + r_ctx_in->active_tunnels,
> + r_ctx_in->chassis_rec)) {
> + VLOG_DBG("extract_nat_addresses: ignoring non-local lport
> %s",
> + lport);
> + goto cleanup;
> + }
> + }
> + for (size_t j = 0; j < laddrs->n_ipv4_addrs; j++) {
> + struct in6_addr addr;
> + in6_addr_set_mapped_ipv4(&addr, laddrs->ipv4_addrs[j].addr);
> + host_route_insert(host_routes, table_id, &addr);
> + }
> + for (size_t j = 0; j < laddrs->n_ipv6_addrs; j++) {
> + host_route_insert(host_routes, table_id,
> + &laddrs->ipv6_addrs[j].addr);
> + }
> +
> +cleanup:
> + destroy_lport_addresses(laddrs);
> + free(laddrs);
> + if (lport) {
> + free(lport);
> + }
> + }
> +}
> +
> +static void
> +extract_lb_vips(const struct sbrec_datapath_binding *dpb,
> + struct hmap *lbs_by_dp_hmap,
> + const struct route_exchange_ctx_in *r_ctx_in,
> + uint32_t table_id, struct hmap *host_routes)
> +{
> + struct load_balancers_by_dp *lbs_by_dp
> + = load_balancers_by_dp_find(lbs_by_dp_hmap, dpb);
> + if (!lbs_by_dp) {
> + return;
> + }
> +
> + for (size_t i = 0; i < lbs_by_dp->n_dp_lbs; i++) {
> + const struct sbrec_load_balancer *sbrec_lb
> + = lbs_by_dp->dp_lbs[i];
> +
> + if (!sbrec_lb) {
> + return;
> + }
> +
> + struct ovn_controller_lb *lb
> + = ovn_controller_lb_find(r_ctx_in->local_lbs,
> + &sbrec_lb->header_.uuid);
> +
> + if (!lb || !lb->slb) {
> + return;
> + }
> +
> + VLOG_DBG("considering lb for route leaking: %s", lb->slb->name);
> + for (i = 0; i < lb->n_vips; i++) {
> + VLOG_DBG("considering lb for route leaking: %s vip_str=%s",
> + lb->slb->name, lb->vips[i].vip_str);
> + host_route_insert(host_routes, table_id, &lb->vips[i].vip);
> + }
> + }
> +}
> +
> +void
> +route_exchange_run(struct route_exchange_ctx_in *r_ctx_in,
> + struct route_exchange_ctx_out *r_ctx_out)
> +{
> + struct sset old_maintained_vrfs = SSET_INITIALIZER(&old_maintained_vrfs);
> + sset_swap(&_maintained_vrfs, &old_maintained_vrfs);
> + struct hmap *lbs_by_dp_hmap
> + = load_balancers_by_dp_init(r_ctx_in->local_datapaths,
> + r_ctx_in->lb_table);
> +
> + /* Extract all NAT- and LB VIP-addresses associated with lports resident
> on
> + * the current chassis to allow full sync of leaked routing tables. */
> + const struct local_datapath *ld;
> + HMAP_FOR_EACH (ld, hmap_node, r_ctx_in->local_datapaths) {
> + if (!ld->n_peer_ports || ld->is_switch) {
> + continue;
> + }
> +
> + bool maintain_vrf = false;
> + bool lbs_sync = false;
> + struct hmap local_host_routes_for_current_dp
> + = HMAP_INITIALIZER(&local_host_routes_for_current_dp);
> +
> + /* This is a LR datapath, find LRPs with route exchange options. */
> + for (size_t i = 0; i < ld->n_peer_ports; i++) {
> + const struct sbrec_port_binding *local_peer
> + = ld->peer_ports[i].local;
> + if (!local_peer || !route_exchange_relevant_port(local_peer)) {
> + continue;
> + }
> +
> + maintain_vrf |= smap_get_bool(&local_peer->options,
> + "maintain-vrf", false);
> + lbs_sync |= smap_get_bool(&local_peer->options,
> + "redistribute-lb-vips",
> + false);
> + if (smap_get_bool(&local_peer->options,
> + "redistribute-nat",
> + false)) {
> + extract_nat_addresses(local_peer, r_ctx_in,
> + ld->datapath->tunnel_key,
> + &local_host_routes_for_current_dp);
> + }
> + }
> +
> + if (lbs_sync) {
> + extract_lb_vips(ld->datapath, lbs_by_dp_hmap, r_ctx_in,
> + ld->datapath->tunnel_key,
> + &local_host_routes_for_current_dp);
> + }
> +
> + /* While tunnel_key would most likely never be negative, the compiler
> + * has opinions if we don't check before using it in snprintf below.
> */
> + if (ld->datapath->tunnel_key < 0 ||
> + ld->datapath->tunnel_key > MAX_TABLE_ID) {
> + VLOG_WARN_RL(&rl,
> + "skip route sync for datapath "UUID_FMT", "
> + "tunnel_key %"PRIi64" would make VRF interface name
> "
> + "overflow.",
> + UUID_ARGS(&ld->datapath->header_.uuid),
> + ld->datapath->tunnel_key);
> + goto out;
> + }
> + char vrf_name[IFNAMSIZ + 1];
> + snprintf(vrf_name, sizeof vrf_name, "ovnvrf%"PRIi64,
> + ld->datapath->tunnel_key);
> +
> + if (maintain_vrf) {
> + int error = re_nl_create_vrf(vrf_name, ld->datapath->tunnel_key);
> + if (error && error != EEXIST) {
> + VLOG_WARN_RL(&rl,
> + "Unable to create VRF %s for datapath "UUID_FMT
> + ": %s.",
> + vrf_name,
> UUID_ARGS(&ld->datapath->header_.uuid),
> + ovs_strerror(error));
> + goto out;
> + }
> + sset_add(&_maintained_vrfs, vrf_name);
> + }
> + if (!hmap_is_empty(&local_host_routes_for_current_dp)) {
> + tracked_datapath_add(ld->datapath, TRACKED_RESOURCE_NEW,
> + r_ctx_out->tracked_re_datapaths);
> + }
> + re_nl_sync_routes(ld->datapath->tunnel_key, vrf_name,
> + &local_host_routes_for_current_dp);
> +
> +out:
> + host_routes_destroy(&local_host_routes_for_current_dp);
> + }
> +
> + /* Remove VRFs previously maintained by us not found in the above loop.
> */
> + const char *vrf_name;
> + SSET_FOR_EACH_SAFE (vrf_name, &old_maintained_vrfs) {
> + if (!sset_find(&_maintained_vrfs, vrf_name)) {
> + re_nl_delete_vrf(vrf_name);
> + }
> + sset_delete(&old_maintained_vrfs, SSET_NODE_FROM_NAME(vrf_name));
> + }
> + sset_destroy(&old_maintained_vrfs);
> +
> + load_balancers_by_dp_cleanup(lbs_by_dp_hmap);
> +}
> +
> +static void
> +route_exchange_cleanup__(bool cleanup)
> +{
> + const char *vrf_name;
> + SSET_FOR_EACH_SAFE (vrf_name, &_maintained_vrfs) {
> + if (cleanup) {
> + re_nl_delete_vrf(vrf_name);
> + } else {
> + sset_delete(&_maintained_vrfs, SSET_NODE_FROM_NAME(vrf_name));
> + }
> + }
> + if (!cleanup) {
> + sset_destroy(&_maintained_vrfs);
> + }
> +}
> +
> +void
> +route_exchange_cleanup(void)
> +{
> + route_exchange_cleanup__(true);
> +}
> +
> +void
> +route_exchange_destroy(void)
> +{
> + route_exchange_cleanup__(false);
> +}
> diff --git a/controller/route-exchange.h b/controller/route-exchange.h
> new file mode 100644
> index 000000000..de554f9b1
> --- /dev/null
> +++ b/controller/route-exchange.h
> @@ -0,0 +1,45 @@
> +/*
> + * Copyright (c) 2024 Canonical
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + * http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#ifndef ROUTE_EXCHANGE_H
> +#define ROUTE_EXCHANGE_H 1
> +
> +struct hmap;
> +struct ovsdb_idl_index;
> +struct sbrec_chassis;
> +struct sbrec_port_binding;
> +struct sset;
> +
> +struct route_exchange_ctx_in {
> + struct ovsdb_idl_index *sbrec_port_binding_by_name;
> + const struct sbrec_load_balancer_table *lb_table;
> + const struct sbrec_chassis *chassis_rec;
> + const struct sset *active_tunnels;
> + struct hmap *local_datapaths;
> + struct hmap *local_lbs;
> +};
> +
> +struct route_exchange_ctx_out {
> + struct hmap *tracked_re_datapaths;
> +};
> +
> +bool route_exchange_relevant_port(const struct sbrec_port_binding *pb);
> +void route_exchange_run(struct route_exchange_ctx_in *,
> + struct route_exchange_ctx_out *);
> +void route_exchange_cleanup(void);
> +void route_exchange_destroy(void);
> +
> +#endif /* ROUTE_EXCHANGE_H */
> diff --git a/tests/system-ovn.at b/tests/system-ovn.at
> index ddb3d14e9..2c410d555 100644
> --- a/tests/system-ovn.at
> +++ b/tests/system-ovn.at
> @@ -13022,3 +13022,385 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port
> patch-.*/d
> /connection dropped.*/d"])
> AT_CLEANUP
> ])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([route-exchange for LB VIPs with gateway router IPv4])
> +AT_KEYWORDS([route-exchange])
> +
> +CHECK_VRF()
> +CHECK_CONNTRACK()
> +CHECK_CONNTRACK_NAT()
> +ovn_start
> +OVS_TRAFFIC_VSWITCHD_START()
> +ADD_BR([br-int])
> +ADD_BR([br-ext], [set Bridge br-ext fail-mode=standalone])
> +
> +# Set external-ids in br-int needed for ovn-controller
> +ovs-vsctl \
> + -- set Open_vSwitch . external-ids:system-id=hv1 \
> + -- set Open_vSwitch .
> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
> + -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
> + -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
> + -- set bridge br-int fail-mode=secure
> other-config:disable-in-band=true
> +
> +# Start ovn-controller
> +start_daemon ovn-controller
> +
> +ovn-appctl vlog/set route_exchange
> +check ovn-nbctl -- lr-add R1 \
> + -- set Logical_Router R1 options:requested-tnl-key=1000
> +
> +check ovn-nbctl ls-add sw0
> +check ovn-nbctl ls-add public
> +
> +check ovn-nbctl --wait=hv sync
> +
> +AT_CHECK([ip link | grep -q ovnvrf1000:.*UP], [1])
> +
> +check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24
> +check ovn-nbctl -- lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24 \
> + -- lrp-set-options rp-public \
> + maintain-vrf=true \
> + redistribute-lb-vips=true
> +
> +check ovn-nbctl set logical_router R1 options:chassis=hv1
> +
> +check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \
> + type=router options:router-port=rp-sw0 \
> + -- lsp-set-addresses sw0-rp router
> +
> +check ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port
> public-rp \
> + type=router options:router-port=rp-public \
> + -- lsp-set-addresses public-rp router
> +
> +check ovs-vsctl set Open_vSwitch .
> external-ids:ovn-bridge-mappings=phynet:br-ext
> +
> +check ovn-nbctl lsp-add public public1 \
> + -- lsp-set-addresses public1 unknown \
> + -- lsp-set-type public1 localnet \
> + -- lsp-set-options public1 network_name=phynet
> +
> +check ovn-nbctl --wait=hv sync
> +
> +AT_CHECK([test `ip route show table 1000 | wc -l` -eq 1], [1])
> +
> +# Create a load balancer and associate to R1
> +check ovn-nbctl lb-add lb1 172.16.1.150:80 172.16.1.100:80
> +check ovn-nbctl lr-lb-add R1 lb1
> +
> +check ovn-nbctl --wait=hv sync
> +
> +AT_CHECK([ip link | grep -q ovnvrf1000:.*UP])
> +AT_CHECK([test `ip route show table 1000 | wc -l` -eq 1])
> +AT_CHECK([ip route show table 1000 | grep -q 172.16.1.150])
> +
> +
> +OVS_APP_EXIT_AND_WAIT([ovn-controller])
> +
> +# Ensure system resources are cleaned up
> +AT_CHECK([ip link | grep -q ovnvrf1000:.*UP], [1])
> +AT_CHECK([test `ip route show table 1000 | wc -l` -eq 1], [1])
> +
> +as ovn-sb
> +OVS_APP_EXIT_AND_WAIT([ovsdb-server])
> +
> +as ovn-nb
> +OVS_APP_EXIT_AND_WAIT([ovsdb-server])
> +
> +as northd
> +OVS_APP_EXIT_AND_WAIT([ovn-northd])
> +
> +as
> +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
> +/Failed to acquire.*/d
> +/connection dropped.*/d"])
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([route-exchange for LB VIPs with gateway router IPv6])
> +AT_KEYWORDS([route-exchange])
> +
> +CHECK_VRF()
> +CHECK_CONNTRACK()
> +CHECK_CONNTRACK_NAT()
> +ovn_start
> +OVS_TRAFFIC_VSWITCHD_START()
> +ADD_BR([br-int])
> +ADD_BR([br-ext], [set Bridge br-ext fail-mode=standalone])
> +
> +# Set external-ids in br-int needed for ovn-controller
> +ovs-vsctl \
> + -- set Open_vSwitch . external-ids:system-id=hv1 \
> + -- set Open_vSwitch .
> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
> + -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
> + -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
> + -- set bridge br-int fail-mode=secure
> other-config:disable-in-band=true
> +
> +# Start ovn-controller
> +start_daemon ovn-controller
> +
> +ovn-appctl vlog/set route_exchange
> +check ovn-nbctl -- lr-add R1 \
> + -- set Logical_Router R1 options:requested-tnl-key=1001
> +
> +check ovn-nbctl ls-add sw0
> +check ovn-nbctl ls-add public
> +
> +check ovn-nbctl --wait=hv sync
> +
> +AT_CHECK([ip link | grep -q ovnvrf1001:.*UP], [1])
> +
> +check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 2001:db8:100::1/64
> +check ovn-nbctl -- lrp-add R1 rp-public 00:00:02:01:02:03
> 2001:db8:1001::1/64 \
> + -- lrp-set-options rp-public \
> + maintain-vrf=true \
> + redistribute-lb-vips=true
> +
> +check ovn-nbctl set logical_router R1 options:chassis=hv1
> +
> +check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \
> + type=router options:router-port=rp-sw0 \
> + -- lsp-set-addresses sw0-rp router
> +
> +check ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port
> public-rp \
> + type=router options:router-port=rp-public \
> + -- lsp-set-addresses public-rp router
> +
> +check ovs-vsctl set Open_vSwitch .
> external-ids:ovn-bridge-mappings=phynet:br-ext
> +
> +check ovn-nbctl lsp-add public public1 \
> + -- lsp-set-addresses public1 unknown \
> + -- lsp-set-type public1 localnet \
> + -- lsp-set-options public1 network_name=phynet
> +
> +check ovn-nbctl --wait=hv sync
> +
> +AT_CHECK([test `ip -6 route show table 1001 | wc -l` -eq 1], [1])
> +
> +# Create a load balancer and associate to R1
> +check ovn-nbctl lb-add lb1 [[2001:db8:1001::150]]:80
> [[2001:db8:1001::100]]:80
> +check ovn-nbctl lr-lb-add R1 lb1
> +
> +check ovn-nbctl --wait=hv sync
> +
> +AT_CHECK([ip link | grep -q ovnvrf1001:.*UP])
> +AT_CHECK([test `ip -6 route show table 1001 | wc -l` -eq 1])
> +AT_CHECK([ip -6 route show table 1001 | grep -q 2001:db8:1001::150])
> +
> +
> +OVS_APP_EXIT_AND_WAIT([ovn-controller])
> +
> +# Ensure system resources are cleaned up
> +AT_CHECK([ip link | grep -q ovnvrf1001:.*UP], [1])
> +AT_CHECK([test `ip -6 route show table 1001 | wc -l` -eq 1], [1])
> +
> +as ovn-sb
> +OVS_APP_EXIT_AND_WAIT([ovsdb-server])
> +
> +as ovn-nb
> +OVS_APP_EXIT_AND_WAIT([ovsdb-server])
> +
> +as northd
> +OVS_APP_EXIT_AND_WAIT([ovn-northd])
> +
> +as
> +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
> +/Failed to acquire.*/d
> +/connection dropped.*/d"])
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([route-exchange for DNAT and DNAT_AND_SNAT with gateway router
> IPv4])
> +AT_KEYWORDS([route-exchange])
> +
> +CHECK_VRF()
> +CHECK_CONNTRACK()
> +CHECK_CONNTRACK_NAT()
> +ovn_start
> +OVS_TRAFFIC_VSWITCHD_START()
> +ADD_BR([br-int])
> +ADD_BR([br-ext], [set Bridge br-ext fail-mode=standalone])
> +
> +# Set external-ids in br-int needed for ovn-controller
> +ovs-vsctl \
> + -- set Open_vSwitch . external-ids:system-id=hv1 \
> + -- set Open_vSwitch .
> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
> + -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
> + -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
> + -- set bridge br-int fail-mode=secure
> other-config:disable-in-band=true
> +
> +# Start ovn-controller
> +start_daemon ovn-controller
> +
> +ovn-appctl vlog/set route_exchange
> +check ovn-nbctl -- lr-add R1 \
> + -- set Logical_Router R1 options:requested-tnl-key=1002
> +
> +check ovn-nbctl ls-add sw0
> +check ovn-nbctl ls-add public
> +
> +check ovn-nbctl --wait=hv sync
> +
> +AT_CHECK([ip link | grep -q ovnvrf1002:.*UP], [1])
> +
> +check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24
> +check ovn-nbctl -- lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24 \
> + -- lrp-set-options rp-public \
> + maintain-vrf=true \
> + redistribute-nat=true
> +
> +check ovn-nbctl set logical_router R1 options:chassis=hv1
> +
> +check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \
> + type=router options:router-port=rp-sw0 \
> + -- lsp-set-addresses sw0-rp router
> +
> +check ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port
> public-rp \
> + type=router options:router-port=rp-public \
> + -- lsp-set-addresses public-rp router
> +
> +check ovs-vsctl set Open_vSwitch .
> external-ids:ovn-bridge-mappings=phynet:br-ext
> +
> +check ovn-nbctl lsp-add public public1 \
> + -- lsp-set-addresses public1 unknown \
> + -- lsp-set-type public1 localnet \
> + -- lsp-set-options public1 network_name=phynet
> +
> +check ovn-nbctl --wait=hv sync
> +
> +AT_CHECK([test `ip route show table 1002 | wc -l` -eq 2], [1])
> +
> +# Create dnat_and_snat, dnat rules in R1
> +check ovn-nbctl lr-nat-add R1 dnat_and_snat 172.16.1.10 192.168.1.10
> +check ovn-nbctl lr-nat-add R1 dnat 172.16.1.11 192.168.1.11
> +
> +check ovn-nbctl --wait=hv sync
> +
> +AT_CHECK([ip link | grep -q ovnvrf1002:.*UP])
> +AT_CHECK([test `ip route show table 1002 | wc -l` -eq 2])
> +AT_CHECK([ip route show table 1002 | grep -q 172.16.1.10])
> +AT_CHECK([ip route show table 1002 | grep -q 172.16.1.11])
> +
> +
> +OVS_APP_EXIT_AND_WAIT([ovn-controller])
> +
> +# Ensure system resources are cleaned up
> +AT_CHECK([ip link | grep -q ovnvrf1000:.*UP], [1])
> +AT_CHECK([test `ip route show table 1000 | wc -l` -eq 1], [1])
> +
> +as ovn-sb
> +OVS_APP_EXIT_AND_WAIT([ovsdb-server])
> +
> +as ovn-nb
> +OVS_APP_EXIT_AND_WAIT([ovsdb-server])
> +
> +as northd
> +OVS_APP_EXIT_AND_WAIT([ovn-northd])
> +
> +as
> +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
> +/Failed to acquire.*/d
> +/connection dropped.*/d"])
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([route-exchange for DNAT and DNAT_AND_SNAT with gateway router
> IPv6])
> +AT_KEYWORDS([route-exchange])
> +
> +CHECK_VRF()
> +CHECK_CONNTRACK()
> +CHECK_CONNTRACK_NAT()
> +ovn_start
> +OVS_TRAFFIC_VSWITCHD_START()
> +ADD_BR([br-int])
> +ADD_BR([br-ext], [set Bridge br-ext fail-mode=standalone])
> +
> +# Set external-ids in br-int needed for ovn-controller
> +ovs-vsctl \
> + -- set Open_vSwitch . external-ids:system-id=hv1 \
> + -- set Open_vSwitch .
> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
> + -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
> + -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
> + -- set bridge br-int fail-mode=secure
> other-config:disable-in-band=true
> +
> +# Start ovn-controller
> +start_daemon ovn-controller
> +
> +ovn-appctl vlog/set route_exchange
> +check ovn-nbctl -- lr-add R1 \
> + -- set Logical_Router R1 options:requested-tnl-key=1003
> +
> +check ovn-nbctl ls-add sw0
> +check ovn-nbctl ls-add public
> +
> +check ovn-nbctl --wait=hv sync
> +
> +AT_CHECK([ip link | grep -q ovnvrf1003:.*UP], [1])
> +
> +check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 2001:db8:100::1/64
> +check ovn-nbctl -- lrp-add R1 rp-public 00:00:02:01:02:03
> 2001:db8:1003::1/64 \
> + -- lrp-set-options rp-public \
> + maintain-vrf=true \
> + redistribute-nat=true
> +
> +check ovn-nbctl set logical_router R1 options:chassis=hv1
> +
> +check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \
> + type=router options:router-port=rp-sw0 \
> + -- lsp-set-addresses sw0-rp router
> +
> +check ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port
> public-rp \
> + type=router options:router-port=rp-public \
> + -- lsp-set-addresses public-rp router
> +
> +check ovs-vsctl set Open_vSwitch .
> external-ids:ovn-bridge-mappings=phynet:br-ext
> +
> +check ovn-nbctl lsp-add public public1 \
> + -- lsp-set-addresses public1 unknown \
> + -- lsp-set-type public1 localnet \
> + -- lsp-set-options public1 network_name=phynet
> +
> +check ovn-nbctl --wait=hv sync
> +
> +AT_CHECK([test `ip -6 route show table 1003 | wc -l` -eq 2], [1])
> +
> +# Create dnat_and_snat, dnat rules in R1
> +check ovn-nbctl lr-nat-add R1 \
> + dnat_and_snat 2001:db8:1003::150 2001:db8:100::100
> +check ovn-nbctl lr-nat-add R1 \
> + dnat 2001:db8:1003::151 2001:db8:100::100
> +
> +check ovn-nbctl --wait=hv sync
> +
> +ovn-nbctl list nat
> +ovn-sbctl list port-binding
> +
> +AT_CHECK([ip link | grep -q ovnvrf1003:.*UP])
> +AT_CHECK([test `ip -6 route show table 1003 | wc -l` -eq 2])
> +AT_CHECK([ip -6 route show table 1003 | grep -q 2001:db8:1003::150])
> +AT_CHECK([ip -6 route show table 1003 | grep -q 2001:db8:1003::151])
> +
> +OVS_APP_EXIT_AND_WAIT([ovn-controller])
> +
> +# Ensure system resources are cleaned up
> +AT_CHECK([ip link | grep -q ovnvrf1003:.*UP], [1])
> +AT_CHECK([test `ip -6 route show table 1003 | wc -l` -eq 2], [1])
> +
> +as ovn-sb
> +OVS_APP_EXIT_AND_WAIT([ovsdb-server])
> +
> +as ovn-nb
> +OVS_APP_EXIT_AND_WAIT([ovsdb-server])
> +
> +as northd
> +OVS_APP_EXIT_AND_WAIT([ovn-northd])
> +
> +as
> +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
> +/Failed to acquire.*/d
> +/connection dropped.*/d"])
> +AT_CLEANUP
> +])
> --
> 2.45.2
>
> _______________________________________________
> dev mailing list
> [email protected]
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev