On 8/24/25 2:02 PM, Alexandra Rukomoinikova wrote: > Currently, load balancers using L3 gateway operate centrally. Despite being > distributed to compute nodes, all VIP traffic is sent to a single gateway > node for load balancing processing. > > This RFC implementation introduces distributed load balancer that integrates > with physical network fabric to advertise VIPs via ECMP routing. Core concept > is to enable every compute node that hosting backend virtual machines to > locally terminate and process traffic destined for a VIP. > > Implementation introduces a new distributed option for load balancer. When > enabled, it functions as follows (static routing example configuration): > 1) Network Fabric Configuration: static ECMP route for Load Balancer's > VIP (e.g., 10.255.0.1/32) is configured on physical network. Nexthops for > this route are IP addresses of all compute nodes that host backends for this > load balancer. > 2) Traffic Distribution: network fabric uses ECMP to balance incoming > client traffic for VIP across listed compute node nexthops, respecting > configured weights. > 3) Local Traffic Processing: Each compute node independently terminates > VIP traffic it receives. Load balancer performs DNAT/UNDNAT right on the node > and then distributes connections only to backend servers running on that same > node. There is no cross-node traffic forwarding. > > Example: > Load Balancer: lb1 with VIP 10.255.0.1 and distributed option enabled. > Fabric is configured with a static ECMP route for 10.255.0.1/32: > nexthop via ip_host1 weight 1 (hosts backend1) > nexthop via ip_host2 weight 1 (hosts backend2) > nexthop via ip_host3 weight 2 (hosts backend3 and backend4) >
Hi Alexandra, I only skimmed through the RFC code changes so this is not a full review, just some thoughts. In general I think this might be a very nice addition to OVN! > As part of testing, following estimates of distribution of requests to > balancers were obtained: > [root@dev11 ~]# for i in $(seq 5000); do curl http://10.255.0.1:80 > 2>/dev/null ; echo ; done | awk '{print $2}' | sort | uniq -c > 1265 “backend 4", > 1260 “backend 3", > 1224 “backend 2", > 1251 “backend 1", > Thus, requests using ecmp balancing are distributed between backends > approximately evenly. > > Key changes: > 1) For distributed load balancers, specifying ip_port_mappings (without > using src_ip) becomes mandatory. > 2) New SBDB action is introduced: ct_lb_mark_local(logical_port:ip:port). > Controllers on each compute node will process logical flows and populate > OpenFlow load balancing group with only local backends, which are identified > by their logical_port name. Would it make sense to add "fallback" flows too for the case in which none of the backends is local? I.e.: - prioritize local backends - if no local backends exist fall back to any other backend OTOH, while typing the above I realized maybe that wouldn't work because reply traffic (from the backend) will not come back via the same chassis so we'll fail unNATing. > 3) This feature is exclusive to L3 mode, which requires associated > logical routers to operate distributively. All load balancer traffic flows > through router's distributed port, while load balancing logic is distributed > and processed locally on each compute node. Is there any reason to not support such "distributed" load balancers on logical switches too? > 4) Added tests that check configuration creation interface, check > recalculation of logical flows, openflow groups and possibility of balancing > on a computer node with this option. > > As part of future work on this RFC, following changes are planned: > 1) Integrate this functionality with BGP support. > 2) Implement incremental configuration updates. > 3) Support following use case: not all backends are processed in a > distributed manner; some backends are handled centrally on gw node (this is > important, for example, when some backends are located on unsecured compute > nodes). Instead of extending `ip_port_mappings` would it make sense to add a new explicit Load_Balancer_Backend table for better "type safety"? E.g. with explicit Logical_Switch_Port references. Maybe something like: "Load_Balancer": { "columns": { "name": {"type": "string"}, "typed_vips": {"type": {"key": {"type": "uuid", "refTable": "Load_Balancer_VIP"}, "min": 1, "max": "unlimited"}} ... "Load_Balancer_VIP": { "columns": { "ip": {"type": "string"}, "backends": { "type": {"key": {"type": "uuid", "refTable": "Load_Balancer_Backend"}, "min": 0, "max": "unlimited"}} ... "Load_Balancer_Backend": { "columns": { "ip": {"type": "string"}, "logical_port": { "type": {"key": {"type": "uuid", "refTable": "Logical_Switch_Port", "refType": "strong"}, "min": 1, "max": 1}}, ... Regards, Dumitru > > Suggested-by: Vladislav Odintsov <[email protected]> > Signed-off-by: Alexandra Rukomoinikova <[email protected]> > --- > controller/lflow.c | 14 +++ > controller/lflow.h | 1 + > controller/ovn-controller.c | 1 + > include/ovn/actions.h | 11 ++ > lib/actions.c | 133 +++++++++++++++++---- > lib/ovn-util.c | 2 +- > northd/en-lb-data.c | 11 +- > northd/en-lb-data.h | 3 + > northd/en-lr-stateful.c | 3 + > northd/en-lr-stateful.h | 1 + > northd/lb.c | 116 +++++++++++------- > northd/lb.h | 3 + > northd/northd.c | 208 +++++++++++++++++++++++--------- > northd/northd.h | 5 + > ovn-nb.xml | 15 ++- > tests/ovn-northd.at | 230 ++++++++++++++++++++++++++++++++++++ > tests/system-ovn.at | 83 +++++++++++++ > utilities/ovn-trace.c | 2 + > 18 files changed, 716 insertions(+), 126 deletions(-) > > diff --git a/controller/lflow.c b/controller/lflow.c > index b75ae5c0d..ab4537c68 100644 > --- a/controller/lflow.c > +++ b/controller/lflow.c > @@ -63,6 +63,7 @@ struct lookup_port_aux { > const struct sbrec_logical_flow *lflow; > struct objdep_mgr *deps_mgr; > const struct hmap *chassis_tunnels; > + const struct shash *local_bindings; > }; > > struct condition_aux { > @@ -172,6 +173,17 @@ tunnel_ofport_cb(const void *aux_, const char > *port_name, ofp_port_t *ofport) > return true; > } > > +static bool > +lookup_local_port_cb(const void *aux_, const char *port_name) > +{ > + const struct lookup_port_aux *aux = aux_; > + > + if (local_binding_get_primary_pb(aux->local_bindings, port_name)) { > + return true; > + } > + return false; > +} > + > static bool > is_chassis_resident_cb(const void *c_aux_, const char *port_name) > { > @@ -850,6 +862,7 @@ add_matches_to_flow_table(const struct sbrec_logical_flow > *lflow, > .lflow = lflow, > .deps_mgr = l_ctx_out->lflow_deps_mgr, > .chassis_tunnels = l_ctx_in->chassis_tunnels, > + .local_bindings = l_ctx_in->lbinding_lports, > }; > > /* Parse any meter to be used if this flow should punt packets to > @@ -865,6 +878,7 @@ add_matches_to_flow_table(const struct sbrec_logical_flow > *lflow, > struct ovnact_encode_params ep = { > .lookup_port = lookup_port_cb, > .tunnel_ofport = tunnel_ofport_cb, > + .lookup_local_port = lookup_local_port_cb, > .aux = &aux, > .is_switch = ldp->is_switch, > .group_table = l_ctx_out->group_table, > diff --git a/controller/lflow.h b/controller/lflow.h > index c8a87c886..d58d20439 100644 > --- a/controller/lflow.h > +++ b/controller/lflow.h > @@ -140,6 +140,7 @@ struct lflow_ctx_in { > const struct smap *template_vars; > const struct flow_collector_ids *collector_ids; > const struct hmap *local_lbs; > + const struct shash *lbinding_lports; > bool localnet_learn_fdb; > bool localnet_learn_fdb_changed; > bool explicit_arp_ns_output; > diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c > index 6396fa898..308f7edd7 100644 > --- a/controller/ovn-controller.c > +++ b/controller/ovn-controller.c > @@ -3923,6 +3923,7 @@ init_lflow_ctx(struct engine_node *node, > l_ctx_in->template_vars = &template_vars->local_templates; > l_ctx_in->collector_ids = &fo->collector_ids; > l_ctx_in->local_lbs = &lb_data->local_lbs; > + l_ctx_in->lbinding_lports = &rt_data->lbinding_data.bindings; > > l_ctx_out->flow_table = &fo->flow_table; > l_ctx_out->group_table = &fo->group_table; > diff --git a/include/ovn/actions.h b/include/ovn/actions.h > index 0eaef9112..051d5a2c9 100644 > --- a/include/ovn/actions.h > +++ b/include/ovn/actions.h > @@ -75,6 +75,7 @@ struct collector_set_ids; > OVNACT(CT_SNAT_IN_CZONE, ovnact_ct_nat) \ > OVNACT(CT_LB, ovnact_ct_lb) \ > OVNACT(CT_LB_MARK, ovnact_ct_lb) \ > + OVNACT(CT_LB_MARK_LOCAL, ovnact_ct_lb) \ > OVNACT(SELECT, ovnact_select) \ > OVNACT(CT_CLEAR, ovnact_null) \ > OVNACT(CT_COMMIT_NAT, ovnact_ct_commit_to_zone) \ > @@ -311,6 +312,12 @@ struct ovnact_ct_commit_to_zone { > uint8_t ltable; > }; > > +enum ovnact_ct_lb_type { > + OVNACT_CT_LB_TYPE_LABEL, > + OVNACT_CT_LB_TYPE_MARK, > + OVNACT_CT_LB_LOCAL_TYPE_MARK, > +}; > + > enum ovnact_ct_lb_flag { > OVNACT_CT_LB_FLAG_NONE, > OVNACT_CT_LB_FLAG_SKIP_SNAT, > @@ -324,6 +331,7 @@ struct ovnact_ct_lb_dst { > ovs_be32 ipv4; > }; > uint16_t port; > + char *port_name; > }; > > /* OVNACT_CT_LB/OVNACT_CT_LB_MARK. */ > @@ -897,6 +905,9 @@ struct ovnact_encode_params { > bool (*tunnel_ofport)(const void *aux, const char *port_name, > ofp_port_t *ofport); > > + /* Checks if the logical port exists and is bound to this chassis. */ > + bool (*lookup_local_port)(const void *aux, const char *port_name); > + > const void *aux; > > /* 'true' if the flow is for a switch. */ > diff --git a/lib/actions.c b/lib/actions.c > index 98ab368fc..3a6495f32 100644 > --- a/lib/actions.c > +++ b/lib/actions.c > @@ -1187,8 +1187,24 @@ ovnact_ct_commit_to_zone_free(struct > ovnact_ct_commit_to_zone *cn OVS_UNUSED) > { > } > > + > +static bool > +parse_ct_lb_logical_port_name(struct action_context *ctx, > + struct ovnact_ct_lb_dst *dst) > +{ > + if (ctx->lexer->token.type != LEX_T_STRING) { > + return false; > + } > + > + dst->port_name = xstrdup(ctx->lexer->token.s); > + > + lexer_get(ctx->lexer); > + return true; > +} > + > static void > -parse_ct_lb_action(struct action_context *ctx, bool ct_lb_mark) > +parse_ct_lb_action(struct action_context *ctx, > + enum ovnact_ct_lb_type type) > { > if (ctx->pp->cur_ltable >= ctx->pp->n_tables) { > lexer_error(ctx->lexer, "\"ct_lb\" action not allowed in last > table."); > @@ -1211,7 +1227,20 @@ parse_ct_lb_action(struct action_context *ctx, bool > ct_lb_mark) > > while (!lexer_match(ctx->lexer, LEX_T_SEMICOLON) && > !lexer_match(ctx->lexer, LEX_T_RPAREN)) { > - struct ovnact_ct_lb_dst dst; > + struct ovnact_ct_lb_dst dst = {0}; > + > + if (type == OVNACT_CT_LB_LOCAL_TYPE_MARK) { > + > + if (!parse_ct_lb_logical_port_name(ctx, &dst)) { > + vector_destroy(&dsts); > + lexer_syntax_error(ctx->lexer, > + "expecting logicl port name " > + "for distributed load balancer"); > + return; > + } > + lexer_get(ctx->lexer); > + } > + > if (lexer_match(ctx->lexer, LEX_T_LSQUARE)) { > /* IPv6 address and port */ > if (ctx->lexer->token.type != LEX_T_INTEGER > @@ -1298,8 +1327,19 @@ parse_ct_lb_action(struct action_context *ctx, bool > ct_lb_mark) > } > } > > - struct ovnact_ct_lb *cl = ct_lb_mark ? > ovnact_put_CT_LB_MARK(ctx->ovnacts) > - : ovnact_put_CT_LB(ctx->ovnacts); > + struct ovnact_ct_lb *cl; > + switch (type) { > + case OVNACT_CT_LB_TYPE_LABEL: > + cl = ovnact_put_CT_LB(ctx->ovnacts); > + break; > + case OVNACT_CT_LB_TYPE_MARK: > + cl = ovnact_put_CT_LB_MARK(ctx->ovnacts); > + break; > + case OVNACT_CT_LB_LOCAL_TYPE_MARK: > + cl = ovnact_put_CT_LB_MARK_LOCAL(ctx->ovnacts); > + break; > + } > + > cl->ltable = ctx->pp->cur_ltable + 1; > cl->n_dsts = vector_len(&dsts); > cl->dsts = vector_steal_array(&dsts); > @@ -1308,13 +1348,16 @@ parse_ct_lb_action(struct action_context *ctx, bool > ct_lb_mark) > } > > static void > -format_ct_lb(const struct ovnact_ct_lb *cl, struct ds *s, bool ct_lb_mark) > +format_ct_lb(const struct ovnact_ct_lb *cl, struct ds *s, > + enum ovnact_ct_lb_type type) > { > - if (ct_lb_mark) { > - ds_put_cstr(s, "ct_lb_mark"); > - } else { > - ds_put_cstr(s, "ct_lb"); > - } > + static const char *const lb_action_strings[] = { > + [OVNACT_CT_LB_TYPE_LABEL] = "ct_lb", > + [OVNACT_CT_LB_TYPE_MARK] = "ct_lb_mark", > + [OVNACT_CT_LB_LOCAL_TYPE_MARK] = "ct_lb_mark_local", > + }; > + ds_put_cstr(s, lb_action_strings[type]); > + > if (cl->n_dsts) { > ds_put_cstr(s, "(backends="); > for (size_t i = 0; i < cl->n_dsts; i++) { > @@ -1337,6 +1380,9 @@ format_ct_lb(const struct ovnact_ct_lb *cl, struct ds > *s, bool ct_lb_mark) > ds_put_format(s, "]:%"PRIu16, dst->port); > } > } > + if (type == OVNACT_CT_LB_LOCAL_TYPE_MARK) { > + ds_put_format(s, ":%s", dst->port_name); > + } > } > > if (cl->hash_fields) { > @@ -1363,20 +1409,36 @@ format_ct_lb(const struct ovnact_ct_lb *cl, struct ds > *s, bool ct_lb_mark) > static void > format_CT_LB(const struct ovnact_ct_lb *cl, struct ds *s) > { > - format_ct_lb(cl, s, false); > + format_ct_lb(cl, s, OVNACT_CT_LB_TYPE_LABEL); > } > > static void > format_CT_LB_MARK(const struct ovnact_ct_lb *cl, struct ds *s) > { > - format_ct_lb(cl, s, true); > + format_ct_lb(cl, s, OVNACT_CT_LB_TYPE_MARK); > +} > + > +static void > +format_CT_LB_MARK_LOCAL(const struct ovnact_ct_lb *cl, struct ds *s) > +{ > + format_ct_lb(cl, s, OVNACT_CT_LB_LOCAL_TYPE_MARK); > +} > + > +static inline void > +append_nat_destination(struct ds *ds, const char *ip_addr, > + bool needs_brackets) > +{ > + ds_put_format(ds, "ct(nat(dst=%s%s%s", > + needs_brackets ? "[" : "", > + ip_addr, > + needs_brackets ? "]" : ""); > } > > static void > encode_ct_lb(const struct ovnact_ct_lb *cl, > const struct ovnact_encode_params *ep, > struct ofpbuf *ofpacts, > - bool ct_lb_mark) > + enum ovnact_ct_lb_type type) > { > uint8_t recirc_table = cl->ltable + first_ptable(ep, ep->pipeline); > if (!cl->n_dsts) { > @@ -1408,7 +1470,8 @@ encode_ct_lb(const struct ovnact_ct_lb *cl, > struct ofpact_group *og; > uint32_t zone_reg = ep->is_switch ? MFF_LOG_CT_ZONE - MFF_REG0 > : MFF_LOG_DNAT_ZONE - MFF_REG0; > - const char *flag_reg = ct_lb_mark ? "ct_mark" : "ct_label"; > + const char *flag_reg = (type == OVNACT_CT_LB_TYPE_LABEL) > + ? "ct_label" : "ct_mark"; > > const char *ct_flag_value; > switch (cl->ct_flag) { > @@ -1443,11 +1506,14 @@ encode_ct_lb(const struct ovnact_ct_lb *cl, > } else { > inet_ntop(AF_INET6, &dst->ipv6, ip_addr, sizeof ip_addr); > } > - ds_put_format(&ds, > ",bucket=bucket_id=%"PRIuSIZE",weight:100,actions=" > - "ct(nat(dst=%s%s%s", bucket_id, > - dst->family == AF_INET6 && dst->port ? "[" : "", > - ip_addr, > - dst->family == AF_INET6 && dst->port ? "]" : ""); > + if (type == OVNACT_CT_LB_LOCAL_TYPE_MARK > + && !ep->lookup_local_port(ep->aux, dst->port_name)) { > + continue; > + } > + ds_put_format(&ds, > ",bucket=bucket_id=%"PRIuSIZE",weight:100,actions=", > + bucket_id); > + bool needs_brackets = (dst->family == AF_INET6 && dst->port); > + append_nat_destination(&ds, ip_addr, needs_brackets); > if (dst->port) { > ds_put_format(&ds, ":%"PRIu16, dst->port); > } > @@ -1480,7 +1546,7 @@ encode_CT_LB(const struct ovnact_ct_lb *cl, > const struct ovnact_encode_params *ep, > struct ofpbuf *ofpacts) > { > - encode_ct_lb(cl, ep, ofpacts, false); > + encode_ct_lb(cl, ep, ofpacts, OVNACT_CT_LB_TYPE_LABEL); > } > > static void > @@ -1488,13 +1554,30 @@ encode_CT_LB_MARK(const struct ovnact_ct_lb *cl, > const struct ovnact_encode_params *ep, > struct ofpbuf *ofpacts) > { > - encode_ct_lb(cl, ep, ofpacts, true); > + encode_ct_lb(cl, ep, ofpacts, OVNACT_CT_LB_TYPE_MARK); > } > > static void > -ovnact_ct_lb_free(struct ovnact_ct_lb *ct_lb) > +encode_CT_LB_MARK_LOCAL(const struct ovnact_ct_lb *cl, > + const struct ovnact_encode_params *ep, > + struct ofpbuf *ofpacts) > +{ > + encode_ct_lb(cl, ep, ofpacts, OVNACT_CT_LB_LOCAL_TYPE_MARK); > +} > + > +static void > +ovnact_ct_lb_free_dsts(struct ovnact_ct_lb *ct_lb) > { > + for (size_t i = 0; i < ct_lb->n_dsts; i++) { > + free(ct_lb->dsts[i].port_name); > + } > free(ct_lb->dsts); > +} > + > +static void > +ovnact_ct_lb_free(struct ovnact_ct_lb *ct_lb) > +{ > + ovnact_ct_lb_free_dsts(ct_lb); > free(ct_lb->hash_fields); > } > > @@ -5900,9 +5983,11 @@ parse_action(struct action_context *ctx) > } else if (lexer_match_id(ctx->lexer, "ct_snat_in_czone")) { > parse_CT_SNAT_IN_CZONE(ctx); > } else if (lexer_match_id(ctx->lexer, "ct_lb")) { > - parse_ct_lb_action(ctx, false); > + parse_ct_lb_action(ctx, OVNACT_CT_LB_TYPE_LABEL); > } else if (lexer_match_id(ctx->lexer, "ct_lb_mark")) { > - parse_ct_lb_action(ctx, true); > + parse_ct_lb_action(ctx, OVNACT_CT_LB_TYPE_MARK); > + } else if (lexer_match_id(ctx->lexer, "ct_lb_mark_local")) { > + parse_ct_lb_action(ctx, OVNACT_CT_LB_LOCAL_TYPE_MARK); > } else if (lexer_match_id(ctx->lexer, "ct_clear")) { > ovnact_put_CT_CLEAR(ctx->ovnacts); > } else if (lexer_match_id(ctx->lexer, "ct_commit_nat")) { > diff --git a/lib/ovn-util.c b/lib/ovn-util.c > index 8b583fa6d..7eb49b553 100644 > --- a/lib/ovn-util.c > +++ b/lib/ovn-util.c > @@ -915,7 +915,7 @@ ip_address_and_port_from_lb_key(const char *key, char > **ip_address, > * > * NOTE: If OVN_NORTHD_PIPELINE_CSUM is updated make sure to double check > * whether an update of OVN_INTERNAL_MINOR_VER is required. */ > -#define OVN_NORTHD_PIPELINE_CSUM "2405300854 10800" > +#define OVN_NORTHD_PIPELINE_CSUM "1693307722 10856" > #define OVN_INTERNAL_MINOR_VER 10 > > /* Returns the OVN version. The caller must free the returned value. */ > diff --git a/northd/en-lb-data.c b/northd/en-lb-data.c > index 6d52d465e..5591aa576 100644 > --- a/northd/en-lb-data.c > +++ b/northd/en-lb-data.c > @@ -136,7 +136,7 @@ en_lb_data_clear_tracked_data(void *data) > destroy_tracked_data(lb_data); > } > > - > +/* TODO: incremental processing for distributed lb. */ > /* Handler functions. */ > enum engine_input_handler_result > lb_data_load_balancer_handler(struct engine_node *node, void *data) > @@ -166,6 +166,7 @@ lb_data_load_balancer_handler(struct engine_node *node, > void *data) > add_crupdated_lb_to_tracked_data(lb, trk_lb_data, > lb->health_checks); > trk_lb_data->has_routable_lb |= lb->routable; > + trk_lb_data->distributed_mode |= lb->distributed_mode; > continue; > } > > @@ -180,6 +181,7 @@ lb_data_load_balancer_handler(struct engine_node *node, > void *data) > add_deleted_lb_to_tracked_data(lb, trk_lb_data, > lb->health_checks); > trk_lb_data->has_routable_lb |= lb->routable; > + trk_lb_data->distributed_mode |= lb->distributed_mode; > } else { > /* Load balancer updated. */ > bool health_checks = lb->health_checks; > @@ -189,12 +191,13 @@ lb_data_load_balancer_handler(struct engine_node *node, > void *data) > sset_swap(&lb->ips_v6, &old_ips_v6); > enum lb_neighbor_responder_mode neigh_mode = lb->neigh_mode; > bool routable = lb->routable; > + bool distributed_mode = lb->distributed_mode; > ovn_northd_lb_reinit(lb, tracked_lb); > health_checks |= lb->health_checks; > struct crupdated_lb *clb = add_crupdated_lb_to_tracked_data( > lb, trk_lb_data, health_checks); > trk_lb_data->has_routable_lb |= lb->routable; > - > + trk_lb_data->distributed_mode |= lb->distributed_mode; > /* Determine the inserted and deleted vips and store them in > * the tracked data. */ > const char *vip; > @@ -226,6 +229,10 @@ lb_data_load_balancer_handler(struct engine_node *node, > void *data) > /* If neigh_mode is updated trigger a full recompute. */ > return EN_UNHANDLED; > } > + if (distributed_mode != lb->distributed_mode) { > + /* If neigh_mode is updated trigger a full recompute. */ > + return EN_UNHANDLED; > + } > } > } > > diff --git a/northd/en-lb-data.h b/northd/en-lb-data.h > index 1da087656..bbc464e45 100644 > --- a/northd/en-lb-data.h > +++ b/northd/en-lb-data.h > @@ -82,6 +82,9 @@ struct tracked_lb_data { > > /* Indicates if any lb (in the tracked data) has 'routable' flag set. */ > bool has_routable_lb; > + > + /* Indicates if a lb is in distributed mode. */ > + bool distributed_mode; > }; > > /* Datapath (logical switch) to lb/lbgrp association data. */ > diff --git a/northd/en-lr-stateful.c b/northd/en-lr-stateful.c > index 56e93f3c4..357c8bbc9 100644 > --- a/northd/en-lr-stateful.c > +++ b/northd/en-lr-stateful.c > @@ -326,6 +326,7 @@ lr_stateful_lb_data_handler(struct engine_node *node, > void *data_) > ovn_datapaths_find_by_index(input_data.lr_datapaths, > lr_stateful_rec->lr_index); > lr_stateful_rec->has_lb_vip = od_has_lb_vip(od); > + lr_stateful_rec->has_distributed_lb = lr_has_distributed_lb(od); > } > > return EN_HANDLED_UPDATED; > @@ -530,7 +531,9 @@ lr_stateful_record_create(struct lr_stateful_table *table, > if (nbr->n_nat) { > lr_stateful_rebuild_vip_nats(lr_stateful_rec); > } > + > lr_stateful_rec->has_lb_vip = od_has_lb_vip(od); > + lr_stateful_rec->has_distributed_lb = lr_has_distributed_lb(od); > > hmap_insert(&table->entries, &lr_stateful_rec->key_node, > uuid_hash(&lr_stateful_rec->nbr_uuid)); > diff --git a/northd/en-lr-stateful.h b/northd/en-lr-stateful.h > index 75975c935..c7e30127a 100644 > --- a/northd/en-lr-stateful.h > +++ b/northd/en-lr-stateful.h > @@ -59,6 +59,7 @@ struct lr_stateful_record { > > bool has_lb_vip; > > + bool has_distributed_lb; > /* Load Balancer vIPs relevant for this datapath. */ > struct ovn_lb_ip_set *lb_ips; > > diff --git a/northd/lb.c b/northd/lb.c > index 30726cd27..13b76a1ef 100644 > --- a/northd/lb.c > +++ b/northd/lb.c > @@ -85,12 +85,12 @@ ovn_lb_ip_set_clone(struct ovn_lb_ip_set *lb_ip_set) > return clone; > } > > -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, > - bool template) > +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, > + bool template) > { > lb_vip_nb->backend_ips = xstrdup(backend_ips); > lb_vip_nb->n_backends = vector_len(&lb_vip->backends); > @@ -101,43 +101,57 @@ void ovn_northd_lb_vip_init(struct ovn_northd_lb_vip > *lb_vip_nb, > } > > /* > - * Initializes health check configuration for load balancer VIP > - * backends. Parses the ip_port_mappings in the format : > + * Initializes health check configuration for load balancer VIP backends. > + * Parses the ip_port_mappings in the format: > * "ip:logical_port:src_ip[:az_name]". > - * If az_name is present and non-empty, it indicates this is a > - * remote service monitor (backend is in another availability zone), > - * it should be propogated to another AZ by interconnection processing. > + * If az_name is present and non-empty, it indicates this is a remote service > + * monitor (backend is in another availability zone), it should be propogated > + * to another AZ by interconnection processing. > + * src_ip parameter becomes optional when distributed mode is enabled without > + * health checks configured. > */ > static void > -ovn_lb_vip_backends_health_check_init(const struct ovn_northd_lb *lb, > - const struct ovn_lb_vip *lb_vip, > - struct ovn_northd_lb_vip *lb_vip_nb) > +ovn_lb_vip_backends_ip_port_mappings_init(const struct ovn_northd_lb *lb, > + const struct ovn_lb_vip *lb_vip, > + struct ovn_northd_lb_vip > *lb_vip_nb, > + bool *is_lb_correctly_configured) > { > struct ds key = DS_EMPTY_INITIALIZER; > + bool allow_without_src_ip = lb->distributed_mode > + && !lb_vip_nb->lb_health_check; > > for (size_t j = 0; j < vector_len(&lb_vip->backends); j++) { > const struct ovn_lb_backend *backend = > vector_get_ptr(&lb_vip->backends, j); > + struct ovn_northd_lb_backend *backend_nb = NULL; > + char *port_name = NULL, *az_name = NULL, *first_colon = NULL; > + char *svc_mon_src_ip = NULL, *src_ip = NULL; > + bool is_remote = false; > ds_clear(&key); > ds_put_format(&key, IN6_IS_ADDR_V4MAPPED(&lb_vip->vip) > ? "%s" : "[%s]", backend->ip_str); > - > const char *s = smap_get(&lb->nlb->ip_port_mappings, ds_cstr(&key)); > if (!s) { > - continue; > + goto mark_error_and_cleanup; > } > > - char *svc_mon_src_ip = NULL; > - char *az_name = NULL; > - bool is_remote = false; > - char *port_name = xstrdup(s); > - char *src_ip = NULL; > - > - char *first_colon = strchr(port_name, ':'); > - if (!first_colon) { > - free(port_name); > - continue; > + port_name = xstrdup(s); > + first_colon = strchr(port_name, ':'); > + if (!first_colon && allow_without_src_ip) { > + if (!*port_name) { > + VLOG_WARN("Empty port name in distributed mode for IP %s", > + ds_cstr(&key)); > + goto mark_error_and_cleanup; > + } > + src_ip = NULL; > + az_name = NULL; > + is_remote = false; > + goto init_backend_nb; > + } else if (!first_colon) { > + VLOG_WARN("Expected ':' separator for: %s", port_name); > + goto mark_error_and_cleanup; > } > + > *first_colon = '\0'; > > if (first_colon[1] == '[') { > @@ -145,8 +159,7 @@ ovn_lb_vip_backends_health_check_init(const struct > ovn_northd_lb *lb, > char *ip_end = strchr(first_colon + 2, ']'); > if (!ip_end) { > VLOG_WARN("Malformed IPv6 address in backend %s", s); > - free(port_name); > - continue; > + goto mark_error_and_cleanup; > } > > src_ip = first_colon + 2; > @@ -157,8 +170,7 @@ ovn_lb_vip_backends_health_check_init(const struct > ovn_northd_lb *lb, > if (!*az_name) { > VLOG_WARN("Empty AZ name specified for backend %s", > port_name); > - free(port_name); > - continue; > + goto mark_error_and_cleanup; > } > is_remote = true; > } > @@ -172,32 +184,37 @@ ovn_lb_vip_backends_health_check_init(const struct > ovn_northd_lb *lb, > if (!*az_name) { > VLOG_WARN("Empty AZ name specified for backend %s", > port_name); > - free(port_name); > - continue; > + goto mark_error_and_cleanup; > } > - is_remote = true; > + is_remote = true; > } > } > > struct sockaddr_storage svc_mon_src_addr; > if (!src_ip || !inet_parse_address(src_ip, &svc_mon_src_addr)) { > VLOG_WARN("Invalid svc mon src IP %s", src_ip ? src_ip : "NULL"); > + goto mark_error_and_cleanup; > } else { > struct ds src_ip_s = DS_EMPTY_INITIALIZER; > ss_format_address_nobracks(&svc_mon_src_addr, &src_ip_s); > svc_mon_src_ip = ds_steal_cstr(&src_ip_s); > } > > - if (svc_mon_src_ip) { > - struct ovn_northd_lb_backend *backend_nb = > - &lb_vip_nb->backends_nb[j]; > - backend_nb->health_check = true; > - backend_nb->logical_port = xstrdup(port_name); > - backend_nb->svc_mon_src_ip = svc_mon_src_ip; > - backend_nb->az_name = is_remote ? xstrdup(az_name) : NULL; > - backend_nb->local_backend = !is_remote; > - } > +init_backend_nb: > + backend_nb = &lb_vip_nb->backends_nb[j]; > + backend_nb->health_check = lb_vip_nb->lb_health_check; > + backend_nb->logical_port = xstrdup(port_name); > + backend_nb->svc_mon_src_ip = svc_mon_src_ip; > + backend_nb->az_name = is_remote ? xstrdup(az_name) : NULL; > + backend_nb->local_backend = !is_remote; > + goto cleanup_and_continue; > + > +mark_error_and_cleanup: > + *is_lb_correctly_configured = false; > + > +cleanup_and_continue: > free(port_name); > + continue; > } > > ds_destroy(&key); > @@ -364,6 +381,9 @@ ovn_northd_lb_init(struct ovn_northd_lb *lb, > lb->hairpin_snat_ip = xstrdup(snat_ip); > } > > + lb->distributed_mode = smap_get_bool(&nbrec_lb->options, > + "distributed", > + false); > sset_init(&lb->ips_v4); > sset_init(&lb->ips_v6); > struct smap_node *node; > @@ -403,8 +423,16 @@ ovn_northd_lb_init(struct ovn_northd_lb *lb, > } > n_vips++; > > - if (lb_vip_nb->lb_health_check) { > - ovn_lb_vip_backends_health_check_init(lb, lb_vip, lb_vip_nb); > + if (lb->distributed_mode || lb_vip_nb->lb_health_check) { > + bool is_lb_correctly_configured = true; > + ovn_lb_vip_backends_ip_port_mappings_init(lb, > + lb_vip, lb_vip_nb, &is_lb_correctly_configured); > + if (lb->distributed_mode && !is_lb_correctly_configured) { > + VLOG_ERR("Proper ip_port_mappings configuration for " > + "all backends is required for distributed load " > + "balancer %s operation.", lb->nlb->name); > + lb->distributed_mode = false; > + } > } > } > > diff --git a/northd/lb.h b/northd/lb.h > index a0e560204..2f30cce0b 100644 > --- a/northd/lb.h > +++ b/northd/lb.h > @@ -74,6 +74,9 @@ struct ovn_northd_lb { > /* Indicates if the load balancer has health checks configured. */ > bool health_checks; > > + /* Indicates if the load balancer is distributed. */ > + bool distributed_mode; > + > char *hairpin_snat_ip; > }; > > diff --git a/northd/northd.c b/northd/northd.c > index 015f30a35..b0d86c741 100644 > --- a/northd/northd.c > +++ b/northd/northd.c > @@ -473,6 +473,20 @@ od_has_lb_vip(const struct ovn_datapath *od) > } > } > > +bool > +lr_has_distributed_lb(const struct ovn_datapath *od) > +{ > + for (size_t i = 0; i < od->nbr->n_load_balancer; i++) { > + if (lb_has_vip(od->nbr->load_balancer[i]) && > + smap_get_bool(&od->nbr->load_balancer[i]->options, > + "distributed", false)) { > + return true; > + } > + } > + > + return false; > +} > + > static const char * > ovn_datapath_name(const struct sbrec_datapath_binding *sb) > { > @@ -537,6 +551,7 @@ ovn_datapath_create(struct hmap *datapaths, const struct > uuid *key, > od->router_ports = VECTOR_EMPTY_INITIALIZER(struct ovn_port *); > od->l3dgw_ports = VECTOR_EMPTY_INITIALIZER(struct ovn_port *); > od->localnet_ports = VECTOR_EMPTY_INITIALIZER(struct ovn_port *); > + od->distrubuted_lbs = VECTOR_EMPTY_INITIALIZER(struct ovn_northd_lb *); > od->lb_with_stateless_mode = false; > od->ipam_info_initialized = false; > od->tunnel_key = sdp->sb_dp->tunnel_key; > @@ -567,6 +582,7 @@ ovn_datapath_destroy(struct hmap *datapaths, struct > ovn_datapath *od) > vector_destroy(&od->ls_peers); > vector_destroy(&od->localnet_ports); > vector_destroy(&od->l3dgw_ports); > + vector_destroy(&od->distrubuted_lbs); > destroy_mcast_info_for_datapath(od); > destroy_ports_for_datapath(od); > sset_destroy(&od->router_ips); > @@ -3140,6 +3156,53 @@ ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn, > } > } > > +static inline void > +append_lb_backend_to_action(const struct ovn_lb_backend *backend, > + const struct ovn_northd_lb_backend *backend_nb, > + bool distributed_mode, > + struct ds *action) > +{ > + bool ipv6 = !IN6_IS_ADDR_V4MAPPED(&backend->ip); > + > + if (distributed_mode) { > + ds_put_format(action, "\"%s\":", backend_nb->logical_port); > + } > + ds_put_format(action, ipv6 ? "[%s]:%"PRIu16"," : "%s:%"PRIu16",", > + backend->ip_str, backend->port); > +} > + > +static bool > +is_backend_online(const struct ovn_northd_lb *lb, > + const struct ovn_lb_backend *backend, > + const struct ovn_northd_lb_backend *backend_nb, > + const struct svc_monitors_map_data *svc_mons_data) > +{ > + const char *protocol = lb->nlb->protocol; > + if (!protocol || !protocol[0]) { > + protocol = "tcp"; > + } > + > + struct service_monitor_info *mon_info = > + get_service_mon(svc_mons_data->local_svc_monitors_map, > + svc_mons_data->ic_learned_svc_monitors_map, > + backend->ip_str, > + backend_nb->logical_port, > + backend->port, > + protocol); > + > + if (!mon_info) { > + return false; > + } > + > + ovs_assert(mon_info->sbrec_mon); > + if (mon_info->sbrec_mon->status && > + strcmp(mon_info->sbrec_mon->status, "online")) { > + return false; > + } > + > + return true; > +} > + > static bool > build_lb_vip_actions(const struct ovn_northd_lb *lb, > const struct ovn_lb_vip *lb_vip, > @@ -3165,12 +3228,14 @@ build_lb_vip_actions(const struct ovn_northd_lb *lb, > } > } > > - if (lb_vip_nb->lb_health_check) { > - ds_put_cstr(action, "ct_lb_mark(backends="); > + ds_put_format(action, "%s", lb->distributed_mode > + ? "ct_lb_mark_local(backends=" > + : "ct_lb_mark(backends="); > > - size_t i = 0; > - size_t n_active_backends = 0; > - const struct ovn_lb_backend *backend; > + const struct ovn_lb_backend *backend; > + size_t n_active_backends = 0; > + size_t i = 0; > + if (lb_vip_nb->lb_health_check) { > VECTOR_FOR_EACH_PTR (&lb_vip->backends, backend) { > struct ovn_northd_lb_backend *backend_nb = > &lb_vip_nb->backends_nb[i++]; > @@ -3179,41 +3244,44 @@ build_lb_vip_actions(const struct ovn_northd_lb *lb, > continue; > } > > - const char *protocol = lb->nlb->protocol; > - if (!protocol || !protocol[0]) { > - protocol = "tcp"; > + if (is_backend_online(lb, backend, > + backend_nb, svc_mons_data)) { > + n_active_backends++; > + append_lb_backend_to_action(backend, backend_nb, > + false, action); > } > + } > + ds_chomp(action, ','); > + } else if (lb->distributed_mode) { > + VECTOR_FOR_EACH_PTR (&lb_vip->backends, backend) { > + struct ovn_northd_lb_backend *backend_nb = > + &lb_vip_nb->backends_nb[i++]; > > - struct service_monitor_info *mon_info = > - get_service_mon(svc_mons_data->local_svc_monitors_map, > - svc_mons_data->ic_learned_svc_monitors_map, > - backend->ip_str, > - backend_nb->logical_port, > - backend->port, > - protocol); > - > - if (!mon_info) { > - continue; > - } > + if (lb_vip_nb->lb_health_check > + && !backend_nb->health_check) { > + continue; > + } > > - ovs_assert(mon_info->sbrec_mon); > - if (mon_info->sbrec_mon->status && > - strcmp(mon_info->sbrec_mon->status, "online")) { > - continue; > - } > + if (lb_vip_nb->lb_health_check) { > + if (is_backend_online(lb, backend, > + backend_nb, svc_mons_data)) { > + n_active_backends++; > + } else { > + continue; > + } > + } > > - n_active_backends++; > - bool ipv6 = !IN6_IS_ADDR_V4MAPPED(&backend->ip); > - ds_put_format(action, ipv6 ? "[%s]:%"PRIu16"," : "%s:%"PRIu16",", > - backend->ip_str, backend->port); > + append_lb_backend_to_action(backend, backend_nb, > + true, action); > } > ds_chomp(action, ','); > + } else { > + ds_put_format(action, "%s", lb_vip_nb->backend_ips); > + } > > + if (lb_vip_nb->lb_health_check) { > drop = !n_active_backends && !lb_vip->empty_backend_rej; > reject = !n_active_backends && lb_vip->empty_backend_rej; > - } else { > - ds_put_format(action, "ct_lb_mark(backends=%s", > - lb_vip_nb->backend_ips); > } > > if (reject) { > @@ -3250,6 +3318,20 @@ build_lb_vip_actions(const struct ovn_northd_lb *lb, > return reject; > } > > +static inline void > +handle_lb_datapath_modes(struct ovn_datapath *od, > + struct ovn_lb_datapaths *lb_dps, > + bool ls_datapath) > +{ > + if (ls_datapath && od->lb_with_stateless_mode) { > + hmapx_add(&lb_dps->ls_lb_with_stateless_mode, od); > + } > + > + if (!ls_datapath && lb_dps->lb->distributed_mode) { > + vector_push(&od->distrubuted_lbs, &lb_dps->lb); > + } > +} > + > static void > build_lb_datapaths(const struct hmap *lbs, const struct hmap *lb_groups, > struct ovn_datapaths *ls_datapaths, > @@ -3292,9 +3374,7 @@ build_lb_datapaths(const struct hmap *lbs, const struct > hmap *lb_groups, > lb_dps = ovn_lb_datapaths_find(lb_datapaths_map, lb_uuid); > ovs_assert(lb_dps); > ovn_lb_datapaths_add_ls(lb_dps, 1, &od); > - if (od->lb_with_stateless_mode) { > - hmapx_add(&lb_dps->ls_lb_with_stateless_mode, od); > - } > + handle_lb_datapath_modes(od, lb_dps, true); > } > > for (size_t i = 0; i < od->nbs->n_load_balancer_group; i++) { > @@ -3328,6 +3408,7 @@ build_lb_datapaths(const struct hmap *lbs, const struct > hmap *lb_groups, > lb_dps = ovn_lb_datapaths_find(lb_datapaths_map, lb_uuid); > ovs_assert(lb_dps); > ovn_lb_datapaths_add_lr(lb_dps, 1, &od); > + handle_lb_datapath_modes(od, lb_dps, false); > } > } > > @@ -3664,6 +3745,7 @@ sync_pb_for_lrp(struct ovn_port *op, > > bool always_redirect = > !lr_stateful_rec->lrnat_rec->has_distributed_nat && > + !lr_stateful_rec->has_distributed_lb && > !l3dgw_port_has_associated_vtep_lports(op->primary_port); > > const char *redirect_type = smap_get(&op->nbrp->options, > @@ -5067,11 +5149,7 @@ northd_handle_lb_data_changes(struct tracked_lb_data > *trk_lb_data, > lb_dps = ovn_lb_datapaths_find(lb_datapaths_map, > &uuidnode->uuid); > ovs_assert(lb_dps); > ovn_lb_datapaths_add_ls(lb_dps, 1, &od); > - > - if (od->lb_with_stateless_mode) { > - hmapx_add(&lb_dps->ls_lb_with_stateless_mode, od); > - } > - > + handle_lb_datapath_modes(od, lb_dps, true); > /* Add the lb to the northd tracked data. */ > hmapx_add(&nd_changes->trk_lbs.crupdated, lb_dps); > } > @@ -5108,7 +5186,7 @@ northd_handle_lb_data_changes(struct tracked_lb_data > *trk_lb_data, > lb_dps = ovn_lb_datapaths_find(lb_datapaths_map, > &uuidnode->uuid); > ovs_assert(lb_dps); > ovn_lb_datapaths_add_lr(lb_dps, 1, &od); > - > + handle_lb_datapath_modes(od, lb_dps, false); > /* Add the lb to the northd tracked data. */ > hmapx_add(&nd_changes->trk_lbs.crupdated, lb_dps); > } > @@ -10134,6 +10212,12 @@ build_lswitch_ip_mcast_igmp_mld(struct > ovn_igmp_group *igmp_group, > 90, ds_cstr(match), ds_cstr(actions), lflow_ref); > } > > +static inline bool > +peer_has_distributed_lb(struct ovn_port *op) > +{ > + return op && op->od && !vector_is_empty(&op->od->distrubuted_lbs); > +} > + > /* Ingress table 25: Destination lookup, unicast handling (priority 50), */ > static void > build_lswitch_ip_unicast_lookup(struct ovn_port *op, > @@ -10208,7 +10292,8 @@ build_lswitch_ip_unicast_lookup(struct ovn_port *op, > struct ovn_port *)->cr_port->json_key; > } > > - if (add_chassis_resident_check) { > + if (add_chassis_resident_check > + && !peer_has_distributed_lb(op->peer)) { > ds_put_format(match, " && is_chassis_resident(%s)", > json_key); > } > } else if (op->cr_port) { > @@ -11922,7 +12007,8 @@ build_distr_lrouter_nat_flows_for_lb(struct > lrouter_nat_lb_flows_ctx *ctx, > struct ovn_datapath *od, > struct lflow_ref *lflow_ref, > struct ovn_port *dgp, > - bool stateless_nat) > + bool stateless_nat, > + bool distributed_mode) > { > struct ds dnat_action = DS_EMPTY_INITIALIZER; > > @@ -11964,8 +12050,9 @@ build_distr_lrouter_nat_flows_for_lb(struct > lrouter_nat_lb_flows_ctx *ctx, > meter = copp_meter_get(COPP_REJECT, od->nbr->copp, > ctx->meter_groups); > } > > - if (!vector_is_empty(&ctx->lb_vip->backends) || > - !ctx->lb_vip->empty_backend_rej) { > + if (!distributed_mode > + && (!vector_is_empty(&ctx->lb_vip->backends) > + || !ctx->lb_vip->empty_backend_rej)) { > ds_put_format(ctx->new_match, " && is_chassis_resident(%s)", > dgp->cr_port->json_key); > } > @@ -12008,23 +12095,33 @@ build_distr_lrouter_nat_flows_for_lb(struct > lrouter_nat_lb_flows_ctx *ctx, > } > > /* We need to centralize the LB traffic to properly perform > - * the undnat stage. > + * the undnat stage in case of non distributed load balancer. > */ > ds_put_format(ctx->undnat_match, ") && outport == %s", dgp->json_key); > ds_clear(ctx->gw_redir_action); > - ds_put_format(ctx->gw_redir_action, "outport = %s; next;", > - dgp->cr_port->json_key); > + const char *outport = distributed_mode ? > + dgp->json_key : > + dgp->cr_port->json_key; > + > + ds_put_format(ctx->gw_redir_action, > + "outport = %s; next;", outport); > > ovn_lflow_add_with_hint(ctx->lflows, od, S_ROUTER_IN_GW_REDIRECT, > 200, ds_cstr(ctx->undnat_match), > ds_cstr(ctx->gw_redir_action), > &ctx->lb->nlb->header_, > lflow_ref); > + > ds_truncate(ctx->undnat_match, undnat_match_len); > > - ds_put_format(ctx->undnat_match, ") && (inport == %s || outport == %s)" > - " && is_chassis_resident(%s)", dgp->json_key, > dgp->json_key, > - dgp->cr_port->json_key); > + ds_put_format(ctx->undnat_match, ") && (inport == %s || outport == %s)", > + dgp->json_key, dgp->json_key); > + > + if (!distributed_mode) { > + ds_put_format(ctx->undnat_match, " && is_chassis_resident(%s)", > + dgp->cr_port->json_key); > + } > + > /* Use the LB protocol as matching criteria for out undnat and snat when > * creating LBs with stateless NAT. */ > if (stateless_nat) { > @@ -12153,6 +12250,8 @@ build_lrouter_nat_flows_for_lb( > svc_mons_data, > false); > > + bool distributed_mode = lb->distributed_mode; > + > /* Higher priority rules are added for load-balancing in DNAT > * table. For every match (on a VIP[:port]), we add two flows. > * One flow is for specific matching on ct.new with an action > @@ -12257,7 +12356,8 @@ build_lrouter_nat_flows_for_lb( > VECTOR_FOR_EACH (&od->l3dgw_ports, dgp) { > build_distr_lrouter_nat_flows_for_lb(&ctx, type, od, > lb_dps->lflow_ref, dgp, > - stateless_nat); > + stateless_nat, > + distributed_mode); > } > } > > @@ -13403,7 +13503,8 @@ build_adm_ctrl_flows_for_lrouter_port( > ds_put_format(match, "%s", op->lrp_networks.ea_s); > } > ds_put_format(match, " && inport == %s", op->json_key); > - if (consider_l3dgw_port_is_centralized(op)) { > + bool l3dgw_port = consider_l3dgw_port_is_centralized(op); > + if (l3dgw_port && vector_is_empty(&op->od->distrubuted_lbs)) { > ds_put_format(match, " && is_chassis_resident(%s)", > op->cr_port->json_key); > } > @@ -15830,7 +15931,8 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op, > struct ovn_port *)->cr_port->json_key; > } > > - if (add_chassis_resident_check) { > + if (add_chassis_resident_check > + && vector_is_empty(&op->od->distrubuted_lbs)) { > ds_put_format(match, " && is_chassis_resident(%s)", > json_key); > } > diff --git a/northd/northd.h b/northd/northd.h > index 8f865e8b3..868721aca 100644 > --- a/northd/northd.h > +++ b/northd/northd.h > @@ -106,6 +106,7 @@ struct ovn_datapath * > ovn_datapath_find_by_key(struct hmap *datapaths, uint32_t dp_key); > > bool od_has_lb_vip(const struct ovn_datapath *od); > +bool lr_has_distributed_lb(const struct ovn_datapath *od); > > /* List of routing and routing-related protocols which > * OVN is capable of redirecting from LRP to specific LSP. */ > @@ -448,6 +449,10 @@ struct ovn_datapath { > /* Map of ovn_port objects belonging to this datapath. > * This map doesn't include derived ports. */ > struct hmap ports; > + > + /* A set of distributed load balancers associated > + * with this datapath. The datapath must be a router. */ > + struct vector distrubuted_lbs; > }; > > const struct ovn_datapath *ovn_datapath_find(const struct hmap *datapaths, > diff --git a/ovn-nb.xml b/ovn-nb.xml > index b7b5b5c40..0ed0a407e 100644 > --- a/ovn-nb.xml > +++ b/ovn-nb.xml > @@ -2288,13 +2288,15 @@ > <p> > Maps from endpoint IP to a colon-separated pair of logical port > name > and source IP, > - e.g. <code><var>port_name</var>:<var>sourc_ip</var></code> for > IPv4. > + e.g. <code><var>port_name</var>:<var>source_ip</var></code> for > IPv4. > Health checks are sent to this port with the specified source IP. > For IPv6 square brackets must be used around IP address, e.g: > - <code><var>port_name</var>:<var>[sourc_ip]</var></code> > + <code><var>port_name</var>:<var>[source_ip]</var></code> > Remote endpoint: > Specify :target_zone_name at the end of the above syntax to create > remote health checks in a specific zone. > + For distributed load balancers - ip_port_mappings is required. > + In the absence of health checks - source_ip is optional. > </p> > > <p> > @@ -2497,6 +2499,15 @@ or > traffic may be dropped in scenarios where we have different chassis > for each DGP. This option is set to <code>false</code> by default. > </column> > + > + <column name="options" key="distributed"> > + When enabled on a load balancer associated with a distributed gateway > + router, load balancing operations are performed locally on chassis > + hosting backends. In this mode, traffic is balanced to backends > + located on the same chassis where the processing occurs. Enabling > + this option requires configuring <ref column="ip_port_mappings"/> > + for proper operation. > + </column> > </group> > </table> > > diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at > index 11bbb211d..6970a19ab 100644 > --- a/tests/ovn-northd.at > +++ b/tests/ovn-northd.at > @@ -17808,8 +17808,12 @@ AT_SETUP([ip_port_mappings validation]) > ovn_start > > # ip_port_mappings syntax: ip:lport_name:src_ip:<az_name>(for remote lports) > +# For distributed load balancer src_ip is optional when health checks are > not configured. > > check ovn-nbctl ls-add ls1 > +check ovn-nbctl lr-add lr1 > + > +ovn-appctl -t ovn-northd vlog/disable-rate-limit > > check ovn-nbctl lb-add lb1_ipv4 1.1.1.1:80 > 192.168.0.1:10880,192.168.0.2:10880,192.168.0.3:10880 > AT_CHECK([ovn-nbctl --wait=sb \ > @@ -17879,6 +17883,54 @@ check ovn-nbctl set load_balancer lb1_ipv4 > ip_port_mappings:192.168.0.1=lport1:1 > check_row_count sb:Service_Monitor 0 > > OVS_WAIT_UNTIL([grep "Empty AZ name specified" northd/ovn-northd.log]) > + > +check ovn-nbctl lb-del lb1_ipv4 > + > +# Check correct setup of distributed load balancers. > +echo > northd/ovn-northd.log > +check ovn-nbctl lb-add lb_distubuted 1.1.1.1:80 > 192.168.0.1:10880,192.168.0.2:10880 > +check ovn-nbctl lr-lb-add lr1 lb_distubuted > +check ovn-nbctl set load_balancer lb_distubuted options:distributed=true > + > +OVS_WAIT_UNTIL([grep "Proper ip_port_mappings" northd/ovn-northd.log]) > +echo > northd/ovn-northd.log > + > +# Ensure proper ip_port_mappings configuration for all health checks is > required for distributed > +# load balancer functionality. src_ip is optional when health checks are not > configured. > +ovn-sbctl lflow-list lr1 > lr1_lflow > +AT_CHECK([cat lr1_lflow | grep lr_in_dnat | grep priority=120 | > ovn_strip_lflows], [0], [dnl > + table=??(lr_in_dnat ), priority=120 , match=(ct.new && !ct.rel && > ip4 && ip4.dst == 1.1.1.1 && reg1[[16..23]] == 6 && reg1[[0..15]] == 80), > action=(ct_lb_mark(backends=192.168.0.1:10880,192.168.0.2:10880);) > +]) > +ovn-nbctl list load_balancer > + > +check ovn-nbctl set load_balancer lb_distubuted > ip_port_mappings:192.168.0.1=lport1 > +OVS_WAIT_UNTIL([grep "Proper ip_port_mappings" northd/ovn-northd.log]) > +echo > northd/ovn-northd.log > +ovn-sbctl lflow-list lr1 > lr1_lflow > +AT_CHECK([cat lr1_lflow | grep lr_in_dnat | grep priority=120 | > ovn_strip_lflows], [0], [dnl > + table=??(lr_in_dnat ), priority=120 , match=(ct.new && !ct.rel && > ip4 && ip4.dst == 1.1.1.1 && reg1[[16..23]] == 6 && reg1[[0..15]] == 80), > action=(ct_lb_mark(backends=192.168.0.1:10880,192.168.0.2:10880);) > +]) > + > +check ovn-nbctl set load_balancer lb_distubuted > ip_port_mappings:192.168.0.2=lport2 > +ovn-sbctl lflow-list lr1 > lr1_lflow > +AT_CHECK([cat lr1_lflow | grep lr_in_dnat | grep priority=120 | > ovn_strip_lflows], [0], [dnl > + table=??(lr_in_dnat ), priority=120 , match=(ct.new && !ct.rel && > ip4 && ip4.dst == 1.1.1.1 && reg1[[16..23]] == 6 && reg1[[0..15]] == 80), > action=(ct_lb_mark_local(backends="lport1":192.168.0.1:10880,"lport2":192.168.0.2:10880);) > +]) > +echo > northd/ovn-northd.log > + > +# Check if health check is configured, ip_port_mappings must be provided. > +AT_CHECK([ovn-nbctl --wait=sb \ > + -- --id=@hc create Load_Balancer_Health_Check vip="1.1.1.1\:80" \ > + options:failure_count=100 \ > + -- add Load_Balancer lb_distubuted health_check @hc | uuidfilt], > [0], [<0> > +]) > + > +ovn-sbctl lflow-list lr1 > lr1_lflow > +OVS_WAIT_UNTIL([grep "Expected ':' separator for:" northd/ovn-northd.log]) > +AT_CHECK([cat lr1_lflow | grep lr_in_dnat | grep priority=120 | > ovn_strip_lflows], [0], [dnl > + table=??(lr_in_dnat ), priority=120 , match=(ct.new && !ct.rel && > ip4 && ip4.dst == 1.1.1.1 && reg1[[16..23]] == 6 && reg1[[0..15]] == 80), > action=(drop;) > +]) > + > AT_CLEANUP > ]) > > @@ -17940,3 +17992,181 @@ AT_CHECK([grep 'ls_in_l2_unknown' lflows | > ovn_strip_lflows], [0], [dnl > > AT_CLEANUP > ]) > + > +OVN_FOR_EACH_NORTHD_NO_HV([ > +AT_SETUP([Distributed lb: logical-flow test]) > +ovn_start > + > +net_add n1 > +sim_add hv1 > +as hv1 > +ovs-vsctl add-br br-phys > +ovn_attach n1 br-phys 192.168.0.1 > +ovs-vsctl add-port br-int lport1 -- set interface lport1 > external_ids:iface-id=lport1 > + > +net_add n2 > +sim_add hv2 > +as hv2 > +check ovs-vsctl add-br br-phys > +ovn_attach n1 br-phys 192.168.0.2 > +ovs-vsctl add-port br-int lport2 -- set interface lport2 > external_ids:iface-id=lport2 > + > +# If load balancer becomes distributed, it operates in a distributed manner > on > +# separate compute nodes and balances traffic to local backends (located on > that compute node). > +# Test topology: one switch, two hypervisors, to verify that OpenFlow groups > are created > +# only from local backends. Logical_switch is connected to a router, a > distributed load > +# balancer is added on Logical_Router, and router is connected to a switch > which is > +# connected to the physical network. > +check ovn-nbctl ls-add outside > + > +check ovn-nbctl lsp-add outside outside \ > + -- lsp-set-addresses outside unknown \ > + -- lsp-set-type outside localnet > + > +check ovn-nbctl --wait=sb set Logical_Switch_Port outside tag_request=2 > + > +check ovn-nbctl lsp-add outside outside-down \ > + -- lsp-set-type outside-down router \ > + -- lsp-set-addresses outside-down router \ > + -- lsp-set-options outside-down router-port=lr1-up > + > +check ovn-nbctl lr-add lr1 \ > + -- lrp-add lr1 lr1-up 11:11:11:11:11:11 169.254.0.1/24 \ > + -- lrp-add lr1 lr1-down 12:12:12:12:12:12 192.168.0.1/24 > + > +check ovn-nbctl ls-add ls1 \ > + -- lsp-add ls1 lport1 \ > + -- lsp-set-addresses lport1 "13:13:13:13:13:13 192.168.0.101" \ > + -- lsp-add ls1 lport2 \ > + -- lsp-set-addresses lport2 "14:14:14:14:14:14 192.168.0.102" > + > +check ovn-nbctl lsp-add ls1 ls1-up \ > + -- lsp-set-type ls1-up router \ > + -- lsp-set-addresses ls1-up router \ > + -- lsp-set-options ls1-up router-port=lr1-down > + > +check ovn-nbctl --wait=sb sync > +wait_for_ports_up > + > +check ovn-nbctl ha-chassis-group-add gateway > +check ovn-nbctl ha-chassis-group-add-chassis gateway test 1 > +ha_g_uuid=$(fetch_column nb:HA_Chassis_Group _uuid name=gateway) > +lr1_up_uuid=$(fetch_column nb:Logical_Router_Port _uuid name=lr1-up) > +check ovn-nbctl set logical_router_port $lr1_up_uuid > ha_chassis_group=$ha_g_uuid > +check ovn-nbctl --wait=hv sync > + > +check ovn-nbctl lb-add lb1 1.1.1.1:80 192.168.0.101:10880,192.168.0.102:10880 > +check ovn-nbctl set Load_Balancer lb1 > ip_port_mappings:192.168.0.101=lport1:192.168.0.199 > +check ovn-nbctl set Load_Balancer lb1 > ip_port_mappings:192.168.0.102=lport2:192.168.0.199 > +check ovn-nbctl lr-lb-add lr1 lb1 > + > +ovn-sbctl lflow-list lr1 > lr1_lflows_before > +ovn-sbctl lflow-list outside > outside_lflows_before > + > +AT_CHECK([cat outside_lflows_before | grep ls_in_l2_lkup | grep priority=50 > | ovn_strip_lflows], [0], [dnl > + table=??(ls_in_l2_lkup ), priority=50 , match=(eth.dst == > 11:11:11:11:11:11 && is_chassis_resident("cr-lr1-up")), action=(outport = > "outside-down"; output;) > +]) > + > +AT_CHECK([cat lr1_lflows_before | grep lr_in_ip_input | grep priority=90 | > grep 169.254.0.1 | ovn_strip_lflows], [0], [dnl > + table=??(lr_in_ip_input ), priority=90 , match=(inport == "lr1-up" > && arp.op == 1 && arp.tpa == 169.254.0.1 && arp.spa == 169.254.0.0/24 && > is_chassis_resident("cr-lr1-up")), action=(eth.dst = eth.src; eth.src = > xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = > xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; > output;) > + table=??(lr_in_ip_input ), priority=90 , match=(ip4.dst == > 169.254.0.1 && icmp4.type == 8 && icmp4.code == 0), action=(ip4.dst <-> > ip4.src; ip.ttl = 255; icmp4.type = 0; flags.loopback = 1; next; ) > +]) > + > +AT_CHECK([cat lr1_lflows_before | grep lr_in_admission | grep priority=50 | > ovn_strip_lflows], [0], [dnl > + table=??(lr_in_admission ), priority=50 , match=(eth.dst == > 11:11:11:11:11:11 && inport == "lr1-up" && is_chassis_resident("cr-lr1-up")), > action=(xreg0[[0..47]] = 11:11:11:11:11:11; next;) > + table=??(lr_in_admission ), priority=50 , match=(eth.dst == > 12:12:12:12:12:12 && inport == "lr1-down"), action=(xreg0[[0..47]] = > 12:12:12:12:12:12; next;) > + table=??(lr_in_admission ), priority=50 , match=(eth.mcast && inport > == "lr1-down"), action=(xreg0[[0..47]] = 12:12:12:12:12:12; next;) > + table=??(lr_in_admission ), priority=50 , match=(eth.mcast && inport > == "lr1-up"), action=(xreg0[[0..47]] = 11:11:11:11:11:11; next;) > +]) > + > +AT_CHECK([cat lr1_lflows_before | grep lr_in_dnat | grep priority=120 | > ovn_strip_lflows], [0], [dnl > + table=??(lr_in_dnat ), priority=120 , match=(ct.new && !ct.rel && > ip4 && ip4.dst == 1.1.1.1 && reg1[[16..23]] == 6 && reg1[[0..15]] == 80 && > is_chassis_resident("cr-lr1-up")), > action=(ct_lb_mark(backends=192.168.0.101:10880,192.168.0.102:10880);) > +]) > + > +AT_CHECK([cat lr1_lflows_before | grep lr_out_undnat | grep priority=120 | > ovn_strip_lflows], [0], [dnl > + table=??(lr_out_undnat ), priority=120 , match=(ip4 && ((ip4.src == > 192.168.0.101 && tcp.src == 10880) || (ip4.src == 192.168.0.102 && tcp.src == > 10880)) && (inport == "lr1-up" || outport == "lr1-up") && > is_chassis_resident("cr-lr1-up")), action=(ct_dnat;) > +]) > + > +AT_CHECK([cat lr1_lflows_before | grep lr_in_gw_redirect | > ovn_strip_lflows], [0], [dnl > + table=??(lr_in_gw_redirect ), priority=0 , match=(1), action=(next;) > + table=??(lr_in_gw_redirect ), priority=200 , match=(ip4 && ((ip4.src == > 192.168.0.101 && tcp.src == 10880) || (ip4.src == 192.168.0.102 && tcp.src == > 10880)) && outport == "lr1-up"), action=(outport = "cr-lr1-up"; next;) > + table=??(lr_in_gw_redirect ), priority=50 , match=(outport == > "lr1-up"), action=(outport = "cr-lr1-up"; next;) > +]) > + > +# OVN controller currently does not recalculate local datapaths when the > 'ha_chassis_group' > +# changes, so we reboot it. > +as hv1 > +OVS_APP_EXIT_AND_WAIT([ovn-controller]) > +start_daemon ovn-controller > +wait_for_ports_up > + > +AT_CHECK([ovn-appctl -t ovn-controller debug/dump-local-datapaths], [0], [dnl > +Local datapaths: > +Datapath: lr1, type: router > +Datapath: ls1, type: switch > +]) > + > +check ovn-nbctl set load_balancer lb1 options:distributed=true > +check ovn-nbctl --wait=hv sync > + > +# Check that flows for router's distributed gw port are now distributed. > +ovn-sbctl lflow-list outside > outside_lflows_after > +AT_CHECK([cat outside_lflows_after | grep ls_in_l2_lkup | grep priority=50 | > ovn_strip_lflows], [0], [dnl > + table=??(ls_in_l2_lkup ), priority=50 , match=(eth.dst == > 11:11:11:11:11:11), action=(outport = "outside-down"; output;) > +]) > + > +ovn-sbctl lflow-list lr1 > lr1_lflows_after > + > +AT_CHECK([cat lr1_lflows_after | grep lr_in_ip_input | grep priority=90 | > grep 169.254.0.1 | ovn_strip_lflows], [0], [dnl > + table=??(lr_in_ip_input ), priority=90 , match=(inport == "lr1-up" > && arp.op == 1 && arp.tpa == 169.254.0.1 && arp.spa == 169.254.0.0/24), > action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply > */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport > = inport; flags.loopback = 1; output;) > + table=??(lr_in_ip_input ), priority=90 , match=(ip4.dst == > 169.254.0.1 && icmp4.type == 8 && icmp4.code == 0), action=(ip4.dst <-> > ip4.src; ip.ttl = 255; icmp4.type = 0; flags.loopback = 1; next; ) > +]) > + > +AT_CHECK([cat lr1_lflows_after | grep lr_in_admission | grep priority=50 | > ovn_strip_lflows], [0], [dnl > + table=??(lr_in_admission ), priority=50 , match=(eth.dst == > 11:11:11:11:11:11 && inport == "lr1-up"), action=(xreg0[[0..47]] = > 11:11:11:11:11:11; next;) > + table=??(lr_in_admission ), priority=50 , match=(eth.dst == > 12:12:12:12:12:12 && inport == "lr1-down"), action=(xreg0[[0..47]] = > 12:12:12:12:12:12; next;) > + table=??(lr_in_admission ), priority=50 , match=(eth.mcast && inport > == "lr1-down"), action=(xreg0[[0..47]] = 12:12:12:12:12:12; next;) > + table=??(lr_in_admission ), priority=50 , match=(eth.mcast && inport > == "lr1-up"), action=(xreg0[[0..47]] = 11:11:11:11:11:11; next;) > +]) > + > +# Check that load balancer flows are now also distributed. > +AT_CHECK([cat lr1_lflows_after | grep lr_in_dnat | grep priority=120 | > ovn_strip_lflows], [0], [dnl > + table=??(lr_in_dnat ), priority=120 , match=(ct.new && !ct.rel && > ip4 && ip4.dst == 1.1.1.1 && reg1[[16..23]] == 6 && reg1[[0..15]] == 80), > action=(ct_lb_mark_local(backends="lport1":192.168.0.101:10880,"lport2":192.168.0.102:10880);) > +]) > + > +AT_CHECK([cat lr1_lflows_after | grep lr_out_undnat | grep priority=120 | > ovn_strip_lflows], [0], [dnl > + table=??(lr_out_undnat ), priority=120 , match=(ip4 && ((ip4.src == > 192.168.0.101 && tcp.src == 10880) || (ip4.src == 192.168.0.102 && tcp.src == > 10880)) && (inport == "lr1-up" || outport == "lr1-up")), action=(ct_dnat;) > +]) > + > +# Check that distributed load balancers do not redirect traffic to gateway > port. > +AT_CHECK([cat lr1_lflows_after | grep lr_in_gw_redirect | ovn_strip_lflows], > [0], [dnl > + table=??(lr_in_gw_redirect ), priority=0 , match=(1), action=(next;) > + table=??(lr_in_gw_redirect ), priority=200 , match=(ip4 && ((ip4.src == > 192.168.0.101 && tcp.src == 10880) || (ip4.src == 192.168.0.102 && tcp.src == > 10880)) && outport == "lr1-up"), action=(outport = "lr1-up"; next;) > + table=??(lr_in_gw_redirect ), priority=50 , match=(outport == > "lr1-up"), action=(outport = "cr-lr1-up"; next;) > +]) > + > +# Check that external switch has been added to local datapaths on compute > nodes > +# when 'distributed' option is enabled on load balancer. > +as hv1 > +AT_CHECK([ovn-appctl -t ovn-controller debug/dump-local-datapaths], [0], [dnl > +Local datapaths: > +Datapath: outside, type: switch > +Datapath: lr1, type: router > +Datapath: ls1, type: switch > +]) > + > +as hv1 > +# Verify that OpenFlow groups were created only for local backends. > +AT_CHECK([ovs-ofctl dump-groups br-int | sed -e > 's/table=[[0-9]]*/table=<cleared>/'], [0], [dnl > +NXST_GROUP_DESC reply (xid=0x2): > + > group_id=1,type=select,selection_method=dp_hash,bucket=bucket_id:0,weight:100,actions=ct(commit,table=<cleared>,zone=NXM_NX_REG11[[0..15]],nat(dst=192.168.0.101:10880),exec(load:0x1->NXM_NX_CT_MARK[[1]])) > +]) > + > +as hv2 > +AT_CHECK([ovs-ofctl dump-groups br-int | sed -e > 's/table=[[0-9]]*/table=<cleared>/'], [0], [dnl > +NXST_GROUP_DESC reply (xid=0x2): > + > group_id=1,type=select,selection_method=dp_hash,bucket=bucket_id:1,weight:100,actions=ct(commit,table=<cleared>,zone=NXM_NX_REG11[[0..15]],nat(dst=192.168.0.102:10880),exec(load:0x1->NXM_NX_CT_MARK[[1]])) > +]) > + > +AT_CLEANUP > +]) > \ No newline at end of file > diff --git a/tests/system-ovn.at b/tests/system-ovn.at > index 8e356df6f..5e2ae9ee9 100644 > --- a/tests/system-ovn.at > +++ b/tests/system-ovn.at > @@ -18484,3 +18484,86 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port > patch-.*/d > /connection dropped.*/d"]) > AT_CLEANUP > ]) > + > +OVN_FOR_EACH_NORTHD([ > +AT_SETUP([Distributed lb: system test]) > +AT_KEYWORDS([ovnlb]) > + > +# Simple test for basic functionality verification > +# topology: > +# client - br-ext - br-int - outside-switch - lr - backend > +# test case: > +# 1. create a centralized load balancer, specifying gateway chassis for > router. > +# 2. enable distributed option on load balancer. > +# 3. change the gateway to a non-existent one - we expect the distributed > load balancer to continue working. > + > +CHECK_CONNTRACK() > +CHECK_CONNTRACK_NAT() > + > +ovn_start > +OVS_TRAFFIC_VSWITCHD_START() > +ADD_BR([br-int]) > +ADD_BR([br-ext]) > + > +check ovs-ofctl add-flow br-ext action=normal > +# Set external-ids in br-int needed for ovn-controller > +check ovs-vsctl \ > + -- set Open_vSwitch . external-ids:system-id=hv1 \ > + -- set Open_vSwitch . > external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ > + -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ > + -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ > + -- set bridge br-int fail-mode=secure > other-config:disable-in-band=true \ > + -- set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext > + > +# Start ovn-controller > +start_daemon ovn-controller > + > +check ovn-nbctl lr-add lr > +check ovn-nbctl ls-add internal > +check ovn-nbctl ls-add public > + > +check ovn-nbctl lrp-add lr lr-pub 00:00:01:01:02:03 192.168.100.1/24 > 1000::1/64 > +check ovn-nbctl lsp-add public pub-lr -- set Logical_Switch_Port pub-lr \ > + type=router options:router-port=lr-pub addresses=\"00:00:01:01:02:03\" > + > +check ovn-nbctl lrp-add lr lr-internal 00:00:01:01:02:04 192.168.200.1/24 > 2000::1/64 > +check ovn-nbctl lsp-add internal internal-lr -- set Logical_Switch_Port > internal-lr \ > + type=router options:router-port=lr-internal > addresses=\"00:00:01:01:02:04\" > + > +check ovn-nbctl lsp-add internal server -- lsp-set-addresses server "unknown" > + > +check ovn-nbctl lsp-add public ln_port \ > + -- lsp-set-addresses ln_port unknown \ > + -- lsp-set-type ln_port localnet \ > + -- lsp-set-options ln_port network_name=phynet > + > +check ovn-nbctl set logical_router lr options:chassis=hv1 > + > +check ovn-nbctl lb-add lb1 192.168.100.20:80 192.168.200.10:10880 > +check ovn-nbctl lr-lb-add lr lb1 > +check ovn-nbctl --wait=hv sync > + > +ADD_NAMESPACES(client) > +ADD_VETH(client, client, br-ext, "192.168.100.10/24", "f0:00:00:01:02:03", \ > + "192.168.100.1") > + > +ADD_NAMESPACES(server) > +ADD_VETH(server, server, br-int, "192.168.200.10/24", "f0:00:0f:01:02:03", \ > + "192.168.200.1") > + > +NETNS_DAEMONIZE([server], [nc -l -u 192.168.200.10 10880 > /dev/null], > [serverv4.pid]) > + > +NS_CHECK_EXEC([client], [nc -z -u 192.168.100.20 80], [ignore], [ignore], > [ignore]) > + > +check ovn-nbctl set load_balancer lb1 options:distributed=true > +check ovn-nbctl set Load_Balancer lb1 ip_port_mappings:192.168.200.10=lport1 > +check ovn-nbctl --wait=hv sync > + > +check ovn-nbctl set logical_router lr options:chassis=hv2 > + > +NS_CHECK_EXEC([client], [nc -z -u 192.168.100.20 80], [ignore], [ignore], > [ignore]) > + > +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d > +/connection dropped.*/d"]) > +AT_CLEANUP > +]) > \ No newline at end of file > diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c > index 9d9f915da..f288c8a8f 100644 > --- a/utilities/ovn-trace.c > +++ b/utilities/ovn-trace.c > @@ -3590,6 +3590,8 @@ trace_actions(const struct ovnact *ovnacts, size_t > ovnacts_len, > case OVNACT_CT_STATE_SAVE: > execute_ct_save_state(ovnact_get_CT_STATE_SAVE(a), uflow, super); > break; > + case OVNACT_CT_LB_MARK_LOCAL: > + break; > } > } > ofpbuf_uninit(&stack); _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
