diff options
Diffstat (limited to 'tests')
26 files changed, 948 insertions, 14 deletions
diff --git a/tests/topotests/bgp_evpn_route_map_match/r1/frr.conf b/tests/topotests/bgp_evpn_route_map_match/r1/frr.conf index 4347052c5e..2390733cc8 100644 --- a/tests/topotests/bgp_evpn_route_map_match/r1/frr.conf +++ b/tests/topotests/bgp_evpn_route_map_match/r1/frr.conf @@ -24,19 +24,16 @@ router bgp 65001 ! address-family l2vpn evpn neighbor 192.168.1.2 activate - neighbor 192.168.1.2 route-map r2 out + neighbor 192.168.1.2 route-map rt5 out advertise-all-vni advertise ipv4 unicast exit-address-family ! -route-map r2 deny 10 - match evpn route-type macip -! -route-map r2 deny 20 +route-map rt5 deny 20 match ip address prefix-list pl match evpn route-type prefix ! -route-map r2 permit 30 +route-map rt5 permit 30 ! ip prefix-list pl seq 5 permit 192.168.1.0/24 ip prefix-list pl seq 10 permit 10.10.10.1/32 diff --git a/tests/topotests/bgp_evpn_route_map_match/r2/frr.conf b/tests/topotests/bgp_evpn_route_map_match/r2/frr.conf index 9ed298d8fe..1c91a3e254 100644 --- a/tests/topotests/bgp_evpn_route_map_match/r2/frr.conf +++ b/tests/topotests/bgp_evpn_route_map_match/r2/frr.conf @@ -7,6 +7,7 @@ int lo int r2-eth0 ip address 192.168.1.2/24 ! +vni 10 router bgp 65002 no bgp ebgp-requires-policy neighbor 192.168.1.1 remote-as external diff --git a/tests/topotests/bgp_evpn_route_map_match/test_bgp_evpn_route_map_match.py b/tests/topotests/bgp_evpn_route_map_match/test_bgp_evpn_route_map_match.py index 36c79d6b2b..925ae1fce8 100644 --- a/tests/topotests/bgp_evpn_route_map_match/test_bgp_evpn_route_map_match.py +++ b/tests/topotests/bgp_evpn_route_map_match/test_bgp_evpn_route_map_match.py @@ -23,6 +23,7 @@ sys.path.append(os.path.join(CWD, "../")) # pylint: disable=C0413 from lib import topotest from lib.topogen import Topogen, get_topogen +from lib.topolog import logger def setup_module(mod): @@ -63,7 +64,7 @@ def teardown_module(mod): tgen.stop_topology() -def test_bgp_evpn_route_map_match_route_type(): +def test_bgp_evpn_route_map_match_route_type5(): tgen = get_topogen() if tgen.routers_have_failure(): @@ -84,16 +85,12 @@ def test_bgp_evpn_route_map_match_route_type(): "valid": True, } }, - "10.10.10.2:2": { - "[3]:[0]:[32]:[10.10.10.2]": { - "valid": True, - } - }, }, - "totalPrefixCounter": 2, + "totalPrefixCounter": 1, } return topotest.json_cmp(output, expected) + logger.info("Check route type-5 filtering") test_func = functools.partial( _bgp_converge, ) @@ -101,6 +98,97 @@ def test_bgp_evpn_route_map_match_route_type(): assert result is None, "Filtered EVPN routes should not be advertised" +def test_bgp_evpn_route_map_match_route_type2(): + tgen = get_topogen() + + # Change to L2VNI + for machine in [tgen.gears["r1"], tgen.gears["r2"]]: + machine.vtysh_cmd("configure terminal\nno vni 10") + + def _check_l2vni(): + for machine in [tgen.gears["r1"], tgen.gears["r2"]]: + output = json.loads(machine.vtysh_cmd("show evpn vni json")) + + expected = {"10": {"vni": 10, "type": "L2"}} + return topotest.json_cmp(output, expected) + + logger.info("Check L2VNI setup") + test_func = functools.partial(_check_l2vni) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert result is None, "L2VNI setup failed." + + c2_mac = ( + tgen.gears["c2"] + .cmd("ip link show c2-eth0 | awk '/link\/ether/ {print $2}'") + .rstrip() + ) + tgen.gears["r1"].vtysh_cmd( + "\n".join( + [ + "configure", + "route-map rt2 deny 30", + "match mac address %s" % c2_mac, + "exit", + "router bgp 65001", + "address-family l2vpn evpn", + "neighbor 192.168.1.2 route-map rt2 in", + ] + ) + ) + + def _check_filter_mac(): + output = json.loads( + tgen.gears["r1"].vtysh_cmd( + "show bgp l2vpn evpn neighbors 192.168.1.2 advertised-routes json" + ) + ) + + if ( + output["advertisedRoutes"] + .get("10.10.10.2:2", {}) + .get("[2]:[0]:[48]:[%s]" % c2_mac) + ): + return False + + return True + + logger.info("check mac filter in, on c2 interface: %s" % c2_mac) + test_func = functools.partial(_check_filter_mac) + _, result = topotest.run_and_expect(test_func, True, count=60, wait=1) + assert result is True, "%s is not filtered" % c2_mac + + tgen.gears["r1"].vtysh_cmd( + "\n".join( + [ + "configure", + "route-map rt2 deny 30", + "no match mac address %s" % c2_mac, + "match evpn route-type macip" "exit", + "router bgp 65001", + "address-family l2vpn evpn", + "neighbor 192.168.1.2 route-map rt2 out", + ] + ) + ) + + def _check_filter_type2(): + output = json.loads( + tgen.gears["r1"].vtysh_cmd( + "show bgp l2vpn evpn neighbors 192.168.1.2 advertised-routes json" + ) + ) + + if output["totalPrefixCounter"] == 0: + return True + + return False + + logger.info("check route type-2 filter out") + test_func = functools.partial(_check_filter_type2) + _, result = topotest.run_and_expect(test_func, True, count=60, wait=1) + assert result is True, "EVPN routes type-2 are not filtered." + + if __name__ == "__main__": args = ["-s"] + sys.argv[1:] sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_ipv6_ll_peering/r1/bgpd.conf b/tests/topotests/bgp_ipv6_ll_peering/r1/bgpd.conf index 724cbf84ab..a26efb4c4f 100644 --- a/tests/topotests/bgp_ipv6_ll_peering/r1/bgpd.conf +++ b/tests/topotests/bgp_ipv6_ll_peering/r1/bgpd.conf @@ -4,3 +4,6 @@ router bgp 65001 neighbor fe80:1::2 remote-as external neighbor fe80:1::2 timers 3 10 neighbor fe80:1::2 interface r1-eth0 + neighbor fe80:1::3 remote-as external + neighbor fe80:1::3 timers 3 10 + neighbor fe80:1::3 interface r1-eth1 diff --git a/tests/topotests/bgp_ipv6_ll_peering/r1/zebra.conf b/tests/topotests/bgp_ipv6_ll_peering/r1/zebra.conf index 4e93d4f4e5..f1bbff2e44 100644 --- a/tests/topotests/bgp_ipv6_ll_peering/r1/zebra.conf +++ b/tests/topotests/bgp_ipv6_ll_peering/r1/zebra.conf @@ -2,3 +2,6 @@ interface r1-eth0 ipv6 address fe80:1::1/64 ! +interface r1-eth1 + ipv6 address fe80:1::1/64 +! diff --git a/tests/topotests/bgp_ipv6_ll_peering/r3/bgpd.conf b/tests/topotests/bgp_ipv6_ll_peering/r3/bgpd.conf new file mode 100644 index 0000000000..f1684880b3 --- /dev/null +++ b/tests/topotests/bgp_ipv6_ll_peering/r3/bgpd.conf @@ -0,0 +1,5 @@ +router bgp 65003 + no bgp ebgp-requires-policy + neighbor fe80:1::1 remote-as external + neighbor fe80:1::1 timers 3 10 + neighbor fe80:1::1 interface r3-eth0 diff --git a/tests/topotests/bgp_ipv6_ll_peering/r3/zebra.conf b/tests/topotests/bgp_ipv6_ll_peering/r3/zebra.conf new file mode 100644 index 0000000000..71053cd2c3 --- /dev/null +++ b/tests/topotests/bgp_ipv6_ll_peering/r3/zebra.conf @@ -0,0 +1,4 @@ +! +interface r3-eth0 + ipv6 address fe80:1::3/64 +! diff --git a/tests/topotests/bgp_ipv6_ll_peering/test_bgp_ipv6_ll_peering.py b/tests/topotests/bgp_ipv6_ll_peering/test_bgp_ipv6_ll_peering.py index aaa68ea340..fbd4097605 100644 --- a/tests/topotests/bgp_ipv6_ll_peering/test_bgp_ipv6_ll_peering.py +++ b/tests/topotests/bgp_ipv6_ll_peering/test_bgp_ipv6_ll_peering.py @@ -27,13 +27,17 @@ pytestmark = [pytest.mark.bgpd] def build_topo(tgen): - for routern in range(1, 3): + for routern in range(1, 4): tgen.add_router("r{}".format(routern)) switch = tgen.add_switch("s1") switch.add_link(tgen.gears["r1"]) switch.add_link(tgen.gears["r2"]) + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r3"]) + def setup_module(mod): tgen = Topogen(build_topo, mod.__name__) @@ -64,6 +68,7 @@ def test_bgp_ipv6_link_local_peering(): pytest.skip(tgen.errors) r1 = tgen.gears["r1"] + r3 = tgen.gears["r3"] def _bgp_converge(): output = json.loads(r1.vtysh_cmd("show bgp summary json")) @@ -82,6 +87,28 @@ def test_bgp_ipv6_link_local_peering(): _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) assert result is None, "Failed to see BGP convergence on R2" + def _bgp_router_id_missing(): + output = json.loads(r3.vtysh_cmd("show bgp summary failed json")) + expected = { + "ipv4Unicast": { + "routerId": "0.0.0.0", + "as": 65003, + "peers": { + "fe80:1::1": { + "connectionsEstablished": 0, + "connectionsDropped": 0, + "peerUptime": "never", + "lastResetDueTo": "Router ID is missing", + } + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_router_id_missing) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "r3 should stay down due to missing router ID" + if __name__ == "__main__": args = ["-s"] + sys.argv[1:] diff --git a/tests/topotests/bgp_oad/r3/frr.conf b/tests/topotests/bgp_oad/r3/frr.conf index 02dd5adfe1..164267d74d 100644 --- a/tests/topotests/bgp_oad/r3/frr.conf +++ b/tests/topotests/bgp_oad/r3/frr.conf @@ -7,12 +7,14 @@ int r3-eth0 ! router bgp 65003 no bgp ebgp-requires-policy + no bgp network import-check neighbor 192.168.2.2 remote-as external neighbor 192.168.2.2 timers 1 3 neighbor 192.168.2.2 timers connect 1 neighbor 192.168.2.2 oad ! address-family ipv4 unicast + network 10.10.10.20/32 route-map static redistribute connected route-map connected exit-address-family ! @@ -20,3 +22,7 @@ route-map connected permit 10 set local-preference 123 set metric 123 ! +route-map static permit 10 + set extcommunity bandwidth 100 non-transitive +exit +! diff --git a/tests/topotests/bgp_oad/test_bgp_oad.py b/tests/topotests/bgp_oad/test_bgp_oad.py index b2ea7e0f19..b397bc6372 100644 --- a/tests/topotests/bgp_oad/test_bgp_oad.py +++ b/tests/topotests/bgp_oad/test_bgp_oad.py @@ -56,6 +56,7 @@ def test_bgp_oad(): r2 = tgen.gears["r2"] r3 = tgen.gears["r3"] r4 = tgen.gears["r4"] + r5 = tgen.gears["r5"] def _bgp_converge(): output = json.loads(r1.vtysh_cmd("show bgp ipv4 unicast 10.10.10.10/32 json")) @@ -121,6 +122,38 @@ def test_bgp_oad(): _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) assert result is None, "10.10.10.1/32 should not be advertised to r4 (not OAD peer)" + def _bgp_check_non_transitive_extended_community( + router, arg={"string": "LB:65003:12500000 (100.000 Mbps)"} + ): + output = json.loads( + router.vtysh_cmd("show bgp ipv4 unicast 10.10.10.20/32 json") + ) + expected = { + "paths": [ + { + "extendedCommunity": arg, + } + ] + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_check_non_transitive_extended_community, + r4, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert ( + result is None + ), "10.10.10.20/32 should be received at r4 with non-transitive extended community" + + test_func = functools.partial( + _bgp_check_non_transitive_extended_community, r5, None + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert ( + result is None + ), "10.10.10.20/32 should NOT be received at r5 with non-transitive extended community" + if __name__ == "__main__": args = ["-s"] + sys.argv[1:] diff --git a/tests/topotests/bgp_srv6_sid_reachability/test_bgp_srv6_sid_reachability.py b/tests/topotests/bgp_srv6_sid_reachability/test_bgp_srv6_sid_reachability.py index cf590ad01d..5c0b909517 100755 --- a/tests/topotests/bgp_srv6_sid_reachability/test_bgp_srv6_sid_reachability.py +++ b/tests/topotests/bgp_srv6_sid_reachability/test_bgp_srv6_sid_reachability.py @@ -159,6 +159,36 @@ def test_sid_reachable_again_bgp_update(): check_ping("c11", "192.168.2.1", True, 10, 1) +def test_sid_unreachable_no_router(): + get_topogen().gears["r2"].vtysh_cmd( + """ + configure terminal + no router bgp 65002 vrf vrf10 + """ + ) + check_ping("c11", "192.168.2.1", False, 10, 1) + + +def test_sid_reachable_again_no_router(): + get_topogen().gears["r2"].vtysh_cmd( + """ + configure terminal + router bgp 65002 vrf vrf10 + bgp router-id 192.0.2.2 + ! + address-family ipv4 unicast + redistribute connected + sid vpn export 1 + rd vpn export 65002:10 + rt vpn both 0:10 + import vpn + export vpn + exit-address-family + """ + ) + check_ping("c11", "192.168.2.1", True, 10, 1) + + if __name__ == "__main__": args = ["-s"] + sys.argv[1:] sys.exit(pytest.main(args)) diff --git a/tests/topotests/sbfd_topo1/__init__.py b/tests/topotests/sbfd_topo1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/topotests/sbfd_topo1/__init__.py diff --git a/tests/topotests/sbfd_topo1/r1/frr.conf b/tests/topotests/sbfd_topo1/r1/frr.conf new file mode 100644 index 0000000000..f865b81303 --- /dev/null +++ b/tests/topotests/sbfd_topo1/r1/frr.conf @@ -0,0 +1,8 @@ +ip forwarding +ipv6 forwarding +! + +interface r1-eth0 + ipv6 address 2001::10/64 +! +! diff --git a/tests/topotests/sbfd_topo1/r2/frr.conf b/tests/topotests/sbfd_topo1/r2/frr.conf new file mode 100644 index 0000000000..c9d166cabb --- /dev/null +++ b/tests/topotests/sbfd_topo1/r2/frr.conf @@ -0,0 +1,8 @@ +ip forwarding +ipv6 forwarding +! + +interface r2-eth0 + ipv6 address 2001::20/64 +! +! diff --git a/tests/topotests/sbfd_topo1/sbfd_topo1.dot b/tests/topotests/sbfd_topo1/sbfd_topo1.dot new file mode 100644 index 0000000000..437e8230ce --- /dev/null +++ b/tests/topotests/sbfd_topo1/sbfd_topo1.dot @@ -0,0 +1,45 @@ +## Color coding: +######################### +## Main FRR: #f08080 red +## Switches: #d0e0d0 gray +## RIP: #19e3d9 Cyan +## RIPng: #fcb314 dark yellow +## OSPFv2: #32b835 Green +## OSPFv3: #19e3d9 Cyan +## ISIS IPv4 #fcb314 dark yellow +## ISIS IPv6 #9a81ec purple +## BGP IPv4 #eee3d3 beige +## BGP IPv6 #fdff00 yellow +##### Colors (see http://www.color-hex.com/) + +graph template { + label="template"; + + # Routers + r1 [ + shape=doubleoctagon, + label="A\nAS 100\n1.1.1.1", + fillcolor="#f08080", + style=filled, + ]; + r2 [ + shape=doubleoctagon + label="B\nAS 200\n1.1.1.2", + fillcolor="#f08080", + style=filled, + ]; + + # Switches + s1 [ + shape=oval, + label="s1\n192.168.0.0/24", + fillcolor="#d0e0d0", + style=filled, + ]; + + + # Connections + r1 -- s1 [label="A-eth0"]; + r2 -- s1 [label="B-eth0"]; + +} diff --git a/tests/topotests/sbfd_topo1/test_sbfd_topo1.py b/tests/topotests/sbfd_topo1/test_sbfd_topo1.py new file mode 100644 index 0000000000..e20902ebf5 --- /dev/null +++ b/tests/topotests/sbfd_topo1/test_sbfd_topo1.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python + +# +# test_sbfd_topo1.py +# basic test cases for sbfd initiator and reflector +# +# Copyright (c) 2025 by Alibaba, Inc. +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# + +""" +<template>.py: Test <template>. +""" + +import os +import sys +import pytest +import json +import re +import time +import pdb +from functools import partial + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, '../')) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger +from lib.common_config import required_linux_kernel_version + +""" +test_sbfd_topo1.py: test simple sbfd with IPv6 encap. RT1 is sbfd Initiator, RT2 is sbfd Reflector + + +----+----+ +----+----+ + | | | | + | RT1 | 1 | RT2 | + | +--------+ | + | 2001::10| | 2001::20| + +----+----+ +----+----+ + +""" +pytestmark = [pytest.mark.bfdd, pytest.mark.sbfd] + +def show_bfd_check(router, status, type='echo', encap=None): + output = router.cmd("vtysh -c 'show bfd peers'") + if encap: + # check encap data if any + pattern1 = re.compile(r'encap-data {}'.format(encap)) + ret = pattern1.findall(output) + if len(ret) <= 0: + logger.info("encap-data not match") + return False + + # check status + pattern2 = re.compile(r'Status: {}'.format(status)) + ret = pattern2.findall(output) + if len(ret) <= 0: + logger.info("Status not match") + return False + + # check type + pattern3 = re.compile(r'Peer Type: {}'.format(type)) + ret = pattern3.findall(output) + if len(ret) <= 0: + logger.info("Peer Type not match") + return False + + logger.info("all check passed") + return True + +def build_topo(tgen): + "Test topology builder" + + # This function only purpose is to define allocation and relationship + # between routers, switches and hosts. + # + # Example + # + # Create 2 routers + for routern in range(1, 3): + tgen.add_router('r{}'.format(routern)) + + # Create a switch with just one router connected to it to simulate a + # empty network. + switch = tgen.add_switch('s1') + switch.add_link(tgen.gears['r1']) + switch.add_link(tgen.gears['r2']) + +def setup_module(mod): + "Sets up the pytest environment" + # This function initiates the topology build with Topogen... + tgen = Topogen(build_topo, mod.__name__) + # ... and here it calls Mininet initialization functions. + tgen.start_topology() + + # This is a sample of configuration loading. + router_list = tgen.routers() + + for rname, router in router_list.items(): + router.load_frr_config( + os.path.join(CWD, "{}/frr.conf".format(rname)), + [(TopoRouter.RD_ZEBRA, None), (TopoRouter.RD_BFD, None)]) + + # After loading the configurations, this function loads configured daemons. + tgen.start_router() + + # Verify that we are using the proper version and that the BFD + # daemon exists. + for router in router_list.values(): + # Check for Version + if router.has_version('<', '5.1'): + tgen.set_error('Unsupported FRR version') + break + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + # This function tears down the whole topology. + tgen.stop_topology() + + +# step 1 : config sbfd Initiator and reflector +def test_sbfd_config_check(): + "Assert that config sbfd and check sbfd status." + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.5") + if result is not True: + pytest.skip("Kernel requirements are not met") + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # config sbfd + r1 = tgen.net['r1'] + r1.cmd("ping -c 5 2001::20") + r1.cmd("vtysh -c 'config t' -c 'bfd' -c 'peer 2001::20 bfd-mode sbfd-init bfd-name 2-44 local-address 2001::10 remote-discr 1234'") + + r2 = tgen.net['r2'] + r2.cmd("vtysh -c 'config t' -c 'bfd' -c 'sbfd reflector source-address 2001::20 discriminator 1234'") + + check_func = partial( + show_bfd_check, r1, 'up', type='sbfd initiator' + ) + success, _ = topotest.run_and_expect(check_func, True, count=15, wait=1) + assert success is True, "sbfd not up in 15 seconds" + +# step 2: shutdown if and no shutdown if then check sbfd status +def test_sbfd_updown_interface(): + "Assert that updown interface then check sbfd status." + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.5") + if result is not True: + pytest.skip("Kernel requirements are not met") + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.net['r1'] + r2 = tgen.net['r2'] + + # shutdown interface + r2.cmd("vtysh -c 'config t' -c 'interface r2-eth0' -c 'shutdown'") + + check_func = partial( + show_bfd_check, r1, 'down', type='sbfd initiator' + ) + success, _ = topotest.run_and_expect(check_func, True, count=15, wait=1) + assert success is True, "sbfd not down in 15 seconds after shut" + + # up interface + r2.cmd("vtysh -c 'config t' -c 'interface r2-eth0' -c 'no shutdown'") + check_func = partial( + show_bfd_check, r1, 'up', type='sbfd initiator' + ) + success, _ = topotest.run_and_expect(check_func, True, count=15, wait=1) + assert success is True, "sbfd not up in 15 seconds after no shut" + +# step 3: change transmit-interval and check sbfd status according to the interval time +def test_sbfd_change_transmit_interval(): + "Assert that sbfd status changes align with transmit-interval." + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.5") + if result is not True: + pytest.skip("Kernel requirements are not met") + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.net['r1'] + r2 = tgen.net['r2'] + + r1.cmd("vtysh -c 'config t' -c 'bfd' -c 'peer 2001::20 bfd-mode sbfd-init bfd-name 2-44 local-address 2001::10 remote-discr 1234' -c 'transmit-interval 3000'") + #wait sometime for polling finish + time.sleep(1) + + # shutdown interface + r2.cmd("vtysh -c 'config t' -c 'interface r2-eth0' -c 'shutdown'") + + #wait enough time for timeout + check_func = partial( + show_bfd_check, r1, 'down', type='sbfd initiator' + ) + success, _ = topotest.run_and_expect(check_func, True, count=5, wait=3) + assert success is True, "sbfd not down as expected" + + r2.cmd("vtysh -c 'config t' -c 'interface r2-eth0' -c 'no shutdown'") + check_func = partial( + show_bfd_check, r1, 'up', type='sbfd initiator' + ) + success, _ = topotest.run_and_expect(check_func, True, count=15, wait=1) + assert success is True, "sbfd not up in 15 seconds after no shut" + + r1.cmd("vtysh -c 'config t' -c 'bfd' -c 'no peer 2001::20 bfd-mode sbfd-init bfd-name 2-44 local-address 2001::10 remote-discr 1234'") + success = show_bfd_check(r1, 'up', type='sbfd initiator') + assert success is False, "sbfd not deleted as unexpected" + +# Memory leak test template +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip('Memory leak test/report is disabled') + + tgen.report_memory_leaks() + +if __name__ == '__main__': + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/srv6_static_route_ipv4/__init__.py b/tests/topotests/srv6_static_route_ipv4/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/topotests/srv6_static_route_ipv4/__init__.py diff --git a/tests/topotests/srv6_static_route_ipv4/expected_srv6_route.json b/tests/topotests/srv6_static_route_ipv4/expected_srv6_route.json new file mode 100644 index 0000000000..57f4c4488d --- /dev/null +++ b/tests/topotests/srv6_static_route_ipv4/expected_srv6_route.json @@ -0,0 +1,28 @@ +{ + "192.0.2.0/24": [ + { + "prefix": "192.0.2.0/24", + "prefixLen": 24, + "protocol": "static", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "nexthops": [ + { + "directlyConnected": true, + "active": true, + "weight": 1, + "seg6local": { + "action": "unspec" + }, + "seg6": [ + "fcbb:bbbb:1:2:3:4:5:6", + "fcbb:bbbb:7:8:fe00::" + ] + } + ] + } + ] +} diff --git a/tests/topotests/srv6_static_route_ipv4/r1/frr.conf b/tests/topotests/srv6_static_route_ipv4/r1/frr.conf new file mode 100644 index 0000000000..8ff23ec1b8 --- /dev/null +++ b/tests/topotests/srv6_static_route_ipv4/r1/frr.conf @@ -0,0 +1,7 @@ +hostname r1 +! +log stdout notifications +log commands +! +ipv6 route fcbb:bbbb:1::/48 sr0 +ip route 192.0.2.0/24 sr0 segments fcbb:bbbb:1:2:3:4:5:6/fcbb:bbbb:7:8:fe00:: diff --git a/tests/topotests/srv6_static_route_ipv4/r1/setup.sh b/tests/topotests/srv6_static_route_ipv4/r1/setup.sh new file mode 100644 index 0000000000..4b6cce89f8 --- /dev/null +++ b/tests/topotests/srv6_static_route_ipv4/r1/setup.sh @@ -0,0 +1,2 @@ +ip link add sr0 type dummy +ip link set sr0 up diff --git a/tests/topotests/srv6_static_route_ipv4/test_srv6_route.py b/tests/topotests/srv6_static_route_ipv4/test_srv6_route.py new file mode 100755 index 0000000000..b49a9cec89 --- /dev/null +++ b/tests/topotests/srv6_static_route_ipv4/test_srv6_route.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_srv6_static_route_ipv4.py +# +# Copyright 2025 +# Carmine Scarpitta <cscarpit.@cisco.com> +# + +""" +test_srv6_static_route_ipv4.py: +Test for SRv6 static route on zebra +""" + +import os +import sys +import json +import pytest +import functools + +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, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.staticd] + + +def open_json_file(filename): + try: + with open(filename, "r") as f: + return json.load(f) + except IOError: + assert False, "Could not read file {}".format(filename) + + +def setup_module(mod): + tgen = Topogen({None: "r1"}, mod.__name__) + tgen.start_topology() + for rname, router in tgen.routers().items(): + router.run("/bin/bash {}/{}/setup.sh".format(CWD, rname)) + router.load_frr_config("frr.conf") + tgen.start_router() + + +def teardown_module(): + tgen = get_topogen() + tgen.stop_topology() + + +def test_srv6_static_route(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + router = tgen.gears["r1"] + + def _check_srv6_static_route(router, expected_route_file): + logger.info("checking zebra srv6 static route with multiple segs status") + output = json.loads(router.vtysh_cmd("show ip route static json")) + expected = open_json_file("{}/{}".format(CWD, expected_route_file)) + return topotest.json_cmp(output, expected) + + def check_srv6_static_route(router, expected_file): + func = functools.partial(_check_srv6_static_route, router, expected_file) + _, result = topotest.run_and_expect(func, None, count=15, wait=1) + assert result is None, "Failed" + + # FOR DEVELOPER: + # If you want to stop some specific line and start interactive shell, + # please use tgen.mininet_cli() to start it. + + logger.info("Test for srv6 route configuration") + check_srv6_static_route(router, "expected_srv6_route.json") + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/static_srv6_sids/expected_srv6_sids.json b/tests/topotests/static_srv6_sids/expected_srv6_sids.json index e1a2a16afe..5799d97988 100644 --- a/tests/topotests/static_srv6_sids/expected_srv6_sids.json +++ b/tests/topotests/static_srv6_sids/expected_srv6_sids.json @@ -1,4 +1,39 @@ { + "fcbb:bbbb:1::/48": [ + { + "prefix": "fcbb:bbbb:1::/48", + "prefixLen": 48, + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 9, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "sr0", + "active": true, + "weight": 1, + "seg6local": { + "action": "End" + }, + "seg6localContext": { + + } + } + ] + } + ], "fcbb:bbbb:1:fe10::/64": [ { "prefix": "fcbb:bbbb:1:fe10::/64", diff --git a/tests/topotests/static_srv6_sids/expected_srv6_sids_sid_delete_1.json b/tests/topotests/static_srv6_sids/expected_srv6_sids_sid_delete_1.json new file mode 100644 index 0000000000..e1a2a16afe --- /dev/null +++ b/tests/topotests/static_srv6_sids/expected_srv6_sids_sid_delete_1.json @@ -0,0 +1,107 @@ +{ + "fcbb:bbbb:1:fe10::/64": [ + { + "prefix": "fcbb:bbbb:1:fe10::/64", + "prefixLen": 64, + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 9, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "Vrf10", + "active": true, + "weight": 1, + "seg6local": { + "action": "End.DT4" + }, + "seg6localContext": { + "table": 10 + } + } + ] + } + ], + "fcbb:bbbb:1:fe20::/64": [ + { + "prefix": "fcbb:bbbb:1:fe20::/64", + "prefixLen": 64, + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 9, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "Vrf20", + "active": true, + "weight": 1, + "seg6local": { + "action": "End.DT6" + }, + "seg6localContext": { + "table": 20 + } + } + ] + } + ], + "fcbb:bbbb:1:fe30::/64": [ + { + "prefix": "fcbb:bbbb:1:fe30::/64", + "prefixLen": 64, + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 9, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "Vrf30", + "active": true, + "weight": 1, + "seg6local": { + "action": "End.DT46" + }, + "seg6localContext": { + "table": 30 + } + } + ] + } + ] +}
\ No newline at end of file diff --git a/tests/topotests/static_srv6_sids/expected_srv6_sids_sid_delete_2.json b/tests/topotests/static_srv6_sids/expected_srv6_sids_sid_delete_2.json new file mode 100644 index 0000000000..b5801d354b --- /dev/null +++ b/tests/topotests/static_srv6_sids/expected_srv6_sids_sid_delete_2.json @@ -0,0 +1,72 @@ +{ + "fcbb:bbbb:1:fe10::/64": [ + { + "prefix": "fcbb:bbbb:1:fe10::/64", + "prefixLen": 64, + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 9, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "Vrf10", + "active": true, + "weight": 1, + "seg6local": { + "action": "End.DT4" + }, + "seg6localContext": { + "table": 10 + } + } + ] + } + ], + "fcbb:bbbb:1:fe30::/64": [ + { + "prefix": "fcbb:bbbb:1:fe30::/64", + "prefixLen": 64, + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 9, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "Vrf30", + "active": true, + "weight": 1, + "seg6local": { + "action": "End.DT46" + }, + "seg6localContext": { + "table": 30 + } + } + ] + } + ] +}
\ No newline at end of file diff --git a/tests/topotests/static_srv6_sids/r1/frr.conf b/tests/topotests/static_srv6_sids/r1/frr.conf index 999e35c35b..b4904d9ac2 100644 --- a/tests/topotests/static_srv6_sids/r1/frr.conf +++ b/tests/topotests/static_srv6_sids/r1/frr.conf @@ -8,6 +8,7 @@ segment-routing ! ! static-sids + sid fcbb:bbbb:1::/48 locator MAIN behavior uN sid fcbb:bbbb:1:fe10::/64 locator MAIN behavior uDT4 vrf Vrf10 sid fcbb:bbbb:1:fe20::/64 locator MAIN behavior uDT6 vrf Vrf20 sid fcbb:bbbb:1:fe30::/64 locator MAIN behavior uDT46 vrf Vrf30 diff --git a/tests/topotests/static_srv6_sids/test_static_srv6_sids.py b/tests/topotests/static_srv6_sids/test_static_srv6_sids.py index 453a30af48..cdcc6fd29e 100755 --- a/tests/topotests/static_srv6_sids/test_static_srv6_sids.py +++ b/tests/topotests/static_srv6_sids/test_static_srv6_sids.py @@ -78,6 +78,100 @@ def test_srv6_static_sids(): check_srv6_static_sids(router, "expected_srv6_sids.json") +def test_srv6_static_sids_sid_delete(): + """ + Remove the static SID and verify it gets removed + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + router = tgen.gears["r1"] + + def _check_srv6_static_sids(router, expected_route_file): + logger.info("checking zebra srv6 static sids") + output = json.loads(router.vtysh_cmd("show ipv6 route static json")) + expected = open_json_file("{}/{}".format(CWD, expected_route_file)) + return topotest.json_cmp(output, expected) + + def check_srv6_static_sids(router, expected_file): + func = functools.partial(_check_srv6_static_sids, router, expected_file) + _, result = topotest.run_and_expect(func, None, count=15, wait=1) + assert result is None, "Failed" + + router.vtysh_cmd( + """ + configure terminal + segment-routing + srv6 + static-sids + no sid fcbb:bbbb:1::/48 + """ + ) + + # FOR DEVELOPER: + # If you want to stop some specific line and start interactive shell, + # please use tgen.mininet_cli() to start it. + + logger.info("Test for srv6 sids configuration") + check_srv6_static_sids(router, "expected_srv6_sids_sid_delete_1.json") + + router.vtysh_cmd( + """ + configure terminal + segment-routing + srv6 + static-sids + no sid fcbb:bbbb:1:fe20::/64 locator MAIN behavior uDT6 vrf Vrf20 + """ + ) + + # FOR DEVELOPER: + # If you want to stop some specific line and start interactive shell, + # please use tgen.mininet_cli() to start it. + + logger.info("Test for srv6 sids configuration") + check_srv6_static_sids(router, "expected_srv6_sids_sid_delete_2.json") + + +def test_srv6_static_sids_sid_readd(): + """ + Re-add the static SID and verify the routing table + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + router = tgen.gears["r1"] + + def _check_srv6_static_sids(router, expected_route_file): + logger.info("checking zebra srv6 static sids") + output = json.loads(router.vtysh_cmd("show ipv6 route static json")) + expected = open_json_file("{}/{}".format(CWD, expected_route_file)) + return topotest.json_cmp(output, expected) + + def check_srv6_static_sids(router, expected_file): + func = functools.partial(_check_srv6_static_sids, router, expected_file) + _, result = topotest.run_and_expect(func, None, count=15, wait=1) + assert result is None, "Failed" + + router.vtysh_cmd( + """ + configure terminal + segment-routing + srv6 + static-sids + sid fcbb:bbbb:1::/48 locator MAIN behavior uN + sid fcbb:bbbb:1:fe20::/64 locator MAIN behavior uDT6 vrf Vrf20 + """ + ) + + # FOR DEVELOPER: + # If you want to stop some specific line and start interactive shell, + # please use tgen.mininet_cli() to start it. + + logger.info("Test for srv6 sids configuration") + check_srv6_static_sids(router, "expected_srv6_sids.json") + + if __name__ == "__main__": args = ["-s"] + sys.argv[1:] sys.exit(pytest.main(args)) |
