diff mbox series

[ovs-dev,ovn,v2] Split SB Port_Group per datapath.

Message ID 1593444281-19811-1-git-send-email-dceara@redhat.com
State Accepted
Headers show
Series [ovs-dev,ovn,v2] Split SB Port_Group per datapath. | expand

Commit Message

Dumitru Ceara June 29, 2020, 3:24 p.m. UTC
In order to avoid ovn-controller reinstalling all logical flows that
refer a port_group when some ports are added/removed from the port group
we now change the way ovn-northd populates the Southbound DB Port_Group
table.

Instead of copying NB.Port_Group.name to SB.Port_Group.name we now
create one SB.Port_Group record for every datapath that has ports
referenced by the NB.Port_Group.ports field. In order to maintain the
SB.Port_Group.name uniqueness constraint, ovn-northd populates the field
with the value: <SB.Logical_Datapath.tunnel_key>_<NB.Port_Group.name>.

In specific scenarios we see significant improvements in time to
install/remove all logical flows to/from OVS. One such scenario, in the
BZ referenced below has:

$ ovn-nbctl acl-list pg
  from-lport  1001 (inport == @pg && ip) drop
    to-lport  1001 (outport == @pg && ip) drop

Then, incrementally, creates new logical ports on different logical
switches, binds them to OVS interfaces and adds them to the port_group.

Measuring the total time to perform the above steps 500 times (for 500
new ports attached to 100 switches, 5 per switch) on a test setup
we observe an improvement of 50% in time it takes to install all
openflow rules when port_groups are split in the SB database.

Suggested-by: Numan Siddique <numans@ovn.org>
Reported-by: Venkata Anil <anilvenkata@redhat.com>
Reported-at: https://bugzilla.redhat.com/1818128
Signed-off-by: Dumitru Ceara <dceara@redhat.com>

---
v2:
- add tests in ovn-northd.at as suggested by Numan.
---
 TODO.rst              |  8 ++++++
 controller/lflow.c    |  4 ++-
 include/ovn/expr.h    |  4 ++-
 lib/actions.c         |  2 +-
 lib/expr.c            | 48 ++++++++++++++++++++++++-------
 lib/ovn-util.h        |  7 +++++
 northd/ovn-northd.c   | 79 ++++++++++++++++++++++++++++++++++-----------------
 tests/ovn-northd.at   | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++
 tests/test-ovn.c      | 10 +++----
 utilities/ovn-trace.c |  3 +-
 10 files changed, 198 insertions(+), 46 deletions(-)

Comments

Numan Siddique June 30, 2020, 8:42 a.m. UTC | #1
On Mon, Jun 29, 2020 at 8:55 PM Dumitru Ceara <dceara@redhat.com> wrote:

> In order to avoid ovn-controller reinstalling all logical flows that
> refer a port_group when some ports are added/removed from the port group
> we now change the way ovn-northd populates the Southbound DB Port_Group
> table.
>
> Instead of copying NB.Port_Group.name to SB.Port_Group.name we now
> create one SB.Port_Group record for every datapath that has ports
> referenced by the NB.Port_Group.ports field. In order to maintain the
> SB.Port_Group.name uniqueness constraint, ovn-northd populates the field
> with the value: <SB.Logical_Datapath.tunnel_key>_<NB.Port_Group.name>.
>
> In specific scenarios we see significant improvements in time to
> install/remove all logical flows to/from OVS. One such scenario, in the
> BZ referenced below has:
>
> $ ovn-nbctl acl-list pg
>   from-lport  1001 (inport == @pg && ip) drop
>     to-lport  1001 (outport == @pg && ip) drop
>
> Then, incrementally, creates new logical ports on different logical
> switches, binds them to OVS interfaces and adds them to the port_group.
>
> Measuring the total time to perform the above steps 500 times (for 500
> new ports attached to 100 switches, 5 per switch) on a test setup
> we observe an improvement of 50% in time it takes to install all
> openflow rules when port_groups are split in the SB database.
>
> Suggested-by: Numan Siddique <numans@ovn.org>
> Reported-by: Venkata Anil <anilvenkata@redhat.com>
> Reported-at: https://bugzilla.redhat.com/1818128
> Signed-off-by: Dumitru Ceara <dceara@redhat.com>
>
> ---
> v2:
> - add tests in ovn-northd.at as suggested by Numan.
>

Thanks for v2 and adding tests.

Acked-by: Numan Siddique <numans@ovn.org>

Numan


> ---
>  TODO.rst              |  8 ++++++
>  controller/lflow.c    |  4 ++-
>  include/ovn/expr.h    |  4 ++-
>  lib/actions.c         |  2 +-
>  lib/expr.c            | 48 ++++++++++++++++++++++++-------
>  lib/ovn-util.h        |  7 +++++
>  northd/ovn-northd.c   | 79
> ++++++++++++++++++++++++++++++++++-----------------
>  tests/ovn-northd.at   | 79
> +++++++++++++++++++++++++++++++++++++++++++++++++++
>  tests/test-ovn.c      | 10 +++----
>  utilities/ovn-trace.c |  3 +-
>  10 files changed, 198 insertions(+), 46 deletions(-)
>
> diff --git a/TODO.rst b/TODO.rst
> index 6df6b84..4b2fc2d 100644
> --- a/TODO.rst
> +++ b/TODO.rst
> @@ -140,3 +140,11 @@ OVN To-do List
>  * ovn-controller: Stop copying the local OVS configuration into the
>    Chassis external_ids column (same for the "is-remote" configuration from
>    ovn-ic) a few releases after the 20.06 version (21.06 maybe ?).
> +
> +* ovn-controller: Remove backwards compatibility for Southbound DB
> Port_Group
> +  names in expr.c a few releases after the 20.09 version. Right now
> +  ovn-controller maintains backwards compatibility when connecting to a
> +  SB database that doesn't store Port_Group.name as
> +  <Logical_Datapath.tunnel_key_NB-Port_Group.name>. This causes an
> additional
> +  hashtable lookup in parse_port_group() which can be avoided when we are
> sure
> +  that the Southbound DB uses the new format.
> diff --git a/controller/lflow.c b/controller/lflow.c
> index c850a0d..5454361 100644
> --- a/controller/lflow.c
> +++ b/controller/lflow.c
> @@ -566,7 +566,9 @@ consider_logical_flow(const struct sbrec_logical_flow
> *lflow,
>      struct sset port_groups_ref = SSET_INITIALIZER(&port_groups_ref);
>      expr = expr_parse_string(lflow->match, &symtab, l_ctx_in->addr_sets,
>                               l_ctx_in->port_groups,
> -                             &addr_sets_ref, &port_groups_ref, &error);
> +                             &addr_sets_ref, &port_groups_ref,
> +                             lflow->logical_datapath->tunnel_key,
> +                             &error);
>      const char *addr_set_name;
>      SSET_FOR_EACH (addr_set_name, &addr_sets_ref) {
>          lflow_resource_add(l_ctx_out->lfrr, REF_TYPE_ADDRSET,
> addr_set_name,
> diff --git a/include/ovn/expr.h b/include/ovn/expr.h
> index 21bf51c..9838251 100644
> --- a/include/ovn/expr.h
> +++ b/include/ovn/expr.h
> @@ -391,12 +391,14 @@ struct expr *expr_parse(struct lexer *, const struct
> shash *symtab,
>                          const struct shash *addr_sets,
>                          const struct shash *port_groups,
>                          struct sset *addr_sets_ref,
> -                        struct sset *port_groups_ref);
> +                        struct sset *port_groups_ref,
> +                        int64_t dp_id);
>  struct expr *expr_parse_string(const char *, const struct shash *symtab,
>                                 const struct shash *addr_sets,
>                                 const struct shash *port_groups,
>                                 struct sset *addr_sets_ref,
>                                 struct sset *port_groups_ref,
> +                               int64_t dp_id,
>                                 char **errorp);
>
>  struct expr *expr_clone(struct expr *);
> diff --git a/lib/actions.c b/lib/actions.c
> index ee7c825..fc6e191 100644
> --- a/lib/actions.c
> +++ b/lib/actions.c
> @@ -242,7 +242,7 @@ add_prerequisite(struct action_context *ctx, const
> char *prerequisite)
>      char *error;
>
>      expr = expr_parse_string(prerequisite, ctx->pp->symtab, NULL, NULL,
> -                             NULL, NULL, &error);
> +                             NULL, NULL, 0, &error);
>      ovs_assert(!error);
>      ctx->prereqs = expr_combine(EXPR_T_AND, ctx->prereqs, expr);
>  }
> diff --git a/lib/expr.c b/lib/expr.c
> index 078d178..497b2ac 100644
> --- a/lib/expr.c
> +++ b/lib/expr.c
> @@ -29,6 +29,7 @@
>  #include "simap.h"
>  #include "sset.h"
>  #include "util.h"
> +#include "ovn-util.h"
>
>  VLOG_DEFINE_THIS_MODULE(expr);
>
> @@ -482,6 +483,10 @@ struct expr_context {
>      const struct shash *port_groups; /* Port group table. */
>      struct sset *addr_sets_ref;      /* The set of address set
> referenced. */
>      struct sset *port_groups_ref;    /* The set of port groups
> referenced. */
> +    int64_t dp_id;                   /* The tunnel_key of the datapath for
> +                                        which we're parsing the current
> +                                        expression. */
> +
>      bool not;                    /* True inside odd number of NOT
> operators. */
>      unsigned int paren_depth;    /* Depth of nested parentheses. */
>  };
> @@ -783,14 +788,32 @@ static bool
>  parse_port_group(struct expr_context *ctx, struct expr_constant_set *cs,
>                   size_t *allocated_values)
>  {
> +    struct ds sb_name = DS_EMPTY_INITIALIZER;
> +
> +    get_sb_port_group_name(ctx->lexer->token.s, ctx->dp_id, &sb_name);
>      if (ctx->port_groups_ref) {
> -        sset_add(ctx->port_groups_ref, ctx->lexer->token.s);
> +        sset_add(ctx->port_groups_ref, ds_cstr(&sb_name));
> +    }
> +
> +    struct expr_constant_set *port_group = NULL;
> +
> +    if (ctx->port_groups) {
> +        port_group = shash_find_data(ctx->port_groups, ds_cstr(&sb_name));
> +        if (!port_group) {
> +            /* For backwards compatibility (e.g., ovn-controller was
> +             * upgraded but ovn-northd not yet), perform an additional
> +             * lookup because the NB Port_Group.name might have been
> +             * stored as is in the SB Port_Group.name field.
> +             */
> +            port_group = shash_find_data(ctx->port_groups,
> +                                         ctx->lexer->token.s);
> +            if (ctx->port_groups_ref) {
> +                sset_add(ctx->port_groups_ref, ctx->lexer->token.s);
> +            }
> +        }
>      }
> +    ds_destroy(&sb_name);
>
> -    struct expr_constant_set *port_group
> -        = (ctx->port_groups
> -           ? shash_find_data(ctx->port_groups, ctx->lexer->token.s)
> -           : NULL);
>      if (!port_group) {
>          lexer_syntax_error(ctx->lexer, "expecting port group name");
>          return false;
> @@ -1302,14 +1325,16 @@ expr_parse(struct lexer *lexer, const struct shash
> *symtab,
>             const struct shash *addr_sets,
>             const struct shash *port_groups,
>             struct sset *addr_sets_ref,
> -           struct sset *port_groups_ref)
> +           struct sset *port_groups_ref,
> +           int64_t dp_id)
>  {
>      struct expr_context ctx = { .lexer = lexer,
>                                  .symtab = symtab,
>                                  .addr_sets = addr_sets,
>                                  .port_groups = port_groups,
>                                  .addr_sets_ref = addr_sets_ref,
> -                                .port_groups_ref = port_groups_ref };
> +                                .port_groups_ref = port_groups_ref,
> +                                .dp_id = dp_id };
>      return lexer->error ? NULL : expr_parse__(&ctx);
>  }
>
> @@ -1325,6 +1350,7 @@ expr_parse_string(const char *s, const struct shash
> *symtab,
>                    const struct shash *port_groups,
>                    struct sset *addr_sets_ref,
>                    struct sset *port_groups_ref,
> +                  int64_t dp_id,
>                    char **errorp)
>  {
>      struct lexer lexer;
> @@ -1332,7 +1358,7 @@ expr_parse_string(const char *s, const struct shash
> *symtab,
>      lexer_init(&lexer, s);
>      lexer_get(&lexer);
>      struct expr *expr = expr_parse(&lexer, symtab, addr_sets, port_groups,
> -                                   addr_sets_ref, port_groups_ref);
> +                                   addr_sets_ref, port_groups_ref, dp_id);
>      lexer_force_end(&lexer);
>      *errorp = lexer_steal_error(&lexer);
>      if (*errorp) {
> @@ -1558,7 +1584,7 @@ expr_get_level(const struct expr *expr)
>  static enum expr_level
>  expr_parse_level(const char *s, const struct shash *symtab, char **errorp)
>  {
> -    struct expr *expr = expr_parse_string(s, symtab, NULL, NULL, NULL,
> NULL,
> +    struct expr *expr = expr_parse_string(s, symtab, NULL, NULL, NULL,
> NULL, 0,
>                                            errorp);
>      enum expr_level level = expr ? expr_get_level(expr) : EXPR_L_NOMINAL;
>      expr_destroy(expr);
> @@ -1730,7 +1756,7 @@ parse_and_annotate(const char *s, const struct shash
> *symtab,
>      char *error;
>      struct expr *expr;
>
> -    expr = expr_parse_string(s, symtab, NULL, NULL, NULL, NULL, &error);
> +    expr = expr_parse_string(s, symtab, NULL, NULL, NULL, NULL, 0,
> &error);
>      if (expr) {
>          expr = expr_annotate_(expr, symtab, nesting, &error);
>      }
> @@ -3456,7 +3482,7 @@ expr_parse_microflow(const char *s, const struct
> shash *symtab,
>      lexer_get(&lexer);
>
>      struct expr *e = expr_parse(&lexer, symtab, addr_sets, port_groups,
> -                                NULL, NULL);
> +                                NULL, NULL, 0);
>      lexer_force_end(&lexer);
>
>      if (e) {
> diff --git a/lib/ovn-util.h b/lib/ovn-util.h
> index eba2948..4e08ee0 100644
> --- a/lib/ovn-util.h
> +++ b/lib/ovn-util.h
> @@ -122,6 +122,13 @@ get_unique_lport_key(uint64_t dp_tunnel_key, uint64_t
> lport_tunnel_key,
>               lport_tunnel_key);
>  }
>
> +static inline void
> +get_sb_port_group_name(const char *nb_pg_name, int64_t dp_tunnel_key,
> +                       struct ds *sb_pg_name)
> +{
> +    ds_put_format(sb_pg_name, "%"PRId64"_%s", dp_tunnel_key, nb_pg_name);
> +}
> +
>  char *ovn_chassis_redirect_name(const char *port_name);
>  void ovn_set_pidfile(const char *name);
>
> diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> index f49e4f2..e1dc491 100644
> --- a/northd/ovn-northd.c
> +++ b/northd/ovn-northd.c
> @@ -4471,7 +4471,11 @@ build_dhcpv6_action(struct ovn_port *op, struct
> in6_addr *offer_ip,
>  struct ovn_port_group_ls {
>      struct hmap_node key_node;  /* Index on 'key'. */
>      struct uuid key;            /* nb_ls->header_.uuid. */
> -    const struct nbrec_logical_switch *nb_ls;
> +    struct ovn_datapath *od;
> +
> +    struct ovn_port **ports; /* Ports in 'od' referrenced by the PG. */
> +    size_t n_ports;
> +    size_t n_allocated_ports;
>  };
>
>  struct ovn_port_group {
> @@ -4481,14 +4485,14 @@ struct ovn_port_group {
>      struct hmap nb_lswitches;   /* NB lswitches related to the port group
> */
>  };
>
> -static void
> -ovn_port_group_ls_add(struct ovn_port_group *pg,
> -                      const struct nbrec_logical_switch *nb_ls)
> +static struct ovn_port_group_ls *
> +ovn_port_group_ls_add(struct ovn_port_group *pg, struct ovn_datapath *od)
>  {
>      struct ovn_port_group_ls *pg_ls = xzalloc(sizeof *pg_ls);
> -    pg_ls->key = nb_ls->header_.uuid;
> -    pg_ls->nb_ls = nb_ls;
> +    pg_ls->key = od->nbs->header_.uuid;
> +    pg_ls->od = od;
>      hmap_insert(&pg->nb_lswitches, &pg_ls->key_node,
> uuid_hash(&pg_ls->key));
> +    return pg_ls;
>  }
>
>  static struct ovn_port_group_ls *
> @@ -4505,6 +4509,18 @@ ovn_port_group_ls_find(struct ovn_port_group *pg,
> const struct uuid *ls_uuid)
>      return NULL;
>  }
>
> +static void
> +ovn_port_group_ls_add_port(struct ovn_port_group_ls *pg_ls,
> +                           struct ovn_port *op)
> +{
> +    if (pg_ls->n_ports == pg_ls->n_allocated_ports) {
> +        pg_ls->ports = x2nrealloc(pg_ls->ports,
> +                                  &pg_ls->n_allocated_ports,
> +                                  sizeof *pg_ls->ports);
> +    }
> +    pg_ls->ports[pg_ls->n_ports++] = op;
> +}
> +
>  struct ovn_ls_port_group {
>      struct hmap_node key_node;  /* Index on 'key'. */
>      struct uuid key;            /* nb_pg->header_.uuid. */
> @@ -5252,6 +5268,7 @@ ovn_port_group_destroy(struct hmap *pgs, struct
> ovn_port_group *pg)
>          hmap_remove(pgs, &pg->key_node);
>          struct ovn_port_group_ls *ls;
>          HMAP_FOR_EACH_POP (ls, key_node, &pg->nb_lswitches) {
> +            free(ls->ports);
>              free(ls);
>          }
>          hmap_destroy(&pg->nb_lswitches);
> @@ -5289,9 +5306,10 @@ build_port_group_lswitches(struct northd_context
> *ctx, struct hmap *pgs,
>              struct ovn_port_group_ls *pg_ls =
>                  ovn_port_group_ls_find(pg, &op->od->nbs->header_.uuid);
>              if (!pg_ls) {
> -                ovn_port_group_ls_add(pg, op->od->nbs);
> +                pg_ls = ovn_port_group_ls_add(pg, op->od);
>                  ovn_ls_port_group_add(&op->od->nb_pgs, nb_pg);
>              }
> +            ovn_port_group_ls_add_port(pg_ls, op);
>          }
>      }
>  }
> @@ -10509,7 +10527,7 @@ sync_address_sets(struct northd_context *ctx)
>   * contains lport uuids, while in OVN_Southbound we store the lport names.
>   */
>  static void
> -sync_port_groups(struct northd_context *ctx)
> +sync_port_groups(struct northd_context *ctx, struct hmap *pgs)
>  {
>      struct shash sb_port_groups = SHASH_INITIALIZER(&sb_port_groups);
>
> @@ -10518,26 +10536,35 @@ sync_port_groups(struct northd_context *ctx)
>          shash_add(&sb_port_groups, sb_port_group->name, sb_port_group);
>      }
>
> -    const struct nbrec_port_group *nb_port_group;
> -    NBREC_PORT_GROUP_FOR_EACH (nb_port_group, ctx->ovnnb_idl) {
> -        sb_port_group = shash_find_and_delete(&sb_port_groups,
> -                                               nb_port_group->name);
> -        if (!sb_port_group) {
> -            sb_port_group = sbrec_port_group_insert(ctx->ovnsb_txn);
> -            sbrec_port_group_set_name(sb_port_group, nb_port_group->name);
> -        }
> +    struct ds sb_name = DS_EMPTY_INITIALIZER;
>
> -        const char **nb_port_names = xcalloc(nb_port_group->n_ports,
> -                                             sizeof *nb_port_names);
> -        int i;
> -        for (i = 0; i < nb_port_group->n_ports; i++) {
> -            nb_port_names[i] = nb_port_group->ports[i]->name;
> +    struct ovn_port_group *pg;
> +    HMAP_FOR_EACH (pg, key_node, pgs) {
> +
> +        struct ovn_port_group_ls *pg_ls;
> +        HMAP_FOR_EACH (pg_ls, key_node, &pg->nb_lswitches) {
> +            ds_clear(&sb_name);
> +            get_sb_port_group_name(pg->nb_pg->name,
> pg_ls->od->sb->tunnel_key,
> +                                   &sb_name);
> +            sb_port_group = shash_find_and_delete(&sb_port_groups,
> +                                                  ds_cstr(&sb_name));
> +            if (!sb_port_group) {
> +                sb_port_group = sbrec_port_group_insert(ctx->ovnsb_txn);
> +                sbrec_port_group_set_name(sb_port_group,
> ds_cstr(&sb_name));
> +            }
> +
> +            const char **nb_port_names = xcalloc(pg_ls->n_ports,
> +                                                 sizeof *nb_port_names);
> +            for (size_t i = 0; i < pg_ls->n_ports; i++) {
> +                nb_port_names[i] = pg_ls->ports[i]->nbsp->name;
> +            }
> +            sbrec_port_group_set_ports(sb_port_group,
> +                                       nb_port_names,
> +                                       pg_ls->n_ports);
> +            free(nb_port_names);
>          }
> -        sbrec_port_group_set_ports(sb_port_group,
> -                                   nb_port_names,
> -                                   nb_port_group->n_ports);
> -        free(nb_port_names);
>      }
> +    ds_destroy(&sb_name);
>
>      struct shash_node *node, *next;
>      SHASH_FOR_EACH_SAFE (node, next, &sb_port_groups) {
> @@ -11140,7 +11167,7 @@ ovnnb_db_run(struct northd_context *ctx,
>      ovn_update_ipv6_prefix(ports);
>
>      sync_address_sets(ctx);
> -    sync_port_groups(ctx);
> +    sync_port_groups(ctx, &port_groups);
>      sync_meters(ctx);
>      sync_dns_entries(ctx, datapaths);
>      destroy_ovn_lbs(&lbs);
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index 80bda0a..d7a940f 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -1472,3 +1472,82 @@ AT_CHECK([ovn-nbctl --wait=sb sync], [0])
>  AT_CHECK([test 0 = $(ovn-sbctl list Ha_Chassis_Group | wc -l)])
>
>  AT_CLEANUP
> +
> +AT_SETUP([ovn -- check NB/SB Port_Group translation (lsp add/del)])
> +ovn_start
> +
> +ovn-nbctl ls-add ls1
> +ovn-nbctl ls-add ls2
> +ovn-nbctl lsp-add ls1 lsp1
> +ovn-nbctl lsp-add ls2 lsp2
> +ovn-nbct --wait=sb sync
> +ls1_key=$(ovn-sbctl --columns tunnel_key --bare list Datapath_Binding ls1)
> +ls2_key=$(ovn-sbctl --columns tunnel_key --bare list Datapath_Binding ls2)
> +
> +# Add an empty port group. This should generate no entry in the SB.
> +ovn-nbctl --wait=sb pg-add pg_test
> +AT_CHECK([test 0 = $(ovn-sbctl --columns _uuid list Port_Group | grep
> uuid -c)])
> +
> +# Add lsp1 to the port group. This should generate an entry in the SB only
> +# for ls1.
> +ovn-nbctl --wait=sb pg-set-ports pg_test lsp1
> +AT_CHECK([test 1 = $(ovn-sbctl --columns _uuid list Port_Group | grep
> uuid -c)])
> +AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group
> name=${ls1_key}_pg_test], [0], [dnl
> +lsp1
> +])
> +
> +# Add lsp2 to the port group. This should generate a new entry in the SB,
> for
> +# ls2.
> +ovn-nbctl --wait=sb pg-set-ports pg_test lsp1 lsp2
> +AT_CHECK([test 2 = $(ovn-sbctl --columns _uuid list Port_Group | grep
> uuid -c)])
> +AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group
> name=${ls1_key}_pg_test], [0], [dnl
> +lsp1
> +])
> +AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group
> name=${ls2_key}_pg_test], [0], [dnl
> +lsp2
> +])
> +
> +# Remove lsp1 from the port group. The SB Port_Group for ls1 should be
> +# removed.
> +ovn-nbctl --wait=sb pg-set-ports pg_test lsp2
> +AT_CHECK([test 1 = $(ovn-sbctl --columns _uuid list Port_Group | grep
> uuid -c)])
> +AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group
> name=${ls2_key}_pg_test], [0], [dnl
> +lsp2
> +])
> +
> +# Remove lsp2 from the port group. All SB Port_Groups should be purged.
> +ovn-nbctl --wait=sb clear Port_Group pg_test ports
> +AT_CHECK([test 0 = $(ovn-sbctl --columns _uuid list Port_Group | grep
> uuid -c)])
> +
> +AT_CLEANUP
> +
> +AT_SETUP([ovn -- check NB/SB Port_Group translation (ls del)])
> +ovn_start
> +
> +ovn-nbctl ls-add ls1
> +ovn-nbctl ls-add ls2
> +ovn-nbctl lsp-add ls1 lsp1
> +ovn-nbctl lsp-add ls2 lsp2
> +ovn-nbct --wait=sb sync
> +ls1_key=$(ovn-sbctl --columns tunnel_key --bare list Datapath_Binding ls1)
> +ls2_key=$(ovn-sbctl --columns tunnel_key --bare list Datapath_Binding ls2)
> +
> +# Add lsp1 & lsp2 to a port group. This should generate two entries in the
> +# SB (one per logical switch).
> +ovn-nbctl --wait=sb pg-add pg_test lsp1 lsp2
> +AT_CHECK([test 2 = $(ovn-sbctl --columns _uuid list Port_Group | grep
> uuid -c)])
> +AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group
> name=${ls1_key}_pg_test], [0], [dnl
> +lsp1
> +])
> +AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group
> name=${ls2_key}_pg_test], [0], [dnl
> +lsp2
> +])
> +
> +# Delete logical switch ls1. This should remove the associated SB
> Port_Group.
> +ovn-nbctl --wait=sb ls-del ls1
> +AT_CHECK([test 1 = $(ovn-sbctl --columns _uuid list Port_Group | grep
> uuid -c)])
> +AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group
> name=${ls2_key}_pg_test], [0], [dnl
> +lsp2
> +])
> +
> +AT_CLEANUP
> diff --git a/tests/test-ovn.c b/tests/test-ovn.c
> index b43f67f..c3bfd20 100644
> --- a/tests/test-ovn.c
> +++ b/tests/test-ovn.c
> @@ -238,8 +238,8 @@ create_port_groups(struct shash *port_groups)
>      };
>      static const char *const pg2[] = { NULL };
>
> -    expr_const_sets_add(port_groups, "pg1", pg1, 3, false);
> -    expr_const_sets_add(port_groups, "pg_empty", pg2, 0, false);
> +    expr_const_sets_add(port_groups, "0_pg1", pg1, 3, false);
> +    expr_const_sets_add(port_groups, "0_pg_empty", pg2, 0, false);
>  }
>
>  static bool
> @@ -305,7 +305,7 @@ test_parse_expr__(int steps)
>          char *error;
>
>          expr = expr_parse_string(ds_cstr(&input), &symtab, &addr_sets,
> -                                 &port_groups, NULL, NULL, &error);
> +                                 &port_groups, NULL, NULL, 0, &error);
>          if (!error && steps > 0) {
>              expr = expr_annotate(expr, &symtab, &error);
>          }
> @@ -431,7 +431,7 @@ test_evaluate_expr(struct ovs_cmdl_context *ctx)
>          struct expr *expr;
>
>          expr = expr_parse_string(ds_cstr(&input), &symtab, NULL, NULL,
> -                                 NULL, NULL, &error);
> +                                 NULL, NULL, 0, &error);
>          if (!error) {
>              expr = expr_annotate(expr, &symtab, &error);
>          }
> @@ -906,7 +906,7 @@ test_tree_shape_exhaustively(struct expr *expr, struct
> shash *symtab,
>
>              char *error;
>              modified = expr_parse_string(ds_cstr(&s), symtab, NULL,
> -                                         NULL, NULL, NULL, &error);
> +                                         NULL, NULL, NULL, 0, &error);
>              if (error) {
>                  fprintf(stderr, "%s fails to parse (%s)\n",
>                          ds_cstr(&s), error);
> diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
> index d7251e7..2666c10 100644
> --- a/utilities/ovn-trace.c
> +++ b/utilities/ovn-trace.c
> @@ -889,7 +889,8 @@ read_flows(void)
>          char *error;
>          struct expr *match;
>          match = expr_parse_string(sblf->match, &symtab, &address_sets,
> -                                  &port_groups, NULL, NULL, &error);
> +                                  &port_groups, NULL, NULL,
> dp->tunnel_key,
> +                                  &error);
>          if (error) {
>              VLOG_WARN("%s: parsing expression failed (%s)",
>                        sblf->match, error);
> --
> 1.8.3.1
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
>
Mark Michelson June 30, 2020, 12:08 p.m. UTC | #2
On 6/30/20 4:42 AM, Numan Siddique wrote:
> On Mon, Jun 29, 2020 at 8:55 PM Dumitru Ceara <dceara@redhat.com> wrote:
> 
>> In order to avoid ovn-controller reinstalling all logical flows that
>> refer a port_group when some ports are added/removed from the port group
>> we now change the way ovn-northd populates the Southbound DB Port_Group
>> table.
>>
>> Instead of copying NB.Port_Group.name to SB.Port_Group.name we now
>> create one SB.Port_Group record for every datapath that has ports
>> referenced by the NB.Port_Group.ports field. In order to maintain the
>> SB.Port_Group.name uniqueness constraint, ovn-northd populates the field
>> with the value: <SB.Logical_Datapath.tunnel_key>_<NB.Port_Group.name>.
>>
>> In specific scenarios we see significant improvements in time to
>> install/remove all logical flows to/from OVS. One such scenario, in the
>> BZ referenced below has:
>>
>> $ ovn-nbctl acl-list pg
>>    from-lport  1001 (inport == @pg && ip) drop
>>      to-lport  1001 (outport == @pg && ip) drop
>>
>> Then, incrementally, creates new logical ports on different logical
>> switches, binds them to OVS interfaces and adds them to the port_group.
>>
>> Measuring the total time to perform the above steps 500 times (for 500
>> new ports attached to 100 switches, 5 per switch) on a test setup
>> we observe an improvement of 50% in time it takes to install all
>> openflow rules when port_groups are split in the SB database.
>>
>> Suggested-by: Numan Siddique <numans@ovn.org>
>> Reported-by: Venkata Anil <anilvenkata@redhat.com>
>> Reported-at: https://bugzilla.redhat.com/1818128
>> Signed-off-by: Dumitru Ceara <dceara@redhat.com>
>>
>> ---
>> v2:
>> - add tests in ovn-northd.at as suggested by Numan.
>>
> 
> Thanks for v2 and adding tests.
> 
> Acked-by: Numan Siddique <numans@ovn.org>
> 
> Numan

I pushed this to master and branch-20.06

> 
> 
>> ---
>>   TODO.rst              |  8 ++++++
>>   controller/lflow.c    |  4 ++-
>>   include/ovn/expr.h    |  4 ++-
>>   lib/actions.c         |  2 +-
>>   lib/expr.c            | 48 ++++++++++++++++++++++++-------
>>   lib/ovn-util.h        |  7 +++++
>>   northd/ovn-northd.c   | 79
>> ++++++++++++++++++++++++++++++++++-----------------
>>   tests/ovn-northd.at   | 79
>> +++++++++++++++++++++++++++++++++++++++++++++++++++
>>   tests/test-ovn.c      | 10 +++----
>>   utilities/ovn-trace.c |  3 +-
>>   10 files changed, 198 insertions(+), 46 deletions(-)
>>
>> diff --git a/TODO.rst b/TODO.rst
>> index 6df6b84..4b2fc2d 100644
>> --- a/TODO.rst
>> +++ b/TODO.rst
>> @@ -140,3 +140,11 @@ OVN To-do List
>>   * ovn-controller: Stop copying the local OVS configuration into the
>>     Chassis external_ids column (same for the "is-remote" configuration from
>>     ovn-ic) a few releases after the 20.06 version (21.06 maybe ?).
>> +
>> +* ovn-controller: Remove backwards compatibility for Southbound DB
>> Port_Group
>> +  names in expr.c a few releases after the 20.09 version. Right now
>> +  ovn-controller maintains backwards compatibility when connecting to a
>> +  SB database that doesn't store Port_Group.name as
>> +  <Logical_Datapath.tunnel_key_NB-Port_Group.name>. This causes an
>> additional
>> +  hashtable lookup in parse_port_group() which can be avoided when we are
>> sure
>> +  that the Southbound DB uses the new format.
>> diff --git a/controller/lflow.c b/controller/lflow.c
>> index c850a0d..5454361 100644
>> --- a/controller/lflow.c
>> +++ b/controller/lflow.c
>> @@ -566,7 +566,9 @@ consider_logical_flow(const struct sbrec_logical_flow
>> *lflow,
>>       struct sset port_groups_ref = SSET_INITIALIZER(&port_groups_ref);
>>       expr = expr_parse_string(lflow->match, &symtab, l_ctx_in->addr_sets,
>>                                l_ctx_in->port_groups,
>> -                             &addr_sets_ref, &port_groups_ref, &error);
>> +                             &addr_sets_ref, &port_groups_ref,
>> +                             lflow->logical_datapath->tunnel_key,
>> +                             &error);
>>       const char *addr_set_name;
>>       SSET_FOR_EACH (addr_set_name, &addr_sets_ref) {
>>           lflow_resource_add(l_ctx_out->lfrr, REF_TYPE_ADDRSET,
>> addr_set_name,
>> diff --git a/include/ovn/expr.h b/include/ovn/expr.h
>> index 21bf51c..9838251 100644
>> --- a/include/ovn/expr.h
>> +++ b/include/ovn/expr.h
>> @@ -391,12 +391,14 @@ struct expr *expr_parse(struct lexer *, const struct
>> shash *symtab,
>>                           const struct shash *addr_sets,
>>                           const struct shash *port_groups,
>>                           struct sset *addr_sets_ref,
>> -                        struct sset *port_groups_ref);
>> +                        struct sset *port_groups_ref,
>> +                        int64_t dp_id);
>>   struct expr *expr_parse_string(const char *, const struct shash *symtab,
>>                                  const struct shash *addr_sets,
>>                                  const struct shash *port_groups,
>>                                  struct sset *addr_sets_ref,
>>                                  struct sset *port_groups_ref,
>> +                               int64_t dp_id,
>>                                  char **errorp);
>>
>>   struct expr *expr_clone(struct expr *);
>> diff --git a/lib/actions.c b/lib/actions.c
>> index ee7c825..fc6e191 100644
>> --- a/lib/actions.c
>> +++ b/lib/actions.c
>> @@ -242,7 +242,7 @@ add_prerequisite(struct action_context *ctx, const
>> char *prerequisite)
>>       char *error;
>>
>>       expr = expr_parse_string(prerequisite, ctx->pp->symtab, NULL, NULL,
>> -                             NULL, NULL, &error);
>> +                             NULL, NULL, 0, &error);
>>       ovs_assert(!error);
>>       ctx->prereqs = expr_combine(EXPR_T_AND, ctx->prereqs, expr);
>>   }
>> diff --git a/lib/expr.c b/lib/expr.c
>> index 078d178..497b2ac 100644
>> --- a/lib/expr.c
>> +++ b/lib/expr.c
>> @@ -29,6 +29,7 @@
>>   #include "simap.h"
>>   #include "sset.h"
>>   #include "util.h"
>> +#include "ovn-util.h"
>>
>>   VLOG_DEFINE_THIS_MODULE(expr);
>>
>> @@ -482,6 +483,10 @@ struct expr_context {
>>       const struct shash *port_groups; /* Port group table. */
>>       struct sset *addr_sets_ref;      /* The set of address set
>> referenced. */
>>       struct sset *port_groups_ref;    /* The set of port groups
>> referenced. */
>> +    int64_t dp_id;                   /* The tunnel_key of the datapath for
>> +                                        which we're parsing the current
>> +                                        expression. */
>> +
>>       bool not;                    /* True inside odd number of NOT
>> operators. */
>>       unsigned int paren_depth;    /* Depth of nested parentheses. */
>>   };
>> @@ -783,14 +788,32 @@ static bool
>>   parse_port_group(struct expr_context *ctx, struct expr_constant_set *cs,
>>                    size_t *allocated_values)
>>   {
>> +    struct ds sb_name = DS_EMPTY_INITIALIZER;
>> +
>> +    get_sb_port_group_name(ctx->lexer->token.s, ctx->dp_id, &sb_name);
>>       if (ctx->port_groups_ref) {
>> -        sset_add(ctx->port_groups_ref, ctx->lexer->token.s);
>> +        sset_add(ctx->port_groups_ref, ds_cstr(&sb_name));
>> +    }
>> +
>> +    struct expr_constant_set *port_group = NULL;
>> +
>> +    if (ctx->port_groups) {
>> +        port_group = shash_find_data(ctx->port_groups, ds_cstr(&sb_name));
>> +        if (!port_group) {
>> +            /* For backwards compatibility (e.g., ovn-controller was
>> +             * upgraded but ovn-northd not yet), perform an additional
>> +             * lookup because the NB Port_Group.name might have been
>> +             * stored as is in the SB Port_Group.name field.
>> +             */
>> +            port_group = shash_find_data(ctx->port_groups,
>> +                                         ctx->lexer->token.s);
>> +            if (ctx->port_groups_ref) {
>> +                sset_add(ctx->port_groups_ref, ctx->lexer->token.s);
>> +            }
>> +        }
>>       }
>> +    ds_destroy(&sb_name);
>>
>> -    struct expr_constant_set *port_group
>> -        = (ctx->port_groups
>> -           ? shash_find_data(ctx->port_groups, ctx->lexer->token.s)
>> -           : NULL);
>>       if (!port_group) {
>>           lexer_syntax_error(ctx->lexer, "expecting port group name");
>>           return false;
>> @@ -1302,14 +1325,16 @@ expr_parse(struct lexer *lexer, const struct shash
>> *symtab,
>>              const struct shash *addr_sets,
>>              const struct shash *port_groups,
>>              struct sset *addr_sets_ref,
>> -           struct sset *port_groups_ref)
>> +           struct sset *port_groups_ref,
>> +           int64_t dp_id)
>>   {
>>       struct expr_context ctx = { .lexer = lexer,
>>                                   .symtab = symtab,
>>                                   .addr_sets = addr_sets,
>>                                   .port_groups = port_groups,
>>                                   .addr_sets_ref = addr_sets_ref,
>> -                                .port_groups_ref = port_groups_ref };
>> +                                .port_groups_ref = port_groups_ref,
>> +                                .dp_id = dp_id };
>>       return lexer->error ? NULL : expr_parse__(&ctx);
>>   }
>>
>> @@ -1325,6 +1350,7 @@ expr_parse_string(const char *s, const struct shash
>> *symtab,
>>                     const struct shash *port_groups,
>>                     struct sset *addr_sets_ref,
>>                     struct sset *port_groups_ref,
>> +                  int64_t dp_id,
>>                     char **errorp)
>>   {
>>       struct lexer lexer;
>> @@ -1332,7 +1358,7 @@ expr_parse_string(const char *s, const struct shash
>> *symtab,
>>       lexer_init(&lexer, s);
>>       lexer_get(&lexer);
>>       struct expr *expr = expr_parse(&lexer, symtab, addr_sets, port_groups,
>> -                                   addr_sets_ref, port_groups_ref);
>> +                                   addr_sets_ref, port_groups_ref, dp_id);
>>       lexer_force_end(&lexer);
>>       *errorp = lexer_steal_error(&lexer);
>>       if (*errorp) {
>> @@ -1558,7 +1584,7 @@ expr_get_level(const struct expr *expr)
>>   static enum expr_level
>>   expr_parse_level(const char *s, const struct shash *symtab, char **errorp)
>>   {
>> -    struct expr *expr = expr_parse_string(s, symtab, NULL, NULL, NULL,
>> NULL,
>> +    struct expr *expr = expr_parse_string(s, symtab, NULL, NULL, NULL,
>> NULL, 0,
>>                                             errorp);
>>       enum expr_level level = expr ? expr_get_level(expr) : EXPR_L_NOMINAL;
>>       expr_destroy(expr);
>> @@ -1730,7 +1756,7 @@ parse_and_annotate(const char *s, const struct shash
>> *symtab,
>>       char *error;
>>       struct expr *expr;
>>
>> -    expr = expr_parse_string(s, symtab, NULL, NULL, NULL, NULL, &error);
>> +    expr = expr_parse_string(s, symtab, NULL, NULL, NULL, NULL, 0,
>> &error);
>>       if (expr) {
>>           expr = expr_annotate_(expr, symtab, nesting, &error);
>>       }
>> @@ -3456,7 +3482,7 @@ expr_parse_microflow(const char *s, const struct
>> shash *symtab,
>>       lexer_get(&lexer);
>>
>>       struct expr *e = expr_parse(&lexer, symtab, addr_sets, port_groups,
>> -                                NULL, NULL);
>> +                                NULL, NULL, 0);
>>       lexer_force_end(&lexer);
>>
>>       if (e) {
>> diff --git a/lib/ovn-util.h b/lib/ovn-util.h
>> index eba2948..4e08ee0 100644
>> --- a/lib/ovn-util.h
>> +++ b/lib/ovn-util.h
>> @@ -122,6 +122,13 @@ get_unique_lport_key(uint64_t dp_tunnel_key, uint64_t
>> lport_tunnel_key,
>>                lport_tunnel_key);
>>   }
>>
>> +static inline void
>> +get_sb_port_group_name(const char *nb_pg_name, int64_t dp_tunnel_key,
>> +                       struct ds *sb_pg_name)
>> +{
>> +    ds_put_format(sb_pg_name, "%"PRId64"_%s", dp_tunnel_key, nb_pg_name);
>> +}
>> +
>>   char *ovn_chassis_redirect_name(const char *port_name);
>>   void ovn_set_pidfile(const char *name);
>>
>> diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
>> index f49e4f2..e1dc491 100644
>> --- a/northd/ovn-northd.c
>> +++ b/northd/ovn-northd.c
>> @@ -4471,7 +4471,11 @@ build_dhcpv6_action(struct ovn_port *op, struct
>> in6_addr *offer_ip,
>>   struct ovn_port_group_ls {
>>       struct hmap_node key_node;  /* Index on 'key'. */
>>       struct uuid key;            /* nb_ls->header_.uuid. */
>> -    const struct nbrec_logical_switch *nb_ls;
>> +    struct ovn_datapath *od;
>> +
>> +    struct ovn_port **ports; /* Ports in 'od' referrenced by the PG. */
>> +    size_t n_ports;
>> +    size_t n_allocated_ports;
>>   };
>>
>>   struct ovn_port_group {
>> @@ -4481,14 +4485,14 @@ struct ovn_port_group {
>>       struct hmap nb_lswitches;   /* NB lswitches related to the port group
>> */
>>   };
>>
>> -static void
>> -ovn_port_group_ls_add(struct ovn_port_group *pg,
>> -                      const struct nbrec_logical_switch *nb_ls)
>> +static struct ovn_port_group_ls *
>> +ovn_port_group_ls_add(struct ovn_port_group *pg, struct ovn_datapath *od)
>>   {
>>       struct ovn_port_group_ls *pg_ls = xzalloc(sizeof *pg_ls);
>> -    pg_ls->key = nb_ls->header_.uuid;
>> -    pg_ls->nb_ls = nb_ls;
>> +    pg_ls->key = od->nbs->header_.uuid;
>> +    pg_ls->od = od;
>>       hmap_insert(&pg->nb_lswitches, &pg_ls->key_node,
>> uuid_hash(&pg_ls->key));
>> +    return pg_ls;
>>   }
>>
>>   static struct ovn_port_group_ls *
>> @@ -4505,6 +4509,18 @@ ovn_port_group_ls_find(struct ovn_port_group *pg,
>> const struct uuid *ls_uuid)
>>       return NULL;
>>   }
>>
>> +static void
>> +ovn_port_group_ls_add_port(struct ovn_port_group_ls *pg_ls,
>> +                           struct ovn_port *op)
>> +{
>> +    if (pg_ls->n_ports == pg_ls->n_allocated_ports) {
>> +        pg_ls->ports = x2nrealloc(pg_ls->ports,
>> +                                  &pg_ls->n_allocated_ports,
>> +                                  sizeof *pg_ls->ports);
>> +    }
>> +    pg_ls->ports[pg_ls->n_ports++] = op;
>> +}
>> +
>>   struct ovn_ls_port_group {
>>       struct hmap_node key_node;  /* Index on 'key'. */
>>       struct uuid key;            /* nb_pg->header_.uuid. */
>> @@ -5252,6 +5268,7 @@ ovn_port_group_destroy(struct hmap *pgs, struct
>> ovn_port_group *pg)
>>           hmap_remove(pgs, &pg->key_node);
>>           struct ovn_port_group_ls *ls;
>>           HMAP_FOR_EACH_POP (ls, key_node, &pg->nb_lswitches) {
>> +            free(ls->ports);
>>               free(ls);
>>           }
>>           hmap_destroy(&pg->nb_lswitches);
>> @@ -5289,9 +5306,10 @@ build_port_group_lswitches(struct northd_context
>> *ctx, struct hmap *pgs,
>>               struct ovn_port_group_ls *pg_ls =
>>                   ovn_port_group_ls_find(pg, &op->od->nbs->header_.uuid);
>>               if (!pg_ls) {
>> -                ovn_port_group_ls_add(pg, op->od->nbs);
>> +                pg_ls = ovn_port_group_ls_add(pg, op->od);
>>                   ovn_ls_port_group_add(&op->od->nb_pgs, nb_pg);
>>               }
>> +            ovn_port_group_ls_add_port(pg_ls, op);
>>           }
>>       }
>>   }
>> @@ -10509,7 +10527,7 @@ sync_address_sets(struct northd_context *ctx)
>>    * contains lport uuids, while in OVN_Southbound we store the lport names.
>>    */
>>   static void
>> -sync_port_groups(struct northd_context *ctx)
>> +sync_port_groups(struct northd_context *ctx, struct hmap *pgs)
>>   {
>>       struct shash sb_port_groups = SHASH_INITIALIZER(&sb_port_groups);
>>
>> @@ -10518,26 +10536,35 @@ sync_port_groups(struct northd_context *ctx)
>>           shash_add(&sb_port_groups, sb_port_group->name, sb_port_group);
>>       }
>>
>> -    const struct nbrec_port_group *nb_port_group;
>> -    NBREC_PORT_GROUP_FOR_EACH (nb_port_group, ctx->ovnnb_idl) {
>> -        sb_port_group = shash_find_and_delete(&sb_port_groups,
>> -                                               nb_port_group->name);
>> -        if (!sb_port_group) {
>> -            sb_port_group = sbrec_port_group_insert(ctx->ovnsb_txn);
>> -            sbrec_port_group_set_name(sb_port_group, nb_port_group->name);
>> -        }
>> +    struct ds sb_name = DS_EMPTY_INITIALIZER;
>>
>> -        const char **nb_port_names = xcalloc(nb_port_group->n_ports,
>> -                                             sizeof *nb_port_names);
>> -        int i;
>> -        for (i = 0; i < nb_port_group->n_ports; i++) {
>> -            nb_port_names[i] = nb_port_group->ports[i]->name;
>> +    struct ovn_port_group *pg;
>> +    HMAP_FOR_EACH (pg, key_node, pgs) {
>> +
>> +        struct ovn_port_group_ls *pg_ls;
>> +        HMAP_FOR_EACH (pg_ls, key_node, &pg->nb_lswitches) {
>> +            ds_clear(&sb_name);
>> +            get_sb_port_group_name(pg->nb_pg->name,
>> pg_ls->od->sb->tunnel_key,
>> +                                   &sb_name);
>> +            sb_port_group = shash_find_and_delete(&sb_port_groups,
>> +                                                  ds_cstr(&sb_name));
>> +            if (!sb_port_group) {
>> +                sb_port_group = sbrec_port_group_insert(ctx->ovnsb_txn);
>> +                sbrec_port_group_set_name(sb_port_group,
>> ds_cstr(&sb_name));
>> +            }
>> +
>> +            const char **nb_port_names = xcalloc(pg_ls->n_ports,
>> +                                                 sizeof *nb_port_names);
>> +            for (size_t i = 0; i < pg_ls->n_ports; i++) {
>> +                nb_port_names[i] = pg_ls->ports[i]->nbsp->name;
>> +            }
>> +            sbrec_port_group_set_ports(sb_port_group,
>> +                                       nb_port_names,
>> +                                       pg_ls->n_ports);
>> +            free(nb_port_names);
>>           }
>> -        sbrec_port_group_set_ports(sb_port_group,
>> -                                   nb_port_names,
>> -                                   nb_port_group->n_ports);
>> -        free(nb_port_names);
>>       }
>> +    ds_destroy(&sb_name);
>>
>>       struct shash_node *node, *next;
>>       SHASH_FOR_EACH_SAFE (node, next, &sb_port_groups) {
>> @@ -11140,7 +11167,7 @@ ovnnb_db_run(struct northd_context *ctx,
>>       ovn_update_ipv6_prefix(ports);
>>
>>       sync_address_sets(ctx);
>> -    sync_port_groups(ctx);
>> +    sync_port_groups(ctx, &port_groups);
>>       sync_meters(ctx);
>>       sync_dns_entries(ctx, datapaths);
>>       destroy_ovn_lbs(&lbs);
>> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
>> index 80bda0a..d7a940f 100644
>> --- a/tests/ovn-northd.at
>> +++ b/tests/ovn-northd.at
>> @@ -1472,3 +1472,82 @@ AT_CHECK([ovn-nbctl --wait=sb sync], [0])
>>   AT_CHECK([test 0 = $(ovn-sbctl list Ha_Chassis_Group | wc -l)])
>>
>>   AT_CLEANUP
>> +
>> +AT_SETUP([ovn -- check NB/SB Port_Group translation (lsp add/del)])
>> +ovn_start
>> +
>> +ovn-nbctl ls-add ls1
>> +ovn-nbctl ls-add ls2
>> +ovn-nbctl lsp-add ls1 lsp1
>> +ovn-nbctl lsp-add ls2 lsp2
>> +ovn-nbct --wait=sb sync
>> +ls1_key=$(ovn-sbctl --columns tunnel_key --bare list Datapath_Binding ls1)
>> +ls2_key=$(ovn-sbctl --columns tunnel_key --bare list Datapath_Binding ls2)
>> +
>> +# Add an empty port group. This should generate no entry in the SB.
>> +ovn-nbctl --wait=sb pg-add pg_test
>> +AT_CHECK([test 0 = $(ovn-sbctl --columns _uuid list Port_Group | grep
>> uuid -c)])
>> +
>> +# Add lsp1 to the port group. This should generate an entry in the SB only
>> +# for ls1.
>> +ovn-nbctl --wait=sb pg-set-ports pg_test lsp1
>> +AT_CHECK([test 1 = $(ovn-sbctl --columns _uuid list Port_Group | grep
>> uuid -c)])
>> +AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group
>> name=${ls1_key}_pg_test], [0], [dnl
>> +lsp1
>> +])
>> +
>> +# Add lsp2 to the port group. This should generate a new entry in the SB,
>> for
>> +# ls2.
>> +ovn-nbctl --wait=sb pg-set-ports pg_test lsp1 lsp2
>> +AT_CHECK([test 2 = $(ovn-sbctl --columns _uuid list Port_Group | grep
>> uuid -c)])
>> +AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group
>> name=${ls1_key}_pg_test], [0], [dnl
>> +lsp1
>> +])
>> +AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group
>> name=${ls2_key}_pg_test], [0], [dnl
>> +lsp2
>> +])
>> +
>> +# Remove lsp1 from the port group. The SB Port_Group for ls1 should be
>> +# removed.
>> +ovn-nbctl --wait=sb pg-set-ports pg_test lsp2
>> +AT_CHECK([test 1 = $(ovn-sbctl --columns _uuid list Port_Group | grep
>> uuid -c)])
>> +AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group
>> name=${ls2_key}_pg_test], [0], [dnl
>> +lsp2
>> +])
>> +
>> +# Remove lsp2 from the port group. All SB Port_Groups should be purged.
>> +ovn-nbctl --wait=sb clear Port_Group pg_test ports
>> +AT_CHECK([test 0 = $(ovn-sbctl --columns _uuid list Port_Group | grep
>> uuid -c)])
>> +
>> +AT_CLEANUP
>> +
>> +AT_SETUP([ovn -- check NB/SB Port_Group translation (ls del)])
>> +ovn_start
>> +
>> +ovn-nbctl ls-add ls1
>> +ovn-nbctl ls-add ls2
>> +ovn-nbctl lsp-add ls1 lsp1
>> +ovn-nbctl lsp-add ls2 lsp2
>> +ovn-nbct --wait=sb sync
>> +ls1_key=$(ovn-sbctl --columns tunnel_key --bare list Datapath_Binding ls1)
>> +ls2_key=$(ovn-sbctl --columns tunnel_key --bare list Datapath_Binding ls2)
>> +
>> +# Add lsp1 & lsp2 to a port group. This should generate two entries in the
>> +# SB (one per logical switch).
>> +ovn-nbctl --wait=sb pg-add pg_test lsp1 lsp2
>> +AT_CHECK([test 2 = $(ovn-sbctl --columns _uuid list Port_Group | grep
>> uuid -c)])
>> +AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group
>> name=${ls1_key}_pg_test], [0], [dnl
>> +lsp1
>> +])
>> +AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group
>> name=${ls2_key}_pg_test], [0], [dnl
>> +lsp2
>> +])
>> +
>> +# Delete logical switch ls1. This should remove the associated SB
>> Port_Group.
>> +ovn-nbctl --wait=sb ls-del ls1
>> +AT_CHECK([test 1 = $(ovn-sbctl --columns _uuid list Port_Group | grep
>> uuid -c)])
>> +AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group
>> name=${ls2_key}_pg_test], [0], [dnl
>> +lsp2
>> +])
>> +
>> +AT_CLEANUP
>> diff --git a/tests/test-ovn.c b/tests/test-ovn.c
>> index b43f67f..c3bfd20 100644
>> --- a/tests/test-ovn.c
>> +++ b/tests/test-ovn.c
>> @@ -238,8 +238,8 @@ create_port_groups(struct shash *port_groups)
>>       };
>>       static const char *const pg2[] = { NULL };
>>
>> -    expr_const_sets_add(port_groups, "pg1", pg1, 3, false);
>> -    expr_const_sets_add(port_groups, "pg_empty", pg2, 0, false);
>> +    expr_const_sets_add(port_groups, "0_pg1", pg1, 3, false);
>> +    expr_const_sets_add(port_groups, "0_pg_empty", pg2, 0, false);
>>   }
>>
>>   static bool
>> @@ -305,7 +305,7 @@ test_parse_expr__(int steps)
>>           char *error;
>>
>>           expr = expr_parse_string(ds_cstr(&input), &symtab, &addr_sets,
>> -                                 &port_groups, NULL, NULL, &error);
>> +                                 &port_groups, NULL, NULL, 0, &error);
>>           if (!error && steps > 0) {
>>               expr = expr_annotate(expr, &symtab, &error);
>>           }
>> @@ -431,7 +431,7 @@ test_evaluate_expr(struct ovs_cmdl_context *ctx)
>>           struct expr *expr;
>>
>>           expr = expr_parse_string(ds_cstr(&input), &symtab, NULL, NULL,
>> -                                 NULL, NULL, &error);
>> +                                 NULL, NULL, 0, &error);
>>           if (!error) {
>>               expr = expr_annotate(expr, &symtab, &error);
>>           }
>> @@ -906,7 +906,7 @@ test_tree_shape_exhaustively(struct expr *expr, struct
>> shash *symtab,
>>
>>               char *error;
>>               modified = expr_parse_string(ds_cstr(&s), symtab, NULL,
>> -                                         NULL, NULL, NULL, &error);
>> +                                         NULL, NULL, NULL, 0, &error);
>>               if (error) {
>>                   fprintf(stderr, "%s fails to parse (%s)\n",
>>                           ds_cstr(&s), error);
>> diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
>> index d7251e7..2666c10 100644
>> --- a/utilities/ovn-trace.c
>> +++ b/utilities/ovn-trace.c
>> @@ -889,7 +889,8 @@ read_flows(void)
>>           char *error;
>>           struct expr *match;
>>           match = expr_parse_string(sblf->match, &symtab, &address_sets,
>> -                                  &port_groups, NULL, NULL, &error);
>> +                                  &port_groups, NULL, NULL,
>> dp->tunnel_key,
>> +                                  &error);
>>           if (error) {
>>               VLOG_WARN("%s: parsing expression failed (%s)",
>>                         sblf->match, error);
>> --
>> 1.8.3.1
>>
>> _______________________________________________
>> dev mailing list
>> dev@openvswitch.org
>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>>
>>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
Dumitru Ceara June 30, 2020, 12:58 p.m. UTC | #3
On 6/30/20 2:08 PM, Mark Michelson wrote:
> On 6/30/20 4:42 AM, Numan Siddique wrote:
>> On Mon, Jun 29, 2020 at 8:55 PM Dumitru Ceara <dceara@redhat.com> wrote:
>>
>>> In order to avoid ovn-controller reinstalling all logical flows that
>>> refer a port_group when some ports are added/removed from the port group
>>> we now change the way ovn-northd populates the Southbound DB Port_Group
>>> table.
>>>
>>> Instead of copying NB.Port_Group.name to SB.Port_Group.name we now
>>> create one SB.Port_Group record for every datapath that has ports
>>> referenced by the NB.Port_Group.ports field. In order to maintain the
>>> SB.Port_Group.name uniqueness constraint, ovn-northd populates the field
>>> with the value: <SB.Logical_Datapath.tunnel_key>_<NB.Port_Group.name>.
>>>
>>> In specific scenarios we see significant improvements in time to
>>> install/remove all logical flows to/from OVS. One such scenario, in the
>>> BZ referenced below has:
>>>
>>> $ ovn-nbctl acl-list pg
>>>    from-lport  1001 (inport == @pg && ip) drop
>>>      to-lport  1001 (outport == @pg && ip) drop
>>>
>>> Then, incrementally, creates new logical ports on different logical
>>> switches, binds them to OVS interfaces and adds them to the port_group.
>>>
>>> Measuring the total time to perform the above steps 500 times (for 500
>>> new ports attached to 100 switches, 5 per switch) on a test setup
>>> we observe an improvement of 50% in time it takes to install all
>>> openflow rules when port_groups are split in the SB database.
>>>
>>> Suggested-by: Numan Siddique <numans@ovn.org>
>>> Reported-by: Venkata Anil <anilvenkata@redhat.com>
>>> Reported-at: https://bugzilla.redhat.com/1818128
>>> Signed-off-by: Dumitru Ceara <dceara@redhat.com>
>>>
>>> ---
>>> v2:
>>> - add tests in ovn-northd.at as suggested by Numan.
>>>
>>
>> Thanks for v2 and adding tests.
>>
>> Acked-by: Numan Siddique <numans@ovn.org>
>>
>> Numan
> 
> I pushed this to master and branch-20.06
> 

Thanks, Numan and Mark, for the reviews.

Regards,
Dumitru
diff mbox series

Patch

diff --git a/TODO.rst b/TODO.rst
index 6df6b84..4b2fc2d 100644
--- a/TODO.rst
+++ b/TODO.rst
@@ -140,3 +140,11 @@  OVN To-do List
 * ovn-controller: Stop copying the local OVS configuration into the
   Chassis external_ids column (same for the "is-remote" configuration from
   ovn-ic) a few releases after the 20.06 version (21.06 maybe ?).
+
+* ovn-controller: Remove backwards compatibility for Southbound DB Port_Group
+  names in expr.c a few releases after the 20.09 version. Right now
+  ovn-controller maintains backwards compatibility when connecting to a
+  SB database that doesn't store Port_Group.name as
+  <Logical_Datapath.tunnel_key_NB-Port_Group.name>. This causes an additional
+  hashtable lookup in parse_port_group() which can be avoided when we are sure
+  that the Southbound DB uses the new format.
diff --git a/controller/lflow.c b/controller/lflow.c
index c850a0d..5454361 100644
--- a/controller/lflow.c
+++ b/controller/lflow.c
@@ -566,7 +566,9 @@  consider_logical_flow(const struct sbrec_logical_flow *lflow,
     struct sset port_groups_ref = SSET_INITIALIZER(&port_groups_ref);
     expr = expr_parse_string(lflow->match, &symtab, l_ctx_in->addr_sets,
                              l_ctx_in->port_groups,
-                             &addr_sets_ref, &port_groups_ref, &error);
+                             &addr_sets_ref, &port_groups_ref,
+                             lflow->logical_datapath->tunnel_key,
+                             &error);
     const char *addr_set_name;
     SSET_FOR_EACH (addr_set_name, &addr_sets_ref) {
         lflow_resource_add(l_ctx_out->lfrr, REF_TYPE_ADDRSET, addr_set_name,
diff --git a/include/ovn/expr.h b/include/ovn/expr.h
index 21bf51c..9838251 100644
--- a/include/ovn/expr.h
+++ b/include/ovn/expr.h
@@ -391,12 +391,14 @@  struct expr *expr_parse(struct lexer *, const struct shash *symtab,
                         const struct shash *addr_sets,
                         const struct shash *port_groups,
                         struct sset *addr_sets_ref,
-                        struct sset *port_groups_ref);
+                        struct sset *port_groups_ref,
+                        int64_t dp_id);
 struct expr *expr_parse_string(const char *, const struct shash *symtab,
                                const struct shash *addr_sets,
                                const struct shash *port_groups,
                                struct sset *addr_sets_ref,
                                struct sset *port_groups_ref,
+                               int64_t dp_id,
                                char **errorp);
 
 struct expr *expr_clone(struct expr *);
diff --git a/lib/actions.c b/lib/actions.c
index ee7c825..fc6e191 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -242,7 +242,7 @@  add_prerequisite(struct action_context *ctx, const char *prerequisite)
     char *error;
 
     expr = expr_parse_string(prerequisite, ctx->pp->symtab, NULL, NULL,
-                             NULL, NULL, &error);
+                             NULL, NULL, 0, &error);
     ovs_assert(!error);
     ctx->prereqs = expr_combine(EXPR_T_AND, ctx->prereqs, expr);
 }
diff --git a/lib/expr.c b/lib/expr.c
index 078d178..497b2ac 100644
--- a/lib/expr.c
+++ b/lib/expr.c
@@ -29,6 +29,7 @@ 
 #include "simap.h"
 #include "sset.h"
 #include "util.h"
+#include "ovn-util.h"
 
 VLOG_DEFINE_THIS_MODULE(expr);
 
@@ -482,6 +483,10 @@  struct expr_context {
     const struct shash *port_groups; /* Port group table. */
     struct sset *addr_sets_ref;      /* The set of address set referenced. */
     struct sset *port_groups_ref;    /* The set of port groups referenced. */
+    int64_t dp_id;                   /* The tunnel_key of the datapath for
+                                        which we're parsing the current
+                                        expression. */
+
     bool not;                    /* True inside odd number of NOT operators. */
     unsigned int paren_depth;    /* Depth of nested parentheses. */
 };
@@ -783,14 +788,32 @@  static bool
 parse_port_group(struct expr_context *ctx, struct expr_constant_set *cs,
                  size_t *allocated_values)
 {
+    struct ds sb_name = DS_EMPTY_INITIALIZER;
+
+    get_sb_port_group_name(ctx->lexer->token.s, ctx->dp_id, &sb_name);
     if (ctx->port_groups_ref) {
-        sset_add(ctx->port_groups_ref, ctx->lexer->token.s);
+        sset_add(ctx->port_groups_ref, ds_cstr(&sb_name));
+    }
+
+    struct expr_constant_set *port_group = NULL;
+
+    if (ctx->port_groups) {
+        port_group = shash_find_data(ctx->port_groups, ds_cstr(&sb_name));
+        if (!port_group) {
+            /* For backwards compatibility (e.g., ovn-controller was
+             * upgraded but ovn-northd not yet), perform an additional
+             * lookup because the NB Port_Group.name might have been
+             * stored as is in the SB Port_Group.name field.
+             */
+            port_group = shash_find_data(ctx->port_groups,
+                                         ctx->lexer->token.s);
+            if (ctx->port_groups_ref) {
+                sset_add(ctx->port_groups_ref, ctx->lexer->token.s);
+            }
+        }
     }
+    ds_destroy(&sb_name);
 
-    struct expr_constant_set *port_group
-        = (ctx->port_groups
-           ? shash_find_data(ctx->port_groups, ctx->lexer->token.s)
-           : NULL);
     if (!port_group) {
         lexer_syntax_error(ctx->lexer, "expecting port group name");
         return false;
@@ -1302,14 +1325,16 @@  expr_parse(struct lexer *lexer, const struct shash *symtab,
            const struct shash *addr_sets,
            const struct shash *port_groups,
            struct sset *addr_sets_ref,
-           struct sset *port_groups_ref)
+           struct sset *port_groups_ref,
+           int64_t dp_id)
 {
     struct expr_context ctx = { .lexer = lexer,
                                 .symtab = symtab,
                                 .addr_sets = addr_sets,
                                 .port_groups = port_groups,
                                 .addr_sets_ref = addr_sets_ref,
-                                .port_groups_ref = port_groups_ref };
+                                .port_groups_ref = port_groups_ref,
+                                .dp_id = dp_id };
     return lexer->error ? NULL : expr_parse__(&ctx);
 }
 
@@ -1325,6 +1350,7 @@  expr_parse_string(const char *s, const struct shash *symtab,
                   const struct shash *port_groups,
                   struct sset *addr_sets_ref,
                   struct sset *port_groups_ref,
+                  int64_t dp_id,
                   char **errorp)
 {
     struct lexer lexer;
@@ -1332,7 +1358,7 @@  expr_parse_string(const char *s, const struct shash *symtab,
     lexer_init(&lexer, s);
     lexer_get(&lexer);
     struct expr *expr = expr_parse(&lexer, symtab, addr_sets, port_groups,
-                                   addr_sets_ref, port_groups_ref);
+                                   addr_sets_ref, port_groups_ref, dp_id);
     lexer_force_end(&lexer);
     *errorp = lexer_steal_error(&lexer);
     if (*errorp) {
@@ -1558,7 +1584,7 @@  expr_get_level(const struct expr *expr)
 static enum expr_level
 expr_parse_level(const char *s, const struct shash *symtab, char **errorp)
 {
-    struct expr *expr = expr_parse_string(s, symtab, NULL, NULL, NULL, NULL,
+    struct expr *expr = expr_parse_string(s, symtab, NULL, NULL, NULL, NULL, 0,
                                           errorp);
     enum expr_level level = expr ? expr_get_level(expr) : EXPR_L_NOMINAL;
     expr_destroy(expr);
@@ -1730,7 +1756,7 @@  parse_and_annotate(const char *s, const struct shash *symtab,
     char *error;
     struct expr *expr;
 
-    expr = expr_parse_string(s, symtab, NULL, NULL, NULL, NULL, &error);
+    expr = expr_parse_string(s, symtab, NULL, NULL, NULL, NULL, 0, &error);
     if (expr) {
         expr = expr_annotate_(expr, symtab, nesting, &error);
     }
@@ -3456,7 +3482,7 @@  expr_parse_microflow(const char *s, const struct shash *symtab,
     lexer_get(&lexer);
 
     struct expr *e = expr_parse(&lexer, symtab, addr_sets, port_groups,
-                                NULL, NULL);
+                                NULL, NULL, 0);
     lexer_force_end(&lexer);
 
     if (e) {
diff --git a/lib/ovn-util.h b/lib/ovn-util.h
index eba2948..4e08ee0 100644
--- a/lib/ovn-util.h
+++ b/lib/ovn-util.h
@@ -122,6 +122,13 @@  get_unique_lport_key(uint64_t dp_tunnel_key, uint64_t lport_tunnel_key,
              lport_tunnel_key);
 }
 
+static inline void
+get_sb_port_group_name(const char *nb_pg_name, int64_t dp_tunnel_key,
+                       struct ds *sb_pg_name)
+{
+    ds_put_format(sb_pg_name, "%"PRId64"_%s", dp_tunnel_key, nb_pg_name);
+}
+
 char *ovn_chassis_redirect_name(const char *port_name);
 void ovn_set_pidfile(const char *name);
 
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index f49e4f2..e1dc491 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -4471,7 +4471,11 @@  build_dhcpv6_action(struct ovn_port *op, struct in6_addr *offer_ip,
 struct ovn_port_group_ls {
     struct hmap_node key_node;  /* Index on 'key'. */
     struct uuid key;            /* nb_ls->header_.uuid. */
-    const struct nbrec_logical_switch *nb_ls;
+    struct ovn_datapath *od;
+
+    struct ovn_port **ports; /* Ports in 'od' referrenced by the PG. */
+    size_t n_ports;
+    size_t n_allocated_ports;
 };
 
 struct ovn_port_group {
@@ -4481,14 +4485,14 @@  struct ovn_port_group {
     struct hmap nb_lswitches;   /* NB lswitches related to the port group */
 };
 
-static void
-ovn_port_group_ls_add(struct ovn_port_group *pg,
-                      const struct nbrec_logical_switch *nb_ls)
+static struct ovn_port_group_ls *
+ovn_port_group_ls_add(struct ovn_port_group *pg, struct ovn_datapath *od)
 {
     struct ovn_port_group_ls *pg_ls = xzalloc(sizeof *pg_ls);
-    pg_ls->key = nb_ls->header_.uuid;
-    pg_ls->nb_ls = nb_ls;
+    pg_ls->key = od->nbs->header_.uuid;
+    pg_ls->od = od;
     hmap_insert(&pg->nb_lswitches, &pg_ls->key_node, uuid_hash(&pg_ls->key));
+    return pg_ls;
 }
 
 static struct ovn_port_group_ls *
@@ -4505,6 +4509,18 @@  ovn_port_group_ls_find(struct ovn_port_group *pg, const struct uuid *ls_uuid)
     return NULL;
 }
 
+static void
+ovn_port_group_ls_add_port(struct ovn_port_group_ls *pg_ls,
+                           struct ovn_port *op)
+{
+    if (pg_ls->n_ports == pg_ls->n_allocated_ports) {
+        pg_ls->ports = x2nrealloc(pg_ls->ports,
+                                  &pg_ls->n_allocated_ports,
+                                  sizeof *pg_ls->ports);
+    }
+    pg_ls->ports[pg_ls->n_ports++] = op;
+}
+
 struct ovn_ls_port_group {
     struct hmap_node key_node;  /* Index on 'key'. */
     struct uuid key;            /* nb_pg->header_.uuid. */
@@ -5252,6 +5268,7 @@  ovn_port_group_destroy(struct hmap *pgs, struct ovn_port_group *pg)
         hmap_remove(pgs, &pg->key_node);
         struct ovn_port_group_ls *ls;
         HMAP_FOR_EACH_POP (ls, key_node, &pg->nb_lswitches) {
+            free(ls->ports);
             free(ls);
         }
         hmap_destroy(&pg->nb_lswitches);
@@ -5289,9 +5306,10 @@  build_port_group_lswitches(struct northd_context *ctx, struct hmap *pgs,
             struct ovn_port_group_ls *pg_ls =
                 ovn_port_group_ls_find(pg, &op->od->nbs->header_.uuid);
             if (!pg_ls) {
-                ovn_port_group_ls_add(pg, op->od->nbs);
+                pg_ls = ovn_port_group_ls_add(pg, op->od);
                 ovn_ls_port_group_add(&op->od->nb_pgs, nb_pg);
             }
+            ovn_port_group_ls_add_port(pg_ls, op);
         }
     }
 }
@@ -10509,7 +10527,7 @@  sync_address_sets(struct northd_context *ctx)
  * contains lport uuids, while in OVN_Southbound we store the lport names.
  */
 static void
-sync_port_groups(struct northd_context *ctx)
+sync_port_groups(struct northd_context *ctx, struct hmap *pgs)
 {
     struct shash sb_port_groups = SHASH_INITIALIZER(&sb_port_groups);
 
@@ -10518,26 +10536,35 @@  sync_port_groups(struct northd_context *ctx)
         shash_add(&sb_port_groups, sb_port_group->name, sb_port_group);
     }
 
-    const struct nbrec_port_group *nb_port_group;
-    NBREC_PORT_GROUP_FOR_EACH (nb_port_group, ctx->ovnnb_idl) {
-        sb_port_group = shash_find_and_delete(&sb_port_groups,
-                                               nb_port_group->name);
-        if (!sb_port_group) {
-            sb_port_group = sbrec_port_group_insert(ctx->ovnsb_txn);
-            sbrec_port_group_set_name(sb_port_group, nb_port_group->name);
-        }
+    struct ds sb_name = DS_EMPTY_INITIALIZER;
 
-        const char **nb_port_names = xcalloc(nb_port_group->n_ports,
-                                             sizeof *nb_port_names);
-        int i;
-        for (i = 0; i < nb_port_group->n_ports; i++) {
-            nb_port_names[i] = nb_port_group->ports[i]->name;
+    struct ovn_port_group *pg;
+    HMAP_FOR_EACH (pg, key_node, pgs) {
+
+        struct ovn_port_group_ls *pg_ls;
+        HMAP_FOR_EACH (pg_ls, key_node, &pg->nb_lswitches) {
+            ds_clear(&sb_name);
+            get_sb_port_group_name(pg->nb_pg->name, pg_ls->od->sb->tunnel_key,
+                                   &sb_name);
+            sb_port_group = shash_find_and_delete(&sb_port_groups,
+                                                  ds_cstr(&sb_name));
+            if (!sb_port_group) {
+                sb_port_group = sbrec_port_group_insert(ctx->ovnsb_txn);
+                sbrec_port_group_set_name(sb_port_group, ds_cstr(&sb_name));
+            }
+
+            const char **nb_port_names = xcalloc(pg_ls->n_ports,
+                                                 sizeof *nb_port_names);
+            for (size_t i = 0; i < pg_ls->n_ports; i++) {
+                nb_port_names[i] = pg_ls->ports[i]->nbsp->name;
+            }
+            sbrec_port_group_set_ports(sb_port_group,
+                                       nb_port_names,
+                                       pg_ls->n_ports);
+            free(nb_port_names);
         }
-        sbrec_port_group_set_ports(sb_port_group,
-                                   nb_port_names,
-                                   nb_port_group->n_ports);
-        free(nb_port_names);
     }
+    ds_destroy(&sb_name);
 
     struct shash_node *node, *next;
     SHASH_FOR_EACH_SAFE (node, next, &sb_port_groups) {
@@ -11140,7 +11167,7 @@  ovnnb_db_run(struct northd_context *ctx,
     ovn_update_ipv6_prefix(ports);
 
     sync_address_sets(ctx);
-    sync_port_groups(ctx);
+    sync_port_groups(ctx, &port_groups);
     sync_meters(ctx);
     sync_dns_entries(ctx, datapaths);
     destroy_ovn_lbs(&lbs);
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 80bda0a..d7a940f 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -1472,3 +1472,82 @@  AT_CHECK([ovn-nbctl --wait=sb sync], [0])
 AT_CHECK([test 0 = $(ovn-sbctl list Ha_Chassis_Group | wc -l)])
 
 AT_CLEANUP
+
+AT_SETUP([ovn -- check NB/SB Port_Group translation (lsp add/del)])
+ovn_start
+
+ovn-nbctl ls-add ls1
+ovn-nbctl ls-add ls2
+ovn-nbctl lsp-add ls1 lsp1
+ovn-nbctl lsp-add ls2 lsp2
+ovn-nbct --wait=sb sync
+ls1_key=$(ovn-sbctl --columns tunnel_key --bare list Datapath_Binding ls1)
+ls2_key=$(ovn-sbctl --columns tunnel_key --bare list Datapath_Binding ls2)
+
+# Add an empty port group. This should generate no entry in the SB.
+ovn-nbctl --wait=sb pg-add pg_test
+AT_CHECK([test 0 = $(ovn-sbctl --columns _uuid list Port_Group | grep uuid -c)])
+
+# Add lsp1 to the port group. This should generate an entry in the SB only
+# for ls1.
+ovn-nbctl --wait=sb pg-set-ports pg_test lsp1
+AT_CHECK([test 1 = $(ovn-sbctl --columns _uuid list Port_Group | grep uuid -c)])
+AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group name=${ls1_key}_pg_test], [0], [dnl
+lsp1
+])
+
+# Add lsp2 to the port group. This should generate a new entry in the SB, for
+# ls2.
+ovn-nbctl --wait=sb pg-set-ports pg_test lsp1 lsp2
+AT_CHECK([test 2 = $(ovn-sbctl --columns _uuid list Port_Group | grep uuid -c)])
+AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group name=${ls1_key}_pg_test], [0], [dnl
+lsp1
+])
+AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group name=${ls2_key}_pg_test], [0], [dnl
+lsp2
+])
+
+# Remove lsp1 from the port group. The SB Port_Group for ls1 should be
+# removed.
+ovn-nbctl --wait=sb pg-set-ports pg_test lsp2
+AT_CHECK([test 1 = $(ovn-sbctl --columns _uuid list Port_Group | grep uuid -c)])
+AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group name=${ls2_key}_pg_test], [0], [dnl
+lsp2
+])
+
+# Remove lsp2 from the port group. All SB Port_Groups should be purged.
+ovn-nbctl --wait=sb clear Port_Group pg_test ports
+AT_CHECK([test 0 = $(ovn-sbctl --columns _uuid list Port_Group | grep uuid -c)])
+
+AT_CLEANUP
+
+AT_SETUP([ovn -- check NB/SB Port_Group translation (ls del)])
+ovn_start
+
+ovn-nbctl ls-add ls1
+ovn-nbctl ls-add ls2
+ovn-nbctl lsp-add ls1 lsp1
+ovn-nbctl lsp-add ls2 lsp2
+ovn-nbct --wait=sb sync
+ls1_key=$(ovn-sbctl --columns tunnel_key --bare list Datapath_Binding ls1)
+ls2_key=$(ovn-sbctl --columns tunnel_key --bare list Datapath_Binding ls2)
+
+# Add lsp1 & lsp2 to a port group. This should generate two entries in the
+# SB (one per logical switch).
+ovn-nbctl --wait=sb pg-add pg_test lsp1 lsp2
+AT_CHECK([test 2 = $(ovn-sbctl --columns _uuid list Port_Group | grep uuid -c)])
+AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group name=${ls1_key}_pg_test], [0], [dnl
+lsp1
+])
+AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group name=${ls2_key}_pg_test], [0], [dnl
+lsp2
+])
+
+# Delete logical switch ls1. This should remove the associated SB Port_Group.
+ovn-nbctl --wait=sb ls-del ls1
+AT_CHECK([test 1 = $(ovn-sbctl --columns _uuid list Port_Group | grep uuid -c)])
+AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group name=${ls2_key}_pg_test], [0], [dnl
+lsp2
+])
+
+AT_CLEANUP
diff --git a/tests/test-ovn.c b/tests/test-ovn.c
index b43f67f..c3bfd20 100644
--- a/tests/test-ovn.c
+++ b/tests/test-ovn.c
@@ -238,8 +238,8 @@  create_port_groups(struct shash *port_groups)
     };
     static const char *const pg2[] = { NULL };
 
