Commit d5a6cfaaf2 for frr

commit d5a6cfaaf27c09b4be07242c53fc8371d87386c4
Author: Enke Chen <enchen@paloaltonetworks.com>
Date:   Tue Nov 3 11:13:34 2026 -0800

    tests: add topotest for prefix-list change withdraw scenario

    Add test_prefixlist_change_withdraw() to verify that modifying a
    prefix-list so the route-map condition is no longer met correctly
    withdraws the default route.

    This tests the flag handling fix where SUBGRP_STATUS_DEFAULT_ORIGINATE
    must be properly managed to ensure withdraws are sent when conditions
    change.

    Signed-off-by: Enke Chen <enchen@paloaltonetworks.com>

diff --git a/tests/topotests/bgp_default_originate_config/test_bgp_default_originate_config.py b/tests/topotests/bgp_default_originate_config/test_bgp_default_originate_config.py
index ab39d22b81..e698193834 100644
--- a/tests/topotests/bgp_default_originate_config/test_bgp_default_originate_config.py
+++ b/tests/topotests/bgp_default_originate_config/test_bgp_default_originate_config.py
@@ -24,17 +24,20 @@ config changes from ongoing re-evaluation:
   4. test_rmap_delete_withdraw: Deleting the route-map from the library while
      'default-originate route-map RMAP' is still configured withdraws the default.

-  5. test_table_readvertise: After de-config, a real 0.0.0.0/0 in the BGP table
+  5. test_prefixlist_change_withdraw: Modifying the prefix-list so the route-map
+     condition is no longer met withdraws the default.
+
+  6. test_table_readvertise: After de-config, a real 0.0.0.0/0 in the BGP table
      is re-announced via subgroup_process_announce_selected().

-  6. test_cond_withdraw_from_table: When conditional default-originate is
+  7. test_cond_withdraw_from_table: When conditional default-originate is
      configured with an unmet condition, a previously table-advertised 0.0.0.0/0
      is withdrawn.

-  7. test_cond_never_sent: When the condition is never met, the synthesized
+  8. test_cond_never_sent: When the condition is never met, the synthesized
      default is never sent, even after the re-evaluation timer fires.

-  8. test_peergroup_inheritance: When removing a neighbor-specific override,
+  9. test_peergroup_inheritance: When removing a neighbor-specific override,
      the neighbor inherits default-originate from its peer-group.

 Topology:
@@ -96,6 +99,7 @@ def teardown_module(mod):
 # Shared helpers
 # ---------------------------------------------------------------------------

+
 def _bgp_established(router, neighbor):
     output = json.loads(router.vtysh_cmd("show bgp neighbor {} json".format(neighbor)))
     expected = {neighbor: {"bgpState": "Established"}}
@@ -169,6 +173,7 @@ def _wait_for(test_func, timeout=30):
 # Test 1: adj-out cache clearing on config transition
 # ---------------------------------------------------------------------------

+
 def test_adjout_cache_clearing():
     """
     Verify adj-out cache is cleared when default-originate is activated.
@@ -193,7 +198,9 @@ def test_adjout_cache_clearing():
         router bgp 65001
             address-family ipv4 unicast
                 no neighbor {} default-originate
-    """.format(R2_IPV4)
+    """.format(
+            R2_IPV4
+        )
     )

     step("Wait for table route (with community) to be advertised")
@@ -206,14 +213,20 @@ def test_adjout_cache_clearing():
         router bgp 65001
             address-family ipv4 unicast
                 neighbor {} default-originate
-    """.format(R2_IPV4)
+    """.format(
+            R2_IPV4
+        )
     )

     step("Verify adj-out shows only synthesized route")

     def _check_adjout():
         output = json.loads(
-            r1.vtysh_cmd("show bgp ipv4 unicast neighbors {} advertised-routes json".format(R2_IPV4))
+            r1.vtysh_cmd(
+                "show bgp ipv4 unicast neighbors {} advertised-routes json".format(
+                    R2_IPV4
+                )
+            )
         )
         if output.get("bgpOriginatingDefaultNetwork") != "0.0.0.0/0":
             return "bgpOriginatingDefaultNetwork not set"
@@ -231,6 +244,7 @@ def test_adjout_cache_clearing():
 # Test 2: conditional default-originate does not fall back to table route
 # ---------------------------------------------------------------------------

+
 def test_conditional_no_fallback():
     """
     Verify conditional default-originate does not fall back to table route.
@@ -251,7 +265,9 @@ def test_conditional_no_fallback():
         router bgp 65001
             address-family ipv4 unicast
                 neighbor {} default-originate route-map CHECK_TRIGGER
-    """.format(R2_IPV4)
+    """.format(
+            R2_IPV4
+        )
     )

     step("Verify R2 receives synthesized default (condition met)")
@@ -288,6 +304,7 @@ def test_conditional_no_fallback():
 # Test 3: explicit de-config withdraws the default route
 # ---------------------------------------------------------------------------

+
 def test_deconfig_withdraw():
     """
     Verify 'no neighbor X default-originate' withdraws the synthesized default.
@@ -319,7 +336,9 @@ def test_deconfig_withdraw():
         router bgp 65001
             address-family ipv4 unicast
                 neighbor {} default-originate
-    """.format(R2_IPV4)
+    """.format(
+            R2_IPV4
+        )
     )

     step("Verify R2 receives synthesized default (no community)")
@@ -332,7 +351,9 @@ def test_deconfig_withdraw():
         router bgp 65001
             address-family ipv4 unicast
                 no neighbor {} default-originate
-    """.format(R2_IPV4)
+    """.format(
+            R2_IPV4
+        )
     )

     step("Verify R2 has no default (explicit withdrawal sent)")
@@ -346,7 +367,9 @@ def test_deconfig_withdraw():
             address-family ipv4 unicast
                 network 0.0.0.0/0 route-map SET_COMM
                 neighbor {} default-originate
-    """.format(R2_IPV4)
+    """.format(
+            R2_IPV4
+        )
     )

     step("Verify R2 receives default again")
@@ -359,7 +382,9 @@ def test_deconfig_withdraw():
         router bgp 65001
             address-family ipv4 unicast
                 no neighbor {} default-originate
-    """.format(R2_IPV4)
+    """.format(
+            R2_IPV4
+        )
     )

     step("Verify table route (with community) is re-advertised")
@@ -370,6 +395,7 @@ def test_deconfig_withdraw():
 # Test 4: deleting route-map withdraws the default route
 # ---------------------------------------------------------------------------

+
 def test_rmap_delete_withdraw():
     """
     Verify deleting the route-map from the library withdraws the default.
@@ -391,7 +417,9 @@ def test_rmap_delete_withdraw():
         router bgp 65001
             address-family ipv4 unicast
                 neighbor {} default-originate route-map SET_COMM
-    """.format(R2_IPV4)
+    """.format(
+            R2_IPV4
+        )
     )

     step("Verify R2 receives default")
@@ -414,14 +442,75 @@ def test_rmap_delete_withdraw():
     configure
         route-map SET_COMM permit 10
             set community {}
-    """.format(COMMUNITY)
+    """.format(
+            COMMUNITY
+        )
+    )
+
+
+# ---------------------------------------------------------------------------
+# Test 5: prefix-list modification withdraws default
+# ---------------------------------------------------------------------------
+
+
+def test_prefixlist_change_withdraw():
+    """
+    Verify modifying prefix-list so condition is unmet withdraws the default.
+
+    This tests the scenario where the route-map still exists but its match
+    condition changes.  The flag handling must correctly send the withdraw.
+    """
+    tgen = get_topogen()
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    r1 = tgen.gears["r1"]
+
+    step("Configure conditional default-originate with route-map")
+    r1.vtysh_cmd(
+        """
+    configure
+        router bgp 65001
+            address-family ipv4 unicast
+                neighbor {} default-originate route-map CHECK_TRIGGER
+    """.format(
+            R2_IPV4
+        )
+    )
+
+    step("Verify R2 receives default (condition met: 10.10.10.10/32 exists)")
+    assert _wait_for(_r2_has_default_no_community) is None
+
+    step("Modify prefix-list to match non-existent prefix")
+    r1.vtysh_cmd(
+        """
+    configure
+        no ip prefix-list TRIGGER seq 5 permit 10.10.10.10/32
+        ip prefix-list TRIGGER seq 5 permit 10.99.99.99/32
+    """
+    )
+
+    step("Verify default is withdrawn (condition no longer met)")
+    assert _wait_for(_r2_has_no_default) is None
+
+    step("Restore prefix-list to match existing prefix")
+    r1.vtysh_cmd(
+        """
+    configure
+        no ip prefix-list TRIGGER seq 5 permit 10.99.99.99/32
+        ip prefix-list TRIGGER seq 5 permit 10.10.10.10/32
+    """
     )

+    step("Verify default is advertised again")
+    assert _wait_for(_r2_has_default_no_community) is None
+

 # ---------------------------------------------------------------------------
-# Test 5: table route is re-advertised after de-config
+# Test 6: table route is re-advertised after de-config
 # ---------------------------------------------------------------------------

+
 def test_table_readvertise():
     """
     Verify table route is re-advertised after removing default-originate.
@@ -443,7 +532,9 @@ def test_table_readvertise():
         router bgp 65001
             address-family ipv4 unicast
                 neighbor {} default-originate
-    """.format(R2_IPV4)
+    """.format(
+            R2_IPV4
+        )
     )

     step("Verify R2 receives synthesized default (no community)")
@@ -456,7 +547,9 @@ def test_table_readvertise():
         router bgp 65001
             address-family ipv4 unicast
                 no neighbor {} default-originate
-    """.format(R2_IPV4)
+    """.format(
+            R2_IPV4
+        )
     )

     step("Verify table route (with community) is re-advertised")
@@ -464,9 +557,10 @@ def test_table_readvertise():


 # ---------------------------------------------------------------------------
-# Test 6: conditional default-originate with unmet condition withdraws table route
+# Test 7: conditional default-originate with unmet condition withdraws table route
 # ---------------------------------------------------------------------------

+
 def test_cond_withdraw_from_table():
     """
     Verify conditional default-originate withdraws table-advertised 0.0.0.0/0.
@@ -494,7 +588,9 @@ def test_cond_withdraw_from_table():
         router bgp 65001
             address-family ipv4 unicast
                 neighbor {} default-originate route-map COND_NEVER_MET
-    """.format(R2_IPV4)
+    """.format(
+            R2_IPV4
+        )
     )

     step("Verify table route is withdrawn (condition unmet)")
@@ -502,9 +598,10 @@ def test_cond_withdraw_from_table():


 # ---------------------------------------------------------------------------
-# Test 7: synthesized default never sent when condition never met
+# Test 8: synthesized default never sent when condition never met
 # ---------------------------------------------------------------------------

+
 def test_cond_never_sent():
     """
     Verify synthesized default is never sent when condition is never met.
@@ -556,9 +653,10 @@ def test_cond_never_sent():


 # ---------------------------------------------------------------------------
-# Test 8: peer-group inheritance of default-originate
+# Test 9: peer-group inheritance of default-originate
 # ---------------------------------------------------------------------------

+
 def test_peergroup_inheritance():
     """
     Verify removing neighbor override inherits default-originate from peer-group.
@@ -580,7 +678,9 @@ def test_peergroup_inheritance():
         router bgp 65001
             address-family ipv4 unicast
                 no neighbor {} default-originate
-    """.format(R2_IPV4)
+    """.format(
+            R2_IPV4
+        )
     )

     step("Create peer-group with default-originate")
@@ -604,7 +704,9 @@ def test_peergroup_inheritance():
     configure
         router bgp 65001
             neighbor {} peer-group DEFORG
-    """.format(R2_IPV4)
+    """.format(
+            R2_IPV4
+        )
     )

     step("Verify r2 receives default (from peer-group)")
@@ -617,7 +719,9 @@ def test_peergroup_inheritance():
         router bgp 65001
             address-family ipv4 unicast
                 neighbor {} default-originate route-map CHECK_TRIGGER
-    """.format(R2_IPV4)
+    """.format(
+            R2_IPV4
+        )
     )

     step("Verify r2 still receives default (condition met)")
@@ -630,7 +734,9 @@ def test_peergroup_inheritance():
         router bgp 65001
             address-family ipv4 unicast
                 no neighbor {} default-originate
-    """.format(R2_IPV4)
+    """.format(
+            R2_IPV4
+        )
     )

     step("Verify r2 still receives default (inherited from peer-group)")
@@ -648,7 +754,9 @@ def test_peergroup_inheritance():
             address-family ipv4 unicast
                 neighbor {} default-originate
                 neighbor {} send-community
-    """.format(R2_IPV4, R2_IPV4, R2_IPV4, R2_IPV4, R2_IPV4, R2_IPV4)
+    """.format(
+            R2_IPV4, R2_IPV4, R2_IPV4, R2_IPV4, R2_IPV4, R2_IPV4
+        )
     )