This change will be useful for migration cases, where it can be used to sync ct-entries before port is up on new chassis, resulting in reduced network package drops. It also fulfills the need of any other service which might need advance ct-zone reservation in future.
--- v10->v11 1. Added in details in news and ovn-controller.8.xml 2. Resolved requested changes in definition declartions, and memory issues. v11->v12 1. Fixed xml file format issues. --- Signed-off-by: Mansi Sharma <mansi.sha...@nutanix.com> --- NEWS | 2 + controller/ct-zone.c | 54 +++++++++++++-- controller/ct-zone.h | 9 ++- controller/ovn-controller.8.xml | 13 ++++ controller/ovn-controller.c | 18 +++-- tests/ovn-controller.at | 119 +++++++++++++++++++++++++++++--- 6 files changed, 193 insertions(+), 22 deletions(-) diff --git a/NEWS b/NEWS index 656176d20..169cccada 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,8 @@ Post v25.03.0 external-ids, this option allows to specify if ovn-controller should perform cleanup when exiting. The "--restart" exit always has priority to keep the backward compatibility. + - Added support to reserve ct-zones for upcoming ports via setting + external_ids:reserve_ct_zones option in Open_vSwitch table. OVN v25.03.0 - 07 Mar 2025 -------------------------- diff --git a/controller/ct-zone.c b/controller/ct-zone.c index 469a8fc54..763837a54 100644 --- a/controller/ct-zone.c +++ b/controller/ct-zone.c @@ -118,6 +118,37 @@ ct_zones_restore(struct ct_zone_ctx *ctx, } } +void +ct_zones_reserved_lports(const struct ovsrec_open_vswitch_table *ovs_table, + struct sset * reserved_lports_set) +{ + const struct ovsrec_open_vswitch *cfg; + cfg = ovsrec_open_vswitch_table_first(ovs_table); + + if (cfg == NULL) { + return; + } + + const char *reserve_ct_zone_request_list = smap_get( + &cfg->external_ids, "reserve_ct_zones"); + + if (reserve_ct_zone_request_list == NULL) { + return; + } + + char *dup_reserve_list = xstrdup(reserve_ct_zone_request_list); + char *reserve_port; + char *save_ptr = NULL; + + for (reserve_port = strtok_r(dup_reserve_list, ",", &save_ptr); + reserve_port != NULL; + reserve_port = strtok_r(NULL, ",", &save_ptr)) { + sset_add(reserved_lports_set, reserve_port); + } + free(reserve_port); + free(dup_reserve_list); +} + void ct_zones_parse_range(const struct ovsrec_open_vswitch_table *ovs_table, int *min_ct_zone, int *max_ct_zone) @@ -169,7 +200,8 @@ out: void ct_zones_update(const struct sset *local_lports, const struct ovsrec_open_vswitch_table *ovs_table, - const struct hmap *local_datapaths, struct ct_zone_ctx *ctx) + const struct hmap *local_datapaths, struct ct_zone_ctx *ctx, + struct sset *reserved_lports) { int min_ct_zone, max_ct_zone; const char *user; @@ -183,6 +215,13 @@ ct_zones_update(const struct sset *local_lports, sset_add(&all_users, local_lport); } + if (!sset_is_empty(reserved_lports)) { + const char *reserved_lport; + SSET_FOR_EACH (reserved_lport, reserved_lports) { + sset_add(&all_users, reserved_lport); + } + } + /* Local patched datapath (gateway routers) need zones assigned. */ const struct local_datapath *ld; HMAP_FOR_EACH (ld, hmap_node, local_datapaths) { @@ -397,12 +436,15 @@ ct_zone_handle_dp_update(struct ct_zone_ctx *ctx, /* Returns "true" if there was an update to the context. */ bool -ct_zone_handle_port_update(struct ct_zone_ctx *ctx, - const struct sbrec_port_binding *pb, - bool updated, int *scan_start, - int min_ct_zone, int max_ct_zone) +ct_zone_handle_port_update( + struct ct_zone_ctx *ctx, + const struct sbrec_port_binding *pb, + bool updated, int *scan_start, + int min_ct_zone, int max_ct_zone, + struct sset *reserved_lports) { struct shash_node *node = shash_find(&ctx->current, pb->logical_port); + bool is_reserved = node && sset_contains(reserved_lports, node->name); if (node) { struct ct_zone *ct_zone = node->data; @@ -419,6 +461,8 @@ ct_zone_handle_port_update(struct ct_zone_ctx *ctx, } ct_zone_limit_update(ctx, pb->logical_port, ct_zone_get_pb_limit(pb)); return true; + } else if (node && is_reserved) { + return true; } else if (node && ct_zone_remove(ctx, node->name)) { return true; } diff --git a/controller/ct-zone.h b/controller/ct-zone.h index 6df03975c..bf26a467d 100644 --- a/controller/ct-zone.h +++ b/controller/ct-zone.h @@ -64,6 +64,9 @@ struct ct_zone_pending_entry { void ct_zone_ctx_init(struct ct_zone_ctx *ctx); void ct_zone_ctx_destroy(struct ct_zone_ctx *ctx); +void ct_zones_reserved_lports( + const struct ovsrec_open_vswitch_table *, + struct sset *reserved_lports_set); void ct_zones_parse_range(const struct ovsrec_open_vswitch_table *ovs_table, int *min_ct_zone, int *max_ct_zone); void ct_zones_restore(struct ct_zone_ctx *ctx, @@ -73,7 +76,8 @@ void ct_zones_restore(struct ct_zone_ctx *ctx, void ct_zones_update(const struct sset *local_lports, const struct ovsrec_open_vswitch_table *ovs_table, const struct hmap *local_datapaths, - struct ct_zone_ctx *ctx); + struct ct_zone_ctx *ctx, + struct sset *reserved_lports); void ct_zones_commit(const struct ovsrec_bridge *br_int, const struct ovsrec_datapath *ovs_dp, struct ovsdb_idl_txn *ovs_idl_txn, @@ -85,7 +89,8 @@ bool ct_zone_handle_dp_update(struct ct_zone_ctx *ctx, bool ct_zone_handle_port_update(struct ct_zone_ctx *ctx, const struct sbrec_port_binding *pb, bool updated, int *scan_start, - int min_ct_zone, int max_ct_zone); + int min_ct_zone, int max_ct_zone, + struct sset *reserved_lports); uint16_t ct_zone_find_zone(const struct shash *ct_zones, const char *name); void ct_zones_limits_sync(struct ct_zone_ctx *ctx, const struct hmap *local_datapaths, diff --git a/controller/ovn-controller.8.xml b/controller/ovn-controller.8.xml index 31f790875..6c9fb6314 100644 --- a/controller/ovn-controller.8.xml +++ b/controller/ovn-controller.8.xml @@ -664,6 +664,19 @@ <code>external_ids:ovn-installed-ts</code>. </p> </dd> + + <dt> + <code>external-ids:reserve_ct_zones</code> in the <code>Bridge</code> + table + </dt> + + <dd> + <p> + This key represents list of ports which are supposed to come up on + the chassis, and hence need advance reservation of ct-zones. + It is comma seprated list of port names. + </p> + </dd> </dl> <h1>OVN Southbound Database Usage</h1> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c index 0b09c98bd..70be0734e 100644 --- a/controller/ovn-controller.c +++ b/controller/ovn-controller.c @@ -2368,10 +2368,13 @@ en_ct_zones_run(struct engine_node *node, void *data) EN_OVSDB_GET(engine_get_input("SB_datapath_binding", node)); const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table); - + struct sset reserved_lports = SSET_INITIALIZER(&reserved_lports); + ct_zones_reserved_lports(ovs_table, &reserved_lports); ct_zones_restore(&ct_zones_data->ctx, ovs_table, dp_table, br_int); ct_zones_update(&rt_data->local_lports, ovs_table, - &rt_data->local_datapaths, &ct_zones_data->ctx); + &rt_data->local_datapaths, &ct_zones_data->ctx, + &reserved_lports); + sset_destroy(&reserved_lports); ct_zones_limits_sync(&ct_zones_data->ctx, &rt_data->local_datapaths, &rt_data->lbinding_data.lports); @@ -2438,6 +2441,9 @@ ct_zones_runtime_data_handler(struct engine_node *node, void *data) ct_zones_parse_range(ovs_table, &min_ct_zone, &max_ct_zone); scan_start = min_ct_zone; + struct sset reserved_lports = SSET_INITIALIZER(&reserved_lports); + ct_zones_reserved_lports(ovs_table, &reserved_lports); + HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) { if (tdp->tracked_type == TRACKED_RESOURCE_NEW) { /* A new datapath has been added. Fall back to full recompute. */ @@ -2458,7 +2464,8 @@ ct_zones_runtime_data_handler(struct engine_node *node, void *data) updated |= ct_zone_handle_port_update(&ct_zones_data->ctx, t_lport->pb, false, &scan_start, - min_ct_zone, max_ct_zone); + min_ct_zone, max_ct_zone, + &reserved_lports); } continue; @@ -2470,10 +2477,13 @@ ct_zones_runtime_data_handler(struct engine_node *node, void *data) updated |= ct_zone_handle_port_update(&ct_zones_data->ctx, t_lport->pb, port_updated, &scan_start, - min_ct_zone, max_ct_zone); + min_ct_zone, max_ct_zone, + &reserved_lports); } } + sset_destroy(&reserved_lports); + if (updated) { engine_set_node_state(node, EN_UPDATED); } diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at index 2ebf879f1..94c75c6b6 100644 --- a/tests/ovn-controller.at +++ b/tests/ovn-controller.at @@ -2633,7 +2633,7 @@ ovn_attach n1 br-phys 192.168.0.1 get_zone_num () { output=$1 name=$2 - printf "$output" | grep $name | cut -d ' ' -f 2 + echo "$output" | grep $name | cut -d ' ' -f 2 } check_ovsdb_zone() { @@ -2643,8 +2643,14 @@ check_ovsdb_zone() { test $ct_zone -eq $db_zone } +check_duplicates() { + output=$1 + AT_CHECK([echo "$output" | tr ' ' '\n' | sort | uniq -d | grep .], [1]) +} + check ovs-vsctl add-port br-int ls0-hv1 -- set Interface ls0-hv1 external-ids:iface-id=ls0-hv1 check ovs-vsctl add-port br-int ls0-hv2 -- set Interface ls0-hv2 external-ids:iface-id=ls0-hv2 +check ovs-vsctl set Open_vSwitch . external_ids:reserve_ct_zones=ls0-req-hv3,ls0-req-hv4 check ovn-nbctl lr-add lr0 @@ -2666,21 +2672,23 @@ check ovn-nbctl lrp-set-gateway-chassis lrp-gw hv1 check ovn-nbctl --wait=hv sync ct_zones=$(ovn-appctl -t ovn-controller ct-zone-list) -echo "$ct_zones" + port1_zone=$(get_zone_num "$ct_zones" ls0-hv1) port2_zone=$(get_zone_num "$ct_zones" ls0-hv2) +req_port3_zone=$(get_zone_num "$ct_zones" ls0-req-hv3) +req_port4_zone=$(get_zone_num "$ct_zones" ls0-req-hv4) snat_zone=$(get_zone_num "$ct_zones" lr0_snat) echo "snat_zone is $snat_zone" -check test "$port1_zone" -ne "$port2_zone" -check test "$port2_zone" -ne "$snat_zone" -check test "$port1_zone" -ne "$snat_zone" - OVS_WAIT_UNTIL([check_ovsdb_zone ls0-hv1 $port1_zone]) OVS_WAIT_UNTIL([check_ovsdb_zone ls0-hv2 $port2_zone]) OVS_WAIT_UNTIL([check_ovsdb_zone lr0_snat $snat_zone]) +OVS_WAIT_UNTIL([check_ovsdb_zone ls0-req-hv3 $req_port3_zone]) +OVS_WAIT_UNTIL([check_ovsdb_zone ls0-req-hv4 $req_port4_zone]) + +check_duplicates "$ct_zones" # Now purposely request an SNAT zone for lr0 that conflicts with a zone # currently assigned to a logical port @@ -2690,20 +2698,110 @@ check ovn-nbctl set Logical_Router lr0 options:snat-ct-zone=$snat_req_zone check ovn-nbctl --wait=hv sync ct_zones=$(ovn-appctl -t ovn-controller ct-zone-list) -echo "$ct_zones" port1_zone=$(get_zone_num "$ct_zones" ls0-hv1) port2_zone=$(get_zone_num "$ct_zones" ls0-hv2) snat_zone=$(get_zone_num "$ct_zones" lr0_snat) +req_port3_zone=$(get_zone_num "$ct_zones" ls0-req-hv3) +req_port4_zone=$(get_zone_num "$ct_zones" ls0-req-hv4) + check test "$snat_zone" -eq "$snat_req_zone" -check test "$port1_zone" -ne "$port2_zone" -check test "$port2_zone" -ne "$snat_zone" -check test "$port1_zone" -ne "$snat_zone" +check_duplicates "$ct_zones" + +OVS_WAIT_UNTIL([check_ovsdb_zone ls0-hv1 $port1_zone]) +OVS_WAIT_UNTIL([check_ovsdb_zone ls0-hv2 $port2_zone]) +OVS_WAIT_UNTIL([check_ovsdb_zone lr0_snat $snat_zone]) +OVS_WAIT_UNTIL([check_ovsdb_zone ls0-req-hv3 $req_port3_zone]) +OVS_WAIT_UNTIL([check_ovsdb_zone ls0-req-hv4 $req_port4_zone]) + +# Add port named ls0-req-hv3 and check if same zone assigned +# previously get assigned to it this time as well. + +check ovn-nbctl lsp-add ls0 ls0-req-hv3 +check ovs-vsctl -- add-port br-int hv3-vif3 -- \ + set interface hv3-vif3 external-ids:iface-id=ls0-req-hv3 +ct_zones=$(ovn-appctl -t ovn-controller ct-zone-list) + +check ovn-nbctl --wait=hv sync + +req_port3_zone_new=$(get_zone_num "$ct_zones" ls0-req-hv3) +check test "$req_port3_zone" -eq "$req_port3_zone_new" +check_duplicates "$ct_zones" OVS_WAIT_UNTIL([check_ovsdb_zone ls0-hv1 $port1_zone]) OVS_WAIT_UNTIL([check_ovsdb_zone ls0-hv2 $port2_zone]) OVS_WAIT_UNTIL([check_ovsdb_zone lr0_snat $snat_zone]) +OVS_WAIT_UNTIL([check_ovsdb_zone ls0-req-hv3 $req_port3_zone]) +OVS_WAIT_UNTIL([check_ovsdb_zone ls0-req-hv4 $req_port4_zone]) + +AT_CHECK([ovs-vsctl get bridge br-int external_ids:ct-zone-ls0-req-hv3], +[0], [ignore]) + +# Checks for two cases after removing entry from ovs_vswitch table - +# 1. If port is already up, ct-zone should be reserved. +# 2. If port is not up yet, ct-zone should not be reserved. + +check ovs-vsctl remove Open_vSwitch . external_ids reserve_ct_zones + +ct_zones=$(ovn-appctl -t ovn-controller ct-zone-list) + +req_port3_zone_after_delete=$(get_zone_num "$ct_zones" ls0-req-hv3) +req_port4_zone_after_delete=$(get_zone_num "$ct_zones" ls0-req-hv4) + +check test "$req_port3_zone_new" -eq "$req_port3_zone_after_delete" + +AT_CHECK([ovs-vsctl get bridge br-int external_ids:ct-zone-ls0-req-hv3], +[0], [ignore]) + +AT_CHECK([ovs-vsctl get bridge br-int external_ids:ct-zone-ls0-req-hv4], +[1], [ignore], [ignore]) + +# check test "$req_port4_zone_after_delete" == "" + +# Checks for case when a ct-zone is reserved it comes up on that chassis, and +# gets deleted, but its persisted in ovs_vswitch table, it should persist the +# same zone throughout. + +check ovs-vsctl set Open_vSwitch . external_ids:reserve_ct_zones=ls0-req-hv5 +check ovn-nbctl lsp-add ls0 ls0-req-hv5 +check ovs-vsctl -- add-port br-int hv5-vif5 -- \ + set interface hv5-vif5 external-ids:iface-id=ls0-req-hv5 +ct_zones=$(ovn-appctl -t ovn-controller ct-zone-list) +req_port5_zone=$(get_zone_num "$ct_zones" ls0-req-hv5) +AT_CHECK([ovs-vsctl get bridge br-int external_ids:ct-zone-ls0-req-hv5], +[0], [ignore]) + +check ovs-vsctl remove interface hv5-vif5 external_ids iface-id +ct_zones=$(ovn-appctl -t ovn-controller ct-zone-list) +req_port5_zone_new=$(get_zone_num "$ct_zones" ls0-req-hv5) +AT_CHECK([ovs-vsctl get bridge br-int external_ids:ct-zone-ls0-req-hv5], +[0], [ignore]) +check test "$req_port5_zone" -eq "$req_port5_zone_new" +check_duplicates "$ct_zones" + +OVS_WAIT_UNTIL([check_ovsdb_zone ls0-hv1 $port1_zone]) +OVS_WAIT_UNTIL([check_ovsdb_zone ls0-hv2 $port2_zone]) +OVS_WAIT_UNTIL([check_ovsdb_zone lr0_snat $snat_zone]) +OVS_WAIT_UNTIL([check_ovsdb_zone ls0-req-hv3 $req_port3_zone]) + +check ovs-vsctl set Open_vSwitch . external_ids:reserve_ct_zones=ls0-req-hv6 +check ovn-nbctl lsp-add ls0 ls0-req-hv6 +check ovs-vsctl -- add-port br-int hv6-vif6 -- \ + set interface hv6-vif6 external-ids:iface-id=ls0-req-hv6 +ct_zones=$(ovn-appctl -t ovn-controller ct-zone-list) +req_port6_zone=$(get_zone_num "$ct_zones" ls0-req-hv6) + +AT_CHECK([ovs-vsctl get bridge br-int external_ids:ct-zone-ls0-req-hv6], +[0], [ignore]) +check ovn-nbctl --wait=hv lsp-del ls0-req-hv6 +ct_zones=$(ovn-appctl -t ovn-controller ct-zone-list) +AT_CHECK([ovs-vsctl get bridge br-int external_ids:ct-zone-ls0-req-hv6], +[0], [ignore]) +req_port6_zone_new=$(get_zone_num "$ct_zones" ls0-req-hv6) +check_duplicates "$ct_zones" +check test "$req_port6_zone" -eq "$req_port6_zone_new" +check_duplicates "$ct_zones" # Now create a conflict in the OVSDB and restart ovn-controller. @@ -2713,7 +2811,6 @@ ovs-vsctl set bridge br-int external_ids:ct-zone-ls0-hv2="$snat_req_zone" ovn-appctl -t ovn-controller inc-engine/recompute ct_zones=$(ovn-appctl -t ovn-controller ct-zone-list) -echo "$ct_zones" port1_zone=$(get_zone_num "$ct_zones" ls0-hv1) port2_zone=$(get_zone_num "$ct_zones" ls0-hv2) -- 2.39.3 _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev