Allow the CMS to configure template LBs. The following configurations are supported: - VIPs of the form: ^vip_variable[:^port_variable|:port] - Backends of the form: ^backendip_variable1[:^port_variable1|:port],^backendip_variable2[:^port_variable2|:port] OR ^backends_variable1,^backends_variable2
The CMS needs to provide a bit more information than with non-template load balancers and must explicitly specify the address family to be used. There is currently no support for template load balancers with options:add_route=true set. That is because ovn-northd does not instantiate template variables. While this is a limitation in a way, its impact is not huge. The load balancer 'add_route' option was added as a way to make the CMS life easier and to avoid having to explicitly add a route for the VIP. The CMS can still achieve the same logical topology by explicitly adding the VIP route. Signed-off-by: Dumitru Ceara <[email protected]> --- controller/lflow.c | 18 +- lib/lb.c | 463 ++++++++++++++++++++++++++++++++++++++++++------- lib/lb.h | 39 +++- lib/ovn-util.c | 3 northd/northd.c | 85 +++++---- ovn-nb.xml | 12 + tests/ovn-nbctl.at | 23 +- tests/ovn-northd.at | 7 + utilities/ovn-nbctl.c | 122 +++++++------ 9 files changed, 576 insertions(+), 196 deletions(-) diff --git a/controller/lflow.c b/controller/lflow.c index ad58fcd3c..ffc8fdb6e 100644 --- a/controller/lflow.c +++ b/controller/lflow.c @@ -1993,7 +1993,9 @@ add_lb_ct_snat_hairpin_flows(struct ovn_controller_lb *lb, static void consider_lb_hairpin_flows(const struct sbrec_load_balancer *sbrec_lb, - const struct hmap *local_datapaths, bool use_ct_mark, + const struct hmap *local_datapaths, + const struct smap *template_vars, + bool use_ct_mark, struct ovn_desired_flow_table *flow_table, struct simap *ids) { @@ -2029,7 +2031,8 @@ consider_lb_hairpin_flows(const struct sbrec_load_balancer *sbrec_lb, return; } - struct ovn_controller_lb *lb = ovn_controller_lb_create(sbrec_lb); + struct ovn_controller_lb *lb = ovn_controller_lb_create(sbrec_lb, + template_vars); uint8_t lb_proto = IPPROTO_TCP; if (lb->slb->protocol && lb->slb->protocol[0]) { if (!strcmp(lb->slb->protocol, "udp")) { @@ -2059,7 +2062,9 @@ consider_lb_hairpin_flows(const struct sbrec_load_balancer *sbrec_lb, * backends to handle the load balanced hairpin traffic. */ static void add_lb_hairpin_flows(const struct sbrec_load_balancer_table *lb_table, - const struct hmap *local_datapaths, bool use_ct_mark, + const struct hmap *local_datapaths, + const struct smap *template_vars, + bool use_ct_mark, struct ovn_desired_flow_table *flow_table, struct simap *ids, struct id_pool *pool) @@ -2082,8 +2087,8 @@ add_lb_hairpin_flows(const struct sbrec_load_balancer_table *lb_table, ovs_assert(id_pool_alloc_id(pool, &id)); simap_put(ids, lb->name, id); } - consider_lb_hairpin_flows(lb, local_datapaths, use_ct_mark, flow_table, - ids); + consider_lb_hairpin_flows(lb, local_datapaths, template_vars, + use_ct_mark, flow_table, ids); } } @@ -2220,6 +2225,7 @@ lflow_run(struct lflow_ctx_in *l_ctx_in, struct lflow_ctx_out *l_ctx_out) l_ctx_in->local_datapaths, l_ctx_out->flow_table); add_lb_hairpin_flows(l_ctx_in->lb_table, l_ctx_in->local_datapaths, + l_ctx_in->template_vars, l_ctx_in->lb_hairpin_use_ct_mark, l_ctx_out->flow_table, l_ctx_out->hairpin_lb_ids, @@ -2351,6 +2357,7 @@ lflow_add_flows_for_datapath(const struct sbrec_datapath_binding *dp, * associated. */ for (size_t i = 0; i < n_dp_lbs; i++) { consider_lb_hairpin_flows(dp_lbs[i], l_ctx_in->local_datapaths, + l_ctx_in->template_vars, l_ctx_in->lb_hairpin_use_ct_mark, l_ctx_out->flow_table, l_ctx_out->hairpin_lb_ids); @@ -2493,6 +2500,7 @@ lflow_handle_changed_lbs(struct lflow_ctx_in *l_ctx_in, VLOG_DBG("Add load balancer hairpin flows for "UUID_FMT, UUID_ARGS(&lb->header_.uuid)); consider_lb_hairpin_flows(lb, l_ctx_in->local_datapaths, + l_ctx_in->template_vars, l_ctx_in->lb_hairpin_use_ct_mark, l_ctx_out->flow_table, l_ctx_out->hairpin_lb_ids); diff --git a/lib/lb.c b/lib/lb.c index 77674ea28..15cbf29ee 100644 --- a/lib/lb.c +++ b/lib/lb.c @@ -19,6 +19,7 @@ #include "lib/ovn-nb-idl.h" #include "lib/ovn-sb-idl.h" #include "lib/ovn-util.h" +#include "ovn/lex.h" /* OpenvSwitch lib includes. */ #include "openvswitch/vlog.h" @@ -26,6 +27,16 @@ VLOG_DEFINE_THIS_MODULE(lb); +static const char *lb_neighbor_responder_mode_names[] = { + [LB_NEIGH_RESPOND_REACHABLE] = "reachable", + [LB_NEIGH_RESPOND_ALL] = "all", + [LB_NEIGH_RESPOND_NONE] = "none", +}; + +static struct nbrec_load_balancer_health_check * +ovn_lb_get_health_check(const struct nbrec_load_balancer *nbrec_lb, + const char *vip_port_str, bool template); + struct ovn_lb_ip_set * ovn_lb_ip_set_create(void) { @@ -71,108 +82,312 @@ ovn_lb_ip_set_clone(struct ovn_lb_ip_set *lb_ip_set) return clone; } -static -bool ovn_lb_vip_init(struct ovn_lb_vip *lb_vip, const char *lb_key, - const char *lb_value) +/* Format for backend ips: "IP1:port1,IP2:port2,...". */ +static char * +ovn_lb_backends_init_explicit(struct ovn_lb_vip *lb_vip, const char *value, + size_t *n_backends) { - int addr_family; - - if (!ip_address_and_port_from_lb_key(lb_key, &lb_vip->vip_str, - &lb_vip->vip_port, &addr_family)) { - return false; - } - - if (addr_family == AF_INET) { - ovs_be32 vip4; - ip_parse(lb_vip->vip_str, &vip4); - in6_addr_set_mapped_ipv4(&lb_vip->vip, vip4); - } else { - ipv6_parse(lb_vip->vip_str, &lb_vip->vip); - } - - /* Format for backend ips: "IP1:port1,IP2:port2,...". */ - size_t n_backends = 0; + struct ds errors = DS_EMPTY_INITIALIZER; size_t n_allocated_backends = 0; - char *tokstr = xstrdup(lb_value); + char *tokstr = xstrdup(value); char *save_ptr = NULL; + *n_backends = 0; + for (char *token = strtok_r(tokstr, ",", &save_ptr); token != NULL; token = strtok_r(NULL, ",", &save_ptr)) { - if (n_backends == n_allocated_backends) { + if (*n_backends == n_allocated_backends) { lb_vip->backends = x2nrealloc(lb_vip->backends, &n_allocated_backends, sizeof *lb_vip->backends); } - struct ovn_lb_backend *backend = &lb_vip->backends[n_backends]; + struct ovn_lb_backend *backend = &lb_vip->backends[(*n_backends)]; int backend_addr_family; if (!ip_address_and_port_from_lb_key(token, &backend->ip_str, &backend->port, &backend_addr_family)) { + if (lb_vip->port_str) { + ds_put_format(&errors, "%s: should be an IP address and a " + "port number with : as a separator, ", + token); + } else { + ds_put_format(&errors, "%s: should be an IP address, ", token); + } continue; } - if (addr_family != backend_addr_family) { + if (lb_vip->address_family != backend_addr_family) { free(backend->ip_str); + ds_put_format(&errors, "%s: IP address family is different from " + "VIP %s, ", + token, lb_vip->vip_str); continue; } - if (addr_family == AF_INET) { + if (lb_vip->port_str) { + if (!backend->port) { + free(backend->ip_str); + ds_put_format(&errors, "%s: should be an IP address and " + "a port number with : as a separator, ", + token); + continue; + } + } else { + if (backend->port) { + free(backend->ip_str); + ds_put_format(&errors, "%s: should be an IP address, ", token); + continue; + } + } + + backend->port_str = + backend->port ? xasprintf("%"PRIu16, backend->port) : NULL; + + if (lb_vip->address_family == AF_INET) { ovs_be32 ip4; ip_parse(backend->ip_str, &ip4); in6_addr_set_mapped_ipv4(&backend->ip, ip4); } else { ipv6_parse(backend->ip_str, &backend->ip); } - n_backends++; + (*n_backends)++; } free(tokstr); - lb_vip->n_backends = n_backends; - return true; + + if (ds_last(&errors) != EOF) { + ds_chomp(&errors, ' '); + ds_chomp(&errors, ','); + ds_put_char(&errors, '.'); + return ds_steal_cstr(&errors); + } + return NULL; } static -void ovn_lb_vip_destroy(struct ovn_lb_vip *vip) +char *ovn_lb_vip_init_explicit(struct ovn_lb_vip *lb_vip, const char *lb_key, + const char *lb_value) +{ + if (!ip_address_and_port_from_lb_key(lb_key, &lb_vip->vip_str, + &lb_vip->vip_port, + &lb_vip->address_family)) { + return xasprintf("%s: should be an IP address (or an IP address " + "and a port number with : as a separator).", lb_key); + } + + lb_vip->port_str = lb_vip->vip_port + ? xasprintf("%"PRIu16, lb_vip->vip_port) + : NULL; + + if (lb_vip->address_family == AF_INET) { + ovs_be32 vip4; + ip_parse(lb_vip->vip_str, &vip4); + in6_addr_set_mapped_ipv4(&lb_vip->vip, vip4); + } else { + ipv6_parse(lb_vip->vip_str, &lb_vip->vip); + } + return ovn_lb_backends_init_explicit(lb_vip, lb_value, + &lb_vip->n_backends); +} + +/* Parses backends of a templated LB VIP. + * For now only the following template forms are supported: + * A. + * ^backendip_variable1[:^port_variable1|:port], + * ^backendip_variable2[:^port_variable2|:port] + * + * B. + * ^backends_variable1,^backends_variable2 is also a thing + * where 'backends_variable1' may expand to IP1_1:PORT1_1 on chassis-1 + * IP1_2:PORT1_2 on chassis-2 + * and 'backends_variable2' may expand to IP2_1:PORT2_1 on chassis-1 + * IP2_2:PORT2_2 on chassis-2 + */ +static char * +ovn_lb_backends_init_template(struct ovn_lb_vip *lb_vip, const char *value_, + size_t *n_backends) +{ + struct ds errors = DS_EMPTY_INITIALIZER; + char *value = xstrdup(value_); + char *save_ptr = NULL; + size_t n_allocated_backends = 0; + *n_backends = 0; + + for (char *backend = strtok_r(value, ",", &save_ptr); backend; + backend = strtok_r(NULL, ",", &save_ptr)) { + + char *atom = xstrdup(backend); + char *save_ptr2 = NULL; + bool success = false; + char *backend_ip = NULL; + char *backend_port = NULL; + + for (char *subatom = strtok_r(atom, ":", &save_ptr2); subatom; + subatom = strtok_r(NULL, ":", &save_ptr2)) { + if (backend_ip && backend_port) { + success = false; + break; + } + success = true; + if (!backend_ip) { + backend_ip = xstrdup(subatom); + } else { + backend_port = xstrdup(subatom); + } + } + + if (success) { + if (*n_backends == n_allocated_backends) { + lb_vip->backends = x2nrealloc(lb_vip->backends, + &n_allocated_backends, + sizeof *lb_vip->backends); + } + + struct ovn_lb_backend *lb_backend = + &lb_vip->backends[(*n_backends)]; + lb_backend->ip_str = backend_ip; + lb_backend->port_str = backend_port; + lb_backend->port = 0; + (*n_backends)++; + } else { + ds_put_format(&errors, "%s: should be a template of the form: " + "'^backendip_variable1[:^port_variable1|:port]', ", + atom); + } + free(atom); + } + + free(value); + if (ds_last(&errors) != EOF) { + ds_chomp(&errors, ' '); + ds_chomp(&errors, ','); + ds_put_char(&errors, '.'); + return ds_steal_cstr(&errors); + } + return NULL; +} + +/* Parses a VIP of a templated LB. + * For now only the following template forms are supported: + * ^vip_variable[:^port_variable|:port] + */ +static char * +ovn_lb_vip_init_template(struct ovn_lb_vip *lb_vip, const char *lb_key_, + const char *lb_value, int address_family) +{ + char *save_ptr = NULL; + char *lb_key = xstrdup(lb_key_); + bool success = false; + + for (char *atom = strtok_r(lb_key, ":", &save_ptr); atom; + atom = strtok_r(NULL, ":", &save_ptr)) { + if (lb_vip->vip_str && lb_vip->port_str) { + success = false; + break; + } + success = true; + if (!lb_vip->vip_str) { + lb_vip->vip_str = xstrdup(atom); + } else { + lb_vip->port_str = xstrdup(atom); + } + } + free(lb_key); + + if (!success) { + return xasprintf("%s: should be a template of the form: " + "'^vip_variable[:^port_variable|:port]'.", + lb_key_); + } + + lb_vip->address_family = address_family; + return ovn_lb_backends_init_template(lb_vip, lb_value, + &lb_vip->n_backends); +} + +/* Returns NULL on success, an error string on failure. The caller is + * responsible for destroying 'lb_vip' in all cases. + */ +char * +ovn_lb_vip_init(struct ovn_lb_vip *lb_vip, const char *lb_key, + const char *lb_value, bool template, int address_family) +{ + memset(lb_vip, 0, sizeof *lb_vip); + + return !template + ? ovn_lb_vip_init_explicit(lb_vip, lb_key, lb_value) + : ovn_lb_vip_init_template(lb_vip, lb_key, lb_value, + address_family); +} + +void +ovn_lb_vip_destroy(struct ovn_lb_vip *vip) { free(vip->vip_str); + free(vip->port_str); for (size_t i = 0; i < vip->n_backends; i++) { free(vip->backends[i].ip_str); + free(vip->backends[i].port_str); } free(vip->backends); } +void +ovn_lb_vip_format(const struct ovn_lb_vip *vip, struct ds *s, bool template) +{ + bool needs_brackets = vip->address_family == AF_INET6 && vip->port_str + && !template; + if (needs_brackets) { + ds_put_char(s, '['); + } + ds_put_cstr(s, vip->vip_str); + if (needs_brackets) { + ds_put_char(s, ']'); + } + if (vip->port_str) { + ds_put_format(s, ":%s", vip->port_str); + } +} + +void +ovn_lb_vip_backends_format(const struct ovn_lb_vip *vip, struct ds *s, + bool template) +{ + bool needs_brackets = vip->address_family == AF_INET6 && vip->port_str + && !template; + for (size_t i = 0; i < vip->n_backends; i++) { + struct ovn_lb_backend *backend = &vip->backends[i]; + + if (needs_brackets) { + ds_put_char(s, '['); + } + ds_put_cstr(s, backend->ip_str); + if (needs_brackets) { + ds_put_char(s, ']'); + } + if (backend->port_str) { + ds_put_format(s, ":%s", backend->port_str); + } + if (i != vip->n_backends - 1) { + ds_put_char(s, ','); + } + } +} + static void ovn_northd_lb_vip_init(struct ovn_northd_lb_vip *lb_vip_nb, const struct ovn_lb_vip *lb_vip, const struct nbrec_load_balancer *nbrec_lb, - const char *vip_port_str, const char *backend_ips) + const char *vip_port_str, const char *backend_ips, + bool template) { lb_vip_nb->backend_ips = xstrdup(backend_ips); lb_vip_nb->n_backends = lb_vip->n_backends; lb_vip_nb->backends_nb = xcalloc(lb_vip_nb->n_backends, sizeof *lb_vip_nb->backends_nb); - - struct nbrec_load_balancer_health_check *lb_health_check = NULL; - if (nbrec_lb->protocol && !strcmp(nbrec_lb->protocol, "sctp")) { - if (nbrec_lb->n_health_check > 0) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, - "SCTP load balancers do not currently support " - "health checks. Not creating health checks for " - "load balancer " UUID_FMT, - UUID_ARGS(&nbrec_lb->header_.uuid)); - } - } else { - for (size_t j = 0; j < nbrec_lb->n_health_check; j++) { - if (!strcmp(nbrec_lb->health_check[j]->vip, vip_port_str)) { - lb_health_check = nbrec_lb->health_check[j]; - break; - } - } - } - - lb_vip_nb->lb_health_check = lb_health_check; + lb_vip_nb->lb_health_check = + ovn_lb_get_health_check(nbrec_lb, vip_port_str, template); } static @@ -203,12 +418,111 @@ ovn_lb_get_hairpin_snat_ip(const struct uuid *lb_uuid, } } +static bool +ovn_lb_get_routable_mode(const struct nbrec_load_balancer *nbrec_lb, + bool routable, bool template) +{ + if (template && routable) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + VLOG_WARN_RL(&rl, "Template load balancer "UUID_FMT" does not suport " + "option 'add_route'. Forcing it to disabled.", + UUID_ARGS(&nbrec_lb->header_.uuid)); + return false; + } + return routable; +} + +static bool +ovn_lb_neigh_mode_is_valid(enum lb_neighbor_responder_mode mode, bool template) +{ + if (!template) { + return true; + } + + switch (mode) { + case LB_NEIGH_RESPOND_REACHABLE: + return false; + case LB_NEIGH_RESPOND_ALL: + case LB_NEIGH_RESPOND_NONE: + return true; + } +} + +static enum lb_neighbor_responder_mode +ovn_lb_get_neigh_mode(const struct nbrec_load_balancer *nbrec_lb, + const char *mode, bool template) +{ + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + enum lb_neighbor_responder_mode default_mode = + template ? LB_NEIGH_RESPOND_NONE : LB_NEIGH_RESPOND_REACHABLE; + + if (!mode) { + mode = lb_neighbor_responder_mode_names[default_mode]; + } + + for (size_t i = 0; i < ARRAY_SIZE(lb_neighbor_responder_mode_names); i++) { + if (!strcmp(mode, lb_neighbor_responder_mode_names[i])) { + if (ovn_lb_neigh_mode_is_valid(i, template)) { + return i; + } + break; + } + } + + VLOG_WARN_RL(&rl, "Invalid neighbor responder mode %s for load balancer " + UUID_FMT", forcing it to %s", + mode, UUID_ARGS(&nbrec_lb->header_.uuid), + lb_neighbor_responder_mode_names[default_mode]); + return default_mode; +} + +static struct nbrec_load_balancer_health_check * +ovn_lb_get_health_check(const struct nbrec_load_balancer *nbrec_lb, + const char *vip_port_str, bool template) +{ + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + + if (!nbrec_lb->n_health_check) { + return NULL; + } + + if (nbrec_lb->protocol && !strcmp(nbrec_lb->protocol, "sctp")) { + VLOG_WARN_RL(&rl, + "SCTP load balancers do not currently support " + "health checks. Not creating health checks for " + "load balancer " UUID_FMT, + UUID_ARGS(&nbrec_lb->header_.uuid)); + return NULL; + } + + if (template) { + VLOG_WARN_RL(&rl, + "Template load balancers do not currently support " + "health checks. Not creating health checks for " + "load balancer " UUID_FMT, + UUID_ARGS(&nbrec_lb->header_.uuid)); + return NULL; + } + + for (size_t i = 0; i < nbrec_lb->n_health_check; i++) { + if (!strcmp(nbrec_lb->health_check[i]->vip, vip_port_str)) { + return nbrec_lb->health_check[i]; + } + } + return NULL; +} + struct ovn_northd_lb * ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb) { bool is_udp = nullable_string_is_equal(nbrec_lb->protocol, "udp"); bool is_sctp = nullable_string_is_equal(nbrec_lb->protocol, "sctp"); struct ovn_northd_lb *lb = xzalloc(sizeof *lb); + int address_family = !strcmp(smap_get_def(&nbrec_lb->options, + "address-family", "ipv4"), + "ipv4") + ? AF_INET + : AF_INET6; lb->nlb = nbrec_lb; lb->proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp"; @@ -216,12 +530,16 @@ ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb) lb->vips = xcalloc(lb->n_vips, sizeof *lb->vips); lb->vips_nb = xcalloc(lb->n_vips, sizeof *lb->vips_nb); lb->controller_event = smap_get_bool(&nbrec_lb->options, "event", false); - lb->routable = smap_get_bool(&nbrec_lb->options, "add_route", false); + + bool routable = smap_get_bool(&nbrec_lb->options, "add_route", false); + lb->routable = ovn_lb_get_routable_mode(nbrec_lb, routable, lb->template); + lb->skip_snat = smap_get_bool(&nbrec_lb->options, "skip_snat", false); - const char *mode = - smap_get_def(&nbrec_lb->options, "neighbor_responder", "reachable"); - lb->neigh_mode = strcmp(mode, "all") ? LB_NEIGH_RESPOND_REACHABLE - : LB_NEIGH_RESPOND_ALL; + lb->template = smap_get_bool(&nbrec_lb->options, "template", false); + + const char *mode = smap_get(&nbrec_lb->options, "neighbor_responder"); + lb->neigh_mode = ovn_lb_get_neigh_mode(nbrec_lb, mode, lb->template); + sset_init(&lb->ips_v4); sset_init(&lb->ips_v6); struct smap_node *node; @@ -231,13 +549,19 @@ ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb) struct ovn_lb_vip *lb_vip = &lb->vips[n_vips]; struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[n_vips]; - lb_vip->empty_backend_rej = smap_get_bool(&nbrec_lb->options, - "reject", false); - if (!ovn_lb_vip_init(lb_vip, node->key, node->value)) { + char *error = ovn_lb_vip_init(lb_vip, node->key, node->value, + lb->template, address_family); + if (error) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "Failed to initialize LB VIP: %s", error); + ovn_lb_vip_destroy(lb_vip); + free(error); continue; } + lb_vip->empty_backend_rej = smap_get_bool(&nbrec_lb->options, + "reject", false); ovn_northd_lb_vip_init(lb_vip_nb, lb_vip, nbrec_lb, - node->key, node->value); + node->key, node->value, lb->template); if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { sset_add(&lb->ips_v4, lb_vip->vip_str); } else { @@ -385,7 +709,8 @@ ovn_lb_group_find(const struct hmap *lb_groups, const struct uuid *uuid) } struct ovn_controller_lb * -ovn_controller_lb_create(const struct sbrec_load_balancer *sbrec_lb) +ovn_controller_lb_create(const struct sbrec_load_balancer *sbrec_lb, + const struct smap *template_vars) { struct ovn_controller_lb *lb = xzalloc(sizeof *lb); @@ -399,10 +724,20 @@ ovn_controller_lb_create(const struct sbrec_load_balancer *sbrec_lb) SMAP_FOR_EACH (node, &sbrec_lb->vips) { struct ovn_lb_vip *lb_vip = &lb->vips[n_vips]; - if (!ovn_lb_vip_init(lb_vip, node->key, node->value)) { - continue; + char *key_s = lexer_parse_template_string(xstrdup(node->key), + template_vars, + NULL); + char *value_s = lexer_parse_template_string(xstrdup(node->value), + template_vars, + NULL); + char *error = ovn_lb_vip_init_explicit(lb_vip, key_s, value_s); + if (error) { + free(error); + } else { + n_vips++; } - n_vips++; + free(key_s); + free(value_s); } /* It's possible that parsing VIPs fails. Update the lb->n_vips to the diff --git a/lib/lb.h b/lib/lb.h index 80ac03399..265a1215a 100644 --- a/lib/lb.h +++ b/lib/lb.h @@ -35,6 +35,7 @@ struct uuid; enum lb_neighbor_responder_mode { LB_NEIGH_RESPOND_REACHABLE, LB_NEIGH_RESPOND_ALL, + LB_NEIGH_RESPOND_NONE, }; /* The "routable" ssets are subsets of the load balancer IPs for which IP @@ -67,6 +68,7 @@ struct ovn_northd_lb { bool controller_event; bool routable; bool skip_snat; + bool template; struct sset ips_v4; struct sset ips_v6; @@ -81,19 +83,31 @@ struct ovn_northd_lb { }; struct ovn_lb_vip { - struct in6_addr vip; - char *vip_str; - uint16_t vip_port; - + struct in6_addr vip; /* Only used in ovn-controller. */ + char *vip_str; /* Actual VIP string representation (without port). + * To be used in ovn-northd. + */ + uint16_t vip_port; /* Only used in ovn-controller. */ + char *port_str; /* Actual port string representation. To be used + * in ovn-controller. + */ struct ovn_lb_backend *backends; size_t n_backends; bool empty_backend_rej; + int address_family; }; struct ovn_lb_backend { - struct in6_addr ip; - char *ip_str; - uint16_t port; + struct in6_addr ip; /* Only used in ovn-controller. */ + char *ip_str; /* Actual IP string representation. To be used in + * ovn-northd. + */ + uint16_t port; /* Mostly used in ovn-controller but also for + * healthcheck in ovn-northd. + */ + char *port_str; /* Actual port string representation. To be used + * in ovn-northd. + */ }; /* ovn-northd specific backend information. */ @@ -171,7 +185,16 @@ struct ovn_controller_lb { }; struct ovn_controller_lb *ovn_controller_lb_create( - const struct sbrec_load_balancer *); + const struct sbrec_load_balancer *, + const struct smap *template_vars); void ovn_controller_lb_destroy(struct ovn_controller_lb *); +char *ovn_lb_vip_init(struct ovn_lb_vip *lb_vip, const char *lb_key, + const char *lb_value, bool template, int address_family); +void ovn_lb_vip_destroy(struct ovn_lb_vip *vip); +void ovn_lb_vip_format(const struct ovn_lb_vip *vip, struct ds *s, + bool template); +void ovn_lb_vip_backends_format(const struct ovn_lb_vip *vip, struct ds *s, + bool template); + #endif /* OVN_LIB_LB_H 1 */ diff --git a/lib/ovn-util.c b/lib/ovn-util.c index 596e93129..1cc4c98b1 100644 --- a/lib/ovn-util.c +++ b/lib/ovn-util.c @@ -799,9 +799,6 @@ ip_address_and_port_from_lb_key(const char *key, char **ip_address, { struct sockaddr_storage ss; if (!inet_parse_active(key, 0, &ss, false, NULL)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad ip address or port for load balancer key %s", - key); *ip_address = NULL; *port = 0; *addr_family = 0; diff --git a/northd/northd.c b/northd/northd.c index 63879ccd0..a93aee37e 100644 --- a/northd/northd.c +++ b/northd/northd.c @@ -3708,6 +3708,10 @@ static void ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn, struct ovn_northd_lb *lb, struct hmap *monitor_map, struct hmap *ports) { + if (lb->template) { + return; + } + for (size_t i = 0; i < lb->n_vips; i++) { struct ovn_lb_vip *lb_vip = &lb->vips[i]; struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[i]; @@ -4024,12 +4028,19 @@ static void build_lrouter_lb_reachable_ips(struct ovn_datapath *od, const struct ovn_northd_lb *lb) { + /* If configured to not reply to any neighbor requests for all VIPs + * return early. + */ + if (lb->neigh_mode == LB_NEIGH_RESPOND_NONE) { + return; + } + /* If configured to reply to neighbor requests for all VIPs force them * all to be considered "reachable". */ if (lb->neigh_mode == LB_NEIGH_RESPOND_ALL) { for (size_t i = 0; i < lb->n_vips; i++) { - if (IN6_IS_ADDR_V4MAPPED(&lb->vips[i].vip)) { + if (lb->vips[i].address_family == AF_INET) { sset_add(&od->lb_ips->ips_v4_reachable, lb->vips[i].vip_str); } else { sset_add(&od->lb_ips->ips_v6_reachable, lb->vips[i].vip_str); @@ -4041,8 +4052,9 @@ build_lrouter_lb_reachable_ips(struct ovn_datapath *od, /* Otherwise, a VIP is reachable if there's at least one router * subnet that includes it. */ + ovs_assert(lb->neigh_mode == LB_NEIGH_RESPOND_REACHABLE); for (size_t i = 0; i < lb->n_vips; i++) { - if (IN6_IS_ADDR_V4MAPPED(&lb->vips[i].vip)) { + if (lb->vips[i].address_family == AF_INET) { ovs_be32 vip_ip4 = in6_addr_get_mapped_ipv4(&lb->vips[i].vip); struct ovn_port *op; @@ -5823,16 +5835,16 @@ build_empty_lb_event_flow(struct ovn_lb_vip *lb_vip, ds_clear(action); ds_clear(match); - bool ipv4 = IN6_IS_ADDR_V4MAPPED(&lb_vip->vip); + bool ipv4 = lb_vip->address_family == AF_INET; ds_put_format(match, "ip%s.dst == %s && %s", ipv4 ? "4": "6", lb_vip->vip_str, lb->proto); char *vip = lb_vip->vip_str; - if (lb_vip->vip_port) { - ds_put_format(match, " && %s.dst == %u", lb->proto, lb_vip->vip_port); - vip = xasprintf("%s%s%s:%u", ipv4 ? "" : "[", lb_vip->vip_str, - ipv4 ? "" : "]", lb_vip->vip_port); + if (lb_vip->port_str) { + ds_put_format(match, " && %s.dst == %s", lb->proto, lb_vip->port_str); + vip = xasprintf("%s%s%s:%s", ipv4 ? "" : "[", lb_vip->vip_str, + ipv4 ? "" : "]", lb_vip->port_str); } ds_put_format(action, @@ -5843,7 +5855,7 @@ build_empty_lb_event_flow(struct ovn_lb_vip *lb_vip, event_to_string(OVN_EVENT_EMPTY_LB_BACKENDS), vip, lb->proto, UUID_ARGS(&lb->nlb->header_.uuid)); - if (lb_vip->vip_port) { + if (lb_vip->port_str) { free(vip); } return true; @@ -6899,7 +6911,7 @@ build_lb_rules_pre_stateful(struct hmap *lflows, struct ovn_northd_lb *lb, /* Store the original destination IP to be used when generating * hairpin flows. */ - if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { + if (lb->vips[i].address_family == AF_INET) { ip_match = "ip4"; ds_put_format(action, REG_ORIG_DIP_IPV4 " = %s; ", lb_vip->vip_str); @@ -6910,7 +6922,7 @@ build_lb_rules_pre_stateful(struct hmap *lflows, struct ovn_northd_lb *lb, } const char *proto = NULL; - if (lb_vip->vip_port) { + if (lb_vip->port_str) { proto = "tcp"; if (lb->nlb->protocol) { if (!strcmp(lb->nlb->protocol, "udp")) { @@ -6923,8 +6935,8 @@ build_lb_rules_pre_stateful(struct hmap *lflows, struct ovn_northd_lb *lb, /* Store the original destination port to be used when generating * hairpin flows. */ - ds_put_format(action, REG_ORIG_TP_DPORT " = %"PRIu16"; ", - lb_vip->vip_port); + ds_put_format(action, REG_ORIG_TP_DPORT " = %s; ", + lb_vip->port_str); } ds_put_format(action, "%s;", ct_lb_mark ? "ct_lb_mark" : "ct_lb"); @@ -6962,24 +6974,12 @@ build_lb_rules(struct hmap *lflows, struct ovn_northd_lb *lb, bool ct_lb_mark, struct ovn_lb_vip *lb_vip = &lb->vips[i]; struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[i]; const char *ip_match = NULL; - if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { + if (lb_vip->address_family == AF_INET) { ip_match = "ip4"; } else { ip_match = "ip6"; } - const char *proto = NULL; - if (lb_vip->vip_port) { - proto = "tcp"; - if (lb->nlb->protocol) { - if (!strcmp(lb->nlb->protocol, "udp")) { - proto = "udp"; - } else if (!strcmp(lb->nlb->protocol, "sctp")) { - proto = "sctp"; - } - } - } - ds_clear(action); ds_clear(match); @@ -6997,8 +6997,9 @@ build_lb_rules(struct hmap *lflows, struct ovn_northd_lb *lb, bool ct_lb_mark, ds_put_format(match, "ct.new && %s.dst == %s", ip_match, lb_vip->vip_str); int priority = 110; - if (lb_vip->vip_port) { - ds_put_format(match, " && %s.dst == %d", proto, lb_vip->vip_port); + if (lb_vip->port_str) { + ds_put_format(match, " && %s.dst == %s", lb->proto, + lb_vip->port_str); priority = 120; } @@ -9998,7 +9999,7 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip, * of "ct_lb_mark($targets);". The other flow is for ct.est with * an action of "next;". */ - if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { + if (lb_vip->address_family == AF_INET) { ds_put_format(match, "ip4 && "REG_NEXT_HOP_IPV4" == %s", lb_vip->vip_str); } else { @@ -10014,14 +10015,14 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip, } int prio = 110; - if (lb_vip->vip_port) { + if (lb_vip->port_str) { prio = 120; new_match = xasprintf("ct.new && %s && %s && " - REG_ORIG_TP_DPORT_ROUTER" == %d", - ds_cstr(match), lb->proto, lb_vip->vip_port); + REG_ORIG_TP_DPORT_ROUTER" == %s", + ds_cstr(match), lb->proto, lb_vip->port_str); est_match = xasprintf("ct.est && %s && %s && " - REG_ORIG_TP_DPORT_ROUTER" == %d && %s == 1", - ds_cstr(match), lb->proto, lb_vip->vip_port, + REG_ORIG_TP_DPORT_ROUTER" == %s && %s == 1", + ds_cstr(match), lb->proto, lb_vip->port_str, ct_natted); } else { new_match = xasprintf("ct.new && %s", ds_cstr(match)); @@ -10030,7 +10031,7 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip, } const char *ip_match = NULL; - if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { + if (lb_vip->address_family == AF_INET) { ip_match = "ip4"; } else { ip_match = "ip6"; @@ -10048,9 +10049,9 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip, ds_put_format(&undnat_match, "(%s.src == %s", ip_match, backend->ip_str); - if (backend->port) { - ds_put_format(&undnat_match, " && %s.src == %d) || ", - lb->proto, backend->port); + if (backend->port_str) { + ds_put_format(&undnat_match, " && %s.src == %s) || ", + lb->proto, backend->port_str); } else { ds_put_cstr(&undnat_match, ") || "); } @@ -10063,9 +10064,9 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip, struct ds unsnat_match = DS_EMPTY_INITIALIZER; ds_put_format(&unsnat_match, "%s && %s.dst == %s && %s", ip_match, ip_match, lb_vip->vip_str, lb->proto); - if (lb_vip->vip_port) { - ds_put_format(&unsnat_match, " && %s.dst == %d", lb->proto, - lb_vip->vip_port); + if (lb_vip->port_str) { + ds_put_format(&unsnat_match, " && %s.dst == %s", lb->proto, + lb_vip->port_str); } struct ovn_datapath **gw_router_skip_snat = @@ -10305,7 +10306,7 @@ build_lrouter_defrag_flows_for_lb(struct ovn_northd_lb *lb, ds_clear(&defrag_actions); ds_clear(match); - if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { + if (lb_vip->address_family == AF_INET) { ds_put_format(match, "ip && ip4.dst == %s", lb_vip->vip_str); ds_put_format(&defrag_actions, REG_NEXT_HOP_IPV4" = %s; ", lb_vip->vip_str); @@ -10315,7 +10316,7 @@ build_lrouter_defrag_flows_for_lb(struct ovn_northd_lb *lb, lb_vip->vip_str); } - if (lb_vip->vip_port) { + if (lb_vip->port_str) { ds_put_format(match, " && %s", lb->proto); prio = 110; diff --git a/ovn-nb.xml b/ovn-nb.xml index bd28ac372..fb63b212e 100644 --- a/ovn-nb.xml +++ b/ovn-nb.xml @@ -1908,6 +1908,18 @@ requests only for VIPs that are part of a router's subnet. The default value of this option, if not specified, is <code>reachable</code>. </column> + + <column name="options" key="template"> + Option to be set to <code>true</code>, if the load balancer is a + template. In this case the template variables used within the + load balancer VIPs and backends will be instantiated differently + on different chassis. + </column> + + <column name="options" key="address-family"> + Address family used by the load balancer. This value is used only + when the load balancer is a template. + </column> </group> </table> diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at index 6a5d52a5d..9b766d63e 100644 --- a/tests/ovn-nbctl.at +++ b/tests/ovn-nbctl.at @@ -857,23 +857,19 @@ AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10 192.168.10.10:a80], [ [ovn-nbctl: 192.168.10.10:a80: should be an IP address. ]) -AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10 192.168.10.10:], [1], [], -[ovn-nbctl: 192.168.10.10:: should be an IP address. -]) - AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10 192.168.10.1a], [1], [], [ovn-nbctl: 192.168.10.1a: should be an IP address. ]) 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: Protocol is unnecessary when no port of vip is given. +[ovn-nbctl: 192.168.10.10:80: should be an IP address, 192.168.10.20:80: should be an IP 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], [], +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. ]) @@ -1111,7 +1107,7 @@ AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 ae0f::10fff [[fd0f::10]]:80,fd0 AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 ae0f::10 [[fd0f::10]]:80,[[fd0f::20]]:80], [1], [], -[ovn-nbctl: [[fd0f::10]]:80: should be an IP address. +[ovn-nbctl: [[fd0f::10]]:80: should be an IP address, [[fd0f::20]]:80: should be an IP address. ]) @@ -1125,18 +1121,13 @@ AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 ae0f::10 [[fd0f::10]]:a80], [1] ]) -AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 ae0f::10 [[fd0f::10]]:], [1], [], -[ovn-nbctl: [[fd0f::10]]:: should be an IP address. -]) - - AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 ae0f::10 fd0f::1001a], [1], [], [ovn-nbctl: fd0f::1001a: should be an IP address. ]) AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 [[ae0f::10]]: [[fd0f::10]]:80,[[fd0f::20]]:80 tcp], [1], [], -[ovn-nbctl: Protocol is unnecessary when no port of vip is given. +[ovn-nbctl: [[fd0f::10]]:80: should be an IP address, [[fd0f::20]]:80: should be an IP address. ]) @@ -1146,7 +1137,7 @@ AT_CHECK([ovn-nbctl lb-add lb0 ae0f::10 fd0f::10 tcp], [1], [], AT_CHECK([ovn-nbctl lb-add lb0 ae0f::10 [[fd0f::10]]:900 tcp], [1], [], -[ovn-nbctl: Protocol is unnecessary when no port of vip is given. +[ovn-nbctl: [[fd0f::10]]:900: should be an IP address. ]) AT_CHECK([ovn-nbctl lb-add lb0 ae0f::10 192.168.10.10], [1], [], @@ -1158,7 +1149,7 @@ AT_CHECK([ovn-nbctl lb-add lb0 ae0f::10 192.168.10.10], [1], [], ]) AT_CHECK([ovn-nbctl lb-add lb0 [[ae0f::10]]:80 192.168.10.10:80], [1], [], -[ovn-nbctl: 192.168.10.10:80: IP address family is different from VIP [[ae0f::10]]:80. +[ovn-nbctl: 192.168.10.10:80: IP address family is different from VIP ae0f::10. ]) AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10 ae0f::10], [1], [], @@ -1166,7 +1157,7 @@ AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10 ae0f::10], [1], [], ]) AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10:80 [[ae0f::10]]:80], [1], [], -[ovn-nbctl: [[ae0f::10]]:80: IP address family is different from VIP 30.0.0.10:80. +[ovn-nbctl: [[ae0f::10]]:80: IP address family is different from VIP 30.0.0.10. ]) AT_CHECK([ovn-nbctl lb-add lb0 ae0f::10 fd0f::10]) diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at index b6843956c..39032d269 100644 --- a/tests/ovn-northd.at +++ b/tests/ovn-northd.at @@ -1828,6 +1828,11 @@ ovn-nbctl set Load_Balancer lb8 options:neighbor_responder=all ovn-nbctl lb-add lb9 "[[4444::4444]]:8080" "[[10::10]]:8080" udp ovn-nbctl set Load_Balancer lb9 options:neighbor_responder=all +ovn-nbctl lb-add lb10 "55.55.55.55:8080" "10.0.0.8:8080" udp +ovn-nbctl set Load_Balancer lb10 options:neighbor_responder=none +ovn-nbctl lb-add lb11 "[[5555::5555]]:8080" "[[10::10]]:8080" udp +ovn-nbctl set Load_Balancer lb11 options:neighbor_responder=none + ovn-nbctl lr-lb-add lr lb1 ovn-nbctl lr-lb-add lr lb2 ovn-nbctl lr-lb-add lr lb3 @@ -1837,6 +1842,8 @@ ovn-nbctl lr-lb-add lr lb6 ovn-nbctl lr-lb-add lr lb7 ovn-nbctl lr-lb-add lr lb8 ovn-nbctl lr-lb-add lr lb9 +ovn-nbctl lr-lb-add lr lb10 +ovn-nbctl lr-lb-add lr lb11 ovn-nbctl --wait=sb sync lr_key=$(fetch_column sb:datapath_binding tunnel_key external_ids:name=lr) diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c index 811468dc6..4feae1db2 100644 --- a/utilities/ovn-nbctl.c +++ b/utilities/ovn-nbctl.c @@ -28,6 +28,7 @@ #include "openvswitch/json.h" #include "lib/acl-log.h" #include "lib/copp.h" +#include "lib/lb.h" #include "lib/ovn-nb-idl.h" #include "lib/ovn-util.h" #include "memory.h" @@ -2837,6 +2838,7 @@ nbctl_lb_add(struct ctl_context *ctx) bool empty_backend_rej = shash_find(&ctx->options, "--reject") != NULL; bool empty_backend_event = shash_find(&ctx->options, "--event") != NULL; bool add_route = shash_find(&ctx->options, "--add-route") != NULL; + bool template = shash_find(&ctx->options, "--template") != NULL; if (empty_backend_event && empty_backend_rej) { ctl_error(ctx, @@ -2844,10 +2846,12 @@ nbctl_lb_add(struct ctl_context *ctx) return; } + const char *lb_address_family_str = "ipv4"; + int lb_address_family = AF_INET; const char *lb_proto; bool is_update_proto = false; - if (ctx->argc == 4) { + if (ctx->argc <= 4) { /* Default protocol. */ lb_proto = "tcp"; } else { @@ -2863,79 +2867,61 @@ nbctl_lb_add(struct ctl_context *ctx) } } - struct sockaddr_storage ss_vip; - if (!inet_parse_active(lb_vip, 0, &ss_vip, false, NULL)) { - ctl_error(ctx, "%s: should be an IP address (or an IP address " - "and a port number with : as a separator).", lb_vip); - return; - } - - struct ds lb_vip_normalized_ds = DS_EMPTY_INITIALIZER; - uint16_t lb_vip_port = ss_get_port(&ss_vip); - if (lb_vip_port) { - ss_format_address(&ss_vip, &lb_vip_normalized_ds); - ds_put_format(&lb_vip_normalized_ds, ":%d", lb_vip_port); - } else { - ss_format_address_nobracks(&ss_vip, &lb_vip_normalized_ds); - } - const char *lb_vip_normalized = ds_cstr(&lb_vip_normalized_ds); + if (ctx->argc > 5) { + lb_address_family_str = ctx->argv[5]; + lb_address_family = !strcmp(lb_address_family_str, "ipv4") + ? AF_INET : AF_INET6; - if (!lb_vip_port && is_update_proto) { - ds_destroy(&lb_vip_normalized_ds); - ctl_error(ctx, "Protocol is unnecessary when no port of vip " - "is given."); - return; } - char *token = NULL, *save_ptr = NULL; + struct ds lb_vip_normalized = DS_EMPTY_INITIALIZER; struct ds lb_ips_new = DS_EMPTY_INITIALIZER; - for (token = strtok_r(lb_ips, ",", &save_ptr); - token != NULL; token = strtok_r(NULL, ",", &save_ptr)) { - struct sockaddr_storage ss_dst; + struct ovn_lb_vip lb_vip_parsed; - if (lb_vip_port) { - if (!inet_parse_active(token, -1, &ss_dst, false, NULL)) { - ctl_error(ctx, "%s: should be an IP address and a port " - "number with : as a separator.", token); - goto out; - } - } else { - if (!inet_parse_address(token, &ss_dst)) { - ctl_error(ctx, "%s: should be an IP address.", token); - goto out; - } - } + char *error = ovn_lb_vip_init(&lb_vip_parsed, lb_vip, lb_ips, template, + lb_address_family); + if (error) { + ctl_error(ctx, "%s", error); + ovn_lb_vip_destroy(&lb_vip_parsed); + free(error); + return; + } - if (ss_vip.ss_family != ss_dst.ss_family) { - ctl_error(ctx, "%s: IP address family is different from VIP %s.", - token, lb_vip_normalized); - goto out; - } - ds_put_format(&lb_ips_new, "%s%s", - lb_ips_new.length ? "," : "", token); + if (is_update_proto && !lb_vip_parsed.port_str) { + ctl_error(ctx, "Protocol is unnecessary when no port of vip is " + "given."); + ovn_lb_vip_destroy(&lb_vip_parsed); + return; } + ovn_lb_vip_format(&lb_vip_parsed, &lb_vip_normalized, template); + ovn_lb_vip_backends_format(&lb_vip_parsed, &lb_ips_new, template); + ovn_lb_vip_destroy(&lb_vip_parsed); + const struct nbrec_load_balancer *lb = NULL; if (!add_duplicate) { - char *error = lb_by_name_or_uuid(ctx, lb_name, false, &lb); + error = lb_by_name_or_uuid(ctx, lb_name, false, &lb); if (error) { ctx->error = error; goto out; } if (lb) { - if (smap_get(&lb->vips, lb_vip_normalized)) { + if (smap_get(&lb->vips, ds_cstr(&lb_vip_normalized))) { if (!may_exist) { ctl_error(ctx, "%s: a load balancer with this vip (%s) " - "already exists", lb_name, lb_vip_normalized); + "already exists", lb_name, + ds_cstr(&lb_vip_normalized)); goto out; } /* Update the vips. */ smap_replace(CONST_CAST(struct smap *, &lb->vips), - lb_vip_normalized, ds_cstr(&lb_ips_new)); + ds_cstr(&lb_vip_normalized), + ds_cstr(&lb_ips_new)); } else { /* Add the new vips. */ smap_add(CONST_CAST(struct smap *, &lb->vips), - lb_vip_normalized, ds_cstr(&lb_ips_new)); + ds_cstr(&lb_vip_normalized), + ds_cstr(&lb_ips_new)); } /* Update the load balancer. */ @@ -2954,7 +2940,7 @@ nbctl_lb_add(struct ctl_context *ctx) 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_normalized, ds_cstr(&lb_ips_new)); + ds_cstr(&lb_vip_normalized), ds_cstr(&lb_ips_new)); nbrec_load_balancer_set_vips(lb, &lb->vips); struct smap options = SMAP_INITIALIZER(&options); if (empty_backend_rej) { @@ -2966,12 +2952,16 @@ nbctl_lb_add(struct ctl_context *ctx) if (add_route) { smap_add(&options, "add_route", "true"); } + if (template) { + smap_add(&options, "template", "true"); + smap_add(&options, "address-family", lb_address_family_str); + } nbrec_load_balancer_set_options(lb, &options); smap_destroy(&options); out: ds_destroy(&lb_ips_new); - ds_destroy(&lb_vip_normalized_ds); + ds_destroy(&lb_vip_normalized); } static void @@ -3025,6 +3015,7 @@ static void nbctl_pre_lb_list(struct ctl_context *ctx) { ovsdb_idl_add_column(ctx->idl, &nbrec_load_balancer_col_name); + ovsdb_idl_add_column(ctx->idl, &nbrec_load_balancer_col_options); ovsdb_idl_add_column(ctx->idl, &nbrec_load_balancer_col_protocol); ovsdb_idl_add_column(ctx->idl, &nbrec_load_balancer_col_vips); } @@ -3033,6 +3024,7 @@ static void lb_info_add_smap(const struct nbrec_load_balancer *lb, struct smap *lbs, int vip_width) { + bool template = smap_get_bool(&lb->options, "template", false); const struct smap_node **nodes = smap_sort(&lb->vips); if (!nodes) { return; @@ -3041,13 +3033,24 @@ lb_info_add_smap(const struct nbrec_load_balancer *lb, struct ds val = DS_EMPTY_INITIALIZER; for (size_t i = 0; i < smap_count(&lb->vips); i++) { const struct smap_node *node = nodes[i]; + const char *protocol = lb->protocol; - struct sockaddr_storage ss; - if (!inet_parse_active(node->key, 0, &ss, false, NULL)) { - continue; + if (!template) { + struct sockaddr_storage ss; + if (!inet_parse_active(node->key, 0, &ss, false, NULL)) { + continue; + } + protocol = ss_get_port(&ss) ? lb->protocol : ""; + } else { + if (!lb->protocol) { + VLOG_WARN("Load Balancer "UUID_FMT" (%s) is a template and " + "misses protocol", UUID_ARGS(&lb->header_.uuid), + lb->name); + continue; + } + protocol = lb->protocol; } - char *protocol = ss_get_port(&ss) ? lb->protocol : ""; if (i == 0) { ds_put_format(&val, UUID_FMT " %-20.16s%-11.7s%-*.*s%s", UUID_ARGS(&lb->header_.uuid), @@ -3239,6 +3242,7 @@ nbctl_pre_lr_lb_list(struct ctl_context *ctx) &nbrec_logical_router_col_load_balancer_group); ovsdb_idl_add_column(ctx->idl, &nbrec_load_balancer_col_name); + ovsdb_idl_add_column(ctx->idl, &nbrec_load_balancer_col_options); ovsdb_idl_add_column(ctx->idl, &nbrec_load_balancer_col_protocol); ovsdb_idl_add_column(ctx->idl, &nbrec_load_balancer_col_vips); @@ -3402,6 +3406,7 @@ nbctl_pre_ls_lb_list(struct ctl_context *ctx) &nbrec_logical_switch_col_load_balancer_group); ovsdb_idl_add_column(ctx->idl, &nbrec_load_balancer_col_name); + ovsdb_idl_add_column(ctx->idl, &nbrec_load_balancer_col_options); ovsdb_idl_add_column(ctx->idl, &nbrec_load_balancer_col_protocol); ovsdb_idl_add_column(ctx->idl, &nbrec_load_balancer_col_vips); @@ -7469,9 +7474,10 @@ static const struct ctl_command_syntax nbctl_commands[] = { nbctl_pre_lr_nat_set_ext_ips, nbctl_lr_nat_set_ext_ips, NULL, "--is-exempted", RW}, /* load balancer commands. */ - { "lb-add", 3, 4, "LB VIP[:PORT] IP[:PORT]... [PROTOCOL]", + { "lb-add", 3, 5, "LB VIP[:PORT] IP[:PORT]... [PROTOCOL] [ADDRESS_FAMILY]", nbctl_pre_lb_add, nbctl_lb_add, NULL, - "--may-exist,--add-duplicate,--reject,--event,--add-route", RW }, + "--may-exist,--add-duplicate,--reject,--event,--add-route,--template", + RW }, { "lb-del", 1, 2, "LB [VIP]", nbctl_pre_lb_del, nbctl_lb_del, NULL, "--if-exists", RW }, { "lb-list", 0, 1, "[LB]", nbctl_pre_lb_list, nbctl_lb_list, NULL, "", RO }, _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
