On Wed, Jan 17, 2024 at 10:17 AM Dumitru Ceara <dce...@redhat.com> wrote:
>
> On 1/11/24 16:30, num...@ovn.org wrote:
> > From: Numan Siddique <num...@ovn.org>
> >
> > This new engine now maintains the load balancer and ACL data of a
> > logical switch which was earlier part of northd engine node data.
> > The main inputs to this engine are:
> >     - northd node
> >     - Port group node
> >
> > A record for each logical switch is maintained in the 'ls_statefuls'
> > hmap table and this record stores the below data which was earlier
> > part of 'struct ovn_datapath'.
> >
> >     - bool has_stateful_acl;
> >     - bool has_lb_vip;
> >     - bool has_acls;
> >     - uint64_t max_acl_tier;
> >
> > This engine node becomes an input to 'lflow' node.
> >
> > If a logical switch is configured with
> >    - DHCP for all or some of its ports
> >    - stateful ACLs and/or load balancers
> >
> > ovn-northd was adding flows to commit the DHCP response generated by
> > ovn-controller to conntrack.  With this patch we don't commit the
> > DHCP response to conntrack anymore for the following reasons:
> >   1.  There is no need to actually commit the response.
> >   2.  Since stateful information is moved to 'ls_stateful' node, it
> >       becomes ineffecient to access ls_stateful's data
> >       ('has_lb_vip' and 'has_acls') in build_dhcpv4_options_flows().
> >
> > Signed-off-by: Numan Siddique <num...@ovn.org>
> > ---
>
> Hi Numan,
>
> Overall this looks correct to me.  I only had a few minor comments,
> please see below.
>
> If you address them feel free to add my ack:
>
> Acked-by: Dumitru Ceara <dce...@redhat.com>
>
> Thanks,
> Dumitru
>
> >  lib/stopwatch-names.h    |   1 +
> >  northd/automake.mk       |   2 +
> >  northd/en-lflow.c        |   4 +
> >  northd/en-lr-stateful.c  |   6 +-
> >  northd/en-lr-stateful.h  |   2 +
> >  northd/en-ls-stateful.c  | 440 +++++++++++++++++++++++++++++++++++++++
> >  northd/en-ls-stateful.h  |  81 +++++++
> >  northd/en-northd.c       |   2 +-
> >  northd/en-port-group.h   |   3 +
> >  northd/inc-proc-northd.c |   7 +
> >  northd/northd.c          | 335 +++++++++++++++--------------
> >  northd/northd.h          |  29 ++-
> >  northd/ovn-northd.c      |   1 +
> >  13 files changed, 753 insertions(+), 160 deletions(-)
> >  create mode 100644 northd/en-ls-stateful.c
> >  create mode 100644 northd/en-ls-stateful.h
> >
> > diff --git a/lib/stopwatch-names.h b/lib/stopwatch-names.h
> > index e5e41fbfd8..c865f125be 100644
> > --- a/lib/stopwatch-names.h
> > +++ b/lib/stopwatch-names.h
> > @@ -31,5 +31,6 @@
> >  #define SYNC_METERS_RUN_STOPWATCH_NAME "sync_meters_run"
> >  #define LR_NAT_RUN_STOPWATCH_NAME "lr_nat_run"
> >  #define LR_STATEFUL_RUN_STOPWATCH_NAME "lr_stateful"
> > +#define LS_STATEFUL_RUN_STOPWATCH_NAME "ls_stateful"
> >
> >  #endif
> > diff --git a/northd/automake.mk b/northd/automake.mk
> > index b886356c9c..a178541759 100644
> > --- a/northd/automake.mk
> > +++ b/northd/automake.mk
> > @@ -28,6 +28,8 @@ northd_ovn_northd_SOURCES = \
> >       northd/en-lr-nat.h \
> >       northd/en-lr-stateful.c \
> >       northd/en-lr-stateful.h \
> > +     northd/en-ls-stateful.c \
> > +     northd/en-ls-stateful.h \
> >       northd/inc-proc-northd.c \
> >       northd/inc-proc-northd.h \
> >       northd/ipam.c \
> > diff --git a/northd/en-lflow.c b/northd/en-lflow.c
> > index a3a0d62f15..eb6b2a8666 100644
> > --- a/northd/en-lflow.c
> > +++ b/northd/en-lflow.c
> > @@ -21,6 +21,7 @@
> >  #include "en-lflow.h"
> >  #include "en-lr-nat.h"
> >  #include "en-lr-stateful.h"
> > +#include "en-ls-stateful.h"
> >  #include "en-northd.h"
> >  #include "en-meters.h"
> >
> > @@ -44,6 +45,8 @@ lflow_get_input_data(struct engine_node *node,
> >          engine_get_input_data("sync_meters", node);
> >      struct ed_type_lr_stateful *lr_stateful_data =
> >          engine_get_input_data("lr_stateful", node);
> > +    struct ed_type_ls_stateful *ls_stateful_data =
> > +        engine_get_input_data("ls_stateful", node);
> >
> >      lflow_input->nbrec_bfd_table =
> >          EN_OVSDB_GET(engine_get_input("NB_bfd", node));
> > @@ -67,6 +70,7 @@ lflow_get_input_data(struct engine_node *node,
> >      lflow_input->lr_ports = &northd_data->lr_ports;
> >      lflow_input->ls_port_groups = &pg_data->ls_port_groups;
> >      lflow_input->lr_stateful_table = &lr_stateful_data->table;
> > +    lflow_input->ls_stateful_table = &ls_stateful_data->table;
> >      lflow_input->meter_groups = &sync_meters_data->meter_groups;
> >      lflow_input->lb_datapaths_map = &northd_data->lb_datapaths_map;
> >      lflow_input->svc_monitor_map = &northd_data->svc_monitor_map;
> > diff --git a/northd/en-lr-stateful.c b/northd/en-lr-stateful.c
> > index dedfdf6747..4287aaddc9 100644
> > --- a/northd/en-lr-stateful.c
> > +++ b/northd/en-lr-stateful.c
> > @@ -295,7 +295,9 @@ lr_stateful_lb_data_handler(struct engine_node *node, 
> > void *data_)
> >          /* For all the modified lr_stateful records (re)build the
> >           * vip nats. */
> >          HMAPX_FOR_EACH (hmapx_node, &data->trk_data.crupdated) {
> > -            lr_stateful_build_vip_nats(hmapx_node->data);
> > +            struct lr_stateful_record *lr_stateful_rec = hmapx_node->data;
> > +            lr_stateful_build_vip_nats(lr_stateful_rec);
> > +            lr_stateful_rec->has_lb_vip = 
> > od_has_lb_vip(lr_stateful_rec->od);
> >          }
> >
> >          engine_set_node_state(node, EN_UPDATED);
> > @@ -510,6 +512,8 @@ lr_stateful_record_init(struct lr_stateful_record 
> > *lr_stateful_rec,
> >      if (nbr->n_nat) {
> >          lr_stateful_build_vip_nats(lr_stateful_rec);
> >      }
> > +
> > +    lr_stateful_rec->has_lb_vip = od_has_lb_vip(lr_stateful_rec->od);
> >  }
> >
> >  static struct lr_stateful_input
> > diff --git a/northd/en-lr-stateful.h b/northd/en-lr-stateful.h
> > index 74029c9a6c..5d647d8863 100644
> > --- a/northd/en-lr-stateful.h
> > +++ b/northd/en-lr-stateful.h
> > @@ -49,6 +49,8 @@ struct lr_stateful_record {
> >      const struct ovn_datapath *od;
> >      const struct lr_nat_record *lrnat_rec;
> >
> > +    bool has_lb_vip;
> > +
> >      /* Load Balancer vIPs relevant for this datapath. */
> >      struct ovn_lb_ip_set *lb_ips;
> >
> > diff --git a/northd/en-ls-stateful.c b/northd/en-ls-stateful.c
> > new file mode 100644
> > index 0000000000..5fa305bc29
> > --- /dev/null
> > +++ b/northd/en-ls-stateful.c
> > @@ -0,0 +1,440 @@
> > +/*
> > + * Copyright (c) 2023, Red Hat, Inc.
>
> 2024

Ack

>
> > + *
> > + * 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 <getopt.h>
> > +#include <stdlib.h>
> > +#include <stdio.h>
> > +
> > +/* OVS includes */
> > +#include "include/openvswitch/hmap.h"
> > +#include "lib/bitmap.h"
> > +#include "lib/socket-util.h"
> > +#include "lib/uuidset.h"
> > +#include "openvswitch/util.h"
> > +#include "openvswitch/vlog.h"
> > +#include "stopwatch.h"
> > +
> > +/* OVN includes */
> > +#include "en-lb-data.h"
> > +#include "en-ls-stateful.h"
> > +#include "en-port-group.h"
> > +#include "lib/inc-proc-eng.h"
> > +#include "lib/lb.h"
> > +#include "lib/ovn-nb-idl.h"
> > +#include "lib/ovn-sb-idl.h"
> > +#include "lib/ovn-util.h"
> > +#include "lib/stopwatch-names.h"
> > +#include "northd.h"
> > +
> > +VLOG_DEFINE_THIS_MODULE(en_ls_stateful);
> > +
> > +/* Static function declarations. */
> > +static void ls_stateful_table_init(struct ls_stateful_table *);
> > +static void ls_stateful_table_clear(struct ls_stateful_table *);
> > +static void ls_stateful_table_destroy(struct ls_stateful_table *);
> > +static struct ls_stateful_record *ls_stateful_table_find_(
> > +    const struct ls_stateful_table *, const struct nbrec_logical_switch *);
> > +static void ls_stateful_table_build(struct ls_stateful_table *,
> > +                                    const struct ovn_datapaths 
> > *ls_datapaths,
> > +                                    const struct ls_port_group_table *);
> > +
> > +static struct ls_stateful_input ls_stateful_get_input_data(
> > +    struct engine_node *);
> > +
> > +static struct ls_stateful_record *ls_stateful_record_create(
> > +    struct ls_stateful_table *,
> > +    const struct ovn_datapath *,
> > +    const struct ls_port_group_table *);
> > +static void ls_stateful_record_destroy(struct ls_stateful_record *);
> > +static void ls_stateful_record_init(
> > +    struct ls_stateful_record *,
> > +    const struct ovn_datapath *,
> > +    const struct ls_port_group *,
> > +    const struct ls_port_group_table *);
> > +static void ls_stateful_record_reinit(
> > +    struct ls_stateful_record *,
> > +    const struct ls_port_group *,
> > +    const struct ls_port_group_table *);
> > +static bool ls_has_lb_vip(const struct ovn_datapath *);
> > +static void ls_stateful_record_set_acl_flags(
> > +    struct ls_stateful_record *, const struct ovn_datapath *,
> > +    const struct ls_port_group *, const struct ls_port_group_table *);
> > +static bool ls_stateful_record_set_acl_flags_(struct ls_stateful_record *,
> > +                                              struct nbrec_acl **,
> > +                                              size_t n_acls);
> > +static struct ls_stateful_input ls_stateful_get_input_data(
> > +    struct engine_node *);
> > +
> > +struct ls_stateful_input {
> > +    const struct ls_port_group_table *ls_port_groups;
> > +    const struct ovn_datapaths *ls_datapaths;
> > +};
> > +
> > +/* public functions. */
> > +const struct ls_stateful_record *
> > +ls_stateful_table_find(const struct ls_stateful_table *table,
> > +                       const struct nbrec_logical_switch *nbs)
> > +{
> > +    return ls_stateful_table_find_(table, nbs);
> > +}
> > +
> > +void *
> > +en_ls_stateful_init(struct engine_node *node OVS_UNUSED,
> > +                    struct engine_arg *arg OVS_UNUSED)
> > +{
> > +    struct ed_type_ls_stateful *data = xzalloc(sizeof *data);
> > +    ls_stateful_table_init(&data->table);
> > +    hmapx_init(&data->trk_data.crupdated);
> > +    return data;
> > +}
> > +
> > +void
> > +en_ls_stateful_cleanup(void *data_)
> > +{
> > +    struct ed_type_ls_stateful *data = data_;
> > +    ls_stateful_table_destroy(&data->table);
> > +    hmapx_destroy(&data->trk_data.crupdated);
> > +}
> > +
> > +void
> > +en_ls_stateful_clear_tracked_data(void *data_)
> > +{
> > +    struct ed_type_ls_stateful *data = data_;
> > +    hmapx_clear(&data->trk_data.crupdated);
> > +}
> > +
> > +void
> > +en_ls_stateful_run(struct engine_node *node, void *data_)
> > +{
> > +    struct ls_stateful_input input_data = ls_stateful_get_input_data(node);
> > +    struct ed_type_ls_stateful *data = data_;
> > +
> > +    stopwatch_start(LS_STATEFUL_RUN_STOPWATCH_NAME, time_msec());
> > +
> > +    ls_stateful_table_clear(&data->table);
> > +    ls_stateful_table_build(&data->table, input_data.ls_datapaths,
> > +                          input_data.ls_port_groups);
> > +
> > +    stopwatch_stop(LS_STATEFUL_RUN_STOPWATCH_NAME, time_msec());
> > +    engine_set_node_state(node, EN_UPDATED);
> > +}
> > +
> > +/* Handler functions. */
> > +bool
> > +ls_stateful_northd_handler(struct engine_node *node, void *data_)
> > +{
> > +    struct northd_data *northd_data = engine_get_input_data("northd", 
> > node);
> > +    if (!northd_has_tracked_data(&northd_data->trk_data)) {
> > +        return false;
> > +    }
> > +
> > +    if (!northd_has_ls_lbs_in_tracked_data(&northd_data->trk_data) &&
> > +        !northd_has_ls_acls_in_tracked_data(&northd_data->trk_data)) {
> > +        return true;
> > +    }
> > +
> > +    struct northd_tracked_data *nd_changes = &northd_data->trk_data;
> > +    struct ls_stateful_input input_data = ls_stateful_get_input_data(node);
> > +    struct ed_type_ls_stateful *data = data_;
> > +    struct hmapx_node *hmapx_node;
> > +
> > +    struct hmapx changed_stateful_od = 
> > HMAPX_INITIALIZER(&changed_stateful_od);
> > +    HMAPX_FOR_EACH (hmapx_node, &nd_changes->ls_with_changed_lbs) {
> > +        hmapx_add(&changed_stateful_od, hmapx_node->data);
> > +    }
> > +
> > +    HMAPX_FOR_EACH (hmapx_node, &nd_changes->ls_with_changed_acls) {
> > +        hmapx_add(&changed_stateful_od, hmapx_node->data);
> > +    }
> > +
> > +    HMAPX_FOR_EACH (hmapx_node, &changed_stateful_od) {
> > +        const struct ovn_datapath *od = hmapx_node->data;
> > +
> > +        struct ls_stateful_record *ls_stateful_rec = 
> > ls_stateful_table_find_(
> > +            &data->table, od->nbs);
> > +        ovs_assert(ls_stateful_rec);
> > +        ls_stateful_record_reinit(ls_stateful_rec, NULL,
> > +                                  input_data.ls_port_groups);
> > +
> > +        /* Add the ls_stateful_rec to the tracking data. */
> > +        hmapx_add(&data->trk_data.crupdated, ls_stateful_rec);
> > +    }
> > +
> > +    if (ls_stateful_has_tracked_data(&data->trk_data)) {
> > +        engine_set_node_state(node, EN_UPDATED);
> > +    }
> > +
> > +    hmapx_destroy(&changed_stateful_od);
> > +
> > +    return true;
> > +}
> > +
> > +bool
> > +ls_stateful_port_group_handler(struct engine_node *node, void *data_)
> > +{
> > +    struct port_group_data *pg_data =
> > +        engine_get_input_data("port_group", node);
> > +
> > +    if (pg_data->ls_port_groups_sets_changed) {
> > +        return false;
> > +    }
> > +
> > +    /* port_group engine node doesn't provide the tracking data yet.
> > +     * Loop through all the ls port groups and update the ls_stateful_rec.
> > +     * This is still better than returning false. */
> > +    struct ls_stateful_input input_data = ls_stateful_get_input_data(node);
> > +    struct ed_type_ls_stateful *data = data_;
> > +    const struct ls_port_group *ls_pg;
> > +
> > +    LS_PORT_GROUP_TABLE_FOR_EACH (ls_pg, input_data.ls_port_groups) {
> > +        struct ls_stateful_record *ls_stateful_rec =
> > +            ls_stateful_table_find_(&data->table, ls_pg->nbs);
> > +        ovs_assert(ls_stateful_rec);
> > +
> > +        bool had_stateful_acl = ls_stateful_rec->has_stateful_acl;
> > +        uint64_t max_acl_tier = ls_stateful_rec->max_acl_tier;
> > +        bool had_acls = ls_stateful_rec->has_acls;
> > +        bool modified = false;
> > +
> > +        ls_stateful_record_reinit(ls_stateful_rec, ls_pg,
> > +                                  input_data.ls_port_groups);
> > +
> > +        if ((had_stateful_acl != ls_stateful_rec->has_stateful_acl)
> > +            || (had_acls != ls_stateful_rec->has_acls)
> > +            || max_acl_tier != ls_stateful_rec->max_acl_tier) {
> > +            modified = true;
> > +        }
> > +
> > +        if (modified) {
> > +            /* Add the ls_stateful_rec to the tracking data. */
> > +            hmapx_add(&data->trk_data.crupdated, ls_stateful_rec);
> > +        }
> > +    }
> > +
> > +    if (ls_stateful_has_tracked_data(&data->trk_data)) {
> > +        engine_set_node_state(node, EN_UPDATED);
> > +    }
> > +    return true;
> > +}
> > +
> > +/* static functions. */
> > +static void
> > +ls_stateful_table_init(struct ls_stateful_table *table)
> > +{
> > +    *table = (struct ls_stateful_table) {
> > +        .entries = HMAP_INITIALIZER(&table->entries),
> > +    };
> > +}
> > +
> > +static void
> > +ls_stateful_table_destroy(struct ls_stateful_table *table)
> > +{
> > +    ls_stateful_table_clear(table);
> > +    hmap_destroy(&table->entries);
> > +}
> > +
> > +static void
> > +ls_stateful_table_clear(struct ls_stateful_table *table)
> > +{
> > +    struct ls_stateful_record *ls_stateful_rec;
> > +    HMAP_FOR_EACH_POP (ls_stateful_rec, key_node, &table->entries) {
> > +        ls_stateful_record_destroy(ls_stateful_rec);
> > +    }
> > +}
> > +
> > +static void
> > +ls_stateful_table_build(struct ls_stateful_table *table,
> > +                        const struct ovn_datapaths *ls_datapaths,
> > +                        const struct ls_port_group_table *ls_pgs)
> > +{
> > +    const struct ovn_datapath *od;
> > +    HMAP_FOR_EACH (od, key_node, &ls_datapaths->datapaths) {
> > +        ls_stateful_record_create(table, od, ls_pgs);
> > +    }
> > +}
> > +
> > +struct ls_stateful_record *
> > +ls_stateful_table_find_(const struct ls_stateful_table *table,
> > +                        const struct nbrec_logical_switch *nbs)
> > +{
> > +    struct ls_stateful_record *rec;
> > +
> > +    HMAP_FOR_EACH_WITH_HASH (rec, key_node,
> > +                             uuid_hash(&nbs->header_.uuid), 
> > &table->entries) {
> > +        if (nbs == rec->od->nbs) {
> > +            return rec;
> > +        }
> > +    }
> > +    return NULL;
> > +}
> > +
> > +static struct ls_stateful_record *
> > +ls_stateful_record_create(struct ls_stateful_table *table,
> > +                          const struct ovn_datapath *od,
> > +                          const struct ls_port_group_table *ls_pgs)
> > +{
> > +    struct ls_stateful_record *ls_stateful_rec =
> > +        xzalloc(sizeof *ls_stateful_rec);
> > +    ls_stateful_rec->od = od;
> > +    ls_stateful_record_init(ls_stateful_rec, od, NULL, ls_pgs);
> > +
> > +    hmap_insert(&table->entries, &ls_stateful_rec->key_node,
> > +                uuid_hash(&ls_stateful_rec->od->nbs->header_.uuid));
> > +
> > +    return ls_stateful_rec;
> > +}
> > +
> > +static void
> > +ls_stateful_record_destroy(struct ls_stateful_record *ls_stateful_rec)
> > +{
> > +    free(ls_stateful_rec);
> > +}
> > +
> > +static void
> > +ls_stateful_record_init(struct ls_stateful_record *ls_stateful_rec,
> > +                        const struct ovn_datapath *od,
> > +                        const struct ls_port_group *ls_pg,
> > +                        const struct ls_port_group_table *ls_pgs)
> > +{
> > +    ls_stateful_rec->has_lb_vip = ls_has_lb_vip(od);
> > +    ls_stateful_record_set_acl_flags(ls_stateful_rec, od, ls_pg, ls_pgs);
> > +}
> > +
> > +static void
> > +ls_stateful_record_reinit(struct ls_stateful_record *ls_stateful_rec,
> > +                          const struct ls_port_group *ls_pg,
> > +                          const struct ls_port_group_table *ls_pgs)
> > +{
> > +    ls_stateful_record_init(ls_stateful_rec, ls_stateful_rec->od,
> > +                            ls_pg, ls_pgs);
> > +}
> > +
> > +static bool
> > +lb_has_vip(const struct nbrec_load_balancer *lb)
> > +{
> > +    return !smap_is_empty(&lb->vips);
> > +}
> > +
> > +static bool
> > +lb_group_has_vip(const struct nbrec_load_balancer_group *lb_group)
> > +{
> > +    for (size_t i = 0; i < lb_group->n_load_balancer; i++) {
> > +        if (lb_has_vip(lb_group->load_balancer[i])) {
> > +            return true;
> > +        }
> > +    }
> > +    return false;
> > +}
> > +
> > +static bool
> > +ls_has_lb_vip(const struct ovn_datapath *od)
> > +{
> > +    for (size_t i = 0; i < od->nbs->n_load_balancer; i++) {
> > +        if (lb_has_vip(od->nbs->load_balancer[i])) {
> > +            return true;
> > +        }
> > +    }
> > +
> > +    for (size_t i = 0; i < od->nbs->n_load_balancer_group; i++) {
> > +        if (lb_group_has_vip(od->nbs->load_balancer_group[i])) {
> > +            return true;
> > +        }
> > +    }
> > +    return false;
> > +}
> > +
> > +static void
> > +ls_stateful_record_set_acl_flags(struct ls_stateful_record 
> > *ls_stateful_rec,
> > +                                 const struct ovn_datapath *od,
> > +                                 const struct ls_port_group *ls_pg,
> > +                                 const struct ls_port_group_table *ls_pgs)
> > +{
> > +    ls_stateful_rec->has_stateful_acl = false;
> > +    ls_stateful_rec->max_acl_tier = 0;
> > +    ls_stateful_rec->has_acls = false;
> > +
> > +    if (ls_stateful_record_set_acl_flags_(ls_stateful_rec, od->nbs->acls,
> > +                                          od->nbs->n_acls)) {
> > +        return;
> > +    }
> > +
> > +    if (!ls_pg) {
> > +        ls_pg = ls_port_group_table_find(ls_pgs, od->nbs);
> > +    }
> > +
> > +    if (!ls_pg) {
> > +        return;
> > +    }
> > +
> > +    const struct ls_port_group_record *ls_pg_rec;
> > +    HMAP_FOR_EACH (ls_pg_rec, key_node, &ls_pg->nb_pgs) {
> > +        if (ls_stateful_record_set_acl_flags_(ls_stateful_rec,
> > +                                              ls_pg_rec->nb_pg->acls,
> > +                                              ls_pg_rec->nb_pg->n_acls)) {
> > +            return;
> > +        }
> > +    }
> > +}
> > +
> > +static bool
> > +ls_stateful_record_set_acl_flags_(struct ls_stateful_record 
> > *ls_stateful_rec,
> > +                                  struct nbrec_acl **acls,
> > +                                  size_t n_acls)
> > +{
> > +    /* A true return indicates that there are no possible ACL flags
> > +     * left to set on ls_stateful record. A false return indicates that
> > +     * further ACLs should be explored in case more flags need to be
> > +     * set on ls_stateful record.
> > +     */
> > +    if (!n_acls) {
> > +        return false;
> > +    }
> > +
> > +    ls_stateful_rec->has_acls = true;
> > +    for (size_t i = 0; i < n_acls; i++) {
> > +        const struct nbrec_acl *acl = acls[i];
> > +        if (acl->tier > ls_stateful_rec->max_acl_tier) {
> > +            ls_stateful_rec->max_acl_tier = acl->tier;
> > +        }
> > +        if (!ls_stateful_rec->has_stateful_acl
> > +                && !strcmp(acl->action, "allow-related")) {
> > +            ls_stateful_rec->has_stateful_acl = true;
> > +        }
> > +        if (ls_stateful_rec->has_stateful_acl &&
> > +            ls_stateful_rec->max_acl_tier ==
> > +                nbrec_acl_col_tier.type.value.integer.max) {
> > +            return true;
> > +        }
> > +    }
> > +
> > +    return false;
> > +}
> > +
> > +static struct ls_stateful_input
> > +ls_stateful_get_input_data(struct engine_node *node)
> > +{
> > +    const struct northd_data *northd_data =
> > +        engine_get_input_data("northd", node);
> > +    const struct port_group_data *pg_data =
> > +        engine_get_input_data("port_group", node);
> > +
> > +    return (struct ls_stateful_input) {
> > +        .ls_port_groups = &pg_data->ls_port_groups,
> > +        .ls_datapaths = &northd_data->ls_datapaths,
> > +    };
> > +}
> > diff --git a/northd/en-ls-stateful.h b/northd/en-ls-stateful.h
> > new file mode 100644
> > index 0000000000..cba53e1f29
> > --- /dev/null
> > +++ b/northd/en-ls-stateful.h
> > @@ -0,0 +1,81 @@
> > +/*
> > + * Copyright (c) 2023, Red Hat, Inc.
>
> 2024

Ack

>
> > + *
> > + * 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 EN_LS_STATEFUL_H
> > +#define EN_LS_STATEFUL_H 1
> > +
> > +#include <stdint.h>
> > +
> > +/* OVS includes. */
> > +#include "lib/hmapx.h"
> > +#include "openvswitch/hmap.h"
> > +#include "sset.h"
> > +
> > +/* OVN includes. */
> > +#include "lib/inc-proc-eng.h"
> > +#include "lib/lb.h"
> > +#include "lib/ovn-nb-idl.h"
> > +#include "lib/ovn-sb-idl.h"
> > +#include "lib/ovn-util.h"
> > +#include "lib/stopwatch-names.h"
> > +
> > +struct ls_stateful_record {
> > +    struct hmap_node key_node;
> > +
> > +    const struct ovn_datapath *od;
> > +    bool has_stateful_acl;
> > +    bool has_lb_vip;
> > +    bool has_acls;
> > +    uint64_t max_acl_tier;
> > +};
> > +
> > +struct ls_stateful_table {
> > +    struct hmap entries;
> > +};
> > +
> > +#define LS_STATEFUL_TABLE_FOR_EACH(LS_STATEFUL_REC, TABLE) \
> > +    HMAP_FOR_EACH (LS_STATEFUL_REC, key_node, &(TABLE)->entries)
> > +
> > +#define LS_STATEFUL_TABLE_FOR_EACH_IN_P(LS_STATEFUL_REC, JOBID, TABLE) \
> > +    HMAP_FOR_EACH_IN_PARALLEL (LS_STATEFUL_REC, key_node, JOBID, \
> > +                               &(TABLE)->entries)
> > +
> > +struct ls_stateful_tracked_data {
> > +    /* Created or updated logical switch with LB and ACL data. */
> > +    struct hmapx crupdated; /* Stores 'struct ls_stateful_record'. */
> > +};
> > +
> > +struct ed_type_ls_stateful {
> > +    struct ls_stateful_table table;
> > +    struct ls_stateful_tracked_data trk_data;
> > +};
> > +
> > +void *en_ls_stateful_init(struct engine_node *, struct engine_arg *);
> > +void en_ls_stateful_cleanup(void *data);
> > +void en_ls_stateful_clear_tracked_data(void *data);
> > +void en_ls_stateful_run(struct engine_node *, void *data);
> > +
> > +bool ls_stateful_northd_handler(struct engine_node *, void *data);
> > +bool ls_stateful_port_group_handler(struct engine_node *, void *data);
> > +
> > +const struct ls_stateful_record *ls_stateful_table_find(
> > +    const struct ls_stateful_table *, const struct nbrec_logical_switch *);
> > +
> > +static inline bool
> > +ls_stateful_has_tracked_data(struct ls_stateful_tracked_data *trk_data) {
> > +    return !hmapx_is_empty(&trk_data->crupdated);
> > +}
> > +
> > +#endif /* EN_LS_STATEFUL_H */
> > diff --git a/northd/en-northd.c b/northd/en-northd.c
> > index 546397f3dc..5143603f39 100644
> > --- a/northd/en-northd.c
> > +++ b/northd/en-northd.c
> > @@ -171,7 +171,7 @@ northd_nb_logical_switch_handler(struct engine_node 
> > *node,
> >          return false;
> >      }
> >
> > -    if (northd_has_lsps_in_tracked_data(&nd->trk_data)) {
> > +    if (northd_has_tracked_data(&nd->trk_data)) {
> >          engine_set_node_state(node, EN_UPDATED);
> >      }
> >
> > diff --git a/northd/en-port-group.h b/northd/en-port-group.h
> > index 3b28a23694..54014062ce 100644
> > --- a/northd/en-port-group.h
> > +++ b/northd/en-port-group.h
> > @@ -48,6 +48,9 @@ struct ls_port_group_record {
> >      struct sset ports;          /* Subset of 'nb_pg' ports in this record. 
> > */
> >  };
> >
> > +#define LS_PORT_GROUP_TABLE_FOR_EACH(LS_PG, TABLE) \
> > +    HMAP_FOR_EACH (LS_PG, key_node, &(TABLE)->entries)
> > +
> >  void ls_port_group_table_init(struct ls_port_group_table *);
> >  void ls_port_group_table_clear(struct ls_port_group_table *);
> >  void ls_port_group_table_destroy(struct ls_port_group_table *);
> > diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
> > index adb38dde78..9ce4279ee8 100644
> > --- a/northd/inc-proc-northd.c
> > +++ b/northd/inc-proc-northd.c
> > @@ -33,6 +33,7 @@
> >  #include "en-lb-data.h"
> >  #include "en-lr-stateful.h"
> >  #include "en-lr-nat.h"
> > +#include "en-ls-stateful.h"
> >  #include "en-northd.h"
> >  #include "en-lflow.h"
> >  #include "en-northd-output.h"
> > @@ -150,6 +151,7 @@ static ENGINE_NODE(sync_to_sb_pb, "sync_to_sb_pb");
> >  static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lb_data, "lb_data");
> >  static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lr_nat, "lr_nat");
> >  static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lr_stateful, "lr_stateful");
> > +static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(ls_stateful, "ls_stateful");
> >
> >  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
> >                            struct ovsdb_idl_loop *sb)
> > @@ -200,6 +202,10 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
> >      engine_add_input(&en_lr_stateful, &en_lb_data,
> >                       lr_stateful_lb_data_handler);
> >
> > +    engine_add_input(&en_ls_stateful, &en_northd, 
> > ls_stateful_northd_handler);
> > +    engine_add_input(&en_ls_stateful, &en_port_group,
> > +                     ls_stateful_port_group_handler);
> > +
> >      engine_add_input(&en_mac_binding_aging, &en_nb_nb_global, NULL);
> >      engine_add_input(&en_mac_binding_aging, &en_sb_mac_binding, NULL);
> >      engine_add_input(&en_mac_binding_aging, &en_northd, NULL);
> > @@ -222,6 +228,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
> >      engine_add_input(&en_lflow, &en_sb_multicast_group, NULL);
> >      engine_add_input(&en_lflow, &en_sb_igmp_group, NULL);
> >      engine_add_input(&en_lflow, &en_lr_stateful, NULL);
> > +    engine_add_input(&en_lflow, &en_ls_stateful, NULL);
> >      engine_add_input(&en_lflow, &en_northd, lflow_northd_handler);
> >      engine_add_input(&en_lflow, &en_port_group, lflow_port_group_handler);
> >
> > diff --git a/northd/northd.c b/northd/northd.c
> > index f56a7c5ea4..68f2b0bab4 100644
> > --- a/northd/northd.c
> > +++ b/northd/northd.c
> > @@ -46,6 +46,7 @@
> >  #include "en-lb-data.h"
> >  #include "en-lr-nat.h"
> >  #include "en-lr-stateful.h"
> > +#include "en-ls-stateful.h"
> >  #include "lib/ovn-parallel-hmap.h"
> >  #include "ovn/actions.h"
> >  #include "ovn/features.h"
> > @@ -576,7 +577,7 @@ lb_group_has_vip(const struct nbrec_load_balancer_group 
> > *lb_group)
> >  }
> >
> >  static bool
> > -ls_has_lb_vip(struct ovn_datapath *od)
> > +ls_has_lb_vip(const struct ovn_datapath *od)
> >  {
> >      for (size_t i = 0; i < od->nbs->n_load_balancer; i++) {
> >          if (lb_has_vip(od->nbs->load_balancer[i])) {
> > @@ -593,7 +594,7 @@ ls_has_lb_vip(struct ovn_datapath *od)
> >  }
> >
> >  static bool
> > -lr_has_lb_vip(struct ovn_datapath *od)
> > +lr_has_lb_vip(const struct ovn_datapath *od)
> >  {
> >      for (size_t i = 0; i < od->nbr->n_load_balancer; i++) {
> >          if (lb_has_vip(od->nbr->load_balancer[i])) {
> > @@ -609,13 +610,13 @@ lr_has_lb_vip(struct ovn_datapath *od)
> >      return false;
> >  }
> >
> > -static void
> > -init_lb_for_datapath(struct ovn_datapath *od)
> > +bool
> > +od_has_lb_vip(const struct ovn_datapath *od)
> >  {
> >      if (od->nbs) {
> > -        od->has_lb_vip = ls_has_lb_vip(od);
> > +        return ls_has_lb_vip(od);
> >      } else {
> > -        od->has_lb_vip = lr_has_lb_vip(od);
> > +        return lr_has_lb_vip(od);
> >      }
> >  }
> >
> > @@ -1065,7 +1066,6 @@ join_datapaths(const struct 
> > nbrec_logical_switch_table *nbrec_ls_table,
> >
> >          init_ipam_info_for_datapath(od);
> >          init_mcast_info_for_datapath(od);
> > -        init_lb_for_datapath(od);
> >      }
> >
> >      const struct nbrec_logical_router *nbr;
> > @@ -1096,7 +1096,6 @@ join_datapaths(const struct 
> > nbrec_logical_switch_table *nbrec_ls_table,
> >              ovs_list_push_back(nb_only, &od->list);
> >          }
> >          init_mcast_info_for_datapath(od);
> > -        init_lb_for_datapath(od);
> >          if (smap_get(&od->nbr->options, "chassis")) {
> >              od->is_gw_router = true;
> >          }
> > @@ -2583,7 +2582,8 @@ get_nat_addresses(const struct ovn_port *op, size_t 
> > *n, bool routable_only,
> >      size_t n_nats = 0;
> >      struct eth_addr mac;
> >      if (!op || !op->nbrp || !op->od || !op->od->nbr
> > -        || (!op->od->nbr->n_nat && !op->od->has_lb_vip)
> > +        || (!op->od->nbr->n_nat && (!lr_stateful_rec
> > +                                    || !lr_stateful_rec->has_lb_vip))
>
> IMO this is very hard to read.  What about a simple inline function in
> en-lr-stateful.h to do this instead?  E.g.:
>
> static inline bool
> lr_stateful_rec_has_lb_vip(const struct lr_stateful_record* lr_rec)
> {
>     return lr_rec && lr_rec->has_lb_vip;
> }

Ack


>
> >          || !eth_addr_from_string(op->nbrp->mac, &mac)) {
> >          *n = n_nats;
> >          return NULL;
> > @@ -3830,7 +3830,7 @@ build_lrouter_lbs_check(const struct ovn_datapaths 
> > *lr_datapaths)
> >      HMAP_FOR_EACH (od, key_node, &lr_datapaths->datapaths) {
> >          ovs_assert(od->nbr);
> >
> > -        if (od->has_lb_vip && od->n_l3dgw_ports > 1
> > +        if (od_has_lb_vip(od) && od->n_l3dgw_ports > 1
>
> This walks all LBs applied to the router again.  It's probably fine to
> just query the lr_stateful_rec associated to this router (it's O(1) to
> do that).

build_lrouter_lbs_check() is part of the northd engine and hence we
can't use the lr_stateful_rec here
as en_lr_stateful is not the input node to en_northd engine node.  Its
actually the other way.

This function only prints warning log.  Shall we move this instead to
lr_stateful.c ?

I'll address all other comments and add your Acked-by.  Let me know if
that's not fine as I can't address the above comment.

Thanks
Numan


>
> >                  && !smap_get(&od->nbr->options, "chassis")) {
> >              static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> >              VLOG_WARN_RL(&rl, "Load-balancers are configured on logical "
> > @@ -4750,6 +4750,8 @@ destroy_northd_data_tracked_changes(struct 
> > northd_data *nd)
> >      destroy_tracked_ovn_ports(&trk_changes->trk_lsps);
> >      destroy_tracked_lbs(&trk_changes->trk_lbs);
> >      hmapx_clear(&trk_changes->lr_with_changed_nats);
> > +    hmapx_clear(&trk_changes->ls_with_changed_lbs);
> > +    hmapx_clear(&trk_changes->ls_with_changed_acls);
> >      nd->trk_data.type = NORTHD_TRACKED_NONE;
> >  }
> >
> > @@ -4764,6 +4766,8 @@ init_northd_tracked_data(struct northd_data *nd)
> >      hmapx_init(&trk_data->trk_lbs.crupdated);
> >      hmapx_init(&trk_data->trk_lbs.deleted);
> >      hmapx_init(&trk_data->lr_with_changed_nats);
> > +    hmapx_init(&trk_data->ls_with_changed_lbs);
> > +    hmapx_init(&trk_data->ls_with_changed_acls);
> >  }
> >
> >  static void
> > @@ -4777,6 +4781,8 @@ destroy_northd_tracked_data(struct northd_data *nd)
> >      hmapx_destroy(&trk_data->trk_lbs.crupdated);
> >      hmapx_destroy(&trk_data->trk_lbs.deleted);
> >      hmapx_destroy(&trk_data->lr_with_changed_nats);
> > +    hmapx_destroy(&trk_data->ls_with_changed_lbs);
> > +    hmapx_destroy(&trk_data->ls_with_changed_acls);
> >  }
> >
> >  /* Check if a changed LSP can be handled incrementally within the I-P 
> > engine
> > @@ -4892,6 +4898,7 @@ ls_port_create(struct ovsdb_idl_txn *ovnsb_txn, 
> > struct hmap *ls_ports,
> >   *    - logical switch ports.
> >   *    - load balancers.
> >   *    - load balancer groups.
> > + *    - ACLs
> >   */
> >  static bool
> >  ls_changes_can_be_handled(
> > @@ -5116,6 +5123,25 @@ fail:
> >      return false;
> >  }
> >
> > +static bool
> > +is_acls_seqno_changed(struct nbrec_acl **nb_acls, size_t n_nb_acls)
> > +{
> > +    for (size_t i = 0; i < n_nb_acls; i++) {
> > +        if (nbrec_acl_row_get_seqno(nb_acls[i],
> > +                                    OVSDB_IDL_CHANGE_MODIFY) > 0) {
> > +            return true;
> > +        }
> > +    }
> > +
> > +    return false;
> > +}
> > +
> > +static bool
> > +is_ls_acls_changed(const struct nbrec_logical_switch *nbs) {
> > +    return (nbrec_logical_switch_is_updated(nbs, 
> > NBREC_LOGICAL_SWITCH_COL_ACLS)
> > +            || is_acls_seqno_changed(nbs->acls, nbs->n_acls));
> > +}
> > +
> >  /* Return true if changes are handled incrementally, false otherwise.
> >   * When there are any changes, try to track what's exactly changed and set
> >   * northd_data->trk_data accordingly.
> > @@ -5157,6 +5183,10 @@ northd_handle_ls_changes(struct ovsdb_idl_txn 
> > *ovnsb_idl_txn,
> >                                     ni, nd, od, &trk_data->trk_lsps)) {
> >              goto fail;
> >          }
> > +
> > +        if (is_ls_acls_changed(changed_ls)) {
> > +            hmapx_add(&trk_data->ls_with_changed_acls, od);
> > +        }
> >      }
> >
> >      if (!hmapx_is_empty(&trk_data->trk_lsps.created)
> > @@ -5165,6 +5195,10 @@ northd_handle_ls_changes(struct ovsdb_idl_txn 
> > *ovnsb_idl_txn,
> >          trk_data->type |= NORTHD_TRACKED_PORTS;
> >      }
> >
> > +    if (!hmapx_is_empty(&trk_data->ls_with_changed_acls)) {
> > +        trk_data->type |= NORTHD_TRACKED_LS_ACLS;
> > +    }
> > +
> >      return true;
> >
> >  fail:
> > @@ -5386,10 +5420,6 @@ northd_handle_sb_port_binding_changes(
> >   *    due to association of a load balancer (eg. ovn-nbctl ls-lb-add sw0 
> > lb1),
> >   *    the logical switch datapath is added to the load balancer 
> > (represented
> >   *    by 'struct ovn_lb_datapaths') by calling ovn_lb_datapaths_add_ls().
> > - *
> > - *  - For every 'lb' in the tracked data (trk_lb_data->crupdated_lbs) ,
> > - *    it gets the associated logical switches and for each switch it
> > - *    re-evaluates 'od->has_lb_vip' to reflect any changes to the lb vips.
> >   * */
> >  bool
> >  northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
> > @@ -5450,13 +5480,13 @@ northd_handle_lb_data_changes(struct 
> > tracked_lb_data *trk_lb_data,
> >          lb_dps = ovn_lb_datapaths_find(lb_datapaths_map, lb_uuid);
> >          ovs_assert(lb_dps);
> >
> > -        /* Re-evaluate 'od->has_lb_vip for od's associated with the
> > -         * deleted lb. */
> >          size_t index;
> >          BITMAP_FOR_EACH_1 (index, ods_size(ls_datapaths),
> >                             lb_dps->nb_ls_map) {
> >              od = ls_datapaths->array[index];
> > -            init_lb_for_datapath(od);
> > +
> > +            /* Add the ls datapath to the northd tracked data. */
> > +            hmapx_add(&nd_changes->ls_with_changed_lbs, od);
> >          }
> >
> >          hmap_remove(lb_datapaths_map, &lb_dps->hmap_node);
> > @@ -5536,8 +5566,8 @@ northd_handle_lb_data_changes(struct tracked_lb_data 
> > *trk_lb_data,
> >              }
> >          }
> >
> > -        /* Re-evaluate 'od->has_lb_vip' */
> > -        init_lb_for_datapath(od);
> > +        /* Add the ls datapath to the northd tracked data. */
> > +        hmapx_add(&nd_changes->ls_with_changed_lbs, od);
> >      }
> >
> >      LIST_FOR_EACH (codlb, list_node, &trk_lb_data->crupdated_lr_lbs) {
> > @@ -5572,9 +5602,6 @@ northd_handle_lb_data_changes(struct tracked_lb_data 
> > *trk_lb_data,
> >                  hmapx_add(&nd_changes->trk_lbs.crupdated, lb_dps);
> >              }
> >          }
> > -
> > -        /* Re-evaluate 'od->has_lb_vip' */
> > -        init_lb_for_datapath(od);
> >      }
> >
> >      HMAP_FOR_EACH (clb, hmap_node, &trk_lb_data->crupdated_lbs) {
> > @@ -5587,15 +5614,9 @@ northd_handle_lb_data_changes(struct tracked_lb_data 
> > *trk_lb_data,
> >          BITMAP_FOR_EACH_1 (index, ods_size(ls_datapaths),
> >                             lb_dps->nb_ls_map) {
> >              od = ls_datapaths->array[index];
> > -            /* Re-evaluate 'od->has_lb_vip' */
> > -            init_lb_for_datapath(od);
> > -        }
> >
> > -        BITMAP_FOR_EACH_1 (index, ods_size(lr_datapaths),
> > -                           lb_dps->nb_lr_map) {
> > -            od = lr_datapaths->array[index];
> > -            /* Re-evaluate 'od->has_lb_vip' */
> > -            init_lb_for_datapath(od);
> > +            /* Add the ls datapath to the northd tracked data. */
> > +            hmapx_add(&nd_changes->ls_with_changed_lbs, od);
> >          }
> >      }
> >
> > @@ -5617,17 +5638,14 @@ northd_handle_lb_data_changes(struct 
> > tracked_lb_data *trk_lb_data,
> >              for (size_t i = 0; i < lbgrp_dps->n_lr; i++) {
> >                  od = lbgrp_dps->lr[i];
> >                  ovn_lb_datapaths_add_lr(lb_dps, 1, &od);
> > -
> > -                /* Re-evaluate 'od->has_lb_vip' */
> > -                init_lb_for_datapath(od);
> >              }
> >
> >              for (size_t i = 0; i < lbgrp_dps->n_ls; i++) {
> >                 od = lbgrp_dps->ls[i];
> >                  ovn_lb_datapaths_add_ls(lb_dps, 1, &od);
> >
> > -                /* Re-evaluate 'od->has_lb_vip' */
> > -                init_lb_for_datapath(od);
> > +                /* Add the ls datapath to the northd tracked data. */
> > +                hmapx_add(&nd_changes->ls_with_changed_lbs, od);
> >              }
> >
> >              /* Add the lb to the northd tracked data. */
> > @@ -5640,6 +5658,10 @@ northd_handle_lb_data_changes(struct tracked_lb_data 
> > *trk_lb_data,
> >          nd_changes->type |= NORTHD_TRACKED_LBS;
> >      }
> >
> > +    if (!hmapx_is_empty(&nd_changes->ls_with_changed_lbs)) {
> > +        nd_changes->type |= NORTHD_TRACKED_LS_LBS;
> > +    }
> > +
> >      return true;
> >  }
> >
> > @@ -6573,63 +6595,6 @@ build_dhcpv6_action(struct ovn_port *op, struct 
> > in6_addr *offer_ip,
> >      return true;
> >  }
> >
> > -static bool
> > -od_set_acl_flags(struct ovn_datapath *od, struct nbrec_acl **acls,
> > -                 size_t n_acls)
> > -{
> > -    /* A true return indicates that there are no possible ACL flags
> > -     * left to set on od. A false return indicates that further ACLs
> > -     * should be explored in case more flags need to be set on od
> > -     */
> > -    if (!n_acls) {
> > -        return false;
> > -    }
> > -
> > -    od->has_acls = true;
> > -    for (size_t i = 0; i < n_acls; i++) {
> > -        const struct nbrec_acl *acl = acls[i];
> > -        if (acl->tier > od->max_acl_tier) {
> > -            od->max_acl_tier = acl->tier;
> > -        }
> > -        if (!od->has_stateful_acl && !strcmp(acl->action, 
> > "allow-related")) {
> > -            od->has_stateful_acl = true;
> > -        }
> > -        if (od->has_stateful_acl &&
> > -            od->max_acl_tier == nbrec_acl_col_tier.type.value.integer.max) 
> > {
> > -            return true;
> > -        }
> > -    }
> > -
> > -    return false;
> > -}
> > -
> > -static void
> > -ls_get_acl_flags(struct ovn_datapath *od,
> > -                 const struct ls_port_group_table *ls_port_groups)
> > -{
> > -    od->has_acls = false;
> > -    od->has_stateful_acl = false;
> > -    od->max_acl_tier = 0;
> > -
> > -    if (od_set_acl_flags(od, od->nbs->acls, od->nbs->n_acls)) {
> > -        return;
> > -    }
> > -
> > -    const struct ls_port_group *ls_pg =
> > -        ls_port_group_table_find(ls_port_groups, od->nbs);
> > -    if (!ls_pg) {
> > -        return;
> > -    }
> > -
> > -    const struct ls_port_group_record *ls_pg_rec;
> > -    HMAP_FOR_EACH (ls_pg_rec, key_node, &ls_pg->nb_pgs) {
> > -        if (od_set_acl_flags(od, ls_pg_rec->nb_pg->acls,
> > -                             ls_pg_rec->nb_pg->n_acls)) {
> > -            return;
> > -        }
> > -    }
> > -}
> > -
> >  /* Adds the logical flows in the (in/out) check port sec stage only if
> >   *   - the lport is disabled or
> >   *   - lport is of type vtep - to skip the ingress pipeline.
> > @@ -6777,9 +6742,10 @@ build_lswitch_output_port_sec_od(struct ovn_datapath 
> > *od,
> >  }
> >
> >  static void
> > -skip_port_from_conntrack(struct ovn_datapath *od, struct ovn_port *op,
> > -                         enum ovn_stage in_stage, enum ovn_stage out_stage,
> > -                         uint16_t priority, struct hmap *lflows)
> > +skip_port_from_conntrack(const struct ovn_datapath *od, struct ovn_port 
> > *op,
> > +                         bool has_stateful_acl, enum ovn_stage in_stage,
> > +                         enum ovn_stage out_stage, uint16_t priority,
> > +                         struct hmap *lflows)
> >  {
> >      /* Can't use ct() for router ports. Consider the following 
> > configuration:
> >       * lp1(10.0.0.2) on hostA--ls1--lr0--ls2--lp2(10.0.1.2) on hostB, For a
> > @@ -6792,7 +6758,7 @@ skip_port_from_conntrack(struct ovn_datapath *od, 
> > struct ovn_port *op,
> >       * conntrack state across all chassis. */
> >
> >      const char *ingress_action = "next;";
> > -    const char *egress_action = od->has_stateful_acl
> > +    const char *egress_action = has_stateful_acl
> >                                  ? "next;"
> >                                  : "ct_clear; next;";
> >
> > @@ -6811,7 +6777,7 @@ skip_port_from_conntrack(struct ovn_datapath *od, 
> > struct ovn_port *op,
> >  }
> >
> >  static void
> > -build_stateless_filter(struct ovn_datapath *od,
> > +build_stateless_filter(const struct ovn_datapath *od,
> >                         const struct nbrec_acl *acl,
> >                         struct hmap *lflows)
> >  {
> > @@ -6832,7 +6798,7 @@ build_stateless_filter(struct ovn_datapath *od,
> >  }
> >
> >  static void
> > -build_stateless_filters(struct ovn_datapath *od,
> > +build_stateless_filters(const struct ovn_datapath *od,
> >                          const struct ls_port_group_table *ls_port_groups,
> >                          struct hmap *lflows)
> >  {
> > @@ -6862,9 +6828,7 @@ build_stateless_filters(struct ovn_datapath *od,
> >  }
> >
> >  static void
> > -build_pre_acls(struct ovn_datapath *od,
> > -               const struct ls_port_group_table *ls_port_groups,
> > -               struct hmap *lflows)
> > +build_pre_acls(struct ovn_datapath *od, struct hmap *lflows)
> >  {
> >      /* Ingress and Egress Pre-ACL Table (Priority 0): Packets are
> >       * allowed by default. */
> > @@ -6876,22 +6840,30 @@ build_pre_acls(struct ovn_datapath *od,
> >
> >      ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110,
> >                    "eth.src == $svc_monitor_mac", "next;");
> > +}
> > +
> > +static void
> > +build_ls_stateful_rec_pre_acls(const struct ls_stateful_record 
> > *ls_sful_rec,
> > +                             const struct ls_port_group_table 
> > *ls_port_groups,
> > +                             struct hmap *lflows)
> > +{
> > +    const struct ovn_datapath *od = ls_sful_rec->od;
>
> Nit: for LRs we mostly use lr_stateful_rec as a variable name.  I think
> I'd prefer something similar for switches too, i.e., 'ls_stateful_rec'
> instead of 'ls_sful_rec'.
>
> >
> >      /* If there are any stateful ACL rules in this datapath, we may
> >       * send IP packets for some (allow) filters through the conntrack 
> > action,
> >       * which handles defragmentation, in order to match L4 headers. */
> > -    if (od->has_stateful_acl) {
> > +    if (ls_sful_rec->has_stateful_acl) {
> >          for (size_t i = 0; i < od->n_router_ports; i++) {
> >              struct ovn_port *op = od->router_ports[i];
> >              if (op->enable_router_port_acl) {
> >                  continue;
> >              }
> > -            skip_port_from_conntrack(od, op,
> > +            skip_port_from_conntrack(od, op, true,
> >                                       S_SWITCH_IN_PRE_ACL, 
> > S_SWITCH_OUT_PRE_ACL,
> >                                       110, lflows);
> >          }
> >          for (size_t i = 0; i < od->n_localnet_ports; i++) {
> > -            skip_port_from_conntrack(od, od->localnet_ports[i],
> > +            skip_port_from_conntrack(od, od->localnet_ports[i], true,
> >                                       S_SWITCH_IN_PRE_ACL,
> >                                       S_SWITCH_OUT_PRE_ACL,
> >                                       110, lflows);
> > @@ -6929,7 +6901,7 @@ build_pre_acls(struct ovn_datapath *od,
> >                        REGBIT_CONNTRACK_DEFRAG" = 1; next;");
> >          ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 100, "ip",
> >                        REGBIT_CONNTRACK_DEFRAG" = 1; next;");
> > -    } else if (od->has_lb_vip) {
> > +    } else if (ls_sful_rec->has_lb_vip) {
> >          /* We'll build stateless filters if there are LB rules so that
> >           * the stateless flows are not tracked in pre-lb. */
> >           build_stateless_filters(od, ls_port_groups, lflows);
> > @@ -7057,30 +7029,40 @@ build_pre_lb(struct ovn_datapath *od, const struct 
> > shash *meter_groups,
> >      ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 0, "1", "next;");
> >      ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, 0, "1", "next;");
> >
> > +    /* Do not send statless flows via conntrack */
> > +    ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 110,
> > +                  REGBIT_ACL_STATELESS" == 1", "next;");
> > +    ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, 110,
> > +                  REGBIT_ACL_STATELESS" == 1", "next;");
> > +}
> > +
> > +static void
> > +build_ls_stateful_rec_pre_lb(const struct ls_stateful_record 
> > *ls_stateful_rec,
> > +                             struct hmap *lflows)
> > +{
> > +    const struct ovn_datapath *od = ls_stateful_rec->od;
> > +
> >      for (size_t i = 0; i < od->n_router_ports; i++) {
> >          skip_port_from_conntrack(od, od->router_ports[i],
> > +                                 ls_stateful_rec->has_stateful_acl,
> >                                   S_SWITCH_IN_PRE_LB, S_SWITCH_OUT_PRE_LB,
> >                                   110, lflows);
> >      }
> > +
> >      /* Localnet ports have no need for going through conntrack, unless
> >       * the logical switch has a load balancer. Then, conntrack is necessary
> >       * so that traffic arriving via the localnet port can be load
> >       * balanced.
> >       */
> > -    if (!od->has_lb_vip) {
> > +    if (!ls_stateful_rec->has_lb_vip) {
> >          for (size_t i = 0; i < od->n_localnet_ports; i++) {
> >              skip_port_from_conntrack(od, od->localnet_ports[i],
> > +                                     ls_stateful_rec->has_stateful_acl,
> >                                       S_SWITCH_IN_PRE_LB, 
> > S_SWITCH_OUT_PRE_LB,
> >                                       110, lflows);
> >          }
> >      }
> >
> > -    /* Do not sent statless flows via conntrack */
> > -    ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 110,
> > -                  REGBIT_ACL_STATELESS" == 1", "next;");
> > -    ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, 110,
> > -                  REGBIT_ACL_STATELESS" == 1", "next;");
> > -
> >      /* 'REGBIT_CONNTRACK_NAT' is set to let the pre-stateful table send
> >       * packet to conntrack for defragmentation and possibly for unNATting.
> >       *
> > @@ -7111,7 +7093,7 @@ build_pre_lb(struct ovn_datapath *od, const struct 
> > shash *meter_groups,
> >       * ingress pipeline if a load balancer is configured. We can now
> >       * add a lflow to drop ct.inv packets.
> >       */
> > -    if (od->has_lb_vip) {
> > +    if (ls_stateful_rec->has_lb_vip) {
> >          ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB,
> >                        100, "ip", REGBIT_CONNTRACK_NAT" = 1; next;");
> >          ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB,
> > @@ -7152,10 +7134,12 @@ build_pre_stateful(struct ovn_datapath *od,
> >  }
> >
> >  static void
> > -build_acl_hints(struct ovn_datapath *od,
> > +build_acl_hints(const struct ls_stateful_record *ls_sful_rec,
> >                  const struct chassis_features *features,
> >                  struct hmap *lflows)
> >  {
> > +    const struct ovn_datapath *od = ls_sful_rec->od;
> > +
> >      /* This stage builds hints for the IN/OUT_ACL stage. Based on various
> >       * combinations of ct flags packets may hit only a subset of the 
> > logical
> >       * flows in the IN/OUT_ACL stage.
> > @@ -7179,13 +7163,13 @@ build_acl_hints(struct ovn_datapath *od,
> >          const char *match;
> >
> >          /* In any case, advance to the next stage. */
> > -        if (!od->has_acls && !od->has_lb_vip) {
> > +        if (!ls_sful_rec->has_acls && !ls_sful_rec->has_lb_vip) {
> >              ovn_lflow_add(lflows, od, stage, UINT16_MAX, "1", "next;");
> >          } else {
> >              ovn_lflow_add(lflows, od, stage, 0, "1", "next;");
> >          }
> >
> > -        if (!od->has_stateful_acl && !od->has_lb_vip) {
> > +        if (!ls_sful_rec->has_stateful_acl && !ls_sful_rec->has_lb_vip) {
> >              continue;
> >          }
> >
> > @@ -7321,10 +7305,10 @@ build_acl_log(struct ds *actions, const struct 
> > nbrec_acl *acl,
> >  }
> >
> >  static void
> > -consider_acl(struct hmap *lflows, struct ovn_datapath *od,
> > +consider_acl(struct hmap *lflows, const struct ovn_datapath *od,
> >               const struct nbrec_acl *acl, bool has_stateful,
> >               bool ct_masked_mark, const struct shash *meter_groups,
> > -             struct ds *match, struct ds *actions)
> > +             uint64_t max_acl_tier, struct ds *match, struct ds *actions)
> >  {
> >      const char *ct_blocked_match = ct_masked_mark
> >                                     ? "ct_mark.blocked"
> > @@ -7361,7 +7345,7 @@ consider_acl(struct hmap *lflows, struct ovn_datapath 
> > *od,
> >      /* All ACLS will start by matching on their respective tier. */
> >      size_t match_tier_len = 0;
> >      ds_clear(match);
> > -    if (od->max_acl_tier) {
> > +    if (max_acl_tier) {
> >          ds_put_format(match, REG_ACL_TIER " == %"PRId64" && ", acl->tier);
> >          match_tier_len = match->length;
> >      }
> > @@ -7548,12 +7532,15 @@ ovn_update_ipv6_options(struct hmap *lr_ports)
> >  #define IPV6_CT_OMIT_MATCH "nd || nd_ra || nd_rs || mldv1 || mldv2"
> >
> >  static void
> > -build_acl_action_lflows(struct ovn_datapath *od, struct hmap *lflows,
> > +build_acl_action_lflows(const struct ls_stateful_record *ls_stateful_rec,
> > +                        struct hmap *lflows,
> >                          const char *default_acl_action,
> >                          const struct shash *meter_groups,
> >                          struct ds *match,
> >                          struct ds *actions)
> >  {
> > +    const struct ovn_datapath *od = ls_stateful_rec->od;
> > +
> >      enum ovn_stage stages [] = {
> >          S_SWITCH_IN_ACL_ACTION,
> >          S_SWITCH_IN_ACL_AFTER_LB_ACTION,
> > @@ -7564,7 +7551,7 @@ build_acl_action_lflows(struct ovn_datapath *od, 
> > struct hmap *lflows,
> >      ds_put_cstr(actions, REGBIT_ACL_VERDICT_ALLOW " = 0; "
> >                          REGBIT_ACL_VERDICT_DROP " = 0; "
> >                          REGBIT_ACL_VERDICT_REJECT " = 0; ");
> > -    if (od->max_acl_tier) {
> > +    if (ls_stateful_rec->max_acl_tier) {
> >          ds_put_cstr(actions, REG_ACL_TIER " = 0; ");
> >      }
> >
> > @@ -7572,7 +7559,7 @@ build_acl_action_lflows(struct ovn_datapath *od, 
> > struct hmap *lflows,
> >
> >      for (size_t i = 0; i < ARRAY_SIZE(stages); i++) {
> >          enum ovn_stage stage = stages[i];
> > -        if (!od->has_acls) {
> > +        if (!ls_stateful_rec->has_acls) {
> >              ovn_lflow_add(lflows, od, stage, 0, "1", "next;");
> >              continue;
> >          }
> > @@ -7607,7 +7594,7 @@ build_acl_action_lflows(struct ovn_datapath *od, 
> > struct hmap *lflows,
> >          ovn_lflow_add(lflows, od, stage, 0, "1", ds_cstr(actions));
> >
> >          struct ds tier_actions = DS_EMPTY_INITIALIZER;
> > -        for (size_t j = 0; j < od->max_acl_tier; j++) {
> > +        for (size_t j = 0; j < ls_stateful_rec->max_acl_tier; j++) {
> >              ds_clear(match);
> >              ds_put_format(match, REG_ACL_TIER " == %"PRIuSIZE, j);
> >              ds_clear(&tier_actions);
> > @@ -7623,7 +7610,7 @@ build_acl_action_lflows(struct ovn_datapath *od, 
> > struct hmap *lflows,
> >  }
> >
> >  static void
> > -build_acl_log_related_flows(struct ovn_datapath *od, struct hmap *lflows,
> > +build_acl_log_related_flows(const struct ovn_datapath *od, struct hmap 
> > *lflows,
> >                              const struct nbrec_acl *acl, bool has_stateful,
> >                              bool ct_masked_mark,
> >                              const struct shash *meter_groups,
> > @@ -7696,15 +7683,19 @@ build_acl_log_related_flows(struct ovn_datapath 
> > *od, struct hmap *lflows,
> >  }
> >
> >  static void
> > -build_acls(struct ovn_datapath *od, const struct chassis_features 
> > *features,
> > +build_acls(const struct ls_stateful_record *ls_stateful_rec,
> > +           const struct chassis_features *features,
> >             struct hmap *lflows,
> >             const struct ls_port_group_table *ls_port_groups,
> >             const struct shash *meter_groups)
> >  {
> > +    const struct ovn_datapath *od = ls_stateful_rec->od;
> > +
> >      const char *default_acl_action = default_acl_drop
> >                                       ? debug_implicit_drop_action()
> >                                       : "next;";
> > -    bool has_stateful = od->has_stateful_acl || od->has_lb_vip;
> > +    bool has_stateful = (ls_stateful_rec->has_stateful_acl
> > +                         || ls_stateful_rec->has_lb_vip);
> >      const char *ct_blocked_match = features->ct_no_masked_label
> >                                     ? "ct_mark.blocked"
> >                                     : "ct_label.blocked";
> > @@ -7718,8 +7709,8 @@ build_acls(struct ovn_datapath *od, const struct 
> > chassis_features *features,
> >       *
> >       * A related rule at priority 1 is added below if there
> >       * are any stateful ACLs in this datapath. */
> > -    if (!od->has_acls) {
> > -        if (!od->has_lb_vip) {
> > +    if (!ls_stateful_rec->has_acls) {
> > +        if (!ls_stateful_rec->has_lb_vip) {
> >              ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL_EVAL, UINT16_MAX, 
> > "1",
> >                            "next;");
> >              ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL_EVAL, UINT16_MAX, 
> > "1",
> > @@ -7882,7 +7873,8 @@ build_acls(struct ovn_datapath *od, const struct 
> > chassis_features *features,
> >                                      meter_groups, &match, &actions);
> >          consider_acl(lflows, od, acl, has_stateful,
> >                       features->ct_no_masked_label,
> > -                     meter_groups, &match, &actions);
> > +                     meter_groups, ls_stateful_rec->max_acl_tier,
> > +                     &match, &actions);
> >      }
> >
> >      const struct ls_port_group *ls_pg =
> > @@ -7898,7 +7890,8 @@ build_acls(struct ovn_datapath *od, const struct 
> > chassis_features *features,
> >                                              meter_groups, &match, 
> > &actions);
> >                  consider_acl(lflows, od, acl, has_stateful,
> >                               features->ct_no_masked_label,
> > -                             meter_groups, &match, &actions);
> > +                             meter_groups, ls_stateful_rec->max_acl_tier,
> > +                             &match, &actions);
> >              }
> >          }
> >      }
> > @@ -7916,7 +7909,7 @@ build_acls(struct ovn_datapath *od, const struct 
> > chassis_features *features,
> >              dns_actions);
> >      }
> >
> > -    if (od->has_acls || od->has_lb_vip) {
> > +    if (ls_stateful_rec->has_acls || ls_stateful_rec->has_lb_vip) {
> >          /* Add a 34000 priority flow to advance the service monitor reply
> >          * packets to skip applying ingress ACLs. */
> >          ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL_EVAL, 34000,
> > @@ -7930,8 +7923,8 @@ build_acls(struct ovn_datapath *od, const struct 
> > chassis_features *features,
> >                      REGBIT_ACL_VERDICT_ALLOW" = 1; next;");
> >      }
> >
> > -    build_acl_action_lflows(od, lflows, default_acl_action, meter_groups,
> > -                            &match, &actions);
> > +    build_acl_action_lflows(ls_stateful_rec, lflows, default_acl_action,
> > +                            meter_groups, &match, &actions);
> >
> >      ds_destroy(&match);
> >      ds_destroy(&actions);
> > @@ -8576,8 +8569,11 @@ build_stateful(struct ovn_datapath *od,
> >  }
> >
> >  static void
> > -build_lb_hairpin(struct ovn_datapath *od, struct hmap *lflows)
> > +build_lb_hairpin(const struct ls_stateful_record *ls_stateful_rec,
> > +                 struct hmap *lflows)
> >  {
> > +    const struct ovn_datapath *od = ls_stateful_rec->od;
> > +
> >      /* Ingress Pre-Hairpin/Nat-Hairpin/Hairpin tabled (Priority 0).
> >       * Packets that don't need hairpinning should continue processing.
> >       */
> > @@ -8585,7 +8581,7 @@ build_lb_hairpin(struct ovn_datapath *od, struct hmap 
> > *lflows)
> >      ovn_lflow_add(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 0, "1", "next;");
> >      ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 0, "1", "next;");
> >
> > -    if (od->has_lb_vip) {
> > +    if (ls_stateful_rec->has_lb_vip) {
> >          /* Check if the packet needs to be hairpinned.
> >           * Set REGBIT_HAIRPIN in the original direction and
> >           * REGBIT_HAIRPIN_REPLY in the reply direction.
> > @@ -9242,9 +9238,7 @@ build_dhcpv4_options_flows(struct ovn_port *op,
> >                      &op->nbsp->dhcpv4_options->options, "lease_time");
> >                  ovs_assert(server_id && server_mac && lease_time);
> >                  const char *dhcp_actions =
> > -                    (op->od->has_stateful_acl || op->od->has_lb_vip)
> > -                     ? REGBIT_ACL_VERDICT_ALLOW" = 1; ct_commit; next;"
> > -                     : REGBIT_ACL_VERDICT_ALLOW" = 1; next;";
> > +                    REGBIT_ACL_VERDICT_ALLOW" = 1; next;";
> >                  ds_clear(&match);
> >                  ds_put_format(&match, "outport == %s && eth.src == %s "
> >                                "&& ip4.src == %s && udp && udp.src == 67 "
> > @@ -9327,9 +9321,7 @@ build_dhcpv6_options_flows(struct ovn_port *op,
> >                  ipv6_string_mapped(server_ip, &lla);
> >
> >                  const char *dhcp6_actions =
> > -                    (op->od->has_stateful_acl || op->od->has_lb_vip)
> > -                        ? REGBIT_ACL_VERDICT_ALLOW" = 1; ct_commit; next;"
> > -                        : REGBIT_ACL_VERDICT_ALLOW" = 1; next;";
> > +                    REGBIT_ACL_VERDICT_ALLOW" = 1; next;";
> >                  ds_clear(&match);
> >                  ds_put_format(&match, "outport == %s && eth.src == %s "
> >                                "&& ip6.src == %s && udp && udp.src == 547 "
> > @@ -9436,22 +9428,16 @@ build_lswitch_lflows_l2_unknown(struct ovn_datapath 
> > *od,
> >  static void
> >  build_lswitch_lflows_pre_acl_and_acl(
> >      struct ovn_datapath *od,
> > -    const struct ls_port_group_table *ls_port_groups,
> >      const struct chassis_features *features,
> >      struct hmap *lflows,
> >      const struct shash *meter_groups)
> >  {
> >      ovs_assert(od->nbs);
> > -    ls_get_acl_flags(od, ls_port_groups);
> > -
> > -    build_pre_acls(od, ls_port_groups, lflows);
> > +    build_pre_acls(od, lflows);
> >      build_pre_lb(od, meter_groups, lflows);
> >      build_pre_stateful(od, features, lflows);
> > -    build_acl_hints(od, features, lflows);
> > -    build_acls(od, features, lflows, ls_port_groups, meter_groups);
> >      build_qos(od, lflows);
> >      build_stateful(od, features, lflows);
> > -    build_lb_hairpin(od, lflows);
> >      build_vtep_hairpin(od, lflows);
> >  }
> >
> > @@ -15511,7 +15497,7 @@ build_lrouter_nat_defrag_and_lb(
> >       * a dynamically negotiated FTP data channel), but will allow
> >       * related traffic such as an ICMP Port Unreachable through
> >       * that's generated from a non-listening UDP port.  */
> > -    if (od->has_lb_vip && features->ct_lb_related) {
> > +    if (lr_stateful_rec->has_lb_vip && features->ct_lb_related) {
> >          ds_clear(match);
> >
> >          ds_put_cstr(match, "ct.rel && !ct.est && !ct.new");
> > @@ -15536,7 +15522,7 @@ build_lrouter_nat_defrag_and_lb(
> >       * Pass the traffic that is already established to the next table with
> >       * proper flags set.
> >       */
> > -    if (od->has_lb_vip) {
> > +    if (lr_stateful_rec->has_lb_vip) {
> >          ds_clear(match);
> >
> >          ds_put_format(match, "ct.est && !ct.rel && !ct.new && %s.natted",
> > @@ -15566,7 +15552,7 @@ build_lrouter_nat_defrag_and_lb(
> >       * not committed, it would produce ongoing datapath flows with the 
> > ct.new
> >       * flag set. Some NICs are unable to offload these flows.
> >       */
> > -    if (od->is_gw_router && (od->nbr->n_nat || od->has_lb_vip)) {
> > +    if (od->is_gw_router && (od->nbr->n_nat || 
> > lr_stateful_rec->has_lb_vip)) {
> >          /* Do not send ND or ICMP packets to connection tracking. */
> >          ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 100,
> >                        "nd || nd_rs || nd_ra", "next;");
> > @@ -15987,6 +15973,22 @@ build_lr_stateful_flows(const struct 
> > lr_stateful_record *lr_stateful_rec,
> >                                        meter_groups);
> >  }
> >
> > +static void
> > +build_ls_stateful_flows(const struct ls_stateful_record *ls_stateful_rec,
> > +                      const struct ls_port_group_table *ls_pgs,
> > +                      const struct chassis_features *features,
> > +                      const struct shash *meter_groups,
> > +                      struct hmap *lflows)
> > +{
> > +    ovs_assert(ls_stateful_rec->od);
> > +
> > +    build_ls_stateful_rec_pre_acls(ls_stateful_rec, ls_pgs, lflows);
> > +    build_ls_stateful_rec_pre_lb(ls_stateful_rec, lflows);
> > +    build_acl_hints(ls_stateful_rec, features, lflows);
> > +    build_acls(ls_stateful_rec, features, lflows, ls_pgs, meter_groups);
> > +    build_lb_hairpin(ls_stateful_rec, lflows);
> > +}
> > +
> >  struct lswitch_flow_build_info {
> >      const struct ovn_datapaths *ls_datapaths;
> >      const struct ovn_datapaths *lr_datapaths;
> > @@ -15994,6 +15996,7 @@ struct lswitch_flow_build_info {
> >      const struct hmap *lr_ports;
> >      const struct ls_port_group_table *ls_port_groups;
> >      const struct lr_stateful_table *lr_stateful_table;
> > +    const struct ls_stateful_table *ls_stateful_table;
> >      struct hmap *lflows;
> >      struct hmap *igmp_groups;
> >      const struct shash *meter_groups;
> > @@ -16018,9 +16021,7 @@ build_lswitch_and_lrouter_iterate_by_ls(struct 
> > ovn_datapath *od,
> >                                          struct lswitch_flow_build_info 
> > *lsi)
> >  {
> >      ovs_assert(od->nbs);
> > -    build_lswitch_lflows_pre_acl_and_acl(od, lsi->ls_port_groups,
> > -                                         lsi->features,
> > -                                         lsi->lflows,
> > +    build_lswitch_lflows_pre_acl_and_acl(od, lsi->features, lsi->lflows,
> >                                           lsi->meter_groups);
> >
> >      build_fwd_group_lflows(od, lsi->lflows);
> > @@ -16135,6 +16136,7 @@ build_lflows_thread(void *arg)
> >  {
> >      struct worker_control *control = (struct worker_control *) arg;
> >      const struct lr_stateful_record *lr_stateful_rec;
> > +    const struct ls_stateful_record *ls_stateful_rec;
> >      struct lswitch_flow_build_info *lsi;
> >      struct ovn_igmp_group *igmp_group;
> >      struct ovn_lb_datapaths *lb_dps;
> > @@ -16259,6 +16261,20 @@ build_lflows_thread(void *arg)
> >                                              lsi->features);
> >                  }
> >              }
> > +
> > +            for (bnum = control->id;
> > +                    bnum <= lsi->ls_stateful_table->entries.mask;
> > +                    bnum += control->pool->size)
> > +            {
> > +                LS_STATEFUL_TABLE_FOR_EACH_IN_P (ls_stateful_rec, bnum,
> > +                                                 lsi->ls_stateful_table) {
> > +                    build_ls_stateful_flows(ls_stateful_rec,
> > +                                            lsi->ls_port_groups,
> > +                                            lsi->features, 
> > lsi->meter_groups,
> > +                                            lsi->lflows);
> > +                }
> > +            }
> > +
> >              for (bnum = control->id;
> >                      bnum <= lsi->igmp_groups->mask;
> >                      bnum += control->pool->size)
> > @@ -16320,6 +16336,7 @@ build_lswitch_and_lrouter_flows(
> >      const struct hmap *lr_ports,
> >      const struct ls_port_group_table *ls_pgs,
> >      const struct lr_stateful_table *lr_stateful_table,
> > +    const struct ls_stateful_table *ls_stateful_table,
> >      struct hmap *lflows,
> >      struct hmap *igmp_groups,
> >      const struct shash *meter_groups,
> > @@ -16350,6 +16367,7 @@ build_lswitch_and_lrouter_flows(
> >              lsiv[index].lr_ports = lr_ports;
> >              lsiv[index].ls_port_groups = ls_pgs;
> >              lsiv[index].lr_stateful_table = lr_stateful_table;
> > +            lsiv[index].ls_stateful_table = ls_stateful_table;
> >              lsiv[index].igmp_groups = igmp_groups;
> >              lsiv[index].meter_groups = meter_groups;
> >              lsiv[index].lb_dps_map = lb_dps_map;
> > @@ -16375,6 +16393,7 @@ build_lswitch_and_lrouter_flows(
> >          free(lsiv);
> >      } else {
> >          const struct lr_stateful_record *lr_stateful_rec;
> > +        const struct ls_stateful_record *ls_stateful_rec;
> >          struct ovn_igmp_group *igmp_group;
> >          struct ovn_lb_datapaths *lb_dps;
> >          struct ovn_datapath *od;
> > @@ -16387,6 +16406,7 @@ build_lswitch_and_lrouter_flows(
> >              .lr_ports = lr_ports,
> >              .ls_port_groups = ls_pgs,
> >              .lr_stateful_table = lr_stateful_table,
> > +            .ls_stateful_table = ls_stateful_table,
> >              .lflows = lflows,
> >              .igmp_groups = igmp_groups,
> >              .meter_groups = meter_groups,
> > @@ -16456,6 +16476,12 @@ build_lswitch_and_lrouter_flows(
> >                                      lsi.features);
> >          }
> >
> > +        LS_STATEFUL_TABLE_FOR_EACH (ls_stateful_rec, ls_stateful_table) {
> > +            build_ls_stateful_flows(ls_stateful_rec, lsi.ls_port_groups,
> > +                                  lsi.features, lsi.meter_groups,
> > +                                  lsi.lflows);
> > +        }
> > +
> >          stopwatch_start(LFLOWS_IGMP_STOPWATCH_NAME, time_msec());
> >          HMAP_FOR_EACH (igmp_group, hmap_node, igmp_groups) {
> >              build_lswitch_ip_mcast_igmp_mld(igmp_group,
> > @@ -16552,6 +16578,7 @@ void build_lflows(struct ovsdb_idl_txn *ovnsb_txn,
> >                                      input_data->lr_ports,
> >                                      input_data->ls_port_groups,
> >                                      input_data->lr_stateful_table,
> > +                                    input_data->ls_stateful_table,
> >                                      lflows,
> >                                      &igmp_groups,
> >                                      input_data->meter_groups,
> > diff --git a/northd/northd.h b/northd/northd.h
> > index 46bd2498f4..88406bffee 100644
> > --- a/northd/northd.h
> > +++ b/northd/northd.h
> > @@ -89,6 +89,8 @@ ods_size(const struct ovn_datapaths *datapaths)
> >      return hmap_count(&datapaths->datapaths);
> >  }
> >
> > +bool od_has_lb_vip(const struct ovn_datapath *od);
> > +
> >  struct tracked_ovn_ports {
> >      /* tracked created ports.
> >       * hmapx node data is 'struct ovn_port *' */
> > @@ -118,6 +120,8 @@ enum northd_tracked_data_type {
> >      NORTHD_TRACKED_PORTS    = (1 << 0),
> >      NORTHD_TRACKED_LBS      = (1 << 1),
> >      NORTHD_TRACKED_LR_NATS  = (1 << 2),
> > +    NORTHD_TRACKED_LS_LBS   = (1 << 3),
> > +    NORTHD_TRACKED_LS_ACLS  = (1 << 4),
> >  };
> >
> >  /* Track what's changed in the northd engine node.
> > @@ -132,6 +136,14 @@ struct northd_tracked_data {
> >      /* Tracked logical routers whose NATs have changed.
> >       * hmapx node is 'struct ovn_datapath *'. */
> >      struct hmapx lr_with_changed_nats;
> > +
> > +    /* Tracked logical switches whose load balancers have changed.
> > +     * hmapx node is 'struct ovn_datapath *'. */
> > +    struct hmapx ls_with_changed_lbs;
> > +
> > +    /* Tracked logical switches whose ACLs have changed.
> > +     * hmapx node is 'struct ovn_datapath *'. */
> > +    struct hmapx ls_with_changed_acls;
> >  };
> >
> >  struct northd_data {
> > @@ -180,6 +192,7 @@ struct lflow_input {
> >      const struct hmap *lr_ports;
> >      const struct ls_port_group_table *ls_port_groups;
> >      const struct lr_stateful_table *lr_stateful_table;
> > +    const struct ls_stateful_table *ls_stateful_table;
> >      const struct shash *meter_groups;
> >      const struct hmap *lb_datapaths_map;
> >      const struct hmap *bfd_connections;
> > @@ -289,11 +302,7 @@ struct ovn_datapath {
> >      struct hmap port_tnlids;
> >      uint32_t port_key_hint;
> >
> > -    bool has_stateful_acl;
> > -    bool has_lb_vip;
> >      bool has_unknown;
> > -    bool has_acls;
> > -    uint64_t max_acl_tier;
> >      bool has_vtep_lports;
> >      bool has_arp_proxy_port;
> >
> > @@ -541,6 +550,18 @@ northd_has_lr_nats_in_tracked_data(struct 
> > northd_tracked_data *trk_nd_changes)
> >      return (trk_nd_changes->type & NORTHD_TRACKED_LR_NATS);
> >  }
> >
> > +static inline bool
> > +northd_has_ls_lbs_in_tracked_data(struct northd_tracked_data 
> > *trk_nd_changes)
> > +{
> > +    return (trk_nd_changes->type & NORTHD_TRACKED_LS_LBS);
>
> No need for parenthesis.
>
> > +}
> > +
> > +static inline bool
> > +northd_has_ls_acls_in_tracked_data(struct northd_tracked_data 
> > *trk_nd_changes)
> > +{
> > +    return (trk_nd_changes->type & NORTHD_TRACKED_LS_ACLS);
>
> No need for parenthesis.
>
> > +}
> > +
> >  /* Returns 'true' if the IPv4 'addr' is on the same subnet with one of the
> >   * IPs configured on the router port.
> >   */
> > diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> > index e85e00cc9b..084d675644 100644
> > --- a/northd/ovn-northd.c
> > +++ b/northd/ovn-northd.c
> > @@ -872,6 +872,7 @@ main(int argc, char *argv[])
> >      stopwatch_create(SYNC_METERS_RUN_STOPWATCH_NAME, SW_MS);
> >      stopwatch_create(LR_NAT_RUN_STOPWATCH_NAME, SW_MS);
> >      stopwatch_create(LR_STATEFUL_RUN_STOPWATCH_NAME, SW_MS);
> > +    stopwatch_create(LS_STATEFUL_RUN_STOPWATCH_NAME, SW_MS);
> >
> >      /* Initialize incremental processing engine for ovn-northd */
> >      inc_proc_northd_init(&ovnnb_idl_loop, &ovnsb_idl_loop);
>
> _______________________________________________
> dev mailing list
> d...@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to