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-zhangtong...@opencloud.tech> --- lib/packets.c | 18 ++ lib/packets.h | 10 + ovn/ovn-nb.ovsschema | 5 +- ovn/ovn-nb.xml | 6 + ovn/utilities/ovn-nbctl.8.xml | 105 +++++++++ ovn/utilities/ovn-nbctl.c | 486 +++++++++++++++++++++++++++++++++++++++++- tests/ovn-nbctl.at | 229 ++++++++++++++++++++ 7 files changed, 856 insertions(+), 3 deletions(-) diff --git a/lib/packets.c b/lib/packets.c index e4c29d5..11bb587 100644 --- a/lib/packets.c +++ b/lib/packets.c @@ -427,6 +427,24 @@ ip_parse(const char *s, ovs_be32 *ip) return inet_pton(AF_INET, s, ip) == 1; } +/* Parses string 's', which must be an IP address with a port number + * with ":" as a separator (e.g.: 192.168.1.2:80). + * Stores the IP address into '*ip' and port number to '*port'. */ +char * OVS_WARN_UNUSED_RESULT +ip_parse_port(const char *s, ovs_be32 *ip, ovs_be16 *port) +{ + int n = 0; + if (!ovs_scan_len(s, &n, IP_PORT_SCAN_FMT, + IP_PORT_SCAN_ARGS(ip, port))) { + return xasprintf("%s: invalid IP address or port number", s); + } + + if (s[n]) { + return xasprintf("%s: invalid IP address or port number", s); + } + return NULL; +} + /* Parses string 's', which must be an IP address with an optional netmask or * CIDR prefix length. Stores the IP address into '*ip', netmask into '*mask', * (255.255.255.255, if 's' lacks a netmask), and number of scanned characters diff --git a/lib/packets.h b/lib/packets.h index dcfcd04..21bd35c 100644 --- a/lib/packets.h +++ b/lib/packets.h @@ -537,6 +537,14 @@ mpls_lse_to_bos(ovs_be32 mpls_lse) &((uint8_t *) ip)[2], \ &((uint8_t *) ip)[3] +#define IP_PORT_SCAN_FMT "%"SCNu8".%"SCNu8".%"SCNu8".%"SCNu8":%"SCNu16 +#define IP_PORT_SCAN_ARGS(ip, port) \ + ((void) (ovs_be32) *(ip), &((uint8_t *) ip)[0]), \ + &((uint8_t *) ip)[1], \ + &((uint8_t *) ip)[2], \ + &((uint8_t *) ip)[3], \ + ((void) (ovs_be16) *(port), (uint16_t *) port) + /* Returns true if 'netmask' is a CIDR netmask, that is, if it consists of N * high-order 1-bits and 32-N low-order 0-bits. */ static inline bool @@ -558,6 +566,8 @@ ip_is_local_multicast(ovs_be32 ip) int ip_count_cidr_bits(ovs_be32 netmask); void ip_format_masked(ovs_be32 ip, ovs_be32 mask, struct ds *); bool ip_parse(const char *s, ovs_be32 *ip); +char *ip_parse_port(const char *s, ovs_be32 *ip, ovs_be16 *port) + OVS_WARN_UNUSED_RESULT; char *ip_parse_masked(const char *s, ovs_be32 *ip, ovs_be32 *mask) OVS_WARN_UNUSED_RESULT; char *ip_parse_cidr(const char *s, ovs_be32 *ip, unsigned int *plen) diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema index b7e70aa..5f2f2bf 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", + "version": "5.3.4", + "cksum": "1155817817 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 c45a444..b7690d0 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..7cd515f 100644 --- a/ovn/utilities/ovn-nbctl.8.xml +++ b/ovn/utilities/ovn-nbctl.8.xml @@ -400,6 +400,111 @@ </dd> </dl> + <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>]</dt> + <dd> + <p> + 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 + 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, 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 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 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> + <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> + 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></dt> + <dd> + Lists the LBs for the given <var>switch</var>. + </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> + 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 removed 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></dt> + <dd> + Lists the LBs for the given <var>router</var>. + </dd> + </dl> + + <h1>DHCP Options commands</h1> <dl> diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c index 2148665..8949212 100644 --- a/ovn/utilities/ovn-nbctl.c +++ b/ovn/utilities/ovn-nbctl.c @@ -384,6 +384,19 @@ 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\ + create a load-balancer or add a VIP to an\n\ + existing load balancer\n\ + lb-del LB [VIP] remove a load-balancer or just the VIP from\n\ + the 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 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 print load-balancers\n\ \n\ DHCP Options commands:\n\ dhcp-options-create CIDR [EXTERNAL_IDS]\n\ @@ -493,6 +506,40 @@ 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 +1363,425 @@ 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]; + 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; + bool is_vip_with_port = true; + + 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); + } + } + + ovs_be32 ipv4 = 0; + ovs_be16 port = 0; + char *error = ip_parse_port(lb_vip, &ipv4, &port); + if (error) { + free(error); + if (!ip_parse(lb_vip, &ipv4)) { + ctl_fatal("%s: should be an IPv4 address (or an IPv4 address " + "and a port number with : as a separator).", lb_vip); + } + + if (is_update_proto) { + ctl_fatal("Protocol is unnecessary when no port of vip " + "is given."); + } + is_vip_with_port = false; + } + + char *token = NULL, *save_ptr = NULL; + struct ds lb_ips_new = DS_EMPTY_INITIALIZER; + for (token = strtok_r(lb_ips, ",", &save_ptr); + token != NULL; token = strtok_r(NULL, ",", &save_ptr)) { + if (is_vip_with_port) { + error = ip_parse_port(token, &ipv4, &port); + if (error) { + free(error); + ds_destroy(&lb_ips_new); + ctl_fatal("%s: should be an IPv4 address and a port " + "number with : as a separator.", token); + } + } else { + if (!ip_parse(token, &ipv4)) { + ds_destroy(&lb_ips_new); + ctl_fatal("%s: should be an IPv4 address.", token); + } + } + ds_put_format(&lb_ips_new, "%s%s", + lb_ips_new.length ? "," : "", token); + } + + 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) { + ds_destroy(&lb_ips_new); + 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, ds_cstr(&lb_ips_new)); + } else { + /* Add the new vips. */ + smap_add(CONST_CAST(struct smap *, &lb->vips), + lb_vip, ds_cstr(&lb_ips_new)); + } + + /* 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); + ds_destroy(&lb_ips_new); + 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, ds_cstr(&lb_ips_new)); + nbrec_load_balancer_set_vips(lb, &lb->vips); + ds_destroy(&lb_ips_new); +} + +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 void +lb_info_add_smap(const struct nbrec_load_balancer *lb, + struct smap *lbs) +{ + struct ds key = DS_EMPTY_INITIALIZER; + struct ds val = DS_EMPTY_INITIALIZER; + char *error, *protocol; + ovs_be32 ipv4 = 0; + ovs_be16 port = 0; + + const struct smap_node **nodes = smap_sort(&lb->vips); + if (nodes) { + for (int i = 0; i < smap_count(&lb->vips); i++) { + const struct smap_node *node = nodes[i]; + protocol = lb->protocol; + error = ip_parse_port(node->key, &ipv4, &port); + if (error) { + free(error); + protocol = "tcp/udp"; + } + + i == 0 ? ds_put_format(&val, + UUID_FMT " %-20.16s%-11.7s%-25.21s%s", + UUID_ARGS(&lb->header_.uuid), + lb->name, protocol, + node->key, node->value) + : ds_put_format(&val, "\n%60s%-11.7s%-25.21s%s", + "", protocol, + node->key, node->value); + } + + ds_put_format(&key, "%-20.16s", lb->name); + smap_add(lbs, ds_cstr(&key), ds_cstr(&val)); + + ds_destroy(&key); + ds_destroy(&val); + free(nodes); + } +} + +static void +lb_info_print(struct ctl_context *ctx, struct smap *lbs) +{ + const struct smap_node **nodes = smap_sort(lbs); + if (nodes) { + ds_put_format(&ctx->output, "%-40.36s%-20.16s%-11.7s%-25.21s%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); + } + + free(nodes); + } +} + +static void +lb_info_list_all(struct ctl_context *ctx, + const char *lb_name, bool lb_check) +{ + const struct nbrec_load_balancer *lb; + struct smap lbs = SMAP_INITIALIZER(&lbs); + + NBREC_LOAD_BALANCER_FOR_EACH(lb, ctx->idl) { + if (lb_check && strcmp(lb->name, lb_name)) { + continue; + } + lb_info_add_smap(lb, &lbs); + } + + lb_info_print(ctx, &lbs); + smap_destroy(&lbs); +} + +static void +nbctl_lb_list(struct ctl_context *ctx) +{ + if (ctx->argc == 1) { + lb_info_list_all(ctx, NULL, false); + } else if (ctx->argc == 2) { + lb_info_list_all(ctx, ctx->argv[1], true); + } +} + +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) +{ + const char *lr_name = ctx->argv[1]; + const struct nbrec_logical_router *lr; + struct smap lbs = SMAP_INITIALIZER(&lbs); + + 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]; + lb_info_add_smap(lb, &lbs); + } + + lb_info_print(ctx, &lbs); + smap_destroy(&lbs); +} + +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) +{ + const char *ls_name = ctx->argv[1]; + const struct nbrec_logical_switch *ls; + struct smap lbs = SMAP_INITIALIZER(&lbs); + + 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]; + lb_info_add_smap(lb, &lbs); + } + + lb_info_print(ctx, &lbs); + smap_destroy(&lbs); +} + static void nbctl_lr_add(struct ctl_context *ctx) { @@ -2480,6 +2945,25 @@ 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, 1, "ROUTER", 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, 1, "SWITCH", 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..d8331f8 100644 --- a/tests/ovn-nbctl.at +++ b/tests/ovn-nbctl.at @@ -239,6 +239,235 @@ 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:80a 192.168.10.10:80,192.168.10.20:80 tcp], [1], [], +[ovn-nbctl: 30.0.0.10:80a: should be an IPv4 address (or an IPv4 address and a port number with : as a separator). +]) + +AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10:a80 192.168.10.10:80,192.168.10.20:80 tcp], [1], [], +[ovn-nbctl: 30.0.0.10:a80: should be an IPv4 address (or an IPv4 address and a port number with : as a separator). +]) + +AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10: 192.168.10.10:80,192.168.10.20:80 tcp], [1], [], +[ovn-nbctl: 30.0.0.10:: should be an IPv4 address (or an IPv4 address and a port number with : as a separator). +]) + +AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10:80 192.168.10.10:80,192.168.10.20 tcp], [1], [], +[ovn-nbctl: 192.168.10.20: should be an IPv4 address and a port number with : as a separator. +]) + +AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.1a 192.168.10.10:80,192.168.10.20:80], [1], [], +[ovn-nbctl: 30.0.0.1a: should be an IPv4 address (or an IPv4 address and a port number with : as a separator). +]) + +AT_CHECK([ovn-nbctl lb-add lb0 30.0.0 192.168.10.10:80,192.168.10.20:80], [1], [], +[ovn-nbctl: 30.0.0: should be an IPv4 address (or an IPv4 address and a port number with : as a separator). +]) + +AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10 192.168.10.10,192.168.10.20:80], [1], [], +[ovn-nbctl: 192.168.10.20:80: should be an IPv4 address. +]) + +AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10 192.168.10.10:a80], [1], [], +[ovn-nbctl: 192.168.10.10:a80: should be an IPv4 address. +]) + +AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10 192.168.10.10:], [1], [], +[ovn-nbctl: 192.168.10.10:: should be an IPv4 address. +]) + +AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10 192.168.10.1a], [1], [], +[ovn-nbctl: 192.168.10.1a: should be an IPv4 address. +]) + +AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10 192.168.10], [1], [], +[ovn-nbctl: 192.168.10: should be an IPv4 address. +]) + +AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10 192.168.10.10 tcp], [1], [], +[ovn-nbctl: Protocol is unnecessary when no port of vip is given. +]) + +AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10 192.168.10.10:900 tcp], [1], [], +[ovn-nbctl: Protocol is unnecessary when no port of vip is given. +]) + +dnl Add ips to lb +AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10:80 ,,,192.168.10.10:80,,,,,]) +AT_CHECK([ovn-nbctl lb-add lb1 30.0.0.10:80 ,,,192.168.10.10:80,,,,192.168.10.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 +<1> lb1 tcp 30.0.0.10:80 192.168.10.10:80,192.168.10.20:80 +]) +AT_CHECK([ovn-nbctl lb-del lb0]) +AT_CHECK([ovn-nbctl lb-del lb1]) + +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:80,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:80,192.168.10.20:8080 +]) + +AT_CHECK([ovn-nbctl --may-exist lb-add lb1 30.0.0.10:80 192.168.10.10:80,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:80,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 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:80,192.168.10.20:8080 + udp 30.0.0.20:80 192.168.10.10:80 +]) + +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:80,192.168.10.20:8080 +]) + +dnl Add LBs whose vip is just an IP address. +AT_CHECK([ovn-nbctl lb-add lb2 30.0.0.30 192.168.10.10]) +AT_CHECK([ovn-nbctl lb-add lb3 30.0.0.30 192.168.10.10]) +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:80,192.168.10.20:8080 +<2> lb2 tcp/udp 30.0.0.30 192.168.10.10 +<3> lb3 tcp/udp 30.0.0.30 192.168.10.10 +]) +AT_CHECK([ovn-nbctl lb-del lb2 30.0.0.30]) +AT_CHECK([ovn-nbctl lb-del lb3 30.0.0.30]) + +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:80,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 lb-add lb3 30.0.0.10 192.168.10.10,192.168.10.20]) +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-add ls0 lb3]) + +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 +<2> lb3 tcp/udp 30.0.0.10 192.168.10.10,192.168.10.20 +]) + +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 +<1> lb3 tcp/udp 30.0.0.10 192.168.10.10,192.168.10.20 +]) + +AT_CHECK([ovn-nbctl ls-lb-del ls0 lb1]) +AT_CHECK([ovn-nbctl ls-lb-del ls0 lb3]) +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-add ls0 lb3]) +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 lr-lb-add lr0 lb0]) +AT_CHECK([ovn-nbctl lr-lb-add lr0 lb1]) +AT_CHECK([ovn-nbctl --may-exist lr-lb-add lr0 lb1]) +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-add lr0 lb3]) + +AT_CHECK([ovn-nbctl lr-lb-list lr0 | ${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 +<2> lb3 tcp/udp 30.0.0.10 192.168.10.10,192.168.10.20 +]) + +AT_CHECK([ovn-nbctl lr-lb-del lr0 lb0]) +AT_CHECK([ovn-nbctl lr-lb-list lr0 | ${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 +<1> lb3 tcp/udp 30.0.0.10 192.168.10.10,192.168.10.20 +]) + +AT_CHECK([ovn-nbctl lr-lb-del lr0 lb1]) +AT_CHECK([ovn-nbctl lr-lb-del lr0 lb3]) +AT_CHECK([ovn-nbctl lr-lb-list lr0 | ${PERL} $srcdir/uuidfilt.pl], [0], []) +AT_CHECK([ovn-nbctl --if-exists lr-lb-del lr0 lb1]) + +dnl Remove all load balancers from logical router. +AT_CHECK([ovn-nbctl lr-lb-add lr0 lb0]) +AT_CHECK([ovn-nbctl lr-lb-add lr0 lb1]) +AT_CHECK([ovn-nbctl lr-lb-add lr0 lb3]) +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