diff mbox

[ovs-dev,v4] ovn-nbctl: Add LB commands.

Message ID 1472798854-23488-1-git-send-email-nickcooper-zhangtonghao@opencloud.tech
State Changes Requested
Headers show

Commit Message

nickcooper-zhangtonghao Sept. 2, 2016, 6:47 a.m. UTC
This patch provides the command line to create a load balancer.
You can create a load balancer independently and add it
to multiple switches. A single load balancer can have multiple vips.
Add a name column for the load balancer. With --add-duplicate,
the command really creates a new load balancer with a duplicate name.
This name has no special meaning or purpose other than to provide
convenience for human interaction with the ovn-nb database.
This patch also provides the unit tests and the documentation.

Signed-off-by: nickcooper-zhangtonghao <nickcooper-zhangtonghao@opencloud.tech>
---
 ovn/ovn-nb.ovsschema          |   3 +-
 ovn/ovn-nb.xml                |   6 +
 ovn/utilities/ovn-nbctl.8.xml |  57 ++++++++
 ovn/utilities/ovn-nbctl.c     | 307 +++++++++++++++++++++++++++++++++++++++++-
 tests/ovn-nbctl.at            |  97 +++++++++++++
 5 files changed, 468 insertions(+), 2 deletions(-)

Comments

Gurucharan Shetty Sept. 9, 2016, 9:54 p.m. UTC | #1
On 1 September 2016 at 23:47, nickcooper-zhangtonghao <
nickcooper-zhangtonghao@opencloud.tech> wrote:

> This patch provides the command line to create a load balancer.
> You can create a load balancer independently and add it
> to multiple switches. A single load balancer can have multiple vips.
> Add a name column for the load balancer. With --add-duplicate,
> the command really creates a new load balancer with a duplicate name.
> This name has no special meaning or purpose other than to provide
> convenience for human interaction with the ovn-nb database.
> This patch also provides the unit tests and the documentation.
>
> Signed-off-by: nickcooper-zhangtonghao <nickcooper-zhangtonghao@
> opencloud.tech>
>

Sorry for the delay on this. I was waiting for the load balancer addition
to gateway routers. Now that has been merged, would you please mind
re-spinning this with the feature to add it to a router too?



> ---
>  ovn/ovn-nb.ovsschema          |   3 +-
>  ovn/ovn-nb.xml                |   6 +
>  ovn/utilities/ovn-nbctl.8.xml |  57 ++++++++
>  ovn/utilities/ovn-nbctl.c     | 307 ++++++++++++++++++++++++++++++
> +++++++++++-
>  tests/ovn-nbctl.at            |  97 +++++++++++++
>  5 files changed, 468 insertions(+), 2 deletions(-)
>
> diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
> index 456ae98..d935475 100644
> --- a/ovn/ovn-nb.ovsschema
> +++ b/ovn/ovn-nb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Northbound",
>      "version": "5.3.1",
> -    "cksum": "1921908091 9353",
> +    "cksum": "440042936 9397",
>      "tables": {
>          "NB_Global": {
>              "columns": {
> @@ -92,6 +92,7 @@
>              "isRoot": true},
>          "Load_Balancer": {
>              "columns": {
> +                "name": {"type": "string"},
>                  "vips": {
>                      "type": {"key": "string", "value": "string",
>                               "min": 0, "max": "unlimited"}},
> diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
> index 5719e74..186abf8 100644
> --- a/ovn/ovn-nb.xml
> +++ b/ovn/ovn-nb.xml
> @@ -660,6 +660,12 @@
>        Each row represents one load balancer.
>      </p>
>
> +    <column name="name">
> +      A name for the load balancer.  This name has no special meaning or
> +      purpose other than to provide convenience for human interaction with
> +      the ovn-nb database.
> +    </column>
> +
>      <column name="vips">
>        <p>
>          A map of virtual IPv4 addresses (and an optional port number with
> diff --git a/ovn/utilities/ovn-nbctl.8.xml b/ovn/utilities/ovn-nbctl.8.xml
> index d44f039..cf685a5 100644
> --- a/ovn/utilities/ovn-nbctl.8.xml
> +++ b/ovn/utilities/ovn-nbctl.8.xml
> @@ -102,6 +102,63 @@
>        </dd>
>      </dl>
>
> +    <h1>Logical Switch LB Commands</h1>
> +    <dl>
> +      <dt>[<code>--may-exist</code> | <code>--add-duplicate</code>]
> <code>lb-add</code> <var>lb</var> <var>vip</var> <var>ips</var>
> [<var>protocol</var>]</dt>
> +      <dd>
> +        Creates a new load balancer named <var>lb</var> with the
> <var>vip</var> or
> +        adds the <var>vip</var> to <var>lb</var>.  A single load balancer
> can
> +        have multiple vips.  We should assign <var>lb</var> a virtual
> IPv4 address
> +        (and an optional port number with : as a separator) and the
> corresponding
> +        endpoint IPv4 addresses (and optional port numbers with : as
> separators)
> +        separated by commas.  The optional argument <var>protocol</var>
> must be
> +        either <code>tcp</code> or <code>udp</code>.  This argument is
> useful when
> +        a port number is provided as part of the <var>vip</var>.  If the
> <var>protocol</var>
> +        is unspecified and a port number is provided as part of
> <var>vip</var>,
> +        OVN assumes the <var>protocol</var> to be <code>tcp</code>.  It
> is an
> +        error if the <var>vip</var> has been included in the load
> balancer named
> +        <var>lb</var>, unless <code>--may-exist</code> is specified.
> +        With <code>--add-duplicate</code>, the command really creates a
> new load
> +        balancer with a duplicate name.  The following example adds a
> load balancer
> +        with <var>protocol</var> <code>udp</code>:
> +        <p>
> +          <code>lb-add lb0 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80
> ,192.168.10.30:80 udp</code>
> +        </p>
> +      </dd>
> +
> +      <dt>[<code>--if-exists</code>] <code>lb-del</code> <var>lb</var>
> [<var>vip</var>]</dt>
> +      <dd>
> +        Deletes <var>lb</var> or the <var>vip</var> from <var>lb</var>.
> +        If <var>vip</var> is supplied, the <var>vip</var> will be deleted
> +        only from the <var>lb</var>.  If only <var>lb</var> is supplied,
> +        the <var>lb</var> will be deleted.  It is an error if
> <var>vip</var>
> +        does not be included in <var>lb</var>, unless
> <code>--if-exists</code> is specified.
> +      </dd>
> +
> +      <dt><code>lb-list</code> [<var>switch</var>] [<var>lb</var>]</dt>
> +      <dd>
> +        Lists the LBs.  If <var>switch</var> is supplied, all the LBs from
> +        the logical switch are listed. If <var>lb</var> is also specified,
> +        then the <var>lb</var> will be listed only from the logical
> switch.
> +      </dd>
> +
> +      <dt>[<code>--may-exist</code>] <code>lb-append-to</code>
> <var>switch</var> <var>lb</var></dt>
> +      <dd>
> +        Adds the specified <var>lb</var> to <var>switch</var>.
> +        It is an error if a load balancer named <var>lb</var> already
> exists
> +        in the <var>switch</var>, unless <code>--may-exist</code> is
> specified.
> +      </dd>
> +
> +      <dt>[<code>--if-exists</code>] <code>lb-remove-from</code>
> <var>switch</var> [<var>lb</var>]</dt>
> +      <dd>
> +        Deletes <var>lb</var> from <var>switch</var>.  If only
> <var>switch</var> is supplied,
> +        all the LBs from the logical switch are deleted.  If
> <var>lb</var> is also specified,
> +        then the <var>lb</var> will be deleted only from the logical
> switch.
> +        It is an error if <var>lb</var> does not exist in the
> <var>switch</var>,
> +        unless <code>--if-exists</code> is specified.
> +      </dd>
> +    </dl>
> +
>      <h1>Logical Switch Port Commands</h1>
>      <dl>
>        <dt>[<code>--may-exist</code>] <code>lsp-add</code>
> <var>switch</var> <var>port</var></dt>
> diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c
> index d6d64ea..d90080f 100644
> --- a/ovn/utilities/ovn-nbctl.c
> +++ b/ovn/utilities/ovn-nbctl.c
> @@ -331,6 +331,18 @@ ACL commands:\n\
>                              remove ACLs from SWITCH\n\
>    acl-list SWITCH           print ACLs for SWITCH\n\
>  \n\
> +LB commands:\n\
> +  lb-add LB VIP[:PORT] IP[:PORT]... [PROTOCOL]\n\
> +                            add a load-balancer or VIP to load balancer\n\
> +  lb-del LB [VIP]\n\
> +                            remove a load-balancer or VIP from load
> balancer\n\
> +  lb-list [SWITCH] [LB]\n\
> +                            print load-balancers\n\
> +  lb-append-to SWITCH LB\n\
> +                            add a load-balancer to SWITCH\n\
> +  lb-remove-from SWITCH [LB]\n\
> +                            remove load-balancers from SWITCH\n\
> +\n\
>  Logical switch port commands:\n\
>    lsp-add SWITCH PORT       add logical port PORT on SWITCH\n\
>    lsp-add SWITCH PORT PARENT TAG\n\
> @@ -493,6 +505,39 @@ ls_by_name_or_uuid(struct ctl_context *ctx, const
> char *id, bool must_exist)
>      return ls;
>  }
>
> +static const struct nbrec_load_balancer *
> +lb_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool
> must_exist)
> +{
> +    const struct nbrec_load_balancer *lb = NULL;
> +
> +    struct uuid lb_uuid;
> +    bool is_uuid = uuid_from_string(&lb_uuid, id);
> +    if (is_uuid) {
> +        lb = nbrec_load_balancer_get_for_uuid(ctx->idl, &lb_uuid);
> +    }
> +
> +    if (!lb) {
> +        const struct nbrec_load_balancer *iter;
> +
> +        NBREC_LOAD_BALANCER_FOR_EACH(iter, ctx->idl) {
> +            if (strcmp(iter->name, id)) {
> +                continue;
> +            }
> +            if (lb) {
> +                ctl_fatal("Multiple load balancers named '%s'.  "
> +                          "Use a UUID.", id);
> +            }
> +            lb = iter;
> +        }
> +    }
> +
> +    if (!lb && must_exist) {
> +        ctl_fatal("%s: load balancer %s not found", id, is_uuid ? "UUID"
> : "name");
> +    }
> +
> +    return lb;
> +}
> +
>  /* Given pointer to logical router, this routine prints the router
>   * information.  */
>  static void
> @@ -1315,7 +1360,259 @@ nbctl_acl_del(struct ctl_context *ctx)
>          }
>      }
>  }
> -
> +
> +static void
> +nbctl_lb_add(struct ctl_context *ctx)
> +{
> +    const char *lb_name = ctx->argv[1];
> +    const char *lb_vip = ctx->argv[2];
> +    const char *lb_ips = ctx->argv[3];
> +
> +    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
> +    bool add_duplicate = shash_find(&ctx->options, "--add-duplicate") !=
> NULL;
> +
> +    const char *lb_proto;
> +    bool is_update_proto = false;
> +    if (ctx->argc == 4) {
> +        /* Default protocol. */
> +        lb_proto = "tcp";
> +    } else {
> +        /* Validate protocol. */
> +        lb_proto = ctx->argv[4];
> +        is_update_proto = true;
> +        if (strcmp(lb_proto, "tcp") && strcmp(lb_proto, "udp")) {
> +            ctl_fatal("%s: protocol must be one of \"tcp\", \"udp\".",
> lb_proto);
> +        }
> +    }
> +
> +    const struct nbrec_load_balancer *lb = NULL;
> +    if (!add_duplicate) {
> +        lb = lb_by_name_or_uuid(ctx, lb_name, false);
> +        if (lb) {
> +            if (smap_get(&lb->vips, lb_vip)) {
> +                if (!may_exist) {
> +                    ctl_fatal("%s: a load balancer with this vip (%s)
> already exists", lb_name, lb_vip);
> +                }
> +                /* Update the vips. */
> +                smap_replace(CONST_CAST(struct smap * ,&lb->vips),
> lb_vip, lb_ips);
> +            } else {
> +                /* Add the new vips. */
> +                smap_add(CONST_CAST(struct smap * ,&lb->vips), lb_vip,
> lb_ips);
> +            }
> +
> +            /* Update the load balancer. */
> +            if (is_update_proto) {
> +                nbrec_load_balancer_verify_protocol(lb);
> +                nbrec_load_balancer_set_protocol(lb, lb_proto);
> +            }
> +            nbrec_load_balancer_verify_vips(lb);
> +            nbrec_load_balancer_set_vips(lb, &lb->vips);
> +            return;
> +        }
> +    }
> +
> +    /* Create the load balancer. */
> +    lb = nbrec_load_balancer_insert(ctx->txn);
> +    nbrec_load_balancer_set_name(lb, lb_name);
> +    nbrec_load_balancer_set_protocol(lb, lb_proto);
> +    smap_add(CONST_CAST(struct smap * ,&lb->vips), lb_vip, lb_ips);
> +    nbrec_load_balancer_set_vips(lb, &lb->vips);
> +}
> +
> +static void
> +nbctl_lb_del(struct ctl_context *ctx)
> +{
> +    const char *id = ctx->argv[1];
> +    const struct nbrec_load_balancer *lb = NULL;
> +    bool must_exist = !shash_find(&ctx->options, "--if-exists");
> +
> +    lb = lb_by_name_or_uuid(ctx, id, false);
> +    if (!lb) {
> +        return;
> +    }
> +
> +    if (ctx->argc == 3) {
> +        const char *lb_vip = ctx->argv[2];
> +        if (smap_get(&lb->vips, lb_vip)) {
> +            smap_remove(CONST_CAST(struct smap * ,&lb->vips), lb_vip);
> +            if (smap_is_empty(&lb->vips)) {
> +                nbrec_load_balancer_delete(lb);
> +                return;
> +            }
> +
> +            /* Delete the vip of the load balancer. */
> +            nbrec_load_balancer_verify_vips(lb);
> +            nbrec_load_balancer_set_vips(lb, &lb->vips);
> +            return;
> +        }
> +        if (must_exist) {
> +            ctl_fatal("vip %s is not part of the load balancer.",
> +                    lb_vip);
> +        }
> +        return;
> +    }
> +    nbrec_load_balancer_delete(lb);
> +}
> +
> +static const struct smap_node **
> +nbctl_lb_list_switch(struct ctl_context *ctx, struct smap *lbs,
> +                     const char *ls_name, const char *lb_name, bool
> lb_check)
> +{
> +    const struct nbrec_logical_switch *ls;
> +    ls = ls_by_name_or_uuid(ctx, ls_name, true);
> +
> +    for (int i = 0; i < ls->n_load_balancer; i++) {
> +        const struct nbrec_load_balancer *lb
> +            = ls->load_balancer[i];
> +        if (lb_check && strcmp(lb->name, lb_name)) {
> +            continue;
> +        }
> +
> +        const struct smap_node **nodes = smap_sort(&lb->vips);
> +        if (nodes) {
> +            struct ds key = DS_EMPTY_INITIALIZER;
> +            ds_put_format(&key, "%-10.8s" UUID_FMT,
> +                    lb->name, UUID_ARGS(&lb->header_.uuid));
> +            for (int i = 0; i < smap_count(&lb->vips); i++) {
> +                const struct smap_node *node = nodes[i];
> +                smap_add_format(lbs, ds_cstr(&key), UUID_FMT "   %-10.8s
> %-8s %-20s %s",
> +                            UUID_ARGS(&lb->header_.uuid), lb->name,
> lb->protocol, node->key, node->value);
> +            }
> +
> +            ds_destroy(&key);
> +            free(nodes);
> +        }
> +    }
> +
> +    return smap_sort(lbs);
> +}
> +
> +static const struct smap_node **
> +nbctl_lb_list_all(struct ctl_context *ctx, struct smap *lbs) {
> +
> +    const struct nbrec_load_balancer *lb;
> +    NBREC_LOAD_BALANCER_FOR_EACH(lb, ctx->idl) {
> +        const struct smap_node **nodes = smap_sort(&lb->vips);
> +        if (nodes) {
> +            struct ds key = DS_EMPTY_INITIALIZER;
> +            ds_put_format(&key, "%-10.8s" UUID_FMT,
> +                    lb->name, UUID_ARGS(&lb->header_.uuid));
> +            for (int i = 0; i < smap_count(&lb->vips); i++) {
> +                const struct smap_node *node = nodes[i];
> +                smap_add_format(lbs, ds_cstr(&key), UUID_FMT "   %-10.8s
> %-8s %-20s %s",
> +                            UUID_ARGS(&lb->header_.uuid), lb->name,
> lb->protocol, node->key, node->value);
> +            }
> +
> +            ds_destroy(&key);
> +            free(nodes);
> +        }
> +    }
> +
> +    return smap_sort(lbs);
> +}
> +
> +static void
> +nbctl_lb_list(struct ctl_context *ctx)
> +{
> +    struct smap lbs = SMAP_INITIALIZER(&lbs);
> +    const struct smap_node **nodes = NULL;
> +
> +    if (ctx->argc == 1) {
> +        nodes = nbctl_lb_list_all(ctx, &lbs);
> +    } else if (ctx->argc == 2) {
> +        nodes = nbctl_lb_list_switch(ctx, &lbs, ctx->argv[1], NULL,
> false);
> +    } else {
> +        nodes = nbctl_lb_list_switch(ctx, &lbs, ctx->argv[1],
> ctx->argv[2], true);
> +    }
> +
> +    if (nodes) {
> +        ds_put_format(&ctx->output, "%-36s   %-10.8s %-8s %-20s %s\n",
> +                "UUID", "LB", "PROTO", "VIP", "IPs");
> +        for (size_t i = 0; i < smap_count(&lbs); i++) {
> +            const struct smap_node *node = nodes[i];
> +            ds_put_format(&ctx->output, "%s\n", node->value);
> +        }
> +
> +        smap_destroy(&lbs);
> +        free(nodes);
> +    }
> +}
> +
> +static void
> +nbctl_lb_append_to(struct ctl_context *ctx)
> +{
> +    const struct nbrec_logical_switch *ls;
> +    const struct nbrec_load_balancer *new_lb;
> +
> +    ls = ls_by_name_or_uuid(ctx, ctx->argv[1], true);
> +    new_lb = lb_by_name_or_uuid(ctx, ctx->argv[2], true);
> +
> +    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
> +    for (int i = 0; i < ls->n_load_balancer; i++) {
> +        const struct nbrec_load_balancer *lb
> +            = ls->load_balancer[i];
> +
> +        if (uuid_equals(&new_lb->header_.uuid, &lb->header_.uuid)) {
> +            if (may_exist) {
> +                return;
> +            }
> +            ctl_fatal(UUID_FMT " : a load balancer with this UUID already
> exists",
> +                    UUID_ARGS(&lb->header_.uuid));
> +        }
> +    }
> +
> +    /* Insert the load balancer into the logical switch. */
> +    nbrec_logical_switch_verify_load_balancer(ls);
> +    struct nbrec_load_balancer **new_lbs
> +        = xmalloc(sizeof *new_lbs * (ls->n_load_balancer + 1));
> +
> +    memcpy(new_lbs, ls->load_balancer, sizeof *new_lbs *
> ls->n_load_balancer);
> +    new_lbs[ls->n_load_balancer] = CONST_CAST(struct nbrec_load_balancer
> *, new_lb);
> +    nbrec_logical_switch_set_load_balancer(ls, new_lbs,
> ls->n_load_balancer + 1);
> +    free(new_lbs);
> +}
> +
> +static void
> +nbctl_lb_remove_from(struct ctl_context *ctx)
> +{
> +    const struct nbrec_logical_switch *ls;
> +    const struct nbrec_load_balancer *del_lb;
> +    ls = ls_by_name_or_uuid(ctx, ctx->argv[1], true);
> +
> +    if (ctx->argc == 2) {
> +        /* If load-balancer is not specified, remove
> +         * all load-balancers from the logical switch. */
> +        nbrec_logical_switch_verify_load_balancer(ls);
> +        nbrec_logical_switch_set_load_balancer(ls, NULL, 0);
> +        return;
> +    }
> +
> +    del_lb = lb_by_name_or_uuid(ctx, ctx->argv[2], true);
> +    for (size_t i = 0; i < ls->n_load_balancer; i++) {
> +        const struct nbrec_load_balancer *lb
> +            = ls->load_balancer[i];
> +
> +        if (uuid_equals(&del_lb->header_.uuid, &lb->header_.uuid)) {
> +            /* Remove the matching rule. */
> +            nbrec_logical_switch_verify_load_balancer(ls);
> +
> +            struct nbrec_load_balancer **new_lbs
> +                = xmemdup(ls->load_balancer, sizeof *new_lbs *
> ls->n_load_balancer);
> +            new_lbs[i] = ls->load_balancer[ls->n_load_balancer - 1];
> +            nbrec_logical_switch_set_load_balancer(ls, new_lbs,
> +                                          ls->n_load_balancer - 1);
> +            free(new_lbs);
> +            return;
> +        }
> +    }
> +
> +    bool must_exist = !shash_find(&ctx->options, "--if-exists");
> +    if (must_exist) {
> +        ctl_fatal("load balancer %s is not part of any logical switch.",
> +                del_lb->name);
> +    }
> +}
> +
>  static void
>  nbctl_lr_add(struct ctl_context *ctx)
>  {
> @@ -2420,6 +2717,14 @@ static const struct ctl_command_syntax
> nbctl_commands[] = {
>        nbctl_acl_del, NULL, "", RW },
>      { "acl-list", 1, 1, "SWITCH", NULL, nbctl_acl_list, NULL, "", RO },
>
> +    /* load balancer commands. */
> +    { "lb-add", 3, 4, "LB VIP[:PORT] IP[:PORT]... [PROTOCOL]", NULL,
> +      nbctl_lb_add, NULL, "--may-exist,--add-duplicate", RW },
> +    { "lb-del", 1, 2, "LB [VIP]", NULL, nbctl_lb_del, NULL,
> "--if-exists", RW },
> +    { "lb-list", 0, 2, "[SWITCH] [LB]", NULL, nbctl_lb_list, NULL, "", RO
> },
> +    { "lb-append-to", 2, 2, "SWITCH LB", NULL, nbctl_lb_append_to, NULL,
> "--may-exist", RW },
> +    { "lb-remove-from", 1, 2, "SWITCH [LB]", NULL, nbctl_lb_remove_from,
> NULL, "--if-exists", RW },
> +
>      /* logical switch port commands. */
>      { "lsp-add", 2, 4, "SWITCH PORT [PARENT] [TAG]", NULL, nbctl_lsp_add,
>        NULL, "--may-exist", RW },
> diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
> index 5357ced..6cbcf16 100644
> --- a/tests/ovn-nbctl.at
> +++ b/tests/ovn-nbctl.at
> @@ -239,6 +239,103 @@ AT_CLEANUP
>
>  dnl ---------------------------------------------------------------------
>
> +AT_SETUP([ovn-nbctl - LBs])
> +OVN_NBCTL_TEST_START
> +
> +dnl Add two LBs.
> +AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10:80 192.168.10.10:80,192.168.10.
> 20:80])
> +AT_CHECK([ovn-nbctl lb-add lb1 30.0.0.10:80 192.168.10.10:80,192.168.10.
> 20:80 tcp])
> +AT_CHECK([ovn-nbctl lb-list | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
> +UUID                                   LB         PROTO    VIP
>       IPs
> +<0>   lb0        tcp      30.0.0.10:80         192.168.10.10:80,
> 192.168.10.20:80
> +<1>   lb1        tcp      30.0.0.10:80         192.168.10.10:80,
> 192.168.10.20:80
> +])
> +
> +dnl Update the VIP of the lb1.
> +AT_CHECK([ovn-nbctl --may-exist lb-add lb1 30.0.0.10:80
> 192.168.10.10:8080,192.168.10.20:8080])
> +AT_CHECK([ovn-nbctl lb-list | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
> +UUID                                   LB         PROTO    VIP
>       IPs
> +<0>   lb0        tcp      30.0.0.10:80         192.168.10.10:80,
> 192.168.10.20:80
> +<1>   lb1        tcp      30.0.0.10:80         192.168.10.10:8080,
> 192.168.10.20:8080
> +])
> +
> +AT_CHECK([ovn-nbctl --may-exist lb-add lb1 30.0.0.10:80
> 192.168.10.10:8080,192.168.10.20:8080 udp])
> +AT_CHECK([ovn-nbctl lb-list | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
> +UUID                                   LB         PROTO    VIP
>       IPs
> +<0>   lb0        tcp      30.0.0.10:80         192.168.10.10:80,
> 192.168.10.20:80
> +<1>   lb1        udp      30.0.0.10:80         192.168.10.10:8080,
> 192.168.10.20:8080
> +])
> +
> +dnl Config lb1 with another VIP.
> +AT_CHECK([ovn-nbctl lb-add lb1 30.0.0.20:80 192.168.10.10:80,192.168.10.
> 20:80 udp])
> +AT_CHECK([ovn-nbctl lb-del lb1 30.0.0.20:80])
> +AT_CHECK([ovn-nbctl lb-list | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
> +UUID                                   LB         PROTO    VIP
>       IPs
> +<0>   lb0        tcp      30.0.0.10:80         192.168.10.10:80,
> 192.168.10.20:80
> +<1>   lb1        udp      30.0.0.10:80         192.168.10.10:8080,
> 192.168.10.20:8080
> +])
> +
> +AT_CHECK([ovn-nbctl lb-add lb2 30.0.0.10:8080 192.168.10.10:80,
> 192.168.10.20:80 tcp])
> +AT_CHECK([ovn-nbctl --add-duplicate lb-add lb2 30.0.0.10:8080
> 192.168.10.10:80,192.168.10.20:80 tcp])
> +AT_CHECK([ovn-nbctl lb-list | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
> +UUID                                   LB         PROTO    VIP
>       IPs
> +<0>   lb0        tcp      30.0.0.10:80         192.168.10.10:80,
> 192.168.10.20:80
> +<1>   lb1        udp      30.0.0.10:80         192.168.10.10:8080,
> 192.168.10.20:8080
> +<2>   lb2        tcp      30.0.0.10:8080       192.168.10.10:80,
> 192.168.10.20:80
> +<3>   lb2        tcp      30.0.0.10:8080       192.168.10.10:80,
> 192.168.10.20:80
> +])
> +
> +dnl If there are multiple load balancers with the same name, use a UUID
> to update/delete.
> +AT_CHECK([ovn-nbctl lb-add lb2 30.0.0.10:8080 192.168.10.10:80,
> 192.168.10.20:80 tcp], [1], [],
> +[ovn-nbctl: Multiple load balancers named 'lb2'.  Use a UUID.
> +])
> +
> +AT_CHECK([ovn-nbctl lb-del lb2], [1], [],
> +[ovn-nbctl: Multiple load balancers named 'lb2'.  Use a UUID.
> +])
> +
> +AT_CHECK([ovn-nbctl --may-exist lb-add lb1 30.0.0.10:80
> 192.168.10.10:8080,192.168.10.20:8080 udp])
> +AT_CHECK([ovn-nbctl --may-exist lb-add lb1 30.0.0.10:8080
> 192.168.10.10:8080,192.168.10.20:8080 udp])
> +AT_CHECK([ovn-nbctl --may-exist lb-add lb1 30.0.0.10:9090
> 192.168.10.10:8080,192.168.10.20:8080 udp])
> +AT_CHECK([ovn-nbctl lb-del lb0 30.0.0.10:80])
> +AT_CHECK([ovn-nbctl lb-del lb1])
> +AT_CHECK([ovn-nbctl lb-list | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
> +UUID                                   LB         PROTO    VIP
>       IPs
> +<0>   lb2        tcp      30.0.0.10:8080       192.168.10.10:80,
> 192.168.10.20:80
> +<1>   lb2        tcp      30.0.0.10:8080       192.168.10.10:80,
> 192.168.10.20:80
> +])
> +
> +AT_CHECK([ovn-nbctl ls-add ls0])
> +AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10:80 192.168.10.10:80,192.168.10.
> 20:80])
> +AT_CHECK([ovn-nbctl lb-add lb1 30.0.0.10:80 192.168.10.10:80,192.168.10.
> 20:80 udp])
> +AT_CHECK([ovn-nbctl lb-append-to ls0 lb0])
> +AT_CHECK([ovn-nbctl lb-append-to ls0 lb1])
> +AT_CHECK([ovn-nbctl --may-exist lb-append-to ls0 lb1])
> +AT_CHECK([ovn-nbctl lb-append-to ls0 lb2], [1], [],
> +[ovn-nbctl: Multiple load balancers named 'lb2'.  Use a UUID.
> +])
> +
> +AT_CHECK([ovn-nbctl lb-list ls0 | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
> +UUID                                   LB         PROTO    VIP
>       IPs
> +<0>   lb0        tcp      30.0.0.10:80         192.168.10.10:80,
> 192.168.10.20:80
> +<1>   lb1        udp      30.0.0.10:80         192.168.10.10:80,
> 192.168.10.20:80
> +])
> +
> +AT_CHECK([ovn-nbctl lb-remove-from ls0 lb0])
> +AT_CHECK([ovn-nbctl lb-list ls0 | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
> +UUID                                   LB         PROTO    VIP
>       IPs
> +<0>   lb1        udp      30.0.0.10:80         192.168.10.10:80,
> 192.168.10.20:80
> +])
> +
> +AT_CHECK([ovn-nbctl lb-remove-from ls0 lb1])
> +AT_CHECK([ovn-nbctl lb-list ls0 | ${PERL} $srcdir/uuidfilt.pl], [0], [])
> +AT_CHECK([ovn-nbctl --if-exists lb-remove-from ls0 lb1])
> +
> +OVN_NBCTL_TEST_STOP
> +AT_CLEANUP
> +
> +dnl ---------------------------------------------------------------------
> +
>  AT_SETUP([ovn-nbctl - basic logical router commands])
>  OVN_NBCTL_TEST_START
>
> --
> 1.8.3.1
>
>
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> http://openvswitch.org/mailman/listinfo/dev
>
nickcooper-zhangtonghao Sept. 10, 2016, 1:34 a.m. UTC | #2
Yes, I have submitted the v5 patch which support for gateway router.
Thank you so much for your reviews.

> On Sep 10, 2016, at 5:54 AM, Guru Shetty <guru@ovn.org> wrote:
> 
> On 1 September 2016 at 23:47, nickcooper-zhangtonghao <nickcooper-zhangtonghao@opencloud.tech <mailto:nickcooper-zhangtonghao@opencloud.tech>> wrote:
> This patch provides the command line to create a load balancer.
> You can create a load balancer independently and add it
> to multiple switches. A single load balancer can have multiple vips.
> Add a name column for the load balancer. With --add-duplicate,
> the command really creates a new load balancer with a duplicate name.
> This name has no special meaning or purpose other than to provide
> convenience for human interaction with the ovn-nb database.
> This patch also provides the unit tests and the documentation.
> 
> Signed-off-by: nickcooper-zhangtonghao <nickcooper-zhangtonghao@opencloud.tech>
> 
> Sorry for the delay on this. I was waiting for the load balancer addition to gateway routers. Now that has been merged, would you please mind re-spinning this with the feature to add it to a router too?
diff mbox

Patch

diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
index 456ae98..d935475 100644
--- a/ovn/ovn-nb.ovsschema
+++ b/ovn/ovn-nb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Northbound",
     "version": "5.3.1",
-    "cksum": "1921908091 9353",
+    "cksum": "440042936 9397",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -92,6 +92,7 @@ 
             "isRoot": true},
         "Load_Balancer": {
             "columns": {
+                "name": {"type": "string"},
                 "vips": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}},
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index 5719e74..186abf8 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -660,6 +660,12 @@ 
       Each row represents one load balancer.
     </p>
 
+    <column name="name">
+      A name for the load balancer.  This name has no special meaning or
+      purpose other than to provide convenience for human interaction with
+      the ovn-nb database.
+    </column>
+
     <column name="vips">
       <p>
         A map of virtual IPv4 addresses (and an optional port number with
diff --git a/ovn/utilities/ovn-nbctl.8.xml b/ovn/utilities/ovn-nbctl.8.xml
index d44f039..cf685a5 100644
--- a/ovn/utilities/ovn-nbctl.8.xml
+++ b/ovn/utilities/ovn-nbctl.8.xml
@@ -102,6 +102,63 @@ 
       </dd>
     </dl>
 
+    <h1>Logical Switch LB Commands</h1>
+    <dl>
+      <dt>[<code>--may-exist</code> | <code>--add-duplicate</code>] <code>lb-add</code> <var>lb</var> <var>vip</var> <var>ips</var> [<var>protocol</var>]</dt>
+      <dd>
+        Creates a new load balancer named <var>lb</var> with the <var>vip</var> or
+        adds the <var>vip</var> to <var>lb</var>.  A single load balancer can
+        have multiple vips.  We should assign <var>lb</var> a virtual IPv4 address
+        (and an optional port number with : as a separator) and the corresponding
+        endpoint IPv4 addresses (and optional port numbers with : as separators)
+        separated by commas.  The optional argument <var>protocol</var> must be
+        either <code>tcp</code> or <code>udp</code>.  This argument is useful when
+        a port number is provided as part of the <var>vip</var>.  If the <var>protocol</var>
+        is unspecified and a port number is provided as part of <var>vip</var>,
+        OVN assumes the <var>protocol</var> to be <code>tcp</code>.  It is an
+        error if the <var>vip</var> has been included in the load balancer named
+        <var>lb</var>, unless <code>--may-exist</code> is specified.
+        With <code>--add-duplicate</code>, the command really creates a new load
+        balancer with a duplicate name.  The following example adds a load balancer
+        with <var>protocol</var> <code>udp</code>:
+        <p>
+          <code>lb-add lb0 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80,192.168.10.30:80 udp</code>
+        </p>
+      </dd>
+
+      <dt>[<code>--if-exists</code>] <code>lb-del</code> <var>lb</var> [<var>vip</var>]</dt>
+      <dd>
+        Deletes <var>lb</var> or the <var>vip</var> from <var>lb</var>.
+        If <var>vip</var> is supplied, the <var>vip</var> will be deleted
+        only from the <var>lb</var>.  If only <var>lb</var> is supplied,
+        the <var>lb</var> will be deleted.  It is an error if <var>vip</var>
+        does not be included in <var>lb</var>, unless <code>--if-exists</code> is specified.
+      </dd>
+
+      <dt><code>lb-list</code> [<var>switch</var>] [<var>lb</var>]</dt>
+      <dd>
+        Lists the LBs.  If <var>switch</var> is supplied, all the LBs from
+        the logical switch are listed. If <var>lb</var> is also specified,
+        then the <var>lb</var> will be listed only from the logical switch.
+      </dd>
+
+      <dt>[<code>--may-exist</code>] <code>lb-append-to</code> <var>switch</var> <var>lb</var></dt>
+      <dd>
+        Adds the specified <var>lb</var> to <var>switch</var>.
+        It is an error if a load balancer named <var>lb</var> already exists
+        in the <var>switch</var>, unless <code>--may-exist</code> is specified.
+      </dd>
+
+      <dt>[<code>--if-exists</code>] <code>lb-remove-from</code> <var>switch</var> [<var>lb</var>]</dt>
+      <dd>
+        Deletes <var>lb</var> from <var>switch</var>.  If only <var>switch</var> is supplied,
+        all the LBs from the logical switch are deleted.  If <var>lb</var> is also specified,
+        then the <var>lb</var> will be deleted only from the logical switch.
+        It is an error if <var>lb</var> does not exist in the <var>switch</var>,
+        unless <code>--if-exists</code> is specified.
+      </dd>
+    </dl>
+
     <h1>Logical Switch Port Commands</h1>
     <dl>
       <dt>[<code>--may-exist</code>] <code>lsp-add</code> <var>switch</var> <var>port</var></dt>
diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c
index d6d64ea..d90080f 100644
--- a/ovn/utilities/ovn-nbctl.c
+++ b/ovn/utilities/ovn-nbctl.c
@@ -331,6 +331,18 @@  ACL commands:\n\
                             remove ACLs from SWITCH\n\
   acl-list SWITCH           print ACLs for SWITCH\n\
 \n\
+LB commands:\n\
+  lb-add LB VIP[:PORT] IP[:PORT]... [PROTOCOL]\n\
+                            add a load-balancer or VIP to load balancer\n\
+  lb-del LB [VIP]\n\
+                            remove a load-balancer or VIP from load balancer\n\
+  lb-list [SWITCH] [LB]\n\
+                            print load-balancers\n\
+  lb-append-to SWITCH LB\n\
+                            add a load-balancer to SWITCH\n\
+  lb-remove-from SWITCH [LB]\n\
+                            remove load-balancers from SWITCH\n\
+\n\
 Logical switch port commands:\n\
   lsp-add SWITCH PORT       add logical port PORT on SWITCH\n\
   lsp-add SWITCH PORT PARENT TAG\n\
@@ -493,6 +505,39 @@  ls_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist)
     return ls;
 }
 
+static const struct nbrec_load_balancer *
+lb_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist)
+{
+    const struct nbrec_load_balancer *lb = NULL;
+
+    struct uuid lb_uuid;
+    bool is_uuid = uuid_from_string(&lb_uuid, id);
+    if (is_uuid) {
+        lb = nbrec_load_balancer_get_for_uuid(ctx->idl, &lb_uuid);
+    }
+
+    if (!lb) {
+        const struct nbrec_load_balancer *iter;
+
+        NBREC_LOAD_BALANCER_FOR_EACH(iter, ctx->idl) {
+            if (strcmp(iter->name, id)) {
+                continue;
+            }
+            if (lb) {
+                ctl_fatal("Multiple load balancers named '%s'.  "
+                          "Use a UUID.", id);
+            }
+            lb = iter;
+        }
+    }
+
+    if (!lb && must_exist) {
+        ctl_fatal("%s: load balancer %s not found", id, is_uuid ? "UUID" : "name");
+    }
+
+    return lb;
+}
+
 /* Given pointer to logical router, this routine prints the router
  * information.  */
 static void
@@ -1315,7 +1360,259 @@  nbctl_acl_del(struct ctl_context *ctx)
         }
     }
 }
-
+
+static void
+nbctl_lb_add(struct ctl_context *ctx)
+{
+    const char *lb_name = ctx->argv[1];
+    const char *lb_vip = ctx->argv[2];
+    const char *lb_ips = ctx->argv[3];
+
+    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
+    bool add_duplicate = shash_find(&ctx->options, "--add-duplicate") != NULL;
+
+    const char *lb_proto;
+    bool is_update_proto = false;
+    if (ctx->argc == 4) {
+        /* Default protocol. */
+        lb_proto = "tcp";
+    } else {
+        /* Validate protocol. */
+        lb_proto = ctx->argv[4];
+        is_update_proto = true;
+        if (strcmp(lb_proto, "tcp") && strcmp(lb_proto, "udp")) {
+            ctl_fatal("%s: protocol must be one of \"tcp\", \"udp\".", lb_proto);
+        }
+    }
+
+    const struct nbrec_load_balancer *lb = NULL;
+    if (!add_duplicate) {
+        lb = lb_by_name_or_uuid(ctx, lb_name, false);
+        if (lb) {
+            if (smap_get(&lb->vips, lb_vip)) {
+                if (!may_exist) {
+                    ctl_fatal("%s: a load balancer with this vip (%s) already exists", lb_name, lb_vip);
+                }
+                /* Update the vips. */
+                smap_replace(CONST_CAST(struct smap * ,&lb->vips), lb_vip, lb_ips);
+            } else {
+                /* Add the new vips. */
+                smap_add(CONST_CAST(struct smap * ,&lb->vips), lb_vip, lb_ips);
+            }
+
+            /* Update the load balancer. */
+            if (is_update_proto) {
+                nbrec_load_balancer_verify_protocol(lb);
+                nbrec_load_balancer_set_protocol(lb, lb_proto);
+            }
+            nbrec_load_balancer_verify_vips(lb);
+            nbrec_load_balancer_set_vips(lb, &lb->vips);
+            return;
+        }
+    }
+
+    /* Create the load balancer. */
+    lb = nbrec_load_balancer_insert(ctx->txn);
+    nbrec_load_balancer_set_name(lb, lb_name);
+    nbrec_load_balancer_set_protocol(lb, lb_proto);
+    smap_add(CONST_CAST(struct smap * ,&lb->vips), lb_vip, lb_ips);
+    nbrec_load_balancer_set_vips(lb, &lb->vips);
+}
+
+static void
+nbctl_lb_del(struct ctl_context *ctx)
+{
+    const char *id = ctx->argv[1];
+    const struct nbrec_load_balancer *lb = NULL;
+    bool must_exist = !shash_find(&ctx->options, "--if-exists");
+
+    lb = lb_by_name_or_uuid(ctx, id, false);
+    if (!lb) {
+        return;
+    }
+
+    if (ctx->argc == 3) {
+        const char *lb_vip = ctx->argv[2];
+        if (smap_get(&lb->vips, lb_vip)) {
+            smap_remove(CONST_CAST(struct smap * ,&lb->vips), lb_vip);
+            if (smap_is_empty(&lb->vips)) {
+                nbrec_load_balancer_delete(lb);
+                return;
+            }
+
+            /* Delete the vip of the load balancer. */
+            nbrec_load_balancer_verify_vips(lb);
+            nbrec_load_balancer_set_vips(lb, &lb->vips);
+            return;
+        }
+        if (must_exist) {
+            ctl_fatal("vip %s is not part of the load balancer.",
+                    lb_vip);
+        }
+        return;
+    }
+    nbrec_load_balancer_delete(lb);
+}
+
+static const struct smap_node **
+nbctl_lb_list_switch(struct ctl_context *ctx, struct smap *lbs,
+                     const char *ls_name, const char *lb_name, bool lb_check)
+{
+    const struct nbrec_logical_switch *ls;
+    ls = ls_by_name_or_uuid(ctx, ls_name, true);
+
+    for (int i = 0; i < ls->n_load_balancer; i++) {
+        const struct nbrec_load_balancer *lb
+            = ls->load_balancer[i];
+        if (lb_check && strcmp(lb->name, lb_name)) {
+            continue;
+        }
+
+        const struct smap_node **nodes = smap_sort(&lb->vips);
+        if (nodes) {
+            struct ds key = DS_EMPTY_INITIALIZER;
+            ds_put_format(&key, "%-10.8s" UUID_FMT,
+                    lb->name, UUID_ARGS(&lb->header_.uuid));
+            for (int i = 0; i < smap_count(&lb->vips); i++) {
+                const struct smap_node *node = nodes[i];
+                smap_add_format(lbs, ds_cstr(&key), UUID_FMT "   %-10.8s %-8s %-20s %s",
+                            UUID_ARGS(&lb->header_.uuid), lb->name, lb->protocol, node->key, node->value);
+            }
+
+            ds_destroy(&key);
+            free(nodes);
+        }
+    }
+
+    return smap_sort(lbs);
+}
+
+static const struct smap_node **
+nbctl_lb_list_all(struct ctl_context *ctx, struct smap *lbs) {
+
+    const struct nbrec_load_balancer *lb;
+    NBREC_LOAD_BALANCER_FOR_EACH(lb, ctx->idl) {
+        const struct smap_node **nodes = smap_sort(&lb->vips);
+        if (nodes) {
+            struct ds key = DS_EMPTY_INITIALIZER;
+            ds_put_format(&key, "%-10.8s" UUID_FMT,
+                    lb->name, UUID_ARGS(&lb->header_.uuid));
+            for (int i = 0; i < smap_count(&lb->vips); i++) {
+                const struct smap_node *node = nodes[i];
+                smap_add_format(lbs, ds_cstr(&key), UUID_FMT "   %-10.8s %-8s %-20s %s",
+                            UUID_ARGS(&lb->header_.uuid), lb->name, lb->protocol, node->key, node->value);
+            }
+
+            ds_destroy(&key);
+            free(nodes);
+        }
+    }
+
+    return smap_sort(lbs);
+}
+
+static void
+nbctl_lb_list(struct ctl_context *ctx)
+{
+    struct smap lbs = SMAP_INITIALIZER(&lbs);
+    const struct smap_node **nodes = NULL;
+
+    if (ctx->argc == 1) {
+        nodes = nbctl_lb_list_all(ctx, &lbs);
+    } else if (ctx->argc == 2) {
+        nodes = nbctl_lb_list_switch(ctx, &lbs, ctx->argv[1], NULL, false);
+    } else {
+        nodes = nbctl_lb_list_switch(ctx, &lbs, ctx->argv[1], ctx->argv[2], true);
+    }
+
+    if (nodes) {
+        ds_put_format(&ctx->output, "%-36s   %-10.8s %-8s %-20s %s\n",
+                "UUID", "LB", "PROTO", "VIP", "IPs");
+        for (size_t i = 0; i < smap_count(&lbs); i++) {
+            const struct smap_node *node = nodes[i];
+            ds_put_format(&ctx->output, "%s\n", node->value);
+        }
+
+        smap_destroy(&lbs);
+        free(nodes);
+    }
+}
+
+static void
+nbctl_lb_append_to(struct ctl_context *ctx)
+{
+    const struct nbrec_logical_switch *ls;
+    const struct nbrec_load_balancer *new_lb;
+
+    ls = ls_by_name_or_uuid(ctx, ctx->argv[1], true);
+    new_lb = lb_by_name_or_uuid(ctx, ctx->argv[2], true);
+
+    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
+    for (int i = 0; i < ls->n_load_balancer; i++) {
+        const struct nbrec_load_balancer *lb
+            = ls->load_balancer[i];
+
+        if (uuid_equals(&new_lb->header_.uuid, &lb->header_.uuid)) {
+            if (may_exist) {
+                return;
+            }
+            ctl_fatal(UUID_FMT " : a load balancer with this UUID already exists",
+                    UUID_ARGS(&lb->header_.uuid));
+        }
+    }
+
+    /* Insert the load balancer into the logical switch. */
+    nbrec_logical_switch_verify_load_balancer(ls);
+    struct nbrec_load_balancer **new_lbs
+        = xmalloc(sizeof *new_lbs * (ls->n_load_balancer + 1));
+
+    memcpy(new_lbs, ls->load_balancer, sizeof *new_lbs * ls->n_load_balancer);
+    new_lbs[ls->n_load_balancer] = CONST_CAST(struct nbrec_load_balancer *, new_lb);
+    nbrec_logical_switch_set_load_balancer(ls, new_lbs, ls->n_load_balancer + 1);
+    free(new_lbs);
+}
+
+static void
+nbctl_lb_remove_from(struct ctl_context *ctx)
+{
+    const struct nbrec_logical_switch *ls;
+    const struct nbrec_load_balancer *del_lb;
+    ls = ls_by_name_or_uuid(ctx, ctx->argv[1], true);
+
+    if (ctx->argc == 2) {
+        /* If load-balancer is not specified, remove
+         * all load-balancers from the logical switch. */
+        nbrec_logical_switch_verify_load_balancer(ls);
+        nbrec_logical_switch_set_load_balancer(ls, NULL, 0);
+        return;
+    }
+
+    del_lb = lb_by_name_or_uuid(ctx, ctx->argv[2], true);
+    for (size_t i = 0; i < ls->n_load_balancer; i++) {
+        const struct nbrec_load_balancer *lb
+            = ls->load_balancer[i];
+
+        if (uuid_equals(&del_lb->header_.uuid, &lb->header_.uuid)) {
+            /* Remove the matching rule. */
+            nbrec_logical_switch_verify_load_balancer(ls);
+
+            struct nbrec_load_balancer **new_lbs
+                = xmemdup(ls->load_balancer, sizeof *new_lbs * ls->n_load_balancer);
+            new_lbs[i] = ls->load_balancer[ls->n_load_balancer - 1];
+            nbrec_logical_switch_set_load_balancer(ls, new_lbs,
+                                          ls->n_load_balancer - 1);
+            free(new_lbs);
+            return;
+        }
+    }
+
+    bool must_exist = !shash_find(&ctx->options, "--if-exists");
+    if (must_exist) {
+        ctl_fatal("load balancer %s is not part of any logical switch.",
+                del_lb->name);
+    }
+}
+
 static void
 nbctl_lr_add(struct ctl_context *ctx)
 {
@@ -2420,6 +2717,14 @@  static const struct ctl_command_syntax nbctl_commands[] = {
       nbctl_acl_del, NULL, "", RW },
     { "acl-list", 1, 1, "SWITCH", NULL, nbctl_acl_list, NULL, "", RO },
 
+    /* load balancer commands. */
+    { "lb-add", 3, 4, "LB VIP[:PORT] IP[:PORT]... [PROTOCOL]", NULL,
+      nbctl_lb_add, NULL, "--may-exist,--add-duplicate", RW },
+    { "lb-del", 1, 2, "LB [VIP]", NULL, nbctl_lb_del, NULL, "--if-exists", RW },
+    { "lb-list", 0, 2, "[SWITCH] [LB]", NULL, nbctl_lb_list, NULL, "", RO },
+    { "lb-append-to", 2, 2, "SWITCH LB", NULL, nbctl_lb_append_to, NULL, "--may-exist", RW },
+    { "lb-remove-from", 1, 2, "SWITCH [LB]", NULL, nbctl_lb_remove_from, NULL, "--if-exists", RW },
+
     /* logical switch port commands. */
     { "lsp-add", 2, 4, "SWITCH PORT [PARENT] [TAG]", NULL, nbctl_lsp_add,
       NULL, "--may-exist", RW },
diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
index 5357ced..6cbcf16 100644
--- a/tests/ovn-nbctl.at
+++ b/tests/ovn-nbctl.at
@@ -239,6 +239,103 @@  AT_CLEANUP
 
 dnl ---------------------------------------------------------------------
 
+AT_SETUP([ovn-nbctl - LBs])
+OVN_NBCTL_TEST_START
+
+dnl Add two LBs.
+AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80])
+AT_CHECK([ovn-nbctl lb-add lb1 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80 tcp])
+AT_CHECK([ovn-nbctl lb-list | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
+UUID                                   LB         PROTO    VIP                  IPs
+<0>   lb0        tcp      30.0.0.10:80         192.168.10.10:80,192.168.10.20:80
+<1>   lb1        tcp      30.0.0.10:80         192.168.10.10:80,192.168.10.20:80
+])
+
+dnl Update the VIP of the lb1.
+AT_CHECK([ovn-nbctl --may-exist lb-add lb1 30.0.0.10:80 192.168.10.10:8080,192.168.10.20:8080])
+AT_CHECK([ovn-nbctl lb-list | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
+UUID                                   LB         PROTO    VIP                  IPs
+<0>   lb0        tcp      30.0.0.10:80         192.168.10.10:80,192.168.10.20:80
+<1>   lb1        tcp      30.0.0.10:80         192.168.10.10:8080,192.168.10.20:8080
+])
+
+AT_CHECK([ovn-nbctl --may-exist lb-add lb1 30.0.0.10:80 192.168.10.10:8080,192.168.10.20:8080 udp])
+AT_CHECK([ovn-nbctl lb-list | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
+UUID                                   LB         PROTO    VIP                  IPs
+<0>   lb0        tcp      30.0.0.10:80         192.168.10.10:80,192.168.10.20:80
+<1>   lb1        udp      30.0.0.10:80         192.168.10.10:8080,192.168.10.20:8080
+])
+
+dnl Config lb1 with another VIP.
+AT_CHECK([ovn-nbctl lb-add lb1 30.0.0.20:80 192.168.10.10:80,192.168.10.20:80 udp])
+AT_CHECK([ovn-nbctl lb-del lb1 30.0.0.20:80])
+AT_CHECK([ovn-nbctl lb-list | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
+UUID                                   LB         PROTO    VIP                  IPs
+<0>   lb0        tcp      30.0.0.10:80         192.168.10.10:80,192.168.10.20:80
+<1>   lb1        udp      30.0.0.10:80         192.168.10.10:8080,192.168.10.20:8080
+])
+
+AT_CHECK([ovn-nbctl lb-add lb2 30.0.0.10:8080 192.168.10.10:80,192.168.10.20:80 tcp])
+AT_CHECK([ovn-nbctl --add-duplicate lb-add lb2 30.0.0.10:8080 192.168.10.10:80,192.168.10.20:80 tcp])
+AT_CHECK([ovn-nbctl lb-list | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
+UUID                                   LB         PROTO    VIP                  IPs
+<0>   lb0        tcp      30.0.0.10:80         192.168.10.10:80,192.168.10.20:80
+<1>   lb1        udp      30.0.0.10:80         192.168.10.10:8080,192.168.10.20:8080
+<2>   lb2        tcp      30.0.0.10:8080       192.168.10.10:80,192.168.10.20:80
+<3>   lb2        tcp      30.0.0.10:8080       192.168.10.10:80,192.168.10.20:80
+])
+
+dnl If there are multiple load balancers with the same name, use a UUID to update/delete.
+AT_CHECK([ovn-nbctl lb-add lb2 30.0.0.10:8080 192.168.10.10:80,192.168.10.20:80 tcp], [1], [],
+[ovn-nbctl: Multiple load balancers named 'lb2'.  Use a UUID.
+])
+
+AT_CHECK([ovn-nbctl lb-del lb2], [1], [],
+[ovn-nbctl: Multiple load balancers named 'lb2'.  Use a UUID.
+])
+
+AT_CHECK([ovn-nbctl --may-exist lb-add lb1 30.0.0.10:80 192.168.10.10:8080,192.168.10.20:8080 udp])
+AT_CHECK([ovn-nbctl --may-exist lb-add lb1 30.0.0.10:8080 192.168.10.10:8080,192.168.10.20:8080 udp])
+AT_CHECK([ovn-nbctl --may-exist lb-add lb1 30.0.0.10:9090 192.168.10.10:8080,192.168.10.20:8080 udp])
+AT_CHECK([ovn-nbctl lb-del lb0 30.0.0.10:80])
+AT_CHECK([ovn-nbctl lb-del lb1])
+AT_CHECK([ovn-nbctl lb-list | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
+UUID                                   LB         PROTO    VIP                  IPs
+<0>   lb2        tcp      30.0.0.10:8080       192.168.10.10:80,192.168.10.20:80
+<1>   lb2        tcp      30.0.0.10:8080       192.168.10.10:80,192.168.10.20:80
+])
+
+AT_CHECK([ovn-nbctl ls-add ls0])
+AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80])
+AT_CHECK([ovn-nbctl lb-add lb1 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80 udp])
+AT_CHECK([ovn-nbctl lb-append-to ls0 lb0])
+AT_CHECK([ovn-nbctl lb-append-to ls0 lb1])
+AT_CHECK([ovn-nbctl --may-exist lb-append-to ls0 lb1])
+AT_CHECK([ovn-nbctl lb-append-to ls0 lb2], [1], [],
+[ovn-nbctl: Multiple load balancers named 'lb2'.  Use a UUID.
+])
+
+AT_CHECK([ovn-nbctl lb-list ls0 | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
+UUID                                   LB         PROTO    VIP                  IPs
+<0>   lb0        tcp      30.0.0.10:80         192.168.10.10:80,192.168.10.20:80
+<1>   lb1        udp      30.0.0.10:80         192.168.10.10:80,192.168.10.20:80
+])
+
+AT_CHECK([ovn-nbctl lb-remove-from ls0 lb0])
+AT_CHECK([ovn-nbctl lb-list ls0 | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
+UUID                                   LB         PROTO    VIP                  IPs
+<0>   lb1        udp      30.0.0.10:80         192.168.10.10:80,192.168.10.20:80
+])
+
+AT_CHECK([ovn-nbctl lb-remove-from ls0 lb1])
+AT_CHECK([ovn-nbctl lb-list ls0 | ${PERL} $srcdir/uuidfilt.pl], [0], [])
+AT_CHECK([ovn-nbctl --if-exists lb-remove-from ls0 lb1])
+
+OVN_NBCTL_TEST_STOP
+AT_CLEANUP
+
+dnl ---------------------------------------------------------------------
+
 AT_SETUP([ovn-nbctl - basic logical router commands])
 OVN_NBCTL_TEST_START