Message ID | 166912649398.709554.4258484145133098747.stgit@dceara.remote.csb |
---|---|
State | Accepted |
Headers | show |
Series | Add OVN component templates. | expand |
Context | Check | Description |
---|---|---|
ovsrobot/apply-robot | success | apply and check: success |
ovsrobot/github-robot-_Build_and_Test | success | github build: passed |
ovsrobot/github-robot-_ovn-kubernetes | fail | github build: failed |
On Tue, Nov 22, 2022 at 6:15 AM Dumitru Ceara <dceara@redhat.com> wrote: > > 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 Sorry if I missed, but I didn't see any tests that test the form "^backends_variable1,^backends_variable2". I only see tests with a single backend variable with a single IP in it. Better to test: 1. Multiple backends variables 2. Multiple IPs in a single variable (I saw this in the tutorial in patch 5, but better to be covered here, too) > > 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. > > Template load balancers don't support the "reachable" neighbor-responder > mode. Instead the CMS can explicitly configure the responder mode to > either "all" or "none". > > To properly handle template updates in ovn-controller we also add a > Chassis_Template_Var <- LB reference in ovn-controller. This way, when > a Chassis_Template_Var changes value all load balancers that refer to > it will also get updated. > > Signed-off-by: Dumitru Ceara <dceara@redhat.com> > --- > V3: > - Addressed Mark's comments: > - Added TODO items about potential future template LB improvements. > - Removed n_backends arg from ovn_lb_backends_init_explicit() and > ovn_lb_backends_init_template(). > - Fixed ovn_northd_lb_create() to first get the template option before > using its value. > - Fixed up comments and man pages. > - Hardenned setting of address family in ovn-nbctl. > > V2: > - Fix GCC build due to missing explicit return. > - Fix ls_in_pre_stateful flows due to using wrong lb field. > - Use new lexer_parse_template_string(). > - Changed lb_handle_changed_ref() signature to return bool. > - Update documentation with info about responder mode=none, LB template > supported formats, lb explicit address family requirements. > - Squashed the template LB patches into a single one > - Added more tests. > - Squashed the system tests patch into this one. > --- > TODO.rst | 7 + > controller/lflow.c | 115 +++++++++-- > controller/lflow.h | 7 + > controller/ovn-controller.c | 67 +++++- > lib/lb.c | 452 ++++++++++++++++++++++++++++++++++++++----- > lib/lb.h | 40 +++- > lib/ovn-util.c | 3 > northd/northd.c | 89 ++++---- > ovn-nb.xml | 62 ++++++ > tests/ovn-nbctl.at | 23 +- > tests/ovn-northd.at | 7 + > tests/ovn.at | 131 ++++++++++++ > tests/system-ovn.at | 183 +++++++++++++++++ > utilities/ovn-nbctl.c | 120 ++++++----- > 14 files changed, 1078 insertions(+), 228 deletions(-) > > diff --git a/TODO.rst b/TODO.rst > index fe5f9a2f30..53cf2870b2 100644 > --- a/TODO.rst > +++ b/TODO.rst > @@ -183,3 +183,10 @@ OVN To-do List > * Chassis_Template_Var > > * Support template variables when tracing packets with ovn-trace. > + > +* Load Balancer templates > + > + * Support combining the VIP (or backend) IP and port into a single > + template variable. Is it still a TODO for backends? At least the tutorial test (in patch 5) is already doing something like this: backends0="42.0.0.1:1,42.1.0.1:1,42.2.0.1:1,42.3.0.1:1,42.4.0.1:1" > + > + * Support combining all backends into a single template variable. What does it mean here? Isn't the tutorial test already combining multiple backends into a single variable? > diff --git a/controller/lflow.c b/controller/lflow.c > index 84625fb3f1..f6ac639541 100644 > --- a/controller/lflow.c > +++ b/controller/lflow.c > @@ -97,6 +97,15 @@ consider_logical_flow(const struct sbrec_logical_flow *lflow, > struct lflow_ctx_in *l_ctx_in, > struct lflow_ctx_out *l_ctx_out); > > +static void > +consider_lb_hairpin_flows(struct objdep_mgr *mgr, > + const struct sbrec_load_balancer *sbrec_lb, > + const struct hmap *local_datapaths, > + const struct smap *template_vars, > + bool use_ct_mark, > + struct ovn_desired_flow_table *flow_table, > + struct simap *ids); > + > static void add_port_sec_flows(const struct shash *binding_lports, > const struct sbrec_chassis *, > struct ovn_desired_flow_table *); > @@ -223,7 +232,7 @@ lflow_handle_changed_flows(struct lflow_ctx_in *l_ctx_in, > UUIDSET_INITIALIZER(&flood_remove_nodes); > SBREC_LOGICAL_FLOW_TABLE_FOR_EACH_TRACKED (lflow, > l_ctx_in->logical_flow_table) { > - if (uuidset_find(l_ctx_out->lflows_processed, &lflow->header_.uuid)) { > + if (uuidset_find(l_ctx_out->objs_processed, &lflow->header_.uuid)) { > VLOG_DBG("lflow "UUID_FMT"has been processed, skip.", > UUID_ARGS(&lflow->header_.uuid)); > continue; > @@ -253,14 +262,14 @@ lflow_handle_changed_flows(struct lflow_ctx_in *l_ctx_in, > UUID_ARGS(&lflow->header_.uuid)); > > /* For the extra lflows that need to be reprocessed because of the > - * flood remove, remove it from lflows_processed. */ > + * flood remove, remove it from objs_processed. */ > struct uuidset_node *unode = > - uuidset_find(l_ctx_out->lflows_processed, > + uuidset_find(l_ctx_out->objs_processed, > &lflow->header_.uuid); > if (unode) { > VLOG_DBG("lflow "UUID_FMT"has been processed, now reprocess.", > UUID_ARGS(&lflow->header_.uuid)); > - uuidset_delete(l_ctx_out->lflows_processed, unode); > + uuidset_delete(l_ctx_out->objs_processed, unode); > } > > consider_logical_flow(lflow, false, l_ctx_in, l_ctx_out); > @@ -687,7 +696,7 @@ lflow_handle_addr_set_update(const char *as_name, > struct object_to_resources_list_node *resource_list_node; > RESOURCE_FOR_EACH_OBJ (resource_list_node, resource_node) { > const struct uuid *obj_uuid = &resource_list_node->obj_uuid; > - if (uuidset_find(l_ctx_out->lflows_processed, obj_uuid)) { > + if (uuidset_find(l_ctx_out->objs_processed, obj_uuid)) { > VLOG_DBG("lflow "UUID_FMT"has been processed, skip.", > UUID_ARGS(obj_uuid)); > continue; > @@ -777,13 +786,13 @@ lflow_handle_changed_ref(enum objdep_type type, const char *res_name, > } > > /* For the extra lflows that need to be reprocessed because of the > - * flood remove, remove it from lflows_processed. */ > + * flood remove, remove it from objs_processed. */ > struct uuidset_node *unode = > - uuidset_find(l_ctx_out->lflows_processed, &lflow->header_.uuid); > + uuidset_find(l_ctx_out->objs_processed, &lflow->header_.uuid); > if (unode) { > VLOG_DBG("lflow "UUID_FMT"has been processed, now reprocess.", > UUID_ARGS(&lflow->header_.uuid)); > - uuidset_delete(l_ctx_out->lflows_processed, unode); > + uuidset_delete(l_ctx_out->objs_processed, unode); > } > > consider_logical_flow(lflow, false, l_ctx_in, l_ctx_out); > @@ -792,6 +801,43 @@ lflow_handle_changed_ref(enum objdep_type type, const char *res_name, > return true; > } > > +bool > +lb_handle_changed_ref(enum objdep_type type, const char *res_name, > + struct ovs_list *objs_todo, > + const void *in_arg, void *out_arg) > +{ > + struct lflow_ctx_in *l_ctx_in = CONST_CAST(struct lflow_ctx_in *, in_arg); > + struct lflow_ctx_out *l_ctx_out = out_arg; > + > + struct object_to_resources_list_node *resource_lb_uuid; > + LIST_FOR_EACH_POP (resource_lb_uuid, list_node, objs_todo) { > + VLOG_DBG("Reprocess LB "UUID_FMT" for resource type: %s, name: %s", > + UUID_ARGS(&resource_lb_uuid->obj_uuid), > + objdep_type_name(type), res_name); > + > + const struct sbrec_load_balancer *lb = > + sbrec_load_balancer_table_get_for_uuid( > + l_ctx_in->lb_table, &resource_lb_uuid->obj_uuid); > + if (!lb) { > + VLOG_DBG("Failed to find LB "UUID_FMT" referred by: %s", nit: I think it should be: Failed to find LB ... that refers ... > + UUID_ARGS(&resource_lb_uuid->obj_uuid), res_name); > + } else { > + ofctrl_remove_flows(l_ctx_out->flow_table, > + &resource_lb_uuid->obj_uuid); > + > + consider_lb_hairpin_flows(l_ctx_out->lb_deps_mgr, 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); > + } > + > + free(resource_lb_uuid); > + } > + return true; > +} > + > static void > lflow_parse_ctrl_meter(const struct sbrec_logical_flow *lflow, > struct ovn_extend_table *meter_table, > @@ -1259,9 +1305,9 @@ consider_logical_flow(const struct sbrec_logical_flow *lflow, > > COVERAGE_INC(consider_logical_flow); > if (!is_recompute) { > - ovs_assert(!uuidset_find(l_ctx_out->lflows_processed, > + ovs_assert(!uuidset_find(l_ctx_out->objs_processed, > &lflow->header_.uuid)); > - uuidset_insert(l_ctx_out->lflows_processed, &lflow->header_.uuid); > + uuidset_insert(l_ctx_out->objs_processed, &lflow->header_.uuid); > } > > if (dp) { > @@ -2001,8 +2047,10 @@ add_lb_ct_snat_hairpin_flows(struct ovn_controller_lb *lb, > } > > static void > -consider_lb_hairpin_flows(const struct sbrec_load_balancer *sbrec_lb, > +consider_lb_hairpin_flows(struct objdep_mgr *mgr, > + const struct sbrec_load_balancer *sbrec_lb, > const struct hmap *local_datapaths, > + const struct smap *template_vars, > bool use_ct_mark, > struct ovn_desired_flow_table *flow_table, > struct simap *ids) > @@ -2039,7 +2087,9 @@ consider_lb_hairpin_flows(const struct sbrec_load_balancer *sbrec_lb, > return; > } > > - struct ovn_controller_lb *lb = ovn_controller_lb_create(sbrec_lb); > + struct sset template_vars_ref = SSET_INITIALIZER(&template_vars_ref); > + struct ovn_controller_lb *lb = > + ovn_controller_lb_create(sbrec_lb, template_vars, &template_vars_ref); > uint8_t lb_proto = IPPROTO_TCP; > if (lb->slb->protocol && lb->slb->protocol[0]) { > if (!strcmp(lb->slb->protocol, "udp")) { > @@ -2049,6 +2099,11 @@ consider_lb_hairpin_flows(const struct sbrec_load_balancer *sbrec_lb, > } > } > > + const char *tv_name; > + SSET_FOR_EACH (tv_name, &template_vars_ref) { > + objdep_mgr_add(mgr, OBJDEP_TYPE_TEMPLATE, tv_name, > + &sbrec_lb->header_.uuid); > + } > for (i = 0; i < lb->n_vips; i++) { > struct ovn_lb_vip *lb_vip = &lb->vips[i]; > > @@ -2063,13 +2118,17 @@ consider_lb_hairpin_flows(const struct sbrec_load_balancer *sbrec_lb, > add_lb_ct_snat_hairpin_flows(lb, id, lb_proto, flow_table); > > ovn_controller_lb_destroy(lb); > + sset_destroy(&template_vars_ref); > } > > /* Adds OpenFlow flows to flow tables for each Load balancer VIPs and > * 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, > +add_lb_hairpin_flows(struct objdep_mgr *mgr, > + const struct sbrec_load_balancer_table *lb_table, > + 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) > @@ -2092,8 +2151,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(mgr, lb, local_datapaths, template_vars, > + use_ct_mark, flow_table, ids); > } > } > > @@ -2229,7 +2288,9 @@ lflow_run(struct lflow_ctx_in *l_ctx_in, struct lflow_ctx_out *l_ctx_out) > l_ctx_in->static_mac_binding_table, > l_ctx_in->local_datapaths, > l_ctx_out->flow_table); > - add_lb_hairpin_flows(l_ctx_in->lb_table, l_ctx_in->local_datapaths, > + add_lb_hairpin_flows(l_ctx_out->lb_deps_mgr, 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, > @@ -2280,10 +2341,10 @@ lflow_add_flows_for_datapath(const struct sbrec_datapath_binding *dp, > const struct sbrec_logical_flow *lflow; > SBREC_LOGICAL_FLOW_FOR_EACH_EQUAL ( > lflow, lf_row, l_ctx_in->sbrec_logical_flow_by_logical_datapath) { > - if (uuidset_find(l_ctx_out->lflows_processed, &lflow->header_.uuid)) { > + if (uuidset_find(l_ctx_out->objs_processed, &lflow->header_.uuid)) { > continue; > } > - uuidset_insert(l_ctx_out->lflows_processed, &lflow->header_.uuid); > + uuidset_insert(l_ctx_out->objs_processed, &lflow->header_.uuid); > consider_logical_flow__(lflow, dp, l_ctx_in, l_ctx_out); > } > sbrec_logical_flow_index_destroy_row(lf_row); > @@ -2308,7 +2369,7 @@ lflow_add_flows_for_datapath(const struct sbrec_datapath_binding *dp, > sbrec_logical_flow_index_set_logical_dp_group(lf_row, ldpg); > SBREC_LOGICAL_FLOW_FOR_EACH_EQUAL ( > lflow, lf_row, l_ctx_in->sbrec_logical_flow_by_logical_dp_group) { > - if (uuidset_find(l_ctx_out->lflows_processed, > + if (uuidset_find(l_ctx_out->objs_processed, > &lflow->header_.uuid)) { > continue; > } > @@ -2360,7 +2421,9 @@ lflow_add_flows_for_datapath(const struct sbrec_datapath_binding *dp, > /* Add load balancer hairpin flows if the datapath has any load balancers > * associated. */ > for (size_t i = 0; i < n_dp_lbs; i++) { > - consider_lb_hairpin_flows(dp_lbs[i], l_ctx_in->local_datapaths, > + consider_lb_hairpin_flows(l_ctx_out->lb_deps_mgr, 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); > @@ -2382,7 +2445,7 @@ lflow_handle_flows_for_lport(const struct sbrec_port_binding *pb, > OBJDEP_TYPE_PORTBINDING, > pb->logical_port, > lflow_handle_changed_ref, > - l_ctx_out->lflows_processed, > + l_ctx_out->objs_processed, > l_ctx_in, l_ctx_out, &changed)) { > return false; > } > @@ -2421,7 +2484,7 @@ lflow_handle_changed_port_bindings(struct lflow_ctx_in *l_ctx_in, > OBJDEP_TYPE_PORTBINDING, > pb->logical_port, > lflow_handle_changed_ref, > - l_ctx_out->lflows_processed, > + l_ctx_out->objs_processed, > l_ctx_in, l_ctx_out, &changed)) { > ret = false; > break; > @@ -2448,7 +2511,7 @@ lflow_handle_changed_mc_groups(struct lflow_ctx_in *l_ctx_in, > if (!objdep_mgr_handle_change(l_ctx_out->lflow_deps_mgr, > OBJDEP_TYPE_MC_GROUP, ds_cstr(&mg_key), > lflow_handle_changed_ref, > - l_ctx_out->lflows_processed, > + l_ctx_out->objs_processed, > l_ctx_in, l_ctx_out, &changed)) { > ret = false; > break; > @@ -2502,7 +2565,9 @@ 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, > + consider_lb_hairpin_flows(l_ctx_out->lb_deps_mgr, 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/controller/lflow.h b/controller/lflow.h > index d95fd41142..9e8f9afd33 100644 > --- a/controller/lflow.h > +++ b/controller/lflow.h > @@ -122,9 +122,10 @@ struct lflow_ctx_out { > struct ovn_extend_table *group_table; > struct ovn_extend_table *meter_table; > struct objdep_mgr *lflow_deps_mgr; > + struct objdep_mgr *lb_deps_mgr; > struct lflow_cache *lflow_cache; > struct conj_ids *conj_ids; > - struct uuidset *lflows_processed; > + struct uuidset *objs_processed; > struct simap *hairpin_lb_ids; > struct id_pool *hairpin_id_pool; > }; > @@ -174,4 +175,8 @@ bool lflow_handle_changed_mc_groups(struct lflow_ctx_in *, > struct lflow_ctx_out *); > bool lflow_handle_changed_port_bindings(struct lflow_ctx_in *, > struct lflow_ctx_out *); > + > +bool lb_handle_changed_ref(enum objdep_type type, const char *res_name, > + struct ovs_list *objs_todo, > + const void *in_arg, void *out_arg); > #endif /* controller/lflow.h */ > diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c > index f9ed0e3855..9807ecd8eb 100644 > --- a/controller/ovn-controller.c > +++ b/controller/ovn-controller.c > @@ -2791,13 +2791,15 @@ struct ed_type_lflow_output { > struct ovn_extend_table meter_table; > /* lflow <-> resource cross reference */ > struct objdep_mgr lflow_deps_mgr;; > + /* load balancer <-> resource cross reference */ > + struct objdep_mgr lb_deps_mgr; > /* conjunciton ID usage information of lflows */ > struct conj_ids conj_ids; > > - /* lflows processed in the current engine execution. > + /* objects (lflows and lbs) processed in the current engine execution. > * Cleared by en_lflow_output_clear_tracked_data before each engine > * execution. */ > - struct uuidset lflows_processed; > + struct uuidset objs_processed; > > /* Data which is persistent and not cleared during > * full recompute. */ > @@ -2954,8 +2956,9 @@ init_lflow_ctx(struct engine_node *node, > l_ctx_out->group_table = &fo->group_table; > l_ctx_out->meter_table = &fo->meter_table; > l_ctx_out->lflow_deps_mgr = &fo->lflow_deps_mgr; > + l_ctx_out->lb_deps_mgr = &fo->lb_deps_mgr; > l_ctx_out->conj_ids = &fo->conj_ids; > - l_ctx_out->lflows_processed = &fo->lflows_processed; > + l_ctx_out->objs_processed = &fo->objs_processed; > l_ctx_out->lflow_cache = fo->pd.lflow_cache; > l_ctx_out->hairpin_id_pool = fo->hd.pool; > l_ctx_out->hairpin_lb_ids = &fo->hd.ids; > @@ -2970,8 +2973,9 @@ en_lflow_output_init(struct engine_node *node OVS_UNUSED, > ovn_extend_table_init(&data->group_table); > ovn_extend_table_init(&data->meter_table); > objdep_mgr_init(&data->lflow_deps_mgr); > + objdep_mgr_init(&data->lb_deps_mgr); > lflow_conj_ids_init(&data->conj_ids); > - uuidset_init(&data->lflows_processed); > + uuidset_init(&data->objs_processed); > simap_init(&data->hd.ids); > data->hd.pool = id_pool_create(1, UINT32_MAX - 1); > nd_ra_opts_init(&data->nd_ra_opts); > @@ -2983,7 +2987,7 @@ static void > en_lflow_output_clear_tracked_data(void *data) > { > struct ed_type_lflow_output *flow_output_data = data; > - uuidset_clear(&flow_output_data->lflows_processed); > + uuidset_clear(&flow_output_data->objs_processed); > } > > static void > @@ -2994,8 +2998,9 @@ en_lflow_output_cleanup(void *data) > ovn_extend_table_destroy(&flow_output_data->group_table); > ovn_extend_table_destroy(&flow_output_data->meter_table); > objdep_mgr_destroy(&flow_output_data->lflow_deps_mgr); > + objdep_mgr_destroy(&flow_output_data->lb_deps_mgr); > lflow_conj_ids_destroy(&flow_output_data->conj_ids); > - uuidset_destroy(&flow_output_data->lflows_processed); > + uuidset_destroy(&flow_output_data->objs_processed); > lflow_cache_destroy(flow_output_data->pd.lflow_cache); > simap_destroy(&flow_output_data->hd.ids); > id_pool_destroy(flow_output_data->hd.pool); > @@ -3030,6 +3035,7 @@ en_lflow_output_run(struct engine_node *node, void *data) > struct ovn_extend_table *group_table = &fo->group_table; > struct ovn_extend_table *meter_table = &fo->meter_table; > struct objdep_mgr *lflow_deps_mgr = &fo->lflow_deps_mgr; > + struct objdep_mgr *lb_deps_mgr = &fo->lb_deps_mgr; > > static bool first_run = true; > if (first_run) { > @@ -3039,6 +3045,7 @@ en_lflow_output_run(struct engine_node *node, void *data) > ovn_extend_table_clear(group_table, false /* desired */); > ovn_extend_table_clear(meter_table, false /* desired */); > objdep_mgr_clear(lflow_deps_mgr); > + objdep_mgr_clear(lb_deps_mgr); > lflow_conj_ids_clear(&fo->conj_ids); > } > > @@ -3172,7 +3179,7 @@ lflow_output_addr_sets_handler(struct engine_node *node, void *data) > if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, > OBJDEP_TYPE_ADDRSET, ref_name, > lflow_handle_changed_ref, > - l_ctx_out.lflows_processed, > + l_ctx_out.objs_processed, > &l_ctx_in, &l_ctx_out, &changed)) { > return false; > } > @@ -3191,7 +3198,7 @@ lflow_output_addr_sets_handler(struct engine_node *node, void *data) > OBJDEP_TYPE_ADDRSET, > shash_node->name, > lflow_handle_changed_ref, > - l_ctx_out.lflows_processed, > + l_ctx_out.objs_processed, > &l_ctx_in, &l_ctx_out, &changed)) { > return false; > } > @@ -3204,7 +3211,7 @@ lflow_output_addr_sets_handler(struct engine_node *node, void *data) > if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, > OBJDEP_TYPE_ADDRSET, ref_name, > lflow_handle_changed_ref, > - l_ctx_out.lflows_processed, > + l_ctx_out.objs_processed, > &l_ctx_in, &l_ctx_out, &changed)) { > return false; > } > @@ -3239,7 +3246,7 @@ lflow_output_port_groups_handler(struct engine_node *node, void *data) > if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, > OBJDEP_TYPE_PORTGROUP, ref_name, > lflow_handle_changed_ref, > - l_ctx_out.lflows_processed, > + l_ctx_out.objs_processed, > &l_ctx_in, &l_ctx_out, &changed)) { > return false; > } > @@ -3251,7 +3258,7 @@ lflow_output_port_groups_handler(struct engine_node *node, void *data) > if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, > OBJDEP_TYPE_PORTGROUP, ref_name, > lflow_handle_changed_ref, > - l_ctx_out.lflows_processed, > + l_ctx_out.objs_processed, > &l_ctx_in, &l_ctx_out, &changed)) { > return false; > } > @@ -3263,7 +3270,7 @@ lflow_output_port_groups_handler(struct engine_node *node, void *data) > if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, > OBJDEP_TYPE_PORTGROUP, ref_name, > lflow_handle_changed_ref, > - l_ctx_out.lflows_processed, > + l_ctx_out.objs_processed, > &l_ctx_in, &l_ctx_out, &changed)) { > return false; > } > @@ -3297,7 +3304,17 @@ lflow_output_template_vars_handler(struct engine_node *node, void *data) > if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, > OBJDEP_TYPE_TEMPLATE, > res_name, lflow_handle_changed_ref, > - l_ctx_out.lflows_processed, > + l_ctx_out.objs_processed, > + &l_ctx_in, &l_ctx_out, &changed)) { > + return false; > + } > + if (changed) { > + engine_set_node_state(node, EN_UPDATED); > + } > + if (!objdep_mgr_handle_change(l_ctx_out.lb_deps_mgr, > + OBJDEP_TYPE_TEMPLATE, > + res_name, lb_handle_changed_ref, > + l_ctx_out.objs_processed, > &l_ctx_in, &l_ctx_out, &changed)) { > return false; > } > @@ -3309,7 +3326,17 @@ lflow_output_template_vars_handler(struct engine_node *node, void *data) > if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, > OBJDEP_TYPE_TEMPLATE, > res_name, lflow_handle_changed_ref, > - l_ctx_out.lflows_processed, > + l_ctx_out.objs_processed, > + &l_ctx_in, &l_ctx_out, &changed)) { > + return false; > + } > + if (changed) { > + engine_set_node_state(node, EN_UPDATED); > + } > + if (!objdep_mgr_handle_change(l_ctx_out.lb_deps_mgr, > + OBJDEP_TYPE_TEMPLATE, > + res_name, lb_handle_changed_ref, > + l_ctx_out.objs_processed, > &l_ctx_in, &l_ctx_out, &changed)) { > return false; > } > @@ -3321,7 +3348,17 @@ lflow_output_template_vars_handler(struct engine_node *node, void *data) > if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, > OBJDEP_TYPE_TEMPLATE, > res_name, lflow_handle_changed_ref, > - l_ctx_out.lflows_processed, > + l_ctx_out.objs_processed, > + &l_ctx_in, &l_ctx_out, &changed)) { > + return false; > + } > + if (changed) { > + engine_set_node_state(node, EN_UPDATED); > + } > + if (!objdep_mgr_handle_change(l_ctx_out.lb_deps_mgr, > + OBJDEP_TYPE_TEMPLATE, > + res_name, lb_handle_changed_ref, > + l_ctx_out.objs_processed, > &l_ctx_in, &l_ctx_out, &changed)) { > return false; > } > diff --git a/lib/lb.c b/lib/lb.c > index c08ccceda1..43628bba77 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,94 +82,293 @@ 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) > { > - int addr_family; > - > - if (!ip_address_and_port_from_lb_key(lb_key, &lb_vip->vip_str, > - &lb_vip->vip, &lb_vip->vip_port, > - &addr_family)) { > - return false; > - } > - > - /* 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; > + lb_vip->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 (lb_vip->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[lb_vip->n_backends]; > int backend_addr_family; > if (!ip_address_and_port_from_lb_key(token, &backend->ip_str, > &backend->ip, &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; > } > > - n_backends++; > + 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; > + lb_vip->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, &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; > + > + return ovn_lb_backends_init_explicit(lb_vip, lb_value); > +} > + > +/* 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_) > +{ > + struct ds errors = DS_EMPTY_INITIALIZER; > + char *value = xstrdup(value_); > + char *save_ptr = NULL; > + size_t n_allocated_backends = 0; > + lb_vip->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 (lb_vip->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[lb_vip->n_backends]; > + lb_backend->ip_str = backend_ip; > + lb_backend->port_str = backend_port; > + lb_backend->port = 0; > + lb_vip->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); > +} > + > +/* 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 > @@ -189,12 +399,113 @@ 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; > + } > + return false; > +} > + > +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 template = smap_get_bool(&nbrec_lb->options, "template", false); > 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"; > @@ -202,12 +513,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, 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 = template; > + > + const char *mode = smap_get(&nbrec_lb->options, "neighbor_responder"); > + lb->neigh_mode = ovn_lb_get_neigh_mode(nbrec_lb, mode, template); > + > uint32_t affinity_timeout = > smap_get_uint(&nbrec_lb->options, "affinity_timeout", 0); > if (affinity_timeout > UINT16_MAX) { > @@ -227,13 +542,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, > + 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, template); > if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { > sset_add(&lb->ips_v4, lb_vip->vip_str); > } else { > @@ -381,9 +702,12 @@ 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 sset *template_vars_ref) > { > struct ovn_controller_lb *lb = xzalloc(sizeof *lb); > + bool template = smap_get_bool(&sbrec_lb->options, "template", false); > > lb->slb = sbrec_lb; > lb->n_vips = smap_count(&sbrec_lb->vips); > @@ -395,10 +719,26 @@ 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; > + struct lex_str key_s = template > + ? lexer_parse_template_string(node->key, > + template_vars, > + template_vars_ref) > + : lex_str_use(node->key); > + struct lex_str value_s = template > + ? lexer_parse_template_string(node->value, > + template_vars, > + template_vars_ref) > + : lex_str_use(node->value); > + char *error = ovn_lb_vip_init_explicit(lb_vip, > + lex_str_get(&key_s), > + lex_str_get(&value_s)); > + if (error) { > + free(error); > + } else { > + n_vips++; > } > - n_vips++; > + lex_str_free(&key_s); > + lex_str_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 62843e4716..55a41ae0bc 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; > uint16_t affinity_timeout; > > struct sset ips_v4; > @@ -82,19 +84,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-northd. > + */ > 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. */ > @@ -174,7 +188,17 @@ 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, > + struct sset *template_vars_ref); > 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 597625a291..1f8d0b8add 100644 > --- a/lib/ovn-util.c > +++ b/lib/ovn-util.c > @@ -793,9 +793,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; > memset(ip, 0, sizeof(*ip)); > *port = 0; > diff --git a/northd/northd.c b/northd/northd.c > index 123127e9c1..c590c14818 100644 > --- a/northd/northd.c > +++ b/northd/northd.c > @@ -3740,6 +3740,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]; > @@ -4056,12 +4060,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); > @@ -4073,8 +4084,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; > > @@ -5834,16 +5846,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, > @@ -5854,7 +5866,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; > @@ -6910,7 +6922,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); > @@ -6921,7 +6933,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")) { > @@ -6934,14 +6946,14 @@ 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"); > > ds_put_format(match, "%s.dst == %s", ip_match, lb_vip->vip_str); > - 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", proto, lb_vip->port_str); > } > > struct ovn_lflow *lflow_ref = NULL; > @@ -7192,24 +7204,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); > > @@ -7227,8 +7227,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; > } > > @@ -10231,7 +10232,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 { > @@ -10247,14 +10248,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)); > @@ -10263,7 +10264,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"; > @@ -10281,9 +10282,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, ") || "); > } > @@ -10296,9 +10297,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 = > @@ -10571,7 +10572,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); > @@ -10581,7 +10582,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 553c0e48c3..8cd2427e8f 100644 > --- a/ovn-nb.xml > +++ b/ovn-nb.xml > @@ -1905,8 +1905,66 @@ > is applied reply to ARP/neighbor discovery requests for all VIPs > of the load balancer. If set to <code>reachable</code>, then routers > on which the load balancer is applied reply to ARP/neighbor discovery > - 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>. > + requests only for VIPs that are part of a router's subnet. If set to > + <code>none</code>, then routers on which the load balancer is applied > + never reply to ARP/neighbor discovery requests for any of the load > + balancer VIPs. Load balancers with <code>options:template=true</code> > + do not support <code>reachable</code> as a valid mode. The default > + value of this option, if not specified, is <code>reachable</code> for > + regular load balancers and <code>none</code> for template load > + balancers. > + </column> > + > + <column name="options" key="template"> > + <p> > + Option to be set to <code>true</code>, if the load balancer is a > + template. The load balancer VIPs and backends must be using > + <ref table="Chassis_Template_Var"/> in their definitions. > + </p> > + > + <p> > + Load balancer template VIP supported formats are: > + </p> > + <pre> > +^VIP_VAR[:^PORT_VAR|:port] > + </pre> > + > + <p> > + where <code>VIP_VAR</code> and <code>PORT_VAR</code> are names of > + <ref table="Chassis_Template_Var"/> records. In this version, the vars are not names but keys of the "variables" column. > + </p> > + > + <p> > + Note: The VIP and PORT cannot be combined into a single template > + variable. For example, a <ref table="Chassis_Template_Var"/> > + variable expanding to <code>10.0.0.1:8080</code> is not valid > + if used as VIP. > + </p> > + > + <p> > + Load balancer template backend supported formats are: > + </p> > + <pre> > +^BACKEND_VAR1[:^PORT_VAR1|:port],^BACKEND_VAR2[:^PORT_VAR2|:port] > + > +or > + > +^BACKENDS_VAR1,^BACKENDS_VAR2 I think here each var means a single backend IP, right? So, s/BACKENDS/BACKEND/g > + </pre> > + <p> > + where <code>BACKEND_VAR1</code>, <code>PORT_VAR1</code>, > + <code>BACKEND_VAR2</code>, <code>PORT_VAR2</code>, > + <code>BACKENDS_VAR1</code> and <code>BACKENDS_VAR2</code> are names Same here, and they are keys instead of "names". Acked-by: Han Zhou <hzhou@ovn.org> > + of <ref table="Chassis_Template_Var"/> records. > + </p> > + </column> > + > + <column name="options" key="address-family"> > + Address family used by the load balancer. Supported values are > + <code>ipv4</code> and <code>ipv6</code>. The address-family is > + only used for load balancers with <code>options:template=true</code>. > + For explicit load balancers, setting the address-family has no > + effect. > </column> > > <column name="options" key="affinity_timeout"> > diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at > index 4d480e3573..9da7c26b31 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 86ab376fd6..770ccbdcda 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/tests/ovn.at b/tests/ovn.at > index 68e788e78f..7c1f2acd04 100644 > --- a/tests/ovn.at > +++ b/tests/ovn.at > @@ -33145,3 +33145,134 @@ AT_CHECK([ovs-ofctl dump-flows br-int | grep '42\.42\.42\.42'], [1], []) > OVN_CLEANUP([hv1]) > AT_CLEANUP > ]) > + > +OVN_FOR_EACH_NORTHD([ > +AT_SETUP([Load balancers with Chassis_Template_Var references]) > +AT_KEYWORDS([templates]) > +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 > + > +check ovn-nbctl ls-add sw > + > +dnl Use --wait=sb to ensure lsp1 getting a tunnel_key before lsp2. > +check ovn-nbctl --wait=sb lsp-add sw lsp1 > +check ovn-nbctl --wait=sb lsp-add sw lsp2 > + > +AT_CHECK([ovn-nbctl create Chassis_Template_Var chassis=hv1], [0], [ignore]) > + > +dnl Create a few LBs that use "uninstantiated" templates. > +check ovn-nbctl --template lb-add lb-test1 "^VIP1:^VPORT1" "^BACKENDS1" tcp > +check ovn-nbctl --template lb-add lb-test2 "^VIP2:^VPORT2" "^BACKENDS21,^BACKENDS22" tcp > +check ovn-nbctl --template lb-add lb-test3 "^VIP3:^VPORT3" "^BACKENDS31:^BPORT1,^BACKENDS32:^BPORT2" tcp > +check ovn-nbctl ls-lb-add sw lb-test1 > +check ovn-nbctl ls-lb-add sw lb-test2 > +check ovn-nbctl ls-lb-add sw lb-test3 > + > +check ovs-vsctl add-port br-int p1 -- set interface p1 external_ids:iface-id=lsp1 > +check ovs-vsctl add-port br-int p2 -- set interface p2 external_ids:iface-id=lsp2 > + > +wait_for_ports_up > +ovn-nbctl --wait=hv sync > + > +dnl Ensure the LBs are not translated to OpenFlow. > +as hv1 > +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat'], [1], []) > + > +dnl Create Chassis_Template_Var mappings. > +check ovn-nbctl --wait=hv set Chassis_Template_Var hv1 \ > + variables:VIP1='43.43.43.1' variables:VPORT1='4301' \ > + variables:BACKENDS1='85.85.85.1:8501' \ > + variables:VIP2='43.43.43.2' variables:VPORT2='4302' \ > + variables:BACKENDS21='85.85.85.21:8502' \ > + variables:BACKENDS22='85.85.85.22:8502' \ > + variables:VIP3='43.43.43.3' variables:VPORT3='4303' \ > + variables:BACKENDS31='85.85.85.31' \ > + variables:BACKENDS32='85.85.85.32' \ > + variables:BPORT1='8503' variables:BPORT2='8503' > + > +dnl Ensure the LBs are translated to OpenFlow. > +as hv1 > +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=85.85.85.1:8501)' -c], [0], [dnl > +1 > +]) > +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=85.85.85.21:8502)' -c], [0], [dnl > +1 > +]) > +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=85.85.85.22:8502)' -c], [0], [dnl > +1 > +]) > +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=85.85.85.31:8503)' -c], [0], [dnl > +1 > +]) > +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=85.85.85.32:8503)' -c], [0], [dnl > +1 > +]) > + > +dnl Ensure hairpin flows are correct. > +as hv1 > +AT_CHECK([ovs-ofctl dump-flows br-int | grep table=68 | ofctl_strip_all], [0], [dnl > + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b01,reg2=0x10cd/0xffff,nw_src=85.85.85.1,nw_dst=85.85.85.1,tp_dst=8501 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.1,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b02,reg2=0x10ce/0xffff,nw_src=85.85.85.21,nw_dst=85.85.85.21,tp_dst=8502 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.2,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b02,reg2=0x10ce/0xffff,nw_src=85.85.85.22,nw_dst=85.85.85.22,tp_dst=8502 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.2,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b03,reg2=0x10cf/0xffff,nw_src=85.85.85.31,nw_dst=85.85.85.31,tp_dst=8503 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.3,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b03,reg2=0x10cf/0xffff,nw_src=85.85.85.32,nw_dst=85.85.85.32,tp_dst=8503 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.3,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > +]) > + > +dnl Change Chassis_Template_Var mappings > +check ovn-nbctl --wait=hv set Chassis_Template_Var hv1 \ > + variables:VIP1='42.42.42.1' variables:VPORT1='4201' \ > + variables:BACKENDS1='84.84.84.1:8401' \ > + variables:VIP2='42.42.42.2' variables:VPORT2='4202' \ > + variables:BACKENDS21='84.84.84.21:8402' \ > + variables:BACKENDS22='84.84.84.22:8402' \ > + variables:VIP3='42.42.42.3' variables:VPORT3='4203' \ > + variables:BACKENDS31='84.84.84.31' \ > + variables:BACKENDS32='84.84.84.32' \ > + variables:BPORT1='8403' variables:BPORT2='8403' > + > +dnl Ensure the LBs are translated to OpenFlow. > +as hv1 > +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=84.84.84.1:8401)' -c], [0], [dnl > +1 > +]) > +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=84.84.84.21:8402)' -c], [0], [dnl > +1 > +]) > +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=84.84.84.22:8402)' -c], [0], [dnl > +1 > +]) > +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=84.84.84.31:8403)' -c], [0], [dnl > +1 > +]) > +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=84.84.84.32:8403)' -c], [0], [dnl > +1 > +]) > + > +dnl Ensure hairpin flows are correct. > +as hv1 > +AT_CHECK([ovs-ofctl dump-flows br-int | grep table=68 | ofctl_strip_all], [0], [dnl > + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a01,reg2=0x1069/0xffff,nw_src=84.84.84.1,nw_dst=84.84.84.1,tp_dst=8401 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.1,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a02,reg2=0x106a/0xffff,nw_src=84.84.84.21,nw_dst=84.84.84.21,tp_dst=8402 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.2,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a02,reg2=0x106a/0xffff,nw_src=84.84.84.22,nw_dst=84.84.84.22,tp_dst=8402 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.2,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a03,reg2=0x106b/0xffff,nw_src=84.84.84.31,nw_dst=84.84.84.31,tp_dst=8403 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.3,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a03,reg2=0x106b/0xffff,nw_src=84.84.84.32,nw_dst=84.84.84.32,tp_dst=8403 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.3,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > +]) > + > +dnl Remove Chassis_Template_Variables and check that everything is > +dnl removed from OpenFlow. > +check ovn-nbctl --wait=hv clear Chassis_Template_Var hv1 variables > + > +as hv1 > +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat'], [1], []) > + > +as hv1 > +AT_CHECK([ovs-ofctl dump-flows br-int | grep table=68 | ofctl_strip_all], [0], []) > + > +OVN_CLEANUP([hv1]) > +AT_CLEANUP > +]) > diff --git a/tests/system-ovn.at b/tests/system-ovn.at > index cb34127174..a15e332de6 100644 > --- a/tests/system-ovn.at > +++ b/tests/system-ovn.at > @@ -9138,3 +9138,186 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d > /connection dropped.*/d"]) > AT_CLEANUP > ]) > + > +OVN_FOR_EACH_NORTHD([ > +AT_SETUP([load-balancer template IPv4]) > +AT_SKIP_IF([test $HAVE_NC = no]) > +AT_KEYWORDS([ovnlb templates]) > + > +CHECK_CONNTRACK() > +CHECK_CONNTRACK_NAT() > +ovn_start > +OVS_TRAFFIC_VSWITCHD_START() > +OVS_CHECK_CT_ZERO_SNAT() > +ADD_BR([br-int]) > + > +# Set external-ids in br-int needed for ovn-controller > +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 > + > +# Start ovn-controller > +start_daemon ovn-controller > + > +# Logical network: > +# VM1 -- LS1 -- GW-Router -- LS2 -- VM3 > +# | > +# VM2 ----+ > +# > +# A templated load balancer applied on LS1 and GW-Router with > +# VM1 as backend. The VIP should be accessible from both VM2 and VM3. > + > +check ovn-nbctl \ > + -- lr-add rtr \ > + -- set Logical_Router rtr options:chassis=hv1 \ > + -- lrp-add rtr rtr-ls1 00:00:00:00:01:00 42.42.42.1/24 \ > + -- lrp-add rtr rtr-ls2 00:00:00:00:02:00 43.43.43.1/24 \ > + -- ls-add ls1 \ > + -- lsp-add ls1 ls1-rtr \ > + -- lsp-set-addresses ls1-rtr 00:00:00:00:01:00 \ > + -- lsp-set-type ls1-rtr router \ > + -- lsp-set-options ls1-rtr router-port=rtr-ls1 \ > + -- lsp-add ls1 vm1 -- lsp-set-addresses vm1 00:00:00:00:00:01 \ > + -- lsp-add ls1 vm2 -- lsp-set-addresses vm2 00:00:00:00:00:02 \ > + -- ls-add ls2 \ > + -- lsp-add ls2 ls2-rtr \ > + -- lsp-set-addresses ls2-rtr 00:00:00:00:02:00 \ > + -- lsp-set-type ls2-rtr router \ > + -- lsp-set-options ls2-rtr router-port=rtr-ls2 \ > + -- lsp-add ls2 vm3 -- lsp-set-addresses vm3 00:00:00:00:00:03 > + > +# Add a template LB that eventually expands to: > +# VIP=66.66.66.66:666 backends=42.42.42.2:4242 proto=tcp > + > +AT_CHECK([ovn-nbctl -- create chassis_template_var chassis="hv1" variables="{vip=66.66.66.66,vport=666,backends=\"42.42.42.2:4242\"}"], > + [0], [ignore]) > + > +check ovn-nbctl --template lb-add lb-test "^vip:^vport" "^backends" tcp \ > + -- ls-lb-add ls1 lb-test \ > + -- lr-lb-add rtr lb-test > + > +ADD_NAMESPACES(vm1) > +ADD_VETH(vm1, vm1, br-int, "42.42.42.2/24", "00:00:00:00:00:01", "42.42.42.1") > + > +ADD_NAMESPACES(vm2) > +ADD_VETH(vm2, vm2, br-int, "42.42.42.3/24", "00:00:00:00:00:02", "42.42.42.1") > + > +ADD_NAMESPACES(vm3) > +ADD_VETH(vm3, vm3, br-int, "43.43.43.2/24", "00:00:00:00:00:03", "43.43.43.1") > + > +# Wait for ovn-controller to catch up. > +wait_for_ports_up > +check ovn-nbctl --wait=hv sync > + > +AT_CHECK([ovn-appctl -t ovn-controller debug/dump-local-template-vars | sort], [0], [dnl > +Local template vars: > +name: 'backends' value: '42.42.42.2:4242' > +name: 'vip' value: '66.66.66.66' > +name: 'vport' value: '666' > +]) > + > +# Start IPv4 TCP server on vm1. > +NETNS_DAEMONIZE([vm1], [nc -k -l 42.42.42.2 4242], [nc-vm1.pid]) > + > +# Make sure connecting to the VIP works. > +NS_CHECK_EXEC([vm2], [nc 66.66.66.66 666 -z], [0], [ignore], [ignore]) > +NS_CHECK_EXEC([vm3], [nc 66.66.66.66 666 -z], [0], [ignore], [ignore]) > + > +AT_CLEANUP > +]) > + > +OVN_FOR_EACH_NORTHD([ > +AT_SETUP([load-balancer template IPv6]) > +AT_SKIP_IF([test $HAVE_NC = no]) > +AT_KEYWORDS([ovnlb templates]) > + > +CHECK_CONNTRACK() > +CHECK_CONNTRACK_NAT() > +ovn_start > +OVS_TRAFFIC_VSWITCHD_START() > +OVS_CHECK_CT_ZERO_SNAT() > +ADD_BR([br-int]) > + > +# Set external-ids in br-int needed for ovn-controller > +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 > + > +# Start ovn-controller > +start_daemon ovn-controller > + > +# Logical network: > +# VM1 -- LS1 -- GW-Router -- LS2 -- VM3 > +# | > +# VM2 ----+ > +# > +# A templated load balancer applied on LS1 and GW-Router with > +# VM1 as backend. The VIP should be accessible from both VM2 and VM3. > + > +check ovn-nbctl \ > + -- lr-add rtr \ > + -- set Logical_Router rtr options:chassis=hv1 \ > + -- lrp-add rtr rtr-ls1 00:00:00:00:01:00 4242::1/64 \ > + -- lrp-add rtr rtr-ls2 00:00:00:00:02:00 4343::1/64 \ > + -- ls-add ls1 \ > + -- lsp-add ls1 ls1-rtr \ > + -- lsp-set-addresses ls1-rtr 00:00:00:00:01:00 \ > + -- lsp-set-type ls1-rtr router \ > + -- lsp-set-options ls1-rtr router-port=rtr-ls1 \ > + -- lsp-add ls1 vm1 -- lsp-set-addresses vm1 00:00:00:00:00:01 \ > + -- lsp-add ls1 vm2 -- lsp-set-addresses vm2 00:00:00:00:00:02 \ > + -- ls-add ls2 \ > + -- lsp-add ls2 ls2-rtr \ > + -- lsp-set-addresses ls2-rtr 00:00:00:00:02:00 \ > + -- lsp-set-type ls2-rtr router \ > + -- lsp-set-options ls2-rtr router-port=rtr-ls2 \ > + -- lsp-add ls2 vm3 -- lsp-set-addresses vm3 00:00:00:00:00:03 > + > +# Add a template LB that eventually expands to: > +# VIP=6666::1 backends=[4242::2]:4242 proto=tcp > + > +AT_CHECK([ovn-nbctl -- create chassis_template_var chassis="hv1" variables="{vip=\"6666::1\",vport=666,backends=\"[[4242::2]]:4242\"}"], > + [0], [ignore]) > + > +check ovn-nbctl --template lb-add lb-test "^vip:^vport" "^backends" tcp ipv6 \ > + -- ls-lb-add ls1 lb-test \ > + -- lr-lb-add rtr lb-test > + > +ADD_NAMESPACES(vm1) > +ADD_VETH(vm1, vm1, br-int, "4242::2/64", "00:00:00:00:00:01", "4242::1") > +OVS_WAIT_UNTIL([test "$(ip netns exec vm1 ip a | grep 4242::2 | grep tentative)" = ""]) > + > +ADD_NAMESPACES(vm2) > +ADD_VETH(vm2, vm2, br-int, "4242::3/64", "00:00:00:00:00:02", "4242::1") > +OVS_WAIT_UNTIL([test "$(ip netns exec vm2 ip a | grep 4242::3 | grep tentative)" = ""]) > + > +ADD_NAMESPACES(vm3) > +ADD_VETH(vm3, vm3, br-int, "4343::2/64", "00:00:00:00:00:03", "4343::1") > +OVS_WAIT_UNTIL([test "$(ip netns exec vm3 ip a | grep 4343::2 | grep tentative)" = ""]) > + > +# Wait for ovn-controller to catch up. > +wait_for_ports_up > +check ovn-nbctl --wait=hv sync > + > +AT_CHECK([ovn-appctl -t ovn-controller debug/dump-local-template-vars | sort], [0], [dnl > +Local template vars: > +name: 'backends' value: '[[4242::2]]:4242' > +name: 'vip' value: '6666::1' > +name: 'vport' value: '666' > +]) > + > +# Start IPv6 TCP server on vm1. > +NETNS_DAEMONIZE([vm1], [nc -k -l 4242::2 4242], [nc-vm1.pid]) > + > +# Make sure connecting to the VIP works. > +NS_CHECK_EXEC([vm2], [nc 6666::1 666 -z], [0], [ignore], [ignore]) > +NS_CHECK_EXEC([vm3], [nc 6666::1 666 -z], [0], [ignore], [ignore]) > + > +AT_CLEANUP > +]) > diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c > index d2dee6b31c..9e9b83ef1f 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,11 @@ nbctl_lb_add(struct ctl_context *ctx) > return; > } > > + 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 +2866,59 @@ 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 = !strcmp(ctx->argv[5], "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 +2937,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 +2949,17 @@ 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 == AF_INET ? "ipv4" : "ipv6"); > + } > 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 +3013,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 +3022,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 +3031,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 +3240,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 +3404,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); > > @@ -7472,9 +7475,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 }, >
On 11/29/22 09:14, Han Zhou wrote: > On Tue, Nov 22, 2022 at 6:15 AM Dumitru Ceara <dceara@redhat.com> wrote: >> >> 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 > > Sorry if I missed, but I didn't see any tests that test the form > "^backends_variable1,^backends_variable2". I only see tests with a single > backend variable with a single IP in it. Better to test: > 1. Multiple backends variables This should be covered. I have this in the test: check ovn-nbctl --template lb-add lb-test1 "^VIP1:^VPORT1" "^BACKENDS1" tcp check ovn-nbctl --template lb-add lb-test2 "^VIP2:^VPORT2" "^BACKENDS21,^BACKENDS22" tcp check ovn-nbctl --template lb-add lb-test3 "^VIP3:^VPORT3" "^BACKENDS31:^BPORT1,^BACKENDS32:^BPORT2" tcp > 2. Multiple IPs in a single variable (I saw this in the tutorial in patch > 5, but better to be covered here, too) > You're right, I added a LB with such a variable instantiation to the test. > > >> >> 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. >> >> Template load balancers don't support the "reachable" neighbor-responder >> mode. Instead the CMS can explicitly configure the responder mode to >> either "all" or "none". >> >> To properly handle template updates in ovn-controller we also add a >> Chassis_Template_Var <- LB reference in ovn-controller. This way, when >> a Chassis_Template_Var changes value all load balancers that refer to >> it will also get updated. >> >> Signed-off-by: Dumitru Ceara <dceara@redhat.com> >> --- >> V3: >> - Addressed Mark's comments: >> - Added TODO items about potential future template LB improvements. >> - Removed n_backends arg from ovn_lb_backends_init_explicit() and >> ovn_lb_backends_init_template(). >> - Fixed ovn_northd_lb_create() to first get the template option before >> using its value. >> - Fixed up comments and man pages. >> - Hardenned setting of address family in ovn-nbctl. >> >> V2: >> - Fix GCC build due to missing explicit return. >> - Fix ls_in_pre_stateful flows due to using wrong lb field. >> - Use new lexer_parse_template_string(). >> - Changed lb_handle_changed_ref() signature to return bool. >> - Update documentation with info about responder mode=none, LB template >> supported formats, lb explicit address family requirements. >> - Squashed the template LB patches into a single one >> - Added more tests. >> - Squashed the system tests patch into this one. >> --- >> TODO.rst | 7 + >> controller/lflow.c | 115 +++++++++-- >> controller/lflow.h | 7 + >> controller/ovn-controller.c | 67 +++++- >> lib/lb.c | 452 > ++++++++++++++++++++++++++++++++++++++----- >> lib/lb.h | 40 +++- >> lib/ovn-util.c | 3 >> northd/northd.c | 89 ++++---- >> ovn-nb.xml | 62 ++++++ >> tests/ovn-nbctl.at | 23 +- >> tests/ovn-northd.at | 7 + >> tests/ovn.at | 131 ++++++++++++ >> tests/system-ovn.at | 183 +++++++++++++++++ >> utilities/ovn-nbctl.c | 120 ++++++----- >> 14 files changed, 1078 insertions(+), 228 deletions(-) >> >> diff --git a/TODO.rst b/TODO.rst >> index fe5f9a2f30..53cf2870b2 100644 >> --- a/TODO.rst >> +++ b/TODO.rst >> @@ -183,3 +183,10 @@ OVN To-do List >> * Chassis_Template_Var >> >> * Support template variables when tracing packets with ovn-trace. >> + >> +* Load Balancer templates >> + >> + * Support combining the VIP (or backend) IP and port into a single >> + template variable. > > Is it still a TODO for backends? At least the tutorial test (in patch 5) is > already doing something like this: > backends0="42.0.0.1:1,42.1.0.1:1,42.2.0.1:1,42.3.0.1:1,42.4.0.1:1" > You're right, we can combine the backends and it works fine. I removed the "(or backend)" part. >> + >> + * Support combining all backends into a single template variable. > > What does it mean here? Isn't the tutorial test already combining multiple > backends into a single variable? > Yes, it is, and it's working fine. I'm not sure anymore why I had added this TODO item here. I removed it now. >> diff --git a/controller/lflow.c b/controller/lflow.c >> index 84625fb3f1..f6ac639541 100644 >> --- a/controller/lflow.c >> +++ b/controller/lflow.c >> @@ -97,6 +97,15 @@ consider_logical_flow(const struct sbrec_logical_flow > *lflow, >> struct lflow_ctx_in *l_ctx_in, >> struct lflow_ctx_out *l_ctx_out); >> >> +static void >> +consider_lb_hairpin_flows(struct objdep_mgr *mgr, >> + const struct sbrec_load_balancer *sbrec_lb, >> + const struct hmap *local_datapaths, >> + const struct smap *template_vars, >> + bool use_ct_mark, >> + struct ovn_desired_flow_table *flow_table, >> + struct simap *ids); >> + >> static void add_port_sec_flows(const struct shash *binding_lports, >> const struct sbrec_chassis *, >> struct ovn_desired_flow_table *); >> @@ -223,7 +232,7 @@ lflow_handle_changed_flows(struct lflow_ctx_in > *l_ctx_in, >> UUIDSET_INITIALIZER(&flood_remove_nodes); >> SBREC_LOGICAL_FLOW_TABLE_FOR_EACH_TRACKED (lflow, >> > l_ctx_in->logical_flow_table) { >> - if (uuidset_find(l_ctx_out->lflows_processed, > &lflow->header_.uuid)) { >> + if (uuidset_find(l_ctx_out->objs_processed, > &lflow->header_.uuid)) { >> VLOG_DBG("lflow "UUID_FMT"has been processed, skip.", >> UUID_ARGS(&lflow->header_.uuid)); >> continue; >> @@ -253,14 +262,14 @@ lflow_handle_changed_flows(struct lflow_ctx_in > *l_ctx_in, >> UUID_ARGS(&lflow->header_.uuid)); >> >> /* For the extra lflows that need to be reprocessed because > of the >> - * flood remove, remove it from lflows_processed. */ >> + * flood remove, remove it from objs_processed. */ >> struct uuidset_node *unode = >> - uuidset_find(l_ctx_out->lflows_processed, >> + uuidset_find(l_ctx_out->objs_processed, >> &lflow->header_.uuid); >> if (unode) { >> VLOG_DBG("lflow "UUID_FMT"has been processed, now > reprocess.", >> UUID_ARGS(&lflow->header_.uuid)); >> - uuidset_delete(l_ctx_out->lflows_processed, unode); >> + uuidset_delete(l_ctx_out->objs_processed, unode); >> } >> >> consider_logical_flow(lflow, false, l_ctx_in, l_ctx_out); >> @@ -687,7 +696,7 @@ lflow_handle_addr_set_update(const char *as_name, >> struct object_to_resources_list_node *resource_list_node; >> RESOURCE_FOR_EACH_OBJ (resource_list_node, resource_node) { >> const struct uuid *obj_uuid = &resource_list_node->obj_uuid; >> - if (uuidset_find(l_ctx_out->lflows_processed, obj_uuid)) { >> + if (uuidset_find(l_ctx_out->objs_processed, obj_uuid)) { >> VLOG_DBG("lflow "UUID_FMT"has been processed, skip.", >> UUID_ARGS(obj_uuid)); >> continue; >> @@ -777,13 +786,13 @@ lflow_handle_changed_ref(enum objdep_type type, > const char *res_name, >> } >> >> /* For the extra lflows that need to be reprocessed because of > the >> - * flood remove, remove it from lflows_processed. */ >> + * flood remove, remove it from objs_processed. */ >> struct uuidset_node *unode = >> - uuidset_find(l_ctx_out->lflows_processed, > &lflow->header_.uuid); >> + uuidset_find(l_ctx_out->objs_processed, > &lflow->header_.uuid); >> if (unode) { >> VLOG_DBG("lflow "UUID_FMT"has been processed, now > reprocess.", >> UUID_ARGS(&lflow->header_.uuid)); >> - uuidset_delete(l_ctx_out->lflows_processed, unode); >> + uuidset_delete(l_ctx_out->objs_processed, unode); >> } >> >> consider_logical_flow(lflow, false, l_ctx_in, l_ctx_out); >> @@ -792,6 +801,43 @@ lflow_handle_changed_ref(enum objdep_type type, > const char *res_name, >> return true; >> } >> >> +bool >> +lb_handle_changed_ref(enum objdep_type type, const char *res_name, >> + struct ovs_list *objs_todo, >> + const void *in_arg, void *out_arg) >> +{ >> + struct lflow_ctx_in *l_ctx_in = CONST_CAST(struct lflow_ctx_in *, > in_arg); >> + struct lflow_ctx_out *l_ctx_out = out_arg; >> + >> + struct object_to_resources_list_node *resource_lb_uuid; >> + LIST_FOR_EACH_POP (resource_lb_uuid, list_node, objs_todo) { >> + VLOG_DBG("Reprocess LB "UUID_FMT" for resource type: %s, name: > %s", >> + UUID_ARGS(&resource_lb_uuid->obj_uuid), >> + objdep_type_name(type), res_name); >> + >> + const struct sbrec_load_balancer *lb = >> + sbrec_load_balancer_table_get_for_uuid( >> + l_ctx_in->lb_table, &resource_lb_uuid->obj_uuid); >> + if (!lb) { >> + VLOG_DBG("Failed to find LB "UUID_FMT" referred by: %s", > > nit: I think it should be: Failed to find LB ... that refers ... > Yes, fixed. > >> + UUID_ARGS(&resource_lb_uuid->obj_uuid), res_name); >> + } else { >> + ofctrl_remove_flows(l_ctx_out->flow_table, >> + &resource_lb_uuid->obj_uuid); >> + >> + consider_lb_hairpin_flows(l_ctx_out->lb_deps_mgr, 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); >> + } >> + >> + free(resource_lb_uuid); >> + } >> + return true; >> +} >> + >> static void >> lflow_parse_ctrl_meter(const struct sbrec_logical_flow *lflow, >> struct ovn_extend_table *meter_table, >> @@ -1259,9 +1305,9 @@ consider_logical_flow(const struct > sbrec_logical_flow *lflow, >> >> COVERAGE_INC(consider_logical_flow); >> if (!is_recompute) { >> - ovs_assert(!uuidset_find(l_ctx_out->lflows_processed, >> + ovs_assert(!uuidset_find(l_ctx_out->objs_processed, >> &lflow->header_.uuid)); >> - uuidset_insert(l_ctx_out->lflows_processed, > &lflow->header_.uuid); >> + uuidset_insert(l_ctx_out->objs_processed, &lflow->header_.uuid); >> } >> >> if (dp) { >> @@ -2001,8 +2047,10 @@ add_lb_ct_snat_hairpin_flows(struct > ovn_controller_lb *lb, >> } >> >> static void >> -consider_lb_hairpin_flows(const struct sbrec_load_balancer *sbrec_lb, >> +consider_lb_hairpin_flows(struct objdep_mgr *mgr, >> + const struct sbrec_load_balancer *sbrec_lb, >> const struct hmap *local_datapaths, >> + const struct smap *template_vars, >> bool use_ct_mark, >> struct ovn_desired_flow_table *flow_table, >> struct simap *ids) >> @@ -2039,7 +2087,9 @@ consider_lb_hairpin_flows(const struct > sbrec_load_balancer *sbrec_lb, >> return; >> } >> >> - struct ovn_controller_lb *lb = ovn_controller_lb_create(sbrec_lb); >> + struct sset template_vars_ref = SSET_INITIALIZER(&template_vars_ref); >> + struct ovn_controller_lb *lb = >> + ovn_controller_lb_create(sbrec_lb, template_vars, > &template_vars_ref); >> uint8_t lb_proto = IPPROTO_TCP; >> if (lb->slb->protocol && lb->slb->protocol[0]) { >> if (!strcmp(lb->slb->protocol, "udp")) { >> @@ -2049,6 +2099,11 @@ consider_lb_hairpin_flows(const struct > sbrec_load_balancer *sbrec_lb, >> } >> } >> >> + const char *tv_name; >> + SSET_FOR_EACH (tv_name, &template_vars_ref) { >> + objdep_mgr_add(mgr, OBJDEP_TYPE_TEMPLATE, tv_name, >> + &sbrec_lb->header_.uuid); >> + } >> for (i = 0; i < lb->n_vips; i++) { >> struct ovn_lb_vip *lb_vip = &lb->vips[i]; >> >> @@ -2063,13 +2118,17 @@ consider_lb_hairpin_flows(const struct > sbrec_load_balancer *sbrec_lb, >> add_lb_ct_snat_hairpin_flows(lb, id, lb_proto, flow_table); >> >> ovn_controller_lb_destroy(lb); >> + sset_destroy(&template_vars_ref); >> } >> >> /* Adds OpenFlow flows to flow tables for each Load balancer VIPs and >> * 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, >> +add_lb_hairpin_flows(struct objdep_mgr *mgr, >> + const struct sbrec_load_balancer_table *lb_table, >> + 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) >> @@ -2092,8 +2151,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(mgr, lb, local_datapaths, > template_vars, >> + use_ct_mark, flow_table, ids); >> } >> } >> >> @@ -2229,7 +2288,9 @@ lflow_run(struct lflow_ctx_in *l_ctx_in, struct > lflow_ctx_out *l_ctx_out) >> l_ctx_in->static_mac_binding_table, >> l_ctx_in->local_datapaths, >> l_ctx_out->flow_table); >> - add_lb_hairpin_flows(l_ctx_in->lb_table, l_ctx_in->local_datapaths, >> + add_lb_hairpin_flows(l_ctx_out->lb_deps_mgr, 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, >> @@ -2280,10 +2341,10 @@ lflow_add_flows_for_datapath(const struct > sbrec_datapath_binding *dp, >> const struct sbrec_logical_flow *lflow; >> SBREC_LOGICAL_FLOW_FOR_EACH_EQUAL ( >> lflow, lf_row, l_ctx_in->sbrec_logical_flow_by_logical_datapath) > { >> - if (uuidset_find(l_ctx_out->lflows_processed, > &lflow->header_.uuid)) { >> + if (uuidset_find(l_ctx_out->objs_processed, > &lflow->header_.uuid)) { >> continue; >> } >> - uuidset_insert(l_ctx_out->lflows_processed, > &lflow->header_.uuid); >> + uuidset_insert(l_ctx_out->objs_processed, &lflow->header_.uuid); >> consider_logical_flow__(lflow, dp, l_ctx_in, l_ctx_out); >> } >> sbrec_logical_flow_index_destroy_row(lf_row); >> @@ -2308,7 +2369,7 @@ lflow_add_flows_for_datapath(const struct > sbrec_datapath_binding *dp, >> sbrec_logical_flow_index_set_logical_dp_group(lf_row, ldpg); >> SBREC_LOGICAL_FLOW_FOR_EACH_EQUAL ( >> lflow, lf_row, > l_ctx_in->sbrec_logical_flow_by_logical_dp_group) { >> - if (uuidset_find(l_ctx_out->lflows_processed, >> + if (uuidset_find(l_ctx_out->objs_processed, >> &lflow->header_.uuid)) { >> continue; >> } >> @@ -2360,7 +2421,9 @@ lflow_add_flows_for_datapath(const struct > sbrec_datapath_binding *dp, >> /* Add load balancer hairpin flows if the datapath has any load > balancers >> * associated. */ >> for (size_t i = 0; i < n_dp_lbs; i++) { >> - consider_lb_hairpin_flows(dp_lbs[i], l_ctx_in->local_datapaths, >> + consider_lb_hairpin_flows(l_ctx_out->lb_deps_mgr, 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); >> @@ -2382,7 +2445,7 @@ lflow_handle_flows_for_lport(const struct > sbrec_port_binding *pb, >> OBJDEP_TYPE_PORTBINDING, >> pb->logical_port, >> lflow_handle_changed_ref, >> - l_ctx_out->lflows_processed, >> + l_ctx_out->objs_processed, >> l_ctx_in, l_ctx_out, &changed)) { >> return false; >> } >> @@ -2421,7 +2484,7 @@ lflow_handle_changed_port_bindings(struct > lflow_ctx_in *l_ctx_in, >> OBJDEP_TYPE_PORTBINDING, >> pb->logical_port, >> lflow_handle_changed_ref, >> - l_ctx_out->lflows_processed, >> + l_ctx_out->objs_processed, >> l_ctx_in, l_ctx_out, &changed)) { >> ret = false; >> break; >> @@ -2448,7 +2511,7 @@ lflow_handle_changed_mc_groups(struct lflow_ctx_in > *l_ctx_in, >> if (!objdep_mgr_handle_change(l_ctx_out->lflow_deps_mgr, >> OBJDEP_TYPE_MC_GROUP, > ds_cstr(&mg_key), >> lflow_handle_changed_ref, >> - l_ctx_out->lflows_processed, >> + l_ctx_out->objs_processed, >> l_ctx_in, l_ctx_out, &changed)) { >> ret = false; >> break; >> @@ -2502,7 +2565,9 @@ 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, >> + consider_lb_hairpin_flows(l_ctx_out->lb_deps_mgr, 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/controller/lflow.h b/controller/lflow.h >> index d95fd41142..9e8f9afd33 100644 >> --- a/controller/lflow.h >> +++ b/controller/lflow.h >> @@ -122,9 +122,10 @@ struct lflow_ctx_out { >> struct ovn_extend_table *group_table; >> struct ovn_extend_table *meter_table; >> struct objdep_mgr *lflow_deps_mgr; >> + struct objdep_mgr *lb_deps_mgr; >> struct lflow_cache *lflow_cache; >> struct conj_ids *conj_ids; >> - struct uuidset *lflows_processed; >> + struct uuidset *objs_processed; >> struct simap *hairpin_lb_ids; >> struct id_pool *hairpin_id_pool; >> }; >> @@ -174,4 +175,8 @@ bool lflow_handle_changed_mc_groups(struct > lflow_ctx_in *, >> struct lflow_ctx_out *); >> bool lflow_handle_changed_port_bindings(struct lflow_ctx_in *, >> struct lflow_ctx_out *); >> + >> +bool lb_handle_changed_ref(enum objdep_type type, const char *res_name, >> + struct ovs_list *objs_todo, >> + const void *in_arg, void *out_arg); >> #endif /* controller/lflow.h */ >> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c >> index f9ed0e3855..9807ecd8eb 100644 >> --- a/controller/ovn-controller.c >> +++ b/controller/ovn-controller.c >> @@ -2791,13 +2791,15 @@ struct ed_type_lflow_output { >> struct ovn_extend_table meter_table; >> /* lflow <-> resource cross reference */ >> struct objdep_mgr lflow_deps_mgr;; >> + /* load balancer <-> resource cross reference */ >> + struct objdep_mgr lb_deps_mgr; >> /* conjunciton ID usage information of lflows */ >> struct conj_ids conj_ids; >> >> - /* lflows processed in the current engine execution. >> + /* objects (lflows and lbs) processed in the current engine > execution. >> * Cleared by en_lflow_output_clear_tracked_data before each engine >> * execution. */ >> - struct uuidset lflows_processed; >> + struct uuidset objs_processed; >> >> /* Data which is persistent and not cleared during >> * full recompute. */ >> @@ -2954,8 +2956,9 @@ init_lflow_ctx(struct engine_node *node, >> l_ctx_out->group_table = &fo->group_table; >> l_ctx_out->meter_table = &fo->meter_table; >> l_ctx_out->lflow_deps_mgr = &fo->lflow_deps_mgr; >> + l_ctx_out->lb_deps_mgr = &fo->lb_deps_mgr; >> l_ctx_out->conj_ids = &fo->conj_ids; >> - l_ctx_out->lflows_processed = &fo->lflows_processed; >> + l_ctx_out->objs_processed = &fo->objs_processed; >> l_ctx_out->lflow_cache = fo->pd.lflow_cache; >> l_ctx_out->hairpin_id_pool = fo->hd.pool; >> l_ctx_out->hairpin_lb_ids = &fo->hd.ids; >> @@ -2970,8 +2973,9 @@ en_lflow_output_init(struct engine_node *node > OVS_UNUSED, >> ovn_extend_table_init(&data->group_table); >> ovn_extend_table_init(&data->meter_table); >> objdep_mgr_init(&data->lflow_deps_mgr); >> + objdep_mgr_init(&data->lb_deps_mgr); >> lflow_conj_ids_init(&data->conj_ids); >> - uuidset_init(&data->lflows_processed); >> + uuidset_init(&data->objs_processed); >> simap_init(&data->hd.ids); >> data->hd.pool = id_pool_create(1, UINT32_MAX - 1); >> nd_ra_opts_init(&data->nd_ra_opts); >> @@ -2983,7 +2987,7 @@ static void >> en_lflow_output_clear_tracked_data(void *data) >> { >> struct ed_type_lflow_output *flow_output_data = data; >> - uuidset_clear(&flow_output_data->lflows_processed); >> + uuidset_clear(&flow_output_data->objs_processed); >> } >> >> static void >> @@ -2994,8 +2998,9 @@ en_lflow_output_cleanup(void *data) >> ovn_extend_table_destroy(&flow_output_data->group_table); >> ovn_extend_table_destroy(&flow_output_data->meter_table); >> objdep_mgr_destroy(&flow_output_data->lflow_deps_mgr); >> + objdep_mgr_destroy(&flow_output_data->lb_deps_mgr); >> lflow_conj_ids_destroy(&flow_output_data->conj_ids); >> - uuidset_destroy(&flow_output_data->lflows_processed); >> + uuidset_destroy(&flow_output_data->objs_processed); >> lflow_cache_destroy(flow_output_data->pd.lflow_cache); >> simap_destroy(&flow_output_data->hd.ids); >> id_pool_destroy(flow_output_data->hd.pool); >> @@ -3030,6 +3035,7 @@ en_lflow_output_run(struct engine_node *node, void > *data) >> struct ovn_extend_table *group_table = &fo->group_table; >> struct ovn_extend_table *meter_table = &fo->meter_table; >> struct objdep_mgr *lflow_deps_mgr = &fo->lflow_deps_mgr; >> + struct objdep_mgr *lb_deps_mgr = &fo->lb_deps_mgr; >> >> static bool first_run = true; >> if (first_run) { >> @@ -3039,6 +3045,7 @@ en_lflow_output_run(struct engine_node *node, void > *data) >> ovn_extend_table_clear(group_table, false /* desired */); >> ovn_extend_table_clear(meter_table, false /* desired */); >> objdep_mgr_clear(lflow_deps_mgr); >> + objdep_mgr_clear(lb_deps_mgr); >> lflow_conj_ids_clear(&fo->conj_ids); >> } >> >> @@ -3172,7 +3179,7 @@ lflow_output_addr_sets_handler(struct engine_node > *node, void *data) >> if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, >> OBJDEP_TYPE_ADDRSET, ref_name, >> lflow_handle_changed_ref, >> - l_ctx_out.lflows_processed, >> + l_ctx_out.objs_processed, >> &l_ctx_in, &l_ctx_out, &changed)) { >> return false; >> } >> @@ -3191,7 +3198,7 @@ lflow_output_addr_sets_handler(struct engine_node > *node, void *data) >> OBJDEP_TYPE_ADDRSET, >> shash_node->name, >> lflow_handle_changed_ref, >> - l_ctx_out.lflows_processed, >> + l_ctx_out.objs_processed, >> &l_ctx_in, &l_ctx_out, > &changed)) { >> return false; >> } >> @@ -3204,7 +3211,7 @@ lflow_output_addr_sets_handler(struct engine_node > *node, void *data) >> if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, >> OBJDEP_TYPE_ADDRSET, ref_name, >> lflow_handle_changed_ref, >> - l_ctx_out.lflows_processed, >> + l_ctx_out.objs_processed, >> &l_ctx_in, &l_ctx_out, &changed)) { >> return false; >> } >> @@ -3239,7 +3246,7 @@ lflow_output_port_groups_handler(struct engine_node > *node, void *data) >> if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, >> OBJDEP_TYPE_PORTGROUP, ref_name, >> lflow_handle_changed_ref, >> - l_ctx_out.lflows_processed, >> + l_ctx_out.objs_processed, >> &l_ctx_in, &l_ctx_out, &changed)) { >> return false; >> } >> @@ -3251,7 +3258,7 @@ lflow_output_port_groups_handler(struct engine_node > *node, void *data) >> if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, >> OBJDEP_TYPE_PORTGROUP, ref_name, >> lflow_handle_changed_ref, >> - l_ctx_out.lflows_processed, >> + l_ctx_out.objs_processed, >> &l_ctx_in, &l_ctx_out, &changed)) { >> return false; >> } >> @@ -3263,7 +3270,7 @@ lflow_output_port_groups_handler(struct engine_node > *node, void *data) >> if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, >> OBJDEP_TYPE_PORTGROUP, ref_name, >> lflow_handle_changed_ref, >> - l_ctx_out.lflows_processed, >> + l_ctx_out.objs_processed, >> &l_ctx_in, &l_ctx_out, &changed)) { >> return false; >> } >> @@ -3297,7 +3304,17 @@ lflow_output_template_vars_handler(struct > engine_node *node, void *data) >> if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, >> OBJDEP_TYPE_TEMPLATE, >> res_name, lflow_handle_changed_ref, >> - l_ctx_out.lflows_processed, >> + l_ctx_out.objs_processed, >> + &l_ctx_in, &l_ctx_out, &changed)) { >> + return false; >> + } >> + if (changed) { >> + engine_set_node_state(node, EN_UPDATED); >> + } >> + if (!objdep_mgr_handle_change(l_ctx_out.lb_deps_mgr, >> + OBJDEP_TYPE_TEMPLATE, >> + res_name, lb_handle_changed_ref, >> + l_ctx_out.objs_processed, >> &l_ctx_in, &l_ctx_out, &changed)) { >> return false; >> } >> @@ -3309,7 +3326,17 @@ lflow_output_template_vars_handler(struct > engine_node *node, void *data) >> if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, >> OBJDEP_TYPE_TEMPLATE, >> res_name, lflow_handle_changed_ref, >> - l_ctx_out.lflows_processed, >> + l_ctx_out.objs_processed, >> + &l_ctx_in, &l_ctx_out, &changed)) { >> + return false; >> + } >> + if (changed) { >> + engine_set_node_state(node, EN_UPDATED); >> + } >> + if (!objdep_mgr_handle_change(l_ctx_out.lb_deps_mgr, >> + OBJDEP_TYPE_TEMPLATE, >> + res_name, lb_handle_changed_ref, >> + l_ctx_out.objs_processed, >> &l_ctx_in, &l_ctx_out, &changed)) { >> return false; >> } >> @@ -3321,7 +3348,17 @@ lflow_output_template_vars_handler(struct > engine_node *node, void *data) >> if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, >> OBJDEP_TYPE_TEMPLATE, >> res_name, lflow_handle_changed_ref, >> - l_ctx_out.lflows_processed, >> + l_ctx_out.objs_processed, >> + &l_ctx_in, &l_ctx_out, &changed)) { >> + return false; >> + } >> + if (changed) { >> + engine_set_node_state(node, EN_UPDATED); >> + } >> + if (!objdep_mgr_handle_change(l_ctx_out.lb_deps_mgr, >> + OBJDEP_TYPE_TEMPLATE, >> + res_name, lb_handle_changed_ref, >> + l_ctx_out.objs_processed, >> &l_ctx_in, &l_ctx_out, &changed)) { >> return false; >> } >> diff --git a/lib/lb.c b/lib/lb.c >> index c08ccceda1..43628bba77 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,94 +82,293 @@ 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) >> { >> - int addr_family; >> - >> - if (!ip_address_and_port_from_lb_key(lb_key, &lb_vip->vip_str, >> - &lb_vip->vip, &lb_vip->vip_port, >> - &addr_family)) { >> - return false; >> - } >> - >> - /* 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; >> + lb_vip->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 (lb_vip->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[lb_vip->n_backends]; >> int backend_addr_family; >> if (!ip_address_and_port_from_lb_key(token, &backend->ip_str, >> &backend->ip, > &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; >> } >> >> - n_backends++; >> + 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; >> + lb_vip->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, &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; >> + >> + return ovn_lb_backends_init_explicit(lb_vip, lb_value); >> +} >> + >> +/* 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_) >> +{ >> + struct ds errors = DS_EMPTY_INITIALIZER; >> + char *value = xstrdup(value_); >> + char *save_ptr = NULL; >> + size_t n_allocated_backends = 0; >> + lb_vip->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 (lb_vip->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[lb_vip->n_backends]; >> + lb_backend->ip_str = backend_ip; >> + lb_backend->port_str = backend_port; >> + lb_backend->port = 0; >> + lb_vip->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); >> +} >> + >> +/* 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 >> @@ -189,12 +399,113 @@ 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; >> + } >> + return false; >> +} >> + >> +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 template = smap_get_bool(&nbrec_lb->options, "template", false); >> 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"; >> @@ -202,12 +513,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, > 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 = template; >> + >> + const char *mode = smap_get(&nbrec_lb->options, > "neighbor_responder"); >> + lb->neigh_mode = ovn_lb_get_neigh_mode(nbrec_lb, mode, template); >> + >> uint32_t affinity_timeout = >> smap_get_uint(&nbrec_lb->options, "affinity_timeout", 0); >> if (affinity_timeout > UINT16_MAX) { >> @@ -227,13 +542,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, >> + 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, template); >> if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { >> sset_add(&lb->ips_v4, lb_vip->vip_str); >> } else { >> @@ -381,9 +702,12 @@ 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 sset *template_vars_ref) >> { >> struct ovn_controller_lb *lb = xzalloc(sizeof *lb); >> + bool template = smap_get_bool(&sbrec_lb->options, "template", false); >> >> lb->slb = sbrec_lb; >> lb->n_vips = smap_count(&sbrec_lb->vips); >> @@ -395,10 +719,26 @@ 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; >> + struct lex_str key_s = template >> + ? lexer_parse_template_string(node->key, >> + > template_vars, >> + > template_vars_ref) >> + : lex_str_use(node->key); >> + struct lex_str value_s = template >> + ? lexer_parse_template_string(node->value, >> + > template_vars, >> + > template_vars_ref) >> + : lex_str_use(node->value); >> + char *error = ovn_lb_vip_init_explicit(lb_vip, >> + lex_str_get(&key_s), >> + lex_str_get(&value_s)); >> + if (error) { >> + free(error); >> + } else { >> + n_vips++; >> } >> - n_vips++; >> + lex_str_free(&key_s); >> + lex_str_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 62843e4716..55a41ae0bc 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; >> uint16_t affinity_timeout; >> >> struct sset ips_v4; >> @@ -82,19 +84,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-northd. >> + */ >> 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. */ >> @@ -174,7 +188,17 @@ 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, >> + struct sset *template_vars_ref); >> 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 597625a291..1f8d0b8add 100644 >> --- a/lib/ovn-util.c >> +++ b/lib/ovn-util.c >> @@ -793,9 +793,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; >> memset(ip, 0, sizeof(*ip)); >> *port = 0; >> diff --git a/northd/northd.c b/northd/northd.c >> index 123127e9c1..c590c14818 100644 >> --- a/northd/northd.c >> +++ b/northd/northd.c >> @@ -3740,6 +3740,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]; >> @@ -4056,12 +4060,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); >> @@ -4073,8 +4084,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; >> >> @@ -5834,16 +5846,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, >> @@ -5854,7 +5866,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; >> @@ -6910,7 +6922,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); >> @@ -6921,7 +6933,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")) { >> @@ -6934,14 +6946,14 @@ 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"); >> >> ds_put_format(match, "%s.dst == %s", ip_match, lb_vip->vip_str); >> - 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", proto, > lb_vip->port_str); >> } >> >> struct ovn_lflow *lflow_ref = NULL; >> @@ -7192,24 +7204,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); >> >> @@ -7227,8 +7227,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; >> } >> >> @@ -10231,7 +10232,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 { >> @@ -10247,14 +10248,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)); >> @@ -10263,7 +10264,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"; >> @@ -10281,9 +10282,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, ") || "); >> } >> @@ -10296,9 +10297,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 = >> @@ -10571,7 +10572,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); >> @@ -10581,7 +10582,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 553c0e48c3..8cd2427e8f 100644 >> --- a/ovn-nb.xml >> +++ b/ovn-nb.xml >> @@ -1905,8 +1905,66 @@ >> is applied reply to ARP/neighbor discovery requests for all VIPs >> of the load balancer. If set to <code>reachable</code>, then > routers >> on which the load balancer is applied reply to ARP/neighbor > discovery >> - 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>. >> + requests only for VIPs that are part of a router's subnet. If > set to >> + <code>none</code>, then routers on which the load balancer is > applied >> + never reply to ARP/neighbor discovery requests for any of the > load >> + balancer VIPs. Load balancers with > <code>options:template=true</code> >> + do not support <code>reachable</code> as a valid mode. The > default >> + value of this option, if not specified, is > <code>reachable</code> for >> + regular load balancers and <code>none</code> for template load >> + balancers. >> + </column> >> + >> + <column name="options" key="template"> >> + <p> >> + Option to be set to <code>true</code>, if the load balancer is > a >> + template. The load balancer VIPs and backends must be using >> + <ref table="Chassis_Template_Var"/> in their definitions. >> + </p> >> + >> + <p> >> + Load balancer template VIP supported formats are: >> + </p> >> + <pre> >> +^VIP_VAR[:^PORT_VAR|:port] >> + </pre> >> + >> + <p> >> + where <code>VIP_VAR</code> and <code>PORT_VAR</code> are names > of >> + <ref table="Chassis_Template_Var"/> records. > > In this version, the vars are not names but keys of the "variables" column. > True, I fixed it. >> + </p> >> + >> + <p> >> + Note: The VIP and PORT cannot be combined into a single > template >> + variable. For example, a <ref table="Chassis_Template_Var"/> >> + variable expanding to <code>10.0.0.1:8080</code> is not valid >> + if used as VIP. >> + </p> >> + >> + <p> >> + Load balancer template backend supported formats are: >> + </p> >> + <pre> >> +^BACKEND_VAR1[:^PORT_VAR1|:port],^BACKEND_VAR2[:^PORT_VAR2|:port] >> + >> +or >> + >> +^BACKENDS_VAR1,^BACKENDS_VAR2 > > I think here each var means a single backend IP, right? So, > s/BACKENDS/BACKEND/g > Not necessarily. There's actually no restriction. >> + </pre> >> + <p> >> + where <code>BACKEND_VAR1</code>, <code>PORT_VAR1</code>, >> + <code>BACKEND_VAR2</code>, <code>PORT_VAR2</code>, >> + <code>BACKENDS_VAR1</code> and <code>BACKENDS_VAR2</code> are > names > > Same here, and they are keys instead of "names". > > Acked-by: Han Zhou <hzhou@ovn.org> > Thanks, Han! I ended up with the following incremental. Let me know if it looks ok to you and I can fold it in. Regards, Dumitru --- diff --git a/TODO.rst b/TODO.rst index 53cf2870b2..15fd131d39 100644 --- a/TODO.rst +++ b/TODO.rst @@ -186,7 +186,4 @@ OVN To-do List * Load Balancer templates - * Support combining the VIP (or backend) IP and port into a single - template variable. - - * Support combining all backends into a single template variable. + * Support combining the VIP IP and port into a single template variable. diff --git a/ovn-nb.xml b/ovn-nb.xml index 7ecf2047e7..0edc3da96c 100644 --- a/ovn-nb.xml +++ b/ovn-nb.xml @@ -1958,8 +1958,9 @@ </pre> <p> - where <code>VIP_VAR</code> and <code>PORT_VAR</code> are names of - <ref table="Chassis_Template_Var"/> records. + where <code>VIP_VAR</code> and <code>PORT_VAR</code> are keys of + the <ref table="Chassis_Template_Var"/> <ref column="variables"/> + records. </p> <p> @@ -1982,8 +1983,9 @@ or <p> where <code>BACKEND_VAR1</code>, <code>PORT_VAR1</code>, <code>BACKEND_VAR2</code>, <code>PORT_VAR2</code>, - <code>BACKENDS_VAR1</code> and <code>BACKENDS_VAR2</code> are names - of <ref table="Chassis_Template_Var"/> records. + <code>BACKENDS_VAR1</code> and <code>BACKENDS_VAR2</code> are keys + of the <ref table="Chassis_Template_Var"/> <ref column="variables"/> + records. </p> </column> diff --git a/tests/ovn.at b/tests/ovn.at index bc3a7adfba..f3bd532423 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -33525,9 +33525,11 @@ dnl Create a few LBs that use "uninstantiated" templates. check ovn-nbctl --template lb-add lb-test1 "^VIP1:^VPORT1" "^BACKENDS1" tcp check ovn-nbctl --template lb-add lb-test2 "^VIP2:^VPORT2" "^BACKENDS21,^BACKENDS22" tcp check ovn-nbctl --template lb-add lb-test3 "^VIP3:^VPORT3" "^BACKENDS31:^BPORT1,^BACKENDS32:^BPORT2" tcp +check ovn-nbctl --template lb-add lb-test4 "^VIP4:^VPORT4" "^BACKENDS41,^BACKENDS42" tcp check ovn-nbctl ls-lb-add sw lb-test1 check ovn-nbctl ls-lb-add sw lb-test2 check ovn-nbctl ls-lb-add sw lb-test3 +check ovn-nbctl ls-lb-add sw lb-test4 check ovs-vsctl add-port br-int p1 -- set interface p1 external_ids:iface-id=lsp1 check ovs-vsctl add-port br-int p2 -- set interface p2 external_ids:iface-id=lsp2 @@ -33549,7 +33551,10 @@ check ovn-nbctl --wait=hv set Chassis_Template_Var hv1 \ variables:VIP3='43.43.43.3' variables:VPORT3='4303' \ variables:BACKENDS31='85.85.85.31' \ variables:BACKENDS32='85.85.85.32' \ - variables:BPORT1='8503' variables:BPORT2='8503' + variables:BPORT1='8503' variables:BPORT2='8503' \ + variables:VIP4='43.43.43.4' variables:VPORT4='4304' \ + variables:BACKENDS41='85.85.85.41:8504,85.85.85.42:8504' \ + variables:BACKENDS42='85.85.85.43:8504,85.85.85.44:8504' dnl Ensure the LBs are translated to OpenFlow. as hv1 @@ -33568,6 +33573,18 @@ AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=85.85.85.31:8503)' -c], [ AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=85.85.85.32:8503)' -c], [0], [dnl 1 ]) +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=85.85.85.41:8504)' -c], [0], [dnl +1 +]) +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=85.85.85.42:8504)' -c], [0], [dnl +1 +]) +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=85.85.85.43:8504)' -c], [0], [dnl +1 +]) +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=85.85.85.44:8504)' -c], [0], [dnl +1 +]) dnl Ensure hairpin flows are correct. as hv1 @@ -33577,6 +33594,10 @@ AT_CHECK([ovs-ofctl dump-flows br-int | grep table=68 | ofctl_strip_all], [0], [ table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b02,reg2=0x10ce/0xffff,nw_src=85.85.85.22,nw_dst=85.85.85.22,tp_dst=8502 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.2,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b03,reg2=0x10cf/0xffff,nw_src=85.85.85.31,nw_dst=85.85.85.31,tp_dst=8503 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.3,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b03,reg2=0x10cf/0xffff,nw_src=85.85.85.32,nw_dst=85.85.85.32,tp_dst=8503 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.3,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b04,reg2=0x10d0/0xffff,nw_src=85.85.85.41,nw_dst=85.85.85.41,tp_dst=8504 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b04,reg2=0x10d0/0xffff,nw_src=85.85.85.42,nw_dst=85.85.85.42,tp_dst=8504 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b04,reg2=0x10d0/0xffff,nw_src=85.85.85.43,nw_dst=85.85.85.43,tp_dst=8504 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b04,reg2=0x10d0/0xffff,nw_src=85.85.85.44,nw_dst=85.85.85.44,tp_dst=8504 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) ]) dnl Change Chassis_Template_Var mappings @@ -33589,7 +33610,10 @@ check ovn-nbctl --wait=hv set Chassis_Template_Var hv1 \ variables:VIP3='42.42.42.3' variables:VPORT3='4203' \ variables:BACKENDS31='84.84.84.31' \ variables:BACKENDS32='84.84.84.32' \ - variables:BPORT1='8403' variables:BPORT2='8403' + variables:BPORT1='8403' variables:BPORT2='8403' \ + variables:VIP4='42.42.42.4' variables:VPORT4='4204' \ + variables:BACKENDS41='84.84.84.41:8404,84.84.84.42:8404' \ + variables:BACKENDS42='84.84.84.43:8404,84.84.84.44:8404' dnl Ensure the LBs are translated to OpenFlow. as hv1 @@ -33608,6 +33632,18 @@ AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=84.84.84.31:8403)' -c], [ AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=84.84.84.32:8403)' -c], [0], [dnl 1 ]) +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=84.84.84.41:8404)' -c], [0], [dnl +1 +]) +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=84.84.84.42:8404)' -c], [0], [dnl +1 +]) +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=84.84.84.43:8404)' -c], [0], [dnl +1 +]) +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=84.84.84.44:8404)' -c], [0], [dnl +1 +]) dnl Ensure hairpin flows are correct. as hv1 @@ -33617,6 +33653,10 @@ AT_CHECK([ovs-ofctl dump-flows br-int | grep table=68 | ofctl_strip_all], [0], [ table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a02,reg2=0x106a/0xffff,nw_src=84.84.84.22,nw_dst=84.84.84.22,tp_dst=8402 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.2,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a03,reg2=0x106b/0xffff,nw_src=84.84.84.31,nw_dst=84.84.84.31,tp_dst=8403 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.3,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a03,reg2=0x106b/0xffff,nw_src=84.84.84.32,nw_dst=84.84.84.32,tp_dst=8403 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.3,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a04,reg2=0x106c/0xffff,nw_src=84.84.84.41,nw_dst=84.84.84.41,tp_dst=8404 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a04,reg2=0x106c/0xffff,nw_src=84.84.84.42,nw_dst=84.84.84.42,tp_dst=8404 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a04,reg2=0x106c/0xffff,nw_src=84.84.84.43,nw_dst=84.84.84.43,tp_dst=8404 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a04,reg2=0x106c/0xffff,nw_src=84.84.84.44,nw_dst=84.84.84.44,tp_dst=8404 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) ]) dnl Remove Chassis_Template_Variables and check that everything is ---
On Tue, Nov 29, 2022 at 5:16 AM Dumitru Ceara <dceara@redhat.com> wrote: > > On 11/29/22 09:14, Han Zhou wrote: > > On Tue, Nov 22, 2022 at 6:15 AM Dumitru Ceara <dceara@redhat.com> wrote: > >> > >> 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 > > > > Sorry if I missed, but I didn't see any tests that test the form > > "^backends_variable1,^backends_variable2". I only see tests with a single > > backend variable with a single IP in it. Better to test: > > 1. Multiple backends variables > > This should be covered. I have this in the test: > > check ovn-nbctl --template lb-add lb-test1 "^VIP1:^VPORT1" "^BACKENDS1" tcp > check ovn-nbctl --template lb-add lb-test2 "^VIP2:^VPORT2" > "^BACKENDS21,^BACKENDS22" tcp > check ovn-nbctl --template lb-add lb-test3 "^VIP3:^VPORT3" > "^BACKENDS31:^BPORT1,^BACKENDS32:^BPORT2" tcp > > > 2. Multiple IPs in a single variable (I saw this in the tutorial in patch > > 5, but better to be covered here, too) > > > > You're right, I added a LB with such a variable instantiation to the > test. > > > > > > >> > >> 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. > >> > >> Template load balancers don't support the "reachable" neighbor-responder > >> mode. Instead the CMS can explicitly configure the responder mode to > >> either "all" or "none". > >> > >> To properly handle template updates in ovn-controller we also add a > >> Chassis_Template_Var <- LB reference in ovn-controller. This way, when > >> a Chassis_Template_Var changes value all load balancers that refer to > >> it will also get updated. > >> > >> Signed-off-by: Dumitru Ceara <dceara@redhat.com> > >> --- > >> V3: > >> - Addressed Mark's comments: > >> - Added TODO items about potential future template LB improvements. > >> - Removed n_backends arg from ovn_lb_backends_init_explicit() and > >> ovn_lb_backends_init_template(). > >> - Fixed ovn_northd_lb_create() to first get the template option before > >> using its value. > >> - Fixed up comments and man pages. > >> - Hardenned setting of address family in ovn-nbctl. > >> > >> V2: > >> - Fix GCC build due to missing explicit return. > >> - Fix ls_in_pre_stateful flows due to using wrong lb field. > >> - Use new lexer_parse_template_string(). > >> - Changed lb_handle_changed_ref() signature to return bool. > >> - Update documentation with info about responder mode=none, LB template > >> supported formats, lb explicit address family requirements. > >> - Squashed the template LB patches into a single one > >> - Added more tests. > >> - Squashed the system tests patch into this one. > >> --- > >> TODO.rst | 7 + > >> controller/lflow.c | 115 +++++++++-- > >> controller/lflow.h | 7 + > >> controller/ovn-controller.c | 67 +++++- > >> lib/lb.c | 452 > > ++++++++++++++++++++++++++++++++++++++----- > >> lib/lb.h | 40 +++- > >> lib/ovn-util.c | 3 > >> northd/northd.c | 89 ++++---- > >> ovn-nb.xml | 62 ++++++ > >> tests/ovn-nbctl.at | 23 +- > >> tests/ovn-northd.at | 7 + > >> tests/ovn.at | 131 ++++++++++++ > >> tests/system-ovn.at | 183 +++++++++++++++++ > >> utilities/ovn-nbctl.c | 120 ++++++----- > >> 14 files changed, 1078 insertions(+), 228 deletions(-) > >> > >> diff --git a/TODO.rst b/TODO.rst > >> index fe5f9a2f30..53cf2870b2 100644 > >> --- a/TODO.rst > >> +++ b/TODO.rst > >> @@ -183,3 +183,10 @@ OVN To-do List > >> * Chassis_Template_Var > >> > >> * Support template variables when tracing packets with ovn-trace. > >> + > >> +* Load Balancer templates > >> + > >> + * Support combining the VIP (or backend) IP and port into a single > >> + template variable. > > > > Is it still a TODO for backends? At least the tutorial test (in patch 5) is > > already doing something like this: > > backends0="42.0.0.1:1,42.1.0.1:1,42.2.0.1:1,42.3.0.1:1,42.4.0.1:1" > > > > You're right, we can combine the backends and it works fine. I removed > the "(or backend)" part. > > >> + > >> + * Support combining all backends into a single template variable. > > > > What does it mean here? Isn't the tutorial test already combining multiple > > backends into a single variable? > > > > Yes, it is, and it's working fine. I'm not sure anymore why I had > added this TODO item here. I removed it now. > > >> diff --git a/controller/lflow.c b/controller/lflow.c > >> index 84625fb3f1..f6ac639541 100644 > >> --- a/controller/lflow.c > >> +++ b/controller/lflow.c > >> @@ -97,6 +97,15 @@ consider_logical_flow(const struct sbrec_logical_flow > > *lflow, > >> struct lflow_ctx_in *l_ctx_in, > >> struct lflow_ctx_out *l_ctx_out); > >> > >> +static void > >> +consider_lb_hairpin_flows(struct objdep_mgr *mgr, > >> + const struct sbrec_load_balancer *sbrec_lb, > >> + const struct hmap *local_datapaths, > >> + const struct smap *template_vars, > >> + bool use_ct_mark, > >> + struct ovn_desired_flow_table *flow_table, > >> + struct simap *ids); > >> + > >> static void add_port_sec_flows(const struct shash *binding_lports, > >> const struct sbrec_chassis *, > >> struct ovn_desired_flow_table *); > >> @@ -223,7 +232,7 @@ lflow_handle_changed_flows(struct lflow_ctx_in > > *l_ctx_in, > >> UUIDSET_INITIALIZER(&flood_remove_nodes); > >> SBREC_LOGICAL_FLOW_TABLE_FOR_EACH_TRACKED (lflow, > >> > > l_ctx_in->logical_flow_table) { > >> - if (uuidset_find(l_ctx_out->lflows_processed, > > &lflow->header_.uuid)) { > >> + if (uuidset_find(l_ctx_out->objs_processed, > > &lflow->header_.uuid)) { > >> VLOG_DBG("lflow "UUID_FMT"has been processed, skip.", > >> UUID_ARGS(&lflow->header_.uuid)); > >> continue; > >> @@ -253,14 +262,14 @@ lflow_handle_changed_flows(struct lflow_ctx_in > > *l_ctx_in, > >> UUID_ARGS(&lflow->header_.uuid)); > >> > >> /* For the extra lflows that need to be reprocessed because > > of the > >> - * flood remove, remove it from lflows_processed. */ > >> + * flood remove, remove it from objs_processed. */ > >> struct uuidset_node *unode = > >> - uuidset_find(l_ctx_out->lflows_processed, > >> + uuidset_find(l_ctx_out->objs_processed, > >> &lflow->header_.uuid); > >> if (unode) { > >> VLOG_DBG("lflow "UUID_FMT"has been processed, now > > reprocess.", > >> UUID_ARGS(&lflow->header_.uuid)); > >> - uuidset_delete(l_ctx_out->lflows_processed, unode); > >> + uuidset_delete(l_ctx_out->objs_processed, unode); > >> } > >> > >> consider_logical_flow(lflow, false, l_ctx_in, l_ctx_out); > >> @@ -687,7 +696,7 @@ lflow_handle_addr_set_update(const char *as_name, > >> struct object_to_resources_list_node *resource_list_node; > >> RESOURCE_FOR_EACH_OBJ (resource_list_node, resource_node) { > >> const struct uuid *obj_uuid = &resource_list_node->obj_uuid; > >> - if (uuidset_find(l_ctx_out->lflows_processed, obj_uuid)) { > >> + if (uuidset_find(l_ctx_out->objs_processed, obj_uuid)) { > >> VLOG_DBG("lflow "UUID_FMT"has been processed, skip.", > >> UUID_ARGS(obj_uuid)); > >> continue; > >> @@ -777,13 +786,13 @@ lflow_handle_changed_ref(enum objdep_type type, > > const char *res_name, > >> } > >> > >> /* For the extra lflows that need to be reprocessed because of > > the > >> - * flood remove, remove it from lflows_processed. */ > >> + * flood remove, remove it from objs_processed. */ > >> struct uuidset_node *unode = > >> - uuidset_find(l_ctx_out->lflows_processed, > > &lflow->header_.uuid); > >> + uuidset_find(l_ctx_out->objs_processed, > > &lflow->header_.uuid); > >> if (unode) { > >> VLOG_DBG("lflow "UUID_FMT"has been processed, now > > reprocess.", > >> UUID_ARGS(&lflow->header_.uuid)); > >> - uuidset_delete(l_ctx_out->lflows_processed, unode); > >> + uuidset_delete(l_ctx_out->objs_processed, unode); > >> } > >> > >> consider_logical_flow(lflow, false, l_ctx_in, l_ctx_out); > >> @@ -792,6 +801,43 @@ lflow_handle_changed_ref(enum objdep_type type, > > const char *res_name, > >> return true; > >> } > >> > >> +bool > >> +lb_handle_changed_ref(enum objdep_type type, const char *res_name, > >> + struct ovs_list *objs_todo, > >> + const void *in_arg, void *out_arg) > >> +{ > >> + struct lflow_ctx_in *l_ctx_in = CONST_CAST(struct lflow_ctx_in *, > > in_arg); > >> + struct lflow_ctx_out *l_ctx_out = out_arg; > >> + > >> + struct object_to_resources_list_node *resource_lb_uuid; > >> + LIST_FOR_EACH_POP (resource_lb_uuid, list_node, objs_todo) { > >> + VLOG_DBG("Reprocess LB "UUID_FMT" for resource type: %s, name: > > %s", > >> + UUID_ARGS(&resource_lb_uuid->obj_uuid), > >> + objdep_type_name(type), res_name); > >> + > >> + const struct sbrec_load_balancer *lb = > >> + sbrec_load_balancer_table_get_for_uuid( > >> + l_ctx_in->lb_table, &resource_lb_uuid->obj_uuid); > >> + if (!lb) { > >> + VLOG_DBG("Failed to find LB "UUID_FMT" referred by: %s", > > > > nit: I think it should be: Failed to find LB ... that refers ... > > > > Yes, fixed. > > > > >> + UUID_ARGS(&resource_lb_uuid->obj_uuid), res_name); > >> + } else { > >> + ofctrl_remove_flows(l_ctx_out->flow_table, > >> + &resource_lb_uuid->obj_uuid); > >> + > >> + consider_lb_hairpin_flows(l_ctx_out->lb_deps_mgr, 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); > >> + } > >> + > >> + free(resource_lb_uuid); > >> + } > >> + return true; > >> +} > >> + > >> static void > >> lflow_parse_ctrl_meter(const struct sbrec_logical_flow *lflow, > >> struct ovn_extend_table *meter_table, > >> @@ -1259,9 +1305,9 @@ consider_logical_flow(const struct > > sbrec_logical_flow *lflow, > >> > >> COVERAGE_INC(consider_logical_flow); > >> if (!is_recompute) { > >> - ovs_assert(!uuidset_find(l_ctx_out->lflows_processed, > >> + ovs_assert(!uuidset_find(l_ctx_out->objs_processed, > >> &lflow->header_.uuid)); > >> - uuidset_insert(l_ctx_out->lflows_processed, > > &lflow->header_.uuid); > >> + uuidset_insert(l_ctx_out->objs_processed, &lflow->header_.uuid); > >> } > >> > >> if (dp) { > >> @@ -2001,8 +2047,10 @@ add_lb_ct_snat_hairpin_flows(struct > > ovn_controller_lb *lb, > >> } > >> > >> static void > >> -consider_lb_hairpin_flows(const struct sbrec_load_balancer *sbrec_lb, > >> +consider_lb_hairpin_flows(struct objdep_mgr *mgr, > >> + const struct sbrec_load_balancer *sbrec_lb, > >> const struct hmap *local_datapaths, > >> + const struct smap *template_vars, > >> bool use_ct_mark, > >> struct ovn_desired_flow_table *flow_table, > >> struct simap *ids) > >> @@ -2039,7 +2087,9 @@ consider_lb_hairpin_flows(const struct > > sbrec_load_balancer *sbrec_lb, > >> return; > >> } > >> > >> - struct ovn_controller_lb *lb = ovn_controller_lb_create(sbrec_lb); > >> + struct sset template_vars_ref = SSET_INITIALIZER(&template_vars_ref); > >> + struct ovn_controller_lb *lb = > >> + ovn_controller_lb_create(sbrec_lb, template_vars, > > &template_vars_ref); > >> uint8_t lb_proto = IPPROTO_TCP; > >> if (lb->slb->protocol && lb->slb->protocol[0]) { > >> if (!strcmp(lb->slb->protocol, "udp")) { > >> @@ -2049,6 +2099,11 @@ consider_lb_hairpin_flows(const struct > > sbrec_load_balancer *sbrec_lb, > >> } > >> } > >> > >> + const char *tv_name; > >> + SSET_FOR_EACH (tv_name, &template_vars_ref) { > >> + objdep_mgr_add(mgr, OBJDEP_TYPE_TEMPLATE, tv_name, > >> + &sbrec_lb->header_.uuid); > >> + } > >> for (i = 0; i < lb->n_vips; i++) { > >> struct ovn_lb_vip *lb_vip = &lb->vips[i]; > >> > >> @@ -2063,13 +2118,17 @@ consider_lb_hairpin_flows(const struct > > sbrec_load_balancer *sbrec_lb, > >> add_lb_ct_snat_hairpin_flows(lb, id, lb_proto, flow_table); > >> > >> ovn_controller_lb_destroy(lb); > >> + sset_destroy(&template_vars_ref); > >> } > >> > >> /* Adds OpenFlow flows to flow tables for each Load balancer VIPs and > >> * 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, > >> +add_lb_hairpin_flows(struct objdep_mgr *mgr, > >> + const struct sbrec_load_balancer_table *lb_table, > >> + 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) > >> @@ -2092,8 +2151,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(mgr, lb, local_datapaths, > > template_vars, > >> + use_ct_mark, flow_table, ids); > >> } > >> } > >> > >> @@ -2229,7 +2288,9 @@ lflow_run(struct lflow_ctx_in *l_ctx_in, struct > > lflow_ctx_out *l_ctx_out) > >> l_ctx_in->static_mac_binding_table, > >> l_ctx_in->local_datapaths, > >> l_ctx_out->flow_table); > >> - add_lb_hairpin_flows(l_ctx_in->lb_table, l_ctx_in->local_datapaths, > >> + add_lb_hairpin_flows(l_ctx_out->lb_deps_mgr, 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, > >> @@ -2280,10 +2341,10 @@ lflow_add_flows_for_datapath(const struct > > sbrec_datapath_binding *dp, > >> const struct sbrec_logical_flow *lflow; > >> SBREC_LOGICAL_FLOW_FOR_EACH_EQUAL ( > >> lflow, lf_row, l_ctx_in->sbrec_logical_flow_by_logical_datapath) > > { > >> - if (uuidset_find(l_ctx_out->lflows_processed, > > &lflow->header_.uuid)) { > >> + if (uuidset_find(l_ctx_out->objs_processed, > > &lflow->header_.uuid)) { > >> continue; > >> } > >> - uuidset_insert(l_ctx_out->lflows_processed, > > &lflow->header_.uuid); > >> + uuidset_insert(l_ctx_out->objs_processed, &lflow->header_.uuid); > >> consider_logical_flow__(lflow, dp, l_ctx_in, l_ctx_out); > >> } > >> sbrec_logical_flow_index_destroy_row(lf_row); > >> @@ -2308,7 +2369,7 @@ lflow_add_flows_for_datapath(const struct > > sbrec_datapath_binding *dp, > >> sbrec_logical_flow_index_set_logical_dp_group(lf_row, ldpg); > >> SBREC_LOGICAL_FLOW_FOR_EACH_EQUAL ( > >> lflow, lf_row, > > l_ctx_in->sbrec_logical_flow_by_logical_dp_group) { > >> - if (uuidset_find(l_ctx_out->lflows_processed, > >> + if (uuidset_find(l_ctx_out->objs_processed, > >> &lflow->header_.uuid)) { > >> continue; > >> } > >> @@ -2360,7 +2421,9 @@ lflow_add_flows_for_datapath(const struct > > sbrec_datapath_binding *dp, > >> /* Add load balancer hairpin flows if the datapath has any load > > balancers > >> * associated. */ > >> for (size_t i = 0; i < n_dp_lbs; i++) { > >> - consider_lb_hairpin_flows(dp_lbs[i], l_ctx_in->local_datapaths, > >> + consider_lb_hairpin_flows(l_ctx_out->lb_deps_mgr, 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); > >> @@ -2382,7 +2445,7 @@ lflow_handle_flows_for_lport(const struct > > sbrec_port_binding *pb, > >> OBJDEP_TYPE_PORTBINDING, > >> pb->logical_port, > >> lflow_handle_changed_ref, > >> - l_ctx_out->lflows_processed, > >> + l_ctx_out->objs_processed, > >> l_ctx_in, l_ctx_out, &changed)) { > >> return false; > >> } > >> @@ -2421,7 +2484,7 @@ lflow_handle_changed_port_bindings(struct > > lflow_ctx_in *l_ctx_in, > >> OBJDEP_TYPE_PORTBINDING, > >> pb->logical_port, > >> lflow_handle_changed_ref, > >> - l_ctx_out->lflows_processed, > >> + l_ctx_out->objs_processed, > >> l_ctx_in, l_ctx_out, &changed)) { > >> ret = false; > >> break; > >> @@ -2448,7 +2511,7 @@ lflow_handle_changed_mc_groups(struct lflow_ctx_in > > *l_ctx_in, > >> if (!objdep_mgr_handle_change(l_ctx_out->lflow_deps_mgr, > >> OBJDEP_TYPE_MC_GROUP, > > ds_cstr(&mg_key), > >> lflow_handle_changed_ref, > >> - l_ctx_out->lflows_processed, > >> + l_ctx_out->objs_processed, > >> l_ctx_in, l_ctx_out, &changed)) { > >> ret = false; > >> break; > >> @@ -2502,7 +2565,9 @@ 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, > >> + consider_lb_hairpin_flows(l_ctx_out->lb_deps_mgr, 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/controller/lflow.h b/controller/lflow.h > >> index d95fd41142..9e8f9afd33 100644 > >> --- a/controller/lflow.h > >> +++ b/controller/lflow.h > >> @@ -122,9 +122,10 @@ struct lflow_ctx_out { > >> struct ovn_extend_table *group_table; > >> struct ovn_extend_table *meter_table; > >> struct objdep_mgr *lflow_deps_mgr; > >> + struct objdep_mgr *lb_deps_mgr; > >> struct lflow_cache *lflow_cache; > >> struct conj_ids *conj_ids; > >> - struct uuidset *lflows_processed; > >> + struct uuidset *objs_processed; > >> struct simap *hairpin_lb_ids; > >> struct id_pool *hairpin_id_pool; > >> }; > >> @@ -174,4 +175,8 @@ bool lflow_handle_changed_mc_groups(struct > > lflow_ctx_in *, > >> struct lflow_ctx_out *); > >> bool lflow_handle_changed_port_bindings(struct lflow_ctx_in *, > >> struct lflow_ctx_out *); > >> + > >> +bool lb_handle_changed_ref(enum objdep_type type, const char *res_name, > >> + struct ovs_list *objs_todo, > >> + const void *in_arg, void *out_arg); > >> #endif /* controller/lflow.h */ > >> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c > >> index f9ed0e3855..9807ecd8eb 100644 > >> --- a/controller/ovn-controller.c > >> +++ b/controller/ovn-controller.c > >> @@ -2791,13 +2791,15 @@ struct ed_type_lflow_output { > >> struct ovn_extend_table meter_table; > >> /* lflow <-> resource cross reference */ > >> struct objdep_mgr lflow_deps_mgr;; > >> + /* load balancer <-> resource cross reference */ > >> + struct objdep_mgr lb_deps_mgr; > >> /* conjunciton ID usage information of lflows */ > >> struct conj_ids conj_ids; > >> > >> - /* lflows processed in the current engine execution. > >> + /* objects (lflows and lbs) processed in the current engine > > execution. > >> * Cleared by en_lflow_output_clear_tracked_data before each engine > >> * execution. */ > >> - struct uuidset lflows_processed; > >> + struct uuidset objs_processed; > >> > >> /* Data which is persistent and not cleared during > >> * full recompute. */ > >> @@ -2954,8 +2956,9 @@ init_lflow_ctx(struct engine_node *node, > >> l_ctx_out->group_table = &fo->group_table; > >> l_ctx_out->meter_table = &fo->meter_table; > >> l_ctx_out->lflow_deps_mgr = &fo->lflow_deps_mgr; > >> + l_ctx_out->lb_deps_mgr = &fo->lb_deps_mgr; > >> l_ctx_out->conj_ids = &fo->conj_ids; > >> - l_ctx_out->lflows_processed = &fo->lflows_processed; > >> + l_ctx_out->objs_processed = &fo->objs_processed; > >> l_ctx_out->lflow_cache = fo->pd.lflow_cache; > >> l_ctx_out->hairpin_id_pool = fo->hd.pool; > >> l_ctx_out->hairpin_lb_ids = &fo->hd.ids; > >> @@ -2970,8 +2973,9 @@ en_lflow_output_init(struct engine_node *node > > OVS_UNUSED, > >> ovn_extend_table_init(&data->group_table); > >> ovn_extend_table_init(&data->meter_table); > >> objdep_mgr_init(&data->lflow_deps_mgr); > >> + objdep_mgr_init(&data->lb_deps_mgr); > >> lflow_conj_ids_init(&data->conj_ids); > >> - uuidset_init(&data->lflows_processed); > >> + uuidset_init(&data->objs_processed); > >> simap_init(&data->hd.ids); > >> data->hd.pool = id_pool_create(1, UINT32_MAX - 1); > >> nd_ra_opts_init(&data->nd_ra_opts); > >> @@ -2983,7 +2987,7 @@ static void > >> en_lflow_output_clear_tracked_data(void *data) > >> { > >> struct ed_type_lflow_output *flow_output_data = data; > >> - uuidset_clear(&flow_output_data->lflows_processed); > >> + uuidset_clear(&flow_output_data->objs_processed); > >> } > >> > >> static void > >> @@ -2994,8 +2998,9 @@ en_lflow_output_cleanup(void *data) > >> ovn_extend_table_destroy(&flow_output_data->group_table); > >> ovn_extend_table_destroy(&flow_output_data->meter_table); > >> objdep_mgr_destroy(&flow_output_data->lflow_deps_mgr); > >> + objdep_mgr_destroy(&flow_output_data->lb_deps_mgr); > >> lflow_conj_ids_destroy(&flow_output_data->conj_ids); > >> - uuidset_destroy(&flow_output_data->lflows_processed); > >> + uuidset_destroy(&flow_output_data->objs_processed); > >> lflow_cache_destroy(flow_output_data->pd.lflow_cache); > >> simap_destroy(&flow_output_data->hd.ids); > >> id_pool_destroy(flow_output_data->hd.pool); > >> @@ -3030,6 +3035,7 @@ en_lflow_output_run(struct engine_node *node, void > > *data) > >> struct ovn_extend_table *group_table = &fo->group_table; > >> struct ovn_extend_table *meter_table = &fo->meter_table; > >> struct objdep_mgr *lflow_deps_mgr = &fo->lflow_deps_mgr; > >> + struct objdep_mgr *lb_deps_mgr = &fo->lb_deps_mgr; > >> > >> static bool first_run = true; > >> if (first_run) { > >> @@ -3039,6 +3045,7 @@ en_lflow_output_run(struct engine_node *node, void > > *data) > >> ovn_extend_table_clear(group_table, false /* desired */); > >> ovn_extend_table_clear(meter_table, false /* desired */); > >> objdep_mgr_clear(lflow_deps_mgr); > >> + objdep_mgr_clear(lb_deps_mgr); > >> lflow_conj_ids_clear(&fo->conj_ids); > >> } > >> > >> @@ -3172,7 +3179,7 @@ lflow_output_addr_sets_handler(struct engine_node > > *node, void *data) > >> if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, > >> OBJDEP_TYPE_ADDRSET, ref_name, > >> lflow_handle_changed_ref, > >> - l_ctx_out.lflows_processed, > >> + l_ctx_out.objs_processed, > >> &l_ctx_in, &l_ctx_out, &changed)) { > >> return false; > >> } > >> @@ -3191,7 +3198,7 @@ lflow_output_addr_sets_handler(struct engine_node > > *node, void *data) > >> OBJDEP_TYPE_ADDRSET, > >> shash_node->name, > >> lflow_handle_changed_ref, > >> - l_ctx_out.lflows_processed, > >> + l_ctx_out.objs_processed, > >> &l_ctx_in, &l_ctx_out, > > &changed)) { > >> return false; > >> } > >> @@ -3204,7 +3211,7 @@ lflow_output_addr_sets_handler(struct engine_node > > *node, void *data) > >> if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, > >> OBJDEP_TYPE_ADDRSET, ref_name, > >> lflow_handle_changed_ref, > >> - l_ctx_out.lflows_processed, > >> + l_ctx_out.objs_processed, > >> &l_ctx_in, &l_ctx_out, &changed)) { > >> return false; > >> } > >> @@ -3239,7 +3246,7 @@ lflow_output_port_groups_handler(struct engine_node > > *node, void *data) > >> if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, > >> OBJDEP_TYPE_PORTGROUP, ref_name, > >> lflow_handle_changed_ref, > >> - l_ctx_out.lflows_processed, > >> + l_ctx_out.objs_processed, > >> &l_ctx_in, &l_ctx_out, &changed)) { > >> return false; > >> } > >> @@ -3251,7 +3258,7 @@ lflow_output_port_groups_handler(struct engine_node > > *node, void *data) > >> if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, > >> OBJDEP_TYPE_PORTGROUP, ref_name, > >> lflow_handle_changed_ref, > >> - l_ctx_out.lflows_processed, > >> + l_ctx_out.objs_processed, > >> &l_ctx_in, &l_ctx_out, &changed)) { > >> return false; > >> } > >> @@ -3263,7 +3270,7 @@ lflow_output_port_groups_handler(struct engine_node > > *node, void *data) > >> if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, > >> OBJDEP_TYPE_PORTGROUP, ref_name, > >> lflow_handle_changed_ref, > >> - l_ctx_out.lflows_processed, > >> + l_ctx_out.objs_processed, > >> &l_ctx_in, &l_ctx_out, &changed)) { > >> return false; > >> } > >> @@ -3297,7 +3304,17 @@ lflow_output_template_vars_handler(struct > > engine_node *node, void *data) > >> if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, > >> OBJDEP_TYPE_TEMPLATE, > >> res_name, lflow_handle_changed_ref, > >> - l_ctx_out.lflows_processed, > >> + l_ctx_out.objs_processed, > >> + &l_ctx_in, &l_ctx_out, &changed)) { > >> + return false; > >> + } > >> + if (changed) { > >> + engine_set_node_state(node, EN_UPDATED); > >> + } > >> + if (!objdep_mgr_handle_change(l_ctx_out.lb_deps_mgr, > >> + OBJDEP_TYPE_TEMPLATE, > >> + res_name, lb_handle_changed_ref, > >> + l_ctx_out.objs_processed, > >> &l_ctx_in, &l_ctx_out, &changed)) { > >> return false; > >> } > >> @@ -3309,7 +3326,17 @@ lflow_output_template_vars_handler(struct > > engine_node *node, void *data) > >> if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, > >> OBJDEP_TYPE_TEMPLATE, > >> res_name, lflow_handle_changed_ref, > >> - l_ctx_out.lflows_processed, > >> + l_ctx_out.objs_processed, > >> + &l_ctx_in, &l_ctx_out, &changed)) { > >> + return false; > >> + } > >> + if (changed) { > >> + engine_set_node_state(node, EN_UPDATED); > >> + } > >> + if (!objdep_mgr_handle_change(l_ctx_out.lb_deps_mgr, > >> + OBJDEP_TYPE_TEMPLATE, > >> + res_name, lb_handle_changed_ref, > >> + l_ctx_out.objs_processed, > >> &l_ctx_in, &l_ctx_out, &changed)) { > >> return false; > >> } > >> @@ -3321,7 +3348,17 @@ lflow_output_template_vars_handler(struct > > engine_node *node, void *data) > >> if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, > >> OBJDEP_TYPE_TEMPLATE, > >> res_name, lflow_handle_changed_ref, > >> - l_ctx_out.lflows_processed, > >> + l_ctx_out.objs_processed, > >> + &l_ctx_in, &l_ctx_out, &changed)) { > >> + return false; > >> + } > >> + if (changed) { > >> + engine_set_node_state(node, EN_UPDATED); > >> + } > >> + if (!objdep_mgr_handle_change(l_ctx_out.lb_deps_mgr, > >> + OBJDEP_TYPE_TEMPLATE, > >> + res_name, lb_handle_changed_ref, > >> + l_ctx_out.objs_processed, > >> &l_ctx_in, &l_ctx_out, &changed)) { > >> return false; > >> } > >> diff --git a/lib/lb.c b/lib/lb.c > >> index c08ccceda1..43628bba77 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,94 +82,293 @@ 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) > >> { > >> - int addr_family; > >> - > >> - if (!ip_address_and_port_from_lb_key(lb_key, &lb_vip->vip_str, > >> - &lb_vip->vip, &lb_vip->vip_port, > >> - &addr_family)) { > >> - return false; > >> - } > >> - > >> - /* 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; > >> + lb_vip->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 (lb_vip->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[lb_vip->n_backends]; > >> int backend_addr_family; > >> if (!ip_address_and_port_from_lb_key(token, &backend->ip_str, > >> &backend->ip, > > &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; > >> } > >> > >> - n_backends++; > >> + 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; > >> + lb_vip->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, &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; > >> + > >> + return ovn_lb_backends_init_explicit(lb_vip, lb_value); > >> +} > >> + > >> +/* 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_) > >> +{ > >> + struct ds errors = DS_EMPTY_INITIALIZER; > >> + char *value = xstrdup(value_); > >> + char *save_ptr = NULL; > >> + size_t n_allocated_backends = 0; > >> + lb_vip->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 (lb_vip->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[lb_vip->n_backends]; > >> + lb_backend->ip_str = backend_ip; > >> + lb_backend->port_str = backend_port; > >> + lb_backend->port = 0; > >> + lb_vip->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); > >> +} > >> + > >> +/* 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 > >> @@ -189,12 +399,113 @@ 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; > >> + } > >> + return false; > >> +} > >> + > >> +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 template = smap_get_bool(&nbrec_lb->options, "template", false); > >> 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"; > >> @@ -202,12 +513,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, > > 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 = template; > >> + > >> + const char *mode = smap_get(&nbrec_lb->options, > > "neighbor_responder"); > >> + lb->neigh_mode = ovn_lb_get_neigh_mode(nbrec_lb, mode, template); > >> + > >> uint32_t affinity_timeout = > >> smap_get_uint(&nbrec_lb->options, "affinity_timeout", 0); > >> if (affinity_timeout > UINT16_MAX) { > >> @@ -227,13 +542,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, > >> + 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, template); > >> if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { > >> sset_add(&lb->ips_v4, lb_vip->vip_str); > >> } else { > >> @@ -381,9 +702,12 @@ 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 sset *template_vars_ref) > >> { > >> struct ovn_controller_lb *lb = xzalloc(sizeof *lb); > >> + bool template = smap_get_bool(&sbrec_lb->options, "template", false); > >> > >> lb->slb = sbrec_lb; > >> lb->n_vips = smap_count(&sbrec_lb->vips); > >> @@ -395,10 +719,26 @@ 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; > >> + struct lex_str key_s = template > >> + ? lexer_parse_template_string(node->key, > >> + > > template_vars, > >> + > > template_vars_ref) > >> + : lex_str_use(node->key); > >> + struct lex_str value_s = template > >> + ? lexer_parse_template_string(node->value, > >> + > > template_vars, > >> + > > template_vars_ref) > >> + : lex_str_use(node->value); > >> + char *error = ovn_lb_vip_init_explicit(lb_vip, > >> + lex_str_get(&key_s), > >> + lex_str_get(&value_s)); > >> + if (error) { > >> + free(error); > >> + } else { > >> + n_vips++; > >> } > >> - n_vips++; > >> + lex_str_free(&key_s); > >> + lex_str_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 62843e4716..55a41ae0bc 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; > >> uint16_t affinity_timeout; > >> > >> struct sset ips_v4; > >> @@ -82,19 +84,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-northd. > >> + */ > >> 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. */ > >> @@ -174,7 +188,17 @@ 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, > >> + struct sset *template_vars_ref); > >> 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 597625a291..1f8d0b8add 100644 > >> --- a/lib/ovn-util.c > >> +++ b/lib/ovn-util.c > >> @@ -793,9 +793,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; > >> memset(ip, 0, sizeof(*ip)); > >> *port = 0; > >> diff --git a/northd/northd.c b/northd/northd.c > >> index 123127e9c1..c590c14818 100644 > >> --- a/northd/northd.c > >> +++ b/northd/northd.c > >> @@ -3740,6 +3740,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]; > >> @@ -4056,12 +4060,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); > >> @@ -4073,8 +4084,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; > >> > >> @@ -5834,16 +5846,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, > >> @@ -5854,7 +5866,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; > >> @@ -6910,7 +6922,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); > >> @@ -6921,7 +6933,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")) { > >> @@ -6934,14 +6946,14 @@ 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"); > >> > >> ds_put_format(match, "%s.dst == %s", ip_match, lb_vip->vip_str); > >> - 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", proto, > > lb_vip->port_str); > >> } > >> > >> struct ovn_lflow *lflow_ref = NULL; > >> @@ -7192,24 +7204,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); > >> > >> @@ -7227,8 +7227,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; > >> } > >> > >> @@ -10231,7 +10232,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 { > >> @@ -10247,14 +10248,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)); > >> @@ -10263,7 +10264,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"; > >> @@ -10281,9 +10282,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, ") || "); > >> } > >> @@ -10296,9 +10297,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 = > >> @@ -10571,7 +10572,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); > >> @@ -10581,7 +10582,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 553c0e48c3..8cd2427e8f 100644 > >> --- a/ovn-nb.xml > >> +++ b/ovn-nb.xml > >> @@ -1905,8 +1905,66 @@ > >> is applied reply to ARP/neighbor discovery requests for all VIPs > >> of the load balancer. If set to <code>reachable</code>, then > > routers > >> on which the load balancer is applied reply to ARP/neighbor > > discovery > >> - 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>. > >> + requests only for VIPs that are part of a router's subnet. If > > set to > >> + <code>none</code>, then routers on which the load balancer is > > applied > >> + never reply to ARP/neighbor discovery requests for any of the > > load > >> + balancer VIPs. Load balancers with > > <code>options:template=true</code> > >> + do not support <code>reachable</code> as a valid mode. The > > default > >> + value of this option, if not specified, is > > <code>reachable</code> for > >> + regular load balancers and <code>none</code> for template load > >> + balancers. > >> + </column> > >> + > >> + <column name="options" key="template"> > >> + <p> > >> + Option to be set to <code>true</code>, if the load balancer is > > a > >> + template. The load balancer VIPs and backends must be using > >> + <ref table="Chassis_Template_Var"/> in their definitions. > >> + </p> > >> + > >> + <p> > >> + Load balancer template VIP supported formats are: > >> + </p> > >> + <pre> > >> +^VIP_VAR[:^PORT_VAR|:port] > >> + </pre> > >> + > >> + <p> > >> + where <code>VIP_VAR</code> and <code>PORT_VAR</code> are names > > of > >> + <ref table="Chassis_Template_Var"/> records. > > > > In this version, the vars are not names but keys of the "variables" column. > > > > True, I fixed it. > > >> + </p> > >> + > >> + <p> > >> + Note: The VIP and PORT cannot be combined into a single > > template > >> + variable. For example, a <ref table="Chassis_Template_Var"/> > >> + variable expanding to <code>10.0.0.1:8080</code> is not valid > >> + if used as VIP. > >> + </p> > >> + > >> + <p> > >> + Load balancer template backend supported formats are: > >> + </p> > >> + <pre> > >> +^BACKEND_VAR1[:^PORT_VAR1|:port],^BACKEND_VAR2[:^PORT_VAR2|:port] > >> + > >> +or > >> + > >> +^BACKENDS_VAR1,^BACKENDS_VAR2 > > > > I think here each var means a single backend IP, right? So, > > s/BACKENDS/BACKEND/g > > > > Not necessarily. There's actually no restriction. Sorry I was intended to remove this comment but forgot to do that before sending. > > >> + </pre> > >> + <p> > >> + where <code>BACKEND_VAR1</code>, <code>PORT_VAR1</code>, > >> + <code>BACKEND_VAR2</code>, <code>PORT_VAR2</code>, > >> + <code>BACKENDS_VAR1</code> and <code>BACKENDS_VAR2</code> are > > names > > > > Same here, and they are keys instead of "names". > > > > Acked-by: Han Zhou <hzhou@ovn.org> > > > > Thanks, Han! I ended up with the following incremental. Let me > know if it looks ok to you and I can fold it in. > Yes, looks good to me. Thanks! Han > Regards, > Dumitru > > --- > diff --git a/TODO.rst b/TODO.rst > index 53cf2870b2..15fd131d39 100644 > --- a/TODO.rst > +++ b/TODO.rst > @@ -186,7 +186,4 @@ OVN To-do List > * Load Balancer templates > - * Support combining the VIP (or backend) IP and port into a single > - template variable. > - > - * Support combining all backends into a single template variable. > + * Support combining the VIP IP and port into a single template variable. > diff --git a/ovn-nb.xml b/ovn-nb.xml > index 7ecf2047e7..0edc3da96c 100644 > --- a/ovn-nb.xml > +++ b/ovn-nb.xml > @@ -1958,8 +1958,9 @@ > </pre> > <p> > - where <code>VIP_VAR</code> and <code>PORT_VAR</code> are names of > - <ref table="Chassis_Template_Var"/> records. > + where <code>VIP_VAR</code> and <code>PORT_VAR</code> are keys of > + the <ref table="Chassis_Template_Var"/> <ref column="variables"/> > + records. > </p> > <p> > @@ -1982,8 +1983,9 @@ or > <p> > where <code>BACKEND_VAR1</code>, <code>PORT_VAR1</code>, > <code>BACKEND_VAR2</code>, <code>PORT_VAR2</code>, > - <code>BACKENDS_VAR1</code> and <code>BACKENDS_VAR2</code> are > names > - of <ref table="Chassis_Template_Var"/> records. > + <code>BACKENDS_VAR1</code> and <code>BACKENDS_VAR2</code> are > keys > + of the <ref table="Chassis_Template_Var"/> <ref > column="variables"/> > + records. > </p> > </column> > diff --git a/tests/ovn.at b/tests/ovn.at > index bc3a7adfba..f3bd532423 100644 > --- a/tests/ovn.at > +++ b/tests/ovn.at > @@ -33525,9 +33525,11 @@ dnl Create a few LBs that use "uninstantiated" > templates. > check ovn-nbctl --template lb-add lb-test1 "^VIP1:^VPORT1" "^BACKENDS1" tcp > check ovn-nbctl --template lb-add lb-test2 "^VIP2:^VPORT2" > "^BACKENDS21,^BACKENDS22" tcp > check ovn-nbctl --template lb-add lb-test3 "^VIP3:^VPORT3" > "^BACKENDS31:^BPORT1,^BACKENDS32:^BPORT2" tcp > +check ovn-nbctl --template lb-add lb-test4 "^VIP4:^VPORT4" > "^BACKENDS41,^BACKENDS42" tcp > check ovn-nbctl ls-lb-add sw lb-test1 > check ovn-nbctl ls-lb-add sw lb-test2 > check ovn-nbctl ls-lb-add sw lb-test3 > +check ovn-nbctl ls-lb-add sw lb-test4 > check ovs-vsctl add-port br-int p1 -- set interface p1 > external_ids:iface-id=lsp1 > check ovs-vsctl add-port br-int p2 -- set interface p2 > external_ids:iface-id=lsp2 > @@ -33549,7 +33551,10 @@ check ovn-nbctl --wait=hv set > Chassis_Template_Var hv1 \ > variables:VIP3='43.43.43.3' variables:VPORT3='4303' \ > variables:BACKENDS31='85.85.85.31' \ > variables:BACKENDS32='85.85.85.32' \ > - variables:BPORT1='8503' variables:BPORT2='8503' > + variables:BPORT1='8503' variables:BPORT2='8503' \ > + variables:VIP4='43.43.43.4' variables:VPORT4='4304' \ > + variables:BACKENDS41='85.85.85.41:8504,85.85.85.42:8504' \ > + variables:BACKENDS42='85.85.85.43:8504,85.85.85.44:8504' > dnl Ensure the LBs are translated to OpenFlow. > as hv1 > @@ -33568,6 +33573,18 @@ AT_CHECK([ovs-ofctl dump-groups br-int | grep > 'nat(dst=85.85.85.31:8503)' -c], [ > AT_CHECK([ovs-ofctl dump-groups br-int | grep > 'nat(dst=85.85.85.32:8503)' -c], [0], [dnl > 1 > ]) > +AT_CHECK([ovs-ofctl dump-groups br-int | grep > 'nat(dst=85.85.85.41:8504)' -c], [0], [dnl > +1 > +]) > +AT_CHECK([ovs-ofctl dump-groups br-int | grep > 'nat(dst=85.85.85.42:8504)' -c], [0], [dnl > +1 > +]) > +AT_CHECK([ovs-ofctl dump-groups br-int | grep > 'nat(dst=85.85.85.43:8504)' -c], [0], [dnl > +1 > +]) > +AT_CHECK([ovs-ofctl dump-groups br-int | grep > 'nat(dst=85.85.85.44:8504)' -c], [0], [dnl > +1 > +]) > dnl Ensure hairpin flows are correct. > as hv1 > @@ -33577,6 +33594,10 @@ AT_CHECK([ovs-ofctl dump-flows br-int | grep > table=68 | ofctl_strip_all], [0], [ > table=68, > priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b02,reg2=0x10ce/0xffff,nw_src=85.85.85.22,nw_dst=85.85.85.22,tp_dst=8502 > actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.2,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > table=68, > priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b03,reg2=0x10cf/0xffff,nw_src=85.85.85.31,nw_dst=85.85.85.31,tp_dst=8503 > actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.3,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > table=68, > priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b03,reg2=0x10cf/0xffff,nw_src=85.85.85.32,nw_dst=85.85.85.32,tp_dst=8503 > actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.3,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > + table=68, > priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b04,reg2=0x10d0/0xffff,nw_src=85.85.85.41,nw_dst=85.85.85.41,tp_dst=8504 > actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > + table=68, > priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b04,reg2=0x10d0/0xffff,nw_src=85.85.85.42,nw_dst=85.85.85.42,tp_dst=8504 > actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > + table=68, > priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b04,reg2=0x10d0/0xffff,nw_src=85.85.85.43,nw_dst=85.85.85.43,tp_dst=8504 > actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > + table=68, > priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b04,reg2=0x10d0/0xffff,nw_src=85.85.85.44,nw_dst=85.85.85.44,tp_dst=8504 > actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > ]) > dnl Change Chassis_Template_Var mappings > @@ -33589,7 +33610,10 @@ check ovn-nbctl --wait=hv set > Chassis_Template_Var hv1 \ > variables:VIP3='42.42.42.3' variables:VPORT3='4203' \ > variables:BACKENDS31='84.84.84.31' \ > variables:BACKENDS32='84.84.84.32' \ > - variables:BPORT1='8403' variables:BPORT2='8403' > + variables:BPORT1='8403' variables:BPORT2='8403' \ > + variables:VIP4='42.42.42.4' variables:VPORT4='4204' \ > + variables:BACKENDS41='84.84.84.41:8404,84.84.84.42:8404' \ > + variables:BACKENDS42='84.84.84.43:8404,84.84.84.44:8404' > dnl Ensure the LBs are translated to OpenFlow. > as hv1 > @@ -33608,6 +33632,18 @@ AT_CHECK([ovs-ofctl dump-groups br-int | grep > 'nat(dst=84.84.84.31:8403)' -c], [ > AT_CHECK([ovs-ofctl dump-groups br-int | grep > 'nat(dst=84.84.84.32:8403)' -c], [0], [dnl > 1 > ]) > +AT_CHECK([ovs-ofctl dump-groups br-int | grep > 'nat(dst=84.84.84.41:8404)' -c], [0], [dnl > +1 > +]) > +AT_CHECK([ovs-ofctl dump-groups br-int | grep > 'nat(dst=84.84.84.42:8404)' -c], [0], [dnl > +1 > +]) > +AT_CHECK([ovs-ofctl dump-groups br-int | grep > 'nat(dst=84.84.84.43:8404)' -c], [0], [dnl > +1 > +]) > +AT_CHECK([ovs-ofctl dump-groups br-int | grep > 'nat(dst=84.84.84.44:8404)' -c], [0], [dnl > +1 > +]) > dnl Ensure hairpin flows are correct. > as hv1 > @@ -33617,6 +33653,10 @@ AT_CHECK([ovs-ofctl dump-flows br-int | grep > table=68 | ofctl_strip_all], [0], [ > table=68, > priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a02,reg2=0x106a/0xffff,nw_src=84.84.84.22,nw_dst=84.84.84.22,tp_dst=8402 > actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.2,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > table=68, > priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a03,reg2=0x106b/0xffff,nw_src=84.84.84.31,nw_dst=84.84.84.31,tp_dst=8403 > actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.3,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > table=68, > priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a03,reg2=0x106b/0xffff,nw_src=84.84.84.32,nw_dst=84.84.84.32,tp_dst=8403 > actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.3,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > + table=68, > priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a04,reg2=0x106c/0xffff,nw_src=84.84.84.41,nw_dst=84.84.84.41,tp_dst=8404 > actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > + table=68, > priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a04,reg2=0x106c/0xffff,nw_src=84.84.84.42,nw_dst=84.84.84.42,tp_dst=8404 > actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > + table=68, > priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a04,reg2=0x106c/0xffff,nw_src=84.84.84.43,nw_dst=84.84.84.43,tp_dst=8404 > actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > + table=68, > priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a04,reg2=0x106c/0xffff,nw_src=84.84.84.44,nw_dst=84.84.84.44,tp_dst=8404 > actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) > ]) > dnl Remove Chassis_Template_Variables and check that everything is > --- >
diff --git a/TODO.rst b/TODO.rst index fe5f9a2f30..53cf2870b2 100644 --- a/TODO.rst +++ b/TODO.rst @@ -183,3 +183,10 @@ OVN To-do List * Chassis_Template_Var * Support template variables when tracing packets with ovn-trace. + +* Load Balancer templates + + * Support combining the VIP (or backend) IP and port into a single + template variable. + + * Support combining all backends into a single template variable. diff --git a/controller/lflow.c b/controller/lflow.c index 84625fb3f1..f6ac639541 100644 --- a/controller/lflow.c +++ b/controller/lflow.c @@ -97,6 +97,15 @@ consider_logical_flow(const struct sbrec_logical_flow *lflow, struct lflow_ctx_in *l_ctx_in, struct lflow_ctx_out *l_ctx_out); +static void +consider_lb_hairpin_flows(struct objdep_mgr *mgr, + const struct sbrec_load_balancer *sbrec_lb, + const struct hmap *local_datapaths, + const struct smap *template_vars, + bool use_ct_mark, + struct ovn_desired_flow_table *flow_table, + struct simap *ids); + static void add_port_sec_flows(const struct shash *binding_lports, const struct sbrec_chassis *, struct ovn_desired_flow_table *); @@ -223,7 +232,7 @@ lflow_handle_changed_flows(struct lflow_ctx_in *l_ctx_in, UUIDSET_INITIALIZER(&flood_remove_nodes); SBREC_LOGICAL_FLOW_TABLE_FOR_EACH_TRACKED (lflow, l_ctx_in->logical_flow_table) { - if (uuidset_find(l_ctx_out->lflows_processed, &lflow->header_.uuid)) { + if (uuidset_find(l_ctx_out->objs_processed, &lflow->header_.uuid)) { VLOG_DBG("lflow "UUID_FMT"has been processed, skip.", UUID_ARGS(&lflow->header_.uuid)); continue; @@ -253,14 +262,14 @@ lflow_handle_changed_flows(struct lflow_ctx_in *l_ctx_in, UUID_ARGS(&lflow->header_.uuid)); /* For the extra lflows that need to be reprocessed because of the - * flood remove, remove it from lflows_processed. */ + * flood remove, remove it from objs_processed. */ struct uuidset_node *unode = - uuidset_find(l_ctx_out->lflows_processed, + uuidset_find(l_ctx_out->objs_processed, &lflow->header_.uuid); if (unode) { VLOG_DBG("lflow "UUID_FMT"has been processed, now reprocess.", UUID_ARGS(&lflow->header_.uuid)); - uuidset_delete(l_ctx_out->lflows_processed, unode); + uuidset_delete(l_ctx_out->objs_processed, unode); } consider_logical_flow(lflow, false, l_ctx_in, l_ctx_out); @@ -687,7 +696,7 @@ lflow_handle_addr_set_update(const char *as_name, struct object_to_resources_list_node *resource_list_node; RESOURCE_FOR_EACH_OBJ (resource_list_node, resource_node) { const struct uuid *obj_uuid = &resource_list_node->obj_uuid; - if (uuidset_find(l_ctx_out->lflows_processed, obj_uuid)) { + if (uuidset_find(l_ctx_out->objs_processed, obj_uuid)) { VLOG_DBG("lflow "UUID_FMT"has been processed, skip.", UUID_ARGS(obj_uuid)); continue; @@ -777,13 +786,13 @@ lflow_handle_changed_ref(enum objdep_type type, const char *res_name, } /* For the extra lflows that need to be reprocessed because of the - * flood remove, remove it from lflows_processed. */ + * flood remove, remove it from objs_processed. */ struct uuidset_node *unode = - uuidset_find(l_ctx_out->lflows_processed, &lflow->header_.uuid); + uuidset_find(l_ctx_out->objs_processed, &lflow->header_.uuid); if (unode) { VLOG_DBG("lflow "UUID_FMT"has been processed, now reprocess.", UUID_ARGS(&lflow->header_.uuid)); - uuidset_delete(l_ctx_out->lflows_processed, unode); + uuidset_delete(l_ctx_out->objs_processed, unode); } consider_logical_flow(lflow, false, l_ctx_in, l_ctx_out); @@ -792,6 +801,43 @@ lflow_handle_changed_ref(enum objdep_type type, const char *res_name, return true; } +bool +lb_handle_changed_ref(enum objdep_type type, const char *res_name, + struct ovs_list *objs_todo, + const void *in_arg, void *out_arg) +{ + struct lflow_ctx_in *l_ctx_in = CONST_CAST(struct lflow_ctx_in *, in_arg); + struct lflow_ctx_out *l_ctx_out = out_arg; + + struct object_to_resources_list_node *resource_lb_uuid; + LIST_FOR_EACH_POP (resource_lb_uuid, list_node, objs_todo) { + VLOG_DBG("Reprocess LB "UUID_FMT" for resource type: %s, name: %s", + UUID_ARGS(&resource_lb_uuid->obj_uuid), + objdep_type_name(type), res_name); + + const struct sbrec_load_balancer *lb = + sbrec_load_balancer_table_get_for_uuid( + l_ctx_in->lb_table, &resource_lb_uuid->obj_uuid); + if (!lb) { + VLOG_DBG("Failed to find LB "UUID_FMT" referred by: %s", + UUID_ARGS(&resource_lb_uuid->obj_uuid), res_name); + } else { + ofctrl_remove_flows(l_ctx_out->flow_table, + &resource_lb_uuid->obj_uuid); + + consider_lb_hairpin_flows(l_ctx_out->lb_deps_mgr, 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); + } + + free(resource_lb_uuid); + } + return true; +} + static void lflow_parse_ctrl_meter(const struct sbrec_logical_flow *lflow, struct ovn_extend_table *meter_table, @@ -1259,9 +1305,9 @@ consider_logical_flow(const struct sbrec_logical_flow *lflow, COVERAGE_INC(consider_logical_flow); if (!is_recompute) { - ovs_assert(!uuidset_find(l_ctx_out->lflows_processed, + ovs_assert(!uuidset_find(l_ctx_out->objs_processed, &lflow->header_.uuid)); - uuidset_insert(l_ctx_out->lflows_processed, &lflow->header_.uuid); + uuidset_insert(l_ctx_out->objs_processed, &lflow->header_.uuid); } if (dp) { @@ -2001,8 +2047,10 @@ add_lb_ct_snat_hairpin_flows(struct ovn_controller_lb *lb, } static void -consider_lb_hairpin_flows(const struct sbrec_load_balancer *sbrec_lb, +consider_lb_hairpin_flows(struct objdep_mgr *mgr, + const struct sbrec_load_balancer *sbrec_lb, const struct hmap *local_datapaths, + const struct smap *template_vars, bool use_ct_mark, struct ovn_desired_flow_table *flow_table, struct simap *ids) @@ -2039,7 +2087,9 @@ consider_lb_hairpin_flows(const struct sbrec_load_balancer *sbrec_lb, return; } - struct ovn_controller_lb *lb = ovn_controller_lb_create(sbrec_lb); + struct sset template_vars_ref = SSET_INITIALIZER(&template_vars_ref); + struct ovn_controller_lb *lb = + ovn_controller_lb_create(sbrec_lb, template_vars, &template_vars_ref); uint8_t lb_proto = IPPROTO_TCP; if (lb->slb->protocol && lb->slb->protocol[0]) { if (!strcmp(lb->slb->protocol, "udp")) { @@ -2049,6 +2099,11 @@ consider_lb_hairpin_flows(const struct sbrec_load_balancer *sbrec_lb, } } + const char *tv_name; + SSET_FOR_EACH (tv_name, &template_vars_ref) { + objdep_mgr_add(mgr, OBJDEP_TYPE_TEMPLATE, tv_name, + &sbrec_lb->header_.uuid); + } for (i = 0; i < lb->n_vips; i++) { struct ovn_lb_vip *lb_vip = &lb->vips[i]; @@ -2063,13 +2118,17 @@ consider_lb_hairpin_flows(const struct sbrec_load_balancer *sbrec_lb, add_lb_ct_snat_hairpin_flows(lb, id, lb_proto, flow_table); ovn_controller_lb_destroy(lb); + sset_destroy(&template_vars_ref); } /* Adds OpenFlow flows to flow tables for each Load balancer VIPs and * 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, +add_lb_hairpin_flows(struct objdep_mgr *mgr, + const struct sbrec_load_balancer_table *lb_table, + 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) @@ -2092,8 +2151,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(mgr, lb, local_datapaths, template_vars, + use_ct_mark, flow_table, ids); } } @@ -2229,7 +2288,9 @@ lflow_run(struct lflow_ctx_in *l_ctx_in, struct lflow_ctx_out *l_ctx_out) l_ctx_in->static_mac_binding_table, l_ctx_in->local_datapaths, l_ctx_out->flow_table); - add_lb_hairpin_flows(l_ctx_in->lb_table, l_ctx_in->local_datapaths, + add_lb_hairpin_flows(l_ctx_out->lb_deps_mgr, 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, @@ -2280,10 +2341,10 @@ lflow_add_flows_for_datapath(const struct sbrec_datapath_binding *dp, const struct sbrec_logical_flow *lflow; SBREC_LOGICAL_FLOW_FOR_EACH_EQUAL ( lflow, lf_row, l_ctx_in->sbrec_logical_flow_by_logical_datapath) { - if (uuidset_find(l_ctx_out->lflows_processed, &lflow->header_.uuid)) { + if (uuidset_find(l_ctx_out->objs_processed, &lflow->header_.uuid)) { continue; } - uuidset_insert(l_ctx_out->lflows_processed, &lflow->header_.uuid); + uuidset_insert(l_ctx_out->objs_processed, &lflow->header_.uuid); consider_logical_flow__(lflow, dp, l_ctx_in, l_ctx_out); } sbrec_logical_flow_index_destroy_row(lf_row); @@ -2308,7 +2369,7 @@ lflow_add_flows_for_datapath(const struct sbrec_datapath_binding *dp, sbrec_logical_flow_index_set_logical_dp_group(lf_row, ldpg); SBREC_LOGICAL_FLOW_FOR_EACH_EQUAL ( lflow, lf_row, l_ctx_in->sbrec_logical_flow_by_logical_dp_group) { - if (uuidset_find(l_ctx_out->lflows_processed, + if (uuidset_find(l_ctx_out->objs_processed, &lflow->header_.uuid)) { continue; } @@ -2360,7 +2421,9 @@ lflow_add_flows_for_datapath(const struct sbrec_datapath_binding *dp, /* Add load balancer hairpin flows if the datapath has any load balancers * associated. */ for (size_t i = 0; i < n_dp_lbs; i++) { - consider_lb_hairpin_flows(dp_lbs[i], l_ctx_in->local_datapaths, + consider_lb_hairpin_flows(l_ctx_out->lb_deps_mgr, 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); @@ -2382,7 +2445,7 @@ lflow_handle_flows_for_lport(const struct sbrec_port_binding *pb, OBJDEP_TYPE_PORTBINDING, pb->logical_port, lflow_handle_changed_ref, - l_ctx_out->lflows_processed, + l_ctx_out->objs_processed, l_ctx_in, l_ctx_out, &changed)) { return false; } @@ -2421,7 +2484,7 @@ lflow_handle_changed_port_bindings(struct lflow_ctx_in *l_ctx_in, OBJDEP_TYPE_PORTBINDING, pb->logical_port, lflow_handle_changed_ref, - l_ctx_out->lflows_processed, + l_ctx_out->objs_processed, l_ctx_in, l_ctx_out, &changed)) { ret = false; break; @@ -2448,7 +2511,7 @@ lflow_handle_changed_mc_groups(struct lflow_ctx_in *l_ctx_in, if (!objdep_mgr_handle_change(l_ctx_out->lflow_deps_mgr, OBJDEP_TYPE_MC_GROUP, ds_cstr(&mg_key), lflow_handle_changed_ref, - l_ctx_out->lflows_processed, + l_ctx_out->objs_processed, l_ctx_in, l_ctx_out, &changed)) { ret = false; break; @@ -2502,7 +2565,9 @@ 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, + consider_lb_hairpin_flows(l_ctx_out->lb_deps_mgr, 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/controller/lflow.h b/controller/lflow.h index d95fd41142..9e8f9afd33 100644 --- a/controller/lflow.h +++ b/controller/lflow.h @@ -122,9 +122,10 @@ struct lflow_ctx_out { struct ovn_extend_table *group_table; struct ovn_extend_table *meter_table; struct objdep_mgr *lflow_deps_mgr; + struct objdep_mgr *lb_deps_mgr; struct lflow_cache *lflow_cache; struct conj_ids *conj_ids; - struct uuidset *lflows_processed; + struct uuidset *objs_processed; struct simap *hairpin_lb_ids; struct id_pool *hairpin_id_pool; }; @@ -174,4 +175,8 @@ bool lflow_handle_changed_mc_groups(struct lflow_ctx_in *, struct lflow_ctx_out *); bool lflow_handle_changed_port_bindings(struct lflow_ctx_in *, struct lflow_ctx_out *); + +bool lb_handle_changed_ref(enum objdep_type type, const char *res_name, + struct ovs_list *objs_todo, + const void *in_arg, void *out_arg); #endif /* controller/lflow.h */ diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c index f9ed0e3855..9807ecd8eb 100644 --- a/controller/ovn-controller.c +++ b/controller/ovn-controller.c @@ -2791,13 +2791,15 @@ struct ed_type_lflow_output { struct ovn_extend_table meter_table; /* lflow <-> resource cross reference */ struct objdep_mgr lflow_deps_mgr;; + /* load balancer <-> resource cross reference */ + struct objdep_mgr lb_deps_mgr; /* conjunciton ID usage information of lflows */ struct conj_ids conj_ids; - /* lflows processed in the current engine execution. + /* objects (lflows and lbs) processed in the current engine execution. * Cleared by en_lflow_output_clear_tracked_data before each engine * execution. */ - struct uuidset lflows_processed; + struct uuidset objs_processed; /* Data which is persistent and not cleared during * full recompute. */ @@ -2954,8 +2956,9 @@ init_lflow_ctx(struct engine_node *node, l_ctx_out->group_table = &fo->group_table; l_ctx_out->meter_table = &fo->meter_table; l_ctx_out->lflow_deps_mgr = &fo->lflow_deps_mgr; + l_ctx_out->lb_deps_mgr = &fo->lb_deps_mgr; l_ctx_out->conj_ids = &fo->conj_ids; - l_ctx_out->lflows_processed = &fo->lflows_processed; + l_ctx_out->objs_processed = &fo->objs_processed; l_ctx_out->lflow_cache = fo->pd.lflow_cache; l_ctx_out->hairpin_id_pool = fo->hd.pool; l_ctx_out->hairpin_lb_ids = &fo->hd.ids; @@ -2970,8 +2973,9 @@ en_lflow_output_init(struct engine_node *node OVS_UNUSED, ovn_extend_table_init(&data->group_table); ovn_extend_table_init(&data->meter_table); objdep_mgr_init(&data->lflow_deps_mgr); + objdep_mgr_init(&data->lb_deps_mgr); lflow_conj_ids_init(&data->conj_ids); - uuidset_init(&data->lflows_processed); + uuidset_init(&data->objs_processed); simap_init(&data->hd.ids); data->hd.pool = id_pool_create(1, UINT32_MAX - 1); nd_ra_opts_init(&data->nd_ra_opts); @@ -2983,7 +2987,7 @@ static void en_lflow_output_clear_tracked_data(void *data) { struct ed_type_lflow_output *flow_output_data = data; - uuidset_clear(&flow_output_data->lflows_processed); + uuidset_clear(&flow_output_data->objs_processed); } static void @@ -2994,8 +2998,9 @@ en_lflow_output_cleanup(void *data) ovn_extend_table_destroy(&flow_output_data->group_table); ovn_extend_table_destroy(&flow_output_data->meter_table); objdep_mgr_destroy(&flow_output_data->lflow_deps_mgr); + objdep_mgr_destroy(&flow_output_data->lb_deps_mgr); lflow_conj_ids_destroy(&flow_output_data->conj_ids); - uuidset_destroy(&flow_output_data->lflows_processed); + uuidset_destroy(&flow_output_data->objs_processed); lflow_cache_destroy(flow_output_data->pd.lflow_cache); simap_destroy(&flow_output_data->hd.ids); id_pool_destroy(flow_output_data->hd.pool); @@ -3030,6 +3035,7 @@ en_lflow_output_run(struct engine_node *node, void *data) struct ovn_extend_table *group_table = &fo->group_table; struct ovn_extend_table *meter_table = &fo->meter_table; struct objdep_mgr *lflow_deps_mgr = &fo->lflow_deps_mgr; + struct objdep_mgr *lb_deps_mgr = &fo->lb_deps_mgr; static bool first_run = true; if (first_run) { @@ -3039,6 +3045,7 @@ en_lflow_output_run(struct engine_node *node, void *data) ovn_extend_table_clear(group_table, false /* desired */); ovn_extend_table_clear(meter_table, false /* desired */); objdep_mgr_clear(lflow_deps_mgr); + objdep_mgr_clear(lb_deps_mgr); lflow_conj_ids_clear(&fo->conj_ids); } @@ -3172,7 +3179,7 @@ lflow_output_addr_sets_handler(struct engine_node *node, void *data) if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, OBJDEP_TYPE_ADDRSET, ref_name, lflow_handle_changed_ref, - l_ctx_out.lflows_processed, + l_ctx_out.objs_processed, &l_ctx_in, &l_ctx_out, &changed)) { return false; } @@ -3191,7 +3198,7 @@ lflow_output_addr_sets_handler(struct engine_node *node, void *data) OBJDEP_TYPE_ADDRSET, shash_node->name, lflow_handle_changed_ref, - l_ctx_out.lflows_processed, + l_ctx_out.objs_processed, &l_ctx_in, &l_ctx_out, &changed)) { return false; } @@ -3204,7 +3211,7 @@ lflow_output_addr_sets_handler(struct engine_node *node, void *data) if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, OBJDEP_TYPE_ADDRSET, ref_name, lflow_handle_changed_ref, - l_ctx_out.lflows_processed, + l_ctx_out.objs_processed, &l_ctx_in, &l_ctx_out, &changed)) { return false; } @@ -3239,7 +3246,7 @@ lflow_output_port_groups_handler(struct engine_node *node, void *data) if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, OBJDEP_TYPE_PORTGROUP, ref_name, lflow_handle_changed_ref, - l_ctx_out.lflows_processed, + l_ctx_out.objs_processed, &l_ctx_in, &l_ctx_out, &changed)) { return false; } @@ -3251,7 +3258,7 @@ lflow_output_port_groups_handler(struct engine_node *node, void *data) if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, OBJDEP_TYPE_PORTGROUP, ref_name, lflow_handle_changed_ref, - l_ctx_out.lflows_processed, + l_ctx_out.objs_processed, &l_ctx_in, &l_ctx_out, &changed)) { return false; } @@ -3263,7 +3270,7 @@ lflow_output_port_groups_handler(struct engine_node *node, void *data) if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, OBJDEP_TYPE_PORTGROUP, ref_name, lflow_handle_changed_ref, - l_ctx_out.lflows_processed, + l_ctx_out.objs_processed, &l_ctx_in, &l_ctx_out, &changed)) { return false; } @@ -3297,7 +3304,17 @@ lflow_output_template_vars_handler(struct engine_node *node, void *data) if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, OBJDEP_TYPE_TEMPLATE, res_name, lflow_handle_changed_ref, - l_ctx_out.lflows_processed, + l_ctx_out.objs_processed, + &l_ctx_in, &l_ctx_out, &changed)) { + return false; + } + if (changed) { + engine_set_node_state(node, EN_UPDATED); + } + if (!objdep_mgr_handle_change(l_ctx_out.lb_deps_mgr, + OBJDEP_TYPE_TEMPLATE, + res_name, lb_handle_changed_ref, + l_ctx_out.objs_processed, &l_ctx_in, &l_ctx_out, &changed)) { return false; } @@ -3309,7 +3326,17 @@ lflow_output_template_vars_handler(struct engine_node *node, void *data) if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, OBJDEP_TYPE_TEMPLATE, res_name, lflow_handle_changed_ref, - l_ctx_out.lflows_processed, + l_ctx_out.objs_processed, + &l_ctx_in, &l_ctx_out, &changed)) { + return false; + } + if (changed) { + engine_set_node_state(node, EN_UPDATED); + } + if (!objdep_mgr_handle_change(l_ctx_out.lb_deps_mgr, + OBJDEP_TYPE_TEMPLATE, + res_name, lb_handle_changed_ref, + l_ctx_out.objs_processed, &l_ctx_in, &l_ctx_out, &changed)) { return false; } @@ -3321,7 +3348,17 @@ lflow_output_template_vars_handler(struct engine_node *node, void *data) if (!objdep_mgr_handle_change(l_ctx_out.lflow_deps_mgr, OBJDEP_TYPE_TEMPLATE, res_name, lflow_handle_changed_ref, - l_ctx_out.lflows_processed, + l_ctx_out.objs_processed, + &l_ctx_in, &l_ctx_out, &changed)) { + return false; + } + if (changed) { + engine_set_node_state(node, EN_UPDATED); + } + if (!objdep_mgr_handle_change(l_ctx_out.lb_deps_mgr, + OBJDEP_TYPE_TEMPLATE, + res_name, lb_handle_changed_ref, + l_ctx_out.objs_processed, &l_ctx_in, &l_ctx_out, &changed)) { return false; } diff --git a/lib/lb.c b/lib/lb.c index c08ccceda1..43628bba77 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,94 +82,293 @@ 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) { - int addr_family; - - if (!ip_address_and_port_from_lb_key(lb_key, &lb_vip->vip_str, - &lb_vip->vip, &lb_vip->vip_port, - &addr_family)) { - return false; - } - - /* 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; + lb_vip->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 (lb_vip->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[lb_vip->n_backends]; int backend_addr_family; if (!ip_address_and_port_from_lb_key(token, &backend->ip_str, &backend->ip, &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; } - n_backends++; + 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; + lb_vip->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, &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; + + return ovn_lb_backends_init_explicit(lb_vip, lb_value); +} + +/* 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_) +{ + struct ds errors = DS_EMPTY_INITIALIZER; + char *value = xstrdup(value_); + char *save_ptr = NULL; + size_t n_allocated_backends = 0; + lb_vip->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 (lb_vip->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[lb_vip->n_backends]; + lb_backend->ip_str = backend_ip; + lb_backend->port_str = backend_port; + lb_backend->port = 0; + lb_vip->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); +} + +/* 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 @@ -189,12 +399,113 @@ 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; + } + return false; +} + +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 template = smap_get_bool(&nbrec_lb->options, "template", false); 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"; @@ -202,12 +513,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, 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 = template; + + const char *mode = smap_get(&nbrec_lb->options, "neighbor_responder"); + lb->neigh_mode = ovn_lb_get_neigh_mode(nbrec_lb, mode, template); + uint32_t affinity_timeout = smap_get_uint(&nbrec_lb->options, "affinity_timeout", 0); if (affinity_timeout > UINT16_MAX) { @@ -227,13 +542,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, + 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, template); if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { sset_add(&lb->ips_v4, lb_vip->vip_str); } else { @@ -381,9 +702,12 @@ 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 sset *template_vars_ref) { struct ovn_controller_lb *lb = xzalloc(sizeof *lb); + bool template = smap_get_bool(&sbrec_lb->options, "template", false); lb->slb = sbrec_lb; lb->n_vips = smap_count(&sbrec_lb->vips); @@ -395,10 +719,26 @@ 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; + struct lex_str key_s = template + ? lexer_parse_template_string(node->key, + template_vars, + template_vars_ref) + : lex_str_use(node->key); + struct lex_str value_s = template + ? lexer_parse_template_string(node->value, + template_vars, + template_vars_ref) + : lex_str_use(node->value); + char *error = ovn_lb_vip_init_explicit(lb_vip, + lex_str_get(&key_s), + lex_str_get(&value_s)); + if (error) { + free(error); + } else { + n_vips++; } - n_vips++; + lex_str_free(&key_s); + lex_str_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 62843e4716..55a41ae0bc 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; uint16_t affinity_timeout; struct sset ips_v4; @@ -82,19 +84,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-northd. + */ 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. */ @@ -174,7 +188,17 @@ 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, + struct sset *template_vars_ref); 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 597625a291..1f8d0b8add 100644 --- a/lib/ovn-util.c +++ b/lib/ovn-util.c @@ -793,9 +793,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; memset(ip, 0, sizeof(*ip)); *port = 0; diff --git a/northd/northd.c b/northd/northd.c index 123127e9c1..c590c14818 100644 --- a/northd/northd.c +++ b/northd/northd.c @@ -3740,6 +3740,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]; @@ -4056,12 +4060,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); @@ -4073,8 +4084,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; @@ -5834,16 +5846,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, @@ -5854,7 +5866,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; @@ -6910,7 +6922,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); @@ -6921,7 +6933,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")) { @@ -6934,14 +6946,14 @@ 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"); ds_put_format(match, "%s.dst == %s", ip_match, lb_vip->vip_str); - 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", proto, lb_vip->port_str); } struct ovn_lflow *lflow_ref = NULL; @@ -7192,24 +7204,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); @@ -7227,8 +7227,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; } @@ -10231,7 +10232,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 { @@ -10247,14 +10248,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)); @@ -10263,7 +10264,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"; @@ -10281,9 +10282,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, ") || "); } @@ -10296,9 +10297,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 = @@ -10571,7 +10572,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); @@ -10581,7 +10582,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 553c0e48c3..8cd2427e8f 100644 --- a/ovn-nb.xml +++ b/ovn-nb.xml @@ -1905,8 +1905,66 @@ is applied reply to ARP/neighbor discovery requests for all VIPs of the load balancer. If set to <code>reachable</code>, then routers on which the load balancer is applied reply to ARP/neighbor discovery - 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>. + requests only for VIPs that are part of a router's subnet. If set to + <code>none</code>, then routers on which the load balancer is applied + never reply to ARP/neighbor discovery requests for any of the load + balancer VIPs. Load balancers with <code>options:template=true</code> + do not support <code>reachable</code> as a valid mode. The default + value of this option, if not specified, is <code>reachable</code> for + regular load balancers and <code>none</code> for template load + balancers. + </column> + + <column name="options" key="template"> + <p> + Option to be set to <code>true</code>, if the load balancer is a + template. The load balancer VIPs and backends must be using + <ref table="Chassis_Template_Var"/> in their definitions. + </p> + + <p> + Load balancer template VIP supported formats are: + </p> + <pre> +^VIP_VAR[:^PORT_VAR|:port] + </pre> + + <p> + where <code>VIP_VAR</code> and <code>PORT_VAR</code> are names of + <ref table="Chassis_Template_Var"/> records. + </p> + + <p> + Note: The VIP and PORT cannot be combined into a single template + variable. For example, a <ref table="Chassis_Template_Var"/> + variable expanding to <code>10.0.0.1:8080</code> is not valid + if used as VIP. + </p> + + <p> + Load balancer template backend supported formats are: + </p> + <pre> +^BACKEND_VAR1[:^PORT_VAR1|:port],^BACKEND_VAR2[:^PORT_VAR2|:port] + +or + +^BACKENDS_VAR1,^BACKENDS_VAR2 + </pre> + <p> + where <code>BACKEND_VAR1</code>, <code>PORT_VAR1</code>, + <code>BACKEND_VAR2</code>, <code>PORT_VAR2</code>, + <code>BACKENDS_VAR1</code> and <code>BACKENDS_VAR2</code> are names + of <ref table="Chassis_Template_Var"/> records. + </p> + </column> + + <column name="options" key="address-family"> + Address family used by the load balancer. Supported values are + <code>ipv4</code> and <code>ipv6</code>. The address-family is + only used for load balancers with <code>options:template=true</code>. + For explicit load balancers, setting the address-family has no + effect. </column> <column name="options" key="affinity_timeout"> diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at index 4d480e3573..9da7c26b31 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 86ab376fd6..770ccbdcda 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/tests/ovn.at b/tests/ovn.at index 68e788e78f..7c1f2acd04 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -33145,3 +33145,134 @@ AT_CHECK([ovs-ofctl dump-flows br-int | grep '42\.42\.42\.42'], [1], []) OVN_CLEANUP([hv1]) AT_CLEANUP ]) + +OVN_FOR_EACH_NORTHD([ +AT_SETUP([Load balancers with Chassis_Template_Var references]) +AT_KEYWORDS([templates]) +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 + +check ovn-nbctl ls-add sw + +dnl Use --wait=sb to ensure lsp1 getting a tunnel_key before lsp2. +check ovn-nbctl --wait=sb lsp-add sw lsp1 +check ovn-nbctl --wait=sb lsp-add sw lsp2 + +AT_CHECK([ovn-nbctl create Chassis_Template_Var chassis=hv1], [0], [ignore]) + +dnl Create a few LBs that use "uninstantiated" templates. +check ovn-nbctl --template lb-add lb-test1 "^VIP1:^VPORT1" "^BACKENDS1" tcp +check ovn-nbctl --template lb-add lb-test2 "^VIP2:^VPORT2" "^BACKENDS21,^BACKENDS22" tcp +check ovn-nbctl --template lb-add lb-test3 "^VIP3:^VPORT3" "^BACKENDS31:^BPORT1,^BACKENDS32:^BPORT2" tcp +check ovn-nbctl ls-lb-add sw lb-test1 +check ovn-nbctl ls-lb-add sw lb-test2 +check ovn-nbctl ls-lb-add sw lb-test3 + +check ovs-vsctl add-port br-int p1 -- set interface p1 external_ids:iface-id=lsp1 +check ovs-vsctl add-port br-int p2 -- set interface p2 external_ids:iface-id=lsp2 + +wait_for_ports_up +ovn-nbctl --wait=hv sync + +dnl Ensure the LBs are not translated to OpenFlow. +as hv1 +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat'], [1], []) + +dnl Create Chassis_Template_Var mappings. +check ovn-nbctl --wait=hv set Chassis_Template_Var hv1 \ + variables:VIP1='43.43.43.1' variables:VPORT1='4301' \ + variables:BACKENDS1='85.85.85.1:8501' \ + variables:VIP2='43.43.43.2' variables:VPORT2='4302' \ + variables:BACKENDS21='85.85.85.21:8502' \ + variables:BACKENDS22='85.85.85.22:8502' \ + variables:VIP3='43.43.43.3' variables:VPORT3='4303' \ + variables:BACKENDS31='85.85.85.31' \ + variables:BACKENDS32='85.85.85.32' \ + variables:BPORT1='8503' variables:BPORT2='8503' + +dnl Ensure the LBs are translated to OpenFlow. +as hv1 +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=85.85.85.1:8501)' -c], [0], [dnl +1 +]) +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=85.85.85.21:8502)' -c], [0], [dnl +1 +]) +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=85.85.85.22:8502)' -c], [0], [dnl +1 +]) +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=85.85.85.31:8503)' -c], [0], [dnl +1 +]) +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=85.85.85.32:8503)' -c], [0], [dnl +1 +]) + +dnl Ensure hairpin flows are correct. +as hv1 +AT_CHECK([ovs-ofctl dump-flows br-int | grep table=68 | ofctl_strip_all], [0], [dnl + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b01,reg2=0x10cd/0xffff,nw_src=85.85.85.1,nw_dst=85.85.85.1,tp_dst=8501 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.1,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b02,reg2=0x10ce/0xffff,nw_src=85.85.85.21,nw_dst=85.85.85.21,tp_dst=8502 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.2,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b02,reg2=0x10ce/0xffff,nw_src=85.85.85.22,nw_dst=85.85.85.22,tp_dst=8502 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.2,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b03,reg2=0x10cf/0xffff,nw_src=85.85.85.31,nw_dst=85.85.85.31,tp_dst=8503 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.3,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2b2b2b03,reg2=0x10cf/0xffff,nw_src=85.85.85.32,nw_dst=85.85.85.32,tp_dst=8503 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.3,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) +]) + +dnl Change Chassis_Template_Var mappings +check ovn-nbctl --wait=hv set Chassis_Template_Var hv1 \ + variables:VIP1='42.42.42.1' variables:VPORT1='4201' \ + variables:BACKENDS1='84.84.84.1:8401' \ + variables:VIP2='42.42.42.2' variables:VPORT2='4202' \ + variables:BACKENDS21='84.84.84.21:8402' \ + variables:BACKENDS22='84.84.84.22:8402' \ + variables:VIP3='42.42.42.3' variables:VPORT3='4203' \ + variables:BACKENDS31='84.84.84.31' \ + variables:BACKENDS32='84.84.84.32' \ + variables:BPORT1='8403' variables:BPORT2='8403' + +dnl Ensure the LBs are translated to OpenFlow. +as hv1 +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=84.84.84.1:8401)' -c], [0], [dnl +1 +]) +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=84.84.84.21:8402)' -c], [0], [dnl +1 +]) +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=84.84.84.22:8402)' -c], [0], [dnl +1 +]) +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=84.84.84.31:8403)' -c], [0], [dnl +1 +]) +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat(dst=84.84.84.32:8403)' -c], [0], [dnl +1 +]) + +dnl Ensure hairpin flows are correct. +as hv1 +AT_CHECK([ovs-ofctl dump-flows br-int | grep table=68 | ofctl_strip_all], [0], [dnl + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a01,reg2=0x1069/0xffff,nw_src=84.84.84.1,nw_dst=84.84.84.1,tp_dst=8401 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.1,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a02,reg2=0x106a/0xffff,nw_src=84.84.84.21,nw_dst=84.84.84.21,tp_dst=8402 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.2,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a02,reg2=0x106a/0xffff,nw_src=84.84.84.22,nw_dst=84.84.84.22,tp_dst=8402 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.2,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a03,reg2=0x106b/0xffff,nw_src=84.84.84.31,nw_dst=84.84.84.31,tp_dst=8403 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.3,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) + table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x2a2a2a03,reg2=0x106b/0xffff,nw_src=84.84.84.32,nw_dst=84.84.84.32,tp_dst=8403 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=42.42.42.3,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) +]) + +dnl Remove Chassis_Template_Variables and check that everything is +dnl removed from OpenFlow. +check ovn-nbctl --wait=hv clear Chassis_Template_Var hv1 variables + +as hv1 +AT_CHECK([ovs-ofctl dump-groups br-int | grep 'nat'], [1], []) + +as hv1 +AT_CHECK([ovs-ofctl dump-flows br-int | grep table=68 | ofctl_strip_all], [0], []) + +OVN_CLEANUP([hv1]) +AT_CLEANUP +]) diff --git a/tests/system-ovn.at b/tests/system-ovn.at index cb34127174..a15e332de6 100644 --- a/tests/system-ovn.at +++ b/tests/system-ovn.at @@ -9138,3 +9138,186 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d /connection dropped.*/d"]) AT_CLEANUP ]) + +OVN_FOR_EACH_NORTHD([ +AT_SETUP([load-balancer template IPv4]) +AT_SKIP_IF([test $HAVE_NC = no]) +AT_KEYWORDS([ovnlb templates]) + +CHECK_CONNTRACK() +CHECK_CONNTRACK_NAT() +ovn_start +OVS_TRAFFIC_VSWITCHD_START() +OVS_CHECK_CT_ZERO_SNAT() +ADD_BR([br-int]) + +# Set external-ids in br-int needed for ovn-controller +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 + +# Start ovn-controller +start_daemon ovn-controller + +# Logical network: +# VM1 -- LS1 -- GW-Router -- LS2 -- VM3 +# | +# VM2 ----+ +# +# A templated load balancer applied on LS1 and GW-Router with +# VM1 as backend. The VIP should be accessible from both VM2 and VM3. + +check ovn-nbctl \ + -- lr-add rtr \ + -- set Logical_Router rtr options:chassis=hv1 \ + -- lrp-add rtr rtr-ls1 00:00:00:00:01:00 42.42.42.1/24 \ + -- lrp-add rtr rtr-ls2 00:00:00:00:02:00 43.43.43.1/24 \ + -- ls-add ls1 \ + -- lsp-add ls1 ls1-rtr \ + -- lsp-set-addresses ls1-rtr 00:00:00:00:01:00 \ + -- lsp-set-type ls1-rtr router \ + -- lsp-set-options ls1-rtr router-port=rtr-ls1 \ + -- lsp-add ls1 vm1 -- lsp-set-addresses vm1 00:00:00:00:00:01 \ + -- lsp-add ls1 vm2 -- lsp-set-addresses vm2 00:00:00:00:00:02 \ + -- ls-add ls2 \ + -- lsp-add ls2 ls2-rtr \ + -- lsp-set-addresses ls2-rtr 00:00:00:00:02:00 \ + -- lsp-set-type ls2-rtr router \ + -- lsp-set-options ls2-rtr router-port=rtr-ls2 \ + -- lsp-add ls2 vm3 -- lsp-set-addresses vm3 00:00:00:00:00:03 + +# Add a template LB that eventually expands to: +# VIP=66.66.66.66:666 backends=42.42.42.2:4242 proto=tcp + +AT_CHECK([ovn-nbctl -- create chassis_template_var chassis="hv1" variables="{vip=66.66.66.66,vport=666,backends=\"42.42.42.2:4242\"}"], + [0], [ignore]) + +check ovn-nbctl --template lb-add lb-test "^vip:^vport" "^backends" tcp \ + -- ls-lb-add ls1 lb-test \ + -- lr-lb-add rtr lb-test + +ADD_NAMESPACES(vm1) +ADD_VETH(vm1, vm1, br-int, "42.42.42.2/24", "00:00:00:00:00:01", "42.42.42.1") + +ADD_NAMESPACES(vm2) +ADD_VETH(vm2, vm2, br-int, "42.42.42.3/24", "00:00:00:00:00:02", "42.42.42.1") + +ADD_NAMESPACES(vm3) +ADD_VETH(vm3, vm3, br-int, "43.43.43.2/24", "00:00:00:00:00:03", "43.43.43.1") + +# Wait for ovn-controller to catch up. +wait_for_ports_up +check ovn-nbctl --wait=hv sync + +AT_CHECK([ovn-appctl -t ovn-controller debug/dump-local-template-vars | sort], [0], [dnl +Local template vars: +name: 'backends' value: '42.42.42.2:4242' +name: 'vip' value: '66.66.66.66' +name: 'vport' value: '666' +]) + +# Start IPv4 TCP server on vm1. +NETNS_DAEMONIZE([vm1], [nc -k -l 42.42.42.2 4242], [nc-vm1.pid]) + +# Make sure connecting to the VIP works. +NS_CHECK_EXEC([vm2], [nc 66.66.66.66 666 -z], [0], [ignore], [ignore]) +NS_CHECK_EXEC([vm3], [nc 66.66.66.66 666 -z], [0], [ignore], [ignore]) + +AT_CLEANUP +]) + +OVN_FOR_EACH_NORTHD([ +AT_SETUP([load-balancer template IPv6]) +AT_SKIP_IF([test $HAVE_NC = no]) +AT_KEYWORDS([ovnlb templates]) + +CHECK_CONNTRACK() +CHECK_CONNTRACK_NAT() +ovn_start +OVS_TRAFFIC_VSWITCHD_START() +OVS_CHECK_CT_ZERO_SNAT() +ADD_BR([br-int]) + +# Set external-ids in br-int needed for ovn-controller +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 + +# Start ovn-controller +start_daemon ovn-controller + +# Logical network: +# VM1 -- LS1 -- GW-Router -- LS2 -- VM3 +# | +# VM2 ----+ +# +# A templated load balancer applied on LS1 and GW-Router with +# VM1 as backend. The VIP should be accessible from both VM2 and VM3. + +check ovn-nbctl \ + -- lr-add rtr \ + -- set Logical_Router rtr options:chassis=hv1 \ + -- lrp-add rtr rtr-ls1 00:00:00:00:01:00 4242::1/64 \ + -- lrp-add rtr rtr-ls2 00:00:00:00:02:00 4343::1/64 \ + -- ls-add ls1 \ + -- lsp-add ls1 ls1-rtr \ + -- lsp-set-addresses ls1-rtr 00:00:00:00:01:00 \ + -- lsp-set-type ls1-rtr router \ + -- lsp-set-options ls1-rtr router-port=rtr-ls1 \ + -- lsp-add ls1 vm1 -- lsp-set-addresses vm1 00:00:00:00:00:01 \ + -- lsp-add ls1 vm2 -- lsp-set-addresses vm2 00:00:00:00:00:02 \ + -- ls-add ls2 \ + -- lsp-add ls2 ls2-rtr \ + -- lsp-set-addresses ls2-rtr 00:00:00:00:02:00 \ + -- lsp-set-type ls2-rtr router \ + -- lsp-set-options ls2-rtr router-port=rtr-ls2 \ + -- lsp-add ls2 vm3 -- lsp-set-addresses vm3 00:00:00:00:00:03 + +# Add a template LB that eventually expands to: +# VIP=6666::1 backends=[4242::2]:4242 proto=tcp + +AT_CHECK([ovn-nbctl -- create chassis_template_var chassis="hv1" variables="{vip=\"6666::1\",vport=666,backends=\"[[4242::2]]:4242\"}"], + [0], [ignore]) + +check ovn-nbctl --template lb-add lb-test "^vip:^vport" "^backends" tcp ipv6 \ + -- ls-lb-add ls1 lb-test \ + -- lr-lb-add rtr lb-test + +ADD_NAMESPACES(vm1) +ADD_VETH(vm1, vm1, br-int, "4242::2/64", "00:00:00:00:00:01", "4242::1") +OVS_WAIT_UNTIL([test "$(ip netns exec vm1 ip a | grep 4242::2 | grep tentative)" = ""]) + +ADD_NAMESPACES(vm2) +ADD_VETH(vm2, vm2, br-int, "4242::3/64", "00:00:00:00:00:02", "4242::1") +OVS_WAIT_UNTIL([test "$(ip netns exec vm2 ip a | grep 4242::3 | grep tentative)" = ""]) + +ADD_NAMESPACES(vm3) +ADD_VETH(vm3, vm3, br-int, "4343::2/64", "00:00:00:00:00:03", "4343::1") +OVS_WAIT_UNTIL([test "$(ip netns exec vm3 ip a | grep 4343::2 | grep tentative)" = ""]) + +# Wait for ovn-controller to catch up. +wait_for_ports_up +check ovn-nbctl --wait=hv sync + +AT_CHECK([ovn-appctl -t ovn-controller debug/dump-local-template-vars | sort], [0], [dnl +Local template vars: +name: 'backends' value: '[[4242::2]]:4242' +name: 'vip' value: '6666::1' +name: 'vport' value: '666' +]) + +# Start IPv6 TCP server on vm1. +NETNS_DAEMONIZE([vm1], [nc -k -l 4242::2 4242], [nc-vm1.pid]) + +# Make sure connecting to the VIP works. +NS_CHECK_EXEC([vm2], [nc 6666::1 666 -z], [0], [ignore], [ignore]) +NS_CHECK_EXEC([vm3], [nc 6666::1 666 -z], [0], [ignore], [ignore]) + +AT_CLEANUP +]) diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c index d2dee6b31c..9e9b83ef1f 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,11 @@ nbctl_lb_add(struct ctl_context *ctx) return; } + 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 +2866,59 @@ 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 = !strcmp(ctx->argv[5], "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 +2937,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 +2949,17 @@ 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 == AF_INET ? "ipv4" : "ipv6"); + } 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 +3013,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 +3022,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 +3031,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 +3240,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 +3404,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); @@ -7472,9 +7475,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 },
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. Template load balancers don't support the "reachable" neighbor-responder mode. Instead the CMS can explicitly configure the responder mode to either "all" or "none". To properly handle template updates in ovn-controller we also add a Chassis_Template_Var <- LB reference in ovn-controller. This way, when a Chassis_Template_Var changes value all load balancers that refer to it will also get updated. Signed-off-by: Dumitru Ceara <dceara@redhat.com> --- V3: - Addressed Mark's comments: - Added TODO items about potential future template LB improvements. - Removed n_backends arg from ovn_lb_backends_init_explicit() and ovn_lb_backends_init_template(). - Fixed ovn_northd_lb_create() to first get the template option before using its value. - Fixed up comments and man pages. - Hardenned setting of address family in ovn-nbctl. V2: - Fix GCC build due to missing explicit return. - Fix ls_in_pre_stateful flows due to using wrong lb field. - Use new lexer_parse_template_string(). - Changed lb_handle_changed_ref() signature to return bool. - Update documentation with info about responder mode=none, LB template supported formats, lb explicit address family requirements. - Squashed the template LB patches into a single one - Added more tests. - Squashed the system tests patch into this one. --- TODO.rst | 7 + controller/lflow.c | 115 +++++++++-- controller/lflow.h | 7 + controller/ovn-controller.c | 67 +++++- lib/lb.c | 452 ++++++++++++++++++++++++++++++++++++++----- lib/lb.h | 40 +++- lib/ovn-util.c | 3 northd/northd.c | 89 ++++---- ovn-nb.xml | 62 ++++++ tests/ovn-nbctl.at | 23 +- tests/ovn-northd.at | 7 + tests/ovn.at | 131 ++++++++++++ tests/system-ovn.at | 183 +++++++++++++++++ utilities/ovn-nbctl.c | 120 ++++++----- 14 files changed, 1078 insertions(+), 228 deletions(-)