On 27/04/2021 18:29, Han Zhou wrote:
> On Tue, Apr 27, 2021 at 8:43 AM Mark Gray <[email protected]> wrote:
>>
>> On 22/04/2021 21:14, Han Zhou wrote:
>>> For an ACL with match: outport == @PG && ip4.src == $PG_AS, given below
>>> scale:
>>>
>>> P: PG size
>>> LP: number of local lports
>>> D: number of all datapaths (logical switches)
>>> LD: number of datapaths that contain local lports
>>>
>>> With current OVN implementation, the total number of OF flows is:
>>>     LP + (P * D) + D
>>>
>>> The reason is, firstly, datapath is not part of the conjunction, so for
>>> each datapath the lflow is reparsed.
>>>
>>> Secondly, although ovn-controller tries to filter out the flows that are
>>> for non-local lports, with the conjunction match, the logic that filters
>>> out non-local flows doesn't work for the conjunction part that doesn't
>>> have the lport in the match (the P * D part). When there is only one
>>> port on each LS it is fine, because no conjunction will be used because
>>> SB port groups are splited per datapath, so each port group would have
>> suggest "split per datapath"
> 
> Ack
> 
>>> only one port. However, when more than one ports are on each LS the flow
>>> explosion happens.
>>>
>>> This patch deal with the second reason above, by refining the SB port
>>> groups to store only locally bound lports: empty const sets will not
>>> generate any flows. This reduces the related flow number from
>>> LP + (P * D) + D to LP + (P * LD) + LD.
>>>
>>> Since LD is expected to be small, so even if it is a multiplier, the
>>> total number is larged reduced.  In particular, in ovn-k8s use cases the
>> suggest "reduced significantly"
> 
> Ack
> 
>>> LD is always 1, so the formular above becomes LP + P + LD.
>>>
>> s/formular/formula
> 
> Ack
> 
>>> With a scale of 1k k8s nodes, each has 4 ports for the same PG: P = 4k,
>>> LP = 4, D = 1k, LD = 1. The current implementation generates ~4m flows.
>>> With this patch it becomes only ~4k.
>> Cool!
>>>
>>> Reported-by: Girish Moodalbail <[email protected]>
>>> Reported-at:
> https://mail.openvswitch.org/pipermail/ovs-dev/2021-March/381082.html
>>> Reported-by: Dumitru Ceara <[email protected]>
>>> Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=1944098
>>> Signed-off-by: Han Zhou <[email protected]>
>>
>> I tested this as well and it seemed to work as expected.
> 
> Thanks for the test!
> 
>>> ---
>>> v1->v2: fix memory leaks found by address sanitizer
>>>
>>>  controller/binding.c        |  20 ++++
>>>  controller/binding.h        |   9 ++
>>>  controller/ovn-controller.c | 217 ++++++++++++++++++++++++++++++------
>>>  include/ovn/expr.h          |   2 +-
>>>  lib/expr.c                  |   8 +-
>>>  tests/ovn.at                |  53 +++++++++
>>>  tests/test-ovn.c            |  12 +-
>>>  utilities/ovn-trace.c       |   4 +-
>>>  8 files changed, 283 insertions(+), 42 deletions(-)
>>>
>>> diff --git a/controller/binding.c b/controller/binding.c
>>> index 514f5f33f..5aca964cc 100644
>>> --- a/controller/binding.c
>>> +++ b/controller/binding.c
>>> @@ -2987,3 +2987,23 @@ cleanup:
>>>
>>>      return b_lport;
>>>  }
>>> +
>>> +struct sset *
>>> +binding_collect_local_binding_lports(struct local_binding_data
> *lbinding_data)
>>> +{
>>> +    struct sset *lports = xzalloc(sizeof *lports);
>>> +    sset_init(lports);
>>> +    struct shash_node *shash_node;
>>> +    SHASH_FOR_EACH (shash_node, &lbinding_data->lports) {
>>> +        struct binding_lport *b_lport = shash_node->data;
>>> +        sset_add(lports, b_lport->name);
>>> +    }
>>> +    return lports;
>>> +}
>>> +
>>> +void
>>> +binding_destroy_local_binding_lports(struct sset *lports)
>>> +{
>>> +    sset_destroy(lports);
>>> +    free(lports);
>>> +}
>>> diff --git a/controller/binding.h b/controller/binding.h
>>> index 4fc9ef207..31f0352a0 100644
>>> --- a/controller/binding.h
>>> +++ b/controller/binding.h
>>> @@ -128,4 +128,13 @@ void binding_seqno_run(struct local_binding_data
> *lbinding_data);
>>>  void binding_seqno_install(struct local_binding_data *lbinding_data);
>>>  void binding_seqno_flush(void);
>>>  void binding_dump_local_bindings(struct local_binding_data *, struct
> ds *);
>>> +
>>> +/* Generates a sset of lport names from local_binding_data.
>>> + * Note: the caller is responsible for destroying and freeing the
> returned
>>> + * sset, by calling binding_collect_local_binding_lports(). */
>> I think this^ should say binding_destroy_local_binding_lports()?
> 
> Oops. My bad.
> 
>>> +struct sset *binding_collect_local_binding_lports(struct
> local_binding_data *);
>>> +
>>> +/* Destroy and free the lports sset returned by
>>> + * binding_collect_local_binding_lports(). */
>>> +void binding_destroy_local_binding_lports(struct sset *lports);
>>>  #endif /* controller/binding.h */
>>> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
>>> index 7320bd56c..c6ba9ff88 100644
>>> --- a/controller/ovn-controller.c
>>> +++ b/controller/ovn-controller.c
>>> @@ -1341,7 +1341,7 @@ addr_sets_init(const struct
> sbrec_address_set_table *address_set_table,
>>>      SBREC_ADDRESS_SET_TABLE_FOR_EACH (as, address_set_table) {
>>>          expr_const_sets_add(addr_sets, as->name,
>>>                              (const char *const *) as->addresses,
>>> -                            as->n_addresses, true);
>>> +                            as->n_addresses, true, NULL);
>>>      }
>>>  }
>>>
>>> @@ -1358,7 +1358,7 @@ addr_sets_update(const struct
> sbrec_address_set_table *address_set_table,
>>>          } else {
>>>              expr_const_sets_add(addr_sets, as->name,
>>>                                  (const char *const *) as->addresses,
>>> -                                as->n_addresses, true);
>>> +                                as->n_addresses, true, NULL);
>>>              if (sbrec_address_set_is_new(as)) {
>>>                  sset_add(new, as->name);
>>>              } else {
>>> @@ -1367,6 +1367,7 @@ addr_sets_update(const struct
> sbrec_address_set_table *address_set_table,
>>>          }
>>>      }
>>>  }
>>> +
>>>  static void
>>>  en_addr_sets_run(struct engine_node *node, void *data)
>>>  {
>>> @@ -1415,20 +1416,72 @@ addr_sets_sb_address_set_handler(struct
> engine_node *node, void *data)
>>>  }
>>>
>>>  struct ed_type_port_groups{
>>> -    struct shash port_groups;
>>> +    /* A copy of SB port_groups, each converted as a sset for
> efficient lport
>>> +     * lookup. */
>>> +    struct shash port_group_ssets;
>>> +
>>> +    /* Const sets containing local lports, used for expr parsing. */
>>> +    struct shash port_groups_cs_local;
>>> +
>>>      bool change_tracked;
>>>      struct sset new;
>>>      struct sset deleted;
>>>      struct sset updated;
>>>  };
>>>
>>> +static void
>>> +port_group_ssets_add_or_update(struct shash *port_group_ssets,
>>> +                               const struct sbrec_port_group *pg)
>>> +{
>>> +    struct sset *lports = shash_find_data(port_group_ssets, pg->name);
>>> +    if (lports) {
>>> +        sset_clear(lports);
>>> +    } else {
>>> +        lports = xzalloc(sizeof *lports);
>>> +        sset_init(lports);
>>> +        shash_add(port_group_ssets, pg->name, lports);
>>> +    }
>>> +
>>> +    for (size_t i = 0; i < pg->n_ports; i++) {
>>> +        sset_add(lports, pg->ports[i]);
>>> +    }
>>> +}
>>> +
>>> +static void
>>> +port_group_ssets_delete(struct shash *port_group_ssets,
>>> +                        const char *pg_name)
>>> +{
>>> +    struct shash_node *node = shash_find(port_group_ssets, pg_name);
>>> +    if (node) {
>>> +        struct sset *lports = node->data;
>>> +        shash_delete(port_group_ssets, node);
>>> +        sset_destroy(lports);
>>> +        free(lports);
>>> +    }
>>> +}
>>> +
>>> +/* Delete and free all ssets in port_group_ssets, but not
>>> + * destroying the shash itself. */
>>> +static void
>>> +port_group_ssets_clear(struct shash *port_group_ssets)
>>> +{
>>> +    struct shash_node *node, *next;
>>> +    SHASH_FOR_EACH_SAFE (node, next, port_group_ssets) {
>>> +        struct sset *lports = node->data;
>>> +        shash_delete(port_group_ssets, node);
>>> +        sset_destroy(lports);
>>> +        free(lports);
>>> +    }
>>> +}
>>> +
>>>  static void *
>>>  en_port_groups_init(struct engine_node *node OVS_UNUSED,
>>>                      struct engine_arg *arg OVS_UNUSED)
>>>  {
>>>      struct ed_type_port_groups *pg = xzalloc(sizeof *pg);
>>>
>>> -    shash_init(&pg->port_groups);
>>> +    shash_init(&pg->port_group_ssets);
>>> +    shash_init(&pg->port_groups_cs_local);
>>>      pg->change_tracked = false;
>>>      sset_init(&pg->new);
>>>      sset_init(&pg->deleted);
>>> @@ -1440,41 +1493,52 @@ static void
>>>  en_port_groups_cleanup(void *data)
>>>  {
>>>      struct ed_type_port_groups *pg = data;
>>> -    expr_const_sets_destroy(&pg->port_groups);
>>> -    shash_destroy(&pg->port_groups);
>>> +
>>> +    expr_const_sets_destroy(&pg->port_groups_cs_local);
>>> +    shash_destroy(&pg->port_groups_cs_local);
>>> +
>>> +    port_group_ssets_clear(&pg->port_group_ssets);
>>> +    shash_destroy(&pg->port_group_ssets);
>>> +
>>>      sset_destroy(&pg->new);
>>>      sset_destroy(&pg->deleted);
>>>      sset_destroy(&pg->updated);
>>>  }
>>>
>>> -/* Iterate port groups in the southbound database.  Create and update
> the
>>> - * corresponding symtab entries as necessary. */
>>> - static void
>>> +static void
>>>  port_groups_init(const struct sbrec_port_group_table *port_group_table,
>>> -                 struct shash *port_groups)
>>> +                 const struct sset *local_lports,
>>> +                 struct shash *port_group_ssets,
>>> +                 struct shash *port_groups_cs_local)
>>>  {
>>>      const struct sbrec_port_group *pg;
>>>      SBREC_PORT_GROUP_TABLE_FOR_EACH (pg, port_group_table) {
>>> -        expr_const_sets_add(port_groups, pg->name,
>>> +        port_group_ssets_add_or_update(port_group_ssets, pg);
>>> +        expr_const_sets_add(port_groups_cs_local, pg->name,
>>>                              (const char *const *) pg->ports,
>>> -                            pg->n_ports, false);
>>> +                            pg->n_ports, false, local_lports);
>>>      }
>>>  }
>>>
>>>  static void
>>>  port_groups_update(const struct sbrec_port_group_table
> *port_group_table,
>>> -                   struct shash *port_groups, struct sset *new,
>>> -                   struct sset *deleted, struct sset *updated)
>>> +                   const struct sset *local_lports,
>>> +                   struct shash *port_group_ssets,
>>> +                   struct shash *port_groups_cs_local,
>>> +                   struct sset *new, struct sset *deleted,
>>> +                   struct sset *updated)
>>>  {
>>>      const struct sbrec_port_group *pg;
>>>      SBREC_PORT_GROUP_TABLE_FOR_EACH_TRACKED (pg, port_group_table) {
>>>          if (sbrec_port_group_is_deleted(pg)) {
>>> -            expr_const_sets_remove(port_groups, pg->name);
>>> +            expr_const_sets_remove(port_groups_cs_local, pg->name);
>>> +            port_group_ssets_delete(port_group_ssets, pg->name);
>>>              sset_add(deleted, pg->name);
>>>          } else {
>>> -            expr_const_sets_add(port_groups, pg->name,
>>> +            expr_const_sets_add(port_groups_cs_local, pg->name,
>>>                                  (const char *const *) pg->ports,
>>> -                                pg->n_ports, false);
>>> +                                pg->n_ports, false, local_lports);
>>> +            port_group_ssets_add_or_update(port_group_ssets, pg);
>>
>> Bit of a nit: maybe the port_group_ssets_() suite of functions could
>> follow the same pattern as expr_const_sets_() by s/ssets/sets and
>> changing the verbs. It keeps it consistent and is basically working on
>> the same type of data sructure.
>>
>> e.g.
>>
>> port_group_sets_add()
>> port_group_sets_remove()
>> port_group_sets_destroy()
>>
> 
> port_group_ssets is different from expr_const_sets. port_groups_ssets is a
> struct shash with each value item being a struct sset. expr_const_sets is
> also a struct shash but each item is an array of strings. I think different
> naming patterns would help remind that they are different.
> 
>>>              if (sbrec_port_group_is_new(pg)) {
>>>                  sset_add(new, pg->name);
>>>              } else {
>>> @@ -1485,22 +1549,36 @@ port_groups_update(const struct
> sbrec_port_group_table *port_group_table,
>>>  }
>>>
>>>  static void
>>> -en_port_groups_run(struct engine_node *node, void *data)
>>> +en_port_groups_clear_tracked_data(void *data_)
>>>  {
>>> -    struct ed_type_port_groups *pg = data;
>>> -
>>> +    struct ed_type_port_groups *pg = data_;
>>>      sset_clear(&pg->new);
>>>      sset_clear(&pg->deleted);
>>>      sset_clear(&pg->updated);
>>> -    expr_const_sets_destroy(&pg->port_groups);
>>> +    pg->change_tracked = false;
>>> +}
>>> +
>>> +static void
>>> +en_port_groups_run(struct engine_node *node, void *data)
>>> +{
>>> +    struct ed_type_port_groups *pg = data;
>>> +
>>> +    expr_const_sets_destroy(&pg->port_groups_cs_local);
>>> +    port_group_ssets_clear(&pg->port_group_ssets);
>>>
>>>      struct sbrec_port_group_table *pg_table =
>>>          (struct sbrec_port_group_table *)EN_OVSDB_GET(
>>>              engine_get_input("SB_port_group", node));
>>>
>>> -    port_groups_init(pg_table, &pg->port_groups);
>>> +    struct ed_type_runtime_data *rt_data =
>>> +        engine_get_input_data("runtime_data", node);
>>> +
>>> +    struct sset *local_b_lports = binding_collect_local_binding_lports(
>>> +        &rt_data->lbinding_data);
>>> +    port_groups_init(pg_table, local_b_lports, &pg->port_group_ssets,
>>> +                     &pg->port_groups_cs_local);
>>> +    binding_destroy_local_binding_lports(local_b_lports);
>>>
>>> -    pg->change_tracked = false;
>>>      engine_set_node_state(node, EN_UPDATED);
>>>  }
>>>
>>> @@ -1509,16 +1587,19 @@ port_groups_sb_port_group_handler(struct
> engine_node *node, void *data)
>>>  {
>>>      struct ed_type_port_groups *pg = data;
>>>
>>> -    sset_clear(&pg->new);
>>> -    sset_clear(&pg->deleted);
>>> -    sset_clear(&pg->updated);
>>> -
>>>      struct sbrec_port_group_table *pg_table =
>>>          (struct sbrec_port_group_table *)EN_OVSDB_GET(
>>>              engine_get_input("SB_port_group", node));
>>>
>>> -    port_groups_update(pg_table, &pg->port_groups, &pg->new,
>>> -                     &pg->deleted, &pg->updated);
>>> +    struct ed_type_runtime_data *rt_data =
>>> +        engine_get_input_data("runtime_data", node);
>>> +
>>> +    struct sset *local_b_lports = binding_collect_local_binding_lports(
>>> +        &rt_data->lbinding_data);
>>> +    port_groups_update(pg_table, local_b_lports, &pg->port_group_ssets,
>>> +                       &pg->port_groups_cs_local, &pg->new,
> &pg->deleted,
>>> +                       &pg->updated);
>>> +    binding_destroy_local_binding_lports(local_b_lports);
>>>
>>>      if (!sset_is_empty(&pg->new) || !sset_is_empty(&pg->deleted) ||
>>>              !sset_is_empty(&pg->updated)) {
>>> @@ -1531,6 +1612,74 @@ port_groups_sb_port_group_handler(struct
> engine_node *node, void *data)
>>>      return true;
>>>  }
>>>
>>> +static bool
>>> +port_groups_runtime_data_handler(struct engine_node *node, void *data)
>>> +{
>>> +    struct ed_type_port_groups *pg = data;
>>> +
>>> +    struct sbrec_port_group_table *pg_table =
>>> +        (struct sbrec_port_group_table *)EN_OVSDB_GET(
>>> +            engine_get_input("SB_port_group", node));
>>> +
>>> +    struct ed_type_runtime_data *rt_data =
>>> +        engine_get_input_data("runtime_data", node);
>>> +
>>> +    if (!rt_data->tracked) {
>>> +        return false;
>>> +    }
>>> +
>>> +    if (hmap_is_empty(&rt_data->tracked_dp_bindings)) {
>>> +        goto out;
>>> +    }
>>> +
>>> +    struct sset *local_b_lports = binding_collect_local_binding_lports(
>>> +        &rt_data->lbinding_data);
>>> +
>>> +    const struct sbrec_port_group *pg_sb;
>>> +    SBREC_PORT_GROUP_TABLE_FOR_EACH (pg_sb, pg_table) {
>>> +
>> Remove blank line^
> 
> Ack
> 
>>> +        struct sset *pg_lports = shash_find_data(&pg->port_group_ssets,
>>> +                                                 pg_sb->name);
>>
>> Correct me if I am wrong but I think this^ is the only reason you need
>> the 'port_group_ssets' data structure? Perhaps you could remove the need
>> for it by implementing expr_const_sets_find() function? It would
>> simplify the code a lot as you would only need 'port_groups_cs_local'.
>> If you could do this, you could also rename that to 'port_groups_local'
> 
> In fact, what's more important of the usage of "port_group_ssets" is the
> check in the below loop:
> 
>                  if (sset_contains(pg_lports, lport->pb->logical_port)) {
> ...
> 
> It is to quickly check if a local port binding is affecting that PG.
> Without the "port_group_ssets" we will have to do a O(n) iteration of each
> lport of a PG. This is also briefly mentioned in the definition:
> 
>     /* A copy of SB port_groups, each converted as a sset for efficient
> lport
>      * lookup. */
>     struct shash port_group_ssets;
> 
> It can't be replaced by 'expr_const_sets_find' because they contain
> different information (local lports v.s. all lports) and also different
> data structure (array v.s. sset).

Yes, I actually realized that when I was thinking about it again
yesterday evening. There are two things that were confusing me
yesterday: 1) there are now three layers of abstraction in this code
(en_port_groups_, port_groups_, and port_groups_ssets_). I get it after
looking at it for a few times but its not immediately clear because the
names are similar. 2) The loops in the function
(port_groups_runtime_data_handler) are quite complicated due to having 3
nesting levels. I dont think there is anything functionally wrong with
it but I was hoping to simplify it somehow and I thought we could remove
the 'port_groups_ssets' abstraction.

For 1) maybe alot of this code could be organised into a port_groups.c/h
file?

For 2)Why can we not just iterate over the port groups in the sb
database and call port_groups_update()? Is this an optimization?
> 
>>> +        ovs_assert(pg_lports);
>>> +
>>> +        struct tracked_binding_datapath *tdp;
>>> +        bool need_update = false;
>>> +        HMAP_FOR_EACH (tdp, node, &rt_data->tracked_dp_bindings) {
>>> +            struct shash_node *shash_node;
>>> +            SHASH_FOR_EACH (shash_node, &tdp->lports) {
>>> +                struct tracked_binding_lport *lport = shash_node->data;
>>> +                if (sset_contains(pg_lports, lport->pb->logical_port))
> {
>>> +                    /* At least one local port-binding change is
> related to the
>>> +                     * port_group, so the port_group_cs_local needs
> update. */
>>> +                    need_update = true;
>>> +                    break;
>>> +                }
>>> +            }
>>> +            if (need_update) {
>>> +                break;
>>> +            }
>>> +        }
>>> +        if (need_update) {
>>> +            expr_const_sets_add(&pg->port_groups_cs_local, pg_sb->name,
>>> +                                (const char *const *) pg_sb->ports,
>>> +                                pg_sb->n_ports, false, local_b_lports);
>>> +            sset_add(&pg->updated, pg_sb->name);
>>> +        }
>>> +    }
>>> +
>>> +    binding_destroy_local_binding_lports(local_b_lports);
>>> +
>>> +out:
>>> +    if (!sset_is_empty(&pg->new) || !sset_is_empty(&pg->deleted) ||
>>> +            !sset_is_empty(&pg->updated)) {
>>> +        engine_set_node_state(node, EN_UPDATED);
>>> +    } else {
>>> +        engine_set_node_state(node, EN_UNCHANGED);
>>> +    }
>>> +    pg->change_tracked = true;
>>> +    return true;
>>> +}
>>> +
>>>  /* Connection tracking zones. */
>>>  struct ed_type_ct_zones {
>>>      unsigned long bitmap[BITMAP_N_LONGS(MAX_CT_ZONES)];
>>> @@ -1880,7 +2029,7 @@ static void init_lflow_ctx(struct engine_node
> *node,
>>>
>>>      struct ed_type_port_groups *pg_data =
>>>          engine_get_input_data("port_groups", node);
>>> -    struct shash *port_groups = &pg_data->port_groups;
>>> +    struct shash *port_groups = &pg_data->port_groups_cs_local;
>>>
>>>      l_ctx_in->sbrec_multicast_group_by_name_datapath =
>>>          sbrec_mc_group_by_name_dp;
>>> @@ -2540,7 +2689,7 @@ main(int argc, char *argv[])
>>>                                        "physical_flow_changes");
>>>      ENGINE_NODE(flow_output, "flow_output");
>>>      ENGINE_NODE(addr_sets, "addr_sets");
>>> -    ENGINE_NODE(port_groups, "port_groups");
>>> +    ENGINE_NODE_WITH_CLEAR_TRACK_DATA(port_groups, "port_groups");
>>>
>>>  #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
>>>      SB_NODES
>>> @@ -2556,6 +2705,10 @@ main(int argc, char *argv[])
>>>                       addr_sets_sb_address_set_handler);
>>>      engine_add_input(&en_port_groups, &en_sb_port_group,
>>>                       port_groups_sb_port_group_handler);
>>> +    /* port_groups computation requires runtime_data's lbinding_data
> for the
>>> +     * locally bound ports. */
>>> +    engine_add_input(&en_port_groups, &en_runtime_data,
>>> +                     port_groups_runtime_data_handler);
>>>
>>>      /* Engine node physical_flow_changes indicates whether
>>>       * we can recompute only physical flows or we can
>>> @@ -2986,7 +3139,7 @@ main(int argc, char *argv[])
>>>                      engine_get_data(&en_port_groups);
>>>                  if (br_int && chassis && as_data && pg_data) {
>>>                      char *error = ofctrl_inject_pkt(br_int,
> pending_pkt.flow_s,
>>> -                        &as_data->addr_sets, &pg_data->port_groups);
>>> +                        &as_data->addr_sets,
> &pg_data->port_groups_cs_local);
>>>                      if (error) {
>>>                          unixctl_command_reply_error(pending_pkt.conn,
> error);
>>>                          free(error);
>>> diff --git a/include/ovn/expr.h b/include/ovn/expr.h
>>> index 032370058..2f7ecbc9c 100644
>>> --- a/include/ovn/expr.h
>>> +++ b/include/ovn/expr.h
>>> @@ -547,7 +547,7 @@ void expr_constant_set_destroy(struct
> expr_constant_set *cs);
>>>
>>>  void expr_const_sets_add(struct shash *const_sets, const char *name,
>>>                           const char * const *values, size_t n_values,
>>> -                         bool convert_to_integer);
>>> +                         bool convert_to_integer, const struct sset
> *filter);
>>>  void expr_const_sets_remove(struct shash *const_sets, const char
> *name);
>>>  void expr_const_sets_destroy(struct shash *const_sets);
>>>
>>> diff --git a/lib/expr.c b/lib/expr.c
>>> index f061a8fbe..b42d78d3e 100644
>>> --- a/lib/expr.c
>>> +++ b/lib/expr.c
>>> @@ -1065,7 +1065,7 @@ expr_constant_set_destroy(struct
> expr_constant_set *cs)
>>>  void
>>
>>>  expr_const_sets_add(struct shash *const_sets, const char *name,
>>>                      const char *const *values, size_t n_values,
>>> -                    bool convert_to_integer)
>>> +                    bool convert_to_integer, const struct sset *filter)
>>>  {
>>>      /* Replace any existing entry for this name. */
>>>      expr_const_sets_remove(const_sets, name);
>>> @@ -1075,6 +1075,7 @@ expr_const_sets_add(struct shash *const_sets,
> const char *name,
>>>      cs->n_values = 0;
>>>      cs->values = xmalloc(n_values * sizeof *cs->values);
>>>      if (convert_to_integer) {
>>> +        ovs_assert(!filter);
>> Because the parameters of this function do not apply to both the integer
>> and the string case, this implies all the callers must know if they are
>> dealing with integers or strings. I'm wondering should this function be
>> split into something like:
>>
>> expr_const_sets_add_ints()
>> expr_const_sets_add_strings()
>>
>> and maybe even:
>>
>> expr_const_sets_add_filtered_strings()
>>
>> I just looked through the code and this seems to be the case - in fact
>> 'convert_to_integer' basically means that this is an integer. What do
>> you think?
>>
> 
> Agree. I thought about the same thing but was lazy :). I can change it in
> the next version of the patch.

Great

> 
>>>          cs->type = EXPR_C_INTEGER;
>>>          for (size_t i = 0; i < n_values; i++) {
>>>              /* Use the lexer to convert each constant set into the
> proper
>>> @@ -1100,6 +1101,11 @@ expr_const_sets_add(struct shash *const_sets,
> const char *name,
>>>      } else {
>>>          cs->type = EXPR_C_STRING;
>>>          for (size_t i = 0; i < n_values; i++) {
>>> +            if (filter && !sset_find(filter, values[i])) {
>>> +                VLOG_DBG("Skip constant set entry '%s' for '%s'",
>>> +                         values[i], name);
>>> +                continue;
>>> +            }
>>>              union expr_constant *c = &cs->values[cs->n_values++];
>>>              c->string = xstrdup(values[i]);
>>>          }
>>> diff --git a/tests/ovn.at b/tests/ovn.at
>>> index 55444bbd7..aa1d1cf17 100644
>>> --- a/tests/ovn.at
>>> +++ b/tests/ovn.at
>>> @@ -26265,3 +26265,56 @@ primary lport : [[sw0p2]]
>>>  OVN_CLEANUP([hv1], [hv2])
>>>  AT_CLEANUP
>>>  ])
>>> +
>>> +# Tests the efficiency of conjunction flow generation when port groups
> are used
>>> +# by ACLs. Make sure there is no open flow explosion in table 45 for
> an ACL
>>> +# with self-referencing match condition of a port group:
>>> +#
>>> +# "outport == @pg1 && ip4.src == $pg1_ip4"
>>> +#
>>> +# 10 LSes (ls[0-9]), each has 2 LSPs (lsp[0-9][01]). All LSPs belong
> to port
>>> +# group pg1, but only LSPs of LS0 are bound on HV1.
>>> +#
>>> +# The expected number of conjunction flows is 2 + 20 = 22.
>>
>> A brief explanation of why that number is expected should help our
>> future selves.
> 
> Ack
> 
>>> +OVN_FOR_EACH_NORTHD([
>>> +AT_SETUP([ovn -- ACL with Port Group conjunction flow efficiency])
>>> +ovn_start
>>> +
>>> +net_add n1
>>> +
>>> +sim_add hv1
>>> +as hv1
>>
>> Maybe this test should be run with 2 hypervisors as it will catch any
>> corner cases that may come up due to the locality of ports and
>> datapaths? What do you think?
>>
> 
> In fact we have at least another 2 test cases, "Port Group" and "ACLs on
> Port Groups", both have 3 HVs, so I think the coverage is good. I could
> have added the flow count check in existing cases but it seems more clear
> to add a new test case focusing on the flow count efficiency only, so I
> added this new test case and I think one HV is enough for this purpose.
> What do you think?
> 

Ok, I had a look at those tests and this seems Ok. Your strategy makes
sense.

> Thanks again for all your comments. I will update with v3.
> 
> Han
> 
>>> +ovs-vsctl add-br br-phys
>>> +ovn_attach n1 br-phys 192.168.0.1
>>> +
>>> +ovn-nbctl lr-add lr0
>>> +
>>> +for i in $(seq 0 9); do
>>> +    ovn-nbctl ls-add ls$i
>>> +    ovn-nbctl lrp-add lr0 lrp_lr0_ls$i aa:bb:bb:00:00:0$i
> 192.168.${i}.1/24
>>> +
>>> +    ovn-nbctl lsp-add ls$i lsp_ls${i}_lr0 -- \
>>> +        lsp-set-addresses lsp_ls${i}_lr0 router -- \
>>> +        lsp-set-type lsp_ls${i}_lr0 router -- \
>>> +        lsp-set-options lsp_ls${i}_lr0 router-port=lrp_lr0_ls${i}
>>> +
>>> +    for j in 0 1; do
>>> +        ovn-nbctl lsp-add ls$i lsp${i}-${j} -- \
>>> +            lsp-set-addresses lsp${i}-${j} "aa:aa:aa:00:0$i:0$j
> 192.168.$i.1$j"
>>> +    done
>>> +done
>>> +
>>> +ovn-nbctl pg-add pg1
>>> +ovn-nbctl pg-set-ports pg1 $(for i in 0 1 2 3 4 5 6 7 8 9; do for j in
> 0 1; do echo lsp${i}-${j}; done; done)
>>> +
>>> +ovn-nbctl --type=port-group acl-add pg1 to-lport 100 "outport == @pg1
> && ip4.src == \$pg1_ip4" allow
>>> +
>>> +ovs-vsctl add-port br-int lsp0-0 -- set interface lsp0-0
> external_ids:iface-id=lsp0-0
>>> +ovs-vsctl add-port br-int lsp0-1 -- set interface lsp0-1
> external_ids:iface-id=lsp0-1
>>> +
>>> +check ovn-nbctl --wait=hv sync
>>> +AT_CHECK([test $(ovs-ofctl dump-flows br-int table=45 | grep
> conjunction | wc -l) == 22])
>>> +
>>> +OVN_CLEANUP([hv1])
>>> +AT_CLEANUP
>>> +])
>>> diff --git a/tests/test-ovn.c b/tests/test-ovn.c
>>> index 202a96c5d..55dbbb6b4 100644
>>> --- a/tests/test-ovn.c
>>> +++ b/tests/test-ovn.c
>>> @@ -227,10 +227,10 @@ create_addr_sets(struct shash *addr_sets)
>>>      };
>>>      static const char *const addrs4[] = { NULL };
>>>
>>> -    expr_const_sets_add(addr_sets, "set1", addrs1, 3, true);
>>> -    expr_const_sets_add(addr_sets, "set2", addrs2, 3, true);
>>> -    expr_const_sets_add(addr_sets, "set3", addrs3, 3, true);
>>> -    expr_const_sets_add(addr_sets, "set4", addrs4, 0, true);
>>> +    expr_const_sets_add(addr_sets, "set1", addrs1, 3, true, NULL);
>>> +    expr_const_sets_add(addr_sets, "set2", addrs2, 3, true, NULL);
>>> +    expr_const_sets_add(addr_sets, "set3", addrs3, 3, true, NULL);
>>> +    expr_const_sets_add(addr_sets, "set4", addrs4, 0, true, NULL);
>>>  }
>>>
>>>  static void
>>> @@ -243,8 +243,8 @@ create_port_groups(struct shash *port_groups)
>>>      };
>>>      static const char *const pg2[] = { NULL };
>>>
>>> -    expr_const_sets_add(port_groups, "0_pg1", pg1, 3, false);
>>> -    expr_const_sets_add(port_groups, "0_pg_empty", pg2, 0, false);
>>> +    expr_const_sets_add(port_groups, "0_pg1", pg1, 3, false, NULL);
>>> +    expr_const_sets_add(port_groups, "0_pg_empty", pg2, 0, false,
> NULL);
>>>  }
>>>
>>>  static bool
>>> diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
>>> index 6b883886f..63b640db9 100644
>>> --- a/utilities/ovn-trace.c
>>> +++ b/utilities/ovn-trace.c
>>> @@ -820,7 +820,7 @@ read_address_sets(void)
>>>      SBREC_ADDRESS_SET_FOR_EACH (sbas, ovnsb_idl) {
>>>          expr_const_sets_add(&address_sets, sbas->name,
>>>                             (const char *const *) sbas->addresses,
>>> -                           sbas->n_addresses, true);
>>> +                           sbas->n_addresses, true, NULL);
>>>      }
>>>  }
>>>
>>> @@ -833,7 +833,7 @@ read_port_groups(void)
>>>      SBREC_PORT_GROUP_FOR_EACH (sbpg, ovnsb_idl) {
>>>          expr_const_sets_add(&port_groups, sbpg->name,
>>>                             (const char *const *) sbpg->ports,
>>> -                           sbpg->n_ports, false);
>>> +                           sbpg->n_ports, false, NULL);
>>>      }
>>>  }
>>>
>>>
>>
> 

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to