diff mbox series

[ovs-dev,v3] controller: Track individual address set constants.

Message ID 20240430165642.1230867-1-amusil@redhat.com
State Superseded
Headers show
Series [ovs-dev,v3] controller: Track individual address set constants. | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/github-robot-_ovn-kubernetes success github build: passed

Commit Message

Ales Musil April 30, 2024, 4:56 p.m. UTC
Instead of tracking address set per struct expr_constant_set track it
per individual struct expr_constant. This allows more fine grained
control for I-P processing of address sets in controller. It helps with
scenarios like matching on two address sets in one expression e.g.
"ip4.src == {$as1, $as2}". This allows any addition or removal of
individual adress from the set to be incrementally processed instead
of reprocessing all the flows.

This unfortunately doesn't help with the following flows:
"ip4.src == $as1 && ip4.dst == $as2"
"ip4.src == $as1 || ip4.dst == $as2"

The memory impact should be minimal as there is only increase of 8 bytes
per the struct expr_constant.

Reported-at: https://issues.redhat.com/browse/FDP-509
Signed-off-by: Ales Musil <amusil@redhat.com>
---
v3: Rebase on top of current main.
    Address comments from Han:
    - Adjust the comment for "lflow_handle_addr_set_update" to include remaning corner cases.
    - Make sure that the flows are consistent between I-P and recompute.
v2: Rebase on top of current main.
    Adjust the comment for I-P optimization.
---
 controller/lflow.c      |  7 ++-
 include/ovn/actions.h   |  2 +-
 include/ovn/expr.h      | 46 ++++++++++---------
 lib/actions.c           | 20 ++++-----
 lib/expr.c              | 99 +++++++++++++++++------------------------
 tests/ovn-controller.at | 79 +++++++++++++++++++++++++++++---
 6 files changed, 153 insertions(+), 100 deletions(-)

Comments