-    expr_const_sets_add(port_groups, "pg1", pg1, 3, false);
-    expr_const_sets_add(port_groups, "pg_empty", pg2, 0, false);
+    expr_const_sets_add(port_groups, "0_pg1", pg1, 3, false);
+    expr_const_sets_add(port_groups, "0_pg_empty", pg2, 0, false);
 }
 
 static bool
@@ -305,7 +305,7 @@  test_parse_expr__(int steps)
         char *error;
 
         expr = expr_parse_string(ds_cstr(&input), &symtab, &addr_sets,
-                                 &port_groups, NULL, NULL, &error);
+                                 &port_groups, NULL, NULL, 0, &error);
         if (!error && steps > 0) {
             expr = expr_annotate(expr, &symtab, &error);
         }
@@ -431,7 +431,7 @@  test_evaluate_expr(struct ovs_cmdl_context *ctx)
         struct expr *expr;
 
         expr = expr_parse_string(ds_cstr(&input), &symtab, NULL, NULL,
-                                 NULL, NULL, &error);
+                                 NULL, NULL, 0, &error);
         if (!error) {
             expr = expr_annotate(expr, &symtab, &error);
         }
@@ -906,7 +906,7 @@  test_tree_shape_exhaustively(struct expr *expr, struct shash *symtab,
 
             char *error;
             modified = expr_parse_string(ds_cstr(&s), symtab, NULL,
-                                         NULL, NULL, NULL, &error);
+                                         NULL, NULL, NULL, 0, &error);
             if (error) {
                 fprintf(stderr, "%s fails to parse (%s)\n",
                         ds_cstr(&s), error);
diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
index d7251e7..2666c10 100644
--- a/utilities/ovn-trace.c
+++ b/utilities/ovn-trace.c
@@ -889,7 +889,8 @@  read_flows(void)
         char *error;
         struct expr *match;
         match = expr_parse_string(sblf->match, &symtab, &address_sets,
-                                  &port_groups, NULL, NULL, &error);
+                                  &port_groups, NULL, NULL, dp->tunnel_key,
+                                  &error);
         if (error) {
             VLOG_WARN("%s: parsing expression failed (%s)",
                       sblf->match, error);