Message ID | 20240725140009.413791-7-fnordahl@ubuntu.com |
---|---|
State | Changes Requested |
Headers | show |
Series | [ovs-dev,v3,1/7] controller: Move address with port parser to lib. | expand |
Context | Check | Description |
---|---|---|
ovsrobot/apply-robot | success | apply and check: success |
ovsrobot/github-robot-_Build_and_Test | success | github build: passed |
ovsrobot/github-robot-_ovn-kubernetes | success | github build: passed |
On Thu, Jul 25, 2024 at 10:05 AM Frode Nordahl <fnordahl@ubuntu.com> 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 <fnordahl@ubuntu.com> 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 > dev@openvswitch.org > https://mail.openvswitch.org/mailman/listinfo/ovs-dev >
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; +} + /* 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); + 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); + 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 +])
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 <fnordahl@ubuntu.com> --- 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