This engine node determines the routes that the ovn-controller should export.
Co-Authored-By: Frode Nordahl <fnord...@ubuntu.com> Signed-off-by: Frode Nordahl <fnord...@ubuntu.com> Signed-off-by: Felix Huettner <felix.huettner@stackit.cloud> --- v3->v4: - addressed review comments. TODO.rst | 1 + controller/automake.mk | 4 +- controller/local_data.c | 7 +- controller/local_data.h | 1 + controller/ovn-controller.c | 191 ++++++++++++++++++++++++++++++++- controller/route.c | 203 ++++++++++++++++++++++++++++++++++++ controller/route.h | 68 ++++++++++++ tests/automake.mk | 1 + 8 files changed, 473 insertions(+), 3 deletions(-) create mode 100644 controller/route.c create mode 100644 controller/route.h diff --git a/TODO.rst b/TODO.rst index 3426497a7..b3ab43232 100644 --- a/TODO.rst +++ b/TODO.rst @@ -97,6 +97,7 @@ OVN To-do List * ovn-controller Incremental processing * Implement I-P for datapath groups. + * Implement I-P for route exchange relevant ports. * ovn-northd parallel logical flow processing diff --git a/controller/automake.mk b/controller/automake.mk index bb0bf2d33..a6a2c517a 100644 --- a/controller/automake.mk +++ b/controller/automake.mk @@ -51,7 +51,9 @@ controller_ovn_controller_SOURCES = \ controller/ct-zone.h \ controller/ct-zone.c \ controller/ovn-dns.c \ - controller/ovn-dns.h + controller/ovn-dns.h \ + controller/route.h \ + controller/route.c controller_ovn_controller_LDADD = lib/libovn.la $(OVS_LIBDIR)/libopenvswitch.la man_MANS += controller/ovn-controller.8 diff --git a/controller/local_data.c b/controller/local_data.c index 69a1b775f..4aee39d6b 100644 --- a/controller/local_data.c +++ b/controller/local_data.c @@ -414,14 +414,19 @@ tracked_datapath_lport_add(const struct sbrec_port_binding *pb, } void -tracked_datapaths_destroy(struct hmap *tracked_datapaths) +tracked_datapaths_clear(struct hmap *tracked_datapaths) { struct tracked_datapath *t_dp; HMAP_FOR_EACH_POP (t_dp, node, tracked_datapaths) { shash_destroy_free_data(&t_dp->lports); free(t_dp); } +} +void +tracked_datapaths_destroy(struct hmap *tracked_datapaths) +{ + tracked_datapaths_clear(tracked_datapaths); hmap_destroy(tracked_datapaths); } diff --git a/controller/local_data.h b/controller/local_data.h index ab8e789a5..1d60dada8 100644 --- a/controller/local_data.h +++ b/controller/local_data.h @@ -131,6 +131,7 @@ struct tracked_datapath *tracked_datapath_find( void tracked_datapath_lport_add(const struct sbrec_port_binding *, enum en_tracked_resource_type, struct hmap *tracked_datapaths); +void tracked_datapaths_clear(struct hmap *tracked_datapaths); void tracked_datapaths_destroy(struct hmap *tracked_datapaths); /* Maps from a chassis to the OpenFlow port number of the tunnel that can be diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c index 854e54854..6ffa283a7 100644 --- a/controller/ovn-controller.c +++ b/controller/ovn-controller.c @@ -88,6 +88,7 @@ #include "lib/dns-resolve.h" #include "ct-zone.h" #include "ovn-dns.h" +#include "route.h" VLOG_DEFINE_THIS_MODULE(main); @@ -247,6 +248,7 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl, struct ovsdb_idl_condition igmp = OVSDB_IDL_CONDITION_INIT(&igmp); struct ovsdb_idl_condition chprv = OVSDB_IDL_CONDITION_INIT(&chprv); struct ovsdb_idl_condition tv = OVSDB_IDL_CONDITION_INIT(&tv); + struct ovsdb_idl_condition ar = OVSDB_IDL_CONDITION_INIT(&ar); /* Always monitor all logical datapath groups. Otherwise, DPG updates may * be received *after* the lflows using it are seen by ovn-controller. @@ -254,6 +256,11 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl, * avoid the unnecessarily extra wake-ups of ovn-controller. */ ovsdb_idl_condition_add_clause_true(&ldpg); + /* Always monitor advertised routes. Otherwise, once we claim a port on + * startup we do not yet know the routes to advertise and might wrongly + * delete already installed ones. */ + ovsdb_idl_condition_add_clause_true(&ar); + if (monitor_all) { ovsdb_idl_condition_add_clause_true(&pb); ovsdb_idl_condition_add_clause_true(&lf); @@ -381,6 +388,7 @@ out:; sb_table_set_req_mon_condition(ovnsb_idl, igmp_group, &igmp), sb_table_set_req_mon_condition(ovnsb_idl, chassis_private, &chprv), sb_table_set_opt_mon_condition(ovnsb_idl, chassis_template_var, &tv), + sb_table_set_opt_mon_condition(ovnsb_idl, advertised_route, &ar), }; unsigned int expected_cond_seqno = 0; @@ -400,6 +408,7 @@ out:; ovsdb_idl_condition_destroy(&igmp); ovsdb_idl_condition_destroy(&chprv); ovsdb_idl_condition_destroy(&tv); + ovsdb_idl_condition_destroy(&ar); return expected_cond_seqno; } @@ -865,7 +874,8 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl) SB_NODE(fdb, "fdb") \ SB_NODE(meter, "meter") \ SB_NODE(static_mac_binding, "static_mac_binding") \ - SB_NODE(chassis_template_var, "chassis_template_var") + SB_NODE(chassis_template_var, "chassis_template_var") \ + SB_NODE(advertised_route, "advertised_route") enum sb_engine_node { #define SB_NODE(NAME, NAME_STR) SB_##NAME, @@ -4812,6 +4822,172 @@ pflow_lflow_output_sb_chassis_handler(struct engine_node *node, return true; } +struct ed_type_route { + struct ovsdb_idl *ovnsb_idl; + /* Contains struct tracked_datapath entries for local datapaths subject to + * route exchange. */ + struct hmap tracked_route_datapaths; + /* Contains struct advertise_datapath_entry */ + struct hmap announce_routes; +}; + +static void +en_route_run(struct engine_node *node, void *data) +{ + struct ed_type_route *re_data = data; + route_cleanup(&re_data->announce_routes); + + 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); + + struct route_ctx_in r_ctx_in = { + .ovnsb_idl = re_data->ovnsb_idl, + .sbrec_port_binding_by_name = sbrec_port_binding_by_name, + .chassis = chassis, + .active_tunnels = &rt_data->active_tunnels, + .local_datapaths = &rt_data->local_datapaths, + .local_lports = &rt_data->local_lports, + }; + + struct route_ctx_out r_ctx_out = { + .tracked_re_datapaths = &re_data->tracked_route_datapaths, + .announce_routes = &re_data->announce_routes, + }; + + tracked_datapaths_clear(r_ctx_out.tracked_re_datapaths); + + route_run(&r_ctx_in, &r_ctx_out); + + engine_set_node_state(node, EN_UPDATED); +} + + +static void * +en_route_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *arg) +{ + struct ed_type_route *data = xzalloc(sizeof *data); + + hmap_init(&data->tracked_route_datapaths); + hmap_init(&data->announce_routes); + data->ovnsb_idl = arg->sb_idl; + + return data; +} + +static void +en_route_cleanup(void *data) +{ + struct ed_type_route *re_data = data; + + tracked_datapaths_destroy(&re_data->tracked_route_datapaths); + route_cleanup(&re_data->announce_routes); + hmap_destroy(&re_data->announce_routes); +} + +static bool +route_runtime_data_handler(struct engine_node *node, void *data) +{ + struct ed_type_route *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_route_datapaths, t_dp->dp); + + if (re_t_dp) { + /* XXX: 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)) { + /* XXX: Until we get I-P support for route exchange we need to + * request recompute. */ + return false; + } + } + } + + return true; +} + +static bool +route_sb_port_binding_data_handler(struct engine_node *node, void *data) +{ + struct ed_type_route *re_data = data; + const struct sbrec_port_binding_table *pb_table = + EN_OVSDB_GET(engine_get_input("SB_port_binding", node)); + + const struct sbrec_port_binding *sbrec_pb; + SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (sbrec_pb, pb_table) { + struct tracked_datapath *re_t_dp = + tracked_datapath_find(&re_data->tracked_route_datapaths, + sbrec_pb->datapath); + if (re_t_dp) { + /* XXX: Until we get I-P support for route exchange we need to + * request recompute. */ + return false; + } + + if (route_exchange_relevant_port(sbrec_pb)) { + /* XXX: Until we get I-P support for route exchange we need to + * request recompute. */ + return false; + } + + } + return true; +} + +static bool +route_sb_advertised_route_data_handler(struct engine_node *node, void *data) +{ + struct ed_type_route *re_data = data; + const struct sbrec_advertised_route_table *advertised_route_table = + EN_OVSDB_GET(engine_get_input("SB_advertised_route", node)); + + const struct sbrec_advertised_route *sbrec_route; + SBREC_ADVERTISED_ROUTE_TABLE_FOR_EACH_TRACKED (sbrec_route, + advertised_route_table) { + struct tracked_datapath *re_t_dp = + tracked_datapath_find(&re_data->tracked_route_datapaths, + sbrec_route->datapath); + if (re_t_dp) { + /* XXX: Until we get I-P support for route exchange we need to + * request recompute. */ + return false; + } + } + return true; +} + /* Returns false if the northd internal version stored in SB_Global * and ovn-controller internal version don't match. */ @@ -5103,6 +5279,7 @@ main(int argc, char *argv[]) ENGINE_NODE(mac_cache, "mac_cache"); ENGINE_NODE(bfd_chassis, "bfd_chassis"); ENGINE_NODE(dns_cache, "dns_cache"); + ENGINE_NODE(route, "route"); #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR); SB_NODES @@ -5125,6 +5302,15 @@ main(int argc, char *argv[]) engine_add_input(&en_lb_data, &en_runtime_data, lb_data_runtime_data_handler); + engine_add_input(&en_route, &en_ovs_open_vswitch, NULL); + engine_add_input(&en_route, &en_sb_chassis, NULL); + engine_add_input(&en_route, &en_sb_port_binding, + route_sb_port_binding_data_handler); + engine_add_input(&en_route, &en_runtime_data, + route_runtime_data_handler); + engine_add_input(&en_route, &en_sb_advertised_route, + route_sb_advertised_route_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, @@ -5310,6 +5496,9 @@ main(int argc, char *argv[]) controller_output_mac_cache_handler); engine_add_input(&en_controller_output, &en_bfd_chassis, controller_output_bfd_chassis_handler); + /* This is just temporary until the route output is actually used. */ + engine_add_input(&en_controller_output, &en_route, + controller_output_bfd_chassis_handler); struct engine_arg engine_arg = { .sb_idl = ovnsb_idl_loop.idl, diff --git a/controller/route.c b/controller/route.c new file mode 100644 index 000000000..49e76ee73 --- /dev/null +++ b/controller/route.c @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2024, Canonical, Ltd. + * Copyright (c) 2024, STACKIT GmbH & Co. KG + * + * 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 <net/if.h> + +#include "openvswitch/hmap.h" +#include "openvswitch/vlog.h" + +#include "lib/ovn-sb-idl.h" + +#include "binding.h" +#include "ha-chassis.h" +#include "local_data.h" +#include "route.h" + + +VLOG_DEFINE_THIS_MODULE(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 + +bool +route_exchange_relevant_port(const struct sbrec_port_binding *pb) +{ + return pb && smap_get_bool(&pb->options, "dynamic-routing", false); +} + +uint32_t +advertise_route_hash(const struct in6_addr *dst, unsigned int plen) +{ + uint32_t hash = hash_bytes(dst->s6_addr, 16, 0); + return hash_int(plen, hash); +} + +static const struct sbrec_port_binding* +find_route_exchange_pb(struct ovsdb_idl_index *sbrec_port_binding_by_name, + const struct sbrec_chassis *chassis, + const struct sset *active_tunnels, + const struct sbrec_port_binding *pb) +{ + if (!pb) { + return NULL; + } + if (route_exchange_relevant_port(pb)) { + return pb; + } + const char *crp = smap_get(&pb->options, "chassis-redirect-port"); + if (!crp) { + return NULL; + } + if (!lport_is_chassis_resident(sbrec_port_binding_by_name, chassis, + active_tunnels, crp)) { + return NULL; + } + const struct sbrec_port_binding *crpbp = lport_lookup_by_name( + sbrec_port_binding_by_name, crp); + if (route_exchange_relevant_port(crpbp)) { + return crpbp; + } + return NULL; +} + +static void +advertise_datapath_cleanup(struct advertise_datapath_entry *ad) +{ + struct advertise_route_entry *ar; + HMAP_FOR_EACH_SAFE (ar, node, &ad->routes) { + hmap_remove(&ad->routes, &ar->node); + free(ar); + } + hmap_destroy(&ad->routes); + sset_destroy(&ad->bound_ports); + free(ad); +} + +static struct advertise_datapath_entry* +advertise_datapath_find(const struct hmap *datapaths, + const struct sbrec_datapath_binding *db) +{ + struct advertise_datapath_entry *ade; + HMAP_FOR_EACH_WITH_HASH (ade, node, db->tunnel_key, datapaths) { + if (ade->db == db) { + return ade; + } + } + return NULL; +} + +void +route_run(struct route_ctx_in *r_ctx_in, + struct route_ctx_out *r_ctx_out) +{ + struct advertise_datapath_entry *ad; + 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; + } + + ad = xzalloc(sizeof(*ad)); + ad->db = ld->datapath; + hmap_init(&ad->routes); + sset_init(&ad->bound_ports); + + /* This is a LR datapath, find LRPs with route exchange options + * that are bound locally. */ + for (size_t i = 0; i < ld->n_peer_ports; i++) { + const struct sbrec_port_binding *local_peer + = ld->peer_ports[i].local; + const struct sbrec_port_binding *repb = find_route_exchange_pb( + r_ctx_in->sbrec_port_binding_by_name, + r_ctx_in->chassis, + r_ctx_in->active_tunnels, + local_peer); + if (!repb) { + continue; + } + + ad->maintain_vrf |= smap_get_bool( + &repb->options, "dynamic-routing-maintain-vrf", false); + sset_add(&ad->bound_ports, local_peer->logical_port); + } + + if (sset_is_empty(&ad->bound_ports)) { + advertise_datapath_cleanup(ad); + continue; + } + tracked_datapath_add(ld->datapath, TRACKED_RESOURCE_NEW, + r_ctx_out->tracked_re_datapaths); + + /* 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 cleanup; + } + + hmap_insert(r_ctx_out->announce_routes, &ad->node, ad->db->tunnel_key); + continue; + +cleanup: + advertise_datapath_cleanup(ad); + } + + const struct sbrec_advertised_route *route; + SBREC_ADVERTISED_ROUTE_FOR_EACH (route, r_ctx_in->ovnsb_idl) { + struct in6_addr prefix; + unsigned int plen; + if (!ip46_parse_cidr(route->ip_prefix, &prefix, &plen)) { + VLOG_WARN_RL(&rl, "bad 'ip_prefix' %s in route " + UUID_FMT, route->ip_prefix, + UUID_ARGS(&route->header_.uuid)); + continue; + } + + ad = advertise_datapath_find(r_ctx_out->announce_routes, + route->datapath); + if (!ad) { + continue; + } + + struct advertise_route_entry *ar = xzalloc(sizeof(*ar)); + hmap_insert(&ad->routes, &ar->node, + advertise_route_hash(&prefix, plen)); + ar->addr = prefix; + ar->plen = plen; + } +} + +void +route_cleanup(struct hmap *announce_routes) +{ + struct advertise_datapath_entry *ad; + HMAP_FOR_EACH_SAFE (ad, node, announce_routes) { + hmap_remove(announce_routes, &ad->node); + advertise_datapath_cleanup(ad); + } +} diff --git a/controller/route.h b/controller/route.h new file mode 100644 index 000000000..6fc5963a5 --- /dev/null +++ b/controller/route.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024, Canonical, Ltd. + * Copyright (c) 2024, STACKIT GmbH & Co. KG + * + * 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_H +#define ROUTE_H 1 + +#include <stdbool.h> +#include <netinet/in.h> +#include "openvswitch/hmap.h" +#include "sset.h" + +struct hmap; +struct ovsdb_idl_index; +struct sbrec_chassis; +struct sbrec_port_binding; + +struct route_ctx_in { + struct ovsdb_idl *ovnsb_idl; + struct ovsdb_idl_index *sbrec_port_binding_by_name; + const struct sbrec_chassis *chassis; + const struct sset *active_tunnels; + const struct hmap *local_datapaths; + const struct sset *local_lports; +}; + +struct route_ctx_out { + struct hmap *tracked_re_datapaths; + /* Contains struct advertise_datapath_entry */ + struct hmap *announce_routes; +}; + +struct advertise_datapath_entry { + struct hmap_node node; + const struct sbrec_datapath_binding *db; + bool maintain_vrf; + struct hmap routes; + + /* The name of the port bindings locally bound for this datapath and + * running route exchange logic. */ + struct sset bound_ports; +}; + +struct advertise_route_entry { + struct hmap_node node; + struct in6_addr addr; + unsigned int plen; +}; + +bool route_exchange_relevant_port(const struct sbrec_port_binding *pb); +uint32_t advertise_route_hash(const struct in6_addr *dst, unsigned int plen); +void route_run(struct route_ctx_in *, struct route_ctx_out *); +void route_cleanup(struct hmap *announce_routes); + +#endif /* ROUTE_H */ diff --git a/tests/automake.mk b/tests/automake.mk index 3899c9e80..9244532fa 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -305,6 +305,7 @@ tests_ovstest_LDADD = $(OVS_LIBDIR)/daemon.lo \ controller/ovsport.$(OBJEXT) \ controller/patch.$(OBJEXT) \ controller/vif-plug.$(OBJEXT) \ + controller/route.$(OBJEXT) \ northd/ipam.$(OBJEXT) # Python tests. -- 2.47.1 _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev