diff mbox series

[ovs-dev,ovn,v2] Support selection fields in load balancer.

Message ID 20200505183741.1350571-1-numans@ovn.org
State Superseded
Headers show
Series [ovs-dev,ovn,v2] Support selection fields in load balancer. | expand

Commit Message

Numan Siddique May 5, 2020, 6:37 p.m. UTC
From: Numan Siddique <numans@ovn.org>

This patch add a new column 'selection_fields' in Load_Balancer
table in NB DB. CMS can define a set of packet headers to use
while selecting a backend. If this column is set, OVN will add the
flow in group table with selection method as 'hash' with the set fields.
Otherwise it will use the default 'dp_hash' selection method.

If a load balancer is configured with the selection_fields as
selection_fields    : [ip_dst, ip_src, tp_dst, tp_src]

then with this patch, the modified ct_lb action will look like
 - ct_lb(backends=IP1:P1,IP2:P1; hash_fields="ip_dst,ip_src,tp_dst,tp_src");

And the OF flow will look like
 - group_id=2,type=select,selection_method=hash,
   fields(ip_src,ip_dst,tcp_src,tcp_dst),bucket=bucket_id:0,weight:100,actions=ct(....

Signed-off-by: Numan Siddique <numans@ovn.org>
---
v1 -> v2
 * The series has only 1 patch now as the first patch is merged.
 * Addressed review comments from Mark and Han.
 * Added test cases.
 * Added documentation.

 NEWS                  |   3 +
 include/ovn/actions.h |   1 +
 lib/actions.c         |  45 +++++++++--
 northd/ovn-northd.c   |  30 ++++++--
 ovn-nb.ovsschema      |  10 ++-
 ovn-nb.xml            |  27 +++++++
 tests/ovn-northd.at   |  24 +++---
 tests/ovn.at          |  49 +++++++-----
 tests/system-ovn.at   | 172 +++++++++++++++++++++++++++++++++++++++---
 9 files changed, 308 insertions(+), 53 deletions(-)

Comments

Maciej Jozefczyk May 6, 2020, 9:03 a.m. UTC | #1
Hey!

Thank you for the patch. I tested it with Octavia tempest tests detailed
here [1] and it works fine.

{0}
octavia_tempest_plugin.tests.scenario.v2.test_traffic_ops.TrafficOperationsScenarioTest.test_source_ip_port_tcp_traffic
[112.944528s] ... ok

[1] https://bugs.launchpad.net/neutron/+bug/1871239

Tested-By: Maciej Józefczyk <mjozefcz@redhat.com>
Acked-By: Maciej Józefczyk <mjozefcz@redhat.com>

On Tue, May 5, 2020 at 8:38 PM <numans@ovn.org> wrote:

> From: Numan Siddique <numans@ovn.org>
>
> This patch add a new column 'selection_fields' in Load_Balancer
> table in NB DB. CMS can define a set of packet headers to use
> while selecting a backend. If this column is set, OVN will add the
> flow in group table with selection method as 'hash' with the set fields.
> Otherwise it will use the default 'dp_hash' selection method.
>
> If a load balancer is configured with the selection_fields as
> selection_fields    : [ip_dst, ip_src, tp_dst, tp_src]
>
> then with this patch, the modified ct_lb action will look like
>  - ct_lb(backends=IP1:P1,IP2:P1;
> hash_fields="ip_dst,ip_src,tp_dst,tp_src");
>
> And the OF flow will look like
>  - group_id=2,type=select,selection_method=hash,
>
>  fields(ip_src,ip_dst,tcp_src,tcp_dst),bucket=bucket_id:0,weight:100,actions=ct(....
>
> Signed-off-by: Numan Siddique <numans@ovn.org>
> ---
> v1 -> v2
>  * The series has only 1 patch now as the first patch is merged.
>  * Addressed review comments from Mark and Han.
>  * Added test cases.
>  * Added documentation.
>
>  NEWS                  |   3 +
>  include/ovn/actions.h |   1 +
>  lib/actions.c         |  45 +++++++++--
>  northd/ovn-northd.c   |  30 ++++++--
>  ovn-nb.ovsschema      |  10 ++-
>  ovn-nb.xml            |  27 +++++++
>  tests/ovn-northd.at   |  24 +++---
>  tests/ovn.at          |  49 +++++++-----
>  tests/system-ovn.at   | 172 +++++++++++++++++++++++++++++++++++++++---
>  9 files changed, 308 insertions(+), 53 deletions(-)
>
> diff --git a/NEWS b/NEWS
> index c2b7945df..431b4cf9c 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -14,6 +14,9 @@ OVN v20.03.0 - xx xxx xxxx
>     - Added support for ECMP routes in OVN router.
>     - Added IPv6 Prefix Delegation support in OVN.
>     - OVN now uses OpenFlow 1.5.
> +   - Added support to choose selection methods - dp_hash or
> +     hash (with specified hash fields) for OVN load balancer
> +     backend selection.
>
>     - OVN Interconnection:
>       * Support for L3 interconnection of multiple OVN deployments with
> tunnels
> diff --git a/include/ovn/actions.h b/include/ovn/actions.h
> index 42d45a74c..4a54abe17 100644
> --- a/include/ovn/actions.h
> +++ b/include/ovn/actions.h
> @@ -259,6 +259,7 @@ struct ovnact_ct_lb {
>      struct ovnact_ct_lb_dst *dsts;
>      size_t n_dsts;
>      uint8_t ltable;             /* Logical table ID of next table. */
> +    char *hash_fields;
>  };
>
>  struct ovnact_select_dst {
> diff --git a/lib/actions.c b/lib/actions.c
> index 021a376b4..c50615177 100644
> --- a/lib/actions.c
> +++ b/lib/actions.c
> @@ -951,9 +951,18 @@ parse_ct_lb_action(struct action_context *ctx)
>      struct ovnact_ct_lb_dst *dsts = NULL;
>      size_t allocated_dsts = 0;
>      size_t n_dsts = 0;
> +    char *hash_fields = NULL;
>
> -    if (lexer_match(ctx->lexer, LEX_T_LPAREN)) {
> -        while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
> +    if (lexer_match(ctx->lexer, LEX_T_LPAREN) &&
> +        !lexer_match(ctx->lexer, LEX_T_RPAREN)) {
> +        if (!lexer_match_id(ctx->lexer, "backends") ||
> +            !lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
> +            lexer_syntax_error(ctx->lexer, "expecting backends");
> +            return;
> +        }
> +
> +        while (!lexer_match(ctx->lexer, LEX_T_SEMICOLON) &&
> +               !lexer_match(ctx->lexer, LEX_T_RPAREN)) {
>              struct ovnact_ct_lb_dst dst;
>              if (lexer_match(ctx->lexer, LEX_T_LSQUARE)) {
>                  /* IPv6 address and port */
> @@ -1020,12 +1029,27 @@ parse_ct_lb_action(struct action_context *ctx)
>              }
>              dsts[n_dsts++] = dst;
>          }
> +
> +        if (lexer_match_id(ctx->lexer, "hash_fields")) {
> +            if (!lexer_match(ctx->lexer, LEX_T_EQUALS) ||
> +                ctx->lexer->token.type != LEX_T_STRING ||
> +                lexer_lookahead(ctx->lexer) != LEX_T_RPAREN) {
> +                lexer_syntax_error(ctx->lexer, "invalid hash_fields");
> +                free(dsts);
> +                return;
> +            }
> +
> +            hash_fields = xstrdup(ctx->lexer->token.s);
> +            lexer_get(ctx->lexer);
> +            lexer_get(ctx->lexer);
> +        }
>      }
>
>      struct ovnact_ct_lb *cl = ovnact_put_CT_LB(ctx->ovnacts);
>      cl->ltable = ctx->pp->cur_ltable + 1;
>      cl->dsts = dsts;
>      cl->n_dsts = n_dsts;
> +    cl->hash_fields = hash_fields;
>  }
>
>  static void
> @@ -1033,10 +1057,10 @@ format_CT_LB(const struct ovnact_ct_lb *cl, struct
> ds *s)
>  {
>      ds_put_cstr(s, "ct_lb");
>      if (cl->n_dsts) {
> -        ds_put_char(s, '(');
> +        ds_put_cstr(s, "(backends=");
>          for (size_t i = 0; i < cl->n_dsts; i++) {
>              if (i) {
> -                ds_put_cstr(s, ", ");
> +                ds_put_char(s, ',');
>              }
>
>              const struct ovnact_ct_lb_dst *dst = &cl->dsts[i];
> @@ -1056,7 +1080,13 @@ format_CT_LB(const struct ovnact_ct_lb *cl, struct
> ds *s)
>              }
>          }
>          ds_put_char(s, ')');
> +
> +        if (cl->hash_fields) {
> +            ds_chomp(s, ')');
> +            ds_put_format(s, "; hash_fields=\"%s\")", cl->hash_fields);
> +        }
>      }
> +
>      ds_put_char(s, ';');
>  }
>
> @@ -1103,7 +1133,11 @@ encode_CT_LB(const struct ovnact_ct_lb *cl,
>                              : MFF_LOG_DNAT_ZONE - MFF_REG0;
>
>      struct ds ds = DS_EMPTY_INITIALIZER;
> -    ds_put_format(&ds, "type=select,selection_method=dp_hash");
> +    ds_put_format(&ds, "type=select,selection_method=%s",
> +                  cl->hash_fields ? "hash": "dp_hash");
> +    if (cl->hash_fields) {
> +        ds_put_format(&ds, ",fields(%s)", cl->hash_fields);
> +    }
>
>      BUILD_ASSERT(MFF_LOG_CT_ZONE >= MFF_REG0);
>      BUILD_ASSERT(MFF_LOG_CT_ZONE < MFF_REG0 + FLOW_N_REGS);
> @@ -1145,6 +1179,7 @@ static void
>  ovnact_ct_lb_free(struct ovnact_ct_lb *ct_lb)
>  {
>      free(ct_lb->dsts);
> +    free(ct_lb->hash_fields);
>  }
>
>  static void
> diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> index 3f03e418a..d98d3a6b6 100644
> --- a/northd/ovn-northd.c
> +++ b/northd/ovn-northd.c
> @@ -3112,7 +3112,7 @@ struct ovn_lb {
>      struct hmap_node hmap_node;
>
>      const struct nbrec_load_balancer *nlb; /* May be NULL. */
> -
> +    char *selection_fields;
>      struct lb_vip *vips;
>      size_t n_vips;
>  };
> @@ -3340,6 +3340,15 @@ ovn_lb_create(struct northd_context *ctx, struct
> hmap *lbs,
>          n_vips++;
>      }
>
> +    if (lb->nlb->n_selection_fields) {
> +        struct ds sel_fields = DS_EMPTY_INITIALIZER;
> +        for (size_t i = 0; i < lb->nlb->n_selection_fields; i++) {
> +            ds_put_format(&sel_fields, "%s,",
> lb->nlb->selection_fields[i]);
> +        }
> +        ds_chomp(&sel_fields, ',');
> +        lb->selection_fields = ds_steal_cstr(&sel_fields);
> +    }
> +
>      return lb;
>  }
>
> @@ -3358,13 +3367,15 @@ ovn_lb_destroy(struct ovn_lb *lb)
>          free(lb->vips[i].backends);
>      }
>      free(lb->vips);
> +    free(lb->selection_fields);
>  }
>
>  static void build_lb_vip_ct_lb_actions(struct lb_vip *lb_vip,
> -                                       struct ds *action)
> +                                       struct ds *action,
> +                                       char *selection_fields)
>  {
>      if (lb_vip->health_check) {
> -        ds_put_cstr(action, "ct_lb(");
> +        ds_put_cstr(action, "ct_lb(backends=");
>
>          size_t n_active_backends = 0;
>          for (size_t k = 0; k < lb_vip->n_backends; k++) {
> @@ -3388,7 +3399,13 @@ static void build_lb_vip_ct_lb_actions(struct
> lb_vip *lb_vip,
>              ds_put_cstr(action, ");");
>          }
>      } else {
> -        ds_put_format(action, "ct_lb(%s);", lb_vip->backend_ips);
> +        ds_put_format(action, "ct_lb(backends=%s);", lb_vip->backend_ips);
> +    }
> +
> +    if (selection_fields && selection_fields[0]) {
> +        ds_chomp(action, ';');
> +        ds_chomp(action, ')');
> +        ds_put_format(action, "; hash_fields=\"%s\");", selection_fields);
>      }
>  }
>
> @@ -5666,7 +5683,7 @@ build_lb_rules(struct ovn_datapath *od, struct hmap
> *lflows, struct ovn_lb *lb)
>
>          /* New connections in Ingress table. */
>          struct ds action = DS_EMPTY_INITIALIZER;
> -        build_lb_vip_ct_lb_actions(lb_vip, &action);
> +        build_lb_vip_ct_lb_actions(lb_vip, &action, lb->selection_fields);
>
>          struct ds match = DS_EMPTY_INITIALIZER;
>          ds_put_format(&match, "ct.new && %s.dst == %s", ip_match,
> lb_vip->vip);
> @@ -9317,7 +9334,8 @@ build_lrouter_flows(struct hmap *datapaths, struct
> hmap *ports,
>              for (size_t j = 0; j < lb->n_vips; j++) {
>                  struct lb_vip *lb_vip = &lb->vips[j];
>                  ds_clear(&actions);
> -                build_lb_vip_ct_lb_actions(lb_vip, &actions);
> +                build_lb_vip_ct_lb_actions(lb_vip, &actions,
> +                                           lb->selection_fields);
>
>                  if (!sset_contains(&all_ips, lb_vip->vip)) {
>                      sset_add(&all_ips, lb_vip->vip);
> diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
> index b2a046571..a06972aa0 100644
> --- a/ovn-nb.ovsschema
> +++ b/ovn-nb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Northbound",
> -    "version": "5.22.0",
> -    "cksum": "384164739 25476",
> +    "version": "5.23.0",
> +    "cksum": "111023208 25806",
>      "tables": {
>          "NB_Global": {
>              "columns": {
> @@ -179,6 +179,12 @@
>                  "ip_port_mappings": {
>                      "type": {"key": "string", "value": "string",
>                               "min": 0, "max": "unlimited"}},
> +                "selection_fields": {
> +                    "type": {"key": {"type": "string",
> +                             "enum": ["set",
> +                                ["eth_src", "eth_dst", "ip_src", "ip_dst",
> +                                 "tp_src", "tp_dst"]]},
> +                             "min": 0, "max": "unlimited"}},
>                  "external_ids": {
>                      "type": {"key": "string", "value": "string",
>                               "min": 0, "max": "unlimited"}}},
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index af15c550a..95ee4c9e6 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -1513,6 +1513,33 @@
>        </p>
>      </column>
>
> +    <column name="selection_fields">
> +      <p>
> +        OVN native load balancers are supported using the OpenFlow groups
> +        of type <code>select</code>. OVS supports two selection methods:
> +        <code>dp_hash</code> and <code>hash (with optional fields
> +        specified)</code> in selecting the buckets of a group.
> +        Please see the OVS documentation (man ovs-ofctl)
> +        for more details on the selection methods. Each endpoint IP (and
> port
> +        if set) is mapped to a bucket in the group flow.
> +      </p>
> +
> +      <p>
> +        CMS can choose the <code>hash</code> selection method by setting
> the
> +        selection fields in this column. <code>ovs-vswitchd</code> uses
> the
> +        specified fields in generating the hash.
> +      </p>
> +
> +      <p>
> +        <code>dp_hash</code> selection method uses the assistance of
> +        datapath to calculate the hash and it is expected to be
> +        faster than <code>hash</code> selection method. So CMS should take
> +        this into consideration before using the <code>hash</code> method.
> +        Please consult the OVS documentation and OVS sources for the
> +        implementation details.
> +      </p>
> +    </column>
> +
>      <group title="Common Columns">
>        <column name="external_ids">
>          See <em>External IDs</em> at the beginning of this document.
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index a0109ae94..57c9eedd5 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -1183,7 +1183,7 @@ ovn-nbctl --wait=sb ls-lb-add sw0 lb1
>
>  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80,20.0.0.3:80);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
> ,20.0.0.3:80);)
>  ])
>
>  # Delete the Load_Balancer_Health_Check
> @@ -1192,7 +1192,7 @@ OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list
> service_monitor |  wc -l`])
>
>  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80,20.0.0.3:80);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
> ,20.0.0.3:80);)
>  ])
>
>  # Create the Load_Balancer_Health_Check again.
> @@ -1205,7 +1205,7 @@ service_monitor | sed '/^$/d' | wc -l`])
>
>  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80,20.0.0.3:80);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
> ,20.0.0.3:80);)
>  ])
>
>  # Get the uuid of both the service_monitor
> @@ -1221,7 +1221,7 @@ OVS_WAIT_UNTIL([
>
>  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80);)
>  ])
>
>  # Set the service monitor for sw0-p1 to offline
> @@ -1251,7 +1251,7 @@ OVS_WAIT_UNTIL([
>
>  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80,20.0.0.3:80);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
> ,20.0.0.3:80);)
>  ])
>
>  # Set the service monitor for sw1-p1 to error
> @@ -1263,7 +1263,7 @@ OVS_WAIT_UNTIL([
>  ovn-sbctl dump-flows sw0 | grep "ip4.dst == 10.0.0.10 && tcp.dst == 80" \
>  | grep priority=120 > lflows.txt
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80);)
>  ])
>
>  # Add one more vip to lb1
> @@ -1293,8 +1293,8 @@ service_monitor port=1000 | sed '/^$/d' | wc -l`])
>
>  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80);)
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(10.0.0.3:1000);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(backends=10.0.0.3:1000);)
>  ])
>
>  # Set the service monitor for sw1-p1 to online
> @@ -1306,16 +1306,16 @@ OVS_WAIT_UNTIL([
>
>  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80,20.0.0.3:80);)
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(10.0.0.3:1000,20.0.0.3:80
> );)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
> ,20.0.0.3:80);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(backends=10.0.0.3:1000
> ,20.0.0.3:80);)
>  ])
>
>  # Associate lb1 to sw1
>  ovn-nbctl --wait=sb ls-lb-add sw1 lb1
>  ovn-sbctl dump-flows sw1 | grep ct_lb | grep priority=120 > lflows.txt
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80,20.0.0.3:80);)
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(10.0.0.3:1000,20.0.0.3:80
> );)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
> ,20.0.0.3:80);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(backends=10.0.0.3:1000
> ,20.0.0.3:80);)
>  ])
>
>  # Now create lb2 same as lb1 but udp protocol.
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 7befc8224..055fb57e2 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -970,29 +970,42 @@ ct_lb();
>      encodes as ct(table=19,zone=NXM_NX_REG13[0..15],nat)
>      has prereqs ip
>  ct_lb(192.168.1.2:80, 192.168.1.3:80);
> +    Syntax error at `192.168.1.2' expecting backends.
> +ct_lb(backends=192.168.1.2:80,192.168.1.3:80);
>      encodes as group:1
>      uses group: id(1),
> name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=ct(nat(dst=
> 192.168.1.2:80
> ),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=
> 192.168.1.3:80),commit,table=19,zone=NXM_NX_REG13[0..15]))
>      has prereqs ip
> -ct_lb(192.168.1.2, 192.168.1.3, );
> -    formats as ct_lb(192.168.1.2, 192.168.1.3);
> +ct_lb(backends=192.168.1.2, 192.168.1.3, );
> +    formats as ct_lb(backends=192.168.1.2,192.168.1.3);
>      encodes as group:2
>      uses group: id(2),
> name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=ct(nat(dst=192.168.1.2),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=192.168.1.3),commit,table=19,zone=NXM_NX_REG13[0..15]))
>      has prereqs ip
> -ct_lb(fd0f::2, fd0f::3, );
> -    formats as ct_lb(fd0f::2, fd0f::3);
> +ct_lb(backends=fd0f::2, fd0f::3, );
> +    formats as ct_lb(backends=fd0f::2,fd0f::3);
>      encodes as group:3
>      uses group: id(3),
> name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=ct(nat(dst=fd0f::2),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=fd0f::3),commit,table=19,zone=NXM_NX_REG13[0..15]))
>      has prereqs ip
>
> -ct_lb(192.168.1.2:);
> +ct_lb(backends=192.168.1.2:);
>      Syntax error at `)' expecting port number.
> -ct_lb(192.168.1.2:123456);
> +ct_lb(backends=192.168.1.2:123456);
>      Syntax error at `123456' expecting port number.
> -ct_lb(foo);
> +ct_lb(backends=foo);
>      Syntax error at `foo' expecting IP address.
> -ct_lb([192.168.1.2]);
> +ct_lb(backends=[192.168.1.2]);
>      Syntax error at `192.168.1.2' expecting IPv6 address.
>
> +ct_lb(backends=192.168.1.2:80,192.168.1.3:80;
> hash_fields=eth_src,eth_dst,ip_src);
> +    Syntax error at `eth_src' invalid hash_fields.
> +ct_lb(backends=192.168.1.2:80,192.168.1.3:80;
> hash_fields="eth_src,eth_dst,ip_src");
> +    encodes as group:4
> +    uses group: id(4),
> name(type=select,selection_method=hash,fields(eth_src,eth_dst,ip_src),bucket=bucket_id=0,weight:100,actions=ct(nat(dst=
> 192.168.1.2:80
> ),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=
> 192.168.1.3:80),commit,table=19,zone=NXM_NX_REG13[0..15]))
> +    has prereqs ip
> +ct_lb(backends=fd0f::2,fd0f::3;
> hash_fields="eth_src,eth_dst,ip_src,ip_dst,tp_src,tp_dst");
> +    encodes as group:5
> +    uses group: id(5),
> name(type=select,selection_method=hash,fields(eth_src,eth_dst,ip_src,ip_dst,tp_src,tp_dst),bucket=bucket_id=0,weight:100,actions=ct(nat(dst=fd0f::2),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=fd0f::3),commit,table=19,zone=NXM_NX_REG13[0..15]))
> +    has prereqs ip
> +
>  # ct_next
>  ct_next;
>      encodes as ct(table=19,zone=NXM_NX_REG13[0..15])
> @@ -1520,13 +1533,13 @@ handle_svc_check(reg0);
>  # select
>  reg9[16..31] = select(1=50, 2=100, 3, );
>      formats as reg9[16..31] = select(1=50, 2=100, 3=100);
> -    encodes as group:4
> -    uses group: id(4),
> name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:50,actions=load:1->xreg4[16..31],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xreg4[16..31],resubmit(,19),bucket=bucket_id=2,weight:100,actions=load:3->xreg4[16..31],resubmit(,19))
> +    encodes as group:6
> +    uses group: id(6),
> name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:50,actions=load:1->xreg4[16..31],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xreg4[16..31],resubmit(,19),bucket=bucket_id=2,weight:100,actions=load:3->xreg4[16..31],resubmit(,19))
>
>  reg0 = select(1, 2);
>      formats as reg0 = select(1=100, 2=100);
> -    encodes as group:5
> -    uses group: id(5),
> name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=load:1->xxreg0[96..127],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xxreg0[96..127],resubmit(,19))
> +    encodes as group:7
> +    uses group: id(7),
> name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=load:1->xxreg0[96..127],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xxreg0[96..127],resubmit(,19))
>
>  reg0 = select(1=, 2);
>      Syntax error at `,' expecting weight.
> @@ -1542,12 +1555,12 @@ reg0[0..14] = select(1, 2, 3);
>      cannot use 15-bit field reg0[0..14] for "select", which requires at
> least 16 bits.
>
>  fwd_group(liveness="true", childports="eth0", "lsp1");
> -    encodes as group:6
> -    uses group: id(6),
> name(type=select,selection_method=dp_hash,bucket=watch_port:5,load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=watch_port:17,load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
> +    encodes as group:8
> +    uses group: id(8),
> name(type=select,selection_method=dp_hash,bucket=watch_port:5,load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=watch_port:17,load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
>
>  fwd_group(childports="eth0", "lsp1");
> -    encodes as group:7
> -    uses group: id(7),
> name(type=select,selection_method=dp_hash,bucket=load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
> +    encodes as group:9
> +    uses group: id(9),
> name(type=select,selection_method=dp_hash,bucket=load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
>
>  fwd_group(childports=eth0);
>      Syntax error at `eth0' expecting logical switch port.
> @@ -18000,12 +18013,12 @@ service_monitor | sed '/^$/d' | wc -l`])
>
>  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80,20.0.0.3:80);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst
> == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
> ,20.0.0.3:80);)
>  ])
>
>  ovn-sbctl dump-flows lr0 | grep ct_lb | grep priority=120 > lflows.txt
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip &&
> ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 &&
> is_chassis_resident("cr-lr0-public")), action=(ct_lb(10.0.0.3:80
> ,20.0.0.3:80);)
> +  table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip &&
> ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 &&
> is_chassis_resident("cr-lr0-public")), action=(ct_lb(backends=10.0.0.3:80
> ,20.0.0.3:80);)
>  ])
>
>  # get the svc monitor mac.
> diff --git a/tests/system-ovn.at b/tests/system-ovn.at
> index fa3b83cb1..32ae60925 100644
> --- a/tests/system-ovn.at
> +++ b/tests/system-ovn.at
> @@ -1095,15 +1095,15 @@ ovn-nbctl lsp-add bar bar3 \
>  -- lsp-set-addresses bar3 "f0:00:0f:01:02:05 172.16.1.4"
>
>  # Config OVN load-balancer with a VIP.
> -uuid=`ovn-nbctl  create load_balancer
> vips:30.0.0.1="172.16.1.2,172.16.1.3,172.16.1.4"`
> -ovn-nbctl set logical_switch foo load_balancer=$uuid
> +ovn-nbctl lb-add lb1 30.0.0.1 "172.16.1.2,172.16.1.3,172.16.1.4"
> +ovn-nbctl ls-lb-add foo lb1
>
>  # Create another load-balancer with another VIP.
> -uuid=`ovn-nbctl create load_balancer
> vips:30.0.0.3="172.16.1.2,172.16.1.3,172.16.1.4"`
> -ovn-nbctl add logical_switch foo load_balancer $uuid
> +lb2_uuid=`ovn-nbctl create load_balancer name=lb2
> vips:30.0.0.3="172.16.1.2,172.16.1.3,172.16.1.4"`
> +ovn-nbctl ls-lb-add foo lb2
>
>  # Config OVN load-balancer with another VIP (this time with ports).
> -ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"172.16.1.2:80,
> 172.16.1.3:80,172.16.1.4:80"'
> +ovn-nbctl set load_balancer $lb2_uuid vips:'"30.0.0.2:8000"'='"
> 172.16.1.2:80,172.16.1.3:80,172.16.1.4:80"'
>
>  # Wait for ovn-controller to catch up.
>  ovn-nbctl --wait=hv sync
> @@ -1157,6 +1157,82 @@
> tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(s
>
>  tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>  ])
>
> +# Configure selection_fields.
> +ovn-nbctl set load_balancer $lb2_uuid
> selection_fields="ip_src,ip_dst,tp_src,tp_dst"
> +OVS_WAIT_UNTIL([
> +    test $(ovs-ofctl dump-groups br-int | \
> +    grep "selection_method=hash,fields(ip_src,ip_dst,tcp_src,tcp_dst)"
> -c) -eq 2
> +])
> +
> +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> +
> +dnl Test load-balancing that includes L4 ports in NAT.
> +for i in `seq 1 20`; do
> +    echo Request $i
> +    NS_CHECK_EXEC([foo1], [wget 30.0.0.2:8000 -t 5 -T 1
> --retry-connrefused -v -o wget$i.log])
> +done
> +
> +dnl Each server should have at least one connection.
> +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | \
> +sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>
> +tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>
> +tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>
> +tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
> +])
> +
> +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> +
> +echo "foo" > foo
> +for i in `seq 1 20`; do
> +    echo Request $i
> +    ip netns exec foo1 nc -p 30000 30.0.0.2 8000 < foo
> +done
> +
> +dnl Only one backend should be chosen.
> +AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 -c) -eq
> 1])
> +
> +ovn-nbctl set load_balancer $lb2_uuid selection_fields="ip_src"
> +OVS_WAIT_UNTIL([
> +    test $(ovs-ofctl dump-groups br-int | \
> +    grep "selection_method=hash,fields=ip_src" -c) -eq 2
> +])
> +
> +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> +for i in `seq 1 20`; do
> +    echo Request $i
> +    ip netns exec foo1 nc 30.0.0.2 8000 < foo
> +done
> +
> +dnl Only one backend should be chosen as eth_src and ip_src is fixed.
> +bar1_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep
> 172.16.1.2 -c)
> +bar2_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep
> 172.16.1.3 -c)
> +bar3_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep
> 172.16.1.4 -c)
> +
> +AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep
> 172.16.1 -c) -ne 0])
> +
> +if [[ "$bar1_ct" == "20" ]]; then
> +    AT_CHECK([test $bar1_ct -eq 20])
> +    AT_CHECK([test $bar2_ct -eq 0])
> +    AT_CHECK([test $bar3_ct -eq 0])
> +else
> +    AT_CHECK([test $bar1_ct -eq 0])
> +fi
> +
> +if [[ "$bar2_ct" == "20" ]]; then
> +    AT_CHECK([test $bar1_ct -eq 20])
> +    AT_CHECK([test $bar2_ct -eq 0])
> +    AT_CHECK([test $bar3_ct -eq 0])
> +else
> +    AT_CHECK([test $bar2_ct -eq 0])
> +fi
> +
> +if [[ "$bar3_ct" == "20" ]]; then
> +    AT_CHECK([test $bar1_ct -eq 20])
> +    AT_CHECK([test $bar2_ct -eq 0])
> +    AT_CHECK([test $bar3_ct -eq 0])
> +else
> +    AT_CHECK([test $bar3_ct -eq 0])
> +fi
>
>  OVS_APP_EXIT_AND_WAIT([ovn-controller])
>
> @@ -1246,11 +1322,11 @@ uuid=`ovn-nbctl  create load_balancer
> vips:\"fd03::1\"=\"fd02::2,fd02::3,fd02::4
>  ovn-nbctl set logical_switch foo load_balancer=$uuid
>
>  # Create another load-balancer with another VIP.
> -uuid=`ovn-nbctl create load_balancer
> vips:\"fd03::3\"=\"fd02::2,fd02::3,fd02::4\"`
> -ovn-nbctl add logical_switch foo load_balancer $uuid
> +lb2_uuid=`ovn-nbctl create load_balancer
> vips:\"fd03::3\"=\"fd02::2,fd02::3,fd02::4\"`
> +ovn-nbctl add logical_switch foo load_balancer $lb2_uuid
>
>  # Config OVN load-balancer with another VIP (this time with ports).
> -ovn-nbctl set load_balancer $uuid vips:'"[[fd03::2]]:8000"'='"@<:@fd02::2@
> :>@:80,@<:@fd02::3@:>@:80,@<:@fd02::4@:>@:80"'
> +ovn-nbctl set load_balancer $lb2_uuid
> vips:'"[[fd03::2]]:8000"'='"@<:@fd02::2@:>@:80,@<:@fd02::3@
> :>@:80,@<:@fd02::4@:>@:80"'
>
>  # Wait for ovn-controller to catch up.
>  ovn-nbctl --wait=hv sync
> @@ -1304,7 +1380,83 @@
> tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd
>
>  tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::4,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>  ])
>
> +# Configure selection_fields.
> +ovn-nbctl set load_balancer $lb2_uuid
> selection_fields="ip_src,ip_dst,tp_src,tp_dst"
> +OVS_WAIT_UNTIL([
> +    test $(ovs-ofctl dump-groups br-int | \
> +    grep "selection_method=hash,fields(ip_src,ip_dst,tcp_src,tcp_dst)"
> -c) -eq 2
> +])
> +
> +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> +
> +dnl Test load-balancing that includes L4 ports in NAT.
> +for i in `seq 1 20`; do
> +    echo Request $i
> +    NS_CHECK_EXEC([foo1], [wget http://[[fd03::2]]:8000 -t 5 -T 1
> --retry-connrefused -v -o wget$i.log])
> +done
> +
> +dnl Each server should have at least one connection.
> +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd03::2) | grep -v
> fe80 | \
> +sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>
> +tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::2,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>
> +tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::3,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>
> +tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::4,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
> +])
> +
> +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> +
> +echo "foo" > foo
> +for i in `seq 1 20`; do
> +    echo Request $i
> +    ip netns exec foo1 nc -6 -p 30000 fd03::2 8000 < foo
> +done
> +
> +# Only one backend should be chosen. Since the source port is fixed,
> +# there should be only one conntrack entry.
> +AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep fd03::2 -c) -eq
> 1])
> +
> +ovn-nbctl set load_balancer $lb2_uuid selection_fields="eth_src,ip_src"
> +OVS_WAIT_UNTIL([
> +    test $(ovs-ofctl dump-groups br-int | \
> +    grep "selection_method=hash,fields(eth_src,ip_src)" -c) -eq 2
> +])
> +
> +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> +for i in `seq 1 20`; do
> +    echo Request $i
> +    ip netns exec foo1 nc -6 fd03::2 8000 < foo
> +done
>
> +dnl Only one backend should be chosen as eth_src and ip_src is fixed.
> +bar1_ct=$(ovs-appctl dpctl/dump-conntrack | grep fd03::2 | grep fd02::2
> -c)
> +bar2_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep fd02::3
> -c)
> +bar3_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep fd02::4
> -c)
> +
> +AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep fd03::2 | grep
> fd02 -c) -ne 0])
> +
> +if [[ "$bar1_ct" == "20" ]]; then
> +    AT_CHECK([test $bar1_ct -eq 20])
> +    AT_CHECK([test $bar2_ct -eq 0])
> +    AT_CHECK([test $bar3_ct -eq 0])
> +else
> +    AT_CHECK([test $bar1_ct -eq 0])
> +fi
> +
> +if [[ "$bar2_ct" == "20" ]]; then
> +    AT_CHECK([test $bar1_ct -eq 20])
> +    AT_CHECK([test $bar2_ct -eq 0])
> +    AT_CHECK([test $bar3_ct -eq 0])
> +else
> +    AT_CHECK([test $bar2_ct -eq 0])
> +fi
> +
> +if [[ "$bar3_ct" == "20" ]]; then
> +    AT_CHECK([test $bar1_ct -eq 20])
> +    AT_CHECK([test $bar2_ct -eq 0])
> +    AT_CHECK([test $bar3_ct -eq 0])
> +else
> +    AT_CHECK([test $bar3_ct -eq 0])
> +fi
>  OVS_APP_EXIT_AND_WAIT([ovn-controller])
>
>  as ovn-sb
> @@ -3448,7 +3600,7 @@ service_monitor | sed '/^$/d' | grep online | wc
> -l`])
>
>  OVS_WAIT_UNTIL(
>      [ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 | grep
> "ip4.dst == 10.0.0.10" > lflows.txt
> -     test 1 = `cat lflows.txt | grep "ct_lb(10.0.0.3:80,20.0.0.3:80)" |
> wc -l`]
> +     test 1 = `cat lflows.txt | grep "ct_lb(backends=10.0.0.3:80,
> 20.0.0.3:80)" | wc -l`]
>  )
>
>  # From sw0-p2 send traffic to vip - 10.0.0.10
> @@ -3474,7 +3626,7 @@ service_monitor logical_port=sw0-p1 | sed '/^$/d' |
> grep offline | wc -l`])
>
>  OVS_WAIT_UNTIL(
>      [ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 | grep
> "ip4.dst == 10.0.0.10" > lflows.txt
> -     test 1 = `cat lflows.txt | grep "ct_lb(20.0.0.3:80)" | wc -l`]
> +     test 1 = `cat lflows.txt | grep "ct_lb(backends=20.0.0.3:80)" | wc
> -l`]
>  )
>
>  ovs-appctl dpctl/flush-conntrack
> --
> 2.26.2
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
>
Han Zhou May 6, 2020, 5:49 p.m. UTC | #2
On Tue, May 5, 2020 at 11:38 AM <numans@ovn.org> wrote:
>
> From: Numan Siddique <numans@ovn.org>
>
> This patch add a new column 'selection_fields' in Load_Balancer
> table in NB DB. CMS can define a set of packet headers to use
> while selecting a backend. If this column is set, OVN will add the
> flow in group table with selection method as 'hash' with the set fields.
> Otherwise it will use the default 'dp_hash' selection method.
>
> If a load balancer is configured with the selection_fields as
> selection_fields    : [ip_dst, ip_src, tp_dst, tp_src]
>
> then with this patch, the modified ct_lb action will look like
>  - ct_lb(backends=IP1:P1,IP2:P1;
hash_fields="ip_dst,ip_src,tp_dst,tp_src");
>
> And the OF flow will look like
>  - group_id=2,type=select,selection_method=hash,
>
 fields(ip_src,ip_dst,tcp_src,tcp_dst),bucket=bucket_id:0,weight:100,actions=ct(....
>
> Signed-off-by: Numan Siddique <numans@ovn.org>
> ---
> v1 -> v2
>  * The series has only 1 patch now as the first patch is merged.
>  * Addressed review comments from Mark and Han.
>  * Added test cases.
>  * Added documentation.
>
>  NEWS                  |   3 +
>  include/ovn/actions.h |   1 +
>  lib/actions.c         |  45 +++++++++--
>  northd/ovn-northd.c   |  30 ++++++--
>  ovn-nb.ovsschema      |  10 ++-
>  ovn-nb.xml            |  27 +++++++
>  tests/ovn-northd.at   |  24 +++---
>  tests/ovn.at          |  49 +++++++-----
>  tests/system-ovn.at   | 172 +++++++++++++++++++++++++++++++++++++++---
>  9 files changed, 308 insertions(+), 53 deletions(-)
>
> diff --git a/NEWS b/NEWS
> index c2b7945df..431b4cf9c 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -14,6 +14,9 @@ OVN v20.03.0 - xx xxx xxxx
>     - Added support for ECMP routes in OVN router.
>     - Added IPv6 Prefix Delegation support in OVN.
>     - OVN now uses OpenFlow 1.5.
> +   - Added support to choose selection methods - dp_hash or
> +     hash (with specified hash fields) for OVN load balancer
> +     backend selection.

Thanks for the update here. Shall we call out this is incompatible with
older version to warn users that upgrade must be taken care specially? With
this addressed:
Acked-by: Han Zhou <hzhou@ovn.org>

>
>     - OVN Interconnection:
>       * Support for L3 interconnection of multiple OVN deployments with
tunnels
> diff --git a/include/ovn/actions.h b/include/ovn/actions.h
> index 42d45a74c..4a54abe17 100644
> --- a/include/ovn/actions.h
> +++ b/include/ovn/actions.h
> @@ -259,6 +259,7 @@ struct ovnact_ct_lb {
>      struct ovnact_ct_lb_dst *dsts;
>      size_t n_dsts;
>      uint8_t ltable;             /* Logical table ID of next table. */
> +    char *hash_fields;
>  };
>
>  struct ovnact_select_dst {
> diff --git a/lib/actions.c b/lib/actions.c
> index 021a376b4..c50615177 100644
> --- a/lib/actions.c
> +++ b/lib/actions.c
> @@ -951,9 +951,18 @@ parse_ct_lb_action(struct action_context *ctx)
>      struct ovnact_ct_lb_dst *dsts = NULL;
>      size_t allocated_dsts = 0;
>      size_t n_dsts = 0;
> +    char *hash_fields = NULL;
>
> -    if (lexer_match(ctx->lexer, LEX_T_LPAREN)) {
> -        while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
> +    if (lexer_match(ctx->lexer, LEX_T_LPAREN) &&
> +        !lexer_match(ctx->lexer, LEX_T_RPAREN)) {
> +        if (!lexer_match_id(ctx->lexer, "backends") ||
> +            !lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
> +            lexer_syntax_error(ctx->lexer, "expecting backends");
> +            return;
> +        }
> +
> +        while (!lexer_match(ctx->lexer, LEX_T_SEMICOLON) &&
> +               !lexer_match(ctx->lexer, LEX_T_RPAREN)) {
>              struct ovnact_ct_lb_dst dst;
>              if (lexer_match(ctx->lexer, LEX_T_LSQUARE)) {
>                  /* IPv6 address and port */
> @@ -1020,12 +1029,27 @@ parse_ct_lb_action(struct action_context *ctx)
>              }
>              dsts[n_dsts++] = dst;
>          }
> +
> +        if (lexer_match_id(ctx->lexer, "hash_fields")) {
> +            if (!lexer_match(ctx->lexer, LEX_T_EQUALS) ||
> +                ctx->lexer->token.type != LEX_T_STRING ||
> +                lexer_lookahead(ctx->lexer) != LEX_T_RPAREN) {
> +                lexer_syntax_error(ctx->lexer, "invalid hash_fields");
> +                free(dsts);
> +                return;
> +            }
> +
> +            hash_fields = xstrdup(ctx->lexer->token.s);
> +            lexer_get(ctx->lexer);
> +            lexer_get(ctx->lexer);
> +        }
>      }
>
>      struct ovnact_ct_lb *cl = ovnact_put_CT_LB(ctx->ovnacts);
>      cl->ltable = ctx->pp->cur_ltable + 1;
>      cl->dsts = dsts;
>      cl->n_dsts = n_dsts;
> +    cl->hash_fields = hash_fields;
>  }
>
>  static void
> @@ -1033,10 +1057,10 @@ format_CT_LB(const struct ovnact_ct_lb *cl,
struct ds *s)
>  {
>      ds_put_cstr(s, "ct_lb");
>      if (cl->n_dsts) {
> -        ds_put_char(s, '(');
> +        ds_put_cstr(s, "(backends=");
>          for (size_t i = 0; i < cl->n_dsts; i++) {
>              if (i) {
> -                ds_put_cstr(s, ", ");
> +                ds_put_char(s, ',');
>              }
>
>              const struct ovnact_ct_lb_dst *dst = &cl->dsts[i];
> @@ -1056,7 +1080,13 @@ format_CT_LB(const struct ovnact_ct_lb *cl, struct
ds *s)
>              }
>          }
>          ds_put_char(s, ')');
> +
> +        if (cl->hash_fields) {
> +            ds_chomp(s, ')');
> +            ds_put_format(s, "; hash_fields=\"%s\")", cl->hash_fields);
> +        }
>      }
> +
>      ds_put_char(s, ';');
>  }
>
> @@ -1103,7 +1133,11 @@ encode_CT_LB(const struct ovnact_ct_lb *cl,
>                              : MFF_LOG_DNAT_ZONE - MFF_REG0;
>
>      struct ds ds = DS_EMPTY_INITIALIZER;
> -    ds_put_format(&ds, "type=select,selection_method=dp_hash");
> +    ds_put_format(&ds, "type=select,selection_method=%s",
> +                  cl->hash_fields ? "hash": "dp_hash");
> +    if (cl->hash_fields) {
> +        ds_put_format(&ds, ",fields(%s)", cl->hash_fields);
> +    }
>
>      BUILD_ASSERT(MFF_LOG_CT_ZONE >= MFF_REG0);
>      BUILD_ASSERT(MFF_LOG_CT_ZONE < MFF_REG0 + FLOW_N_REGS);
> @@ -1145,6 +1179,7 @@ static void
>  ovnact_ct_lb_free(struct ovnact_ct_lb *ct_lb)
>  {
>      free(ct_lb->dsts);
> +    free(ct_lb->hash_fields);
>  }
>
>  static void
> diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> index 3f03e418a..d98d3a6b6 100644
> --- a/northd/ovn-northd.c
> +++ b/northd/ovn-northd.c
> @@ -3112,7 +3112,7 @@ struct ovn_lb {
>      struct hmap_node hmap_node;
>
>      const struct nbrec_load_balancer *nlb; /* May be NULL. */
> -
> +    char *selection_fields;
>      struct lb_vip *vips;
>      size_t n_vips;
>  };
> @@ -3340,6 +3340,15 @@ ovn_lb_create(struct northd_context *ctx, struct
hmap *lbs,
>          n_vips++;
>      }
>
> +    if (lb->nlb->n_selection_fields) {
> +        struct ds sel_fields = DS_EMPTY_INITIALIZER;
> +        for (size_t i = 0; i < lb->nlb->n_selection_fields; i++) {
> +            ds_put_format(&sel_fields, "%s,",
lb->nlb->selection_fields[i]);
> +        }
> +        ds_chomp(&sel_fields, ',');
> +        lb->selection_fields = ds_steal_cstr(&sel_fields);
> +    }
> +
>      return lb;
>  }
>
> @@ -3358,13 +3367,15 @@ ovn_lb_destroy(struct ovn_lb *lb)
>          free(lb->vips[i].backends);
>      }
>      free(lb->vips);
> +    free(lb->selection_fields);
>  }
>
>  static void build_lb_vip_ct_lb_actions(struct lb_vip *lb_vip,
> -                                       struct ds *action)
> +                                       struct ds *action,
> +                                       char *selection_fields)
>  {
>      if (lb_vip->health_check) {
> -        ds_put_cstr(action, "ct_lb(");
> +        ds_put_cstr(action, "ct_lb(backends=");
>
>          size_t n_active_backends = 0;
>          for (size_t k = 0; k < lb_vip->n_backends; k++) {
> @@ -3388,7 +3399,13 @@ static void build_lb_vip_ct_lb_actions(struct
lb_vip *lb_vip,
>              ds_put_cstr(action, ");");
>          }
>      } else {
> -        ds_put_format(action, "ct_lb(%s);", lb_vip->backend_ips);
> +        ds_put_format(action, "ct_lb(backends=%s);",
lb_vip->backend_ips);
> +    }
> +
> +    if (selection_fields && selection_fields[0]) {
> +        ds_chomp(action, ';');
> +        ds_chomp(action, ')');
> +        ds_put_format(action, "; hash_fields=\"%s\");",
selection_fields);
>      }
>  }
>
> @@ -5666,7 +5683,7 @@ build_lb_rules(struct ovn_datapath *od, struct hmap
*lflows, struct ovn_lb *lb)
>
>          /* New connections in Ingress table. */
>          struct ds action = DS_EMPTY_INITIALIZER;
> -        build_lb_vip_ct_lb_actions(lb_vip, &action);
> +        build_lb_vip_ct_lb_actions(lb_vip, &action,
lb->selection_fields);
>
>          struct ds match = DS_EMPTY_INITIALIZER;
>          ds_put_format(&match, "ct.new && %s.dst == %s", ip_match,
lb_vip->vip);
> @@ -9317,7 +9334,8 @@ build_lrouter_flows(struct hmap *datapaths, struct
hmap *ports,
>              for (size_t j = 0; j < lb->n_vips; j++) {
>                  struct lb_vip *lb_vip = &lb->vips[j];
>                  ds_clear(&actions);
> -                build_lb_vip_ct_lb_actions(lb_vip, &actions);
> +                build_lb_vip_ct_lb_actions(lb_vip, &actions,
> +                                           lb->selection_fields);
>
>                  if (!sset_contains(&all_ips, lb_vip->vip)) {
>                      sset_add(&all_ips, lb_vip->vip);
> diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
> index b2a046571..a06972aa0 100644
> --- a/ovn-nb.ovsschema
> +++ b/ovn-nb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Northbound",
> -    "version": "5.22.0",
> -    "cksum": "384164739 25476",
> +    "version": "5.23.0",
> +    "cksum": "111023208 25806",
>      "tables": {
>          "NB_Global": {
>              "columns": {
> @@ -179,6 +179,12 @@
>                  "ip_port_mappings": {
>                      "type": {"key": "string", "value": "string",
>                               "min": 0, "max": "unlimited"}},
> +                "selection_fields": {
> +                    "type": {"key": {"type": "string",
> +                             "enum": ["set",
> +                                ["eth_src", "eth_dst", "ip_src",
"ip_dst",
> +                                 "tp_src", "tp_dst"]]},
> +                             "min": 0, "max": "unlimited"}},
>                  "external_ids": {
>                      "type": {"key": "string", "value": "string",
>                               "min": 0, "max": "unlimited"}}},
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index af15c550a..95ee4c9e6 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -1513,6 +1513,33 @@
>        </p>
>      </column>
>
> +    <column name="selection_fields">
> +      <p>
> +        OVN native load balancers are supported using the OpenFlow groups
> +        of type <code>select</code>. OVS supports two selection methods:
> +        <code>dp_hash</code> and <code>hash (with optional fields
> +        specified)</code> in selecting the buckets of a group.
> +        Please see the OVS documentation (man ovs-ofctl)
> +        for more details on the selection methods. Each endpoint IP (and
port
> +        if set) is mapped to a bucket in the group flow.
> +      </p>
> +
> +      <p>
> +        CMS can choose the <code>hash</code> selection method by setting
the
> +        selection fields in this column. <code>ovs-vswitchd</code> uses
the
> +        specified fields in generating the hash.
> +      </p>
> +
> +      <p>
> +        <code>dp_hash</code> selection method uses the assistance of
> +        datapath to calculate the hash and it is expected to be
> +        faster than <code>hash</code> selection method. So CMS should
take
> +        this into consideration before using the <code>hash</code>
method.
> +        Please consult the OVS documentation and OVS sources for the
> +        implementation details.
> +      </p>
> +    </column>
> +
>      <group title="Common Columns">
>        <column name="external_ids">
>          See <em>External IDs</em> at the beginning of this document.
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index a0109ae94..57c9eedd5 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -1183,7 +1183,7 @@ ovn-nbctl --wait=sb ls-lb-add sw0 lb1
>
>  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
,20.0.0.3:80);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
,20.0.0.3:80);)
>  ])
>
>  # Delete the Load_Balancer_Health_Check
> @@ -1192,7 +1192,7 @@ OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list
service_monitor |  wc -l`])
>
>  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
,20.0.0.3:80);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
,20.0.0.3:80);)
>  ])
>
>  # Create the Load_Balancer_Health_Check again.
> @@ -1205,7 +1205,7 @@ service_monitor | sed '/^$/d' | wc -l`])
>
>  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
,20.0.0.3:80);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
,20.0.0.3:80);)
>  ])
>
>  # Get the uuid of both the service_monitor
> @@ -1221,7 +1221,7 @@ OVS_WAIT_UNTIL([
>
>  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
);)
>  ])
>
>  # Set the service monitor for sw0-p1 to offline
> @@ -1251,7 +1251,7 @@ OVS_WAIT_UNTIL([
>
>  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
,20.0.0.3:80);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
,20.0.0.3:80);)
>  ])
>
>  # Set the service monitor for sw1-p1 to error
> @@ -1263,7 +1263,7 @@ OVS_WAIT_UNTIL([
>  ovn-sbctl dump-flows sw0 | grep "ip4.dst == 10.0.0.10 && tcp.dst == 80" \
>  | grep priority=120 > lflows.txt
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
);)
>  ])
>
>  # Add one more vip to lb1
> @@ -1293,8 +1293,8 @@ service_monitor port=1000 | sed '/^$/d' | wc -l`])
>
>  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80);)
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(10.0.0.3:1000);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.40 && tcp.dst == 1000),
action=(ct_lb(backends=10.0.0.3:1000);)
>  ])
>
>  # Set the service monitor for sw1-p1 to online
> @@ -1306,16 +1306,16 @@ OVS_WAIT_UNTIL([
>
>  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
,20.0.0.3:80);)
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(10.0.0.3:1000
,20.0.0.3:80);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
,20.0.0.3:80);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(backends=
10.0.0.3:1000,20.0.0.3:80);)
>  ])
>
>  # Associate lb1 to sw1
>  ovn-nbctl --wait=sb ls-lb-add sw1 lb1
>  ovn-sbctl dump-flows sw1 | grep ct_lb | grep priority=120 > lflows.txt
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
,20.0.0.3:80);)
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(10.0.0.3:1000
,20.0.0.3:80);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
,20.0.0.3:80);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(backends=
10.0.0.3:1000,20.0.0.3:80);)
>  ])
>
>  # Now create lb2 same as lb1 but udp protocol.
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 7befc8224..055fb57e2 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -970,29 +970,42 @@ ct_lb();
>      encodes as ct(table=19,zone=NXM_NX_REG13[0..15],nat)
>      has prereqs ip
>  ct_lb(192.168.1.2:80, 192.168.1.3:80);
> +    Syntax error at `192.168.1.2' expecting backends.
> +ct_lb(backends=192.168.1.2:80,192.168.1.3:80);
>      encodes as group:1
>      uses group: id(1),
name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=ct(nat(dst=
192.168.1.2:80
),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=
192.168.1.3:80),commit,table=19,zone=NXM_NX_REG13[0..15]))
>      has prereqs ip
> -ct_lb(192.168.1.2, 192.168.1.3, );
> -    formats as ct_lb(192.168.1.2, 192.168.1.3);
> +ct_lb(backends=192.168.1.2, 192.168.1.3, );
> +    formats as ct_lb(backends=192.168.1.2,192.168.1.3);
>      encodes as group:2
>      uses group: id(2),
name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=ct(nat(dst=192.168.1.2),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=192.168.1.3),commit,table=19,zone=NXM_NX_REG13[0..15]))
>      has prereqs ip
> -ct_lb(fd0f::2, fd0f::3, );
> -    formats as ct_lb(fd0f::2, fd0f::3);
> +ct_lb(backends=fd0f::2, fd0f::3, );
> +    formats as ct_lb(backends=fd0f::2,fd0f::3);
>      encodes as group:3
>      uses group: id(3),
name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=ct(nat(dst=fd0f::2),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=fd0f::3),commit,table=19,zone=NXM_NX_REG13[0..15]))
>      has prereqs ip
>
> -ct_lb(192.168.1.2:);
> +ct_lb(backends=192.168.1.2:);
>      Syntax error at `)' expecting port number.
> -ct_lb(192.168.1.2:123456);
> +ct_lb(backends=192.168.1.2:123456);
>      Syntax error at `123456' expecting port number.
> -ct_lb(foo);
> +ct_lb(backends=foo);
>      Syntax error at `foo' expecting IP address.
> -ct_lb([192.168.1.2]);
> +ct_lb(backends=[192.168.1.2]);
>      Syntax error at `192.168.1.2' expecting IPv6 address.
>
> +ct_lb(backends=192.168.1.2:80,192.168.1.3:80;
hash_fields=eth_src,eth_dst,ip_src);
> +    Syntax error at `eth_src' invalid hash_fields.
> +ct_lb(backends=192.168.1.2:80,192.168.1.3:80;
hash_fields="eth_src,eth_dst,ip_src");
> +    encodes as group:4
> +    uses group: id(4),
name(type=select,selection_method=hash,fields(eth_src,eth_dst,ip_src),bucket=bucket_id=0,weight:100,actions=ct(nat(dst=
192.168.1.2:80
),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=
192.168.1.3:80),commit,table=19,zone=NXM_NX_REG13[0..15]))
> +    has prereqs ip
> +ct_lb(backends=fd0f::2,fd0f::3;
hash_fields="eth_src,eth_dst,ip_src,ip_dst,tp_src,tp_dst");
> +    encodes as group:5
> +    uses group: id(5),
name(type=select,selection_method=hash,fields(eth_src,eth_dst,ip_src,ip_dst,tp_src,tp_dst),bucket=bucket_id=0,weight:100,actions=ct(nat(dst=fd0f::2),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=fd0f::3),commit,table=19,zone=NXM_NX_REG13[0..15]))
> +    has prereqs ip
> +
>  # ct_next
>  ct_next;
>      encodes as ct(table=19,zone=NXM_NX_REG13[0..15])
> @@ -1520,13 +1533,13 @@ handle_svc_check(reg0);
>  # select
>  reg9[16..31] = select(1=50, 2=100, 3, );
>      formats as reg9[16..31] = select(1=50, 2=100, 3=100);
> -    encodes as group:4
> -    uses group: id(4),
name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:50,actions=load:1->xreg4[16..31],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xreg4[16..31],resubmit(,19),bucket=bucket_id=2,weight:100,actions=load:3->xreg4[16..31],resubmit(,19))
> +    encodes as group:6
> +    uses group: id(6),
name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:50,actions=load:1->xreg4[16..31],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xreg4[16..31],resubmit(,19),bucket=bucket_id=2,weight:100,actions=load:3->xreg4[16..31],resubmit(,19))
>
>  reg0 = select(1, 2);
>      formats as reg0 = select(1=100, 2=100);
> -    encodes as group:5
> -    uses group: id(5),
name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=load:1->xxreg0[96..127],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xxreg0[96..127],resubmit(,19))
> +    encodes as group:7
> +    uses group: id(7),
name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=load:1->xxreg0[96..127],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xxreg0[96..127],resubmit(,19))
>
>  reg0 = select(1=, 2);
>      Syntax error at `,' expecting weight.
> @@ -1542,12 +1555,12 @@ reg0[0..14] = select(1, 2, 3);
>      cannot use 15-bit field reg0[0..14] for "select", which requires at
least 16 bits.
>
>  fwd_group(liveness="true", childports="eth0", "lsp1");
> -    encodes as group:6
> -    uses group: id(6),
name(type=select,selection_method=dp_hash,bucket=watch_port:5,load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=watch_port:17,load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
> +    encodes as group:8
> +    uses group: id(8),
name(type=select,selection_method=dp_hash,bucket=watch_port:5,load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=watch_port:17,load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
>
>  fwd_group(childports="eth0", "lsp1");
> -    encodes as group:7
> -    uses group: id(7),
name(type=select,selection_method=dp_hash,bucket=load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
> +    encodes as group:9
> +    uses group: id(9),
name(type=select,selection_method=dp_hash,bucket=load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
>
>  fwd_group(childports=eth0);
>      Syntax error at `eth0' expecting logical switch port.
> @@ -18000,12 +18013,12 @@ service_monitor | sed '/^$/d' | wc -l`])
>
>  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
,20.0.0.3:80);)
> +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
,20.0.0.3:80);)
>  ])
>
>  ovn-sbctl dump-flows lr0 | grep ct_lb | grep priority=120 > lflows.txt
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip &&
ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 &&
is_chassis_resident("cr-lr0-public")), action=(ct_lb(10.0.0.3:80,20.0.0.3:80
);)
> +  table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip &&
ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 &&
is_chassis_resident("cr-lr0-public")), action=(ct_lb(backends=10.0.0.3:80
,20.0.0.3:80);)
>  ])
>
>  # get the svc monitor mac.
> diff --git a/tests/system-ovn.at b/tests/system-ovn.at
> index fa3b83cb1..32ae60925 100644
> --- a/tests/system-ovn.at
> +++ b/tests/system-ovn.at
> @@ -1095,15 +1095,15 @@ ovn-nbctl lsp-add bar bar3 \
>  -- lsp-set-addresses bar3 "f0:00:0f:01:02:05 172.16.1.4"
>
>  # Config OVN load-balancer with a VIP.
> -uuid=`ovn-nbctl  create load_balancer
vips:30.0.0.1="172.16.1.2,172.16.1.3,172.16.1.4"`
> -ovn-nbctl set logical_switch foo load_balancer=$uuid
> +ovn-nbctl lb-add lb1 30.0.0.1 "172.16.1.2,172.16.1.3,172.16.1.4"
> +ovn-nbctl ls-lb-add foo lb1
>
>  # Create another load-balancer with another VIP.
> -uuid=`ovn-nbctl create load_balancer
vips:30.0.0.3="172.16.1.2,172.16.1.3,172.16.1.4"`
> -ovn-nbctl add logical_switch foo load_balancer $uuid
> +lb2_uuid=`ovn-nbctl create load_balancer name=lb2
vips:30.0.0.3="172.16.1.2,172.16.1.3,172.16.1.4"`
> +ovn-nbctl ls-lb-add foo lb2
>
>  # Config OVN load-balancer with another VIP (this time with ports).
> -ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"172.16.1.2:80,
172.16.1.3:80,172.16.1.4:80"'
> +ovn-nbctl set load_balancer $lb2_uuid vips:'"30.0.0.2:8000"'='"
172.16.1.2:80,172.16.1.3:80,172.16.1.4:80"'
>
>  # Wait for ovn-controller to catch up.
>  ovn-nbctl --wait=hv sync
> @@ -1157,6 +1157,82 @@
tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(s
>
 tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>  ])
>
> +# Configure selection_fields.
> +ovn-nbctl set load_balancer $lb2_uuid
selection_fields="ip_src,ip_dst,tp_src,tp_dst"
> +OVS_WAIT_UNTIL([
> +    test $(ovs-ofctl dump-groups br-int | \
> +    grep "selection_method=hash,fields(ip_src,ip_dst,tcp_src,tcp_dst)"
-c) -eq 2
> +])
> +
> +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> +
> +dnl Test load-balancing that includes L4 ports in NAT.
> +for i in `seq 1 20`; do
> +    echo Request $i
> +    NS_CHECK_EXEC([foo1], [wget 30.0.0.2:8000 -t 5 -T 1
--retry-connrefused -v -o wget$i.log])
> +done
> +
> +dnl Each server should have at least one connection.
> +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | \
> +sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>
+tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>
+tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>
+tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
> +])
> +
> +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> +
> +echo "foo" > foo
> +for i in `seq 1 20`; do
> +    echo Request $i
> +    ip netns exec foo1 nc -p 30000 30.0.0.2 8000 < foo
> +done
> +
> +dnl Only one backend should be chosen.
> +AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 -c) -eq
1])
> +
> +ovn-nbctl set load_balancer $lb2_uuid selection_fields="ip_src"
> +OVS_WAIT_UNTIL([
> +    test $(ovs-ofctl dump-groups br-int | \
> +    grep "selection_method=hash,fields=ip_src" -c) -eq 2
> +])
> +
> +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> +for i in `seq 1 20`; do
> +    echo Request $i
> +    ip netns exec foo1 nc 30.0.0.2 8000 < foo
> +done
> +
> +dnl Only one backend should be chosen as eth_src and ip_src is fixed.
> +bar1_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep
172.16.1.2 -c)
> +bar2_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep
172.16.1.3 -c)
> +bar3_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep
172.16.1.4 -c)
> +
> +AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep
172.16.1 -c) -ne 0])
> +
> +if [[ "$bar1_ct" == "20" ]]; then
> +    AT_CHECK([test $bar1_ct -eq 20])
> +    AT_CHECK([test $bar2_ct -eq 0])
> +    AT_CHECK([test $bar3_ct -eq 0])
> +else
> +    AT_CHECK([test $bar1_ct -eq 0])
> +fi
> +
> +if [[ "$bar2_ct" == "20" ]]; then
> +    AT_CHECK([test $bar1_ct -eq 20])
> +    AT_CHECK([test $bar2_ct -eq 0])
> +    AT_CHECK([test $bar3_ct -eq 0])
> +else
> +    AT_CHECK([test $bar2_ct -eq 0])
> +fi
> +
> +if [[ "$bar3_ct" == "20" ]]; then
> +    AT_CHECK([test $bar1_ct -eq 20])
> +    AT_CHECK([test $bar2_ct -eq 0])
> +    AT_CHECK([test $bar3_ct -eq 0])
> +else
> +    AT_CHECK([test $bar3_ct -eq 0])
> +fi
>
>  OVS_APP_EXIT_AND_WAIT([ovn-controller])
>
> @@ -1246,11 +1322,11 @@ uuid=`ovn-nbctl  create load_balancer
vips:\"fd03::1\"=\"fd02::2,fd02::3,fd02::4
>  ovn-nbctl set logical_switch foo load_balancer=$uuid
>
>  # Create another load-balancer with another VIP.
> -uuid=`ovn-nbctl create load_balancer
vips:\"fd03::3\"=\"fd02::2,fd02::3,fd02::4\"`
> -ovn-nbctl add logical_switch foo load_balancer $uuid
> +lb2_uuid=`ovn-nbctl create load_balancer
vips:\"fd03::3\"=\"fd02::2,fd02::3,fd02::4\"`
> +ovn-nbctl add logical_switch foo load_balancer $lb2_uuid
>
>  # Config OVN load-balancer with another VIP (this time with ports).
> -ovn-nbctl set load_balancer $uuid
vips:'"[[fd03::2]]:8000"'='"@<:@fd02::2@:>@:80,@<:@fd02::3@
:>@:80,@<:@fd02::4@:>@:80"'
> +ovn-nbctl set load_balancer $lb2_uuid
vips:'"[[fd03::2]]:8000"'='"@<:@fd02::2@:>@:80,@<:@fd02::3@
:>@:80,@<:@fd02::4@:>@:80"'
>
>  # Wait for ovn-controller to catch up.
>  ovn-nbctl --wait=hv sync
> @@ -1304,7 +1380,83 @@
tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd
>
 tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::4,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>  ])
>
> +# Configure selection_fields.
> +ovn-nbctl set load_balancer $lb2_uuid
selection_fields="ip_src,ip_dst,tp_src,tp_dst"
> +OVS_WAIT_UNTIL([
> +    test $(ovs-ofctl dump-groups br-int | \
> +    grep "selection_method=hash,fields(ip_src,ip_dst,tcp_src,tcp_dst)"
-c) -eq 2
> +])
> +
> +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> +
> +dnl Test load-balancing that includes L4 ports in NAT.
> +for i in `seq 1 20`; do
> +    echo Request $i
> +    NS_CHECK_EXEC([foo1], [wget http://[[fd03::2]]:8000 -t 5 -T 1
--retry-connrefused -v -o wget$i.log])
> +done
> +
> +dnl Each server should have at least one connection.
> +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd03::2) | grep -v
fe80 | \
> +sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>
+tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::2,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>
+tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::3,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>
+tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::4,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
> +])
> +
> +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> +
> +echo "foo" > foo
> +for i in `seq 1 20`; do
> +    echo Request $i
> +    ip netns exec foo1 nc -6 -p 30000 fd03::2 8000 < foo
> +done
> +
> +# Only one backend should be chosen. Since the source port is fixed,
> +# there should be only one conntrack entry.
> +AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep fd03::2 -c) -eq
1])
> +
> +ovn-nbctl set load_balancer $lb2_uuid selection_fields="eth_src,ip_src"
> +OVS_WAIT_UNTIL([
> +    test $(ovs-ofctl dump-groups br-int | \
> +    grep "selection_method=hash,fields(eth_src,ip_src)" -c) -eq 2
> +])
> +
> +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> +for i in `seq 1 20`; do
> +    echo Request $i
> +    ip netns exec foo1 nc -6 fd03::2 8000 < foo
> +done
>
> +dnl Only one backend should be chosen as eth_src and ip_src is fixed.
> +bar1_ct=$(ovs-appctl dpctl/dump-conntrack | grep fd03::2 | grep fd02::2
-c)
> +bar2_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep fd02::3
-c)
> +bar3_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep fd02::4
-c)
> +
> +AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep fd03::2 | grep
fd02 -c) -ne 0])
> +
> +if [[ "$bar1_ct" == "20" ]]; then
> +    AT_CHECK([test $bar1_ct -eq 20])
> +    AT_CHECK([test $bar2_ct -eq 0])
> +    AT_CHECK([test $bar3_ct -eq 0])
> +else
> +    AT_CHECK([test $bar1_ct -eq 0])
> +fi
> +
> +if [[ "$bar2_ct" == "20" ]]; then
> +    AT_CHECK([test $bar1_ct -eq 20])
> +    AT_CHECK([test $bar2_ct -eq 0])
> +    AT_CHECK([test $bar3_ct -eq 0])
> +else
> +    AT_CHECK([test $bar2_ct -eq 0])
> +fi
> +
> +if [[ "$bar3_ct" == "20" ]]; then
> +    AT_CHECK([test $bar1_ct -eq 20])
> +    AT_CHECK([test $bar2_ct -eq 0])
> +    AT_CHECK([test $bar3_ct -eq 0])
> +else
> +    AT_CHECK([test $bar3_ct -eq 0])
> +fi
>  OVS_APP_EXIT_AND_WAIT([ovn-controller])
>
>  as ovn-sb
> @@ -3448,7 +3600,7 @@ service_monitor | sed '/^$/d' | grep online | wc
-l`])
>
>  OVS_WAIT_UNTIL(
>      [ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 | grep
"ip4.dst == 10.0.0.10" > lflows.txt
> -     test 1 = `cat lflows.txt | grep "ct_lb(10.0.0.3:80,20.0.0.3:80)" |
wc -l`]
> +     test 1 = `cat lflows.txt | grep "ct_lb(backends=10.0.0.3:80,
20.0.0.3:80)" | wc -l`]
>  )
>
>  # From sw0-p2 send traffic to vip - 10.0.0.10
> @@ -3474,7 +3626,7 @@ service_monitor logical_port=sw0-p1 | sed '/^$/d' |
grep offline | wc -l`])
>
>  OVS_WAIT_UNTIL(
>      [ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 | grep
"ip4.dst == 10.0.0.10" > lflows.txt
> -     test 1 = `cat lflows.txt | grep "ct_lb(20.0.0.3:80)" | wc -l`]
> +     test 1 = `cat lflows.txt | grep "ct_lb(backends=20.0.0.3:80)" | wc
-l`]
>  )
>
>  ovs-appctl dpctl/flush-conntrack
> --
> 2.26.2
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
Numan Siddique May 6, 2020, 6:05 p.m. UTC | #3
On Wed, May 6, 2020 at 11:24 PM Han Zhou <hzhou@ovn.org> wrote:

> On Tue, May 5, 2020 at 11:38 AM <numans@ovn.org> wrote:
> >
> > From: Numan Siddique <numans@ovn.org>
> >
> > This patch add a new column 'selection_fields' in Load_Balancer
> > table in NB DB. CMS can define a set of packet headers to use
> > while selecting a backend. If this column is set, OVN will add the
> > flow in group table with selection method as 'hash' with the set fields.
> > Otherwise it will use the default 'dp_hash' selection method.
> >
> > If a load balancer is configured with the selection_fields as
> > selection_fields    : [ip_dst, ip_src, tp_dst, tp_src]
> >
> > then with this patch, the modified ct_lb action will look like
> >  - ct_lb(backends=IP1:P1,IP2:P1;
> hash_fields="ip_dst,ip_src,tp_dst,tp_src");
> >
> > And the OF flow will look like
> >  - group_id=2,type=select,selection_method=hash,
> >
>
>  fields(ip_src,ip_dst,tcp_src,tcp_dst),bucket=bucket_id:0,weight:100,actions=ct(....
> >
> > Signed-off-by: Numan Siddique <numans@ovn.org>
> > ---
> > v1 -> v2
> >  * The series has only 1 patch now as the first patch is merged.
> >  * Addressed review comments from Mark and Han.
> >  * Added test cases.
> >  * Added documentation.
> >
> >  NEWS                  |   3 +
> >  include/ovn/actions.h |   1 +
> >  lib/actions.c         |  45 +++++++++--
> >  northd/ovn-northd.c   |  30 ++++++--
> >  ovn-nb.ovsschema      |  10 ++-
> >  ovn-nb.xml            |  27 +++++++
> >  tests/ovn-northd.at   |  24 +++---
> >  tests/ovn.at          |  49 +++++++-----
> >  tests/system-ovn.at   | 172 +++++++++++++++++++++++++++++++++++++++---
> >  9 files changed, 308 insertions(+), 53 deletions(-)
> >
> > diff --git a/NEWS b/NEWS
> > index c2b7945df..431b4cf9c 100644
> > --- a/NEWS
> > +++ b/NEWS
> > @@ -14,6 +14,9 @@ OVN v20.03.0 - xx xxx xxxx
> >     - Added support for ECMP routes in OVN router.
> >     - Added IPv6 Prefix Delegation support in OVN.
> >     - OVN now uses OpenFlow 1.5.
> > +   - Added support to choose selection methods - dp_hash or
> > +     hash (with specified hash fields) for OVN load balancer
> > +     backend selection.
>
> Thanks for the update here. Shall we call out this is incompatible with
> older version to warn users that upgrade must be taken care specially? With
> this addressed:
> Acked-by: Han Zhou <hzhou@ovn.org>
>
>
Thanks for the review.

I'm not clear on how this will cause incompatibility issues ? If a user has
created
few load balancers before upgrade, it should work the same after the upgrade
too right ? In this case, the selection_fields column will be empty and
ovn-northd will
use selection_method as "dp_hash" which will be the case before the upgrade
too right ?

Thanks
Numan



>
> >     - OVN Interconnection:
> >       * Support for L3 interconnection of multiple OVN deployments with
> tunnels
> > diff --git a/include/ovn/actions.h b/include/ovn/actions.h
> > index 42d45a74c..4a54abe17 100644
> > --- a/include/ovn/actions.h
> > +++ b/include/ovn/actions.h
> > @@ -259,6 +259,7 @@ struct ovnact_ct_lb {
> >      struct ovnact_ct_lb_dst *dsts;
> >      size_t n_dsts;
> >      uint8_t ltable;             /* Logical table ID of next table. */
> > +    char *hash_fields;
> >  };
> >
> >  struct ovnact_select_dst {
> > diff --git a/lib/actions.c b/lib/actions.c
> > index 021a376b4..c50615177 100644
> > --- a/lib/actions.c
> > +++ b/lib/actions.c
> > @@ -951,9 +951,18 @@ parse_ct_lb_action(struct action_context *ctx)
> >      struct ovnact_ct_lb_dst *dsts = NULL;
> >      size_t allocated_dsts = 0;
> >      size_t n_dsts = 0;
> > +    char *hash_fields = NULL;
> >
> > -    if (lexer_match(ctx->lexer, LEX_T_LPAREN)) {
> > -        while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
> > +    if (lexer_match(ctx->lexer, LEX_T_LPAREN) &&
> > +        !lexer_match(ctx->lexer, LEX_T_RPAREN)) {
> > +        if (!lexer_match_id(ctx->lexer, "backends") ||
> > +            !lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
> > +            lexer_syntax_error(ctx->lexer, "expecting backends");
> > +            return;
> > +        }
> > +
> > +        while (!lexer_match(ctx->lexer, LEX_T_SEMICOLON) &&
> > +               !lexer_match(ctx->lexer, LEX_T_RPAREN)) {
> >              struct ovnact_ct_lb_dst dst;
> >              if (lexer_match(ctx->lexer, LEX_T_LSQUARE)) {
> >                  /* IPv6 address and port */
> > @@ -1020,12 +1029,27 @@ parse_ct_lb_action(struct action_context *ctx)
> >              }
> >              dsts[n_dsts++] = dst;
> >          }
> > +
> > +        if (lexer_match_id(ctx->lexer, "hash_fields")) {
> > +            if (!lexer_match(ctx->lexer, LEX_T_EQUALS) ||
> > +                ctx->lexer->token.type != LEX_T_STRING ||
> > +                lexer_lookahead(ctx->lexer) != LEX_T_RPAREN) {
> > +                lexer_syntax_error(ctx->lexer, "invalid hash_fields");
> > +                free(dsts);
> > +                return;
> > +            }
> > +
> > +            hash_fields = xstrdup(ctx->lexer->token.s);
> > +            lexer_get(ctx->lexer);
> > +            lexer_get(ctx->lexer);
> > +        }
> >      }
> >
> >      struct ovnact_ct_lb *cl = ovnact_put_CT_LB(ctx->ovnacts);
> >      cl->ltable = ctx->pp->cur_ltable + 1;
> >      cl->dsts = dsts;
> >      cl->n_dsts = n_dsts;
> > +    cl->hash_fields = hash_fields;
> >  }
> >
> >  static void
> > @@ -1033,10 +1057,10 @@ format_CT_LB(const struct ovnact_ct_lb *cl,
> struct ds *s)
> >  {
> >      ds_put_cstr(s, "ct_lb");
> >      if (cl->n_dsts) {
> > -        ds_put_char(s, '(');
> > +        ds_put_cstr(s, "(backends=");
> >          for (size_t i = 0; i < cl->n_dsts; i++) {
> >              if (i) {
> > -                ds_put_cstr(s, ", ");
> > +                ds_put_char(s, ',');
> >              }
> >
> >              const struct ovnact_ct_lb_dst *dst = &cl->dsts[i];
> > @@ -1056,7 +1080,13 @@ format_CT_LB(const struct ovnact_ct_lb *cl, struct
> ds *s)
> >              }
> >          }
> >          ds_put_char(s, ')');
> > +
> > +        if (cl->hash_fields) {
> > +            ds_chomp(s, ')');
> > +            ds_put_format(s, "; hash_fields=\"%s\")", cl->hash_fields);
> > +        }
> >      }
> > +
> >      ds_put_char(s, ';');
> >  }
> >
> > @@ -1103,7 +1133,11 @@ encode_CT_LB(const struct ovnact_ct_lb *cl,
> >                              : MFF_LOG_DNAT_ZONE - MFF_REG0;
> >
> >      struct ds ds = DS_EMPTY_INITIALIZER;
> > -    ds_put_format(&ds, "type=select,selection_method=dp_hash");
> > +    ds_put_format(&ds, "type=select,selection_method=%s",
> > +                  cl->hash_fields ? "hash": "dp_hash");
> > +    if (cl->hash_fields) {
> > +        ds_put_format(&ds, ",fields(%s)", cl->hash_fields);
> > +    }
> >
> >      BUILD_ASSERT(MFF_LOG_CT_ZONE >= MFF_REG0);
> >      BUILD_ASSERT(MFF_LOG_CT_ZONE < MFF_REG0 + FLOW_N_REGS);
> > @@ -1145,6 +1179,7 @@ static void
> >  ovnact_ct_lb_free(struct ovnact_ct_lb *ct_lb)
> >  {
> >      free(ct_lb->dsts);
> > +    free(ct_lb->hash_fields);
> >  }
> >
> >  static void
> > diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> > index 3f03e418a..d98d3a6b6 100644
> > --- a/northd/ovn-northd.c
> > +++ b/northd/ovn-northd.c
> > @@ -3112,7 +3112,7 @@ struct ovn_lb {
> >      struct hmap_node hmap_node;
> >
> >      const struct nbrec_load_balancer *nlb; /* May be NULL. */
> > -
> > +    char *selection_fields;
> >      struct lb_vip *vips;
> >      size_t n_vips;
> >  };
> > @@ -3340,6 +3340,15 @@ ovn_lb_create(struct northd_context *ctx, struct
> hmap *lbs,
> >          n_vips++;
> >      }
> >
> > +    if (lb->nlb->n_selection_fields) {
> > +        struct ds sel_fields = DS_EMPTY_INITIALIZER;
> > +        for (size_t i = 0; i < lb->nlb->n_selection_fields; i++) {
> > +            ds_put_format(&sel_fields, "%s,",
> lb->nlb->selection_fields[i]);
> > +        }
> > +        ds_chomp(&sel_fields, ',');
> > +        lb->selection_fields = ds_steal_cstr(&sel_fields);
> > +    }
> > +
> >      return lb;
> >  }
> >
> > @@ -3358,13 +3367,15 @@ ovn_lb_destroy(struct ovn_lb *lb)
> >          free(lb->vips[i].backends);
> >      }
> >      free(lb->vips);
> > +    free(lb->selection_fields);
> >  }
> >
> >  static void build_lb_vip_ct_lb_actions(struct lb_vip *lb_vip,
> > -                                       struct ds *action)
> > +                                       struct ds *action,
> > +                                       char *selection_fields)
> >  {
> >      if (lb_vip->health_check) {
> > -        ds_put_cstr(action, "ct_lb(");
> > +        ds_put_cstr(action, "ct_lb(backends=");
> >
> >          size_t n_active_backends = 0;
> >          for (size_t k = 0; k < lb_vip->n_backends; k++) {
> > @@ -3388,7 +3399,13 @@ static void build_lb_vip_ct_lb_actions(struct
> lb_vip *lb_vip,
> >              ds_put_cstr(action, ");");
> >          }
> >      } else {
> > -        ds_put_format(action, "ct_lb(%s);", lb_vip->backend_ips);
> > +        ds_put_format(action, "ct_lb(backends=%s);",
> lb_vip->backend_ips);
> > +    }
> > +
> > +    if (selection_fields && selection_fields[0]) {
> > +        ds_chomp(action, ';');
> > +        ds_chomp(action, ')');
> > +        ds_put_format(action, "; hash_fields=\"%s\");",
> selection_fields);
> >      }
> >  }
> >
> > @@ -5666,7 +5683,7 @@ build_lb_rules(struct ovn_datapath *od, struct hmap
> *lflows, struct ovn_lb *lb)
> >
> >          /* New connections in Ingress table. */
> >          struct ds action = DS_EMPTY_INITIALIZER;
> > -        build_lb_vip_ct_lb_actions(lb_vip, &action);
> > +        build_lb_vip_ct_lb_actions(lb_vip, &action,
> lb->selection_fields);
> >
> >          struct ds match = DS_EMPTY_INITIALIZER;
> >          ds_put_format(&match, "ct.new && %s.dst == %s", ip_match,
> lb_vip->vip);
> > @@ -9317,7 +9334,8 @@ build_lrouter_flows(struct hmap *datapaths, struct
> hmap *ports,
> >              for (size_t j = 0; j < lb->n_vips; j++) {
> >                  struct lb_vip *lb_vip = &lb->vips[j];
> >                  ds_clear(&actions);
> > -                build_lb_vip_ct_lb_actions(lb_vip, &actions);
> > +                build_lb_vip_ct_lb_actions(lb_vip, &actions,
> > +                                           lb->selection_fields);
> >
> >                  if (!sset_contains(&all_ips, lb_vip->vip)) {
> >                      sset_add(&all_ips, lb_vip->vip);
> > diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
> > index b2a046571..a06972aa0 100644
> > --- a/ovn-nb.ovsschema
> > +++ b/ovn-nb.ovsschema
> > @@ -1,7 +1,7 @@
> >  {
> >      "name": "OVN_Northbound",
> > -    "version": "5.22.0",
> > -    "cksum": "384164739 25476",
> > +    "version": "5.23.0",
> > +    "cksum": "111023208 25806",
> >      "tables": {
> >          "NB_Global": {
> >              "columns": {
> > @@ -179,6 +179,12 @@
> >                  "ip_port_mappings": {
> >                      "type": {"key": "string", "value": "string",
> >                               "min": 0, "max": "unlimited"}},
> > +                "selection_fields": {
> > +                    "type": {"key": {"type": "string",
> > +                             "enum": ["set",
> > +                                ["eth_src", "eth_dst", "ip_src",
> "ip_dst",
> > +                                 "tp_src", "tp_dst"]]},
> > +                             "min": 0, "max": "unlimited"}},
> >                  "external_ids": {
> >                      "type": {"key": "string", "value": "string",
> >                               "min": 0, "max": "unlimited"}}},
> > diff --git a/ovn-nb.xml b/ovn-nb.xml
> > index af15c550a..95ee4c9e6 100644
> > --- a/ovn-nb.xml
> > +++ b/ovn-nb.xml
> > @@ -1513,6 +1513,33 @@
> >        </p>
> >      </column>
> >
> > +    <column name="selection_fields">
> > +      <p>
> > +        OVN native load balancers are supported using the OpenFlow
> groups
> > +        of type <code>select</code>. OVS supports two selection methods:
> > +        <code>dp_hash</code> and <code>hash (with optional fields
> > +        specified)</code> in selecting the buckets of a group.
> > +        Please see the OVS documentation (man ovs-ofctl)
> > +        for more details on the selection methods. Each endpoint IP (and
> port
> > +        if set) is mapped to a bucket in the group flow.
> > +      </p>
> > +
> > +      <p>
> > +        CMS can choose the <code>hash</code> selection method by setting
> the
> > +        selection fields in this column. <code>ovs-vswitchd</code> uses
> the
> > +        specified fields in generating the hash.
> > +      </p>
> > +
> > +      <p>
> > +        <code>dp_hash</code> selection method uses the assistance of
> > +        datapath to calculate the hash and it is expected to be
> > +        faster than <code>hash</code> selection method. So CMS should
> take
> > +        this into consideration before using the <code>hash</code>
> method.
> > +        Please consult the OVS documentation and OVS sources for the
> > +        implementation details.
> > +      </p>
> > +    </column>
> > +
> >      <group title="Common Columns">
> >        <column name="external_ids">
> >          See <em>External IDs</em> at the beginning of this document.
> > diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> > index a0109ae94..57c9eedd5 100644
> > --- a/tests/ovn-northd.at
> > +++ b/tests/ovn-northd.at
> > @@ -1183,7 +1183,7 @@ ovn-nbctl --wait=sb ls-lb-add sw0 lb1
> >
> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
> >  AT_CHECK([cat lflows.txt], [0], [dnl
> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
> ,20.0.0.3:80);)
> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
> ,20.0.0.3:80);)
> >  ])
> >
> >  # Delete the Load_Balancer_Health_Check
> > @@ -1192,7 +1192,7 @@ OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list
> service_monitor |  wc -l`])
> >
> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
> >  AT_CHECK([cat lflows.txt], [0], [dnl
> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
> ,20.0.0.3:80);)
> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
> ,20.0.0.3:80);)
> >  ])
> >
> >  # Create the Load_Balancer_Health_Check again.
> > @@ -1205,7 +1205,7 @@ service_monitor | sed '/^$/d' | wc -l`])
> >
> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
> >  AT_CHECK([cat lflows.txt], [0], [dnl
> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
> ,20.0.0.3:80);)
> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
> ,20.0.0.3:80);)
> >  ])
> >
> >  # Get the uuid of both the service_monitor
> > @@ -1221,7 +1221,7 @@ OVS_WAIT_UNTIL([
> >
> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
> >  AT_CHECK([cat lflows.txt], [0], [dnl
> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80);)
> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
> );)
> >  ])
> >
> >  # Set the service monitor for sw0-p1 to offline
> > @@ -1251,7 +1251,7 @@ OVS_WAIT_UNTIL([
> >
> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
> >  AT_CHECK([cat lflows.txt], [0], [dnl
> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
> ,20.0.0.3:80);)
> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
> ,20.0.0.3:80);)
> >  ])
> >
> >  # Set the service monitor for sw1-p1 to error
> > @@ -1263,7 +1263,7 @@ OVS_WAIT_UNTIL([
> >  ovn-sbctl dump-flows sw0 | grep "ip4.dst == 10.0.0.10 && tcp.dst == 80"
> \
> >  | grep priority=120 > lflows.txt
> >  AT_CHECK([cat lflows.txt], [0], [dnl
> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80);)
> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
> );)
> >  ])
> >
> >  # Add one more vip to lb1
> > @@ -1293,8 +1293,8 @@ service_monitor port=1000 | sed '/^$/d' | wc -l`])
> >
> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
> >  AT_CHECK([cat lflows.txt], [0], [dnl
> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80);)
> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(10.0.0.3:1000);)
> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
> );)
> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.40 && tcp.dst == 1000),
> action=(ct_lb(backends=10.0.0.3:1000);)
> >  ])
> >
> >  # Set the service monitor for sw1-p1 to online
> > @@ -1306,16 +1306,16 @@ OVS_WAIT_UNTIL([
> >
> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
> >  AT_CHECK([cat lflows.txt], [0], [dnl
> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
> ,20.0.0.3:80);)
> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(10.0.0.3:1000
> ,20.0.0.3:80);)
> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
> ,20.0.0.3:80);)
> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(backends=
> 10.0.0.3:1000,20.0.0.3:80);)
> >  ])
> >
> >  # Associate lb1 to sw1
> >  ovn-nbctl --wait=sb ls-lb-add sw1 lb1
> >  ovn-sbctl dump-flows sw1 | grep ct_lb | grep priority=120 > lflows.txt
> >  AT_CHECK([cat lflows.txt], [0], [dnl
> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
> ,20.0.0.3:80);)
> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(10.0.0.3:1000
> ,20.0.0.3:80);)
> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
> ,20.0.0.3:80);)
> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(backends=
> 10.0.0.3:1000,20.0.0.3:80);)
> >  ])
> >
> >  # Now create lb2 same as lb1 but udp protocol.
> > diff --git a/tests/ovn.at b/tests/ovn.at
> > index 7befc8224..055fb57e2 100644
> > --- a/tests/ovn.at
> > +++ b/tests/ovn.at
> > @@ -970,29 +970,42 @@ ct_lb();
> >      encodes as ct(table=19,zone=NXM_NX_REG13[0..15],nat)
> >      has prereqs ip
> >  ct_lb(192.168.1.2:80, 192.168.1.3:80);
> > +    Syntax error at `192.168.1.2' expecting backends.
> > +ct_lb(backends=192.168.1.2:80,192.168.1.3:80);
> >      encodes as group:1
> >      uses group: id(1),
>
> name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=ct(nat(dst=
> 192.168.1.2:80
>
> ),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=
> 192.168.1.3:80),commit,table=19,zone=NXM_NX_REG13[0..15]))
> >      has prereqs ip
> > -ct_lb(192.168.1.2, 192.168.1.3, );
> > -    formats as ct_lb(192.168.1.2, 192.168.1.3);
> > +ct_lb(backends=192.168.1.2, 192.168.1.3, );
> > +    formats as ct_lb(backends=192.168.1.2,192.168.1.3);
> >      encodes as group:2
> >      uses group: id(2),
>
> name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=ct(nat(dst=192.168.1.2),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=192.168.1.3),commit,table=19,zone=NXM_NX_REG13[0..15]))
> >      has prereqs ip
> > -ct_lb(fd0f::2, fd0f::3, );
> > -    formats as ct_lb(fd0f::2, fd0f::3);
> > +ct_lb(backends=fd0f::2, fd0f::3, );
> > +    formats as ct_lb(backends=fd0f::2,fd0f::3);
> >      encodes as group:3
> >      uses group: id(3),
>
> name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=ct(nat(dst=fd0f::2),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=fd0f::3),commit,table=19,zone=NXM_NX_REG13[0..15]))
> >      has prereqs ip
> >
> > -ct_lb(192.168.1.2:);
> > +ct_lb(backends=192.168.1.2:);
> >      Syntax error at `)' expecting port number.
> > -ct_lb(192.168.1.2:123456);
> > +ct_lb(backends=192.168.1.2:123456);
> >      Syntax error at `123456' expecting port number.
> > -ct_lb(foo);
> > +ct_lb(backends=foo);
> >      Syntax error at `foo' expecting IP address.
> > -ct_lb([192.168.1.2]);
> > +ct_lb(backends=[192.168.1.2]);
> >      Syntax error at `192.168.1.2' expecting IPv6 address.
> >
> > +ct_lb(backends=192.168.1.2:80,192.168.1.3:80;
> hash_fields=eth_src,eth_dst,ip_src);
> > +    Syntax error at `eth_src' invalid hash_fields.
> > +ct_lb(backends=192.168.1.2:80,192.168.1.3:80;
> hash_fields="eth_src,eth_dst,ip_src");
> > +    encodes as group:4
> > +    uses group: id(4),
>
> name(type=select,selection_method=hash,fields(eth_src,eth_dst,ip_src),bucket=bucket_id=0,weight:100,actions=ct(nat(dst=
> 192.168.1.2:80
>
> ),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=
> 192.168.1.3:80),commit,table=19,zone=NXM_NX_REG13[0..15]))
> > +    has prereqs ip
> > +ct_lb(backends=fd0f::2,fd0f::3;
> hash_fields="eth_src,eth_dst,ip_src,ip_dst,tp_src,tp_dst");
> > +    encodes as group:5
> > +    uses group: id(5),
>
> name(type=select,selection_method=hash,fields(eth_src,eth_dst,ip_src,ip_dst,tp_src,tp_dst),bucket=bucket_id=0,weight:100,actions=ct(nat(dst=fd0f::2),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=fd0f::3),commit,table=19,zone=NXM_NX_REG13[0..15]))
> > +    has prereqs ip
> > +
> >  # ct_next
> >  ct_next;
> >      encodes as ct(table=19,zone=NXM_NX_REG13[0..15])
> > @@ -1520,13 +1533,13 @@ handle_svc_check(reg0);
> >  # select
> >  reg9[16..31] = select(1=50, 2=100, 3, );
> >      formats as reg9[16..31] = select(1=50, 2=100, 3=100);
> > -    encodes as group:4
> > -    uses group: id(4),
>
> name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:50,actions=load:1->xreg4[16..31],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xreg4[16..31],resubmit(,19),bucket=bucket_id=2,weight:100,actions=load:3->xreg4[16..31],resubmit(,19))
> > +    encodes as group:6
> > +    uses group: id(6),
>
> name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:50,actions=load:1->xreg4[16..31],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xreg4[16..31],resubmit(,19),bucket=bucket_id=2,weight:100,actions=load:3->xreg4[16..31],resubmit(,19))
> >
> >  reg0 = select(1, 2);
> >      formats as reg0 = select(1=100, 2=100);
> > -    encodes as group:5
> > -    uses group: id(5),
>
> name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=load:1->xxreg0[96..127],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xxreg0[96..127],resubmit(,19))
> > +    encodes as group:7
> > +    uses group: id(7),
>
> name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=load:1->xxreg0[96..127],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xxreg0[96..127],resubmit(,19))
> >
> >  reg0 = select(1=, 2);
> >      Syntax error at `,' expecting weight.
> > @@ -1542,12 +1555,12 @@ reg0[0..14] = select(1, 2, 3);
> >      cannot use 15-bit field reg0[0..14] for "select", which requires at
> least 16 bits.
> >
> >  fwd_group(liveness="true", childports="eth0", "lsp1");
> > -    encodes as group:6
> > -    uses group: id(6),
>
> name(type=select,selection_method=dp_hash,bucket=watch_port:5,load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=watch_port:17,load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
> > +    encodes as group:8
> > +    uses group: id(8),
>
> name(type=select,selection_method=dp_hash,bucket=watch_port:5,load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=watch_port:17,load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
> >
> >  fwd_group(childports="eth0", "lsp1");
> > -    encodes as group:7
> > -    uses group: id(7),
>
> name(type=select,selection_method=dp_hash,bucket=load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
> > +    encodes as group:9
> > +    uses group: id(9),
>
> name(type=select,selection_method=dp_hash,bucket=load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
> >
> >  fwd_group(childports=eth0);
> >      Syntax error at `eth0' expecting logical switch port.
> > @@ -18000,12 +18013,12 @@ service_monitor | sed '/^$/d' | wc -l`])
> >
> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
> >  AT_CHECK([cat lflows.txt], [0], [dnl
> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
> ,20.0.0.3:80);)
> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80
> ,20.0.0.3:80);)
> >  ])
> >
> >  ovn-sbctl dump-flows lr0 | grep ct_lb | grep priority=120 > lflows.txt
> >  AT_CHECK([cat lflows.txt], [0], [dnl
> > -  table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip &&
> ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 &&
> is_chassis_resident("cr-lr0-public")), action=(ct_lb(10.0.0.3:80,
> 20.0.0.3:80
> );)
> > +  table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip &&
> ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 &&
> is_chassis_resident("cr-lr0-public")), action=(ct_lb(backends=10.0.0.3:80
> ,20.0.0.3:80);)
> >  ])
> >
> >  # get the svc monitor mac.
> > diff --git a/tests/system-ovn.at b/tests/system-ovn.at
> > index fa3b83cb1..32ae60925 100644
> > --- a/tests/system-ovn.at
> > +++ b/tests/system-ovn.at
> > @@ -1095,15 +1095,15 @@ ovn-nbctl lsp-add bar bar3 \
> >  -- lsp-set-addresses bar3 "f0:00:0f:01:02:05 172.16.1.4"
> >
> >  # Config OVN load-balancer with a VIP.
> > -uuid=`ovn-nbctl  create load_balancer
> vips:30.0.0.1="172.16.1.2,172.16.1.3,172.16.1.4"`
> > -ovn-nbctl set logical_switch foo load_balancer=$uuid
> > +ovn-nbctl lb-add lb1 30.0.0.1 "172.16.1.2,172.16.1.3,172.16.1.4"
> > +ovn-nbctl ls-lb-add foo lb1
> >
> >  # Create another load-balancer with another VIP.
> > -uuid=`ovn-nbctl create load_balancer
> vips:30.0.0.3="172.16.1.2,172.16.1.3,172.16.1.4"`
> > -ovn-nbctl add logical_switch foo load_balancer $uuid
> > +lb2_uuid=`ovn-nbctl create load_balancer name=lb2
> vips:30.0.0.3="172.16.1.2,172.16.1.3,172.16.1.4"`
> > +ovn-nbctl ls-lb-add foo lb2
> >
> >  # Config OVN load-balancer with another VIP (this time with ports).
> > -ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"
> 172.16.1.2:80,
> 172.16.1.3:80,172.16.1.4:80"'
> > +ovn-nbctl set load_balancer $lb2_uuid vips:'"30.0.0.2:8000"'='"
> 172.16.1.2:80,172.16.1.3:80,172.16.1.4:80"'
> >
> >  # Wait for ovn-controller to catch up.
> >  ovn-nbctl --wait=hv sync
> > @@ -1157,6 +1157,82 @@
>
> tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(s
> >
>
>  tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
> >  ])
> >
> > +# Configure selection_fields.
> > +ovn-nbctl set load_balancer $lb2_uuid
> selection_fields="ip_src,ip_dst,tp_src,tp_dst"
> > +OVS_WAIT_UNTIL([
> > +    test $(ovs-ofctl dump-groups br-int | \
> > +    grep "selection_method=hash,fields(ip_src,ip_dst,tcp_src,tcp_dst)"
> -c) -eq 2
> > +])
> > +
> > +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> > +
> > +dnl Test load-balancing that includes L4 ports in NAT.
> > +for i in `seq 1 20`; do
> > +    echo Request $i
> > +    NS_CHECK_EXEC([foo1], [wget 30.0.0.2:8000 -t 5 -T 1
> --retry-connrefused -v -o wget$i.log])
> > +done
> > +
> > +dnl Each server should have at least one connection.
> > +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | \
> > +sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
> >
>
> +tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
> >
>
> +tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
> >
>
> +tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
> > +])
> > +
> > +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> > +
> > +echo "foo" > foo
> > +for i in `seq 1 20`; do
> > +    echo Request $i
> > +    ip netns exec foo1 nc -p 30000 30.0.0.2 8000 < foo
> > +done
> > +
> > +dnl Only one backend should be chosen.
> > +AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 -c) -eq
> 1])
> > +
> > +ovn-nbctl set load_balancer $lb2_uuid selection_fields="ip_src"
> > +OVS_WAIT_UNTIL([
> > +    test $(ovs-ofctl dump-groups br-int | \
> > +    grep "selection_method=hash,fields=ip_src" -c) -eq 2
> > +])
> > +
> > +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> > +for i in `seq 1 20`; do
> > +    echo Request $i
> > +    ip netns exec foo1 nc 30.0.0.2 8000 < foo
> > +done
> > +
> > +dnl Only one backend should be chosen as eth_src and ip_src is fixed.
> > +bar1_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep
> 172.16.1.2 -c)
> > +bar2_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep
> 172.16.1.3 -c)
> > +bar3_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep
> 172.16.1.4 -c)
> > +
> > +AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep
> 172.16.1 -c) -ne 0])
> > +
> > +if [[ "$bar1_ct" == "20" ]]; then
> > +    AT_CHECK([test $bar1_ct -eq 20])
> > +    AT_CHECK([test $bar2_ct -eq 0])
> > +    AT_CHECK([test $bar3_ct -eq 0])
> > +else
> > +    AT_CHECK([test $bar1_ct -eq 0])
> > +fi
> > +
> > +if [[ "$bar2_ct" == "20" ]]; then
> > +    AT_CHECK([test $bar1_ct -eq 20])
> > +    AT_CHECK([test $bar2_ct -eq 0])
> > +    AT_CHECK([test $bar3_ct -eq 0])
> > +else
> > +    AT_CHECK([test $bar2_ct -eq 0])
> > +fi
> > +
> > +if [[ "$bar3_ct" == "20" ]]; then
> > +    AT_CHECK([test $bar1_ct -eq 20])
> > +    AT_CHECK([test $bar2_ct -eq 0])
> > +    AT_CHECK([test $bar3_ct -eq 0])
> > +else
> > +    AT_CHECK([test $bar3_ct -eq 0])
> > +fi
> >
> >  OVS_APP_EXIT_AND_WAIT([ovn-controller])
> >
> > @@ -1246,11 +1322,11 @@ uuid=`ovn-nbctl  create load_balancer
> vips:\"fd03::1\"=\"fd02::2,fd02::3,fd02::4
> >  ovn-nbctl set logical_switch foo load_balancer=$uuid
> >
> >  # Create another load-balancer with another VIP.
> > -uuid=`ovn-nbctl create load_balancer
> vips:\"fd03::3\"=\"fd02::2,fd02::3,fd02::4\"`
> > -ovn-nbctl add logical_switch foo load_balancer $uuid
> > +lb2_uuid=`ovn-nbctl create load_balancer
> vips:\"fd03::3\"=\"fd02::2,fd02::3,fd02::4\"`
> > +ovn-nbctl add logical_switch foo load_balancer $lb2_uuid
> >
> >  # Config OVN load-balancer with another VIP (this time with ports).
> > -ovn-nbctl set load_balancer $uuid
> vips:'"[[fd03::2]]:8000"'='"@<:@fd02::2@:>@:80,@<:@fd02::3@
> :>@:80,@<:@fd02::4@:>@:80"'
> > +ovn-nbctl set load_balancer $lb2_uuid
> vips:'"[[fd03::2]]:8000"'='"@<:@fd02::2@:>@:80,@<:@fd02::3@
> :>@:80,@<:@fd02::4@:>@:80"'
> >
> >  # Wait for ovn-controller to catch up.
> >  ovn-nbctl --wait=hv sync
> > @@ -1304,7 +1380,83 @@
>
> tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd
> >
>
>  tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::4,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
> >  ])
> >
> > +# Configure selection_fields.
> > +ovn-nbctl set load_balancer $lb2_uuid
> selection_fields="ip_src,ip_dst,tp_src,tp_dst"
> > +OVS_WAIT_UNTIL([
> > +    test $(ovs-ofctl dump-groups br-int | \
> > +    grep "selection_method=hash,fields(ip_src,ip_dst,tcp_src,tcp_dst)"
> -c) -eq 2
> > +])
> > +
> > +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> > +
> > +dnl Test load-balancing that includes L4 ports in NAT.
> > +for i in `seq 1 20`; do
> > +    echo Request $i
> > +    NS_CHECK_EXEC([foo1], [wget http://[[fd03::2]]:8000 -t 5 -T 1
> --retry-connrefused -v -o wget$i.log])
> > +done
> > +
> > +dnl Each server should have at least one connection.
> > +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd03::2) | grep -v
> fe80 | \
> > +sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
> >
>
> +tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::2,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
> >
>
> +tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::3,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
> >
>
> +tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::4,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
> > +])
> > +
> > +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> > +
> > +echo "foo" > foo
> > +for i in `seq 1 20`; do
> > +    echo Request $i
> > +    ip netns exec foo1 nc -6 -p 30000 fd03::2 8000 < foo
> > +done
> > +
> > +# Only one backend should be chosen. Since the source port is fixed,
> > +# there should be only one conntrack entry.
> > +AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep fd03::2 -c) -eq
> 1])
> > +
> > +ovn-nbctl set load_balancer $lb2_uuid selection_fields="eth_src,ip_src"
> > +OVS_WAIT_UNTIL([
> > +    test $(ovs-ofctl dump-groups br-int | \
> > +    grep "selection_method=hash,fields(eth_src,ip_src)" -c) -eq 2
> > +])
> > +
> > +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> > +for i in `seq 1 20`; do
> > +    echo Request $i
> > +    ip netns exec foo1 nc -6 fd03::2 8000 < foo
> > +done
> >
> > +dnl Only one backend should be chosen as eth_src and ip_src is fixed.
> > +bar1_ct=$(ovs-appctl dpctl/dump-conntrack | grep fd03::2 | grep fd02::2
> -c)
> > +bar2_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep fd02::3
> -c)
> > +bar3_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep fd02::4
> -c)
> > +
> > +AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep fd03::2 | grep
> fd02 -c) -ne 0])
> > +
> > +if [[ "$bar1_ct" == "20" ]]; then
> > +    AT_CHECK([test $bar1_ct -eq 20])
> > +    AT_CHECK([test $bar2_ct -eq 0])
> > +    AT_CHECK([test $bar3_ct -eq 0])
> > +else
> > +    AT_CHECK([test $bar1_ct -eq 0])
> > +fi
> > +
> > +if [[ "$bar2_ct" == "20" ]]; then
> > +    AT_CHECK([test $bar1_ct -eq 20])
> > +    AT_CHECK([test $bar2_ct -eq 0])
> > +    AT_CHECK([test $bar3_ct -eq 0])
> > +else
> > +    AT_CHECK([test $bar2_ct -eq 0])
> > +fi
> > +
> > +if [[ "$bar3_ct" == "20" ]]; then
> > +    AT_CHECK([test $bar1_ct -eq 20])
> > +    AT_CHECK([test $bar2_ct -eq 0])
> > +    AT_CHECK([test $bar3_ct -eq 0])
> > +else
> > +    AT_CHECK([test $bar3_ct -eq 0])
> > +fi
> >  OVS_APP_EXIT_AND_WAIT([ovn-controller])
> >
> >  as ovn-sb
> > @@ -3448,7 +3600,7 @@ service_monitor | sed '/^$/d' | grep online | wc
> -l`])
> >
> >  OVS_WAIT_UNTIL(
> >      [ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 | grep
> "ip4.dst == 10.0.0.10" > lflows.txt
> > -     test 1 = `cat lflows.txt | grep "ct_lb(10.0.0.3:80,20.0.0.3:80)" |
> wc -l`]
> > +     test 1 = `cat lflows.txt | grep "ct_lb(backends=10.0.0.3:80,
> 20.0.0.3:80)" | wc -l`]
> >  )
> >
> >  # From sw0-p2 send traffic to vip - 10.0.0.10
> > @@ -3474,7 +3626,7 @@ service_monitor logical_port=sw0-p1 | sed '/^$/d' |
> grep offline | wc -l`])
> >
> >  OVS_WAIT_UNTIL(
> >      [ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 | grep
> "ip4.dst == 10.0.0.10" > lflows.txt
> > -     test 1 = `cat lflows.txt | grep "ct_lb(20.0.0.3:80)" | wc -l`]
> > +     test 1 = `cat lflows.txt | grep "ct_lb(backends=20.0.0.3:80)" | wc
> -l`]
> >  )
> >
> >  ovs-appctl dpctl/flush-conntrack
> > --
> > 2.26.2
> >
> > _______________________________________________
> > 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
>
>
Han Zhou May 6, 2020, 6:16 p.m. UTC | #4
On Wed, May 6, 2020 at 11:05 AM Numan Siddique <numans@ovn.org> wrote:
>
>
>
> On Wed, May 6, 2020 at 11:24 PM Han Zhou <hzhou@ovn.org> wrote:
>>
>> On Tue, May 5, 2020 at 11:38 AM <numans@ovn.org> wrote:
>> >
>> > From: Numan Siddique <numans@ovn.org>
>> >
>> > This patch add a new column 'selection_fields' in Load_Balancer
>> > table in NB DB. CMS can define a set of packet headers to use
>> > while selecting a backend. If this column is set, OVN will add the
>> > flow in group table with selection method as 'hash' with the set
fields.
>> > Otherwise it will use the default 'dp_hash' selection method.
>> >
>> > If a load balancer is configured with the selection_fields as
>> > selection_fields    : [ip_dst, ip_src, tp_dst, tp_src]
>> >
>> > then with this patch, the modified ct_lb action will look like
>> >  - ct_lb(backends=IP1:P1,IP2:P1;
>> hash_fields="ip_dst,ip_src,tp_dst,tp_src");
>> >
>> > And the OF flow will look like
>> >  - group_id=2,type=select,selection_method=hash,
>> >
>>
 fields(ip_src,ip_dst,tcp_src,tcp_dst),bucket=bucket_id:0,weight:100,actions=ct(....
>> >
>> > Signed-off-by: Numan Siddique <numans@ovn.org>
>> > ---
>> > v1 -> v2
>> >  * The series has only 1 patch now as the first patch is merged.
>> >  * Addressed review comments from Mark and Han.
>> >  * Added test cases.
>> >  * Added documentation.
>> >
>> >  NEWS                  |   3 +
>> >  include/ovn/actions.h |   1 +
>> >  lib/actions.c         |  45 +++++++++--
>> >  northd/ovn-northd.c   |  30 ++++++--
>> >  ovn-nb.ovsschema      |  10 ++-
>> >  ovn-nb.xml            |  27 +++++++
>> >  tests/ovn-northd.at   |  24 +++---
>> >  tests/ovn.at          |  49 +++++++-----
>> >  tests/system-ovn.at   | 172 +++++++++++++++++++++++++++++++++++++++---
>> >  9 files changed, 308 insertions(+), 53 deletions(-)
>> >
>> > diff --git a/NEWS b/NEWS
>> > index c2b7945df..431b4cf9c 100644
>> > --- a/NEWS
>> > +++ b/NEWS
>> > @@ -14,6 +14,9 @@ OVN v20.03.0 - xx xxx xxxx
>> >     - Added support for ECMP routes in OVN router.
>> >     - Added IPv6 Prefix Delegation support in OVN.
>> >     - OVN now uses OpenFlow 1.5.
>> > +   - Added support to choose selection methods - dp_hash or
>> > +     hash (with specified hash fields) for OVN load balancer
>> > +     backend selection.
>>
>> Thanks for the update here. Shall we call out this is incompatible with
>> older version to warn users that upgrade must be taken care specially?
With
>> this addressed:
>> Acked-by: Han Zhou <hzhou@ovn.org>
>>
>
> Thanks for the review.
>
> I'm not clear on how this will cause incompatibility issues ? If a user
has created
> few load balancers before upgrade, it should work the same after the
upgrade
> too right ? In this case, the selection_fields column will be empty and
ovn-northd will
> use selection_method as "dp_hash" which will be the case before the
upgrade too right ?
>
> Thanks
> Numan
>
Isn't "backends=" something new? So I think during the upgrade (without
stopping all ovn-controllers), all the LBs will stop working.

>
>> >
>> >     - OVN Interconnection:
>> >       * Support for L3 interconnection of multiple OVN deployments with
>> tunnels
>> > diff --git a/include/ovn/actions.h b/include/ovn/actions.h
>> > index 42d45a74c..4a54abe17 100644
>> > --- a/include/ovn/actions.h
>> > +++ b/include/ovn/actions.h
>> > @@ -259,6 +259,7 @@ struct ovnact_ct_lb {
>> >      struct ovnact_ct_lb_dst *dsts;
>> >      size_t n_dsts;
>> >      uint8_t ltable;             /* Logical table ID of next table. */
>> > +    char *hash_fields;
>> >  };
>> >
>> >  struct ovnact_select_dst {
>> > diff --git a/lib/actions.c b/lib/actions.c
>> > index 021a376b4..c50615177 100644
>> > --- a/lib/actions.c
>> > +++ b/lib/actions.c
>> > @@ -951,9 +951,18 @@ parse_ct_lb_action(struct action_context *ctx)
>> >      struct ovnact_ct_lb_dst *dsts = NULL;
>> >      size_t allocated_dsts = 0;
>> >      size_t n_dsts = 0;
>> > +    char *hash_fields = NULL;
>> >
>> > -    if (lexer_match(ctx->lexer, LEX_T_LPAREN)) {
>> > -        while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
>> > +    if (lexer_match(ctx->lexer, LEX_T_LPAREN) &&
>> > +        !lexer_match(ctx->lexer, LEX_T_RPAREN)) {
>> > +        if (!lexer_match_id(ctx->lexer, "backends") ||
>> > +            !lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
>> > +            lexer_syntax_error(ctx->lexer, "expecting backends");
>> > +            return;
>> > +        }
>> > +
>> > +        while (!lexer_match(ctx->lexer, LEX_T_SEMICOLON) &&
>> > +               !lexer_match(ctx->lexer, LEX_T_RPAREN)) {
>> >              struct ovnact_ct_lb_dst dst;
>> >              if (lexer_match(ctx->lexer, LEX_T_LSQUARE)) {
>> >                  /* IPv6 address and port */
>> > @@ -1020,12 +1029,27 @@ parse_ct_lb_action(struct action_context *ctx)
>> >              }
>> >              dsts[n_dsts++] = dst;
>> >          }
>> > +
>> > +        if (lexer_match_id(ctx->lexer, "hash_fields")) {
>> > +            if (!lexer_match(ctx->lexer, LEX_T_EQUALS) ||
>> > +                ctx->lexer->token.type != LEX_T_STRING ||
>> > +                lexer_lookahead(ctx->lexer) != LEX_T_RPAREN) {
>> > +                lexer_syntax_error(ctx->lexer, "invalid hash_fields");
>> > +                free(dsts);
>> > +                return;
>> > +            }
>> > +
>> > +            hash_fields = xstrdup(ctx->lexer->token.s);
>> > +            lexer_get(ctx->lexer);
>> > +            lexer_get(ctx->lexer);
>> > +        }
>> >      }
>> >
>> >      struct ovnact_ct_lb *cl = ovnact_put_CT_LB(ctx->ovnacts);
>> >      cl->ltable = ctx->pp->cur_ltable + 1;
>> >      cl->dsts = dsts;
>> >      cl->n_dsts = n_dsts;
>> > +    cl->hash_fields = hash_fields;
>> >  }
>> >
>> >  static void
>> > @@ -1033,10 +1057,10 @@ format_CT_LB(const struct ovnact_ct_lb *cl,
>> struct ds *s)
>> >  {
>> >      ds_put_cstr(s, "ct_lb");
>> >      if (cl->n_dsts) {
>> > -        ds_put_char(s, '(');
>> > +        ds_put_cstr(s, "(backends=");
>> >          for (size_t i = 0; i < cl->n_dsts; i++) {
>> >              if (i) {
>> > -                ds_put_cstr(s, ", ");
>> > +                ds_put_char(s, ',');
>> >              }
>> >
>> >              const struct ovnact_ct_lb_dst *dst = &cl->dsts[i];
>> > @@ -1056,7 +1080,13 @@ format_CT_LB(const struct ovnact_ct_lb *cl,
struct
>> ds *s)
>> >              }
>> >          }
>> >          ds_put_char(s, ')');
>> > +
>> > +        if (cl->hash_fields) {
>> > +            ds_chomp(s, ')');
>> > +            ds_put_format(s, "; hash_fields=\"%s\")",
cl->hash_fields);
>> > +        }
>> >      }
>> > +
>> >      ds_put_char(s, ';');
>> >  }
>> >
>> > @@ -1103,7 +1133,11 @@ encode_CT_LB(const struct ovnact_ct_lb *cl,
>> >                              : MFF_LOG_DNAT_ZONE - MFF_REG0;
>> >
>> >      struct ds ds = DS_EMPTY_INITIALIZER;
>> > -    ds_put_format(&ds, "type=select,selection_method=dp_hash");
>> > +    ds_put_format(&ds, "type=select,selection_method=%s",
>> > +                  cl->hash_fields ? "hash": "dp_hash");
>> > +    if (cl->hash_fields) {
>> > +        ds_put_format(&ds, ",fields(%s)", cl->hash_fields);
>> > +    }
>> >
>> >      BUILD_ASSERT(MFF_LOG_CT_ZONE >= MFF_REG0);
>> >      BUILD_ASSERT(MFF_LOG_CT_ZONE < MFF_REG0 + FLOW_N_REGS);
>> > @@ -1145,6 +1179,7 @@ static void
>> >  ovnact_ct_lb_free(struct ovnact_ct_lb *ct_lb)
>> >  {
>> >      free(ct_lb->dsts);
>> > +    free(ct_lb->hash_fields);
>> >  }
>> >
>> >  static void
>> > diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
>> > index 3f03e418a..d98d3a6b6 100644
>> > --- a/northd/ovn-northd.c
>> > +++ b/northd/ovn-northd.c
>> > @@ -3112,7 +3112,7 @@ struct ovn_lb {
>> >      struct hmap_node hmap_node;
>> >
>> >      const struct nbrec_load_balancer *nlb; /* May be NULL. */
>> > -
>> > +    char *selection_fields;
>> >      struct lb_vip *vips;
>> >      size_t n_vips;
>> >  };
>> > @@ -3340,6 +3340,15 @@ ovn_lb_create(struct northd_context *ctx, struct
>> hmap *lbs,
>> >          n_vips++;
>> >      }
>> >
>> > +    if (lb->nlb->n_selection_fields) {
>> > +        struct ds sel_fields = DS_EMPTY_INITIALIZER;
>> > +        for (size_t i = 0; i < lb->nlb->n_selection_fields; i++) {
>> > +            ds_put_format(&sel_fields, "%s,",
>> lb->nlb->selection_fields[i]);
>> > +        }
>> > +        ds_chomp(&sel_fields, ',');
>> > +        lb->selection_fields = ds_steal_cstr(&sel_fields);
>> > +    }
>> > +
>> >      return lb;
>> >  }
>> >
>> > @@ -3358,13 +3367,15 @@ ovn_lb_destroy(struct ovn_lb *lb)
>> >          free(lb->vips[i].backends);
>> >      }
>> >      free(lb->vips);
>> > +    free(lb->selection_fields);
>> >  }
>> >
>> >  static void build_lb_vip_ct_lb_actions(struct lb_vip *lb_vip,
>> > -                                       struct ds *action)
>> > +                                       struct ds *action,
>> > +                                       char *selection_fields)
>> >  {
>> >      if (lb_vip->health_check) {
>> > -        ds_put_cstr(action, "ct_lb(");
>> > +        ds_put_cstr(action, "ct_lb(backends=");
>> >
>> >          size_t n_active_backends = 0;
>> >          for (size_t k = 0; k < lb_vip->n_backends; k++) {
>> > @@ -3388,7 +3399,13 @@ static void build_lb_vip_ct_lb_actions(struct
>> lb_vip *lb_vip,
>> >              ds_put_cstr(action, ");");
>> >          }
>> >      } else {
>> > -        ds_put_format(action, "ct_lb(%s);", lb_vip->backend_ips);
>> > +        ds_put_format(action, "ct_lb(backends=%s);",
>> lb_vip->backend_ips);
>> > +    }
>> > +
>> > +    if (selection_fields && selection_fields[0]) {
>> > +        ds_chomp(action, ';');
>> > +        ds_chomp(action, ')');
>> > +        ds_put_format(action, "; hash_fields=\"%s\");",
>> selection_fields);
>> >      }
>> >  }
>> >
>> > @@ -5666,7 +5683,7 @@ build_lb_rules(struct ovn_datapath *od, struct
hmap
>> *lflows, struct ovn_lb *lb)
>> >
>> >          /* New connections in Ingress table. */
>> >          struct ds action = DS_EMPTY_INITIALIZER;
>> > -        build_lb_vip_ct_lb_actions(lb_vip, &action);
>> > +        build_lb_vip_ct_lb_actions(lb_vip, &action,
>> lb->selection_fields);
>> >
>> >          struct ds match = DS_EMPTY_INITIALIZER;
>> >          ds_put_format(&match, "ct.new && %s.dst == %s", ip_match,
>> lb_vip->vip);
>> > @@ -9317,7 +9334,8 @@ build_lrouter_flows(struct hmap *datapaths,
struct
>> hmap *ports,
>> >              for (size_t j = 0; j < lb->n_vips; j++) {
>> >                  struct lb_vip *lb_vip = &lb->vips[j];
>> >                  ds_clear(&actions);
>> > -                build_lb_vip_ct_lb_actions(lb_vip, &actions);
>> > +                build_lb_vip_ct_lb_actions(lb_vip, &actions,
>> > +                                           lb->selection_fields);
>> >
>> >                  if (!sset_contains(&all_ips, lb_vip->vip)) {
>> >                      sset_add(&all_ips, lb_vip->vip);
>> > diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
>> > index b2a046571..a06972aa0 100644
>> > --- a/ovn-nb.ovsschema
>> > +++ b/ovn-nb.ovsschema
>> > @@ -1,7 +1,7 @@
>> >  {
>> >      "name": "OVN_Northbound",
>> > -    "version": "5.22.0",
>> > -    "cksum": "384164739 25476",
>> > +    "version": "5.23.0",
>> > +    "cksum": "111023208 25806",
>> >      "tables": {
>> >          "NB_Global": {
>> >              "columns": {
>> > @@ -179,6 +179,12 @@
>> >                  "ip_port_mappings": {
>> >                      "type": {"key": "string", "value": "string",
>> >                               "min": 0, "max": "unlimited"}},
>> > +                "selection_fields": {
>> > +                    "type": {"key": {"type": "string",
>> > +                             "enum": ["set",
>> > +                                ["eth_src", "eth_dst", "ip_src",
>> "ip_dst",
>> > +                                 "tp_src", "tp_dst"]]},
>> > +                             "min": 0, "max": "unlimited"}},
>> >                  "external_ids": {
>> >                      "type": {"key": "string", "value": "string",
>> >                               "min": 0, "max": "unlimited"}}},
>> > diff --git a/ovn-nb.xml b/ovn-nb.xml
>> > index af15c550a..95ee4c9e6 100644
>> > --- a/ovn-nb.xml
>> > +++ b/ovn-nb.xml
>> > @@ -1513,6 +1513,33 @@
>> >        </p>
>> >      </column>
>> >
>> > +    <column name="selection_fields">
>> > +      <p>
>> > +        OVN native load balancers are supported using the OpenFlow
groups
>> > +        of type <code>select</code>. OVS supports two selection
methods:
>> > +        <code>dp_hash</code> and <code>hash (with optional fields
>> > +        specified)</code> in selecting the buckets of a group.
>> > +        Please see the OVS documentation (man ovs-ofctl)
>> > +        for more details on the selection methods. Each endpoint IP
(and
>> port
>> > +        if set) is mapped to a bucket in the group flow.
>> > +      </p>
>> > +
>> > +      <p>
>> > +        CMS can choose the <code>hash</code> selection method by
setting
>> the
>> > +        selection fields in this column. <code>ovs-vswitchd</code>
uses
>> the
>> > +        specified fields in generating the hash.
>> > +      </p>
>> > +
>> > +      <p>
>> > +        <code>dp_hash</code> selection method uses the assistance of
>> > +        datapath to calculate the hash and it is expected to be
>> > +        faster than <code>hash</code> selection method. So CMS should
>> take
>> > +        this into consideration before using the <code>hash</code>
>> method.
>> > +        Please consult the OVS documentation and OVS sources for the
>> > +        implementation details.
>> > +      </p>
>> > +    </column>
>> > +
>> >      <group title="Common Columns">
>> >        <column name="external_ids">
>> >          See <em>External IDs</em> at the beginning of this document.
>> > diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
>> > index a0109ae94..57c9eedd5 100644
>> > --- a/tests/ovn-northd.at
>> > +++ b/tests/ovn-northd.at
>> > @@ -1183,7 +1183,7 @@ ovn-nbctl --wait=sb ls-lb-add sw0 lb1
>> >
>> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>> >  AT_CHECK([cat lflows.txt], [0], [dnl
>> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
>> ,20.0.0.3:80);)
>> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=
10.0.0.3:80
>> ,20.0.0.3:80);)
>> >  ])
>> >
>> >  # Delete the Load_Balancer_Health_Check
>> > @@ -1192,7 +1192,7 @@ OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list
>> service_monitor |  wc -l`])
>> >
>> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>> >  AT_CHECK([cat lflows.txt], [0], [dnl
>> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
>> ,20.0.0.3:80);)
>> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=
10.0.0.3:80
>> ,20.0.0.3:80);)
>> >  ])
>> >
>> >  # Create the Load_Balancer_Health_Check again.
>> > @@ -1205,7 +1205,7 @@ service_monitor | sed '/^$/d' | wc -l`])
>> >
>> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>> >  AT_CHECK([cat lflows.txt], [0], [dnl
>> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
>> ,20.0.0.3:80);)
>> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=
10.0.0.3:80
>> ,20.0.0.3:80);)
>> >  ])
>> >
>> >  # Get the uuid of both the service_monitor
>> > @@ -1221,7 +1221,7 @@ OVS_WAIT_UNTIL([
>> >
>> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>> >  AT_CHECK([cat lflows.txt], [0], [dnl
>> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80);)
>> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=
10.0.0.3:80
>> );)
>> >  ])
>> >
>> >  # Set the service monitor for sw0-p1 to offline
>> > @@ -1251,7 +1251,7 @@ OVS_WAIT_UNTIL([
>> >
>> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>> >  AT_CHECK([cat lflows.txt], [0], [dnl
>> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
>> ,20.0.0.3:80);)
>> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=
10.0.0.3:80
>> ,20.0.0.3:80);)
>> >  ])
>> >
>> >  # Set the service monitor for sw1-p1 to error
>> > @@ -1263,7 +1263,7 @@ OVS_WAIT_UNTIL([
>> >  ovn-sbctl dump-flows sw0 | grep "ip4.dst == 10.0.0.10 && tcp.dst ==
80" \
>> >  | grep priority=120 > lflows.txt
>> >  AT_CHECK([cat lflows.txt], [0], [dnl
>> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80);)
>> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=
10.0.0.3:80
>> );)
>> >  ])
>> >
>> >  # Add one more vip to lb1
>> > @@ -1293,8 +1293,8 @@ service_monitor port=1000 | sed '/^$/d' | wc
-l`])
>> >
>> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>> >  AT_CHECK([cat lflows.txt], [0], [dnl
>> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80);)
>> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(10.0.0.3:1000);)
>> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=
10.0.0.3:80
>> );)
>> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.40 && tcp.dst == 1000),
>> action=(ct_lb(backends=10.0.0.3:1000);)
>> >  ])
>> >
>> >  # Set the service monitor for sw1-p1 to online
>> > @@ -1306,16 +1306,16 @@ OVS_WAIT_UNTIL([
>> >
>> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>> >  AT_CHECK([cat lflows.txt], [0], [dnl
>> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
>> ,20.0.0.3:80);)
>> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(10.0.0.3:1000
>> ,20.0.0.3:80);)
>> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=
10.0.0.3:80
>> ,20.0.0.3:80);)
>> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(backends=
>> 10.0.0.3:1000,20.0.0.3:80);)
>> >  ])
>> >
>> >  # Associate lb1 to sw1
>> >  ovn-nbctl --wait=sb ls-lb-add sw1 lb1
>> >  ovn-sbctl dump-flows sw1 | grep ct_lb | grep priority=120 > lflows.txt
>> >  AT_CHECK([cat lflows.txt], [0], [dnl
>> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
>> ,20.0.0.3:80);)
>> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(10.0.0.3:1000
>> ,20.0.0.3:80);)
>> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=
10.0.0.3:80
>> ,20.0.0.3:80);)
>> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(backends=
>> 10.0.0.3:1000,20.0.0.3:80);)
>> >  ])
>> >
>> >  # Now create lb2 same as lb1 but udp protocol.
>> > diff --git a/tests/ovn.at b/tests/ovn.at
>> > index 7befc8224..055fb57e2 100644
>> > --- a/tests/ovn.at
>> > +++ b/tests/ovn.at
>> > @@ -970,29 +970,42 @@ ct_lb();
>> >      encodes as ct(table=19,zone=NXM_NX_REG13[0..15],nat)
>> >      has prereqs ip
>> >  ct_lb(192.168.1.2:80, 192.168.1.3:80);
>> > +    Syntax error at `192.168.1.2' expecting backends.
>> > +ct_lb(backends=192.168.1.2:80,192.168.1.3:80);
>> >      encodes as group:1
>> >      uses group: id(1),
>>
name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=ct(nat(dst=
>> 192.168.1.2:80
>>
),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=
>> 192.168.1.3:80),commit,table=19,zone=NXM_NX_REG13[0..15]))
>> >      has prereqs ip
>> > -ct_lb(192.168.1.2, 192.168.1.3, );
>> > -    formats as ct_lb(192.168.1.2, 192.168.1.3);
>> > +ct_lb(backends=192.168.1.2, 192.168.1.3, );
>> > +    formats as ct_lb(backends=192.168.1.2,192.168.1.3);
>> >      encodes as group:2
>> >      uses group: id(2),
>>
name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=ct(nat(dst=192.168.1.2),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=192.168.1.3),commit,table=19,zone=NXM_NX_REG13[0..15]))
>> >      has prereqs ip
>> > -ct_lb(fd0f::2, fd0f::3, );
>> > -    formats as ct_lb(fd0f::2, fd0f::3);
>> > +ct_lb(backends=fd0f::2, fd0f::3, );
>> > +    formats as ct_lb(backends=fd0f::2,fd0f::3);
>> >      encodes as group:3
>> >      uses group: id(3),
>>
name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=ct(nat(dst=fd0f::2),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=fd0f::3),commit,table=19,zone=NXM_NX_REG13[0..15]))
>> >      has prereqs ip
>> >
>> > -ct_lb(192.168.1.2:);
>> > +ct_lb(backends=192.168.1.2:);
>> >      Syntax error at `)' expecting port number.
>> > -ct_lb(192.168.1.2:123456);
>> > +ct_lb(backends=192.168.1.2:123456);
>> >      Syntax error at `123456' expecting port number.
>> > -ct_lb(foo);
>> > +ct_lb(backends=foo);
>> >      Syntax error at `foo' expecting IP address.
>> > -ct_lb([192.168.1.2]);
>> > +ct_lb(backends=[192.168.1.2]);
>> >      Syntax error at `192.168.1.2' expecting IPv6 address.
>> >
>> > +ct_lb(backends=192.168.1.2:80,192.168.1.3:80;
>> hash_fields=eth_src,eth_dst,ip_src);
>> > +    Syntax error at `eth_src' invalid hash_fields.
>> > +ct_lb(backends=192.168.1.2:80,192.168.1.3:80;
>> hash_fields="eth_src,eth_dst,ip_src");
>> > +    encodes as group:4
>> > +    uses group: id(4),
>>
name(type=select,selection_method=hash,fields(eth_src,eth_dst,ip_src),bucket=bucket_id=0,weight:100,actions=ct(nat(dst=
>> 192.168.1.2:80
>>
),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=
>> 192.168.1.3:80),commit,table=19,zone=NXM_NX_REG13[0..15]))
>> > +    has prereqs ip
>> > +ct_lb(backends=fd0f::2,fd0f::3;
>> hash_fields="eth_src,eth_dst,ip_src,ip_dst,tp_src,tp_dst");
>> > +    encodes as group:5
>> > +    uses group: id(5),
>>
name(type=select,selection_method=hash,fields(eth_src,eth_dst,ip_src,ip_dst,tp_src,tp_dst),bucket=bucket_id=0,weight:100,actions=ct(nat(dst=fd0f::2),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=fd0f::3),commit,table=19,zone=NXM_NX_REG13[0..15]))
>> > +    has prereqs ip
>> > +
>> >  # ct_next
>> >  ct_next;
>> >      encodes as ct(table=19,zone=NXM_NX_REG13[0..15])
>> > @@ -1520,13 +1533,13 @@ handle_svc_check(reg0);
>> >  # select
>> >  reg9[16..31] = select(1=50, 2=100, 3, );
>> >      formats as reg9[16..31] = select(1=50, 2=100, 3=100);
>> > -    encodes as group:4
>> > -    uses group: id(4),
>>
name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:50,actions=load:1->xreg4[16..31],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xreg4[16..31],resubmit(,19),bucket=bucket_id=2,weight:100,actions=load:3->xreg4[16..31],resubmit(,19))
>> > +    encodes as group:6
>> > +    uses group: id(6),
>>
name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:50,actions=load:1->xreg4[16..31],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xreg4[16..31],resubmit(,19),bucket=bucket_id=2,weight:100,actions=load:3->xreg4[16..31],resubmit(,19))
>> >
>> >  reg0 = select(1, 2);
>> >      formats as reg0 = select(1=100, 2=100);
>> > -    encodes as group:5
>> > -    uses group: id(5),
>>
name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=load:1->xxreg0[96..127],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xxreg0[96..127],resubmit(,19))
>> > +    encodes as group:7
>> > +    uses group: id(7),
>>
name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=load:1->xxreg0[96..127],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xxreg0[96..127],resubmit(,19))
>> >
>> >  reg0 = select(1=, 2);
>> >      Syntax error at `,' expecting weight.
>> > @@ -1542,12 +1555,12 @@ reg0[0..14] = select(1, 2, 3);
>> >      cannot use 15-bit field reg0[0..14] for "select", which requires
at
>> least 16 bits.
>> >
>> >  fwd_group(liveness="true", childports="eth0", "lsp1");
>> > -    encodes as group:6
>> > -    uses group: id(6),
>>
name(type=select,selection_method=dp_hash,bucket=watch_port:5,load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=watch_port:17,load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
>> > +    encodes as group:8
>> > +    uses group: id(8),
>>
name(type=select,selection_method=dp_hash,bucket=watch_port:5,load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=watch_port:17,load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
>> >
>> >  fwd_group(childports="eth0", "lsp1");
>> > -    encodes as group:7
>> > -    uses group: id(7),
>>
name(type=select,selection_method=dp_hash,bucket=load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
>> > +    encodes as group:9
>> > +    uses group: id(9),
>>
name(type=select,selection_method=dp_hash,bucket=load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
>> >
>> >  fwd_group(childports=eth0);
>> >      Syntax error at `eth0' expecting logical switch port.
>> > @@ -18000,12 +18013,12 @@ service_monitor | sed '/^$/d' | wc -l`])
>> >
>> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
>> >  AT_CHECK([cat lflows.txt], [0], [dnl
>> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
>> ,20.0.0.3:80);)
>> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
>> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=
10.0.0.3:80
>> ,20.0.0.3:80);)
>> >  ])
>> >
>> >  ovn-sbctl dump-flows lr0 | grep ct_lb | grep priority=120 > lflows.txt
>> >  AT_CHECK([cat lflows.txt], [0], [dnl
>> > -  table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip
&&
>> ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 &&
>> is_chassis_resident("cr-lr0-public")), action=(ct_lb(10.0.0.3:80,
20.0.0.3:80
>> );)
>> > +  table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip
&&
>> ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 &&
>> is_chassis_resident("cr-lr0-public")), action=(ct_lb(backends=10.0.0.3:80
>> ,20.0.0.3:80);)
>> >  ])
>> >
>> >  # get the svc monitor mac.
>> > diff --git a/tests/system-ovn.at b/tests/system-ovn.at
>> > index fa3b83cb1..32ae60925 100644
>> > --- a/tests/system-ovn.at
>> > +++ b/tests/system-ovn.at
>> > @@ -1095,15 +1095,15 @@ ovn-nbctl lsp-add bar bar3 \
>> >  -- lsp-set-addresses bar3 "f0:00:0f:01:02:05 172.16.1.4"
>> >
>> >  # Config OVN load-balancer with a VIP.
>> > -uuid=`ovn-nbctl  create load_balancer
>> vips:30.0.0.1="172.16.1.2,172.16.1.3,172.16.1.4"`
>> > -ovn-nbctl set logical_switch foo load_balancer=$uuid
>> > +ovn-nbctl lb-add lb1 30.0.0.1 "172.16.1.2,172.16.1.3,172.16.1.4"
>> > +ovn-nbctl ls-lb-add foo lb1
>> >
>> >  # Create another load-balancer with another VIP.
>> > -uuid=`ovn-nbctl create load_balancer
>> vips:30.0.0.3="172.16.1.2,172.16.1.3,172.16.1.4"`
>> > -ovn-nbctl add logical_switch foo load_balancer $uuid
>> > +lb2_uuid=`ovn-nbctl create load_balancer name=lb2
>> vips:30.0.0.3="172.16.1.2,172.16.1.3,172.16.1.4"`
>> > +ovn-nbctl ls-lb-add foo lb2
>> >
>> >  # Config OVN load-balancer with another VIP (this time with ports).
>> > -ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"
172.16.1.2:80,
>> 172.16.1.3:80,172.16.1.4:80"'
>> > +ovn-nbctl set load_balancer $lb2_uuid vips:'"30.0.0.2:8000"'='"
>> 172.16.1.2:80,172.16.1.3:80,172.16.1.4:80"'
>> >
>> >  # Wait for ovn-controller to catch up.
>> >  ovn-nbctl --wait=hv sync
>> > @@ -1157,6 +1157,82 @@
>>
tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(s
>> >
>>
 tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>> >  ])
>> >
>> > +# Configure selection_fields.
>> > +ovn-nbctl set load_balancer $lb2_uuid
>> selection_fields="ip_src,ip_dst,tp_src,tp_dst"
>> > +OVS_WAIT_UNTIL([
>> > +    test $(ovs-ofctl dump-groups br-int | \
>> > +    grep "selection_method=hash,fields(ip_src,ip_dst,tcp_src,tcp_dst)"
>> -c) -eq 2
>> > +])
>> > +
>> > +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>> > +
>> > +dnl Test load-balancing that includes L4 ports in NAT.
>> > +for i in `seq 1 20`; do
>> > +    echo Request $i
>> > +    NS_CHECK_EXEC([foo1], [wget 30.0.0.2:8000 -t 5 -T 1
>> --retry-connrefused -v -o wget$i.log])
>> > +done
>> > +
>> > +dnl Each server should have at least one connection.
>> > +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | \
>> > +sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>> >
>>
+tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>> >
>>
+tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>> >
>>
+tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>> > +])
>> > +
>> > +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>> > +
>> > +echo "foo" > foo
>> > +for i in `seq 1 20`; do
>> > +    echo Request $i
>> > +    ip netns exec foo1 nc -p 30000 30.0.0.2 8000 < foo
>> > +done
>> > +
>> > +dnl Only one backend should be chosen.
>> > +AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 -c)
-eq
>> 1])
>> > +
>> > +ovn-nbctl set load_balancer $lb2_uuid selection_fields="ip_src"
>> > +OVS_WAIT_UNTIL([
>> > +    test $(ovs-ofctl dump-groups br-int | \
>> > +    grep "selection_method=hash,fields=ip_src" -c) -eq 2
>> > +])
>> > +
>> > +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>> > +for i in `seq 1 20`; do
>> > +    echo Request $i
>> > +    ip netns exec foo1 nc 30.0.0.2 8000 < foo
>> > +done
>> > +
>> > +dnl Only one backend should be chosen as eth_src and ip_src is fixed.
>> > +bar1_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep
>> 172.16.1.2 -c)
>> > +bar2_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep
>> 172.16.1.3 -c)
>> > +bar3_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep
>> 172.16.1.4 -c)
>> > +
>> > +AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 |
grep
>> 172.16.1 -c) -ne 0])
>> > +
>> > +if [[ "$bar1_ct" == "20" ]]; then
>> > +    AT_CHECK([test $bar1_ct -eq 20])
>> > +    AT_CHECK([test $bar2_ct -eq 0])
>> > +    AT_CHECK([test $bar3_ct -eq 0])
>> > +else
>> > +    AT_CHECK([test $bar1_ct -eq 0])
>> > +fi
>> > +
>> > +if [[ "$bar2_ct" == "20" ]]; then
>> > +    AT_CHECK([test $bar1_ct -eq 20])
>> > +    AT_CHECK([test $bar2_ct -eq 0])
>> > +    AT_CHECK([test $bar3_ct -eq 0])
>> > +else
>> > +    AT_CHECK([test $bar2_ct -eq 0])
>> > +fi
>> > +
>> > +if [[ "$bar3_ct" == "20" ]]; then
>> > +    AT_CHECK([test $bar1_ct -eq 20])
>> > +    AT_CHECK([test $bar2_ct -eq 0])
>> > +    AT_CHECK([test $bar3_ct -eq 0])
>> > +else
>> > +    AT_CHECK([test $bar3_ct -eq 0])
>> > +fi
>> >
>> >  OVS_APP_EXIT_AND_WAIT([ovn-controller])
>> >
>> > @@ -1246,11 +1322,11 @@ uuid=`ovn-nbctl  create load_balancer
>> vips:\"fd03::1\"=\"fd02::2,fd02::3,fd02::4
>> >  ovn-nbctl set logical_switch foo load_balancer=$uuid
>> >
>> >  # Create another load-balancer with another VIP.
>> > -uuid=`ovn-nbctl create load_balancer
>> vips:\"fd03::3\"=\"fd02::2,fd02::3,fd02::4\"`
>> > -ovn-nbctl add logical_switch foo load_balancer $uuid
>> > +lb2_uuid=`ovn-nbctl create load_balancer
>> vips:\"fd03::3\"=\"fd02::2,fd02::3,fd02::4\"`
>> > +ovn-nbctl add logical_switch foo load_balancer $lb2_uuid
>> >
>> >  # Config OVN load-balancer with another VIP (this time with ports).
>> > -ovn-nbctl set load_balancer $uuid
>> vips:'"[[fd03::2]]:8000"'='"@<:@fd02::2@:>@:80,@<:@fd02::3@
>> :>@:80,@<:@fd02::4@:>@:80"'
>> > +ovn-nbctl set load_balancer $lb2_uuid
>> vips:'"[[fd03::2]]:8000"'='"@<:@fd02::2@:>@:80,@<:@fd02::3@
>> :>@:80,@<:@fd02::4@:>@:80"'
>> >
>> >  # Wait for ovn-controller to catch up.
>> >  ovn-nbctl --wait=hv sync
>> > @@ -1304,7 +1380,83 @@
>>
tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd
>> >
>>
 tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::4,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>> >  ])
>> >
>> > +# Configure selection_fields.
>> > +ovn-nbctl set load_balancer $lb2_uuid
>> selection_fields="ip_src,ip_dst,tp_src,tp_dst"
>> > +OVS_WAIT_UNTIL([
>> > +    test $(ovs-ofctl dump-groups br-int | \
>> > +    grep "selection_method=hash,fields(ip_src,ip_dst,tcp_src,tcp_dst)"
>> -c) -eq 2
>> > +])
>> > +
>> > +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>> > +
>> > +dnl Test load-balancing that includes L4 ports in NAT.
>> > +for i in `seq 1 20`; do
>> > +    echo Request $i
>> > +    NS_CHECK_EXEC([foo1], [wget http://[[fd03::2]]:8000 -t 5 -T 1
>> --retry-connrefused -v -o wget$i.log])
>> > +done
>> > +
>> > +dnl Each server should have at least one connection.
>> > +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd03::2) | grep
-v
>> fe80 | \
>> > +sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>> >
>>
+tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::2,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>> >
>>
+tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::3,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>> >
>>
+tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::4,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>> > +])
>> > +
>> > +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>> > +
>> > +echo "foo" > foo
>> > +for i in `seq 1 20`; do
>> > +    echo Request $i
>> > +    ip netns exec foo1 nc -6 -p 30000 fd03::2 8000 < foo
>> > +done
>> > +
>> > +# Only one backend should be chosen. Since the source port is fixed,
>> > +# there should be only one conntrack entry.
>> > +AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep fd03::2 -c)
-eq
>> 1])
>> > +
>> > +ovn-nbctl set load_balancer $lb2_uuid
selection_fields="eth_src,ip_src"
>> > +OVS_WAIT_UNTIL([
>> > +    test $(ovs-ofctl dump-groups br-int | \
>> > +    grep "selection_method=hash,fields(eth_src,ip_src)" -c) -eq 2
>> > +])
>> > +
>> > +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>> > +for i in `seq 1 20`; do
>> > +    echo Request $i
>> > +    ip netns exec foo1 nc -6 fd03::2 8000 < foo
>> > +done
>> >
>> > +dnl Only one backend should be chosen as eth_src and ip_src is fixed.
>> > +bar1_ct=$(ovs-appctl dpctl/dump-conntrack | grep fd03::2 | grep
fd02::2
>> -c)
>> > +bar2_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep
fd02::3
>> -c)
>> > +bar3_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep
fd02::4
>> -c)
>> > +
>> > +AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep fd03::2 | grep
>> fd02 -c) -ne 0])
>> > +
>> > +if [[ "$bar1_ct" == "20" ]]; then
>> > +    AT_CHECK([test $bar1_ct -eq 20])
>> > +    AT_CHECK([test $bar2_ct -eq 0])
>> > +    AT_CHECK([test $bar3_ct -eq 0])
>> > +else
>> > +    AT_CHECK([test $bar1_ct -eq 0])
>> > +fi
>> > +
>> > +if [[ "$bar2_ct" == "20" ]]; then
>> > +    AT_CHECK([test $bar1_ct -eq 20])
>> > +    AT_CHECK([test $bar2_ct -eq 0])
>> > +    AT_CHECK([test $bar3_ct -eq 0])
>> > +else
>> > +    AT_CHECK([test $bar2_ct -eq 0])
>> > +fi
>> > +
>> > +if [[ "$bar3_ct" == "20" ]]; then
>> > +    AT_CHECK([test $bar1_ct -eq 20])
>> > +    AT_CHECK([test $bar2_ct -eq 0])
>> > +    AT_CHECK([test $bar3_ct -eq 0])
>> > +else
>> > +    AT_CHECK([test $bar3_ct -eq 0])
>> > +fi
>> >  OVS_APP_EXIT_AND_WAIT([ovn-controller])
>> >
>> >  as ovn-sb
>> > @@ -3448,7 +3600,7 @@ service_monitor | sed '/^$/d' | grep online | wc
>> -l`])
>> >
>> >  OVS_WAIT_UNTIL(
>> >      [ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 | grep
>> "ip4.dst == 10.0.0.10" > lflows.txt
>> > -     test 1 = `cat lflows.txt | grep "ct_lb(10.0.0.3:80,20.0.0.3:80)"
|
>> wc -l`]
>> > +     test 1 = `cat lflows.txt | grep "ct_lb(backends=10.0.0.3:80,
>> 20.0.0.3:80)" | wc -l`]
>> >  )
>> >
>> >  # From sw0-p2 send traffic to vip - 10.0.0.10
>> > @@ -3474,7 +3626,7 @@ service_monitor logical_port=sw0-p1 | sed
'/^$/d' |
>> grep offline | wc -l`])
>> >
>> >  OVS_WAIT_UNTIL(
>> >      [ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 | grep
>> "ip4.dst == 10.0.0.10" > lflows.txt
>> > -     test 1 = `cat lflows.txt | grep "ct_lb(20.0.0.3:80)" | wc -l`]
>> > +     test 1 = `cat lflows.txt | grep "ct_lb(backends=20.0.0.3:80)" |
wc
>> -l`]
>> >  )
>> >
>> >  ovs-appctl dpctl/flush-conntrack
>> > --
>> > 2.26.2
>> >
>> > _______________________________________________
>> > 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
>>
Numan Siddique May 6, 2020, 6:18 p.m. UTC | #5
On Wed, May 6, 2020 at 11:46 PM Han Zhou <hzhou@ovn.org> wrote:

> On Wed, May 6, 2020 at 11:05 AM Numan Siddique <numans@ovn.org> wrote:
> >
> >
> >
> > On Wed, May 6, 2020 at 11:24 PM Han Zhou <hzhou@ovn.org> wrote:
> >>
> >> On Tue, May 5, 2020 at 11:38 AM <numans@ovn.org> wrote:
> >> >
> >> > From: Numan Siddique <numans@ovn.org>
> >> >
> >> > This patch add a new column 'selection_fields' in Load_Balancer
> >> > table in NB DB. CMS can define a set of packet headers to use
> >> > while selecting a backend. If this column is set, OVN will add the
> >> > flow in group table with selection method as 'hash' with the set
> fields.
> >> > Otherwise it will use the default 'dp_hash' selection method.
> >> >
> >> > If a load balancer is configured with the selection_fields as
> >> > selection_fields    : [ip_dst, ip_src, tp_dst, tp_src]
> >> >
> >> > then with this patch, the modified ct_lb action will look like
> >> >  - ct_lb(backends=IP1:P1,IP2:P1;
> >> hash_fields="ip_dst,ip_src,tp_dst,tp_src");
> >> >
> >> > And the OF flow will look like
> >> >  - group_id=2,type=select,selection_method=hash,
> >> >
> >>
>
>  fields(ip_src,ip_dst,tcp_src,tcp_dst),bucket=bucket_id:0,weight:100,actions=ct(....
> >> >
> >> > Signed-off-by: Numan Siddique <numans@ovn.org>
> >> > ---
> >> > v1 -> v2
> >> >  * The series has only 1 patch now as the first patch is merged.
> >> >  * Addressed review comments from Mark and Han.
> >> >  * Added test cases.
> >> >  * Added documentation.
> >> >
> >> >  NEWS                  |   3 +
> >> >  include/ovn/actions.h |   1 +
> >> >  lib/actions.c         |  45 +++++++++--
> >> >  northd/ovn-northd.c   |  30 ++++++--
> >> >  ovn-nb.ovsschema      |  10 ++-
> >> >  ovn-nb.xml            |  27 +++++++
> >> >  tests/ovn-northd.at   |  24 +++---
> >> >  tests/ovn.at          |  49 +++++++-----
> >> >  tests/system-ovn.at   | 172
> +++++++++++++++++++++++++++++++++++++++---
> >> >  9 files changed, 308 insertions(+), 53 deletions(-)
> >> >
> >> > diff --git a/NEWS b/NEWS
> >> > index c2b7945df..431b4cf9c 100644
> >> > --- a/NEWS
> >> > +++ b/NEWS
> >> > @@ -14,6 +14,9 @@ OVN v20.03.0 - xx xxx xxxx
> >> >     - Added support for ECMP routes in OVN router.
> >> >     - Added IPv6 Prefix Delegation support in OVN.
> >> >     - OVN now uses OpenFlow 1.5.
> >> > +   - Added support to choose selection methods - dp_hash or
> >> > +     hash (with specified hash fields) for OVN load balancer
> >> > +     backend selection.
> >>
> >> Thanks for the update here. Shall we call out this is incompatible with
> >> older version to warn users that upgrade must be taken care specially?
> With
> >> this addressed:
> >> Acked-by: Han Zhou <hzhou@ovn.org>
> >>
> >
> > Thanks for the review.
> >
> > I'm not clear on how this will cause incompatibility issues ? If a user
> has created
> > few load balancers before upgrade, it should work the same after the
> upgrade
> > too right ? In this case, the selection_fields column will be empty and
> ovn-northd will
> > use selection_method as "dp_hash" which will be the case before the
> upgrade too right ?
> >
> > Thanks
> > Numan
> >
> Isn't "backends=" something new? So I think during the upgrade (without
> stopping all ovn-controllers), all the LBs will stop working.
>

Ouch. Sorry, my apologies. I missed that part. I'll update that in the NEWS.

Thanks
Numan


>
> >
> >> >
> >> >     - OVN Interconnection:
> >> >       * Support for L3 interconnection of multiple OVN deployments
> with
> >> tunnels
> >> > diff --git a/include/ovn/actions.h b/include/ovn/actions.h
> >> > index 42d45a74c..4a54abe17 100644
> >> > --- a/include/ovn/actions.h
> >> > +++ b/include/ovn/actions.h
> >> > @@ -259,6 +259,7 @@ struct ovnact_ct_lb {
> >> >      struct ovnact_ct_lb_dst *dsts;
> >> >      size_t n_dsts;
> >> >      uint8_t ltable;             /* Logical table ID of next table. */
> >> > +    char *hash_fields;
> >> >  };
> >> >
> >> >  struct ovnact_select_dst {
> >> > diff --git a/lib/actions.c b/lib/actions.c
> >> > index 021a376b4..c50615177 100644
> >> > --- a/lib/actions.c
> >> > +++ b/lib/actions.c
> >> > @@ -951,9 +951,18 @@ parse_ct_lb_action(struct action_context *ctx)
> >> >      struct ovnact_ct_lb_dst *dsts = NULL;
> >> >      size_t allocated_dsts = 0;
> >> >      size_t n_dsts = 0;
> >> > +    char *hash_fields = NULL;
> >> >
> >> > -    if (lexer_match(ctx->lexer, LEX_T_LPAREN)) {
> >> > -        while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
> >> > +    if (lexer_match(ctx->lexer, LEX_T_LPAREN) &&
> >> > +        !lexer_match(ctx->lexer, LEX_T_RPAREN)) {
> >> > +        if (!lexer_match_id(ctx->lexer, "backends") ||
> >> > +            !lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
> >> > +            lexer_syntax_error(ctx->lexer, "expecting backends");
> >> > +            return;
> >> > +        }
> >> > +
> >> > +        while (!lexer_match(ctx->lexer, LEX_T_SEMICOLON) &&
> >> > +               !lexer_match(ctx->lexer, LEX_T_RPAREN)) {
> >> >              struct ovnact_ct_lb_dst dst;
> >> >              if (lexer_match(ctx->lexer, LEX_T_LSQUARE)) {
> >> >                  /* IPv6 address and port */
> >> > @@ -1020,12 +1029,27 @@ parse_ct_lb_action(struct action_context *ctx)
> >> >              }
> >> >              dsts[n_dsts++] = dst;
> >> >          }
> >> > +
> >> > +        if (lexer_match_id(ctx->lexer, "hash_fields")) {
> >> > +            if (!lexer_match(ctx->lexer, LEX_T_EQUALS) ||
> >> > +                ctx->lexer->token.type != LEX_T_STRING ||
> >> > +                lexer_lookahead(ctx->lexer) != LEX_T_RPAREN) {
> >> > +                lexer_syntax_error(ctx->lexer, "invalid
> hash_fields");
> >> > +                free(dsts);
> >> > +                return;
> >> > +            }
> >> > +
> >> > +            hash_fields = xstrdup(ctx->lexer->token.s);
> >> > +            lexer_get(ctx->lexer);
> >> > +            lexer_get(ctx->lexer);
> >> > +        }
> >> >      }
> >> >
> >> >      struct ovnact_ct_lb *cl = ovnact_put_CT_LB(ctx->ovnacts);
> >> >      cl->ltable = ctx->pp->cur_ltable + 1;
> >> >      cl->dsts = dsts;
> >> >      cl->n_dsts = n_dsts;
> >> > +    cl->hash_fields = hash_fields;
> >> >  }
> >> >
> >> >  static void
> >> > @@ -1033,10 +1057,10 @@ format_CT_LB(const struct ovnact_ct_lb *cl,
> >> struct ds *s)
> >> >  {
> >> >      ds_put_cstr(s, "ct_lb");
> >> >      if (cl->n_dsts) {
> >> > -        ds_put_char(s, '(');
> >> > +        ds_put_cstr(s, "(backends=");
> >> >          for (size_t i = 0; i < cl->n_dsts; i++) {
> >> >              if (i) {
> >> > -                ds_put_cstr(s, ", ");
> >> > +                ds_put_char(s, ',');
> >> >              }
> >> >
> >> >              const struct ovnact_ct_lb_dst *dst = &cl->dsts[i];
> >> > @@ -1056,7 +1080,13 @@ format_CT_LB(const struct ovnact_ct_lb *cl,
> struct
> >> ds *s)
> >> >              }
> >> >          }
> >> >          ds_put_char(s, ')');
> >> > +
> >> > +        if (cl->hash_fields) {
> >> > +            ds_chomp(s, ')');
> >> > +            ds_put_format(s, "; hash_fields=\"%s\")",
> cl->hash_fields);
> >> > +        }
> >> >      }
> >> > +
> >> >      ds_put_char(s, ';');
> >> >  }
> >> >
> >> > @@ -1103,7 +1133,11 @@ encode_CT_LB(const struct ovnact_ct_lb *cl,
> >> >                              : MFF_LOG_DNAT_ZONE - MFF_REG0;
> >> >
> >> >      struct ds ds = DS_EMPTY_INITIALIZER;
> >> > -    ds_put_format(&ds, "type=select,selection_method=dp_hash");
> >> > +    ds_put_format(&ds, "type=select,selection_method=%s",
> >> > +                  cl->hash_fields ? "hash": "dp_hash");
> >> > +    if (cl->hash_fields) {
> >> > +        ds_put_format(&ds, ",fields(%s)", cl->hash_fields);
> >> > +    }
> >> >
> >> >      BUILD_ASSERT(MFF_LOG_CT_ZONE >= MFF_REG0);
> >> >      BUILD_ASSERT(MFF_LOG_CT_ZONE < MFF_REG0 + FLOW_N_REGS);
> >> > @@ -1145,6 +1179,7 @@ static void
> >> >  ovnact_ct_lb_free(struct ovnact_ct_lb *ct_lb)
> >> >  {
> >> >      free(ct_lb->dsts);
> >> > +    free(ct_lb->hash_fields);
> >> >  }
> >> >
> >> >  static void
> >> > diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> >> > index 3f03e418a..d98d3a6b6 100644
> >> > --- a/northd/ovn-northd.c
> >> > +++ b/northd/ovn-northd.c
> >> > @@ -3112,7 +3112,7 @@ struct ovn_lb {
> >> >      struct hmap_node hmap_node;
> >> >
> >> >      const struct nbrec_load_balancer *nlb; /* May be NULL. */
> >> > -
> >> > +    char *selection_fields;
> >> >      struct lb_vip *vips;
> >> >      size_t n_vips;
> >> >  };
> >> > @@ -3340,6 +3340,15 @@ ovn_lb_create(struct northd_context *ctx,
> struct
> >> hmap *lbs,
> >> >          n_vips++;
> >> >      }
> >> >
> >> > +    if (lb->nlb->n_selection_fields) {
> >> > +        struct ds sel_fields = DS_EMPTY_INITIALIZER;
> >> > +        for (size_t i = 0; i < lb->nlb->n_selection_fields; i++) {
> >> > +            ds_put_format(&sel_fields, "%s,",
> >> lb->nlb->selection_fields[i]);
> >> > +        }
> >> > +        ds_chomp(&sel_fields, ',');
> >> > +        lb->selection_fields = ds_steal_cstr(&sel_fields);
> >> > +    }
> >> > +
> >> >      return lb;
> >> >  }
> >> >
> >> > @@ -3358,13 +3367,15 @@ ovn_lb_destroy(struct ovn_lb *lb)
> >> >          free(lb->vips[i].backends);
> >> >      }
> >> >      free(lb->vips);
> >> > +    free(lb->selection_fields);
> >> >  }
> >> >
> >> >  static void build_lb_vip_ct_lb_actions(struct lb_vip *lb_vip,
> >> > -                                       struct ds *action)
> >> > +                                       struct ds *action,
> >> > +                                       char *selection_fields)
> >> >  {
> >> >      if (lb_vip->health_check) {
> >> > -        ds_put_cstr(action, "ct_lb(");
> >> > +        ds_put_cstr(action, "ct_lb(backends=");
> >> >
> >> >          size_t n_active_backends = 0;
> >> >          for (size_t k = 0; k < lb_vip->n_backends; k++) {
> >> > @@ -3388,7 +3399,13 @@ static void build_lb_vip_ct_lb_actions(struct
> >> lb_vip *lb_vip,
> >> >              ds_put_cstr(action, ");");
> >> >          }
> >> >      } else {
> >> > -        ds_put_format(action, "ct_lb(%s);", lb_vip->backend_ips);
> >> > +        ds_put_format(action, "ct_lb(backends=%s);",
> >> lb_vip->backend_ips);
> >> > +    }
> >> > +
> >> > +    if (selection_fields && selection_fields[0]) {
> >> > +        ds_chomp(action, ';');
> >> > +        ds_chomp(action, ')');
> >> > +        ds_put_format(action, "; hash_fields=\"%s\");",
> >> selection_fields);
> >> >      }
> >> >  }
> >> >
> >> > @@ -5666,7 +5683,7 @@ build_lb_rules(struct ovn_datapath *od, struct
> hmap
> >> *lflows, struct ovn_lb *lb)
> >> >
> >> >          /* New connections in Ingress table. */
> >> >          struct ds action = DS_EMPTY_INITIALIZER;
> >> > -        build_lb_vip_ct_lb_actions(lb_vip, &action);
> >> > +        build_lb_vip_ct_lb_actions(lb_vip, &action,
> >> lb->selection_fields);
> >> >
> >> >          struct ds match = DS_EMPTY_INITIALIZER;
> >> >          ds_put_format(&match, "ct.new && %s.dst == %s", ip_match,
> >> lb_vip->vip);
> >> > @@ -9317,7 +9334,8 @@ build_lrouter_flows(struct hmap *datapaths,
> struct
> >> hmap *ports,
> >> >              for (size_t j = 0; j < lb->n_vips; j++) {
> >> >                  struct lb_vip *lb_vip = &lb->vips[j];
> >> >                  ds_clear(&actions);
> >> > -                build_lb_vip_ct_lb_actions(lb_vip, &actions);
> >> > +                build_lb_vip_ct_lb_actions(lb_vip, &actions,
> >> > +                                           lb->selection_fields);
> >> >
> >> >                  if (!sset_contains(&all_ips, lb_vip->vip)) {
> >> >                      sset_add(&all_ips, lb_vip->vip);
> >> > diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
> >> > index b2a046571..a06972aa0 100644
> >> > --- a/ovn-nb.ovsschema
> >> > +++ b/ovn-nb.ovsschema
> >> > @@ -1,7 +1,7 @@
> >> >  {
> >> >      "name": "OVN_Northbound",
> >> > -    "version": "5.22.0",
> >> > -    "cksum": "384164739 25476",
> >> > +    "version": "5.23.0",
> >> > +    "cksum": "111023208 25806",
> >> >      "tables": {
> >> >          "NB_Global": {
> >> >              "columns": {
> >> > @@ -179,6 +179,12 @@
> >> >                  "ip_port_mappings": {
> >> >                      "type": {"key": "string", "value": "string",
> >> >                               "min": 0, "max": "unlimited"}},
> >> > +                "selection_fields": {
> >> > +                    "type": {"key": {"type": "string",
> >> > +                             "enum": ["set",
> >> > +                                ["eth_src", "eth_dst", "ip_src",
> >> "ip_dst",
> >> > +                                 "tp_src", "tp_dst"]]},
> >> > +                             "min": 0, "max": "unlimited"}},
> >> >                  "external_ids": {
> >> >                      "type": {"key": "string", "value": "string",
> >> >                               "min": 0, "max": "unlimited"}}},
> >> > diff --git a/ovn-nb.xml b/ovn-nb.xml
> >> > index af15c550a..95ee4c9e6 100644
> >> > --- a/ovn-nb.xml
> >> > +++ b/ovn-nb.xml
> >> > @@ -1513,6 +1513,33 @@
> >> >        </p>
> >> >      </column>
> >> >
> >> > +    <column name="selection_fields">
> >> > +      <p>
> >> > +        OVN native load balancers are supported using the OpenFlow
> groups
> >> > +        of type <code>select</code>. OVS supports two selection
> methods:
> >> > +        <code>dp_hash</code> and <code>hash (with optional fields
> >> > +        specified)</code> in selecting the buckets of a group.
> >> > +        Please see the OVS documentation (man ovs-ofctl)
> >> > +        for more details on the selection methods. Each endpoint IP
> (and
> >> port
> >> > +        if set) is mapped to a bucket in the group flow.
> >> > +      </p>
> >> > +
> >> > +      <p>
> >> > +        CMS can choose the <code>hash</code> selection method by
> setting
> >> the
> >> > +        selection fields in this column. <code>ovs-vswitchd</code>
> uses
> >> the
> >> > +        specified fields in generating the hash.
> >> > +      </p>
> >> > +
> >> > +      <p>
> >> > +        <code>dp_hash</code> selection method uses the assistance of
> >> > +        datapath to calculate the hash and it is expected to be
> >> > +        faster than <code>hash</code> selection method. So CMS should
> >> take
> >> > +        this into consideration before using the <code>hash</code>
> >> method.
> >> > +        Please consult the OVS documentation and OVS sources for the
> >> > +        implementation details.
> >> > +      </p>
> >> > +    </column>
> >> > +
> >> >      <group title="Common Columns">
> >> >        <column name="external_ids">
> >> >          See <em>External IDs</em> at the beginning of this document.
> >> > diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> >> > index a0109ae94..57c9eedd5 100644
> >> > --- a/tests/ovn-northd.at
> >> > +++ b/tests/ovn-northd.at
> >> > @@ -1183,7 +1183,7 @@ ovn-nbctl --wait=sb ls-lb-add sw0 lb1
> >> >
> >> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 >
> lflows.txt
> >> >  AT_CHECK([cat lflows.txt], [0], [dnl
> >> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
> >> ,20.0.0.3:80);)
> >> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=
> 10.0.0.3:80
> >> ,20.0.0.3:80);)
> >> >  ])
> >> >
> >> >  # Delete the Load_Balancer_Health_Check
> >> > @@ -1192,7 +1192,7 @@ OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list
> >> service_monitor |  wc -l`])
> >> >
> >> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 >
> lflows.txt
> >> >  AT_CHECK([cat lflows.txt], [0], [dnl
> >> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
> >> ,20.0.0.3:80);)
> >> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=
> 10.0.0.3:80
> >> ,20.0.0.3:80);)
> >> >  ])
> >> >
> >> >  # Create the Load_Balancer_Health_Check again.
> >> > @@ -1205,7 +1205,7 @@ service_monitor | sed '/^$/d' | wc -l`])
> >> >
> >> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 >
> lflows.txt
> >> >  AT_CHECK([cat lflows.txt], [0], [dnl
> >> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
> >> ,20.0.0.3:80);)
> >> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=
> 10.0.0.3:80
> >> ,20.0.0.3:80);)
> >> >  ])
> >> >
> >> >  # Get the uuid of both the service_monitor
> >> > @@ -1221,7 +1221,7 @@ OVS_WAIT_UNTIL([
> >> >
> >> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 >
> lflows.txt
> >> >  AT_CHECK([cat lflows.txt], [0], [dnl
> >> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80);)
> >> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=
> 10.0.0.3:80
> >> );)
> >> >  ])
> >> >
> >> >  # Set the service monitor for sw0-p1 to offline
> >> > @@ -1251,7 +1251,7 @@ OVS_WAIT_UNTIL([
> >> >
> >> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 >
> lflows.txt
> >> >  AT_CHECK([cat lflows.txt], [0], [dnl
> >> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
> >> ,20.0.0.3:80);)
> >> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=
> 10.0.0.3:80
> >> ,20.0.0.3:80);)
> >> >  ])
> >> >
> >> >  # Set the service monitor for sw1-p1 to error
> >> > @@ -1263,7 +1263,7 @@ OVS_WAIT_UNTIL([
> >> >  ovn-sbctl dump-flows sw0 | grep "ip4.dst == 10.0.0.10 && tcp.dst ==
> 80" \
> >> >  | grep priority=120 > lflows.txt
> >> >  AT_CHECK([cat lflows.txt], [0], [dnl
> >> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80);)
> >> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=
> 10.0.0.3:80
> >> );)
> >> >  ])
> >> >
> >> >  # Add one more vip to lb1
> >> > @@ -1293,8 +1293,8 @@ service_monitor port=1000 | sed '/^$/d' | wc
> -l`])
> >> >
> >> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 >
> lflows.txt
> >> >  AT_CHECK([cat lflows.txt], [0], [dnl
> >> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80);)
> >> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(10.0.0.3:1000
> );)
> >> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=
> 10.0.0.3:80
> >> );)
> >> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.40 && tcp.dst == 1000),
> >> action=(ct_lb(backends=10.0.0.3:1000);)
> >> >  ])
> >> >
> >> >  # Set the service monitor for sw1-p1 to online
> >> > @@ -1306,16 +1306,16 @@ OVS_WAIT_UNTIL([
> >> >
> >> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 >
> lflows.txt
> >> >  AT_CHECK([cat lflows.txt], [0], [dnl
> >> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
> >> ,20.0.0.3:80);)
> >> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(10.0.0.3:1000
> >> ,20.0.0.3:80);)
> >> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=
> 10.0.0.3:80
> >> ,20.0.0.3:80);)
> >> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(backends=
> >> 10.0.0.3:1000,20.0.0.3:80);)
> >> >  ])
> >> >
> >> >  # Associate lb1 to sw1
> >> >  ovn-nbctl --wait=sb ls-lb-add sw1 lb1
> >> >  ovn-sbctl dump-flows sw1 | grep ct_lb | grep priority=120 >
> lflows.txt
> >> >  AT_CHECK([cat lflows.txt], [0], [dnl
> >> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
> >> ,20.0.0.3:80);)
> >> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(10.0.0.3:1000
> >> ,20.0.0.3:80);)
> >> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=
> 10.0.0.3:80
> >> ,20.0.0.3:80);)
> >> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(backends=
> >> 10.0.0.3:1000,20.0.0.3:80);)
> >> >  ])
> >> >
> >> >  # Now create lb2 same as lb1 but udp protocol.
> >> > diff --git a/tests/ovn.at b/tests/ovn.at
> >> > index 7befc8224..055fb57e2 100644
> >> > --- a/tests/ovn.at
> >> > +++ b/tests/ovn.at
> >> > @@ -970,29 +970,42 @@ ct_lb();
> >> >      encodes as ct(table=19,zone=NXM_NX_REG13[0..15],nat)
> >> >      has prereqs ip
> >> >  ct_lb(192.168.1.2:80, 192.168.1.3:80);
> >> > +    Syntax error at `192.168.1.2' expecting backends.
> >> > +ct_lb(backends=192.168.1.2:80,192.168.1.3:80);
> >> >      encodes as group:1
> >> >      uses group: id(1),
> >>
>
> name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=ct(nat(dst=
> >> 192.168.1.2:80
> >>
>
> ),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=
> >> 192.168.1.3:80),commit,table=19,zone=NXM_NX_REG13[0..15]))
> >> >      has prereqs ip
> >> > -ct_lb(192.168.1.2, 192.168.1.3, );
> >> > -    formats as ct_lb(192.168.1.2, 192.168.1.3);
> >> > +ct_lb(backends=192.168.1.2, 192.168.1.3, );
> >> > +    formats as ct_lb(backends=192.168.1.2,192.168.1.3);
> >> >      encodes as group:2
> >> >      uses group: id(2),
> >>
>
> name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=ct(nat(dst=192.168.1.2),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=192.168.1.3),commit,table=19,zone=NXM_NX_REG13[0..15]))
> >> >      has prereqs ip
> >> > -ct_lb(fd0f::2, fd0f::3, );
> >> > -    formats as ct_lb(fd0f::2, fd0f::3);
> >> > +ct_lb(backends=fd0f::2, fd0f::3, );
> >> > +    formats as ct_lb(backends=fd0f::2,fd0f::3);
> >> >      encodes as group:3
> >> >      uses group: id(3),
> >>
>
> name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=ct(nat(dst=fd0f::2),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=fd0f::3),commit,table=19,zone=NXM_NX_REG13[0..15]))
> >> >      has prereqs ip
> >> >
> >> > -ct_lb(192.168.1.2:);
> >> > +ct_lb(backends=192.168.1.2:);
> >> >      Syntax error at `)' expecting port number.
> >> > -ct_lb(192.168.1.2:123456);
> >> > +ct_lb(backends=192.168.1.2:123456);
> >> >      Syntax error at `123456' expecting port number.
> >> > -ct_lb(foo);
> >> > +ct_lb(backends=foo);
> >> >      Syntax error at `foo' expecting IP address.
> >> > -ct_lb([192.168.1.2]);
> >> > +ct_lb(backends=[192.168.1.2]);
> >> >      Syntax error at `192.168.1.2' expecting IPv6 address.
> >> >
> >> > +ct_lb(backends=192.168.1.2:80,192.168.1.3:80;
> >> hash_fields=eth_src,eth_dst,ip_src);
> >> > +    Syntax error at `eth_src' invalid hash_fields.
> >> > +ct_lb(backends=192.168.1.2:80,192.168.1.3:80;
> >> hash_fields="eth_src,eth_dst,ip_src");
> >> > +    encodes as group:4
> >> > +    uses group: id(4),
> >>
>
> name(type=select,selection_method=hash,fields(eth_src,eth_dst,ip_src),bucket=bucket_id=0,weight:100,actions=ct(nat(dst=
> >> 192.168.1.2:80
> >>
>
> ),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=
> >> 192.168.1.3:80),commit,table=19,zone=NXM_NX_REG13[0..15]))
> >> > +    has prereqs ip
> >> > +ct_lb(backends=fd0f::2,fd0f::3;
> >> hash_fields="eth_src,eth_dst,ip_src,ip_dst,tp_src,tp_dst");
> >> > +    encodes as group:5
> >> > +    uses group: id(5),
> >>
>
> name(type=select,selection_method=hash,fields(eth_src,eth_dst,ip_src,ip_dst,tp_src,tp_dst),bucket=bucket_id=0,weight:100,actions=ct(nat(dst=fd0f::2),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=fd0f::3),commit,table=19,zone=NXM_NX_REG13[0..15]))
> >> > +    has prereqs ip
> >> > +
> >> >  # ct_next
> >> >  ct_next;
> >> >      encodes as ct(table=19,zone=NXM_NX_REG13[0..15])
> >> > @@ -1520,13 +1533,13 @@ handle_svc_check(reg0);
> >> >  # select
> >> >  reg9[16..31] = select(1=50, 2=100, 3, );
> >> >      formats as reg9[16..31] = select(1=50, 2=100, 3=100);
> >> > -    encodes as group:4
> >> > -    uses group: id(4),
> >>
>
> name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:50,actions=load:1->xreg4[16..31],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xreg4[16..31],resubmit(,19),bucket=bucket_id=2,weight:100,actions=load:3->xreg4[16..31],resubmit(,19))
> >> > +    encodes as group:6
> >> > +    uses group: id(6),
> >>
>
> name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:50,actions=load:1->xreg4[16..31],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xreg4[16..31],resubmit(,19),bucket=bucket_id=2,weight:100,actions=load:3->xreg4[16..31],resubmit(,19))
> >> >
> >> >  reg0 = select(1, 2);
> >> >      formats as reg0 = select(1=100, 2=100);
> >> > -    encodes as group:5
> >> > -    uses group: id(5),
> >>
>
> name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=load:1->xxreg0[96..127],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xxreg0[96..127],resubmit(,19))
> >> > +    encodes as group:7
> >> > +    uses group: id(7),
> >>
>
> name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=load:1->xxreg0[96..127],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xxreg0[96..127],resubmit(,19))
> >> >
> >> >  reg0 = select(1=, 2);
> >> >      Syntax error at `,' expecting weight.
> >> > @@ -1542,12 +1555,12 @@ reg0[0..14] = select(1, 2, 3);
> >> >      cannot use 15-bit field reg0[0..14] for "select", which requires
> at
> >> least 16 bits.
> >> >
> >> >  fwd_group(liveness="true", childports="eth0", "lsp1");
> >> > -    encodes as group:6
> >> > -    uses group: id(6),
> >>
>
> name(type=select,selection_method=dp_hash,bucket=watch_port:5,load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=watch_port:17,load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
> >> > +    encodes as group:8
> >> > +    uses group: id(8),
> >>
>
> name(type=select,selection_method=dp_hash,bucket=watch_port:5,load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=watch_port:17,load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
> >> >
> >> >  fwd_group(childports="eth0", "lsp1");
> >> > -    encodes as group:7
> >> > -    uses group: id(7),
> >>
>
> name(type=select,selection_method=dp_hash,bucket=load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
> >> > +    encodes as group:9
> >> > +    uses group: id(9),
> >>
>
> name(type=select,selection_method=dp_hash,bucket=load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
> >> >
> >> >  fwd_group(childports=eth0);
> >> >      Syntax error at `eth0' expecting logical switch port.
> >> > @@ -18000,12 +18013,12 @@ service_monitor | sed '/^$/d' | wc -l`])
> >> >
> >> >  ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 >
> lflows.txt
> >> >  AT_CHECK([cat lflows.txt], [0], [dnl
> >> > -  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80
> >> ,20.0.0.3:80);)
> >> > +  table=10(ls_in_stateful     ), priority=120  , match=(ct.new &&
> >> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=
> 10.0.0.3:80
> >> ,20.0.0.3:80);)
> >> >  ])
> >> >
> >> >  ovn-sbctl dump-flows lr0 | grep ct_lb | grep priority=120 >
> lflows.txt
> >> >  AT_CHECK([cat lflows.txt], [0], [dnl
> >> > -  table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip
> &&
> >> ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 &&
> >> is_chassis_resident("cr-lr0-public")), action=(ct_lb(10.0.0.3:80,
> 20.0.0.3:80
> >> );)
> >> > +  table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip
> &&
> >> ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 &&
> >> is_chassis_resident("cr-lr0-public")), action=(ct_lb(backends=
> 10.0.0.3:80
> >> ,20.0.0.3:80);)
> >> >  ])
> >> >
> >> >  # get the svc monitor mac.
> >> > diff --git a/tests/system-ovn.at b/tests/system-ovn.at
> >> > index fa3b83cb1..32ae60925 100644
> >> > --- a/tests/system-ovn.at
> >> > +++ b/tests/system-ovn.at
> >> > @@ -1095,15 +1095,15 @@ ovn-nbctl lsp-add bar bar3 \
> >> >  -- lsp-set-addresses bar3 "f0:00:0f:01:02:05 172.16.1.4"
> >> >
> >> >  # Config OVN load-balancer with a VIP.
> >> > -uuid=`ovn-nbctl  create load_balancer
> >> vips:30.0.0.1="172.16.1.2,172.16.1.3,172.16.1.4"`
> >> > -ovn-nbctl set logical_switch foo load_balancer=$uuid
> >> > +ovn-nbctl lb-add lb1 30.0.0.1 "172.16.1.2,172.16.1.3,172.16.1.4"
> >> > +ovn-nbctl ls-lb-add foo lb1
> >> >
> >> >  # Create another load-balancer with another VIP.
> >> > -uuid=`ovn-nbctl create load_balancer
> >> vips:30.0.0.3="172.16.1.2,172.16.1.3,172.16.1.4"`
> >> > -ovn-nbctl add logical_switch foo load_balancer $uuid
> >> > +lb2_uuid=`ovn-nbctl create load_balancer name=lb2
> >> vips:30.0.0.3="172.16.1.2,172.16.1.3,172.16.1.4"`
> >> > +ovn-nbctl ls-lb-add foo lb2
> >> >
> >> >  # Config OVN load-balancer with another VIP (this time with ports).
> >> > -ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"
> 172.16.1.2:80,
> >> 172.16.1.3:80,172.16.1.4:80"'
> >> > +ovn-nbctl set load_balancer $lb2_uuid vips:'"30.0.0.2:8000"'='"
> >> 172.16.1.2:80,172.16.1.3:80,172.16.1.4:80"'
> >> >
> >> >  # Wait for ovn-controller to catch up.
> >> >  ovn-nbctl --wait=hv sync
> >> > @@ -1157,6 +1157,82 @@
> >>
>
> tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(s
> >> >
> >>
>
>  tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
> >> >  ])
> >> >
> >> > +# Configure selection_fields.
> >> > +ovn-nbctl set load_balancer $lb2_uuid
> >> selection_fields="ip_src,ip_dst,tp_src,tp_dst"
> >> > +OVS_WAIT_UNTIL([
> >> > +    test $(ovs-ofctl dump-groups br-int | \
> >> > +    grep
> "selection_method=hash,fields(ip_src,ip_dst,tcp_src,tcp_dst)"
> >> -c) -eq 2
> >> > +])
> >> > +
> >> > +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> >> > +
> >> > +dnl Test load-balancing that includes L4 ports in NAT.
> >> > +for i in `seq 1 20`; do
> >> > +    echo Request $i
> >> > +    NS_CHECK_EXEC([foo1], [wget 30.0.0.2:8000 -t 5 -T 1
> >> --retry-connrefused -v -o wget$i.log])
> >> > +done
> >> > +
> >> > +dnl Each server should have at least one connection.
> >> > +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | \
> >> > +sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
> >> >
> >>
>
> +tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
> >> >
> >>
>
> +tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
> >> >
> >>
>
> +tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
> >> > +])
> >> > +
> >> > +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> >> > +
> >> > +echo "foo" > foo
> >> > +for i in `seq 1 20`; do
> >> > +    echo Request $i
> >> > +    ip netns exec foo1 nc -p 30000 30.0.0.2 8000 < foo
> >> > +done
> >> > +
> >> > +dnl Only one backend should be chosen.
> >> > +AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 -c)
> -eq
> >> 1])
> >> > +
> >> > +ovn-nbctl set load_balancer $lb2_uuid selection_fields="ip_src"
> >> > +OVS_WAIT_UNTIL([
> >> > +    test $(ovs-ofctl dump-groups br-int | \
> >> > +    grep "selection_method=hash,fields=ip_src" -c) -eq 2
> >> > +])
> >> > +
> >> > +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> >> > +for i in `seq 1 20`; do
> >> > +    echo Request $i
> >> > +    ip netns exec foo1 nc 30.0.0.2 8000 < foo
> >> > +done
> >> > +
> >> > +dnl Only one backend should be chosen as eth_src and ip_src is fixed.
> >> > +bar1_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep
> >> 172.16.1.2 -c)
> >> > +bar2_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep
> >> 172.16.1.3 -c)
> >> > +bar3_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep
> >> 172.16.1.4 -c)
> >> > +
> >> > +AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 |
> grep
> >> 172.16.1 -c) -ne 0])
> >> > +
> >> > +if [[ "$bar1_ct" == "20" ]]; then
> >> > +    AT_CHECK([test $bar1_ct -eq 20])
> >> > +    AT_CHECK([test $bar2_ct -eq 0])
> >> > +    AT_CHECK([test $bar3_ct -eq 0])
> >> > +else
> >> > +    AT_CHECK([test $bar1_ct -eq 0])
> >> > +fi
> >> > +
> >> > +if [[ "$bar2_ct" == "20" ]]; then
> >> > +    AT_CHECK([test $bar1_ct -eq 20])
> >> > +    AT_CHECK([test $bar2_ct -eq 0])
> >> > +    AT_CHECK([test $bar3_ct -eq 0])
> >> > +else
> >> > +    AT_CHECK([test $bar2_ct -eq 0])
> >> > +fi
> >> > +
> >> > +if [[ "$bar3_ct" == "20" ]]; then
> >> > +    AT_CHECK([test $bar1_ct -eq 20])
> >> > +    AT_CHECK([test $bar2_ct -eq 0])
> >> > +    AT_CHECK([test $bar3_ct -eq 0])
> >> > +else
> >> > +    AT_CHECK([test $bar3_ct -eq 0])
> >> > +fi
> >> >
> >> >  OVS_APP_EXIT_AND_WAIT([ovn-controller])
> >> >
> >> > @@ -1246,11 +1322,11 @@ uuid=`ovn-nbctl  create load_balancer
> >> vips:\"fd03::1\"=\"fd02::2,fd02::3,fd02::4
> >> >  ovn-nbctl set logical_switch foo load_balancer=$uuid
> >> >
> >> >  # Create another load-balancer with another VIP.
> >> > -uuid=`ovn-nbctl create load_balancer
> >> vips:\"fd03::3\"=\"fd02::2,fd02::3,fd02::4\"`
> >> > -ovn-nbctl add logical_switch foo load_balancer $uuid
> >> > +lb2_uuid=`ovn-nbctl create load_balancer
> >> vips:\"fd03::3\"=\"fd02::2,fd02::3,fd02::4\"`
> >> > +ovn-nbctl add logical_switch foo load_balancer $lb2_uuid
> >> >
> >> >  # Config OVN load-balancer with another VIP (this time with ports).
> >> > -ovn-nbctl set load_balancer $uuid
> >> vips:'"[[fd03::2]]:8000"'='"@<:@fd02::2@:>@:80,@<:@fd02::3@
> >> :>@:80,@<:@fd02::4@:>@:80"'
> >> > +ovn-nbctl set load_balancer $lb2_uuid
> >> vips:'"[[fd03::2]]:8000"'='"@<:@fd02::2@:>@:80,@<:@fd02::3@
> >> :>@:80,@<:@fd02::4@:>@:80"'
> >> >
> >> >  # Wait for ovn-controller to catch up.
> >> >  ovn-nbctl --wait=hv sync
> >> > @@ -1304,7 +1380,83 @@
> >>
>
> tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd
> >> >
> >>
>
>  tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::4,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
> >> >  ])
> >> >
> >> > +# Configure selection_fields.
> >> > +ovn-nbctl set load_balancer $lb2_uuid
> >> selection_fields="ip_src,ip_dst,tp_src,tp_dst"
> >> > +OVS_WAIT_UNTIL([
> >> > +    test $(ovs-ofctl dump-groups br-int | \
> >> > +    grep
> "selection_method=hash,fields(ip_src,ip_dst,tcp_src,tcp_dst)"
> >> -c) -eq 2
> >> > +])
> >> > +
> >> > +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> >> > +
> >> > +dnl Test load-balancing that includes L4 ports in NAT.
> >> > +for i in `seq 1 20`; do
> >> > +    echo Request $i
> >> > +    NS_CHECK_EXEC([foo1], [wget http://[[fd03::2]]:8000 -t 5 -T 1
> >> --retry-connrefused -v -o wget$i.log])
> >> > +done
> >> > +
> >> > +dnl Each server should have at least one connection.
> >> > +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd03::2) | grep
> -v
> >> fe80 | \
> >> > +sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
> >> >
> >>
>
> +tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::2,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
> >> >
> >>
>
> +tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::3,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
> >> >
> >>
>
> +tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::4,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
> >> > +])
> >> > +
> >> > +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> >> > +
> >> > +echo "foo" > foo
> >> > +for i in `seq 1 20`; do
> >> > +    echo Request $i
> >> > +    ip netns exec foo1 nc -6 -p 30000 fd03::2 8000 < foo
> >> > +done
> >> > +
> >> > +# Only one backend should be chosen. Since the source port is fixed,
> >> > +# there should be only one conntrack entry.
> >> > +AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep fd03::2 -c)
> -eq
> >> 1])
> >> > +
> >> > +ovn-nbctl set load_balancer $lb2_uuid
> selection_fields="eth_src,ip_src"
> >> > +OVS_WAIT_UNTIL([
> >> > +    test $(ovs-ofctl dump-groups br-int | \
> >> > +    grep "selection_method=hash,fields(eth_src,ip_src)" -c) -eq 2
> >> > +])
> >> > +
> >> > +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> >> > +for i in `seq 1 20`; do
> >> > +    echo Request $i
> >> > +    ip netns exec foo1 nc -6 fd03::2 8000 < foo
> >> > +done
> >> >
> >> > +dnl Only one backend should be chosen as eth_src and ip_src is fixed.
> >> > +bar1_ct=$(ovs-appctl dpctl/dump-conntrack | grep fd03::2 | grep
> fd02::2
> >> -c)
> >> > +bar2_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep
> fd02::3
> >> -c)
> >> > +bar3_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep
> fd02::4
> >> -c)
> >> > +
> >> > +AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep fd03::2 |
> grep
> >> fd02 -c) -ne 0])
> >> > +
> >> > +if [[ "$bar1_ct" == "20" ]]; then
> >> > +    AT_CHECK([test $bar1_ct -eq 20])
> >> > +    AT_CHECK([test $bar2_ct -eq 0])
> >> > +    AT_CHECK([test $bar3_ct -eq 0])
> >> > +else
> >> > +    AT_CHECK([test $bar1_ct -eq 0])
> >> > +fi
> >> > +
> >> > +if [[ "$bar2_ct" == "20" ]]; then
> >> > +    AT_CHECK([test $bar1_ct -eq 20])
> >> > +    AT_CHECK([test $bar2_ct -eq 0])
> >> > +    AT_CHECK([test $bar3_ct -eq 0])
> >> > +else
> >> > +    AT_CHECK([test $bar2_ct -eq 0])
> >> > +fi
> >> > +
> >> > +if [[ "$bar3_ct" == "20" ]]; then
> >> > +    AT_CHECK([test $bar1_ct -eq 20])
> >> > +    AT_CHECK([test $bar2_ct -eq 0])
> >> > +    AT_CHECK([test $bar3_ct -eq 0])
> >> > +else
> >> > +    AT_CHECK([test $bar3_ct -eq 0])
> >> > +fi
> >> >  OVS_APP_EXIT_AND_WAIT([ovn-controller])
> >> >
> >> >  as ovn-sb
> >> > @@ -3448,7 +3600,7 @@ service_monitor | sed '/^$/d' | grep online | wc
> >> -l`])
> >> >
> >> >  OVS_WAIT_UNTIL(
> >> >      [ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 | grep
> >> "ip4.dst == 10.0.0.10" > lflows.txt
> >> > -     test 1 = `cat lflows.txt | grep "ct_lb(10.0.0.3:80,20.0.0.3:80
> )"
> |
> >> wc -l`]
> >> > +     test 1 = `cat lflows.txt | grep "ct_lb(backends=10.0.0.3:80,
> >> 20.0.0.3:80)" | wc -l`]
> >> >  )
> >> >
> >> >  # From sw0-p2 send traffic to vip - 10.0.0.10
> >> > @@ -3474,7 +3626,7 @@ service_monitor logical_port=sw0-p1 | sed
> '/^$/d' |
> >> grep offline | wc -l`])
> >> >
> >> >  OVS_WAIT_UNTIL(
> >> >      [ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 | grep
> >> "ip4.dst == 10.0.0.10" > lflows.txt
> >> > -     test 1 = `cat lflows.txt | grep "ct_lb(20.0.0.3:80)" | wc -l`]
> >> > +     test 1 = `cat lflows.txt | grep "ct_lb(backends=20.0.0.3:80)" |
> wc
> >> -l`]
> >> >  )
> >> >
> >> >  ovs-appctl dpctl/flush-conntrack
> >> > --
> >> > 2.26.2
> >> >
> >> > _______________________________________________
> >> > 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
> >>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
>
diff mbox series

Patch

diff --git a/NEWS b/NEWS
index c2b7945df..431b4cf9c 100644
--- a/NEWS
+++ b/NEWS
@@ -14,6 +14,9 @@  OVN v20.03.0 - xx xxx xxxx
    - Added support for ECMP routes in OVN router.
    - Added IPv6 Prefix Delegation support in OVN.
    - OVN now uses OpenFlow 1.5.
+   - Added support to choose selection methods - dp_hash or
+     hash (with specified hash fields) for OVN load balancer
+     backend selection.
 
    - OVN Interconnection:
      * Support for L3 interconnection of multiple OVN deployments with tunnels
diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index 42d45a74c..4a54abe17 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -259,6 +259,7 @@  struct ovnact_ct_lb {
     struct ovnact_ct_lb_dst *dsts;
     size_t n_dsts;
     uint8_t ltable;             /* Logical table ID of next table. */
+    char *hash_fields;
 };
 
 struct ovnact_select_dst {
diff --git a/lib/actions.c b/lib/actions.c
index 021a376b4..c50615177 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -951,9 +951,18 @@  parse_ct_lb_action(struct action_context *ctx)
     struct ovnact_ct_lb_dst *dsts = NULL;
     size_t allocated_dsts = 0;
     size_t n_dsts = 0;
+    char *hash_fields = NULL;
 
-    if (lexer_match(ctx->lexer, LEX_T_LPAREN)) {
-        while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
+    if (lexer_match(ctx->lexer, LEX_T_LPAREN) &&
+        !lexer_match(ctx->lexer, LEX_T_RPAREN)) {
+        if (!lexer_match_id(ctx->lexer, "backends") ||
+            !lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
+            lexer_syntax_error(ctx->lexer, "expecting backends");
+            return;
+        }
+
+        while (!lexer_match(ctx->lexer, LEX_T_SEMICOLON) &&
+               !lexer_match(ctx->lexer, LEX_T_RPAREN)) {
             struct ovnact_ct_lb_dst dst;
             if (lexer_match(ctx->lexer, LEX_T_LSQUARE)) {
                 /* IPv6 address and port */
@@ -1020,12 +1029,27 @@  parse_ct_lb_action(struct action_context *ctx)
             }
             dsts[n_dsts++] = dst;
         }
+
+        if (lexer_match_id(ctx->lexer, "hash_fields")) {
+            if (!lexer_match(ctx->lexer, LEX_T_EQUALS) ||
+                ctx->lexer->token.type != LEX_T_STRING ||
+                lexer_lookahead(ctx->lexer) != LEX_T_RPAREN) {
+                lexer_syntax_error(ctx->lexer, "invalid hash_fields");
+                free(dsts);
+                return;
+            }
+
+            hash_fields = xstrdup(ctx->lexer->token.s);
+            lexer_get(ctx->lexer);
+            lexer_get(ctx->lexer);
+        }
     }
 
     struct ovnact_ct_lb *cl = ovnact_put_CT_LB(ctx->ovnacts);
     cl->ltable = ctx->pp->cur_ltable + 1;
     cl->dsts = dsts;
     cl->n_dsts = n_dsts;
+    cl->hash_fields = hash_fields;
 }
 
 static void
@@ -1033,10 +1057,10 @@  format_CT_LB(const struct ovnact_ct_lb *cl, struct ds *s)
 {
     ds_put_cstr(s, "ct_lb");
     if (cl->n_dsts) {
-        ds_put_char(s, '(');
+        ds_put_cstr(s, "(backends=");
         for (size_t i = 0; i < cl->n_dsts; i++) {
             if (i) {
-                ds_put_cstr(s, ", ");
+                ds_put_char(s, ',');
             }
 
             const struct ovnact_ct_lb_dst *dst = &cl->dsts[i];
@@ -1056,7 +1080,13 @@  format_CT_LB(const struct ovnact_ct_lb *cl, struct ds *s)
             }
         }
         ds_put_char(s, ')');
+
+        if (cl->hash_fields) {
+            ds_chomp(s, ')');
+            ds_put_format(s, "; hash_fields=\"%s\")", cl->hash_fields);
+        }
     }
+
     ds_put_char(s, ';');
 }
 
@@ -1103,7 +1133,11 @@  encode_CT_LB(const struct ovnact_ct_lb *cl,
                             : MFF_LOG_DNAT_ZONE - MFF_REG0;
 
     struct ds ds = DS_EMPTY_INITIALIZER;
-    ds_put_format(&ds, "type=select,selection_method=dp_hash");
+    ds_put_format(&ds, "type=select,selection_method=%s",
+                  cl->hash_fields ? "hash": "dp_hash");
+    if (cl->hash_fields) {
+        ds_put_format(&ds, ",fields(%s)", cl->hash_fields);
+    }
 
     BUILD_ASSERT(MFF_LOG_CT_ZONE >= MFF_REG0);
     BUILD_ASSERT(MFF_LOG_CT_ZONE < MFF_REG0 + FLOW_N_REGS);
@@ -1145,6 +1179,7 @@  static void
 ovnact_ct_lb_free(struct ovnact_ct_lb *ct_lb)
 {
     free(ct_lb->dsts);
+    free(ct_lb->hash_fields);
 }
 
 static void
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index 3f03e418a..d98d3a6b6 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -3112,7 +3112,7 @@  struct ovn_lb {
     struct hmap_node hmap_node;
 
     const struct nbrec_load_balancer *nlb; /* May be NULL. */
-
+    char *selection_fields;
     struct lb_vip *vips;
     size_t n_vips;
 };
@@ -3340,6 +3340,15 @@  ovn_lb_create(struct northd_context *ctx, struct hmap *lbs,
         n_vips++;
     }
 
+    if (lb->nlb->n_selection_fields) {
+        struct ds sel_fields = DS_EMPTY_INITIALIZER;
+        for (size_t i = 0; i < lb->nlb->n_selection_fields; i++) {
+            ds_put_format(&sel_fields, "%s,", lb->nlb->selection_fields[i]);
+        }
+        ds_chomp(&sel_fields, ',');
+        lb->selection_fields = ds_steal_cstr(&sel_fields);
+    }
+
     return lb;
 }
 
@@ -3358,13 +3367,15 @@  ovn_lb_destroy(struct ovn_lb *lb)
         free(lb->vips[i].backends);
     }
     free(lb->vips);
+    free(lb->selection_fields);
 }
 
 static void build_lb_vip_ct_lb_actions(struct lb_vip *lb_vip,
-                                       struct ds *action)
+                                       struct ds *action,
+                                       char *selection_fields)
 {
     if (lb_vip->health_check) {
-        ds_put_cstr(action, "ct_lb(");
+        ds_put_cstr(action, "ct_lb(backends=");
 
         size_t n_active_backends = 0;
         for (size_t k = 0; k < lb_vip->n_backends; k++) {
@@ -3388,7 +3399,13 @@  static void build_lb_vip_ct_lb_actions(struct lb_vip *lb_vip,
             ds_put_cstr(action, ");");
         }
     } else {
-        ds_put_format(action, "ct_lb(%s);", lb_vip->backend_ips);
+        ds_put_format(action, "ct_lb(backends=%s);", lb_vip->backend_ips);
+    }
+
+    if (selection_fields && selection_fields[0]) {
+        ds_chomp(action, ';');
+        ds_chomp(action, ')');
+        ds_put_format(action, "; hash_fields=\"%s\");", selection_fields);
     }
 }
 
@@ -5666,7 +5683,7 @@  build_lb_rules(struct ovn_datapath *od, struct hmap *lflows, struct ovn_lb *lb)
 
         /* New connections in Ingress table. */
         struct ds action = DS_EMPTY_INITIALIZER;
-        build_lb_vip_ct_lb_actions(lb_vip, &action);
+        build_lb_vip_ct_lb_actions(lb_vip, &action, lb->selection_fields);
 
         struct ds match = DS_EMPTY_INITIALIZER;
         ds_put_format(&match, "ct.new && %s.dst == %s", ip_match, lb_vip->vip);
@@ -9317,7 +9334,8 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
             for (size_t j = 0; j < lb->n_vips; j++) {
                 struct lb_vip *lb_vip = &lb->vips[j];
                 ds_clear(&actions);
-                build_lb_vip_ct_lb_actions(lb_vip, &actions);
+                build_lb_vip_ct_lb_actions(lb_vip, &actions,
+                                           lb->selection_fields);
 
                 if (!sset_contains(&all_ips, lb_vip->vip)) {
                     sset_add(&all_ips, lb_vip->vip);
diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
index b2a046571..a06972aa0 100644
--- a/ovn-nb.ovsschema
+++ b/ovn-nb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Northbound",
-    "version": "5.22.0",
-    "cksum": "384164739 25476",
+    "version": "5.23.0",
+    "cksum": "111023208 25806",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -179,6 +179,12 @@ 
                 "ip_port_mappings": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}},
+                "selection_fields": {
+                    "type": {"key": {"type": "string",
+                             "enum": ["set",
+                                ["eth_src", "eth_dst", "ip_src", "ip_dst",
+                                 "tp_src", "tp_dst"]]},
+                             "min": 0, "max": "unlimited"}},
                 "external_ids": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}}},
diff --git a/ovn-nb.xml b/ovn-nb.xml
index af15c550a..95ee4c9e6 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -1513,6 +1513,33 @@ 
       </p>
     </column>
 
+    <column name="selection_fields">
+      <p>
+        OVN native load balancers are supported using the OpenFlow groups
+        of type <code>select</code>. OVS supports two selection methods:
+        <code>dp_hash</code> and <code>hash (with optional fields
+        specified)</code> in selecting the buckets of a group.
+        Please see the OVS documentation (man ovs-ofctl)
+        for more details on the selection methods. Each endpoint IP (and port
+        if set) is mapped to a bucket in the group flow.
+      </p>
+
+      <p>
+        CMS can choose the <code>hash</code> selection method by setting the
+        selection fields in this column. <code>ovs-vswitchd</code> uses the
+        specified fields in generating the hash.
+      </p>
+
+      <p>
+        <code>dp_hash</code> selection method uses the assistance of
+        datapath to calculate the hash and it is expected to be
+        faster than <code>hash</code> selection method. So CMS should take
+        this into consideration before using the <code>hash</code> method.
+        Please consult the OVS documentation and OVS sources for the
+        implementation details.
+      </p>
+    </column>
+
     <group title="Common Columns">
       <column name="external_ids">
         See <em>External IDs</em> at the beginning of this document.
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index a0109ae94..57c9eedd5 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -1183,7 +1183,7 @@  ovn-nbctl --wait=sb ls-lb-add sw0 lb1
 
 ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
 AT_CHECK([cat lflows.txt], [0], [dnl
-  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80,20.0.0.3:80);)
+  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
 ])
 
 # Delete the Load_Balancer_Health_Check
@@ -1192,7 +1192,7 @@  OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list service_monitor |  wc -l`])
 
 ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
 AT_CHECK([cat lflows.txt], [0], [dnl
-  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80,20.0.0.3:80);)
+  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
 ])
 
 # Create the Load_Balancer_Health_Check again.
@@ -1205,7 +1205,7 @@  service_monitor | sed '/^$/d' | wc -l`])
 
 ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
 AT_CHECK([cat lflows.txt], [0], [dnl
-  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80,20.0.0.3:80);)
+  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
 ])
 
 # Get the uuid of both the service_monitor
@@ -1221,7 +1221,7 @@  OVS_WAIT_UNTIL([
 
 ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
 AT_CHECK([cat lflows.txt], [0], [dnl
-  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80);)
+  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80);)
 ])
 
 # Set the service monitor for sw0-p1 to offline
@@ -1251,7 +1251,7 @@  OVS_WAIT_UNTIL([
 
 ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
 AT_CHECK([cat lflows.txt], [0], [dnl
-  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80,20.0.0.3:80);)
+  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
 ])
 
 # Set the service monitor for sw1-p1 to error
@@ -1263,7 +1263,7 @@  OVS_WAIT_UNTIL([
 ovn-sbctl dump-flows sw0 | grep "ip4.dst == 10.0.0.10 && tcp.dst == 80" \
 | grep priority=120 > lflows.txt
 AT_CHECK([cat lflows.txt], [0], [dnl
-  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80);)
+  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80);)
 ])
 
 # Add one more vip to lb1
@@ -1293,8 +1293,8 @@  service_monitor port=1000 | sed '/^$/d' | wc -l`])
 
 ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
 AT_CHECK([cat lflows.txt], [0], [dnl
-  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80);)
-  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(10.0.0.3:1000);)
+  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80);)
+  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(backends=10.0.0.3:1000);)
 ])
 
 # Set the service monitor for sw1-p1 to online
@@ -1306,16 +1306,16 @@  OVS_WAIT_UNTIL([
 
 ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
 AT_CHECK([cat lflows.txt], [0], [dnl
-  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80,20.0.0.3:80);)
-  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(10.0.0.3:1000,20.0.0.3:80);)
+  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
+  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);)
 ])
 
 # Associate lb1 to sw1
 ovn-nbctl --wait=sb ls-lb-add sw1 lb1
 ovn-sbctl dump-flows sw1 | grep ct_lb | grep priority=120 > lflows.txt
 AT_CHECK([cat lflows.txt], [0], [dnl
-  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80,20.0.0.3:80);)
-  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(10.0.0.3:1000,20.0.0.3:80);)
+  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
+  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);)
 ])
 
 # Now create lb2 same as lb1 but udp protocol.
diff --git a/tests/ovn.at b/tests/ovn.at
index 7befc8224..055fb57e2 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -970,29 +970,42 @@  ct_lb();
     encodes as ct(table=19,zone=NXM_NX_REG13[0..15],nat)
     has prereqs ip
 ct_lb(192.168.1.2:80, 192.168.1.3:80);
+    Syntax error at `192.168.1.2' expecting backends.
+ct_lb(backends=192.168.1.2:80,192.168.1.3:80);
     encodes as group:1
     uses group: id(1), name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=ct(nat(dst=192.168.1.2:80),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=192.168.1.3:80),commit,table=19,zone=NXM_NX_REG13[0..15]))
     has prereqs ip
-ct_lb(192.168.1.2, 192.168.1.3, );
-    formats as ct_lb(192.168.1.2, 192.168.1.3);
+ct_lb(backends=192.168.1.2, 192.168.1.3, );
+    formats as ct_lb(backends=192.168.1.2,192.168.1.3);
     encodes as group:2
     uses group: id(2), name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=ct(nat(dst=192.168.1.2),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=192.168.1.3),commit,table=19,zone=NXM_NX_REG13[0..15]))
     has prereqs ip
-ct_lb(fd0f::2, fd0f::3, );
-    formats as ct_lb(fd0f::2, fd0f::3);
+ct_lb(backends=fd0f::2, fd0f::3, );
+    formats as ct_lb(backends=fd0f::2,fd0f::3);
     encodes as group:3
     uses group: id(3), name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=ct(nat(dst=fd0f::2),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=fd0f::3),commit,table=19,zone=NXM_NX_REG13[0..15]))
     has prereqs ip
 
-ct_lb(192.168.1.2:);
+ct_lb(backends=192.168.1.2:);
     Syntax error at `)' expecting port number.
-ct_lb(192.168.1.2:123456);
+ct_lb(backends=192.168.1.2:123456);
     Syntax error at `123456' expecting port number.
-ct_lb(foo);
+ct_lb(backends=foo);
     Syntax error at `foo' expecting IP address.
-ct_lb([192.168.1.2]);
+ct_lb(backends=[192.168.1.2]);
     Syntax error at `192.168.1.2' expecting IPv6 address.
 
+ct_lb(backends=192.168.1.2:80,192.168.1.3:80; hash_fields=eth_src,eth_dst,ip_src);
+    Syntax error at `eth_src' invalid hash_fields.
+ct_lb(backends=192.168.1.2:80,192.168.1.3:80; hash_fields="eth_src,eth_dst,ip_src");
+    encodes as group:4
+    uses group: id(4), name(type=select,selection_method=hash,fields(eth_src,eth_dst,ip_src),bucket=bucket_id=0,weight:100,actions=ct(nat(dst=192.168.1.2:80),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=192.168.1.3:80),commit,table=19,zone=NXM_NX_REG13[0..15]))
+    has prereqs ip
+ct_lb(backends=fd0f::2,fd0f::3; hash_fields="eth_src,eth_dst,ip_src,ip_dst,tp_src,tp_dst");
+    encodes as group:5
+    uses group: id(5), name(type=select,selection_method=hash,fields(eth_src,eth_dst,ip_src,ip_dst,tp_src,tp_dst),bucket=bucket_id=0,weight:100,actions=ct(nat(dst=fd0f::2),commit,table=19,zone=NXM_NX_REG13[0..15]),bucket=bucket_id=1,weight:100,actions=ct(nat(dst=fd0f::3),commit,table=19,zone=NXM_NX_REG13[0..15]))
+    has prereqs ip
+
 # ct_next
 ct_next;
     encodes as ct(table=19,zone=NXM_NX_REG13[0..15])
@@ -1520,13 +1533,13 @@  handle_svc_check(reg0);
 # select
 reg9[16..31] = select(1=50, 2=100, 3, );
     formats as reg9[16..31] = select(1=50, 2=100, 3=100);
-    encodes as group:4
-    uses group: id(4), name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:50,actions=load:1->xreg4[16..31],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xreg4[16..31],resubmit(,19),bucket=bucket_id=2,weight:100,actions=load:3->xreg4[16..31],resubmit(,19))
+    encodes as group:6
+    uses group: id(6), name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:50,actions=load:1->xreg4[16..31],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xreg4[16..31],resubmit(,19),bucket=bucket_id=2,weight:100,actions=load:3->xreg4[16..31],resubmit(,19))
 
 reg0 = select(1, 2);
     formats as reg0 = select(1=100, 2=100);
-    encodes as group:5
-    uses group: id(5), name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=load:1->xxreg0[96..127],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xxreg0[96..127],resubmit(,19))
+    encodes as group:7
+    uses group: id(7), name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=load:1->xxreg0[96..127],resubmit(,19),bucket=bucket_id=1,weight:100,actions=load:2->xxreg0[96..127],resubmit(,19))
 
 reg0 = select(1=, 2);
     Syntax error at `,' expecting weight.
@@ -1542,12 +1555,12 @@  reg0[0..14] = select(1, 2, 3);
     cannot use 15-bit field reg0[0..14] for "select", which requires at least 16 bits.
 
 fwd_group(liveness="true", childports="eth0", "lsp1");
-    encodes as group:6
-    uses group: id(6), name(type=select,selection_method=dp_hash,bucket=watch_port:5,load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=watch_port:17,load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
+    encodes as group:8
+    uses group: id(8), name(type=select,selection_method=dp_hash,bucket=watch_port:5,load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=watch_port:17,load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
 
 fwd_group(childports="eth0", "lsp1");
-    encodes as group:7
-    uses group: id(7), name(type=select,selection_method=dp_hash,bucket=load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
+    encodes as group:9
+    uses group: id(9), name(type=select,selection_method=dp_hash,bucket=load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=load=0x17->NXM_NX_REG15[0..15],resubmit(,64))
 
 fwd_group(childports=eth0);
     Syntax error at `eth0' expecting logical switch port.
@@ -18000,12 +18013,12 @@  service_monitor | sed '/^$/d' | wc -l`])
 
 ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
 AT_CHECK([cat lflows.txt], [0], [dnl
-  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80,20.0.0.3:80);)
+  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
 ])
 
 ovn-sbctl dump-flows lr0 | grep ct_lb | grep priority=120 > lflows.txt
 AT_CHECK([cat lflows.txt], [0], [dnl
-  table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 && is_chassis_resident("cr-lr0-public")), action=(ct_lb(10.0.0.3:80,20.0.0.3:80);)
+  table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 && is_chassis_resident("cr-lr0-public")), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
 ])
 
 # get the svc monitor mac.
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index fa3b83cb1..32ae60925 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -1095,15 +1095,15 @@  ovn-nbctl lsp-add bar bar3 \
 -- lsp-set-addresses bar3 "f0:00:0f:01:02:05 172.16.1.4"
 
 # Config OVN load-balancer with a VIP.
-uuid=`ovn-nbctl  create load_balancer vips:30.0.0.1="172.16.1.2,172.16.1.3,172.16.1.4"`
-ovn-nbctl set logical_switch foo load_balancer=$uuid
+ovn-nbctl lb-add lb1 30.0.0.1 "172.16.1.2,172.16.1.3,172.16.1.4"
+ovn-nbctl ls-lb-add foo lb1
 
 # Create another load-balancer with another VIP.
-uuid=`ovn-nbctl create load_balancer vips:30.0.0.3="172.16.1.2,172.16.1.3,172.16.1.4"`
-ovn-nbctl add logical_switch foo load_balancer $uuid
+lb2_uuid=`ovn-nbctl create load_balancer name=lb2 vips:30.0.0.3="172.16.1.2,172.16.1.3,172.16.1.4"`
+ovn-nbctl ls-lb-add foo lb2
 
 # Config OVN load-balancer with another VIP (this time with ports).
-ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"172.16.1.2:80,172.16.1.3:80,172.16.1.4:80"'
+ovn-nbctl set load_balancer $lb2_uuid vips:'"30.0.0.2:8000"'='"172.16.1.2:80,172.16.1.3:80,172.16.1.4:80"'
 
 # Wait for ovn-controller to catch up.
 ovn-nbctl --wait=hv sync
@@ -1157,6 +1157,82 @@  tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(s
 tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
 ])
 
+# Configure selection_fields.
+ovn-nbctl set load_balancer $lb2_uuid selection_fields="ip_src,ip_dst,tp_src,tp_dst"
+OVS_WAIT_UNTIL([
+    test $(ovs-ofctl dump-groups br-int | \
+    grep "selection_method=hash,fields(ip_src,ip_dst,tcp_src,tcp_dst)" -c) -eq 2
+])
+
+AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+dnl Test load-balancing that includes L4 ports in NAT.
+for i in `seq 1 20`; do
+    echo Request $i
+    NS_CHECK_EXEC([foo1], [wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+done
+
+dnl Each server should have at least one connection.
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
+tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
+tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
+])
+
+AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+echo "foo" > foo
+for i in `seq 1 20`; do
+    echo Request $i
+    ip netns exec foo1 nc -p 30000 30.0.0.2 8000 < foo
+done
+
+dnl Only one backend should be chosen.
+AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 -c) -eq 1])
+
+ovn-nbctl set load_balancer $lb2_uuid selection_fields="ip_src"
+OVS_WAIT_UNTIL([
+    test $(ovs-ofctl dump-groups br-int | \
+    grep "selection_method=hash,fields=ip_src" -c) -eq 2
+])
+
+AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+for i in `seq 1 20`; do
+    echo Request $i
+    ip netns exec foo1 nc 30.0.0.2 8000 < foo
+done
+
+dnl Only one backend should be chosen as eth_src and ip_src is fixed.
+bar1_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep 172.16.1.2 -c)
+bar2_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep 172.16.1.3 -c)
+bar3_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep 172.16.1.4 -c)
+
+AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep 172.16.1 -c) -ne 0])
+
+if [[ "$bar1_ct" == "20" ]]; then
+    AT_CHECK([test $bar1_ct -eq 20])
+    AT_CHECK([test $bar2_ct -eq 0])
+    AT_CHECK([test $bar3_ct -eq 0])
+else
+    AT_CHECK([test $bar1_ct -eq 0])
+fi
+
+if [[ "$bar2_ct" == "20" ]]; then
+    AT_CHECK([test $bar1_ct -eq 20])
+    AT_CHECK([test $bar2_ct -eq 0])
+    AT_CHECK([test $bar3_ct -eq 0])
+else
+    AT_CHECK([test $bar2_ct -eq 0])
+fi
+
+if [[ "$bar3_ct" == "20" ]]; then
+    AT_CHECK([test $bar1_ct -eq 20])
+    AT_CHECK([test $bar2_ct -eq 0])
+    AT_CHECK([test $bar3_ct -eq 0])
+else
+    AT_CHECK([test $bar3_ct -eq 0])
+fi
 
 OVS_APP_EXIT_AND_WAIT([ovn-controller])
 
@@ -1246,11 +1322,11 @@  uuid=`ovn-nbctl  create load_balancer vips:\"fd03::1\"=\"fd02::2,fd02::3,fd02::4
 ovn-nbctl set logical_switch foo load_balancer=$uuid
 
 # Create another load-balancer with another VIP.
-uuid=`ovn-nbctl create load_balancer vips:\"fd03::3\"=\"fd02::2,fd02::3,fd02::4\"`
-ovn-nbctl add logical_switch foo load_balancer $uuid
+lb2_uuid=`ovn-nbctl create load_balancer vips:\"fd03::3\"=\"fd02::2,fd02::3,fd02::4\"`
+ovn-nbctl add logical_switch foo load_balancer $lb2_uuid
 
 # Config OVN load-balancer with another VIP (this time with ports).
-ovn-nbctl set load_balancer $uuid vips:'"[[fd03::2]]:8000"'='"@<:@fd02::2@:>@:80,@<:@fd02::3@:>@:80,@<:@fd02::4@:>@:80"'
+ovn-nbctl set load_balancer $lb2_uuid vips:'"[[fd03::2]]:8000"'='"@<:@fd02::2@:>@:80,@<:@fd02::3@:>@:80,@<:@fd02::4@:>@:80"'
 
 # Wait for ovn-controller to catch up.
 ovn-nbctl --wait=hv sync
@@ -1304,7 +1380,83 @@  tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd
 tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::4,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
 ])
 
+# Configure selection_fields.
+ovn-nbctl set load_balancer $lb2_uuid selection_fields="ip_src,ip_dst,tp_src,tp_dst"
+OVS_WAIT_UNTIL([
+    test $(ovs-ofctl dump-groups br-int | \
+    grep "selection_method=hash,fields(ip_src,ip_dst,tcp_src,tcp_dst)" -c) -eq 2
+])
+
+AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+dnl Test load-balancing that includes L4 ports in NAT.
+for i in `seq 1 20`; do
+    echo Request $i
+    NS_CHECK_EXEC([foo1], [wget http://[[fd03::2]]:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+done
+
+dnl Each server should have at least one connection.
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd03::2) | grep -v fe80 | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::2,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
+tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::3,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
+tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::4,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
+])
+
+AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+echo "foo" > foo
+for i in `seq 1 20`; do
+    echo Request $i
+    ip netns exec foo1 nc -6 -p 30000 fd03::2 8000 < foo
+done
+
+# Only one backend should be chosen. Since the source port is fixed,
+# there should be only one conntrack entry.
+AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep fd03::2 -c) -eq 1])
+
+ovn-nbctl set load_balancer $lb2_uuid selection_fields="eth_src,ip_src"
+OVS_WAIT_UNTIL([
+    test $(ovs-ofctl dump-groups br-int | \
+    grep "selection_method=hash,fields(eth_src,ip_src)" -c) -eq 2
+])
+
+AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+for i in `seq 1 20`; do
+    echo Request $i
+    ip netns exec foo1 nc -6 fd03::2 8000 < foo
+done
 
+dnl Only one backend should be chosen as eth_src and ip_src is fixed.
+bar1_ct=$(ovs-appctl dpctl/dump-conntrack | grep fd03::2 | grep fd02::2 -c)
+bar2_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep fd02::3 -c)
+bar3_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep fd02::4 -c)
+
+AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep fd03::2 | grep fd02 -c) -ne 0])
+
+if [[ "$bar1_ct" == "20" ]]; then
+    AT_CHECK([test $bar1_ct -eq 20])
+    AT_CHECK([test $bar2_ct -eq 0])
+    AT_CHECK([test $bar3_ct -eq 0])
+else
+    AT_CHECK([test $bar1_ct -eq 0])
+fi
+
+if [[ "$bar2_ct" == "20" ]]; then
+    AT_CHECK([test $bar1_ct -eq 20])
+    AT_CHECK([test $bar2_ct -eq 0])
+    AT_CHECK([test $bar3_ct -eq 0])
+else
+    AT_CHECK([test $bar2_ct -eq 0])
+fi
+
+if [[ "$bar3_ct" == "20" ]]; then
+    AT_CHECK([test $bar1_ct -eq 20])
+    AT_CHECK([test $bar2_ct -eq 0])
+    AT_CHECK([test $bar3_ct -eq 0])
+else
+    AT_CHECK([test $bar3_ct -eq 0])
+fi
 OVS_APP_EXIT_AND_WAIT([ovn-controller])
 
 as ovn-sb
@@ -3448,7 +3600,7 @@  service_monitor | sed '/^$/d' | grep online | wc -l`])
 
 OVS_WAIT_UNTIL(
     [ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 | grep "ip4.dst == 10.0.0.10" > lflows.txt
-     test 1 = `cat lflows.txt | grep "ct_lb(10.0.0.3:80,20.0.0.3:80)" | wc -l`]
+     test 1 = `cat lflows.txt | grep "ct_lb(backends=10.0.0.3:80,20.0.0.3:80)" | wc -l`]
 )
 
 # From sw0-p2 send traffic to vip - 10.0.0.10
@@ -3474,7 +3626,7 @@  service_monitor logical_port=sw0-p1 | sed '/^$/d' | grep offline | wc -l`])
 
 OVS_WAIT_UNTIL(
     [ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 | grep "ip4.dst == 10.0.0.10" > lflows.txt
-     test 1 = `cat lflows.txt | grep "ct_lb(20.0.0.3:80)" | wc -l`]
+     test 1 = `cat lflows.txt | grep "ct_lb(backends=20.0.0.3:80)" | wc -l`]
 )
 
 ovs-appctl dpctl/flush-conntrack