On 8/14/25 7:48 AM, Ales Musil wrote:
> On Wed, Aug 13, 2025 at 7:21 PM Mark Michelson via dev <
> ovs-dev@openvswitch.org> wrote:
> 
>> All changes to northbound logical routers can be incrementally processed
>> by en-datapath-logical-router. This change ensures that it is done and
>> the changes can then be propagated to the next nodes. A new test in
>> ovn-northd.at ensures the behavior.
>>
>> Signed-off-by: Mark Michelson <mmich...@redhat.com>
>> ---
>>
> 
> Hi Mark and Dumitru,
> 
> I have just one comment down below.
> 

Hi Mark, Ales,

> 
>> V17:
>> - cherry picked from v15
>> ---
>>  northd/datapath-sync.c              |  53 ++++++++
>>  northd/datapath-sync.h              |  13 ++
>>  northd/en-datapath-logical-router.c | 182 ++++++++++++++++++++--------
>>  northd/en-datapath-logical-router.h |   5 +
>>  northd/inc-proc-northd.c            |   5 +-
>>  tests/ovn-northd.at                 |  45 +++++++
>>  6 files changed, 250 insertions(+), 53 deletions(-)
>>
>> diff --git a/northd/datapath-sync.c b/northd/datapath-sync.c
>> index 5de5b8439..40f1761db 100644
>> --- a/northd/datapath-sync.c
>> +++ b/northd/datapath-sync.c
>> @@ -16,6 +16,8 @@
>>  #include <config.h>
>>
>>  #include "datapath-sync.h"
>> +#include "ovsdb-idl-provider.h"
>> +#include "uuid.h"
>>
>>  static const char *ovn_datapath_strings[] = {
>>      [DP_SWITCH] = "logical-switch",
>> @@ -74,6 +76,9 @@ ovn_unsynced_datapath_map_init(struct
>> ovn_unsynced_datapath_map *map,
>>  {
>>      *map = (struct ovn_unsynced_datapath_map) {
>>          .dps = HMAP_INITIALIZER(&map->dps),
>> +        .new = HMAPX_INITIALIZER(&map->new),
>> +        .deleted = HMAPX_INITIALIZER(&map->deleted),
>> +        .updated = HMAPX_INITIALIZER(&map->updated),
>>          .dp_type = dp_type,
>>      };
>>  }
>> @@ -82,9 +87,57 @@ void
>>  ovn_unsynced_datapath_map_destroy(struct ovn_unsynced_datapath_map *map)
>>  {
>>      struct ovn_unsynced_datapath *dp;
>> +    struct hmapx_node *node;
>> +
>> +    hmapx_destroy(&map->new);
>> +    hmapx_destroy(&map->updated);
>> +    HMAPX_FOR_EACH_SAFE (node, &map->deleted) {
>> +       /* Items in the deleted hmapx need to be freed individually since
>> +        * they are not in map->dps.
>> +        */
>> +        dp = node->data;
>> +        ovn_unsynced_datapath_destroy(dp);
>> +        free(dp);
>> +    }
>> +    hmapx_destroy(&map->deleted);
>> +
>>      HMAP_FOR_EACH_POP (dp, hmap_node, &map->dps) {
>>          ovn_unsynced_datapath_destroy(dp);
>>          free(dp);
>>      }
>>      hmap_destroy(&map->dps);
>>  }
>> +
>> +struct ovn_unsynced_datapath *
>> +ovn_unsynced_datapath_find(const struct ovn_unsynced_datapath_map *map,
>> +                           const struct uuid *datapath_uuid)
>> +{
>> +    uint32_t hash = uuid_hash(datapath_uuid);
>> +
>> +    struct ovn_unsynced_datapath *udp;
>> +    HMAP_FOR_EACH_WITH_HASH (udp, hmap_node, hash, &map->dps) {
>> +        if (uuid_equals(&udp->nb_row->uuid, datapath_uuid)) {
>> +            return udp;
>> +        }
>> +    }
>> +    return NULL;
>> +}
>> +
>> +void
>> +ovn_unsynced_datapath_map_clear_tracked_data(
>> +    struct ovn_unsynced_datapath_map *map)
>> +{
>> +    hmapx_clear(&map->new);
>> +    hmapx_clear(&map->updated);
>> +
>> +    /* Deleted entries need to be freed since they don't
>> +     * exist in map->dps.
>> +     */
>> +    struct hmapx_node *node;
>> +    HMAPX_FOR_EACH_SAFE (node, &map->deleted) {
>> +        struct ovn_unsynced_datapath *udp = node->data;
>> +        ovn_unsynced_datapath_destroy(udp);
>> +        free(udp);
>> +        hmapx_delete(&map->deleted, node);
>> +    }
>> +}
>> diff --git a/northd/datapath-sync.h b/northd/datapath-sync.h
>> index 246500c7b..081d0af93 100644
>> --- a/northd/datapath-sync.h
>> +++ b/northd/datapath-sync.h
>> @@ -18,6 +18,7 @@
>>
>>  #include "openvswitch/hmap.h"
>>  #include "smap.h"
>> +#include "hmapx.h"
>>
>>  /* Datapath syncing API. This file consists of utility functions
>>   * that can be used when syncing northbound datapath types (e.g.
>> @@ -62,6 +63,11 @@ struct ovn_unsynced_datapath_map {
>>      /* ovn_unsynced_datapath */
>>      struct hmap dps;
>>      enum ovn_datapath_type dp_type;
>> +
>> +    /* Data for incremental case */
>> +    struct hmapx new;
>> +    struct hmapx updated;
>> +    struct hmapx deleted;
>>  };
>>
>>  struct ovn_synced_datapath {
>> @@ -83,4 +89,11 @@ void ovn_unsynced_datapath_map_init(struct
>> ovn_unsynced_datapath_map *,
>>                                      enum ovn_datapath_type);
>>  void ovn_unsynced_datapath_map_destroy(struct ovn_unsynced_datapath_map
>> *);
>>
>> +struct ovn_unsynced_datapath *
>> +ovn_unsynced_datapath_find(const struct ovn_unsynced_datapath_map *,
>> +                           const struct uuid *);
>> +
>> +void ovn_unsynced_datapath_map_clear_tracked_data(
>> +    struct ovn_unsynced_datapath_map *);
>> +
>>  #endif /* DATAPATH_SYNC_H */
>> diff --git a/northd/en-datapath-logical-router.c
>> b/northd/en-datapath-logical-router.c
>> index 86c3eb365..a033e4520 100644
>> --- a/northd/en-datapath-logical-router.c
>> +++ b/northd/en-datapath-logical-router.c
>> @@ -37,6 +37,80 @@ en_datapath_logical_router_init(struct engine_node
>> *node OVS_UNUSED,
>>      return map;
>>  }
>>
>> +static void
>> +gather_external_ids(const struct nbrec_logical_router *nbr,
>> +                    struct smap *external_ids)
>> +{
>> +    smap_add(external_ids, "name", nbr->name);
>> +    const char *neutron_router = smap_get(&nbr->options,
>> +                                           "neutron:router_name");
>> +    if (neutron_router && neutron_router[0]) {
>> +        smap_add(external_ids, "name2", neutron_router);
>> +    }
>> +
>> +    int64_t ct_zone_limit = ovn_smap_get_llong(&nbr->options,
>> +                                               "ct-zone-limit", -1);
>> +    if (ct_zone_limit > 0) {
>> +        smap_add_format(external_ids, "ct-zone-limit", "%"PRId64,
>> +                        ct_zone_limit);
>> +    }
>> +
>> +    int nat_default_ct = smap_get_int(&nbr->options,
>> +                                      "snat-ct-zone", -1);
>> +    if (nat_default_ct >= 0) {
>> +        smap_add_format(external_ids, "snat-ct-zone", "%d",
>> +                        nat_default_ct);
>> +    }
>> +
>> +    bool learn_from_arp_request =
>> +        smap_get_bool(&nbr->options, "always_learn_from_arp_request",
>> +                      true);
>> +    if (!learn_from_arp_request) {
>> +        smap_add(external_ids, "always_learn_from_arp_request",
>> +                 "false");
>> +    }
>> +
>> +    /* For timestamp refreshing, the smallest threshold of the option is
>> +     * set to SB to make sure all entries are refreshed in time.
>> +     * This approach simplifies processing in ovn-controller, but it
>> +     * may be enhanced, if necessary, to parse the complete CIDR-based
>> +     * threshold configurations to SB to reduce unnecessary refreshes. */
>> +    uint32_t age_threshold = min_mac_binding_age_threshold(
>> +                                   smap_get(&nbr->options,
>> +                                           "mac_binding_age_threshold"));
>> +    if (age_threshold) {
>> +        smap_add_format(external_ids, "mac_binding_age_threshold",
>> +                        "%u", age_threshold);
>> +    }
>> +
>> +    /* For backwards-compatibility, also store the NB UUID in
>> +     * external-ids:logical-router. This is useful if ovn-controller
>> +     * has not updated and expects this to be where to find the
>> +     * UUID.
>> +     */
>> +    smap_add_format(external_ids, "logical-router", UUID_FMT,
>> +                    UUID_ARGS(&nbr->header_.uuid));
>> +}
>> +
>> +static struct ovn_unsynced_datapath *
>> +allocate_unsynced_router(const struct nbrec_logical_router *nbr)
>> +{
>> +    struct ovn_unsynced_datapath *dp =
>> +        ovn_unsynced_datapath_alloc(nbr->name, DP_ROUTER,
>> +                                    smap_get_int(&nbr->options,
>> +                                                 "requested-tnl-key", 0),
>> +                                    &nbr->header_);
>> +
>> +    gather_external_ids(nbr, &dp->external_ids);
>> +    return dp;
>> +}
>> +
>> +static bool
>> +logical_router_is_enabled(const struct nbrec_logical_router *nbr)
>> +{
>> +    return !nbr->enabled || *nbr->enabled;
>> +}
>> +
>>  enum engine_node_state
>>  en_datapath_logical_router_run(struct engine_node *node , void *data)
>>  {
>> @@ -50,69 +124,75 @@ en_datapath_logical_router_run(struct engine_node
>> *node , void *data)
>>
>>      const struct nbrec_logical_router *nbr;
>>      NBREC_LOGICAL_ROUTER_TABLE_FOR_EACH (nbr, nb_lr_table) {
>> -        if (nbr->enabled && !(*nbr->enabled)) {
>> +        if (!logical_router_is_enabled(nbr)) {
>>              continue;
>>          }
>> -        struct ovn_unsynced_datapath *dp =
>> -            ovn_unsynced_datapath_alloc(nbr->name, DP_ROUTER,
>> -                                        smap_get_int(&nbr->options,
>> -                                                     "requested-tnl-key",
>> 0),
>> -                                        &nbr->header_);
>> -
>> -        smap_add(&dp->external_ids, "name", dp->name);
>> -        const char *neutron_router = smap_get(&nbr->options,
>> -                                               "neutron:router_name");
>> -        if (neutron_router && neutron_router[0]) {
>> -            smap_add(&dp->external_ids, "name2", neutron_router);
>> -        }
>> +        struct ovn_unsynced_datapath *dp = allocate_unsynced_router(nbr);
>> +        hmap_insert(&map->dps, &dp->hmap_node,
>> uuid_hash(&nbr->header_.uuid));
>> +    }
>>
>> -        int64_t ct_zone_limit = ovn_smap_get_llong(&nbr->options,
>> -                                                   "ct-zone-limit", -1);
>> -        if (ct_zone_limit > 0) {
>> -            smap_add_format(&dp->external_ids, "ct-zone-limit", "%"PRId64,
>> -                            ct_zone_limit);
>> -        }
>> +    return EN_UPDATED;
>> +}
>>
>> -        int nat_default_ct = smap_get_int(&nbr->options,
>> -                                          "snat-ct-zone", -1);
>> -        if (nat_default_ct >= 0) {
>> -            smap_add_format(&dp->external_ids, "snat-ct-zone", "%d",
>> -                            nat_default_ct);
>> -        }
>> +void
>> +en_datapath_logical_router_clear_tracked_data(void *data)
>> +{
>> +    ovn_unsynced_datapath_map_clear_tracked_data(data);
>> +}
>>
>> -        bool learn_from_arp_request =
>> -            smap_get_bool(&nbr->options, "always_learn_from_arp_request",
>> -                          true);
>> -        if (!learn_from_arp_request) {
>> -            smap_add(&dp->external_ids, "always_learn_from_arp_request",
>> -                     "false");
>> +enum engine_input_handler_result
>> +en_datapath_logical_router_logical_router_handler(struct engine_node
>> *node,
>> +                                                  void *data)
>> +{
>> +    const struct nbrec_logical_router_table *nb_lr_table =
>> +        EN_OVSDB_GET(engine_get_input("NB_logical_router", node));
>> +
>> +    struct ovn_unsynced_datapath_map *map = data;
>> +
>> +    struct ovn_unsynced_datapath *udp;
>> +    const struct nbrec_logical_router *nbr;
>> +    NBREC_LOGICAL_ROUTER_TABLE_FOR_EACH_TRACKED (nbr, nb_lr_table) {
>> +        udp = ovn_unsynced_datapath_find(map, &nbr->header_.uuid);
>> +
>> +        if (nbrec_logical_router_is_deleted(nbr) && !udp) {
>> +            return EN_UNHANDLED;
>>          }
>>
>> -        /* For timestamp refreshing, the smallest threshold of the option
>> is
>> -         * set to SB to make sure all entries are refreshed in time.
>> -         * This approach simplifies processing in ovn-controller, but it
>> -         * may be enhanced, if necessary, to parse the complete CIDR-based
>> -         * threshold configurations to SB to reduce unnecessary
>> refreshes. */
>> -        uint32_t age_threshold = min_mac_binding_age_threshold(
>> -                                       smap_get(&nbr->options,
>> -
>>  "mac_binding_age_threshold"));
>> -        if (age_threshold) {
>> -            smap_add_format(&dp->external_ids,
>> "mac_binding_age_threshold",
>> -                            "%u", age_threshold);
>> +        if (nbrec_logical_router_is_new(nbr) && udp) {
>> +            return EN_UNHANDLED;
>>          }
>>
>> -        /* For backwards-compatibility, also store the NB UUID in
>> -         * external-ids:logical-router. This is useful if ovn-controller
>> -         * has not updated and expects this to be where to find the
>> -         * UUID.
>> -         */
>> -        smap_add_format(&dp->external_ids, "logical-router", UUID_FMT,
>> -                        UUID_ARGS(&nbr->header_.uuid));
>> +        if (udp) {
>> +            if (nbrec_logical_router_is_deleted(nbr) ||
>> +                !logical_router_is_enabled(nbr)) {
>> +                hmap_remove(&map->dps, &udp->hmap_node);
>> +                hmapx_add(&map->deleted, udp);
>> +            } else {
>> +                /* We could try to modify the unsynced datapath in place
>> +                 * based on the new logical router, but it's easier to
>> +                 * just allocate a new one.
>> +                 */
>>
> 
> This comment isn't accurate, we are actually modifying it in
> place. There is only one thing that is created from scratch
> and that's the external_ids.
> 

Good point, I changed it to:

/* We could try to modify the unsynced datapath external_ids
 * in place based on the new logical router, but it's easier to
 * just create a new map.
 */

And applied the patch to main.

Thanks a lot for the hard work on this one!

Regards,
Dumitru

> 
>> +                udp->requested_tunnel_key =
>> +                    smap_get_int(&nbr->options, "requested-tnl-key", 0);
>> +                smap_destroy(&udp->external_ids);
>> +                smap_init(&udp->external_ids);
>> +                gather_external_ids(nbr, &udp->external_ids);
>> +                hmapx_add(&map->updated, udp);
>> +            }
>> +        } else if (logical_router_is_enabled(nbr)) {
>> +            udp = allocate_unsynced_router(nbr);
>> +            hmap_insert(&map->dps, &udp->hmap_node,
>> +                        uuid_hash(&nbr->header_.uuid));
>> +            hmapx_add(&map->new, udp);
>> +        }
>> +    }
>>
>> -        hmap_insert(&map->dps, &dp->hmap_node,
>> uuid_hash(&nbr->header_.uuid));
>> +    if (!(hmapx_is_empty(&map->new) && hmapx_is_empty(&map->updated) &&
>> +        hmapx_is_empty(&map->deleted))) {
>> +        return EN_HANDLED_UPDATED;
>>      }
>>
>> -    return EN_UPDATED;
>> +    return EN_HANDLED_UNCHANGED;
>>  }
>>
>>  void
>> diff --git a/northd/en-datapath-logical-router.h
>> b/northd/en-datapath-logical-router.h
>> index 31e70fbf5..fa2bf798f 100644
>> --- a/northd/en-datapath-logical-router.h
>> +++ b/northd/en-datapath-logical-router.h
>> @@ -25,6 +25,7 @@ void *en_datapath_logical_router_init(struct engine_node
>> *,
>>
>>  enum engine_node_state en_datapath_logical_router_run(struct engine_node
>> *,
>>                                                        void *data);
>> +void en_datapath_logical_router_clear_tracked_data(void *data);
>>  void en_datapath_logical_router_cleanup(void *data);
>>
>>  struct ovn_synced_logical_router {
>> @@ -45,4 +46,8 @@ enum engine_node_state
>> en_datapath_synced_logical_router_run(
>>
>>  void en_datapath_synced_logical_router_cleanup(void *data);
>>
>> +enum engine_input_handler_result
>> +en_datapath_logical_router_logical_router_handler(struct engine_node *,
>> +                                                  void *);
>> +
>>  #endif /* EN_DATAPATH_LOGICAL_ROUTER_H */
>> diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
>> index f34110eb8..ff309c402 100644
>> --- a/northd/inc-proc-northd.c
>> +++ b/northd/inc-proc-northd.c
>> @@ -182,7 +182,7 @@ static ENGINE_NODE(advertised_route_sync);
>>  static ENGINE_NODE(learned_route_sync, CLEAR_TRACKED_DATA);
>>  static ENGINE_NODE(dynamic_routes);
>>  static ENGINE_NODE(group_ecmp_route, CLEAR_TRACKED_DATA);
>> -static ENGINE_NODE(datapath_logical_router);
>> +static ENGINE_NODE(datapath_logical_router, CLEAR_TRACKED_DATA);
>>  static ENGINE_NODE(datapath_logical_switch);
>>  static ENGINE_NODE(datapath_sync);
>>  static ENGINE_NODE(datapath_synced_logical_router);
>> @@ -220,7 +220,8 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>>      engine_add_input(&en_datapath_logical_switch, &en_nb_logical_switch,
>> NULL);
>>      engine_add_input(&en_datapath_logical_switch, &en_global_config,
>> NULL);
>>
>> -    engine_add_input(&en_datapath_logical_router, &en_nb_logical_router,
>> NULL);
>> +    engine_add_input(&en_datapath_logical_router, &en_nb_logical_router,
>> +                     en_datapath_logical_router_logical_router_handler);
>>
>>      engine_add_input(&en_datapath_sync, &en_datapath_logical_switch,
>> NULL);
>>      engine_add_input(&en_datapath_sync, &en_datapath_logical_router,
>> NULL);
>> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
>> index 0ab1384f6..a85f75a2f 100644
>> --- a/tests/ovn-northd.at
>> +++ b/tests/ovn-northd.at
>> @@ -17641,3 +17641,48 @@ check as northd ovn-appctl -t ovn-northd
>> inc-engine/clear-stats
>>
>>  AT_CLEANUP
>>  ])
>> +
>> +OVN_FOR_EACH_NORTHD_NO_HV([
>> +AT_SETUP([Unsynced Logical Router Incremental Processing])
>> +ovn_start
>> +
>> +# Start by issuing a recompute just so we know that the engine
>> +# has run at least once.
>> +check as northd ovn-appctl -t ovn-northd inc-engine/recompute
>> +
>> +# Now all of our router changes should be handled incrementally.
>> +# There is no circumstance under which en-datapath-logical-router
>> +# should have to recompute.
>> +
>> +# Add a router
>> +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
>> +check ovn-nbctl --wait=sb lr-add lr1
>> +check_engine_stats datapath_logical_router norecompute compute
>> +CHECK_NO_CHANGE_AFTER_RECOMPUTE
>> +
>> +# Update router options
>> +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
>> +check ovn-nbctl --wait=sb set Logical_Router lr1 options:ct-zone-limit=10
>> +check_engine_stats datapath_logical_router norecompute compute
>> +CHECK_NO_CHANGE_AFTER_RECOMPUTE
>> +
>> +# Disable logical router.
>> +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
>> +check ovn-nbctl --wait=sb set Logical_Router lr1 enabled=false
>> +check_engine_stats datapath_logical_router norecompute compute
>> +CHECK_NO_CHANGE_AFTER_RECOMPUTE
>> +
>> +# Re-enable logical router.
>> +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
>> +check ovn-nbctl --wait=sb set Logical_Router lr1 enabled=true
>> +check_engine_stats datapath_logical_router norecompute compute
>> +CHECK_NO_CHANGE_AFTER_RECOMPUTE
>> +
>> +# Delete logical router
>> +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
>> +check ovn-nbctl --wait=sb lr-del lr1
>> +check_engine_stats datapath_logical_router norecompute compute
>> +CHECK_NO_CHANGE_AFTER_RECOMPUTE
>> +
>> +AT_CLEANUP
>> +])
>> --
>> 2.49.0
>>
>> _______________________________________________
>> dev mailing list
>> d...@openvswitch.org
>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>>
>>
> With that addressed:
> Acked-by: Ales Musil <amu...@redhat.com>
> 
> Thanks,
> Ales
> 

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

Reply via email to