Commit a703920aa9 for frr
commit a703920aa94ef8a19235e77b063d846990bcad02
Author: Chirag Shah <chirag@nvidia.com>
Date: Sun Sep 13 18:40:36 2026 -0700
lib: unconfigure user VRFs in lib_vrf_destroy without vrf_delete
no vrf while the kernel device is up used to fail VALIDATE, so FRR kept
inactive (configured) VRFs until a later reload. APPLY now clears
VRF_CONFIGURED and skips vrf_delete() while VRF_ACTIVE so the RIB stays
until netlink deletes the device. Keep the mgmtd abort coverage in
test_regress2 via no interface on an active iface.
Without this (current behavior):
When no vrf is performed it would not remove "configured".
until the net device is not deleted, the vrf2 stanza wil remained,
only user configured flag is unset.
no vrf vrf2
show vrfs:
vrf vrf1 id 78 table 1002 (configured)
vrf vrf2 id 80 table 1004
Ticket: #5233072
Signed-off-by: Chirag Shah <chirag@nvidia.com>
diff --git a/lib/vrf.c b/lib/vrf.c
index b27866c976..30a2689d48 100644
--- a/lib/vrf.c
+++ b/lib/vrf.c
@@ -941,9 +941,13 @@ static int lib_vrf_destroy(struct nb_cb_destroy_args *args)
switch (args->event) {
case NB_EV_VALIDATE:
vrfp = nb_running_get_entry(args->dnode, NULL, true);
- if (CHECK_FLAG(vrfp->status, VRF_ACTIVE)) {
+ /* Kernel-backed VRFs are unconfigured in APPLY, not rejected
+ * here. Reject only the default VRF: it is always active and
+ * vrf_delete() must not run against it.
+ */
+ if (vrfp && strcmp(vrfp->name, VRF_DEFAULT_NAME) == 0) {
snprintf(args->errmsg, args->errmsg_len,
- "Only inactive VRFs can be deleted");
+ "Default VRF cannot be deleted");
return NB_ERR_VALIDATION;
}
break;
@@ -952,9 +956,18 @@ static int lib_vrf_destroy(struct nb_cb_destroy_args *args)
break;
case NB_EV_APPLY:
vrfp = nb_running_unset_entry(args->dnode);
+ if (!vrfp)
+ return NB_OK;
- /* Clear configured flag and invoke delete. */
UNSET_FLAG(vrfp->status, VRF_CONFIGURED);
+ /* vrf_delete() calls vrf_disable() when the VRF is enabled.
+ * That must not run while the kernel device is still up:
+ * drop FRR configuration only and leave the object in place.
+ */
+ if (CHECK_FLAG(vrfp->status, VRF_ACTIVE) ||
+ vrf_is_enabled(vrfp))
+ return NB_OK;
+
vrf_delete(vrfp);
break;
}
diff --git a/tests/topotests/mgmt_config/test_regress2.py b/tests/topotests/mgmt_config/test_regress2.py
index b24207d4a7..6422a05d2d 100644
--- a/tests/topotests/mgmt_config/test_regress2.py
+++ b/tests/topotests/mgmt_config/test_regress2.py
@@ -74,16 +74,41 @@ def test_regression_disconnect_after_abort(tgen):
wl.snapshot()
- step('Try to un-config an active vrf (i.e., "no vrf"), verify failure')
+ step('Un-config an active VRF ("no vrf"); kernel VRF stays, not configured')
output = r1g.vtysh_multicmd(
"""
show vrf
conf t
no vrf red
end
+ show vrf
+ """
+ )
+ assert "Only inactive VRFs can be deleted" not in output
+ assert "Configuration failed" not in output
+ # Combined vtysh output includes both show vrf dumps; take the last
+ # "vrf red ..." status line (not the first, and not "no vrf red").
+ red_lines = [
+ line.strip()
+ for line in output.splitlines()
+ if line.strip().startswith("vrf red")
+ ]
+ # Both show vrf dumps must list kernel VRF "red" (created by the fixture).
+ assert len(red_lines) >= 2, output
+ # First dump is before no vrf; the stanza is still user-configured.
+ assert "(configured)" in red_lines[0], red_lines[0]
+ # Last dump is after no vrf; VRF_CONFIGURED is gone, kernel VRF remains.
+ assert "(configured)" not in red_lines[-1], red_lines[-1]
+
+ step("Trigger a VALIDATE abort via no interface on an active interface")
+ output = r1g.vtysh_multicmd(
+ """
+ conf t
+ no interface r1-eth0
+ end
"""
)
- assert "Only inactive VRFs can be deleted" in output
+ assert "only inactive interfaces can be deleted" in output.lower()
logged = wl.snapshot()
@@ -95,8 +120,8 @@ def test_regression_disconnect_after_abort(tgen):
regex = r"([-0-9A-Z_]+): \[[-0-9A-Z]*\] BE-CLIENT:.*Ignoring TXN_DELETE"
matches = re.findall(regex, logged)
assert (
- len(matches) == 2
- ), f"Wrong number of clients (2 != {len(matches)}) received TXN_REQ delete"
+ len(matches) >= 1
+ ), f"Wrong number of clients ({len(matches)}) received TXN_REQ delete"
step("Checking for still locked regression")
check_locked(r1g)