From 49567328b9f0bd57becaf607f3c235c0b655416a Mon Sep 17 00:00:00 2001 From: Louis Scalbert Date: Wed, 19 Mar 2025 15:51:30 +0100 Subject: [PATCH] tests: add bfd_static_vrf Add bfd_static_vrf to test BFD tracking of static routes in VRF. Signed-off-by: Louis Scalbert --- tests/topotests/bfd_static_vrf/__init__.py | 0 tests/topotests/bfd_static_vrf/r1/frr.conf | 23 +++ .../bfd_static_vrf/r1/show_bfd_peers.json | 90 +++++++++ .../r1/show_bfd_peers_step1.json | 90 +++++++++ .../bfd_static_vrf/r1/show_ip_route.json | 55 ++++++ .../r1/show_ip_route_step1.json | 31 +++ .../bfd_static_vrf/r1/show_ipv6_route.json | 55 ++++++ .../r1/show_ipv6_route_step1.json | 31 +++ tests/topotests/bfd_static_vrf/r2/frr.conf | 10 + tests/topotests/bfd_static_vrf/r3/frr.conf | 10 + .../bfd_static_vrf/test_bfd_static_vrf.py | 185 ++++++++++++++++++ 11 files changed, 580 insertions(+) create mode 100644 tests/topotests/bfd_static_vrf/__init__.py create mode 100644 tests/topotests/bfd_static_vrf/r1/frr.conf create mode 100644 tests/topotests/bfd_static_vrf/r1/show_bfd_peers.json create mode 100644 tests/topotests/bfd_static_vrf/r1/show_bfd_peers_step1.json create mode 100644 tests/topotests/bfd_static_vrf/r1/show_ip_route.json create mode 100644 tests/topotests/bfd_static_vrf/r1/show_ip_route_step1.json create mode 100644 tests/topotests/bfd_static_vrf/r1/show_ipv6_route.json create mode 100644 tests/topotests/bfd_static_vrf/r1/show_ipv6_route_step1.json create mode 100644 tests/topotests/bfd_static_vrf/r2/frr.conf create mode 100644 tests/topotests/bfd_static_vrf/r3/frr.conf create mode 100755 tests/topotests/bfd_static_vrf/test_bfd_static_vrf.py diff --git a/tests/topotests/bfd_static_vrf/__init__.py b/tests/topotests/bfd_static_vrf/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/topotests/bfd_static_vrf/r1/frr.conf b/tests/topotests/bfd_static_vrf/r1/frr.conf new file mode 100644 index 0000000000..fed2c1a2f7 --- /dev/null +++ b/tests/topotests/bfd_static_vrf/r1/frr.conf @@ -0,0 +1,23 @@ +interface r1-eth0 vrf cust1 + ip address 192.168.0.1/24 + ipv6 address fc00:1::0:1/96 +! +interface r1-eth1 vrf cust1 + ip address 192.168.5.1/24 + ipv6 address fc00:2::0:1/96 +! +ip route 0.0.0.0/0 192.168.5.4 bfd vrf cust1 +ipv6 route 0::0/0 fc00:2::4 bfd vrf cust1 +ip route 0.0.0.0/0 192.168.0.2 10 bfd vrf cust1 +ipv6 route 0::0/0 fc00:1::2 10 bfd vrf cust1 +! +bfd + peer 192.168.0.2 vrf cust1 + ! + peer 192.168.5.4 vrf cust1 + ! + peer fc00:1::2 vrf cust1 + ! + peer fc00:2::4 vrf cust1 + ! +! diff --git a/tests/topotests/bfd_static_vrf/r1/show_bfd_peers.json b/tests/topotests/bfd_static_vrf/r1/show_bfd_peers.json new file mode 100644 index 0000000000..77b9ac9114 --- /dev/null +++ b/tests/topotests/bfd_static_vrf/r1/show_bfd_peers.json @@ -0,0 +1,90 @@ +[ + { + "multihop": false, + "peer": "192.168.5.4", + "vrf": "cust1", + "passive-mode": false, + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok", + "type": "configured", + "receive-interval": 300, + "transmit-interval": 300, + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "detect-multiplier": 3, + "remote-receive-interval": 300, + "remote-transmit-interval": 300, + "remote-echo-receive-interval": 50, + "remote-detect-multiplier": 3, + "rtt-min": 0, + "rtt-avg": 0, + "rtt-max": 0 + }, + { + "multihop": false, + "peer": "fc00:2::4", + "vrf": "cust1", + "passive-mode": false, + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok", + "type": "configured", + "receive-interval": 300, + "transmit-interval": 300, + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "detect-multiplier": 3, + "remote-receive-interval": 300, + "remote-transmit-interval": 300, + "remote-echo-receive-interval": 50, + "remote-detect-multiplier": 3, + "rtt-min": 0, + "rtt-avg": 0, + "rtt-max": 0 + }, + { + "multihop": false, + "peer": "192.168.0.2", + "vrf": "cust1", + "passive-mode": false, + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok", + "type": "configured", + "receive-interval": 300, + "transmit-interval": 300, + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "detect-multiplier": 3, + "remote-receive-interval": 300, + "remote-transmit-interval": 300, + "remote-echo-receive-interval": 50, + "remote-detect-multiplier": 3, + "rtt-min": 0, + "rtt-avg": 0, + "rtt-max": 0 + }, + { + "multihop": false, + "peer": "fc00:1::2", + "vrf": "cust1", + "passive-mode": false, + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok", + "type": "configured", + "receive-interval": 300, + "transmit-interval": 300, + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "detect-multiplier": 3, + "remote-receive-interval": 300, + "remote-transmit-interval": 300, + "remote-echo-receive-interval": 50, + "remote-detect-multiplier": 3, + "rtt-min": 0, + "rtt-avg": 0, + "rtt-max": 0 + } +] diff --git a/tests/topotests/bfd_static_vrf/r1/show_bfd_peers_step1.json b/tests/topotests/bfd_static_vrf/r1/show_bfd_peers_step1.json new file mode 100644 index 0000000000..0db00a89c5 --- /dev/null +++ b/tests/topotests/bfd_static_vrf/r1/show_bfd_peers_step1.json @@ -0,0 +1,90 @@ +[ + { + "multihop": false, + "peer": "fc00:2::4", + "vrf": "cust1", + "passive-mode": false, + "status": "down", + "diagnostic": "control detection time expired", + "remote-diagnostic": "ok", + "type": "configured", + "receive-interval": 300, + "transmit-interval": 300, + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "detect-multiplier": 3, + "remote-receive-interval": 300, + "remote-transmit-interval": 300, + "remote-echo-receive-interval": 50, + "remote-detect-multiplier": 3, + "rtt-min": 0, + "rtt-avg": 0, + "rtt-max": 0 + }, + { + "multihop": false, + "peer": "192.168.0.2", + "vrf": "cust1", + "passive-mode": false, + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok", + "type": "configured", + "receive-interval": 300, + "transmit-interval": 300, + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "detect-multiplier": 3, + "remote-receive-interval": 300, + "remote-transmit-interval": 300, + "remote-echo-receive-interval": 50, + "remote-detect-multiplier": 3, + "rtt-min": 0, + "rtt-avg": 0, + "rtt-max": 0 + }, + { + "multihop": false, + "peer": "192.168.5.4", + "vrf": "cust1", + "passive-mode": false, + "status": "down", + "diagnostic": "control detection time expired", + "remote-diagnostic": "ok", + "type": "configured", + "receive-interval": 300, + "transmit-interval": 300, + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "detect-multiplier": 3, + "remote-receive-interval": 300, + "remote-transmit-interval": 300, + "remote-echo-receive-interval": 50, + "remote-detect-multiplier": 3, + "rtt-min": 0, + "rtt-avg": 0, + "rtt-max": 0 + }, + { + "multihop": false, + "peer": "fc00:1::2", + "vrf": "cust1", + "passive-mode": false, + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok", + "type": "configured", + "receive-interval": 300, + "transmit-interval": 300, + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "detect-multiplier": 3, + "remote-receive-interval": 300, + "remote-transmit-interval": 300, + "remote-echo-receive-interval": 50, + "remote-detect-multiplier": 3, + "rtt-min": 0, + "rtt-avg": 0, + "rtt-max": 0 + } +] diff --git a/tests/topotests/bfd_static_vrf/r1/show_ip_route.json b/tests/topotests/bfd_static_vrf/r1/show_ip_route.json new file mode 100644 index 0000000000..6d537b95ba --- /dev/null +++ b/tests/topotests/bfd_static_vrf/r1/show_ip_route.json @@ -0,0 +1,55 @@ +{ + "0.0.0.0/0": [ + { + "prefix": "0.0.0.0/0", + "prefixLen": 0, + "protocol": "static", + "vrfName": "cust1", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "192.168.5.4", + "afi": "ipv4", + "interfaceName": "r1-eth1", + "active": true, + "weight": 1 + } + ] + }, + { + "prefix": "0.0.0.0/0", + "prefixLen": 0, + "protocol": "static", + "vrfName": "cust1", + "selected": null, + "destSelected": null, + "distance": 10, + "metric": 0, + "table": 10, + "internalStatus": 0, + "internalFlags": 65, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "ip": "192.168.0.2", + "afi": "ipv4", + "interfaceName": "r1-eth0", + "active": true, + "weight": 1 + } + ] + } + ] +} diff --git a/tests/topotests/bfd_static_vrf/r1/show_ip_route_step1.json b/tests/topotests/bfd_static_vrf/r1/show_ip_route_step1.json new file mode 100644 index 0000000000..858c308359 --- /dev/null +++ b/tests/topotests/bfd_static_vrf/r1/show_ip_route_step1.json @@ -0,0 +1,31 @@ +{ + "0.0.0.0/0": [ + { + "prefix": "0.0.0.0/0", + "prefixLen": 0, + "protocol": "static", + "vrfName": "cust1", + "selected": true, + "destSelected": true, + "distance": 10, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "192.168.0.2", + "afi": "ipv4", + "interfaceName": "r1-eth0", + "active": true, + "weight": 1 + } + ] + } + ] +} diff --git a/tests/topotests/bfd_static_vrf/r1/show_ipv6_route.json b/tests/topotests/bfd_static_vrf/r1/show_ipv6_route.json new file mode 100644 index 0000000000..b002764c9f --- /dev/null +++ b/tests/topotests/bfd_static_vrf/r1/show_ipv6_route.json @@ -0,0 +1,55 @@ +{ + "::/0": [ + { + "prefix": "::/0", + "prefixLen": 0, + "protocol": "static", + "vrfName": "cust1", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "fc00:2::4", + "afi": "ipv6", + "interfaceName": "r1-eth1", + "active": true, + "weight": 1 + } + ] + }, + { + "prefix": "::/0", + "prefixLen": 0, + "protocol": "static", + "vrfName": "cust1", + "selected": null, + "destSelected": null, + "distance": 10, + "metric": 0, + "table": 10, + "internalStatus": 0, + "internalFlags": 65, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "ip": "fc00:1::2", + "afi": "ipv6", + "interfaceName": "r1-eth0", + "active": true, + "weight": 1 + } + ] + } + ] +} diff --git a/tests/topotests/bfd_static_vrf/r1/show_ipv6_route_step1.json b/tests/topotests/bfd_static_vrf/r1/show_ipv6_route_step1.json new file mode 100644 index 0000000000..f9b931efd8 --- /dev/null +++ b/tests/topotests/bfd_static_vrf/r1/show_ipv6_route_step1.json @@ -0,0 +1,31 @@ +{ + "::/0": [ + { + "prefix": "::/0", + "prefixLen": 0, + "protocol": "static", + "vrfName": "cust1", + "selected": true, + "destSelected": true, + "distance": 10, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "fc00:1::2", + "afi": "ipv6", + "interfaceName": "r1-eth0", + "active": true, + "weight": 1 + } + ] + } + ] +} diff --git a/tests/topotests/bfd_static_vrf/r2/frr.conf b/tests/topotests/bfd_static_vrf/r2/frr.conf new file mode 100644 index 0000000000..984e3e1e46 --- /dev/null +++ b/tests/topotests/bfd_static_vrf/r2/frr.conf @@ -0,0 +1,10 @@ +interface r2-eth0 vrf cust1 + ip address 192.168.0.2/24 + ipv6 address fc00:1::0:2/96 +! +bfd + peer 192.168.0.1 vrf cust1 + ! + peer fc00:1::1 vrf cust1 + ! +! diff --git a/tests/topotests/bfd_static_vrf/r3/frr.conf b/tests/topotests/bfd_static_vrf/r3/frr.conf new file mode 100644 index 0000000000..0b8140983e --- /dev/null +++ b/tests/topotests/bfd_static_vrf/r3/frr.conf @@ -0,0 +1,10 @@ +interface r3-eth0 vrf cust1 + ip address 192.168.5.4/24 + ipv6 address fc00:2::0:4/96 +! +bfd + peer 192.168.5.1 vrf cust1 + ! + peer fc00:2::1 vrf cust1 + ! +! diff --git a/tests/topotests/bfd_static_vrf/test_bfd_static_vrf.py b/tests/topotests/bfd_static_vrf/test_bfd_static_vrf.py new file mode 100755 index 0000000000..deb618c399 --- /dev/null +++ b/tests/topotests/bfd_static_vrf/test_bfd_static_vrf.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bfd_static_vrf.py +# Part of NetDEF Topology Tests +# +# Copyright 2025 6WIND S.A. +# + +""" +test_bfd_static_vrf.py: Test the FRR static routes with BFD tracking. +""" + +import os +import sys +import json +import platform +import functools +import pytest + +pytestmark = [pytest.mark.staticd, pytest.mark.bfdd] + +# 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 + + +def build_topo(tgen): + "Build function" + + # Create 3 routers + 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): + "Sets up the pytest environment" + + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for rname, router in router_list.items(): + tgen.net[rname].cmd( + f""" +ip link add cust1 type vrf table 10 +ip link set dev cust1 up +ip link set dev {rname}-eth0 master cust1 +sysctl net.ipv6.conf.{rname}-eth0.keep_addr_on_down=1 +""" + ) + + tgen.net["r1"].cmd( + """ +ip link set dev r1-eth1 master cust1 +sysctl net.ipv6.conf.r1-eth1.keep_addr_on_down=1 +""" + ) + + for _, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config( + os.path.join(CWD, "{}/frr.conf".format(rname)), + [ + (TopoRouter.RD_ZEBRA, None), + (TopoRouter.RD_MGMTD, None), + (TopoRouter.RD_BFD, None), + (TopoRouter.RD_STATIC, None), + ], + ) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + + tgen = get_topogen() + tgen.stop_topology() + + +def check_bfd_state(step=None): + tgen = get_topogen() + + r1 = tgen.gears["r1"] + + step_suffix = f"_step{step}" if step else "" + + logger.info("Check BFD entries") + reffile = os.path.join(CWD, f"r1/show_bfd_peers{step_suffix}.json") + expected = json.loads(open(reffile).read()) + cmd = "show bfd peers json" + test_func = functools.partial(topotest.router_json_cmp, r1, cmd, expected) + _, res = topotest.run_and_expect(test_func, None, count=30, wait=1) + assertmsg = f"BFD did not converge. Error on r1 {cmd}" + assert res is None, assertmsg + + logger.info("Check IPv4 default route") + reffile = os.path.join(CWD, f"r1/show_ip_route{step_suffix}.json") + expected = json.loads(open(reffile).read()) + cmd = "show ip route vrf cust1 0.0.0.0/0 json" + test_func = functools.partial(topotest.router_json_cmp, r1, cmd, expected) + _, res = topotest.run_and_expect(test_func, None, count=30, wait=1) + assertmsg = f"BFD did not converge. Error on r1 {cmd}" + assert res is None, assertmsg + + logger.info("Check IPv6 default route") + reffile = os.path.join(CWD, f"r1/show_ipv6_route{step_suffix}.json") + expected = json.loads(open(reffile).read()) + cmd = "show ipv6 route vrf cust1 ::/0 json" + test_func = functools.partial(topotest.router_json_cmp, r1, cmd, expected) + _, res = topotest.run_and_expect(test_func, None, count=30, wait=1) + assertmsg = f"BFD did not converge. Error on r1 {cmd}" + assert res is None, assertmsg + + +def test_bfd_convergence(): + "Assert that the BFD peers can find themselves." + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + check_bfd_state() + + +def test_bfd_static_vrf_step1(): + """ + Assert that BFD notices the link down after simulating network + failure. + """ + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Set r3-eth0 down") + tgen.gears["r3"].link_enable("r3-eth0", enabled=False) + + check_bfd_state(step=1) + + +def test_bfd_static_vrf_step2(): + """ + Assert that BFD goes back to the nominal stater after links are back up. + """ + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Set r3-eth0 up") + tgen.gears["r3"].link_enable("r3-eth0", enabled=True) + + check_bfd_state() + + +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)) -- 2.39.5