From patchwork Fri Sep 2 06:47:34 2016
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
X-Patchwork-Submitter: nickcooper-zhangtonghao
X-Patchwork-Id: 665138
Return-Path:
X-Original-To: incoming@patchwork.ozlabs.org
Delivered-To: patchwork-incoming@bilbo.ozlabs.org
Received: from archives.nicira.com (archives.nicira.com [96.126.127.54])
by ozlabs.org (Postfix) with ESMTP id 3sQV7J0fwfz9s4n
for ;
Fri, 2 Sep 2016 16:47:54 +1000 (AEST)
Received: from archives.nicira.com (localhost [127.0.0.1])
by archives.nicira.com (Postfix) with ESMTP id 641AC10BA6;
Thu, 1 Sep 2016 23:47:52 -0700 (PDT)
X-Original-To: dev@openvswitch.org
Delivered-To: dev@openvswitch.org
Received: from mx3v3.cudamail.com (mx3.cudamail.com [64.34.241.5])
by archives.nicira.com (Postfix) with ESMTPS id A28E610BA5
for ; Thu, 1 Sep 2016 23:47:51 -0700 (PDT)
Received: from bar6.cudamail.com (localhost [127.0.0.1])
by mx3v3.cudamail.com (Postfix) with ESMTPS id DA4811621AB
for ; Fri, 2 Sep 2016 00:47:50 -0600 (MDT)
X-ASG-Debug-ID: 1472798866-0b32370b332b6760001-byXFYA
Received: from mx1-pf2.cudamail.com ([192.168.24.2]) by bar6.cudamail.com
with
ESMTP id 5qhfmU0i2YIxjHe4 (version=TLSv1 cipher=DHE-RSA-AES256-SHA
bits=256 verify=NO) for ;
Fri, 02 Sep 2016 00:47:46 -0600 (MDT)
X-Barracuda-Envelope-From: nickcooper-zhangtonghao@opencloud.tech
X-Barracuda-RBL-Trusted-Forwarder: 192.168.24.2
Received: from unknown (HELO smtpbg65.qq.com) (103.7.28.233)
by mx1-pf2.cudamail.com with ESMTPS (DHE-RSA-AES256-SHA encrypted);
2 Sep 2016 06:47:44 -0000
Received-SPF: none (mx1-pf2.cudamail.com: domain at opencloud.tech does not
designate permitted sender hosts)
X-Barracuda-Apparent-Source-IP: 103.7.28.233
X-Barracuda-RBL-IP: 103.7.28.233
X-QQ-mid: bizesmtp14t1472798854tl075r5f
Received: from local.opencloud.tech.localdomai (unknown [106.120.127.11])
by esmtp4.qq.com (ESMTP) with
id ; Fri, 02 Sep 2016 14:47:27 +0800 (CST)
X-QQ-SSF: 01100000002000F0FFF0B00A0000000
X-QQ-FEAT: Me8Xob1wlXIWqjHPx3jYkP8vkn7IgHwvqLREZ5O4ixPm9NHpVzF9jt6rjetYA
fwmiOa1g4xZ20a+YtGjy1zE2qgi0HBK7DqpYKLZ4es2fbxGcsThiMT/kI1BQW/amsOpqkO5
fBpRV4onUPWRdQg3gKGobYOrBd6TU9P/z59fCk950dRPSylzCNpbAolIcT0iZdXHqRwaAa7
qy1I254iSABDqCbi5faxk0DqPTWEK9/VZAmRQenfveJpJOCrHjhcx0GnxJRpRbkETAvVvoA
o/OA==
X-QQ-GoodBg: 0
X-CudaMail-Envelope-Sender: nickcooper-zhangtonghao@opencloud.tech
From: nickcooper-zhangtonghao
To: dev@openvswitch.org
X-CudaMail-MID: CM-E2-901000652
X-CudaMail-DTE: 090216
X-CudaMail-Originating-IP: 103.7.28.233
Date: Thu, 1 Sep 2016 23:47:34 -0700
X-ASG-Orig-Subj: [##CM-E2-901000652##][PATCH v4] ovn-nbctl: Add LB commands.
Message-Id:
<1472798854-23488-1-git-send-email-nickcooper-zhangtonghao@opencloud.tech>
X-Mailer: git-send-email 1.8.3.1
X-QQ-SENDSIZE: 520
X-QQ-Bgrelay: 1
X-GBUdb-Analysis: 0, 103.7.28.233, Ugly c=0.308711 p=-0.3 Source Normal
X-MessageSniffer-Rules: 0-0-0-32767-c
X-Barracuda-Connect: UNKNOWN[192.168.24.2]
X-Barracuda-Start-Time: 1472798866
X-Barracuda-Encrypted: DHE-RSA-AES256-SHA
X-Barracuda-URL: https://web.cudamail.com:443/cgi-mod/mark.cgi
X-Barracuda-BRTS-Status: 1
X-Virus-Scanned: by bsmtpd at cudamail.com
X-Barracuda-Spam-Score: 1.10
X-Barracuda-Spam-Status: No, SCORE=1.10 using global scores of TAG_LEVEL=3.5
QUARANTINE_LEVEL=1000.0 KILL_LEVEL=4.0
tests=BSF_SC0_MV0713, BSF_SC5_MJ1963, RDNS_NONE
X-Barracuda-Spam-Report: Code version 3.2, rules version 3.2.3.32552
Rule breakdown below
pts rule name description
---- ----------------------
--------------------------------------------------
0.10 RDNS_NONE Delivered to trusted network by a host with no rDNS
0.50 BSF_SC0_MV0713 Custom rule MV0713
0.50 BSF_SC5_MJ1963 Custom Rule MJ1963
Cc: guru@ovn.org,
nickcooper-zhangtonghao
Subject: [ovs-dev] [PATCH v4] ovn-nbctl: Add LB commands.
X-BeenThere: dev@openvswitch.org
X-Mailman-Version: 2.1.16
Precedence: list
List-Id:
List-Unsubscribe: ,
List-Archive:
List-Post:
List-Help:
List-Subscribe: ,
MIME-Version: 1.0
Errors-To: dev-bounces@openvswitch.org
Sender: "dev"
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
---
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.
+
+ 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.
+
+
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 @@
+
Logical Switch LB Commands
+
+ - [
--may-exist
| --add-duplicate
] lb-add
lb vip ips [protocol]
+ -
+ Creates a new load balancer named lb with the vip or
+ adds the vip to lb. A single load balancer can
+ have multiple vips. We should assign lb 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 protocol must be
+ either
tcp
or udp
. This argument is useful when
+ a port number is provided as part of the vip. If the protocol
+ is unspecified and a port number is provided as part of vip,
+ OVN assumes the protocol to be tcp
. It is an
+ error if the vip has been included in the load balancer named
+ lb, unless --may-exist
is specified.
+ With --add-duplicate
, the command really creates a new load
+ balancer with a duplicate name. The following example adds a load balancer
+ with protocol udp
:
+
+ lb-add lb0 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80,192.168.10.30:80 udp
+
+
+
+ - [
--if-exists
] lb-del
lb [vip]
+ -
+ Deletes lb or the vip from lb.
+ If vip is supplied, the vip will be deleted
+ only from the lb. If only lb is supplied,
+ the lb will be deleted. It is an error if vip
+ does not be included in lb, unless
--if-exists
is specified.
+
+
+ lb-list
[switch] [lb]
+ -
+ Lists the LBs. If switch is supplied, all the LBs from
+ the logical switch are listed. If lb is also specified,
+ then the lb will be listed only from the logical switch.
+
+
+ - [
--may-exist
] lb-append-to
switch lb
+ -
+ Adds the specified lb to switch.
+ It is an error if a load balancer named lb already exists
+ in the switch, unless
--may-exist
is specified.
+
+
+ - [
--if-exists
] lb-remove-from
switch [lb]
+ -
+ Deletes lb from switch. If only switch is supplied,
+ all the LBs from the logical switch are deleted. If lb is also specified,
+ then the lb will be deleted only from the logical switch.
+ It is an error if lb does not exist in the switch,
+ unless
--if-exists
is specified.
+
+
+
Logical Switch Port Commands
- [
--may-exist
] lsp-add
switch port
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