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 <felix.huettner@stackit.cloud>
---
v3->v4: Fix memory leak
v2->v3:
  * Addressed review comments.

 northd/en-group-ecmp-route.c | 270 ++++++++++++++++++++++++++++++++---
 northd/en-group-ecmp-route.h |  19 +++
 northd/inc-proc-northd.c     |   3 +-
 tests/ovn-northd.at          |   4 +-
 4 files changed, 271 insertions(+), 25 deletions(-)

diff --git a/northd/en-group-ecmp-route.c b/northd/en-group-ecmp-route.c
index 570ae062c..fa656c656 100644
--- a/northd/en-group-ecmp-route.c
+++ b/northd/en-group-ecmp-route.c
@@ -31,38 +31,74 @@ VLOG_DEFINE_THIS_MODULE(en_group_ecmp_route);
 static void
 unique_routes_destroy(struct hmap *unique_routes);
 
+static void
+ecmp_groups_node_free(struct ecmp_groups_node *eg)
+{
+    if (!eg) {
+        return;
+    }
+
+    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);
 }
 
+static void
+group_node_free(struct group_ecmp_datapath *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_datapath_routes);
+
+    struct hmapx_node *hmapx_node;
+    HMAPX_FOR_EACH (hmapx_node, &data->trk_data.deleted_datapath_routes) {
+        group_node_free(hmapx_node->data);
+    }
+    hmapx_clear(&data->trk_data.deleted_datapath_routes);
+}
+
 static void
 group_ecmp_route_clear(struct group_ecmp_route_data *data)
 {
     struct group_ecmp_datapath *n;
     HMAP_FOR_EACH_POP (n, hmap_node, &data->datapaths) {
-        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->datapaths);
+    hmapx_init(&data->trk_data.crupdated_datapath_routes);
+    hmapx_init(&data->trk_data.deleted_datapath_routes);
 }
 
 void *en_group_ecmp_route_init(struct engine_node *node OVS_UNUSED,
@@ -78,11 +114,14 @@ void en_group_ecmp_route_cleanup(void *_data)
     struct group_ecmp_route_data *data = _data;
     group_ecmp_route_clear(data);
     hmap_destroy(&data->datapaths);
+    hmapx_destroy(&data->trk_data.crupdated_datapath_routes);
+    hmapx_destroy(&data->trk_data.deleted_datapath_routes);
 }
 
 void
 en_group_ecmp_route_clear_tracked_data(void *data OVS_UNUSED)
 {
+    group_ecmp_route_clear_tracked(data);
 }
 
 struct group_ecmp_datapath *
@@ -103,13 +142,8 @@ static struct group_ecmp_datapath *
 group_ecmp_datapath_add(struct group_ecmp_route_data *data,
                         const struct ovn_datapath *od)
 {
-    struct group_ecmp_datapath *n = group_ecmp_datapath_lookup(data, od);
-    if (n) {
-        return n;
-    }
-
     size_t hash = uuid_hash(&od->key);
-    n = xmalloc(sizeof *n);
+    struct group_ecmp_datapath *n = xmalloc(sizeof *n);
     n->od = od;
     hmap_init(&n->ecmp_groups);
     hmap_init(&n->unique_routes);
@@ -117,6 +151,18 @@ group_ecmp_datapath_add(struct group_ecmp_route_data *data,
     return n;
 }
 
+static struct group_ecmp_datapath *
+group_ecmp_datapath_lookup_or_add(struct group_ecmp_route_data *data,
+                                  const struct ovn_datapath *od)
+{
+    struct group_ecmp_datapath *n = group_ecmp_datapath_lookup(data, od);
+    if (n) {
+        return n;
+    }
+
+    return group_ecmp_datapath_add(data, od);
+}
+
 static void
 unique_routes_add(struct group_ecmp_datapath *gn,
                   const struct parsed_route *route)
@@ -183,6 +229,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_datapath *gn,
                 const struct parsed_route *route)
@@ -226,11 +304,21 @@ ecmp_groups_find(struct group_ecmp_datapath *gn,
     return NULL;
 }
 
-static void
-add_route(struct group_ecmp_route_data *data, const struct parsed_route *pr)
+static bool
+ecmp_group_has_symmetric_reply(struct ecmp_groups_node *eg)
 {
-    struct group_ecmp_datapath *gn = group_ecmp_datapath_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_datapath *gn, const struct parsed_route *pr)
+{
     if (pr->source == ROUTE_SOURCE_CONNECTED) {
         unique_routes_add(gn, pr);
         return;
@@ -263,17 +351,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_datapath *gn;
     const struct parsed_route *pr;
     HMAP_FOR_EACH (pr, key_node, &routes_data->parsed_routes) {
-        add_route(data, pr);
+        gn = group_ecmp_datapath_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_datapath_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);
@@ -290,3 +382,137 @@ 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_datapath *node = group_ecmp_datapath_lookup(data,
+                                                                  pr->od);
+
+    if (!node) {
+        node = group_ecmp_datapath_add(data, pr->od);
+    }
+
+    hmapx_add(updated_routes, node);
+    add_route(node, pr);
+}
+
+static bool
+handle_deleted_route(struct group_ecmp_route_data *data,
+                     const struct parsed_route *pr,
+                     struct hmapx *updated_routes)
+{
+    struct group_ecmp_datapath *node = group_ecmp_datapath_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_has_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->tracked) {
+        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_deleted_parsed_route) {
+        pr = hmapx_node->data;
+        if (!handle_deleted_route(data, pr, &updated_routes)) {
+            hmapx_destroy(&updated_routes);
+            return false;
+        }
+    }
+
+    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);
+    }
+
+    /* 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_datapath *node = hmapx_node->data;
+        if (hmap_is_empty(&node->unique_routes) &&
+                hmap_is_empty(&node->ecmp_groups)) {
+            hmapx_add(&data->trk_data.deleted_datapath_routes, node);
+            hmap_remove(&data->datapaths, &node->hmap_node);
+        } else {
+            hmapx_add(&data->trk_data.crupdated_datapath_routes, node);
+        }
+    }
+
+    hmapx_destroy(&updated_routes);
+
+    if (!(hmapx_is_empty(&data->trk_data.crupdated_datapath_routes) &&
+          hmapx_is_empty(&data->trk_data.deleted_datapath_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 5ffae68a0..c4f732c31 100644
--- a/northd/en-group-ecmp-route.h
+++ b/northd/en-group-ecmp-route.h
@@ -63,9 +63,25 @@ struct group_ecmp_datapath {
     struct hmap unique_routes;
 };
 
+struct group_ecmp_route_tracked_data {
+    /* Contains references to group_ecmp_route_node. Each of the referenced
+     * datapaths contains at least one route. */
+    struct hmapx 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_datapath_routes;
+};
+
 struct group_ecmp_route_data {
     /* Contains struct group_ecmp_route_node. */
     struct hmap datapaths;
+
+    /* 'tracked' is set to true if there is information available for
+     * incremental 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 *);
@@ -73,6 +89,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_datapath *group_ecmp_datapath_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 a01fc2a59..1a05e88af 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 3e1839b1b..f12d73ced 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -15688,7 +15688,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
 
@@ -15699,7 +15699,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
 
-- 
2.48.1


_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to