Han Zhou May 1, 2024, 4:37 p.m. UTC | #1
On Tue, Apr 30, 2024 at 9:56 AM Ales Musil <amusil@redhat.com> wrote:
>
> Instead of tracking address set per struct expr_constant_set track it
> per individual struct expr_constant. This allows more fine grained
> control for I-P processing of address sets in controller. It helps with
> scenarios like matching on two address sets in one expression e.g.
> "ip4.src == {$as1, $as2}". This allows any addition or removal of
> individual adress from the set to be incrementally processed instead
> of reprocessing all the flows.
>
> This unfortunately doesn't help with the following flows:
> "ip4.src == $as1 && ip4.dst == $as2"
> "ip4.src == $as1 || ip4.dst == $as2"
>
> The memory impact should be minimal as there is only increase of 8 bytes
> per the struct expr_constant.
>
> Reported-at: https://issues.redhat.com/browse/FDP-509
> Signed-off-by: Ales Musil <amusil@redhat.com>
> ---
> v3: Rebase on top of current main.
>     Address comments from Han:
>     - Adjust the comment for "lflow_handle_addr_set_update" to include
remaning corner cases.
>     - Make sure that the flows are consistent between I-P and recompute.
> v2: Rebase on top of current main.
>     Adjust the comment for I-P optimization.
> ---
>  controller/lflow.c      |  7 ++-
>  include/ovn/actions.h   |  2 +-
>  include/ovn/expr.h      | 46 ++++++++++---------
>  lib/actions.c           | 20 ++++-----
>  lib/expr.c              | 99 +++++++++++++++++------------------------
>  tests/ovn-controller.at | 79 +++++++++++++++++++++++++++++---
>  6 files changed, 153 insertions(+), 100 deletions(-)
>
> diff --git a/controller/lflow.c b/controller/lflow.c
> index 760ec0b41..06e839cbe 100644
> --- a/controller/lflow.c
> +++ b/controller/lflow.c
> @@ -278,7 +278,7 @@ lflow_handle_changed_flows(struct lflow_ctx_in
*l_ctx_in,
>  }
>
>  static bool
> -as_info_from_expr_const(const char *as_name, const union expr_constant
*c,
> +as_info_from_expr_const(const char *as_name, const struct expr_constant
*c,
>                          struct addrset_info *as_info)
>  {
>      as_info->name = as_name;
> @@ -647,9 +647,8 @@ as_update_can_be_handled(const char *as_name, struct
addr_set_diff *as_diff,
>   *        expressions/constants, usually because of disjunctions between
>   *        sub-expressions/constants, e.g.:
>   *
> + *          ip.src == $as1 && ip.dst == $as2
>   *          ip.src == $as1 || ip.dst == $as2
> - *          ip.src == {$as1, $as2}
> - *          ip.src == {$as1, ip1}
>   *
>   *        All these could have been split into separate lflows.

Hi Ales, thanks for v3.

I checked again and wondered why you mentioned that "ip.src == $as1 &&
ip.dst == $as2" is not supported. This expression would generate
conjunctions, which works with I-P before your change and still works. Did
I miss anything?

In addition, since the constraints are relaxed after your change, I'd also
update the above comments a little more, something like:

   *      - The sub expression of the address set is combined with other
sub-
   *        expressions/constants on different fields, e.g.:


   *




   *          ip.src == $as1 || ip.dst == $as2

   *

   *        This could have been split into separate lflows.


What do you think?

Thanks,
Han

>   *
> @@ -714,7 +713,7 @@ lflow_handle_addr_set_update(const char *as_name,
>          if (as_diff->deleted) {
>              struct addrset_info as_info;
>              for (size_t i = 0; i < as_diff->deleted->n_values; i++) {
> -                union expr_constant *c = &as_diff->deleted->values[i];
> +                struct expr_constant *c = &as_diff->deleted->values[i];
>                  if (!as_info_from_expr_const(as_name, c, &as_info)) {
>                      continue;
>                  }
> diff --git a/include/ovn/actions.h b/include/ovn/actions.h
> index ae0864fdd..88cf4de79 100644
> --- a/include/ovn/actions.h
> +++ b/include/ovn/actions.h
> @@ -241,7 +241,7 @@ struct ovnact_next {
>  struct ovnact_load {
>      struct ovnact ovnact;
>      struct expr_field dst;
> -    union expr_constant imm;
> +    struct expr_constant imm;
>  };
>
>  /* OVNACT_MOVE, OVNACT_EXCHANGE. */
> diff --git a/include/ovn/expr.h b/include/ovn/expr.h
> index c48f82398..e54edb5bf 100644
> --- a/include/ovn/expr.h
> +++ b/include/ovn/expr.h
> @@ -368,7 +368,7 @@ bool expr_relop_from_token(enum lex_type type, enum
expr_relop *relop);
>  struct expr {
>      struct ovs_list node;       /* In parent EXPR_T_AND or EXPR_T_OR if
any. */
>      enum expr_type type;        /* Expression type. */
> -    char *as_name;              /* Address set name. Null if it is not an
> +    const char *as_name;        /* Address set name. Null if it is not an
>                                     address set. */
>
>      union {
> @@ -505,40 +505,42 @@ enum expr_constant_type {
>  };
>
>  /* A string or integer constant (one must know which from context). */
> -union expr_constant {
> -    /* Integer constant.
> -     *
> -     * The width of a constant isn't always clear, e.g. if you write "1",
> -     * there's no way to tell whether you mean for that to be a 1-bit
constant
> -     * or a 128-bit constant or somewhere in between. */
> -    struct {
> -        union mf_subvalue value;
> -        union mf_subvalue mask; /* Only initialized if 'masked'. */
> -        bool masked;
> -
> -        enum lex_format format; /* From the constant's lex_token. */
> -    };
> +struct expr_constant {
> +    const char *as_name;
>
> -    /* Null-terminated string constant. */
> -    char *string;
> +    union {
> +        /* Integer constant.
> +         *
> +         * The width of a constant isn't always clear, e.g. if you write
"1",
> +         * there's no way to tell whether you mean for that to be a 1-bit
> +         * constant or a 128-bit constant or somewhere in between. */
> +        struct {
> +            union mf_subvalue value;
> +            union mf_subvalue mask; /* Only initialized if 'masked'. */
> +            bool masked;
> +
> +            enum lex_format format; /* From the constant's lex_token. */
> +        };
> +
> +        /* Null-terminated string constant. */
> +        char *string;
> +    };
>  };
>
>  bool expr_constant_parse(struct lexer *,
>                           const struct expr_field *,
> -                         union expr_constant *);
> -void expr_constant_format(const union expr_constant *,
> +                         struct expr_constant *);
> +void expr_constant_format(const struct expr_constant *,
>                            enum expr_constant_type, struct ds *);
> -void expr_constant_destroy(const union expr_constant *,
> +void expr_constant_destroy(const struct expr_constant *,
>                             enum expr_constant_type);
>
>  /* A collection of "union expr_constant"s of the same type. */
>  struct expr_constant_set {
> -    union expr_constant *values;  /* Constants. */
> +    struct expr_constant *values; /* Constants. */
>      size_t n_values;              /* Number of constants. */
>      enum expr_constant_type type; /* Type of the constants. */
>      bool in_curlies;              /* Whether the constants were in {}. */
> -    char *as_name;                /* Name of an address set. It is NULL
if not
> -                                     an address set. */
>  };
>
>  bool expr_constant_set_parse(struct lexer *, struct expr_constant_set *);
> diff --git a/lib/actions.c b/lib/actions.c
> index 94751d059..cff4f9e3c 100644
> --- a/lib/actions.c
> +++ b/lib/actions.c
> @@ -450,7 +450,7 @@ encode_LOAD(const struct ovnact_load *load,
>              const struct ovnact_encode_params *ep,
>              struct ofpbuf *ofpacts)
>  {
> -    const union expr_constant *c = &load->imm;
> +    const struct expr_constant *c = &load->imm;
>      struct mf_subfield dst = expr_resolve_field(&load->dst);
>      struct ofpact_set_field *sf = ofpact_put_set_field(ofpacts,
dst.field,
>                                                         NULL, NULL);
> @@ -2077,7 +2077,7 @@ encode_event_empty_lb_backends_opts(struct ofpbuf
*ofpacts,
>          /* All empty_lb_backends fields are of type 'str' */
>          ovs_assert(!strcmp(o->option->type, "str"));
>
> -        const union expr_constant *c = o->value.values;
> +        const struct expr_constant *c = o->value.values;
>          size_t size = strlen(c->string);
>          struct controller_event_opt_header hdr =
>              (struct controller_event_opt_header) {
> @@ -2553,7 +2553,7 @@ validate_empty_lb_backends(struct action_context
*ctx,
>  {
>      for (size_t i = 0; i < n_options; i++) {
>          const struct ovnact_gen_option *o = &options[i];
> -        const union expr_constant *c = o->value.values;
> +        const struct expr_constant *c = o->value.values;
>          struct sockaddr_storage ss;
>          struct uuid uuid;
>
> @@ -2861,7 +2861,7 @@ encode_put_dhcpv4_option(const struct
ovnact_gen_option *o,
>      uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 2);
>      opt_header[0] = o->option->code;
>
> -    const union expr_constant *c = o->value.values;
> +    const struct expr_constant *c = o->value.values;
>      size_t n_values = o->value.n_values;
>      if (!strcmp(o->option->type, "bool") ||
>          !strcmp(o->option->type, "uint8")) {
> @@ -3027,7 +3027,7 @@ encode_put_dhcpv6_option(const struct
ovnact_gen_option *o,
>                           struct ofpbuf *ofpacts)
>  {
>      struct dhcpv6_opt_header *opt = ofpbuf_put_uninit(ofpacts, sizeof
*opt);
> -    const union expr_constant *c = o->value.values;
> +    const struct expr_constant *c = o->value.values;
>      size_t n_values = o->value.n_values;
>      size_t size;
>
> @@ -3083,7 +3083,7 @@ encode_PUT_DHCPV4_OPTS(const struct ovnact_put_opts
*pdo,
>          find_opt(pdo->options, pdo->n_options, DHCP_OPT_BOOTFILE_CODE);
>      if (boot_opt) {
>          uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 2);
> -        const union expr_constant *c = boot_opt->value.values;
> +        const struct expr_constant *c = boot_opt->value.values;
>          opt_header[0] = boot_opt->option->code;
>          opt_header[1] = strlen(c->string);
>          ofpbuf_put(ofpacts, c->string, opt_header[1]);
> @@ -3093,7 +3093,7 @@ encode_PUT_DHCPV4_OPTS(const struct ovnact_put_opts
*pdo,
>          find_opt(pdo->options, pdo->n_options,
DHCP_OPT_BOOTFILE_ALT_CODE);
>      if (boot_alt_opt) {
>          uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 2);
> -        const union expr_constant *c = boot_alt_opt->value.values;
> +        const struct expr_constant *c = boot_alt_opt->value.values;
>          opt_header[0] = boot_alt_opt->option->code;
>          opt_header[1] = strlen(c->string);
>          ofpbuf_put(ofpacts, c->string, opt_header[1]);
> @@ -3103,7 +3103,7 @@ encode_PUT_DHCPV4_OPTS(const struct ovnact_put_opts
*pdo,
>          pdo->options, pdo->n_options, DHCP_OPT_NEXT_SERVER_CODE);
>      if (next_server_opt) {
>          uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 2);
> -        const union expr_constant *c = next_server_opt->value.values;
> +        const struct expr_constant *c = next_server_opt->value.values;
>          opt_header[0] = next_server_opt->option->code;
>          opt_header[1] = sizeof(ovs_be32);
>          ofpbuf_put(ofpacts, &c->value.ipv4, sizeof(ovs_be32));
> @@ -3307,7 +3307,7 @@ parse_put_nd_ra_opts(struct action_context *ctx,
const struct expr_field *dst,
>      /* Let's validate the options. */
>      for (size_t i = 0; i < po->n_options; i++) {
>          const struct ovnact_gen_option *o = &po->options[i];
> -        const union expr_constant *c = o->value.values;
> +        const struct expr_constant *c = o->value.values;
>          if (o->value.n_values > 1) {
>              lexer_error(ctx->lexer, "Invalid value for \"%s\" option",
>                          o->option->name);
> @@ -3490,7 +3490,7 @@ static void
>  encode_put_nd_ra_option(const struct ovnact_gen_option *o,
>                          struct ofpbuf *ofpacts, ptrdiff_t ra_offset)
>  {
> -    const union expr_constant *c = o->value.values;
> +    const struct expr_constant *c = o->value.values;
>
>      switch (o->option->code) {
>      case ND_RA_FLAG_ADDR_MODE:
> diff --git a/lib/expr.c b/lib/expr.c
> index 0cb44e1b6..ecf8a6338 100644
> --- a/lib/expr.c
> +++ b/lib/expr.c
> @@ -179,12 +179,10 @@ expr_combine(enum expr_type type, struct expr *a,
struct expr *b)
>          } else {
>              ovs_list_push_back(&a->andor, &b->node);
>          }
> -        free(a->as_name);
>          a->as_name = NULL;
>          return a;
>      } else if (b->type == type) {
>          ovs_list_push_front(&b->andor, &a->node);
> -        free(b->as_name);
>          b->as_name = NULL;
>          return b;
>      } else {
> @@ -521,12 +519,13 @@ static bool parse_field(struct expr_context *,
struct expr_field *);
>
>  static struct expr *
>  make_cmp__(const struct expr_field *f, enum expr_relop r,
> -             const union expr_constant *c)
> +             const struct expr_constant *c)
>  {
>      struct expr *e = xzalloc(sizeof *e);
>      e->type = EXPR_T_CMP;
>      e->cmp.symbol = f->symbol;
>      e->cmp.relop = r;
> +    e->as_name = c->as_name;
>      if (f->symbol->width) {
>          bitwise_copy(&c->value, sizeof c->value, 0,
>                       &e->cmp.value, sizeof e->cmp.value, f->ofs,
> @@ -547,7 +546,7 @@ make_cmp__(const struct expr_field *f, enum
expr_relop r,
>
>  /* Returns the minimum reasonable width for integer constant 'c'. */
>  static int
> -expr_constant_width(const union expr_constant *c)
> +expr_constant_width(const struct expr_constant *c)
>  {
>      if (c->masked) {
>          return mf_subvalue_width(&c->mask);
> @@ -674,10 +673,7 @@ make_cmp(struct expr_context *ctx,
>          e = expr_combine(r == EXPR_R_EQ ? EXPR_T_OR : EXPR_T_AND,
>                           e, make_cmp__(f, r, &cs->values[i]));
>      }
> -    /* Track address set */
> -    if (r == EXPR_R_EQ && e->type == EXPR_T_OR && cs->as_name) {
> -        e->as_name = xstrdup(cs->as_name);
> -    }
> +
>  exit:
>      expr_constant_set_destroy(cs);
>      return e;
> @@ -797,11 +793,10 @@ parse_addr_sets(struct expr_context *ctx, struct
expr_constant_set *cs,
>          }
>      }
>
> -    struct expr_constant_set *addr_sets
> -        = (ctx->addr_sets
> -           ? shash_find_data(ctx->addr_sets, ctx->lexer->token.s)
> -           : NULL);
> -    if (!addr_sets) {
> +    struct shash_node *node = ctx->addr_sets
> +                              ? shash_find(ctx->addr_sets,
ctx->lexer->token.s)
> +                              : NULL;
> +    if (!node) {
>          lexer_syntax_error(ctx->lexer, "expecting address set name");
>          return false;
>      }
> @@ -810,17 +805,16 @@ parse_addr_sets(struct expr_context *ctx, struct
expr_constant_set *cs,
>          return false;
>      }
>
> -    if (!cs->n_values) {
> -        cs->as_name = xstrdup(ctx->lexer->token.s);
> -    }
> -
> +    struct expr_constant_set *addr_sets = node->data;
>      size_t n_values = cs->n_values + addr_sets->n_values;
>      if (n_values >= *allocated_values) {
>          cs->values = xrealloc(cs->values, n_values * sizeof *cs->values);
>          *allocated_values = n_values;
>      }
>      for (size_t i = 0; i < addr_sets->n_values; i++) {
> -        cs->values[cs->n_values++] = addr_sets->values[i];
> +        struct expr_constant *c = &cs->values[cs->n_values++];
> +        *c = addr_sets->values[i];
> +        c->as_name = node->name;
>      }
>
>      return true;
> @@ -859,8 +853,9 @@ parse_port_group(struct expr_context *ctx, struct
expr_constant_set *cs,
>          *allocated_values = n_values;
>      }
>      for (size_t i = 0; i < port_group->n_values; i++) {
> -        cs->values[cs->n_values++].string =
> -            xstrdup(port_group->values[i].string);
> +        struct expr_constant *c = &cs->values[cs->n_values++];
> +        c->string = xstrdup(port_group->values[i].string);
> +        c->as_name = NULL;
>      }
>
>      return true;
> @@ -875,11 +870,6 @@ parse_constant(struct expr_context *ctx, struct
expr_constant_set *cs,
>                                  sizeof *cs->values);
>      }
>
> -    /* Combining other values to the constant set that is tracking an
> -     * address set, so untrack it. */
> -    free(cs->as_name);
> -    cs->as_name = NULL;
> -
>      if (ctx->lexer->token.type == LEX_T_TEMPLATE) {
>          lexer_error(ctx->lexer, "Unexpanded template.");
>          return false;
> @@ -887,7 +877,9 @@ parse_constant(struct expr_context *ctx, struct
expr_constant_set *cs,
>          if (!assign_constant_set_type(ctx, cs, EXPR_C_STRING)) {
>              return false;
>          }
> -        cs->values[cs->n_values++].string = xstrdup(ctx->lexer->token.s);
> +        struct expr_constant *c = &cs->values[cs->n_values++];
> +        c->string = xstrdup(ctx->lexer->token.s);
> +        c->as_name = NULL;
>          lexer_get(ctx->lexer);
>          return true;
>      } else if (ctx->lexer->token.type == LEX_T_INTEGER ||
> @@ -896,13 +888,14 @@ parse_constant(struct expr_context *ctx, struct
expr_constant_set *cs,
>              return false;
>          }
>
> -        union expr_constant *c = &cs->values[cs->n_values++];
> +        struct expr_constant *c = &cs->values[cs->n_values++];
>          c->value = ctx->lexer->token.value;
>          c->format = ctx->lexer->token.format;
>          c->masked = ctx->lexer->token.type == LEX_T_MASKED_INTEGER;
>          if (c->masked) {
>              c->mask = ctx->lexer->token.mask;
>          }
> +        c->as_name = NULL;
>          lexer_get(ctx->lexer);
>          return true;
>      } else if (ctx->lexer->token.type == LEX_T_MACRO) {
> @@ -961,7 +954,7 @@ parse_constant_set(struct expr_context *ctx, struct
expr_constant_set *cs)
>   * indeterminate. */
>  bool
>  expr_constant_parse(struct lexer *lexer, const struct expr_field *f,
> -                    union expr_constant *c)
> +                    struct expr_constant *c)
>  {
>      if (lexer->error) {
>          return false;
> @@ -987,7 +980,7 @@ expr_constant_parse(struct lexer *lexer, const struct
expr_field *f,
>  /* Appends to 's' a re-parseable representation of constant 'c' with the
given
>   * 'type'. */
>  void
> -expr_constant_format(const union expr_constant *c,
> +expr_constant_format(const struct expr_constant *c,
>                       enum expr_constant_type type, struct ds *s)
>  {
>      if (type == EXPR_C_STRING) {
> @@ -1010,7 +1003,7 @@ expr_constant_format(const union expr_constant *c,
>   *
>   * Does not free(c). */
>  void
> -expr_constant_destroy(const union expr_constant *c,
> +expr_constant_destroy(const struct expr_constant *c,
>                        enum expr_constant_type type)
>  {
>      if (c && type == EXPR_C_STRING) {
> @@ -1043,7 +1036,7 @@ expr_constant_set_format(const struct
expr_constant_set *cs, struct ds *s)
>          ds_put_char(s, '{');
>      }
>
> -    for (const union expr_constant *c = cs->values;
> +    for (const struct expr_constant *c = cs->values;
>           c < &cs->values[cs->n_values]; c++) {
>          if (c != cs->values) {
>              ds_put_cstr(s, ", ");
> @@ -1067,15 +1060,14 @@ expr_constant_set_destroy(struct
expr_constant_set *cs)
>              }
>          }
>          free(cs->values);
> -        free(cs->as_name);
>      }
>  }
>
>  static int
>  compare_expr_constant_integer_cb(const void *a_, const void *b_)
>  {
> -    const union expr_constant *a = a_;
> -    const union expr_constant *b = b_;
> +    const struct expr_constant *a = a_;
> +    const struct expr_constant *b = b_;
>
>      int d = memcmp(&a->value, &b->value, sizeof a->value);
>      if (d) {
> @@ -1114,7 +1106,7 @@ expr_constant_set_create_integers(const char *const
*values, size_t n_values)
>              VLOG_WARN("Invalid constant set entry: '%s', token type: %d",
>                        values[i], lex.token.type);
>          } else {
> -            union expr_constant *c = &cs->values[cs->n_values++];
> +            struct expr_constant *c = &cs->values[cs->n_values++];
>              c->value = lex.token.value;
>              c->format = lex.token.format;
>              c->masked = lex.token.type == LEX_T_MASKED_INTEGER;
> @@ -1135,7 +1127,7 @@ expr_constant_set_create_integers(const char *const
*values, size_t n_values)
>
>  static void
>  expr_constant_set_add_value(struct expr_constant_set **p_cs,
> -                            union expr_constant *c, size_t *allocated)
> +                            struct expr_constant *c, size_t *allocated)
>  {
>      struct expr_constant_set *cs = *p_cs;
>      if (!cs) {
> @@ -1246,7 +1238,7 @@ expr_const_sets_add_strings(struct shash
*const_sets, const char *name,
>                          values[i], name);
>              continue;
>          }
> -        union expr_constant *c = &cs->values[cs->n_values++];
> +        struct expr_constant *c = &cs->values[cs->n_values++];
>          c->string = xstrdup(values[i]);
>      }
>
> @@ -1359,7 +1351,7 @@ expr_parse_primary(struct expr_context *ctx, bool
*atomic)
>
>              *atomic = true;
>
> -            union expr_constant *cst = xzalloc(sizeof *cst);
> +            struct expr_constant *cst = xzalloc(sizeof *cst);
>              cst->format = LEX_F_HEXADECIMAL;
>              cst->masked = false;
>
> @@ -1367,7 +1359,6 @@ expr_parse_primary(struct expr_context *ctx, bool
*atomic)
>              c.values = cst;
>              c.n_values = 1;
>              c.in_curlies = false;
> -            c.as_name = NULL;
>              return make_cmp(ctx, &f, EXPR_R_NE, &c);
>          } else if (parse_relop(ctx, &r) && parse_constant_set(ctx, &c)) {
>              return make_cmp(ctx, &f, r, &c);
> @@ -1834,7 +1825,6 @@ expr_symtab_destroy(struct shash *symtab)
>  static struct expr *
>  expr_clone_cmp(struct expr *expr)
>  {
> -    ovs_assert(!expr->as_name);
>      struct expr *new = xmemdup(expr, sizeof *expr);
>      if (!new->cmp.symbol->width) {
>          new->cmp.string = xstrdup(new->cmp.string);
> @@ -1858,7 +1848,6 @@ expr_clone_andor(struct expr *expr)
>  static struct expr *
>  expr_clone_condition(struct expr *expr)
>  {
> -    ovs_assert(!expr->as_name);
>      struct expr *new = xmemdup(expr, sizeof *expr);
>      new->cond.string = xstrdup(new->cond.string);
>      return new;
> @@ -1894,8 +1883,6 @@ expr_destroy(struct expr *expr)
>          return;
>      }
>
> -    free(expr->as_name);
> -
>      struct expr *sub;
>
>      switch (expr->type) {
> @@ -2567,7 +2554,7 @@ crush_or_supersets(struct expr *expr, const struct
expr_symbol *symbol)
>       * mask sizes. */
>      size_t n = ovs_list_size(&expr->andor);
>      struct expr **subs = xmalloc(n * sizeof *subs);
> -    bool modified = false;
> +    bool has_addr_set = false;
>      /* Linked list over the 'subs' array to quickly skip deleted
elements,
>       * i.e. the index of the next potentially non-NULL element. */
>      size_t *next = xmalloc(n * sizeof *next);
> @@ -2575,6 +2562,9 @@ crush_or_supersets(struct expr *expr, const struct
expr_symbol *symbol)
>      size_t i = 0, j, max_n_bits = 0;
>      struct expr *sub;
>      LIST_FOR_EACH (sub, node, &expr->andor) {
> +        if (sub->as_name) {
> +            has_addr_set = true;
> +        }
>          if (symbol->width) {
>              const unsigned long *sub_mask;
>
> @@ -2596,14 +2586,14 @@ crush_or_supersets(struct expr *expr, const
struct expr_symbol *symbol)
>              next[last] = i;
>              last = i;
>          } else {
> +            /* Remove address set reference from the duplicate. */
> +            subs[last]->as_name = NULL;
>              expr_destroy(subs[i]);
>              subs[i] = NULL;
> -            modified = true;
>          }
>      }
>
> -    if (!symbol->width || symbol->level != EXPR_L_ORDINAL
> -        || (!modified && expr->as_name)) {
> +    if (!symbol->width || symbol->level != EXPR_L_ORDINAL ||
has_addr_set) {
>          /* Not a fully maskable field or this expression is tracking an
>           * address set.  Don't try to optimize to preserve address set
I-P. */
>          goto done;
> @@ -2658,10 +2648,11 @@ crush_or_supersets(struct expr *expr, const
struct expr_symbol *symbol)
>              if (expr_bitmap_intersect_check(a_value, a_mask, b_value,
b_mask,
>                                              bit_width)
>                  && bitmap_is_superset(b_mask, a_mask, bit_width)) {
> -                /* 'a' is the same expression with a smaller mask. */
> +                /* 'a' is the same expression with a smaller mask.
> +                 * Remove address set reference from the duplicate. */
> +                a->as_name = NULL;
>                  expr_destroy(subs[j]);
>                  subs[j] = NULL;
> -                modified = true;
>
>                  /* Shorten the path for the next round. */
>                  if (last) {
> @@ -2685,12 +2676,6 @@ done:
>          }
>      }
>
> -    if (modified) {
> -        /* Members modified, so untrack address set. */
> -        free(expr->as_name);
> -        expr->as_name = NULL;
> -    }
> -
>      free(next);
>      free(subs);
>      return expr;
> @@ -3271,10 +3256,10 @@ add_disjunction(const struct expr *or,
>      LIST_FOR_EACH (sub, node, &or->andor) {
>          struct expr_match *match = expr_match_new(m, clause, n_clauses,
>                                                    conj_id);
> -        if (or->as_name) {
> +        if (sub->as_name) {
>              ovs_assert(sub->type == EXPR_T_CMP);
>              ovs_assert(sub->cmp.symbol->width);
> -            match->as_name = xstrdup(or->as_name);
> +            match->as_name = xstrdup(sub->as_name);
>              match->as_ip = sub->cmp.value.ipv6;
>              match->as_mask = sub->cmp.mask.ipv6;
>          }
> diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
> index be198e00d..27cec2aec 100644
> --- a/tests/ovn-controller.at
> +++ b/tests/ovn-controller.at
> @@ -1625,7 +1625,9 @@
priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.8
actions=lo
>  done
>
>  reprocess_count_new=$(read_counter consider_logical_flow)
> -AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0],
[10
> +# First 2 reprocess are due to change from 1 IP in AS to 2.
> +# Last 5 is due to overlap in IP addresses between as1 and as2.
> +AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0], [7
>  ])
>
>  # Remove the IPs from as1 and as2, 1 IP each time.
> @@ -1648,9 +1650,9 @@ for i in $(seq 10); do
>  done
>
>  reprocess_count_new=$(read_counter consider_logical_flow)
> -# In this case the as1 and as2 are merged to a single OR expr, so we
lose track of
> -# address set information - can't handle deletion incrementally.
> -AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0],
[10
> +# First 5 are due to overlap in IP addresses between as1 and as2.
> +# Last 2 are due to change from 2 IP in AS to 1.
> +AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0], [7
>  ])
>
>  OVN_CLEANUP([hv1])
> @@ -1817,7 +1819,7 @@
priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.5
actions=co
>  priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.10.10.10
actions=conjunction,2/2)
>  ])
>  reprocess_count_new=$(read_counter consider_logical_flow)
> -AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0], [1
> +AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0], [0
>  ])
>
>  # Delete 2 IPs
> @@ -1837,7 +1839,7 @@
priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.3
actions=co
>  priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.10.10.10
actions=conjunction,2/2)
>  ])
>  reprocess_count_new=$(read_counter consider_logical_flow)
> -AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0], [1
> +AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0], [0
>  ])
>
>  OVN_CLEANUP([hv1])
> @@ -2906,3 +2908,68 @@ OVN_CLEANUP([hv1],[hv2])
>
>  AT_CLEANUP
>  ])
> +
> +AT_SETUP([ovn-controller - AS I-P and recompute consistency])
> +AT_KEYWORDS([as-i-p])
> +
> +ovn_start
> +
> +net_add n1
> +sim_add hv1
> +as hv1
> +check ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.1
> +check ovs-vsctl -- add-port br-int hv1-vif1 -- \
> +    set interface hv1-vif1 external-ids:iface-id=ls1-lp1
> +
> +check ovn-nbctl ls-add ls1
> +
> +check ovn-nbctl lsp-add ls1 ls1-lp1 \
> +-- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01"
> +
> +wait_for_ports_up
> +ovn-appctl -t ovn-controller vlog/set file:dbg
> +
> +# Get the OF table numbers
> +acl_eval=$(ovn-debug lflow-stage-to-oftable ls_out_acl_eval)
> +acl_action=$(ovn-debug lflow-stage-to-oftable ls_out_acl_action)
> +
> +dp_key=$(printf "%x" $(fetch_column datapath tunnel_key
external_ids:name=ls1))
> +port_key=$(printf "%x" $(fetch_column port_binding tunnel_key
logical_port=ls1-lp1))
> +
> +ovn-nbctl create address_set name=as1
> +check ovn-nbctl acl-add ls1 to-lport 100 'outport == "ls1-lp1" &&
ip4.src == $as1' drop
> +check ovn-nbctl add address_set as1 addresses 10.0.0.0/24
> +check ovn-nbctl --wait=hv sync
> +
> +AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int
table=$acl_eval,reg15=0x$port_key | grep -v reply | awk '{print $7, $8}' |
sort], [0], [dnl
> +priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.0/24
actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
> +])
> +
> +check ovn-nbctl add address_set as1 addresses 10.0.0.1
> +check ovn-nbctl add address_set as1 addresses 10.0.0.2
> +check ovn-nbctl add address_set as1 addresses 10.0.0.3
> +check ovn-nbctl add address_set as1 addresses 10.0.0.4
> +check ovn-nbctl --wait=hv sync
> +
> +AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int
table=$acl_eval,reg15=0x$port_key | grep -v reply | awk '{print $7, $8}' |
sort], [0], [dnl
> +priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.0/24
actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
> +priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.1
actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
> +priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.2
actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
> +priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.3
actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
> +priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.4
actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
> +])
> +
> +
> +check ovn-appctl inc-engine/recompute
> +
> +AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int
table=$acl_eval,reg15=0x$port_key | grep -v reply | awk '{print $7, $8}' |
sort], [0], [dnl
> +priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.0/24
actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
> +priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.1
actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
> +priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.2
actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
> +priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.3
actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
> +priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.4
actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
> +])
> +
> +OVN_CLEANUP([hv1])
> +AT_CLEANUP
> --
> 2.44.0
>
Ales Musil May 2, 2024, 1:02 p.m. UTC | #2
On Wed, May 1, 2024 at 6:38 PM Han Zhou <hzhou@ovn.org> wrote:

>
>
> On Tue, Apr 30, 2024 at 9:56 AM Ales Musil <amusil@redhat.com> wrote:
> >
> > Instead of tracking address set per struct expr_constant_set track it
> > per individual struct expr_constant. This allows more fine grained
> > control for I-P processing of address sets in controller. It helps with
> > scenarios like matching on two address sets in one expression e.g.
> > "ip4.src == {$as1, $as2}". This allows any addition or removal of
> > individual adress from the set to be incrementally processed instead
> > of reprocessing all the flows.
> >
> > This unfortunately doesn't help with the following flows:
> > "ip4.src == $as1 && ip4.dst == $as2"
> > "ip4.src == $as1 || ip4.dst == $as2"
> >
> > The memory impact should be minimal as there is only increase of 8 bytes
> > per the struct expr_constant.
> >
> > Reported-at: https://issues.redhat.com/browse/FDP-509
> > Signed-off-by: Ales Musil <amusil@redhat.com>
> > ---
> > v3: Rebase on top of current main.
> >     Address comments from Han:
> >     - Adjust the comment for "lflow_handle_addr_set_update" to include
> remaning corner cases.
> >     - Make sure that the flows are consistent between I-P and recompute.
> > v2: Rebase on top of current main.
> >     Adjust the comment for I-P optimization.
> > ---
> >  controller/lflow.c      |  7 ++-
> >  include/ovn/actions.h   |  2 +-
> >  include/ovn/expr.h      | 46 ++++++++++---------
> >  lib/actions.c           | 20 ++++-----
> >  lib/expr.c              | 99 +++++++++++++++++------------------------
> >  tests/ovn-controller.at | 79 +++++++++++++++++++++++++++++---
> >  6 files changed, 153 insertions(+), 100 deletions(-)
> >
> > diff --git a/controller/lflow.c b/controller/lflow.c
> > index 760ec0b41..06e839cbe 100644
> > --- a/controller/lflow.c
> > +++ b/controller/lflow.c
> > @@ -278,7 +278,7 @@ lflow_handle_changed_flows(struct lflow_ctx_in
> *l_ctx_in,
> >  }
> >
> >  static bool
> > -as_info_from_expr_const(const char *as_name, const union expr_constant
> *c,
> > +as_info_from_expr_const(const char *as_name, const struct expr_constant
> *c,
> >                          struct addrset_info *as_info)
> >  {
> >      as_info->name = as_name;
> > @@ -647,9 +647,8 @@ as_update_can_be_handled(const char *as_name, struct
> addr_set_diff *as_diff,
> >   *        expressions/constants, usually because of disjunctions between
> >   *        sub-expressions/constants, e.g.:
> >   *
> > + *          ip.src == $as1 && ip.dst == $as2
> >   *          ip.src == $as1 || ip.dst == $as2
> > - *          ip.src == {$as1, $as2}
> > - *          ip.src == {$as1, ip1}
> >   *
> >   *        All these could have been split into separate lflows.
>
> Hi Ales, thanks for v3.
>

Hi Han,


> I checked again and wondered why you mentioned that "ip.src == $as1 &&
> ip.dst == $as2" is not supported. This expression would generate
> conjunctions, which works with I-P before your change and still works. Did
> I miss anything?
>

yeah my bad, I was focused on this patch rather than what is supported
overall.


>
> In addition, since the constraints are relaxed after your change, I'd also
> update the above comments a little more, something like:
>
>    *      - The sub expression of the address set is combined with other
> sub-
>    *        expressions/constants on different fields, e.g.:
>
>
>    *
>
>
>
>
>    *          ip.src == $as1 || ip.dst == $as2
>
>    *
>
>    *        This could have been split into separate lflows.
>
>
> What do you think?
>

Sounds good, I'll post v4 with this update.


>
> Thanks,
> Han
>
> >   *
> > @@ -714,7 +713,7 @@ lflow_handle_addr_set_update(const char *as_name,
> >          if (as_diff->deleted) {
> >              struct addrset_info as_info;
> >              for (size_t i = 0; i < as_diff->deleted->n_values; i++) {
> > -                union expr_constant *c = &as_diff->deleted->values[i];
> > +                struct expr_constant *c = &as_diff->deleted->values[i];
> >                  if (!as_info_from_expr_const(as_name, c, &as_info)) {
> >                      continue;
> >                  }
> > diff --git a/include/ovn/actions.h b/include/ovn/actions.h
> > index ae0864fdd..88cf4de79 100644
> > --- a/include/ovn/actions.h
> > +++ b/include/ovn/actions.h
> > @@ -241,7 +241,7 @@ struct ovnact_next {
> >  struct ovnact_load {
> >      struct ovnact ovnact;
> >      struct expr_field dst;
> > -    union expr_constant imm;
> > +    struct expr_constant imm;
> >  };
> >
> >  /* OVNACT_MOVE, OVNACT_EXCHANGE. */
> > diff --git a/include/ovn/expr.h b/include/ovn/expr.h
> > index c48f82398..e54edb5bf 100644
> > --- a/include/ovn/expr.h
> > +++ b/include/ovn/expr.h
> > @@ -368,7 +368,7 @@ bool expr_relop_from_token(enum lex_type type, enum
> expr_relop *relop);
> >  struct expr {
> >      struct ovs_list node;       /* In parent EXPR_T_AND or EXPR_T_OR if
> any. */
> >      enum expr_type type;        /* Expression type. */
> > -    char *as_name;              /* Address set name. Null if it is not
> an
> > +    const char *as_name;        /* Address set name. Null if it is not
> an
> >                                     address set. */
> >
> >      union {
> > @@ -505,40 +505,42 @@ enum expr_constant_type {
> >  };
> >
> >  /* A string or integer constant (one must know which from context). */
> > -union expr_constant {
> > -    /* Integer constant.
> > -     *
> > -     * The width of a constant isn't always clear, e.g. if you write
> "1",
> > -     * there's no way to tell whether you mean for that to be a 1-bit
> constant
> > -     * or a 128-bit constant or somewhere in between. */
> > -    struct {
> > -        union mf_subvalue value;
> > -        union mf_subvalue mask; /* Only initialized if 'masked'. */
> > -        bool masked;
> > -
> > -        enum lex_format format; /* From the constant's lex_token. */
> > -    };
> > +struct expr_constant {
> > +    const char *as_name;
> >
> > -    /* Null-terminated string constant. */
> > -    char *string;
> > +    union {
> > +        /* Integer constant.
> > +         *
> > +         * The width of a constant isn't always clear, e.g. if you
> write "1",
> > +         * there's no way to tell whether you mean for that to be a
> 1-bit
> > +         * constant or a 128-bit constant or somewhere in between. */
> > +        struct {
> > +            union mf_subvalue value;
> > +            union mf_subvalue mask; /* Only initialized if 'masked'. */
> > +            bool masked;
> > +
> > +            enum lex_format format; /* From the constant's lex_token. */
> > +        };
> > +
> > +        /* Null-terminated string constant. */
> > +        char *string;
> > +    };
> >  };
> >
> >  bool expr_constant_parse(struct lexer *,
> >                           const struct expr_field *,
> > -                         union expr_constant *);
> > -void expr_constant_format(const union expr_constant *,
> > +                         struct expr_constant *);
> > +void expr_constant_format(const struct expr_constant *,
> >                            enum expr_constant_type, struct ds *);
> > -void expr_constant_destroy(const union expr_constant *,
> > +void expr_constant_destroy(const struct expr_constant *,
> >                             enum expr_constant_type);
> >
> >  /* A collection of "union expr_constant"s of the same type. */
> >  struct expr_constant_set {
> > -    union expr_constant *values;  /* Constants. */
> > +    struct expr_constant *values; /* Constants. */
> >      size_t n_values;              /* Number of constants. */
> >      enum expr_constant_type type; /* Type of the constants. */
> >      bool in_curlies;              /* Whether the constants were in {}.
> */
> > -    char *as_name;                /* Name of an address set. It is NULL
> if not
> > -                                     an address set. */
> >  };
> >
> >  bool expr_constant_set_parse(struct lexer *, struct expr_constant_set
> *);
> > diff --git a/lib/actions.c b/lib/actions.c
> > index 94751d059..cff4f9e3c 100644
> > --- a/lib/actions.c
> > +++ b/lib/actions.c
> > @@ -450,7 +450,7 @@ encode_LOAD(const struct ovnact_load *load,
> >              const struct ovnact_encode_params *ep,
> >              struct ofpbuf *ofpacts)
> >  {
> > -    const union expr_constant *c = &load->imm;
> > +    const struct expr_constant *c = &load->imm;
> >      struct mf_subfield dst = expr_resolve_field(&load->dst);
> >      struct ofpact_set_field *sf = ofpact_put_set_field(ofpacts,
> dst.field,
> >                                                         NULL, NULL);
> > @@ -2077,7 +2077,7 @@ encode_event_empty_lb_backends_opts(struct ofpbuf
> *ofpacts,
> >          /* All empty_lb_backends fields are of type 'str' */
> >          ovs_assert(!strcmp(o->option->type, "str"));
> >
> > -        const union expr_constant *c = o->value.values;
> > +        const struct expr_constant *c = o->value.values;
> >          size_t size = strlen(c->string);
> >          struct controller_event_opt_header hdr =
> >              (struct controller_event_opt_header) {
> > @@ -2553,7 +2553,7 @@ validate_empty_lb_backends(struct action_context
> *ctx,
> >  {
> >      for (size_t i = 0; i < n_options; i++) {
> >          const struct ovnact_gen_option *o = &options[i];
> > -        const union expr_constant *c = o->value.values;
> > +        const struct expr_constant *c = o->value.values;
> >          struct sockaddr_storage ss;
> >          struct uuid uuid;
> >
> > @@ -2861,7 +2861,7 @@ encode_put_dhcpv4_option(const struct
> ovnact_gen_option *o,
> >      uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 2);
> >      opt_header[0] = o->option->code;
> >
> > -    const union expr_constant *c = o->value.values;
> > +    const struct expr_constant *c = o->value.values;
> >      size_t n_values = o->value.n_values;
> >      if (!strcmp(o->option->type, "bool") ||
> >          !strcmp(o->option->type, "uint8")) {
> > @@ -3027,7 +3027,7 @@ encode_put_dhcpv6_option(const struct
> ovnact_gen_option *o,
> >                           struct ofpbuf *ofpacts)
> >  {
> >      struct dhcpv6_opt_header *opt = ofpbuf_put_uninit(ofpacts, sizeof
> *opt);
> > -    const union expr_constant *c = o->value.values;
> > +    const struct expr_constant *c = o->value.values;
> >      size_t n_values = o->value.n_values;
> >      size_t size;
> >
> > @@ -3083,7 +3083,7 @@ encode_PUT_DHCPV4_OPTS(const struct
> ovnact_put_opts *pdo,
> >          find_opt(pdo->options, pdo->n_options, DHCP_OPT_BOOTFILE_CODE);
> >      if (boot_opt) {
> >          uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 2);
> > -        const union expr_constant *c = boot_opt->value.values;
> > +        const struct expr_constant *c = boot_opt->value.values;
> >          opt_header[0] = boot_opt->option->code;
> >          opt_header[1] = strlen(c->string);
> >          ofpbuf_put(ofpacts, c->string, opt_header[1]);
> > @@ -3093,7 +3093,7 @@ encode_PUT_DHCPV4_OPTS(const struct
> ovnact_put_opts *pdo,
> >          find_opt(pdo->options, pdo->n_options,
> DHCP_OPT_BOOTFILE_ALT_CODE);
> >      if (boot_alt_opt) {
> >          uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 2);
> > -        const union expr_constant *c = boot_alt_opt->value.values;
> > +        const struct expr_constant *c = boot_alt_opt->value.values;
> >          opt_header[0] = boot_alt_opt->option->code;
> >          opt_header[1] = strlen(c->string);
> >          ofpbuf_put(ofpacts, c->string, opt_header[1]);
> > @@ -3103,7 +3103,7 @@ encode_PUT_DHCPV4_OPTS(const struct
> ovnact_put_opts *pdo,
> >          pdo->options, pdo->n_options, DHCP_OPT_NEXT_SERVER_CODE);
> >      if (next_server_opt) {
> >          uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 2);
> > -        const union expr_constant *c = next_server_opt->value.values;
> > +        const struct expr_constant *c = next_server_opt->value.values;
> >          opt_header[0] = next_server_opt->option->code;
> >          opt_header[1] = sizeof(ovs_be32);
> >          ofpbuf_put(ofpacts, &c->value.ipv4, sizeof(ovs_be32));
> > @@ -3307,7 +3307,7 @@ parse_put_nd_ra_opts(struct action_context *ctx,
> const struct expr_field *dst,
> >      /* Let's validate the options. */
> >      for (size_t i = 0; i < po->n_options; i++) {
> >          const struct ovnact_gen_option *o = &po->options[i];
> > -        const union expr_constant *c = o->value.values;
> > +        const struct expr_constant *c = o->value.values;
> >          if (o->value.n_values > 1) {
> >              lexer_error(ctx->lexer, "Invalid value for \"%s\" option",
> >                          o->option->name);
> > @@ -3490,7 +3490,7 @@ static void
> >  encode_put_nd_ra_option(const struct ovnact_gen_option *o,
> >                          struct ofpbuf *ofpacts, ptrdiff_t ra_offset)
> >  {
> > -    const union expr_constant *c = o->value.values;
> > +    const struct expr_constant *c = o->value.values;
> >
> >      switch (o->option->code) {
> >      case ND_RA_FLAG_ADDR_MODE:
> > diff --git a/lib/expr.c b/lib/expr.c
> > index 0cb44e1b6..ecf8a6338 100644
> > --- a/lib/expr.c
> > +++ b/lib/expr.c
> > @@ -179,12 +179,10 @@ expr_combine(enum expr_type type, struct expr *a,
> struct expr *b)
> >          } else {
> >              ovs_list_push_back(&a->andor, &b->node);
> >          }
> > -        free(a->as_name);
> >          a->as_name = NULL;
> >          return a;
> >      } else if (b->type == type) {
> >          ovs_list_push_front(&b->andor, &a->node);
> > -        free(b->as_name);
> >          b->as_name = NULL;
> >          return b;
> >      } else {
> > @@ -521,12 +519,13 @@ static bool parse_field(struct expr_context *,
> struct expr_field *);
> >
> >  static struct expr *
> >  make_cmp__(const struct expr_field *f, enum expr_relop r,
> > -             const union expr_constant *c)
> > +             const struct expr_constant *c)
> >  {
> >      struct expr *e = xzalloc(sizeof *e);
> >      e->type = EXPR_T_CMP;
> >      e->cmp.symbol = f->symbol;
> >      e->cmp.relop = r;
> > +    e->as_name = c->as_name;
> >      if (f->symbol->width) {
> >          bitwise_copy(&c->value, sizeof c->value, 0,
> >                       &e->cmp.value, sizeof e->cmp.value, f->ofs,
> > @@ -547,7 +546,7 @@ make_cmp__(const struct expr_field *f, enum
> expr_relop r,
> >
> >  /* Returns the minimum reasonable width for integer constant 'c'. */
> >  static int
> > -expr_constant_width(const union expr_constant *c)
> > +expr_constant_width(const struct expr_constant *c)
> >  {
> >      if (c->masked) {
> >          return mf_subvalue_width(&c->mask);
> > @@ -674,10 +673,7 @@ make_cmp(struct expr_context *ctx,
> >          e = expr_combine(r == EXPR_R_EQ ? EXPR_T_OR : EXPR_T_AND,
> >                           e, make_cmp__(f, r, &cs->values[i]));
> >      }
> > -    /* Track address set */
> > -    if (r == EXPR_R_EQ && e->type == EXPR_T_OR && cs->as_name) {
> > -        e->as_name = xstrdup(cs->as_name);
> > -    }
> > +
> >  exit:
> >      expr_constant_set_destroy(cs);
> >      return e;
> > @@ -797,11 +793,10 @@ parse_addr_sets(struct expr_context *ctx, struct
> expr_constant_set *cs,
> >          }
> >      }
> >
> > -    struct expr_constant_set *addr_sets
> > -        = (ctx->addr_sets
> > -           ? shash_find_data(ctx->addr_sets, ctx->lexer->token.s)
> > -           : NULL);
> > -    if (!addr_sets) {
> > +    struct shash_node *node = ctx->addr_sets
> > +                              ? shash_find(ctx->addr_sets,
> ctx->lexer->token.s)
> > +                              : NULL;
> > +    if (!node) {
> >          lexer_syntax_error(ctx->lexer, "expecting address set name");
> >          return false;
> >      }
> > @@ -810,17 +805,16 @@ parse_addr_sets(struct expr_context *ctx, struct
> expr_constant_set *cs,
> >          return false;
> >      }
> >
> > -    if (!cs->n_values) {
> > -        cs->as_name = xstrdup(ctx->lexer->token.s);
> > -    }
> > -
> > +    struct expr_constant_set *addr_sets = node->data;
> >      size_t n_values = cs->n_values + addr_sets->n_values;
> >      if (n_values >= *allocated_values) {
> >          cs->values = xrealloc(cs->values, n_values * sizeof
> *cs->values);
> >          *allocated_values = n_values;
> >      }
> >      for (size_t i = 0; i < addr_sets->n_values; i++) {
> > -        cs->values[cs->n_values++] = addr_sets->values[i];
> > +        struct expr_constant *c = &cs->values[cs->n_values++];
> > +        *c = addr_sets->values[i];
> > +        c->as_name = node->name;
> >      }
> >
> >      return true;
> > @@ -859,8 +853,9 @@ parse_port_group(struct expr_context *ctx, struct
> expr_constant_set *cs,
> >          *allocated_values = n_values;
> >      }
> >      for (size_t i = 0; i < port_group->n_values; i++) {
> > -        cs->values[cs->n_values++].string =
> > -            xstrdup(port_group->values[i].string);
> > +        struct expr_constant *c = &cs->values[cs->n_values++];
> > +        c->string = xstrdup(port_group->values[i].string);
> > +        c->as_name = NULL;
> >      }
> >
> >      return true;
> > @@ -875,11 +870,6 @@ parse_constant(struct expr_context *ctx, struct
> expr_constant_set *cs,
> >                                  sizeof *cs->values);
> >      }
> >
> > -    /* Combining other values to the constant set that is tracking an
> > -     * address set, so untrack it. */
> > -    free(cs->as_name);
> > -    cs->as_name = NULL;
> > -
> >      if (ctx->lexer->token.type == LEX_T_TEMPLATE) {
> >          lexer_error(ctx->lexer, "Unexpanded template.");
> >          return false;
> > @@ -887,7 +877,9 @@ parse_constant(struct expr_context *ctx, struct
> expr_constant_set *cs,
> >          if (!assign_constant_set_type(ctx, cs, EXPR_C_STRING)) {
> >              return false;
> >          }
> > -        cs->values[cs->n_values++].string =
> xstrdup(ctx->lexer->token.s);
> > +        struct expr_constant *c = &cs->values[cs->n_values++];
> > +        c->string = xstrdup(ctx->lexer->token.s);
> > +        c->as_name = NULL;
> >          lexer_get(ctx->lexer);
> >          return true;
> >      } else if (ctx->lexer->token.type == LEX_T_INTEGER ||
> > @@ -896,13 +888,14 @@ parse_constant(struct expr_context *ctx, struct
> expr_constant_set *cs,
> >              return false;
> >          }
> >
> > -        union expr_constant *c = &cs->values[cs->n_values++];
> > +        struct expr_constant *c = &cs->values[cs->n_values++];
> >          c->value = ctx->lexer->token.value;
> >          c->format = ctx->lexer->token.format;
> >          c->masked = ctx->lexer->token.type == LEX_T_MASKED_INTEGER;
> >          if (c->masked) {
> >              c->mask = ctx->lexer->token.mask;
> >          }
> > +        c->as_name = NULL;
> >          lexer_get(ctx->lexer);
> >          return true;
> >      } else if (ctx->lexer->token.type == LEX_T_MACRO) {
> > @@ -961,7 +954,7 @@ parse_constant_set(struct expr_context *ctx, struct
> expr_constant_set *cs)
> >   * indeterminate. */
> >  bool
> >  expr_constant_parse(struct lexer *lexer, const struct expr_field *f,
> > -                    union expr_constant *c)
> > +                    struct expr_constant *c)
> >  {
> >      if (lexer->error) {
> >          return false;
> > @@ -987,7 +980,7 @@ expr_constant_parse(struct lexer *lexer, const
> struct expr_field *f,
> >  /* Appends to 's' a re-parseable representation of constant 'c' with
> the given
> >   * 'type'. */
> >  void
> > -expr_constant_format(const union expr_constant *c,
> > +expr_constant_format(const struct expr_constant *c,
> >                       enum expr_constant_type type, struct ds *s)
> >  {
> >      if (type == EXPR_C_STRING) {
> > @@ -1010,7 +1003,7 @@ expr_constant_format(const union expr_constant *c,
> >   *
> >   * Does not free(c). */
> >  void
> > -expr_constant_destroy(const union expr_constant *c,
> > +expr_constant_destroy(const struct expr_constant *c,
> >                        enum expr_constant_type type)
> >  {
> >      if (c && type == EXPR_C_STRING) {
> > @@ -1043,7 +1036,7 @@ expr_constant_set_format(const struct
> expr_constant_set *cs, struct ds *s)
> >          ds_put_char(s, '{');
> >      }
> >
> > -    for (const union expr_constant *c = cs->values;
> > +    for (const struct expr_constant *c = cs->values;
> >           c < &cs->values[cs->n_values]; c++) {
> >          if (c != cs->values) {
> >              ds_put_cstr(s, ", ");
> > @@ -1067,15 +1060,14 @@ expr_constant_set_destroy(struct
> expr_constant_set *cs)
> >              }
> >          }
> >          free(cs->values);
> > -        free(cs->as_name);
> >      }
> >  }
> >
> >  static int
> >  compare_expr_constant_integer_cb(const void *a_, const void *b_)
> >  {
> > -    const union expr_constant *a = a_;
> > -    const union expr_constant *b = b_;
> > +    const struct expr_constant *a = a_;
> > +    const struct expr_constant *b = b_;
> >
> >      int d = memcmp(&a->value, &b->value, sizeof a->value);
> >      if (d) {
> > @@ -1114,7 +1106,7 @@ expr_constant_set_create_integers(const char
> *const *values, size_t n_values)
> >              VLOG_WARN("Invalid constant set entry: '%s', token type:
> %d",
> >                        values[i], lex.token.type);
> >          } else {
> > -            union expr_constant *c = &cs->values[cs->n_values++];
> > +            struct expr_constant *c = &cs->values[cs->n_values++];
> >              c->value = lex.token.value;
> >              c->format = lex.token.format;
> >              c->masked = lex.token.type == LEX_T_MASKED_INTEGER;
> > @@ -1135,7 +1127,7 @@ expr_constant_set_create_integers(const char
> *const *values, size_t n_values)
> >
> >  static void
> >  expr_constant_set_add_value(struct expr_constant_set **p_cs,
> > -                            union expr_constant *c, size_t *allocated)
> > +                            struct expr_constant *c, size_t *allocated)
> >  {
> >      struct expr_constant_set *cs = *p_cs;
> >      if (!cs) {
> > @@ -1246,7 +1238,7 @@ expr_const_sets_add_strings(struct shash
> *const_sets, const char *name,
> >                          values[i], name);
> >              continue;
> >          }
> > -        union expr_constant *c = &cs->values[cs->n_values++];
> > +        struct expr_constant *c = &cs->values[cs->n_values++];
> >          c->string = xstrdup(values[i]);
> >      }
> >
> > @@ -1359,7 +1351,7 @@ expr_parse_primary(struct expr_context *ctx, bool
> *atomic)
> >
> >              *atomic = true;
> >
> > -            union expr_constant *cst = xzalloc(sizeof *cst);
> > +            struct expr_constant *cst = xzalloc(sizeof *cst);
> >              cst->format = LEX_F_HEXADECIMAL;
> >              cst->masked = false;
> >
> > @@ -1367,7 +1359,6 @@ expr_parse_primary(struct expr_context *ctx, bool
> *atomic)
> >              c.values = cst;
> >              c.n_values = 1;
> >              c.in_curlies = false;
> > -            c.as_name = NULL;
> >              return make_cmp(ctx, &f, EXPR_R_NE, &c);
> >          } else if (parse_relop(ctx, &r) && parse_constant_set(ctx, &c))
> {
> >              return make_cmp(ctx, &f, r, &c);
> > @@ -1834,7 +1825,6 @@ expr_symtab_destroy(struct shash *symtab)
> >  static struct expr *
> >  expr_clone_cmp(struct expr *expr)
> >  {
> > -    ovs_assert(!expr->as_name);
> >      struct expr *new = xmemdup(expr, sizeof *expr);
> >      if (!new->cmp.symbol->width) {
> >          new->cmp.string = xstrdup(new->cmp.string);
> > @@ -1858,7 +1848,6 @@ expr_clone_andor(struct expr *expr)
> >  static struct expr *
> >  expr_clone_condition(struct expr *expr)
> >  {
> > -    ovs_assert(!expr->as_name);
> >      struct expr *new = xmemdup(expr, sizeof *expr);
> >      new->cond.string = xstrdup(new->cond.string);
> >      return new;
> > @@ -1894,8 +1883,6 @@ expr_destroy(struct expr *expr)
> >          return;
> >      }
> >
> > -    free(expr->as_name);
> > -
> >      struct expr *sub;
> >
> >      switch (expr->type) {
> > @@ -2567,7 +2554,7 @@ crush_or_supersets(struct expr *expr, const struct
> expr_symbol *symbol)
> >       * mask sizes. */
> >      size_t n = ovs_list_size(&expr->andor);
> >      struct expr **subs = xmalloc(n * sizeof *subs);
> > -    bool modified = false;
> > +    bool has_addr_set = false;
> >      /* Linked list over the 'subs' array to quickly skip deleted
> elements,
> >       * i.e. the index of the next potentially non-NULL element. */
> >      size_t *next = xmalloc(n * sizeof *next);
> > @@ -2575,6 +2562,9 @@ crush_or_supersets(struct expr *expr, const struct
> expr_symbol *symbol)
> >      size_t i = 0, j, max_n_bits = 0;
> >      struct expr *sub;
> >      LIST_FOR_EACH (sub, node, &expr->andor) {
> > +        if (sub->as_name) {
> > +            has_addr_set = true;
> > +        }
> >          if (symbol->width) {
> >              const unsigned long *sub_mask;
> >
> > @@ -2596,14 +2586,14 @@ crush_or_supersets(struct expr *expr, const
> struct expr_symbol *symbol)
> >              next[last] = i;
> >              last = i;
> >          } else {
> > +            /* Remove address set reference from the duplicate. */
> > +            subs[last]->as_name = NULL;
> >              expr_destroy(subs[i]);
> >              subs[i] = NULL;
> > -            modified = true;
> >          }
> >      }
> >
> > -    if (!symbol->width || symbol->level != EXPR_L_ORDINAL
> > -        || (!modified && expr->as_name)) {
> > +    if (!symbol->width || symbol->level != EXPR_L_ORDINAL ||
> has_addr_set) {
> >          /* Not a fully maskable field or this expression is tracking an
> >           * address set.  Don't try to optimize to preserve address set
> I-P. */
> >          goto done;
> > @@ -2658,10 +2648,11 @@ crush_or_supersets(struct expr *expr, const
> struct expr_symbol *symbol)
> >              if (expr_bitmap_intersect_check(a_value, a_mask, b_value,
> b_mask,
> >                                              bit_width)
> >                  && bitmap_is_superset(b_mask, a_mask, bit_width)) {
> > -                /* 'a' is the same expression with a smaller mask. */
> > +                /* 'a' is the same expression with a smaller mask.
> > +                 * Remove address set reference from the duplicate. */
> > +                a->as_name = NULL;
> >                  expr_destroy(subs[j]);
> >                  subs[j] = NULL;
> > -                modified = true;
> >
> >                  /* Shorten the path for the next round. */
> >                  if (last) {
> > @@ -2685,12 +2676,6 @@ done:
> >          }
> >      }
> >
> > -    if (modified) {
> > -        /* Members modified, so untrack address set. */
> > -        free(expr->as_name);
> > -        expr->as_name = NULL;
> > -    }
> > -
> >      free(next);
> >      free(subs);
> >      return expr;
> > @@ -3271,10 +3256,10 @@ add_disjunction(const struct expr *or,
> >      LIST_FOR_EACH (sub, node, &or->andor) {
> >          struct expr_match *match = expr_match_new(m, clause, n_clauses,
> >                                                    conj_id);
> > -        if (or->as_name) {
> > +        if (sub->as_name) {
> >              ovs_assert(sub->type == EXPR_T_CMP);
> >              ovs_assert(sub->cmp.symbol->width);
> > -            match->as_name = xstrdup(or->as_name);
> > +            match->as_name = xstrdup(sub->as_name);
> >              match->as_ip = sub->cmp.value.ipv6;
> >              match->as_mask = sub->cmp.mask.ipv6;
> >          }
> > diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
> > index be198e00d..27cec2aec 100644
> > --- a/tests/ovn-controller.at
> > +++ b/tests/ovn-controller.at
> > @@ -1625,7 +1625,9 @@
> priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.8
> actions=lo
> >  done
> >
> >  reprocess_count_new=$(read_counter consider_logical_flow)
> > -AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0],
> [10
> > +# First 2 reprocess are due to change from 1 IP in AS to 2.
> > +# Last 5 is due to overlap in IP addresses between as1 and as2.
> > +AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0],
> [7
> >  ])
> >
> >  # Remove the IPs from as1 and as2, 1 IP each time.
> > @@ -1648,9 +1650,9 @@ for i in $(seq 10); do
> >  done
> >
> >  reprocess_count_new=$(read_counter consider_logical_flow)
> > -# In this case the as1 and as2 are merged to a single OR expr, so we
> lose track of
> > -# address set information - can't handle deletion incrementally.
> > -AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0],
> [10
> > +# First 5 are due to overlap in IP addresses between as1 and as2.
> > +# Last 2 are due to change from 2 IP in AS to 1.
> > +AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0],
> [7
> >  ])
> >
> >  OVN_CLEANUP([hv1])
> > @@ -1817,7 +1819,7 @@
> priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.5
> actions=co
> >
>  priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.10.10.10
> actions=conjunction,2/2)
> >  ])
> >  reprocess_count_new=$(read_counter consider_logical_flow)
> > -AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0],
> [1
> > +AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0],
> [0
> >  ])
> >
> >  # Delete 2 IPs
> > @@ -1837,7 +1839,7 @@
> priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.3
> actions=co
> >
>  priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.10.10.10
> actions=conjunction,2/2)
> >  ])
> >  reprocess_count_new=$(read_counter consider_logical_flow)
> > -AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0],
> [1
> > +AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0],
> [0
> >  ])
> >
> >  OVN_CLEANUP([hv1])
> > @@ -2906,3 +2908,68 @@ OVN_CLEANUP([hv1],[hv2])
> >
> >  AT_CLEANUP
> >  ])
> > +
> > +AT_SETUP([ovn-controller - AS I-P and recompute consistency])
> > +AT_KEYWORDS([as-i-p])
> > +
> > +ovn_start
> > +
> > +net_add n1
> > +sim_add hv1
> > +as hv1
> > +check ovs-vsctl add-br br-phys
> > +ovn_attach n1 br-phys 192.168.0.1
> > +check ovs-vsctl -- add-port br-int hv1-vif1 -- \
> > +    set interface hv1-vif1 external-ids:iface-id=ls1-lp1
> > +
> > +check ovn-nbctl ls-add ls1
> > +
> > +check ovn-nbctl lsp-add ls1 ls1-lp1 \
> > +-- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01"
> > +
> > +wait_for_ports_up
> > +ovn-appctl -t ovn-controller vlog/set file:dbg
> > +
> > +# Get the OF table numbers
> > +acl_eval=$(ovn-debug lflow-stage-to-oftable ls_out_acl_eval)
> > +acl_action=$(ovn-debug lflow-stage-to-oftable ls_out_acl_action)
> > +
> > +dp_key=$(printf "%x" $(fetch_column datapath tunnel_key
> external_ids:name=ls1))
> > +port_key=$(printf "%x" $(fetch_column port_binding tunnel_key
> logical_port=ls1-lp1))
> > +
> > +ovn-nbctl create address_set name=as1
> > +check ovn-nbctl acl-add ls1 to-lport 100 'outport == "ls1-lp1" &&
> ip4.src == $as1' drop
> > +check ovn-nbctl add address_set as1 addresses 10.0.0.0/24
> > +check ovn-nbctl --wait=hv sync
> > +
> > +AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int
> table=$acl_eval,reg15=0x$port_key | grep -v reply | awk '{print $7, $8}' |
> sort], [0], [dnl
> > +priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.0/24
> actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
> > +])
> > +
> > +check ovn-nbctl add address_set as1 addresses 10.0.0.1
> > +check ovn-nbctl add address_set as1 addresses 10.0.0.2
> > +check ovn-nbctl add address_set as1 addresses 10.0.0.3
> > +check ovn-nbctl add address_set as1 addresses 10.0.0.4
> > +check ovn-nbctl --wait=hv sync
> > +
> > +AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int
> table=$acl_eval,reg15=0x$port_key | grep -v reply | awk '{print $7, $8}' |
> sort], [0], [dnl
> > +priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.0/24
> actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
> > +priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.1
> actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
> > +priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.2
> actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
> > +priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.3
> actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
> > +priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.4
> actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
> > +])
> > +
> > +
> > +check ovn-appctl inc-engine/recompute
> > +
> > +AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int
> table=$acl_eval,reg15=0x$port_key | grep -v reply | awk '{print $7, $8}' |
> sort], [0], [dnl
> > +priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.0/24
> actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
> > +priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.1
> actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
> > +priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.2
> actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
> > +priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.3
> actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
> > +priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.4
> actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
> > +])
> > +
> > +OVN_CLEANUP([hv1])
> > +AT_CLEANUP
> > --
> > 2.44.0
> >
>

Thanks,
Ales
diff mbox series

Patch

diff --git a/controller/lflow.c b/controller/lflow.c
index 760ec0b41..06e839cbe 100644
--- a/controller/lflow.c
+++ b/controller/lflow.c
@@ -278,7 +278,7 @@  lflow_handle_changed_flows(struct lflow_ctx_in *l_ctx_in,
 }
 
 static bool
-as_info_from_expr_const(const char *as_name, const union expr_constant *c,
+as_info_from_expr_const(const char *as_name, const struct expr_constant *c,
                         struct addrset_info *as_info)
 {
     as_info->name = as_name;
@@ -647,9 +647,8 @@  as_update_can_be_handled(const char *as_name, struct addr_set_diff *as_diff,
  *        expressions/constants, usually because of disjunctions between
  *        sub-expressions/constants, e.g.:
  *
+ *          ip.src == $as1 && ip.dst == $as2
  *          ip.src == $as1 || ip.dst == $as2
- *          ip.src == {$as1, $as2}
- *          ip.src == {$as1, ip1}
  *
  *        All these could have been split into separate lflows.
  *
@@ -714,7 +713,7 @@  lflow_handle_addr_set_update(const char *as_name,
         if (as_diff->deleted) {
             struct addrset_info as_info;
             for (size_t i = 0; i < as_diff->deleted->n_values; i++) {
-                union expr_constant *c = &as_diff->deleted->values[i];
+                struct expr_constant *c = &as_diff->deleted->values[i];
                 if (!as_info_from_expr_const(as_name, c, &as_info)) {
                     continue;
                 }
diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index ae0864fdd..88cf4de79 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -241,7 +241,7 @@  struct ovnact_next {
 struct ovnact_load {
     struct ovnact ovnact;
     struct expr_field dst;
-    union expr_constant imm;
+    struct expr_constant imm;
 };
 
 /* OVNACT_MOVE, OVNACT_EXCHANGE. */
diff --git a/include/ovn/expr.h b/include/ovn/expr.h
index c48f82398..e54edb5bf 100644
--- a/include/ovn/expr.h
+++ b/include/ovn/expr.h
@@ -368,7 +368,7 @@  bool expr_relop_from_token(enum lex_type type, enum expr_relop *relop);
 struct expr {
     struct ovs_list node;       /* In parent EXPR_T_AND or EXPR_T_OR if any. */
     enum expr_type type;        /* Expression type. */
-    char *as_name;              /* Address set name. Null if it is not an
+    const char *as_name;        /* Address set name. Null if it is not an
                                    address set. */
 
     union {
@@ -505,40 +505,42 @@  enum expr_constant_type {
 };
 
 /* A string or integer constant (one must know which from context). */
-union expr_constant {
-    /* Integer constant.
-     *
-     * The width of a constant isn't always clear, e.g. if you write "1",
-     * there's no way to tell whether you mean for that to be a 1-bit constant
-     * or a 128-bit constant or somewhere in between. */
-    struct {
-        union mf_subvalue value;
-        union mf_subvalue mask; /* Only initialized if 'masked'. */
-        bool masked;
-
-        enum lex_format format; /* From the constant's lex_token. */
-    };
+struct expr_constant {
+    const char *as_name;
 
-    /* Null-terminated string constant. */
-    char *string;
+    union {
+        /* Integer constant.
+         *
+         * The width of a constant isn't always clear, e.g. if you write "1",
+         * there's no way to tell whether you mean for that to be a 1-bit
+         * constant or a 128-bit constant or somewhere in between. */
+        struct {
+            union mf_subvalue value;
+            union mf_subvalue mask; /* Only initialized if 'masked'. */
+            bool masked;
+
+            enum lex_format format; /* From the constant's lex_token. */
+        };
+
+        /* Null-terminated string constant. */
+        char *string;
+    };
 };
 
 bool expr_constant_parse(struct lexer *,
                          const struct expr_field *,
-                         union expr_constant *);
-void expr_constant_format(const union expr_constant *,
+                         struct expr_constant *);
+void expr_constant_format(const struct expr_constant *,
                           enum expr_constant_type, struct ds *);
-void expr_constant_destroy(const union expr_constant *,
+void expr_constant_destroy(const struct expr_constant *,
                            enum expr_constant_type);
 
 /* A collection of "union expr_constant"s of the same type. */
 struct expr_constant_set {
-    union expr_constant *values;  /* Constants. */
+    struct expr_constant *values; /* Constants. */
     size_t n_values;              /* Number of constants. */
     enum expr_constant_type type; /* Type of the constants. */
     bool in_curlies;              /* Whether the constants were in {}. */
-    char *as_name;                /* Name of an address set. It is NULL if not
-                                     an address set. */
 };
 
 bool expr_constant_set_parse(struct lexer *, struct expr_constant_set *);
diff --git a/lib/actions.c b/lib/actions.c
index 94751d059..cff4f9e3c 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -450,7 +450,7 @@  encode_LOAD(const struct ovnact_load *load,
             const struct ovnact_encode_params *ep,
             struct ofpbuf *ofpacts)
 {
-    const union expr_constant *c = &load->imm;
+    const struct expr_constant *c = &load->imm;
     struct mf_subfield dst = expr_resolve_field(&load->dst);
     struct ofpact_set_field *sf = ofpact_put_set_field(ofpacts, dst.field,
                                                        NULL, NULL);
@@ -2077,7 +2077,7 @@  encode_event_empty_lb_backends_opts(struct ofpbuf *ofpacts,
         /* All empty_lb_backends fields are of type 'str' */
         ovs_assert(!strcmp(o->option->type, "str"));
 
-        const union expr_constant *c = o->value.values;
+        const struct expr_constant *c = o->value.values;
         size_t size = strlen(c->string);
         struct controller_event_opt_header hdr =
             (struct controller_event_opt_header) {
@@ -2553,7 +2553,7 @@  validate_empty_lb_backends(struct action_context *ctx,
 {
     for (size_t i = 0; i < n_options; i++) {
         const struct ovnact_gen_option *o = &options[i];
-        const union expr_constant *c = o->value.values;
+        const struct expr_constant *c = o->value.values;
         struct sockaddr_storage ss;
         struct uuid uuid;
 
@@ -2861,7 +2861,7 @@  encode_put_dhcpv4_option(const struct ovnact_gen_option *o,
     uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 2);
     opt_header[0] = o->option->code;
 
-    const union expr_constant *c = o->value.values;
+    const struct expr_constant *c = o->value.values;
     size_t n_values = o->value.n_values;
     if (!strcmp(o->option->type, "bool") ||
         !strcmp(o->option->type, "uint8")) {
@@ -3027,7 +3027,7 @@  encode_put_dhcpv6_option(const struct ovnact_gen_option *o,
                          struct ofpbuf *ofpacts)
 {
     struct dhcpv6_opt_header *opt = ofpbuf_put_uninit(ofpacts, sizeof *opt);
-    const union expr_constant *c = o->value.values;
+    const struct expr_constant *c = o->value.values;
     size_t n_values = o->value.n_values;
     size_t size;
 
@@ -3083,7 +3083,7 @@  encode_PUT_DHCPV4_OPTS(const struct ovnact_put_opts *pdo,
         find_opt(pdo->options, pdo->n_options, DHCP_OPT_BOOTFILE_CODE);
     if (boot_opt) {
         uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 2);
-        const union expr_constant *c = boot_opt->value.values;
+        const struct expr_constant *c = boot_opt->value.values;
         opt_header[0] = boot_opt->option->code;
         opt_header[1] = strlen(c->string);
         ofpbuf_put(ofpacts, c->string, opt_header[1]);
@@ -3093,7 +3093,7 @@  encode_PUT_DHCPV4_OPTS(const struct ovnact_put_opts *pdo,
         find_opt(pdo->options, pdo->n_options, DHCP_OPT_BOOTFILE_ALT_CODE);
     if (boot_alt_opt) {
         uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 2);
-        const union expr_constant *c = boot_alt_opt->value.values;
+        const struct expr_constant *c = boot_alt_opt->value.values;
         opt_header[0] = boot_alt_opt->option->code;
         opt_header[1] = strlen(c->string);
         ofpbuf_put(ofpacts, c->string, opt_header[1]);
@@ -3103,7 +3103,7 @@  encode_PUT_DHCPV4_OPTS(const struct ovnact_put_opts *pdo,
         pdo->options, pdo->n_options, DHCP_OPT_NEXT_SERVER_CODE);
     if (next_server_opt) {
         uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 2);
-        const union expr_constant *c = next_server_opt->value.values;
+        const struct expr_constant *c = next_server_opt->value.values;
         opt_header[0] = next_server_opt->option->code;
         opt_header[1] = sizeof(ovs_be32);
         ofpbuf_put(ofpacts, &c->value.ipv4, sizeof(ovs_be32));
@@ -3307,7 +3307,7 @@  parse_put_nd_ra_opts(struct action_context *ctx, const struct expr_field *dst,
     /* Let's validate the options. */
     for (size_t i = 0; i < po->n_options; i++) {
         const struct ovnact_gen_option *o = &po->options[i];
-        const union expr_constant *c = o->value.values;
+        const struct expr_constant *c = o->value.values;
         if (o->value.n_values > 1) {
             lexer_error(ctx->lexer, "Invalid value for \"%s\" option",
                         o->option->name);
@@ -3490,7 +3490,7 @@  static void
 encode_put_nd_ra_option(const struct ovnact_gen_option *o,
                         struct ofpbuf *ofpacts, ptrdiff_t ra_offset)
 {
-    const union expr_constant *c = o->value.values;
+    const struct expr_constant *c = o->value.values;
 
     switch (o->option->code) {
     case ND_RA_FLAG_ADDR_MODE:
diff --git a/lib/expr.c b/lib/expr.c
index 0cb44e1b6..ecf8a6338 100644
--- a/lib/expr.c
+++ b/lib/expr.c
@@ -179,12 +179,10 @@  expr_combine(enum expr_type type, struct expr *a, struct expr *b)
         } else {
             ovs_list_push_back(&a->andor, &b->node);
         }
-        free(a->as_name);
         a->as_name = NULL;
         return a;
     } else if (b->type == type) {
         ovs_list_push_front(&b->andor, &a->node);
-        free(b->as_name);
         b->as_name = NULL;
         return b;
     } else {
@@ -521,12 +519,13 @@  static bool parse_field(struct expr_context *, struct expr_field *);
 
 static struct expr *
 make_cmp__(const struct expr_field *f, enum expr_relop r,
-             const union expr_constant *c)
+             const struct expr_constant *c)
 {
     struct expr *e = xzalloc(sizeof *e);
     e->type = EXPR_T_CMP;
     e->cmp.symbol = f->symbol;
     e->cmp.relop = r;
+    e->as_name = c->as_name;
     if (f->symbol->width) {
         bitwise_copy(&c->value, sizeof c->value, 0,
                      &e->cmp.value, sizeof e->cmp.value, f->ofs,
@@ -547,7 +546,7 @@  make_cmp__(const struct expr_field *f, enum expr_relop r,
 
 /* Returns the minimum reasonable width for integer constant 'c'. */
 static int
-expr_constant_width(const union expr_constant *c)
+expr_constant_width(const struct expr_constant *c)
 {
     if (c->masked) {
         return mf_subvalue_width(&c->mask);
@@ -674,10 +673,7 @@  make_cmp(struct expr_context *ctx,
         e = expr_combine(r == EXPR_R_EQ ? EXPR_T_OR : EXPR_T_AND,
                          e, make_cmp__(f, r, &cs->values[i]));
     }
-    /* Track address set */
-    if (r == EXPR_R_EQ && e->type == EXPR_T_OR && cs->as_name) {
-        e->as_name = xstrdup(cs->as_name);
-    }
+
 exit:
     expr_constant_set_destroy(cs);
     return e;
@@ -797,11 +793,10 @@  parse_addr_sets(struct expr_context *ctx, struct expr_constant_set *cs,
         }
     }
 
-    struct expr_constant_set *addr_sets
-        = (ctx->addr_sets
-           ? shash_find_data(ctx->addr_sets, ctx->lexer->token.s)
-           : NULL);
-    if (!addr_sets) {
+    struct shash_node *node = ctx->addr_sets
+                              ? shash_find(ctx->addr_sets, ctx->lexer->token.s)
+                              : NULL;
+    if (!node) {
         lexer_syntax_error(ctx->lexer, "expecting address set name");
         return false;
     }
@@ -810,17 +805,16 @@  parse_addr_sets(struct expr_context *ctx, struct expr_constant_set *cs,
         return false;
     }
 
-    if (!cs->n_values) {
-        cs->as_name = xstrdup(ctx->lexer->token.s);
-    }
-
+    struct expr_constant_set *addr_sets = node->data;
     size_t n_values = cs->n_values + addr_sets->n_values;
     if (n_values >= *allocated_values) {
         cs->values = xrealloc(cs->values, n_values * sizeof *cs->values);
         *allocated_values = n_values;
     }
     for (size_t i = 0; i < addr_sets->n_values; i++) {
-        cs->values[cs->n_values++] = addr_sets->values[i];
+        struct expr_constant *c = &cs->values[cs->n_values++];
+        *c = addr_sets->values[i];
+        c->as_name = node->name;
     }
 
     return true;
@@ -859,8 +853,9 @@  parse_port_group(struct expr_context *ctx, struct expr_constant_set *cs,
         *allocated_values = n_values;
     }
     for (size_t i = 0; i < port_group->n_values; i++) {
-        cs->values[cs->n_values++].string =
-            xstrdup(port_group->values[i].string);
+        struct expr_constant *c = &cs->values[cs->n_values++];
+        c->string = xstrdup(port_group->values[i].string);
+        c->as_name = NULL;
     }
 
     return true;
@@ -875,11 +870,6 @@  parse_constant(struct expr_context *ctx, struct expr_constant_set *cs,
                                 sizeof *cs->values);
     }
 
-    /* Combining other values to the constant set that is tracking an
-     * address set, so untrack it. */
-    free(cs->as_name);
-    cs->as_name = NULL;
-
     if (ctx->lexer->token.type == LEX_T_TEMPLATE) {
         lexer_error(ctx->lexer, "Unexpanded template.");
         return false;
@@ -887,7 +877,9 @@  parse_constant(struct expr_context *ctx, struct expr_constant_set *cs,
         if (!assign_constant_set_type(ctx, cs, EXPR_C_STRING)) {
             return false;
         }
-        cs->values[cs->n_values++].string = xstrdup(ctx->lexer->token.s);
+        struct expr_constant *c = &cs->values[cs->n_values++];
+        c->string = xstrdup(ctx->lexer->token.s);
+        c->as_name = NULL;
         lexer_get(ctx->lexer);
         return true;
     } else if (ctx->lexer->token.type == LEX_T_INTEGER ||
@@ -896,13 +888,14 @@  parse_constant(struct expr_context *ctx, struct expr_constant_set *cs,
             return false;
         }
 
-        union expr_constant *c = &cs->values[cs->n_values++];
+        struct expr_constant *c = &cs->values[cs->n_values++];
         c->value = ctx->lexer->token.value;
         c->format = ctx->lexer->token.format;
         c->masked = ctx->lexer->token.type == LEX_T_MASKED_INTEGER;
         if (c->masked) {
             c->mask = ctx->lexer->token.mask;
         }
+        c->as_name = NULL;
         lexer_get(ctx->lexer);
         return true;
     } else if (ctx->lexer->token.type == LEX_T_MACRO) {
@@ -961,7 +954,7 @@  parse_constant_set(struct expr_context *ctx, struct expr_constant_set *cs)
  * indeterminate. */
 bool
 expr_constant_parse(struct lexer *lexer, const struct expr_field *f,
-                    union expr_constant *c)
+                    struct expr_constant *c)
 {
     if (lexer->error) {
         return false;
@@ -987,7 +980,7 @@  expr_constant_parse(struct lexer *lexer, const struct expr_field *f,
 /* Appends to 's' a re-parseable representation of constant 'c' with the given
  * 'type'. */
 void
-expr_constant_format(const union expr_constant *c,
+expr_constant_format(const struct expr_constant *c,
                      enum expr_constant_type type, struct ds *s)
 {
     if (type == EXPR_C_STRING) {
@@ -1010,7 +1003,7 @@  expr_constant_format(const union expr_constant *c,
  *
  * Does not free(c). */
 void
-expr_constant_destroy(const union expr_constant *c,
+expr_constant_destroy(const struct expr_constant *c,
                       enum expr_constant_type type)
 {
     if (c && type == EXPR_C_STRING) {
@@ -1043,7 +1036,7 @@  expr_constant_set_format(const struct expr_constant_set *cs, struct ds *s)
         ds_put_char(s, '{');
     }
 
-    for (const union expr_constant *c = cs->values;
+    for (const struct expr_constant *c = cs->values;
          c < &cs->values[cs->n_values]; c++) {
         if (c != cs->values) {
             ds_put_cstr(s, ", ");
@@ -1067,15 +1060,14 @@  expr_constant_set_destroy(struct expr_constant_set *cs)
             }
         }
         free(cs->values);
-        free(cs->as_name);
     }
 }
 
 static int
 compare_expr_constant_integer_cb(const void *a_, const void *b_)
 {
-    const union expr_constant *a = a_;
-    const union expr_constant *b = b_;
+    const struct expr_constant *a = a_;
+    const struct expr_constant *b = b_;
 
     int d = memcmp(&a->value, &b->value, sizeof a->value);
     if (d) {
@@ -1114,7 +1106,7 @@  expr_constant_set_create_integers(const char *const *values, size_t n_values)
             VLOG_WARN("Invalid constant set entry: '%s', token type: %d",
                       values[i], lex.token.type);
         } else {
-            union expr_constant *c = &cs->values[cs->n_values++];
+            struct expr_constant *c = &cs->values[cs->n_values++];
             c->value = lex.token.value;
             c->format = lex.token.format;
             c->masked = lex.token.type == LEX_T_MASKED_INTEGER;
@@ -1135,7 +1127,7 @@  expr_constant_set_create_integers(const char *const *values, size_t n_values)
 
 static void
 expr_constant_set_add_value(struct expr_constant_set **p_cs,
-                            union expr_constant *c, size_t *allocated)
+                            struct expr_constant *c, size_t *allocated)
 {
     struct expr_constant_set *cs = *p_cs;
     if (!cs) {
@@ -1246,7 +1238,7 @@  expr_const_sets_add_strings(struct shash *const_sets, const char *name,
                         values[i], name);
             continue;
         }
-        union expr_constant *c = &cs->values[cs->n_values++];
+        struct expr_constant *c = &cs->values[cs->n_values++];
         c->string = xstrdup(values[i]);
     }
 
@@ -1359,7 +1351,7 @@  expr_parse_primary(struct expr_context *ctx, bool *atomic)
 
             *atomic = true;
 
-            union expr_constant *cst = xzalloc(sizeof *cst);
+            struct expr_constant *cst = xzalloc(sizeof *cst);
             cst->format = LEX_F_HEXADECIMAL;
             cst->masked = false;
 
@@ -1367,7 +1359,6 @@  expr_parse_primary(struct expr_context *ctx, bool *atomic)
             c.values = cst;
             c.n_values = 1;
             c.in_curlies = false;
-            c.as_name = NULL;
             return make_cmp(ctx, &f, EXPR_R_NE, &c);
         } else if (parse_relop(ctx, &r) && parse_constant_set(ctx, &c)) {
             return make_cmp(ctx, &f, r, &c);
@@ -1834,7 +1825,6 @@  expr_symtab_destroy(struct shash *symtab)
 static struct expr *
 expr_clone_cmp(struct expr *expr)
 {
-    ovs_assert(!expr->as_name);
     struct expr *new = xmemdup(expr, sizeof *expr);
     if (!new->cmp.symbol->width) {
         new->cmp.string = xstrdup(new->cmp.string);
@@ -1858,7 +1848,6 @@  expr_clone_andor(struct expr *expr)
 static struct expr *
 expr_clone_condition(struct expr *expr)
 {
-    ovs_assert(!expr->as_name);
     struct expr *new = xmemdup(expr, sizeof *expr);
     new->cond.string = xstrdup(new->cond.string);
     return new;
@@ -1894,8 +1883,6 @@  expr_destroy(struct expr *expr)
         return;
     }
 
-    free(expr->as_name);
-
     struct expr *sub;
 
     switch (expr->type) {
@@ -2567,7 +2554,7 @@  crush_or_supersets(struct expr *expr, const struct expr_symbol *symbol)
      * mask sizes. */
     size_t n = ovs_list_size(&expr->andor);
     struct expr **subs = xmalloc(n * sizeof *subs);
-    bool modified = false;
+    bool has_addr_set = false;
     /* Linked list over the 'subs' array to quickly skip deleted elements,
      * i.e. the index of the next potentially non-NULL element. */
     size_t *next = xmalloc(n * sizeof *next);
@@ -2575,6 +2562,9 @@  crush_or_supersets(struct expr *expr, const struct expr_symbol *symbol)
     size_t i = 0, j, max_n_bits = 0;
     struct expr *sub;
     LIST_FOR_EACH (sub, node, &expr->andor) {
+        if (sub->as_name) {
+            has_addr_set = true;
+        }
         if (symbol->width) {
             const unsigned long *sub_mask;
 
@@ -2596,14 +2586,14 @@  crush_or_supersets(struct expr *expr, const struct expr_symbol *symbol)
             next[last] = i;
             last = i;
         } else {
+            /* Remove address set reference from the duplicate. */
+            subs[last]->as_name = NULL;
             expr_destroy(subs[i]);
             subs[i] = NULL;
-            modified = true;
         }
     }
 
-    if (!symbol->width || symbol->level != EXPR_L_ORDINAL
-        || (!modified && expr->as_name)) {
+    if (!symbol->width || symbol->level != EXPR_L_ORDINAL || has_addr_set) {
         /* Not a fully maskable field or this expression is tracking an
          * address set.  Don't try to optimize to preserve address set I-P. */
         goto done;
@@ -2658,10 +2648,11 @@  crush_or_supersets(struct expr *expr, const struct expr_symbol *symbol)
             if (expr_bitmap_intersect_check(a_value, a_mask, b_value, b_mask,
                                             bit_width)
                 && bitmap_is_superset(b_mask, a_mask, bit_width)) {
-                /* 'a' is the same expression with a smaller mask. */
+                /* 'a' is the same expression with a smaller mask.
+                 * Remove address set reference from the duplicate. */
+                a->as_name = NULL;
                 expr_destroy(subs[j]);
                 subs[j] = NULL;
-                modified = true;
 
                 /* Shorten the path for the next round. */
                 if (last) {
@@ -2685,12 +2676,6 @@  done:
         }
     }
 
-    if (modified) {
-        /* Members modified, so untrack address set. */
-        free(expr->as_name);
-        expr->as_name = NULL;
-    }
-
     free(next);
     free(subs);
     return expr;
@@ -3271,10 +3256,10 @@  add_disjunction(const struct expr *or,
     LIST_FOR_EACH (sub, node, &or->andor) {
         struct expr_match *match = expr_match_new(m, clause, n_clauses,
                                                   conj_id);
-        if (or->as_name) {
+        if (sub->as_name) {
             ovs_assert(sub->type == EXPR_T_CMP);
             ovs_assert(sub->cmp.symbol->width);
-            match->as_name = xstrdup(or->as_name);
+            match->as_name = xstrdup(sub->as_name);
             match->as_ip = sub->cmp.value.ipv6;
             match->as_mask = sub->cmp.mask.ipv6;
         }
diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
index be198e00d..27cec2aec 100644
--- a/tests/ovn-controller.at
+++ b/tests/ovn-controller.at
@@ -1625,7 +1625,9 @@  priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.8 actions=lo
 done
 
 reprocess_count_new=$(read_counter consider_logical_flow)
-AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0], [10
+# First 2 reprocess are due to change from 1 IP in AS to 2.
+# Last 5 is due to overlap in IP addresses between as1 and as2.
+AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0], [7
 ])
 
 # Remove the IPs from as1 and as2, 1 IP each time.
@@ -1648,9 +1650,9 @@  for i in $(seq 10); do
 done
 
 reprocess_count_new=$(read_counter consider_logical_flow)
-# In this case the as1 and as2 are merged to a single OR expr, so we lose track of
-# address set information - can't handle deletion incrementally.
-AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0], [10
+# First 5 are due to overlap in IP addresses between as1 and as2.
+# Last 2 are due to change from 2 IP in AS to 1.
+AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0], [7
 ])
 
 OVN_CLEANUP([hv1])
@@ -1817,7 +1819,7 @@  priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.5 actions=co
 priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.10.10.10 actions=conjunction,2/2)
 ])
 reprocess_count_new=$(read_counter consider_logical_flow)
-AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0], [1
+AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0], [0
 ])
 
 # Delete 2 IPs
@@ -1837,7 +1839,7 @@  priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.3 actions=co
 priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.10.10.10 actions=conjunction,2/2)
 ])
 reprocess_count_new=$(read_counter consider_logical_flow)
-AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0], [1
+AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0], [0
 ])
 
 OVN_CLEANUP([hv1])
@@ -2906,3 +2908,68 @@  OVN_CLEANUP([hv1],[hv2])
 
 AT_CLEANUP
 ])
+
+AT_SETUP([ovn-controller - AS I-P and recompute consistency])
+AT_KEYWORDS([as-i-p])
+
+ovn_start
+
+net_add n1
+sim_add hv1
+as hv1
+check ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+    set interface hv1-vif1 external-ids:iface-id=ls1-lp1
+
+check ovn-nbctl ls-add ls1
+
+check ovn-nbctl lsp-add ls1 ls1-lp1 \
+-- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01"
+
+wait_for_ports_up
+ovn-appctl -t ovn-controller vlog/set file:dbg
+
+# Get the OF table numbers
+acl_eval=$(ovn-debug lflow-stage-to-oftable ls_out_acl_eval)
+acl_action=$(ovn-debug lflow-stage-to-oftable ls_out_acl_action)
+
+dp_key=$(printf "%x" $(fetch_column datapath tunnel_key external_ids:name=ls1))
+port_key=$(printf "%x" $(fetch_column port_binding tunnel_key logical_port=ls1-lp1))
+
+ovn-nbctl create address_set name=as1
+check ovn-nbctl acl-add ls1 to-lport 100 'outport == "ls1-lp1" && ip4.src == $as1' drop
+check ovn-nbctl add address_set as1 addresses 10.0.0.0/24
+check ovn-nbctl --wait=hv sync
+
+AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=$acl_eval,reg15=0x$port_key | grep -v reply | awk '{print $7, $8}' | sort], [0], [dnl
+priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.0/24 actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
+])
+
+check ovn-nbctl add address_set as1 addresses 10.0.0.1
+check ovn-nbctl add address_set as1 addresses 10.0.0.2
+check ovn-nbctl add address_set as1 addresses 10.0.0.3
+check ovn-nbctl add address_set as1 addresses 10.0.0.4
+check ovn-nbctl --wait=hv sync
+
+AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=$acl_eval,reg15=0x$port_key | grep -v reply | awk '{print $7, $8}' | sort], [0], [dnl
+priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.0/24 actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
+priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.1 actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
+priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.2 actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
+priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.3 actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
+priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.4 actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
+])
+
+
+check ovn-appctl inc-engine/recompute
+
+AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=$acl_eval,reg15=0x$port_key | grep -v reply | awk '{print $7, $8}' | sort], [0], [dnl
+priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.0/24 actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
+priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.1 actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
+priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.2 actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
+priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.3 actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
+priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.4 actions=load:0x1->OXM_OF_PKT_REG4[[49]],resubmit(,$acl_action)
+])
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP