[ovs-dev,v5] ovn-nbctl: Add LB commands.
diff mbox

Message ID CAM_3v9L_MOjBo8TnpM88qutreKd83zH6=h7gP56qt2MjyO0+2A@mail.gmail.com
State Not Applicable
Headers show

Commit Message

Gurucharan Shetty Sept. 15, 2016, 5:12 p.m. UTC
On 9 September 2016 at 18:30, 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 or routers. 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>
>

Thanks. This looks close. I feel the output of the "list" commands are a
little awkward. For e.g.:

ovn-nbctl lb-list
UUID                                   LB         PROTO    VIP
     IPs
fc916e82-bace-4b86-9d1a-cee5fc796499   lb0        udp      192.168.1.2
     10.0.0.10,10.0.0.20
fc916e82-bace-4b86-9d1a-cee5fc796499   lb0        udp      30.0.0.10:80
    192.168.10.10:80,192.168.10.20:80,192.168.10.30:80


What would be nice is to aggregate the vips of a load-balancer and list it
without the first 2 columns. e.g:

UUID                                   LB         PROTO    VIP
     IPs
fc916e82-bace-4b86-9d1a-cee5fc796499   lb0        udp      192.168.1.2
     10.0.0.10,10.0.0.20

     udp      30.0.0.10:80         192.168.10.10:80,192.168.10.20:80,
192.168.10.30:80


The above style is used in many commands in the OVS repo, so it would be
nice if we are consistent.

Also, in the documentation please try to limit lines to less than 79
characters. I use vim and I have the following in my .vimrc to show the the
transgressing lines:

:syntax on
:colorscheme zellner
:match ErrorMsg '\%>79v.\+'


Please add the following incremental for your next version (please do read
it through once and fix any formatting or spelling issues).

+          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,
+        If <var>vip</var> is supplied, only the <var>vip</var> will be
deleted
+        from the <var>lb</var>.  If only the <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.
+        does not already exist in <var>lb</var>, unless
+        <code>--if-exists</code> is specified.
       </dd>

       <dt><code>lb-list</code> [<var>lb</var>]</dt>
       <dd>
-        Lists the LBs.  If <var>lb</var> is also specified, then the
<var>lb</var> will be listed.
+        Lists the LBs.  If <var>lb</var> is also specified, then only the
+        specified <var>lb</var> will be listed.
       </dd>

       <dt>[<code>--may-exist</code>] <code>ls-lb-add</code>
<var>switch</var> <var>lb</var></dt>
@@ -447,18 +467,19 @@

       <dt>[<code>--if-exists</code>] <code>ls-lb-del</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.
+        Removes <var>lb</var> from <var>switch</var>.  If only
+        <var>switch</var> is supplied, all the LBs from the logical switch
are
+        removed.  If <var>lb</var> is also specified, then only the
+        <var>lb</var> will be removed 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>

       <dt><code>ls-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.
+        the logical switch are listed.  If <var>lb</var> is also specified,
+        only the <var>lb</var> will be listed from the logical switch.
       </dd>

       <dt>[<code>--may-exist</code>] <code>lr-lb-add</code>
<var>router</var> <var>lb</var></dt>
@@ -470,18 +491,19 @@

       <dt>[<code>--if-exists</code>] <code>lr-lb-del</code>
<var>router</var> [<var>lb</var>]</dt>
       <dd>
-        Deletes <var>lb</var> from <var>router</var>.  If only
<var>router</var> is supplied,
-        all the LBs from the logical router are deleted.  If <var>lb</var>
is also specified,
-        then the <var>lb</var> will be deleted only from the logical
router.
-        It is an error if <var>lb</var> does not exist in the
<var>router</var>,
-        unless <code>--if-exists</code> is specified.
+        Removes <var>lb</var> from <var>router</var>.  If only
+        <var>router</var> is supplied, all the LBs from the logical router
are
+        removed.  If <var>lb</var> is also specified, then only the
+        <var>lb</var> will be deleted from the logical router.
+        It is an error if <var>lb</var> does not exist in the
+        <var>router</var>, unless <code>--if-exists</code> is specified.
       </dd>

       <dt><code>lr-lb-list</code> <var>router</var> [<var>lb</var>]</dt>
       <dd>
         Lists the LBs.  If <var>router</var> is supplied, all the LBs from
         the logical router are listed. If <var>lb</var> is also specified,
-        then the <var>lb</var> will be listed only from the logical router.
+        then only the <var>lb</var> will be listed from the logical router.
       </dd>
     </dl>






> ---
>  ovn/ovn-nb.ovsschema          |   3 +-
>  ovn/ovn-nb.xml                |   6 +
>  ovn/utilities/ovn-nbctl.8.xml |  86 ++++++++
>  ovn/utilities/ovn-nbctl.c     | 471 ++++++++++++++++++++++++++++++
> +++++++++++-
>  tests/ovn-nbctl.at            | 147 +++++++++++++
>  5 files changed, 711 insertions(+), 2 deletions(-)
>
> diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
> index b7e70aa..7772ad2 100644
> --- a/ovn/ovn-nb.ovsschema
> +++ b/ovn/ovn-nb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Northbound",
>      "version": "5.3.3",
> -    "cksum": "2442952958 9945",
> +    "cksum": "1191768021 9975",
>      "tables": {
>          "NB_Global": {
>              "columns": {
> @@ -97,6 +97,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 9a8bdbd..2ebae29 100644
> --- a/ovn/ovn-nb.xml
> +++ b/ovn/ovn-nb.xml
> @@ -676,6 +676,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 76cf97e..84a74ae 100644
> --- a/ovn/utilities/ovn-nbctl.8.xml
> +++ b/ovn/utilities/ovn-nbctl.8.xml
> @@ -400,6 +400,92 @@
>        </dd>
>      </dl>
>
> +    <h1>Logical Switch/Router 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>lb</var>]</dt>
> +      <dd>
> +        Lists the LBs.  If <var>lb</var> is also specified, then the
> <var>lb</var> will be listed.
> +      </dd>
> +
> +      <dt>[<code>--may-exist</code>] <code>ls-lb-add</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>ls-lb-del</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>
> +
> +      <dt><code>ls-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>lr-lb-add</code>
> <var>router</var> <var>lb</var></dt>
> +      <dd>
> +        Adds the specified <var>lb</var> to <var>router</var>.
> +        It is an error if a load balancer named <var>lb</var> already
> exists
> +        in the <var>router</var>, unless <code>--may-exist</code> is
> specified.
> +      </dd>
> +
> +      <dt>[<code>--if-exists</code>] <code>lr-lb-del</code>
> <var>router</var> [<var>lb</var>]</dt>
> +      <dd>
> +        Deletes <var>lb</var> from <var>router</var>.  If only
> <var>router</var> is supplied,
> +        all the LBs from the logical router are deleted.  If
> <var>lb</var> is also specified,
> +        then the <var>lb</var> will be deleted only from the logical
> router.
> +        It is an error if <var>lb</var> does not exist in the
> <var>router</var>,
> +        unless <code>--if-exists</code> is specified.
> +      </dd>
> +
> +      <dt><code>lr-lb-list</code> <var>router</var> [<var>lb</var>]</dt>
> +      <dd>
> +        Lists the LBs.  If <var>router</var> is supplied, all the LBs from
> +        the logical router are listed. If <var>lb</var> is also specified,
> +        then the <var>lb</var> will be listed only from the logical
> router.
> +      </dd>
> +    </dl>
> +
> +
>      <h1>DHCP Options commands</h1>
>
>      <dl>
> diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c
> index 2148665..f82aa39 100644
> --- a/ovn/utilities/ovn-nbctl.c
> +++ b/ovn/utilities/ovn-nbctl.c
> @@ -384,6 +384,17 @@ Route commands:\n\
>                              remove routes from ROUTER\n\
>    lr-route-list ROUTER      print routes for ROUTER\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]           remove a load-balancer or VIP from load
> balancer\n\
> +  lb-list [LB]              print load-balancers\n\
> +  lr-lb-add ROUTER LB       add a load-balancer to ROUTER\n\
> +  lr-lb-del ROUTER [LB]     remove load-balancers from ROUTER\n\
> +  lr-lb-list ROUTER [LB]    print load-balancers\n\
> +  ls-lb-add SWITCH LB       add a load-balancer to SWITCH\n\
> +  ls-lb-del SWITCH [LB]     remove load-balancers from SWITCH\n\
> +  ls-lb-list SWITCH [LB]    print load-balancers\n\
>  \n\
>  DHCP Options commands:\n\
>    dhcp-options-create CIDR [EXTERNAL_IDS]\n\
> @@ -493,6 +504,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
> @@ -1316,7 +1360,420 @@ 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_router(struct ctl_context *ctx, struct smap *lbs,
> +                     const char *lr_name, const char *lb_name, bool
> lb_check)
> +{
> +    const struct nbrec_logical_router *lr;
> +    lr = lr_by_name_or_uuid(ctx, lr_name, true);
> +
> +    for (int i = 0; i < lr->n_load_balancer; i++) {
> +        const struct nbrec_load_balancer *lb
> +            = lr->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_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 char
> *lb_name, bool lb_check) {
> +
> +    const struct nbrec_load_balancer *lb;
> +    NBREC_LOAD_BALANCER_FOR_EACH(lb, ctx->idl) {
> +
> +        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 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, NULL, false);
> +    } else if (ctx->argc == 2) {
> +        nodes = nbctl_lb_list_all(ctx, &lbs, ctx->argv[1], 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_lr_lb_add(struct ctl_context *ctx)
> +{
> +    const struct nbrec_logical_router *lr;
> +    const struct nbrec_load_balancer *new_lb;
> +
> +    lr = lr_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 < lr->n_load_balancer; i++) {
> +        const struct nbrec_load_balancer *lb
> +            = lr->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 router. */
> +    nbrec_logical_router_verify_load_balancer(lr);
> +    struct nbrec_load_balancer **new_lbs
> +        = xmalloc(sizeof *new_lbs * (lr->n_load_balancer + 1));
> +
> +    memcpy(new_lbs, lr->load_balancer, sizeof *new_lbs *
> lr->n_load_balancer);
> +    new_lbs[lr->n_load_balancer] = CONST_CAST(struct nbrec_load_balancer
> *, new_lb);
> +    nbrec_logical_router_set_load_balancer(lr, new_lbs,
> lr->n_load_balancer + 1);
> +    free(new_lbs);
> +}
> +
> +static void
> +nbctl_lr_lb_del(struct ctl_context *ctx)
> +{
> +    const struct nbrec_logical_router *lr;
> +    const struct nbrec_load_balancer *del_lb;
> +    lr = lr_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 router. */
> +        nbrec_logical_router_verify_load_balancer(lr);
> +        nbrec_logical_router_set_load_balancer(lr, NULL, 0);
> +        return;
> +    }
> +
> +    del_lb = lb_by_name_or_uuid(ctx, ctx->argv[2], true);
> +    for (size_t i = 0; i < lr->n_load_balancer; i++) {
> +        const struct nbrec_load_balancer *lb
> +            = lr->load_balancer[i];
> +
> +        if (uuid_equals(&del_lb->header_.uuid, &lb->header_.uuid)) {
> +            /* Remove the matching rule. */
> +            nbrec_logical_router_verify_load_balancer(lr);
> +
> +            struct nbrec_load_balancer **new_lbs
> +                = xmemdup(lr->load_balancer, sizeof *new_lbs *
> lr->n_load_balancer);
> +            new_lbs[i] = lr->load_balancer[lr->n_load_balancer - 1];
> +            nbrec_logical_router_set_load_balancer(lr, new_lbs,
> +                                          lr->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 router.",
> +                del_lb->name);
> +    }
> +}
> +
> +static void
> +nbctl_lr_lb_list(struct ctl_context *ctx)
> +{
> +    struct smap lbs = SMAP_INITIALIZER(&lbs);
> +    const struct smap_node **nodes = NULL;
> +
> +    if (ctx->argc == 2) {
> +        nodes = nbctl_lb_list_router(ctx, &lbs, ctx->argv[1], NULL,
> false);
> +    } else {
> +        nodes = nbctl_lb_list_router(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_ls_lb_add(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_ls_lb_del(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_ls_lb_list(struct ctl_context *ctx)
> +{
> +    struct smap lbs = SMAP_INITIALIZER(&lbs);
> +    const struct smap_node **nodes = NULL;
> +
> +    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_lr_add(struct ctl_context *ctx)
>  {
> @@ -2480,6 +2937,18 @@ static const struct ctl_command_syntax
> nbctl_commands[] = {
>      { "lr-route-list", 1, 1, "ROUTER", NULL, nbctl_lr_route_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, 1, "[LB]", NULL, nbctl_lb_list, NULL, "", RO },
> +    { "lr-lb-add", 2, 2, "ROUTER LB", NULL, nbctl_lr_lb_add, NULL,
> "--may-exist", RW },
> +    { "lr-lb-del", 1, 2, "ROUTER [LB]", NULL, nbctl_lr_lb_del, NULL,
> "--if-exists", RW },
> +    { "lr-lb-list", 1, 2, "ROUTER [LB]", NULL, nbctl_lr_lb_list, NULL,
> "", RO },
> +    { "ls-lb-add", 2, 2, "SWITCH LB", NULL, nbctl_ls_lb_add, NULL,
> "--may-exist", RW },
> +    { "ls-lb-del", 1, 2, "SWITCH [LB]", NULL, nbctl_ls_lb_del, NULL,
> "--if-exists", RW },
> +    { "ls-lb-list", 1, 2, "SWITCH [LB]", NULL, nbctl_ls_lb_list, NULL,
> "", RO },
> +
>      /* DHCP_Options commands */
>      {"dhcp-options-create", 1, INT_MAX, "CIDR [EXTERNAL:IDS]", NULL,
>       nbctl_dhcp_options_create, NULL, "", RW },
> diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
> index 241e6d3..9508e70 100644
> --- a/tests/ovn-nbctl.at
> +++ b/tests/ovn-nbctl.at
> @@ -239,6 +239,153 @@ 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
> +])
> +
> +dnl Add load balancer to logical switch.
> +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 ls-lb-add ls0 lb0])
> +AT_CHECK([ovn-nbctl ls-lb-add ls0 lb1])
> +AT_CHECK([ovn-nbctl --may-exist ls-lb-add ls0 lb1])
> +AT_CHECK([ovn-nbctl ls-lb-add ls0 lb2], [1], [],
> +[ovn-nbctl: Multiple load balancers named 'lb2'.  Use a UUID.
> +])
> +
> +AT_CHECK([ovn-nbctl ls-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 ls-lb-list ls0 lb0 | ${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
> +])
> +
> +AT_CHECK([ovn-nbctl ls-lb-del ls0 lb0])
> +AT_CHECK([ovn-nbctl ls-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 ls-lb-del ls0 lb1])
> +AT_CHECK([ovn-nbctl ls-lb-list ls0 | ${PERL} $srcdir/uuidfilt.pl], [0],
> [])
> +AT_CHECK([ovn-nbctl --if-exists ls-lb-del ls0 lb1])
> +
> +dnl Remove all load balancers from logical switch.
> +AT_CHECK([ovn-nbctl ls-lb-add ls0 lb0])
> +AT_CHECK([ovn-nbctl ls-lb-add ls0 lb1])
> +AT_CHECK([ovn-nbctl ls-lb-del ls0])
> +AT_CHECK([ovn-nbctl ls-lb-list ls0 | ${PERL} $srcdir/uuidfilt.pl], [0],
> [])
> +
> +dnl Add load balancer to logical router.
> +AT_CHECK([ovn-nbctl lr-add lr0])
> +AT_CHECK([ovn-nbctl lb-add lb00 30.0.0.10:80 192.168.10.10:80,192.168.10.
> 20:80])
> +AT_CHECK([ovn-nbctl lb-add lb01 30.0.0.10:80 192.168.10.10:80,192.168.10.
> 20:80 udp])
> +AT_CHECK([ovn-nbctl lr-lb-add lr0 lb00])
> +AT_CHECK([ovn-nbctl lr-lb-add lr0 lb01])
> +AT_CHECK([ovn-nbctl --may-exist lr-lb-add lr0 lb01])
> +AT_CHECK([ovn-nbctl lr-lb-add lr0 lb2], [1], [],
> +[ovn-nbctl: Multiple load balancers named 'lb2'.  Use a UUID.
> +])
> +
> +AT_CHECK([ovn-nbctl lr-lb-list lr0 | ${PERL} $srcdir/uuidfilt.pl], [0],
> [dnl
> +UUID                                   LB         PROTO    VIP
>       IPs
> +<0>   lb00       tcp      30.0.0.10:80         192.168.10.10:80,
> 192.168.10.20:80
> +<1>   lb01       udp      30.0.0.10:80         192.168.10.10:80,
> 192.168.10.20:80
> +])
> +
> +AT_CHECK([ovn-nbctl lr-lb-list lr0 lb00 | ${PERL} $srcdir/uuidfilt.pl],
> [0], [dnl
> +UUID                                   LB         PROTO    VIP
>       IPs
> +<0>   lb00       tcp      30.0.0.10:80         192.168.10.10:80,
> 192.168.10.20:80
> +])
> +
> +AT_CHECK([ovn-nbctl lr-lb-del lr0 lb00])
> +AT_CHECK([ovn-nbctl lr-lb-list lr0 | ${PERL} $srcdir/uuidfilt.pl], [0],
> [dnl
> +UUID                                   LB         PROTO    VIP
>       IPs
> +<0>   lb01       udp      30.0.0.10:80         192.168.10.10:80,
> 192.168.10.20:80
> +])
> +
> +AT_CHECK([ovn-nbctl lr-lb-del lr0 lb01])
> +AT_CHECK([ovn-nbctl lr-lb-list lr0 | ${PERL} $srcdir/uuidfilt.pl], [0],
> [])
> +AT_CHECK([ovn-nbctl --if-exists lr-lb-del lr0 lb01])
> +
> +dnl Remove all load balancers from logical router.
> +AT_CHECK([ovn-nbctl lr-lb-add lr0 lb00])
> +AT_CHECK([ovn-nbctl lr-lb-add lr0 lb01])
> +AT_CHECK([ovn-nbctl lr-lb-del lr0])
> +AT_CHECK([ovn-nbctl lr-lb-list lr0 | ${PERL} $srcdir/uuidfilt.pl], [0],
> [])
> +
> +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
>

Patch
diff mbox

diff --git a/ovn/utilities/ovn-nbctl.8.xml b/ovn/utilities/ovn-nbctl.8.xml
index 84a74ae..ada65fb 100644
--- a/ovn/utilities/ovn-nbctl.8.xml
+++ b/ovn/utilities/ovn-nbctl.8.xml
@@ -400,42 +400,62 @@ 
       </dd>
     </dl>

-    <h1>Logical Switch/Router LB Commands</h1>
+    <h1>Load Balancer 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>]
       <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>
+          Creates a new load balancer named <var>lb</var> with the provided
+          <var>vip</var> and <var>ips</var> or adds the <var>vip</var> to
+          an existing <var>lb</var>.  <var>vip</var> should be a
+          virtual IPv4 address (or an IPv4 address and a port number with
+          <code>:</code> as a separator).  Examples for <var>vip</var> are
+          <code>192.168.1.4</code> and <code>192.168.1.5:8080</code>.
+          <var>ips</var> should be comma separated IPv4 endpoints (or comma
+          separated IPv4 addresses and port numbers with <code>:</code> as
a
+          separator).  Examples for <var>ips</var> are
<code>10.0.0.1,10.0.0.2
+          </code> or <code>20.0.0.10:8800,20.0.0.11:8800</code>.
+        </p>
+
+        <p>
+          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 the <var>vip</var>, OVN assumes the <var>protocol</var>
to
+          be <code>tcp</code>.
+        </p>
+
+        <p>
+          It is an error if the <var>vip</var> already exists 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.
+        </p>
+
+        <p>
+          The following example adds a load balancer.
+        </p>
+        <p>
+          <code>lb-add lb0 30.0.0.10:80