Commit 0e2bcf70ac4f moved the release of the dp group referenced by an
lflow from sync_lflow_to_sb() to do_ovn_lflow_add(), so that the group is
already free when a new one is looked up and the SB row can be reused.
However, do_ovn_lflow_add() is only called when the lflow is generated
again. An lflow that is merely unlinked from one of its lflow_refs, and
that survives because other lflow_refs still reference it, never goes
through do_ovn_lflow_add(): only its dp group bitmap shrinks. Such an
lflow is synced with a different dp group (or with a single datapath),
and the reference to the previous group is never dropped.
The leaked group stays in the 'dp_groups' map with a non zero refcount
while no lflow uses it anymore, so its SB Logical_DP_Group row is garbage
collected. Any lflow that later needs that same set of datapaths finds
the leaked group, fails to look its row up and northd logs:
SB Logical flow [...]'s logical_dp_group column is not set (which is
unexpected). It should have been referencing the dp group [...]
and falls back to a full recompute.
Release the group in lflow_ref_unlink_lflows(), where the bitmap actually
shrinks. Like the release done by do_ovn_lflow_add(), this happens before
any lflow of the round is synced, which is what allows
ovn_dp_group_create() to modify the SB row of a group that just became
free instead of inserting a new row and leaving the old one to be deleted.
Releasing at sync time would fix the leak but not that churn: a group is
usually referenced by several lflows, so it is still in 'dp_groups' when
the first of them is synced.
Fixes: 0e2bcf70ac4f ("northd: Change ovn_dp_groups to decrement refcount in
do_ovn_lflow_add.")
Assisted-by: Claude Opus 5, Claude Code
Signed-off-by: Lucas Vargas Dias <[email protected]>
---
northd/en-lflow.c | 6 ++-
northd/lflow-mgr.c | 39 ++++++++++++++++---
northd/lflow-mgr.h | 2 +-
northd/northd.c | 16 ++++----
tests/ovn-northd.at | 94 +++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 140 insertions(+), 17 deletions(-)
diff --git a/northd/en-lflow.c b/northd/en-lflow.c
index 8cb987777..e8e6cdaa2 100644
--- a/northd/en-lflow.c
+++ b/northd/en-lflow.c
@@ -294,7 +294,8 @@ lflow_group_ecmp_route_change_handler(struct engine_node
*node,
HMAPX_FOR_EACH (hmapx_node,
&group_ecmp_route_data->trk_data.deleted_datapath_routes) {
route_node = hmapx_node->data;
- lflow_ref_unlink_lflows(route_node->lflow_ref);
+ lflow_ref_unlink_lflows(route_node->lflow_ref,
+ lflow_data->lflow_table);
bool handled = lflow_ref_sync_lflows(
route_node->lflow_ref, lflow_data->lflow_table,
@@ -312,7 +313,8 @@ lflow_group_ecmp_route_change_handler(struct engine_node
*node,
&group_ecmp_route_data->trk_data.crupdated_datapath_routes;
HMAPX_FOR_EACH (hmapx_node, crupdated_datapath_routes) {
route_node = hmapx_node->data;
- lflow_ref_unlink_lflows(route_node->lflow_ref);
+ lflow_ref_unlink_lflows(route_node->lflow_ref,
+ lflow_data->lflow_table);
build_route_data_flows_for_lrouter(
route_node->od, lflow_data->lflow_table,
route_node, lflow_input.bfd_ports);
diff --git a/northd/lflow-mgr.c b/northd/lflow-mgr.c
index ce9c4f854..f51df9ce0 100644
--- a/northd/lflow-mgr.c
+++ b/northd/lflow-mgr.c
@@ -657,26 +657,53 @@ lflow_ref_destroy(struct lflow_ref *lflow_ref)
* from the lrn->lflow's dpg bitmap
*/
void
-lflow_ref_unlink_lflows(struct lflow_ref *lflow_ref)
+lflow_ref_unlink_lflows(struct lflow_ref *lflow_ref,
+ struct lflow_table *lflow_table)
{
struct lflow_ref_node *lrn;
HMAP_FOR_EACH (lrn, ref_node, &lflow_ref->lflow_ref_nodes) {
+ struct ovn_lflow *lflow = lrn->lflow;
+ bool dpg_bitmap_changed = false;
+
if (lrn->dpgrp_lflow) {
size_t index;
BITMAP_FOR_EACH_1 (index, lrn->dpgrp_bitmap_len,
lrn->dpgrp_bitmap) {
- if (dp_refcnt_release(&lrn->lflow->dp_refcnts_map, index)) {
- dynamic_bitmap_set0(&lrn->lflow->dpg_bitmap, index);
+ if (dp_refcnt_release(&lflow->dp_refcnts_map, index)) {
+ dynamic_bitmap_set0(&lflow->dpg_bitmap, index);
+ dpg_bitmap_changed = true;
}
}
} else {
- if (dp_refcnt_release(&lrn->lflow->dp_refcnts_map,
+ if (dp_refcnt_release(&lflow->dp_refcnts_map,
lrn->dp_index)) {
- dynamic_bitmap_set0(&lrn->lflow->dpg_bitmap, lrn->dp_index);
+ dynamic_bitmap_set0(&lflow->dpg_bitmap, lrn->dp_index);
+ dpg_bitmap_changed = true;
}
}
+ /* The lflow doesn't apply to the same set of datapaths anymore, so
+ * the dp group it is referencing is not the one it will be synced
+ * with. Drop the reference now, before any lflow of this round is
+ * synced: as long as the group is still referenced it stays in
+ * 'dp_groups' and ovn_dp_group_create() refuses to modify its SB
+ * row, inserting a new Logical_DP_Group row and deleting the one
+ * that just became free instead of reusing it.
+ *
+ * This is the counterpart of the release done by
+ * do_ovn_lflow_add(), which covers the lflows that are generated
+ * again. An lflow that is only unlinked from one of its lflow_refs
+ * and survives because other lflow_refs still reference it never
+ * goes through do_ovn_lflow_add(). */
+ if (dpg_bitmap_changed && lflow->dpg) {
+ enum ovn_datapath_type dp_type =
+ ovn_stage_to_datapath_type(lflow->stage);
+ ovn_dp_group_release(&lflow_table->dp_groups[dp_type],
+ lflow->dpg);
+ lflow->dpg = NULL;
+ }
+
lrn->linked = false;
}
}
@@ -690,7 +717,7 @@ lflow_ref_resync_flows(struct lflow_ref *lflow_ref,
const struct sbrec_logical_flow_table *sbflow_table,
const struct sbrec_logical_dp_group_table *dpgrp_table)
{
- lflow_ref_unlink_lflows(lflow_ref);
+ lflow_ref_unlink_lflows(lflow_ref, lflow_table);
return lflow_ref_sync_lflows__(lflow_ref, lflow_table, ovnsb_txn,
dps,
ovn_internal_version_changed, sbflow_table,
diff --git a/northd/lflow-mgr.h b/northd/lflow-mgr.h
index 84d0b3e67..6253be685 100644
--- a/northd/lflow-mgr.h
+++ b/northd/lflow-mgr.h
@@ -57,7 +57,7 @@ struct lflow_ref;
struct lflow_ref *lflow_ref_create(void);
void lflow_ref_destroy(struct lflow_ref *);
void lflow_ref_clear(struct lflow_ref *lflow_ref);
-void lflow_ref_unlink_lflows(struct lflow_ref *);
+void lflow_ref_unlink_lflows(struct lflow_ref *, struct lflow_table *);
bool lflow_ref_resync_flows(struct lflow_ref *,
struct lflow_table *lflow_table,
struct ovsdb_idl_txn *ovnsb_txn,
diff --git a/northd/northd.c b/northd/northd.c
index 9f33b50cf..7753de970 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -20997,7 +20997,7 @@ lflow_handle_northd_lr_changes(struct ovsdb_idl_txn
*ovnsb_txn,
HMAPX_FOR_EACH (hmapx_node, &tracked_lrs->crupdated) {
struct ovn_datapath *od = hmapx_node->data;
- lflow_ref_unlink_lflows(od->datapath_lflows);
+ lflow_ref_unlink_lflows(od->datapath_lflows, lflows);
build_lswitch_and_lrouter_iterate_by_lr(od, &lsi);
}
@@ -21052,7 +21052,7 @@ lflow_handle_northd_port_changes(struct ovsdb_idl_txn
*ovnsb_txn,
/* Make sure 'op' is an lsp and not lrp. */
ovs_assert(op->nbsp);
/* Clear old lflows. */
- lflow_ref_unlink_lflows(op->lflow_ref);
+ lflow_ref_unlink_lflows(op->lflow_ref, lflows);
/* Generate new lflows. */
struct ds match = DS_EMPTY_INITIALIZER;
@@ -21072,7 +21072,7 @@ lflow_handle_northd_port_changes(struct ovsdb_idl_txn
*ovnsb_txn,
if (handled) {
/* Now regenerate the stateful lflows for 'op' */
/* Clear old lflows. */
- lflow_ref_unlink_lflows(op->stateful_lflow_ref);
+ lflow_ref_unlink_lflows(op->stateful_lflow_ref, lflows);
build_lbnat_lflows_iterate_by_lsp(op,
lflow_input->lr_stateful_table,
&match, &actions, lflows);
@@ -21164,7 +21164,7 @@ lflow_handle_northd_lb_changes(struct ovsdb_idl_txn
*ovnsb_txn,
lb_dps = hmapx_node->data;
/* unlink old lflows. */
- lflow_ref_unlink_lflows(lb_dps->lflow_ref);
+ lflow_ref_unlink_lflows(lb_dps->lflow_ref, lflows);
/* Generate new lflows. */
struct ds match = DS_EMPTY_INITIALIZER;
@@ -21220,7 +21220,7 @@ lflow_handle_lr_stateful_changes(struct ovsdb_idl_txn
*ovnsb_txn,
HMAPX_FOR_EACH (hmapx_node, &trk_data->crupdated) {
lr_stateful_rec = hmapx_node->data;
/* Unlink old lflows. */
- lflow_ref_unlink_lflows(lr_stateful_rec->lflow_ref);
+ lflow_ref_unlink_lflows(lr_stateful_rec->lflow_ref, lflows);
/* Generate new lflows. */
build_lr_stateful_flows(lr_stateful_rec, lflow_input->lr_datapaths,
@@ -21245,7 +21245,7 @@ lflow_handle_lr_stateful_changes(struct ovsdb_idl_txn
*ovnsb_txn,
lr_stateful_rec->lr_index);
struct ovn_port *op;
HMAP_FOR_EACH (op, dp_node, &od->ports) {
- lflow_ref_unlink_lflows(op->stateful_lflow_ref);
+ lflow_ref_unlink_lflows(op->stateful_lflow_ref, lflows);
build_lbnat_lflows_iterate_by_lrp(op,
lflow_input->lr_stateful_table,
@@ -21265,7 +21265,7 @@ lflow_handle_lr_stateful_changes(struct ovsdb_idl_txn
*ovnsb_txn,
}
if (op->peer && op->peer->nbsp) {
- lflow_ref_unlink_lflows(op->peer->stateful_lflow_ref);
+ lflow_ref_unlink_lflows(op->peer->stateful_lflow_ref, lflows);
build_lbnat_lflows_iterate_by_lsp(
op->peer, lflow_input->lr_stateful_table, &match, &actions,
@@ -21307,7 +21307,7 @@ lflow_handle_ls_stateful_changes(struct ovsdb_idl_txn
*ovnsb_txn,
ovs_assert(od->nbs && uuid_equals(&od->nbs->header_.uuid,
&ls_stateful_rec->nbs_uuid));
- lflow_ref_unlink_lflows(ls_stateful_rec->lflow_ref);
+ lflow_ref_unlink_lflows(ls_stateful_rec->lflow_ref, lflows);
/* Generate new lflows. */
build_ls_stateful_flows(ls_stateful_rec, od,
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 8c8d7852e..aa281c1a2 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -23647,3 +23647,97 @@ AT_CHECK([as northd ovn-appctl -t ovn-northd
inc-engine/enable-stopwatch nonexis
OVN_CLEANUP_NORTHD
AT_CLEANUP
])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([Datapath group reuse after an lflow loses a datapath])
+ovn_start
+
+# All four switches have ACLs, so the logical flows that only depend on
+# "the switch has ACLs" are shared by the four of them. ls1 and ls2 use
+# tier 0 while ls3 and ls4 use tier 1, which changes the actions of the
+# egress "acl action" flows. Hence three datapath groups are expected:
+# {ls1, ls2}, {ls3, ls4} and {ls1, ls2, ls3, ls4}.
+check ovn-nbctl ls-add ls1
+check ovn-nbctl ls-add ls2
+check ovn-nbctl ls-add ls3
+check ovn-nbctl ls-add ls4
+check ovn-nbctl acl-add ls1 to-lport 1000 ip4 allow
+check ovn-nbctl acl-add ls2 to-lport 1000 ip6 allow
+check ovn-nbctl --tier=1 acl-add ls3 to-lport 1000 tcp allow
+check ovn-nbctl --wait=sb --tier=1 acl-add ls4 to-lport 1000 udp allow
+
+acl1=$(fetch_column nb:ACL _uuid match=ip4)
+check_row_count Logical_DP_Group 3
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+
+# Move ls1's ACL to tier 1. ls1 stops generating the egress "acl action"
+# flows it shared with ls2, so those flows are left with a single datapath
+# and the {ls1, ls2} datapath group becomes unused and is deleted. northd
+# has to drop the reference it holds to that group, otherwise the group is
+# leaked in the in-memory dp group table, still pointing to the SB row that
+# has just been deleted.
+check ovn-nbctl --wait=sb set ACL $acl1 tier=1
+check_engine_stats lflow norecompute compute
+check_row_count Logical_DP_Group 2
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+
+# Move ls1's ACL back to tier 0. The {ls1, ls2} datapath group is needed
+# again: a leaked group would be picked up here and northd would complain
+# about its dangling SB reference.
+check ovn-nbctl --wait=sb set ACL $acl1 tier=0
+check_engine_stats lflow norecompute compute
+check_row_count Logical_DP_Group 3
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AT_CHECK([grep -q "logical_dp_group column is not set" \
+ northd/ovn-northd.log], [1])
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([Datapath group SB row reuse when an lflow loses a datapath])
+ovn_start
+
+# ls1, ls2 and ls3 have tier 0 ACLs, ls4 has a tier 1 ACL. The flows that
+# only depend on "the switch has ACLs" are shared by the four of them, and
+# the egress "acl action" flows of tier 0 are shared by ls1, ls2 and ls3.
+# ls4 is the only user of the tier 1 flows, so those are synced with a
+# single datapath. Hence two datapath groups: {ls1, ls2, ls3} and
+# {ls1, ls2, ls3, ls4}.
+check ovn-nbctl ls-add ls1
+check ovn-nbctl ls-add ls2
+check ovn-nbctl ls-add ls3
+check ovn-nbctl ls-add ls4
+check ovn-nbctl acl-add ls1 to-lport 1000 ip4 allow
+check ovn-nbctl acl-add ls2 to-lport 1000 ip6 allow
+check ovn-nbctl acl-add ls3 to-lport 1000 tcp allow
+check ovn-nbctl --wait=sb --tier=1 acl-add ls4 to-lport 1000 udp allow
+
+acl3=$(fetch_column nb:ACL _uuid match=tcp)
+check_row_count Logical_DP_Group 2
+ovn-sbctl --bare --columns _uuid list Logical_DP_Group | sort > dpg_uuids
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+
+# Move ls3's ACL to tier 1. The tier 0 "acl action" flows are left with
+# {ls1, ls2}: they are not generated again (ls1 and ls2 didn't change), they
+# are only unlinked from ls3's lflow_ref. The {ls1, ls2, ls3} group becomes
+# unused, so its SB row is free and northd is expected to modify it in place
+# instead of inserting a new row and deleting this one.
+check ovn-nbctl --wait=sb set ACL $acl3 tier=1
+check_engine_stats lflow norecompute compute
+check_row_count Logical_DP_Group 3
+
+ovn-sbctl --bare --columns _uuid list Logical_DP_Group | sort > dpg_uuids2
+AT_CHECK([comm -23 dpg_uuids dpg_uuids2], [0], [])
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
--
2.43.0
--
_'Esta mensagem é direcionada apenas para os endereços constantes no
cabeçalho inicial. Se você não está listado nos endereços constantes no
cabeçalho, pedimos-lhe que desconsidere completamente o conteúdo dessa
mensagem e cuja cópia, encaminhamento e/ou execução das ações citadas estão
imediatamente anuladas e proibidas'._
* **'Apesar do Magazine Luiza tomar
todas as precauções razoáveis para assegurar que nenhum vírus esteja
presente nesse e-mail, a empresa não poderá aceitar a responsabilidade por
quaisquer perdas ou danos causados por esse e-mail ou por seus anexos'.*
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev