Commit 1845c34c96 for frr

commit 1845c34c9698068b0219ac7219ab0470bc3444f6
Author: Donald Sharp <sharpd@nvidia.com>
Date:   Tue Jul 14 19:02:38 2026 -0400

    tests: Add some additional testing to the `bgp extcommunity-list`

    This command was not clear to me in how it operated.  I spent some
    time understanding it better by adding a topotest.

    Signed-off-by: Donald Sharp <sharpd@nvidia.com>

diff --git a/tests/topotests/bgp_extcomm_partial_delete/r1/frr.conf b/tests/topotests/bgp_extcomm_partial_delete/r1/frr.conf
new file mode 100644
index 0000000000..605152a3b2
--- /dev/null
+++ b/tests/topotests/bgp_extcomm_partial_delete/r1/frr.conf
@@ -0,0 +1,30 @@
+interface lo
+ ip address 172.16.255.254/32
+ ip address 172.16.255.253/32
+!
+interface r1-eth0
+ ip address 192.168.255.1/24
+!
+ip forwarding
+!
+hostname r1
+log stdout
+!
+router bgp 65000
+ no bgp ebgp-requires-policy
+ no bgp network import-check
+ neighbor 192.168.255.2 remote-as 65001
+ neighbor 192.168.255.2 timers 3 10
+ !
+ address-family ipv4 unicast
+  network 172.16.255.254/32 route-map r2-out-rt
+  network 172.16.255.253/32 route-map r2-out-soo
+ exit-address-family
+!
+route-map r2-out-rt permit 10
+ set extcommunity rt 65001:100 65001:200 65001:300
+!
+route-map r2-out-soo permit 10
+ set extcommunity soo 65002:100 65002:250 65002:300 65002:400
+ set extcommunity bandwidth 100
+!
diff --git a/tests/topotests/bgp_extcomm_partial_delete/r2/frr.conf b/tests/topotests/bgp_extcomm_partial_delete/r2/frr.conf
new file mode 100644
index 0000000000..38ab6b05c1
--- /dev/null
+++ b/tests/topotests/bgp_extcomm_partial_delete/r2/frr.conf
@@ -0,0 +1,34 @@
+interface r2-eth0
+ ip address 192.168.255.2/24
+!
+ip forwarding
+!
+hostname r2
+log stdout
+!
+bgp extcommunity-list expanded r1-rt permit 65001:(1[0-9]{2}|2[0-4][0-9]|250)
+bgp extcommunity-list expanded r1-soo permit 65002:(2[0-4][0-9]|25[0-9]|3[0-4][0-9]|350)
+!
+ip prefix-list RT-PREFIX seq 5 permit 172.16.255.254/32
+ip prefix-list SOO-PREFIX seq 5 permit 172.16.255.253/32
+!
+router bgp 65001
+ no bgp ebgp-requires-policy
+ neighbor 192.168.255.1 remote-as 65000
+ neighbor 192.168.255.1 timers 3 10
+ !
+ address-family ipv4 unicast
+  neighbor 192.168.255.1 activate
+  neighbor 192.168.255.1 route-map r1-in in
+ exit-address-family
+!
+route-map r1-in permit 10
+ match ip address prefix-list RT-PREFIX
+ set extended-comm-list delete r1-rt
+!
+route-map r1-in permit 20
+ match ip address prefix-list SOO-PREFIX
+ set extended-comm-list delete r1-soo
+!
+route-map r1-in permit 30
+!
diff --git a/tests/topotests/bgp_extcomm_partial_delete/test_bgp_extcomm_partial_delete.py b/tests/topotests/bgp_extcomm_partial_delete/test_bgp_extcomm_partial_delete.py
new file mode 100644
index 0000000000..2c81392449
--- /dev/null
+++ b/tests/topotests/bgp_extcomm_partial_delete/test_bgp_extcomm_partial_delete.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+# Copyright 2026 Nvidia Inc
+#                Donald Sharp
+"""
+bgp_extcomm_partial_delete.py:
+
+Verify that set extended-comm-list delete strips only extended communities
+matching expanded extcommunity-list regexes for RT and SoO routes.
+"""
+
+import functools
+import json
+import os
+import re
+import sys
+
+import pytest
+
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+
+# pylint: disable=C0413
+from lib import topotest
+from lib.topogen import Topogen, get_topogen
+
+pytestmark = [pytest.mark.bgpd]
+
+
+def build_topo(tgen):
+    for routern in range(1, 3):
+        tgen.add_router("r{}".format(routern))
+
+    switch = tgen.add_switch("s1")
+    switch.add_link(tgen.gears["r1"])
+    switch.add_link(tgen.gears["r2"])
+
+
+def setup_module(mod):
+    tgen = Topogen(build_topo, mod.__name__)
+    tgen.start_topology()
+
+    router_list = tgen.routers()
+
+    for _, (rname, router) in enumerate(router_list.items(), 1):
+        router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname)))
+
+    tgen.start_router()
+
+
+def teardown_module(mod):
+    tgen = get_topogen()
+    tgen.stop_topology()
+
+
+def _bgp_extcomm_partial_delete_check(gear, prefix, absent, present):
+    output = json.loads(gear.vtysh_cmd("show ip bgp {} json".format(prefix)))
+    ecoms = output.get("paths", [])[0].get("extendedCommunity", {}).get("string")
+    if not ecoms:
+        return False
+    for ecom in absent:
+        if re.search(ecom, ecoms):
+            return False
+    for ecom in present:
+        if re.search(ecom, ecoms) is None:
+            return False
+    return True
+
+
+def test_bgp_extcomm_partial_delete():
+    tgen = get_topogen()
+
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    r2 = tgen.gears["r2"]
+
+    def _bgp_converge():
+        output = json.loads(r2.vtysh_cmd("show ip bgp neighbor 192.168.255.1 json"))
+        expected = {
+            "192.168.255.1": {
+                "bgpState": "Established",
+                "addressFamilyInfo": {
+                    "ipv4Unicast": {
+                        "acceptedPrefixCounter": 2,
+                    }
+                },
+            }
+        }
+        return topotest.json_cmp(output, expected)
+
+    test_func = functools.partial(_bgp_converge)
+    _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5)
+    assert result is None, "BGP session did not converge"
+
+    test_func = functools.partial(
+        _bgp_extcomm_partial_delete_check,
+        r2,
+        "172.16.255.254/32",
+        [r"65001:100", r"65001:200"],
+        [r"65001:300"],
+    )
+    _, result = topotest.run_and_expect(test_func, True, count=60, wait=0.5)
+    assert (
+        result
+    ), "65001:100 and 65001:200 RTs should be stripped while 65001:300 remains"
+
+    test_func = functools.partial(
+        _bgp_extcomm_partial_delete_check,
+        r2,
+        "172.16.255.253/32",
+        [r"65002:250", r"65002:300"],
+        [r"65002:100", r"65002:400", r"LB:65000:12500000 \(100\.000 Mbps\)"],
+    )
+    _, result = topotest.run_and_expect(test_func, True, count=60, wait=0.5)
+    assert (
+        result
+    ), "65002:250 and 65002:300 SoOs should be stripped while 65002:100, 65002:400, and link bandwidth remain"
+
+
+if __name__ == "__main__":
+    args = ["-s"] + sys.argv[1:]
+    sys.exit(pytest.main(args))