On 2/18/25 3:02 PM, Felix Huettner via dev wrote:
> This adds initial incremental support for the new group_ecmp_route
> engine. It only supports incremental processing for learned routes for
> now as other route types miss the incremental handling up the chain.
>
> Signed-off-by: Felix Huettner <[email protected]>
> ---
Hi Felix,
> northd/en-group-ecmp-route.c | 271 ++++++++++++++++++++++++++++++++---
> northd/en-group-ecmp-route.h | 19 +++
> northd/inc-proc-northd.c | 3 +-
> tests/ovn-northd.at | 4 +-
> 4 files changed, 271 insertions(+), 26 deletions(-)
>
> diff --git a/northd/en-group-ecmp-route.c b/northd/en-group-ecmp-route.c
> index e8a882004..d930905a5 100644
> --- a/northd/en-group-ecmp-route.c
> +++ b/northd/en-group-ecmp-route.c
> @@ -17,6 +17,7 @@
> #include <config.h>
> #include <stdbool.h>
>
> +#include "openvswitch/list.h"
This doesn't seem to be needed.
> #include "openvswitch/vlog.h"
> #include "stopwatch.h"
> #include "northd.h"
> @@ -28,19 +29,29 @@
>
> VLOG_DEFINE_THIS_MODULE(en_group_ecmp_route);
>
> +static void
> +ecmp_groups_node_free(struct ecmp_groups_node *eg)
> +{
> + if (!eg) {
> + return;
Nit: indentation.
> + }
> +
> + struct ecmp_route_list_node *er;
> + LIST_FOR_EACH_SAFE (er, list_node, &eg->route_list) {
> + ovs_list_remove(&er->list_node);
> + free(er);
> + }
> + sset_destroy(&eg->selection_fields);
> + free(eg);
> +}
> +
> static void
> ecmp_groups_destroy(struct hmap *ecmp_groups)
> {
> struct ecmp_groups_node *eg;
> HMAP_FOR_EACH_SAFE (eg, hmap_node, ecmp_groups) {
> - struct ecmp_route_list_node *er;
> - LIST_FOR_EACH_SAFE (er, list_node, &eg->route_list) {
> - ovs_list_remove(&er->list_node);
> - free(er);
> - }
> hmap_remove(ecmp_groups, &eg->hmap_node);
> - sset_destroy(&eg->selection_fields);
> - free(eg);
> + ecmp_groups_node_free(eg);
> }
> hmap_destroy(ecmp_groups);
> }
> @@ -56,21 +67,47 @@ unique_routes_destroy(struct hmap *unique_routes)
> hmap_destroy(unique_routes);
> }
>
> +static void
> +group_node_free(struct group_ecmp_route_node *n)
> +{
> + if (!n) {
> + return;
> + }
> +
> + unique_routes_destroy(&n->unique_routes);
> + ecmp_groups_destroy(&n->ecmp_groups);
> + free(n);
> +}
> +
> +static void
> +group_ecmp_route_clear_tracked(struct group_ecmp_route_data *data)
> +{
> + data->tracked = false;
> + hmapx_clear(&data->trk_data.crupdated_routes);
> +
> + struct hmapx_node *hmapx_node;
> + HMAPX_FOR_EACH (hmapx_node, &data->trk_data.deleted_routes) {
> + group_node_free(hmapx_node->data);
> + }
> + hmapx_clear(&data->trk_data.deleted_routes);
> +}
> +
> static void
> group_ecmp_route_clear(struct group_ecmp_route_data *data)
> {
> struct group_ecmp_route_node *n;
> HMAP_FOR_EACH_POP (n, hmap_node, &data->routes) {
> - unique_routes_destroy(&n->unique_routes);
> - ecmp_groups_destroy(&n->ecmp_groups);
> - free(n);
> + group_node_free(n);
> }
> + group_ecmp_route_clear_tracked(data);
> }
>
> static void
> group_ecmp_route_init(struct group_ecmp_route_data *data)
> {
> hmap_init(&data->routes);
> + hmapx_init(&data->trk_data.crupdated_routes);
> + hmapx_init(&data->trk_data.deleted_routes);
> }
>
> void *en_group_ecmp_route_init(struct engine_node *node OVS_UNUSED,
> @@ -86,11 +123,14 @@ void en_group_ecmp_route_cleanup(void *_data)
> struct group_ecmp_route_data *data = _data;
> group_ecmp_route_clear(data);
> hmap_destroy(&data->routes);
> + hmapx_destroy(&data->trk_data.crupdated_routes);
> + hmapx_destroy(&data->trk_data.deleted_routes);
> }
>
> void
> en_group_ecmp_route_clear_tracked_data(void *data OVS_UNUSED)
> {
> + group_ecmp_route_clear_tracked(data);
> }
>
> struct group_ecmp_route_node *
> @@ -106,18 +146,12 @@ group_ecmp_route_lookup(const struct
> group_ecmp_route_data *data,
> }
> return NULL;
> }
> -
> static struct group_ecmp_route_node *
> group_ecmp_route_add(struct group_ecmp_route_data *data,
> const struct ovn_datapath *od)
> {
> - struct group_ecmp_route_node *n = group_ecmp_route_lookup(data, od);
> - if (n) {
> - return n;
> - }
> -
> size_t hash = uuid_hash(&od->key);
> - n = xmalloc(sizeof *n);
> + struct group_ecmp_route_node *n = xmalloc(sizeof *n);
> n->od = od;
> hmap_init(&n->ecmp_groups);
> hmap_init(&n->unique_routes);
> @@ -125,6 +159,18 @@ group_ecmp_route_add(struct group_ecmp_route_data *data,
> return n;
> }
>
> +static struct group_ecmp_route_node *
> +group_ecmp_route_lookup_or_add(struct group_ecmp_route_data *data,
> + const struct ovn_datapath *od)
> +{
> + struct group_ecmp_route_node *n = group_ecmp_route_lookup(data, od);
> + if (n) {
> + return n;
> + }
> +
> + return group_ecmp_route_add(data, od);
> +}
> +
> static void
> unique_routes_add(struct group_ecmp_route_node *gn,
> const struct parsed_route *route)
> @@ -180,6 +226,38 @@ ecmp_groups_add_route(struct ecmp_groups_node *group,
> ovs_list_insert(&group->route_list, &er->list_node);
> }
>
> +/* Removes a route from an ecmp group. If the ecmp group should persist
> + * afterwards you must call ecmp_groups_update_ids before any further
> + * insertions. */
> +static const struct parsed_route *
> +ecmp_groups_remove_route(struct ecmp_groups_node *group,
> + const struct parsed_route *pr)
> +{
> + struct ecmp_route_list_node *er;
> + LIST_FOR_EACH (er, list_node, &group->route_list) {
> + if (er->route == pr) {
> + const struct parsed_route *found_route = er->route;
> + ovs_list_remove(&er->list_node);
> + free(er);
> + return found_route;
> + }
> + }
> +
> + return NULL;
> +}
> +
> +static void
> +ecmp_group_update_ids(struct ecmp_groups_node *group)
> +{
> + struct ecmp_route_list_node *er;
> + size_t i = 0;
> + LIST_FOR_EACH (er, list_node, &group->route_list) {
> + er->id = i;
> + i++;
> + }
> + group->route_count = i;
> +}
> +
> static struct ecmp_groups_node *
> ecmp_groups_add(struct group_ecmp_route_node *gn,
> const struct parsed_route *route)
> @@ -223,11 +301,21 @@ ecmp_groups_find(struct group_ecmp_route_node *gn,
> return NULL;
> }
>
> -static void
> -add_route(struct group_ecmp_route_data *data, const struct parsed_route *pr)
> +static bool
> +ecmp_group_any_symmetric_reply(struct ecmp_groups_node *eg)
We never call this for any groups that have more than one entry. But
OK. I'd renable it to ecmp_group_has_symmetric_reply() though.
> {
> - struct group_ecmp_route_node *gn = group_ecmp_route_add(data, pr->od);
> + struct ecmp_route_list_node *er;
> + LIST_FOR_EACH (er, list_node, &eg->route_list) {
> + if (er->route->ecmp_symmetric_reply) {
> + return true;
> + }
> + }
> + return false;
> +}
>
> +static void
> +add_route(struct group_ecmp_route_node *gn, const struct parsed_route *pr)
> +{
> if (pr->source == ROUTE_SOURCE_CONNECTED) {
> unique_routes_add(gn, pr);
> return;
> @@ -260,17 +348,21 @@ group_ecmp_route(struct group_ecmp_route_data *data,
> const struct routes_data *routes_data,
> const struct learned_route_sync_data *learned_route_data)
> {
> + struct group_ecmp_route_node *gn;
> const struct parsed_route *pr;
> HMAP_FOR_EACH (pr, key_node, &routes_data->parsed_routes) {
> - add_route(data, pr);
> + gn = group_ecmp_route_lookup_or_add(data, pr->od);
> + add_route(gn, pr);
> }
>
> HMAP_FOR_EACH (pr, key_node, &learned_route_data->parsed_routes) {
> - add_route(data, pr);
> + gn = group_ecmp_route_lookup_or_add(data, pr->od);
> + add_route(gn, pr);
> }
> }
>
> -void en_group_ecmp_route_run(struct engine_node *node, void *_data)
> +void
> +en_group_ecmp_route_run(struct engine_node *node, void *_data)
> {
> struct group_ecmp_route_data *data = _data;
> group_ecmp_route_clear(data);
> @@ -287,3 +379,136 @@ void en_group_ecmp_route_run(struct engine_node *node,
> void *_data)
> stopwatch_stop(GROUP_ECMP_ROUTE_RUN_STOPWATCH_NAME, time_msec());
> engine_set_node_state(node, EN_UPDATED);
> }
> +
> +static void
> +handle_added_route(struct group_ecmp_route_data *data,
> + const struct parsed_route *pr,
> + struct hmapx *updated_routes)
> +{
> + struct group_ecmp_route_node *node = group_ecmp_route_lookup(data,
> pr->od);
> +
> + if (!node) {
> + node = group_ecmp_route_add(data, pr->od);
> + }
> +
> + add_route(node, pr);
> +
Nit: I'd remove this empty line, potentially I'd also swap these two
statements but that's not a must.
> + hmapx_add(updated_routes, node);
> +}
> +
> +static bool
> +handle_deleted_route(struct group_ecmp_route_data *data,
> + const struct parsed_route *pr,
> + struct hmapx *updated_routes)
> +{
> + struct group_ecmp_route_node *node = group_ecmp_route_lookup(data,
> pr->od);
> + if (!node) {
> + /* This should not happen since we should know the datapath. */
> + return false;
> + }
> +
> + const struct parsed_route *existing = unique_routes_remove(node, pr);
> + if (!existing) {
> + /* The route must be part of an ecmp group. */
> + if (pr->source == ROUTE_SOURCE_CONNECTED) {
> + /* Connected routes are never part of an ecmp group.
> + * We should recompute. */
> + return false;
> + }
> +
> + struct ecmp_groups_node *eg = ecmp_groups_find(node, pr);
> + if (!eg) {
> + /* We neither found the route as unique nor as ecmp group.
> + * We should recompute. */
> + return false;
> + }
> +
> + size_t ecmp_members = ovs_list_size(&eg->route_list);
> + if (ecmp_members == 1) {
> + /* The route is the only ecmp member, we remove the whole group.
> */
> + hmap_remove(&node->ecmp_groups, &eg->hmap_node);
> + ecmp_groups_node_free(eg);
> + } else if (ecmp_members == 2) {
> + /* There is only one other member. If it does not have
> + * ecmp_symmetric_reply configured, we convert it to a
> + * unique route. Otherwise it stays an ecmp group with just one
> + * member. */
> + ecmp_groups_remove_route(eg, pr);
> + if (ecmp_group_any_symmetric_reply(eg)) {
> + ecmp_group_update_ids(eg);
> + } else {
> + struct ecmp_route_list_node *er = CONTAINER_OF(
> + ovs_list_front(&eg->route_list),
> + struct ecmp_route_list_node, list_node);
> + unique_routes_add(node, er->route);
> + hmap_remove(&node->ecmp_groups, &eg->hmap_node);
> + ecmp_groups_node_free(eg);
> + }
> + } else {
> + /* We can just remove the member from the group. We need to
> update
> + * the indices of all routes so that future insertions directly
> + * have a new index. */
> + ecmp_groups_remove_route(eg, pr);
> + ecmp_group_update_ids(eg);
> + }
> +
> + }
> +
> + hmapx_add(updated_routes, node);
> + return true;
> +}
> +
> +bool
> +group_ecmp_route_learned_route_change_handler(struct engine_node *eng_node,
> + void *_data)
> +{
> + struct group_ecmp_route_data *data = _data;
> + struct learned_route_sync_data *learned_route_data
> + = engine_get_input_data("learned_route_sync", eng_node);
> +
> + if (learned_route_data->trk_data.type == LEARNED_ROUTES_TRACKED_NONE) {
> + data->tracked = false;
> + return false;
> + }
> +
> + data->tracked = true;
> +
> + struct hmapx updated_routes = HMAPX_INITIALIZER(&updated_routes);
> +
> + const struct hmapx_node *hmapx_node;
> + const struct parsed_route *pr;
> + HMAPX_FOR_EACH (hmapx_node,
> + &learned_route_data->trk_data.trk_created_parsed_route) {
> + pr = hmapx_node->data;
> + handle_added_route(data, pr, &updated_routes);
> + }
> +
> + HMAPX_FOR_EACH (hmapx_node,
> + &learned_route_data->trk_data.trk_deleted_parsed_route) {
> + pr = hmapx_node->data;
> + if (!handle_deleted_route(data, pr, &updated_routes)) {
> + return false;
> + }
> + }
Is there a reason to not handle deletions first? It might be safer to
do so, just in case.
> +
> + /* Now we need to group the route_nodes based on if there are any routes
> + * left. */
> + HMAPX_FOR_EACH (hmapx_node, &updated_routes) {
> + struct group_ecmp_route_node *node = hmapx_node->data;
> + if (hmap_is_empty(&node->unique_routes) &&
> + hmap_is_empty(&node->ecmp_groups)) {
> + hmapx_add(&data->trk_data.deleted_routes, node);
> + hmap_remove(&data->routes, &node->hmap_node);
> + } else {
> + hmapx_add(&data->trk_data.crupdated_routes, node);
> + }
> + }
> +
> + hmapx_destroy(&updated_routes);
> +
> + if (!(hmapx_is_empty(&data->trk_data.crupdated_routes) &&
> + hmapx_is_empty(&data->trk_data.deleted_routes))) {
> + engine_set_node_state(eng_node, EN_UPDATED);
> + }
> + return true;
> +}
> diff --git a/northd/en-group-ecmp-route.h b/northd/en-group-ecmp-route.h
> index c84bb11ee..0da8fa88c 100644
> --- a/northd/en-group-ecmp-route.h
> +++ b/northd/en-group-ecmp-route.h
> @@ -61,10 +61,26 @@ struct group_ecmp_route_node {
> struct hmap unique_routes;
> };
>
> +struct group_ecmp_route_tracked_data {
> + /* Contains references to group_ecmp_route_node. Each of the referenced
> + * datapath contains any route. */
Maybe "Each of the referenced datapaths contains at least one route." is
more clear?
> + struct hmapx crupdated_routes;
Same as on the previous patch, this actually stores pointers to per
datapath route maps. The 'crupdated' name is confusing IMO. Maybe
'crupdated_datapath_routes'?
> +
> + /* Contains references to group_ecmp_route_node. Each of the referenced
> + * datapath previously had some routes. The datapath now no longer
> + * contains any route.*/
> + struct hmapx deleted_routes;
Same comment about the name here. Maybe 'deleted_datapath_routes'?
> +};
> +
> struct group_ecmp_route_data {
> /* Contains struct group_ecmp_route_node.
> * Each entry represents the routes of a single datapath. */
> struct hmap routes;
> +
> + /* tracked is set to true if there is information available for
> incremental
Nit: because we're referring to an actual field name this should be:
/* 'tracked' is set to ..
> + * processing. If true then trk_data is valid. */
> + bool tracked;
> + struct group_ecmp_route_tracked_data trk_data;
> };
>
> void *en_group_ecmp_route_init(struct engine_node *, struct engine_arg *);
> @@ -72,6 +88,9 @@ void en_group_ecmp_route_cleanup(void *data);
> void en_group_ecmp_route_clear_tracked_data(void *data);
> void en_group_ecmp_route_run(struct engine_node *, void *data);
>
> +bool group_ecmp_route_learned_route_change_handler(struct engine_node *,
> + void *data);
> +
> struct group_ecmp_route_node * group_ecmp_route_lookup(
> const struct group_ecmp_route_data *data,
> const struct ovn_datapath *od);
> diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
> index 712aebaa1..147f54da0 100644
> --- a/northd/inc-proc-northd.c
> +++ b/northd/inc-proc-northd.c
> @@ -315,7 +315,8 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
> learned_route_sync_northd_change_handler);
>
> engine_add_input(&en_group_ecmp_route, &en_routes, NULL);
> - engine_add_input(&en_group_ecmp_route, &en_learned_route_sync, NULL);
> + engine_add_input(&en_group_ecmp_route, &en_learned_route_sync,
> + group_ecmp_route_learned_route_change_handler);
>
> engine_add_input(&en_sync_meters, &en_nb_acl, NULL);
> engine_add_input(&en_sync_meters, &en_nb_meter, NULL);
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index 8d77b3534..b70deade3 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -15642,7 +15642,7 @@ check_engine_compute northd unchanged
> check_engine_compute routes unchanged
> check_engine_compute advertised_route_sync unchanged
> check_engine_compute learned_route_sync incremental
> -check_engine_compute group_ecmp_route recompute
> +check_engine_compute group_ecmp_route incremental
> check_engine_compute lflow recompute
> CHECK_NO_CHANGE_AFTER_RECOMPUTE
>
> @@ -15653,7 +15653,7 @@ check_engine_compute northd unchanged
> check_engine_compute routes unchanged
> check_engine_compute advertised_route_sync unchanged
> check_engine_compute learned_route_sync incremental
> -check_engine_compute group_ecmp_route recompute
> +check_engine_compute group_ecmp_route incremental
> check_engine_compute lflow recompute
> CHECK_NO_CHANGE_AFTER_RECOMPUTE
>
Regards,
Dumitru
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev