--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+# SPDX-License-Identifier: ISC
+#
+# Copyright (c) 2024 NFWare Inc.
+#
+# noqa: E501
+#
+"""
+Test static route functionality
+"""
+
+import ipaddress
+
+import pytest
+from lib.topogen import Topogen
+from lib.common_config import retry
+
+pytestmark = [pytest.mark.staticd, pytest.mark.mgmtd]
+
+
+@pytest.fixture(scope="module")
+def tgen(request):
+ "Setup/Teardown the environment and provide tgen argument to tests"
+
+ topodef = {"s1": ("r1",), "s2": ("r1",)}
+
+ tgen = Topogen(topodef, request.module.__name__)
+ tgen.start_topology()
+
+ router_list = tgen.routers()
+ for rname, router in router_list.items():
+ # Setup VRF red
+ router.net.add_l3vrf("red", 10)
+ router.net.attach_iface_to_l3vrf(rname + "-eth0", "red")
+ # Setup VRF blue
+ router.net.add_l3vrf("blue", 20)
+ router.net.attach_iface_to_l3vrf(rname + "-eth1", "blue")
+ # Load configuration
+ router.load_frr_config("frr.conf")
+
+ tgen.start_router()
+ yield tgen
+ tgen.stop_topology()
+
+
+@retry(retry_timeout=1, initial_wait=0.1)
+def check_kernel(r1, prefix, nexthops, vrf, expected_p=True, expected_nh=True):
+ vrfstr = f" vrf {vrf}" if vrf else ""
+
+ net = ipaddress.ip_network(prefix)
+ if net.version == 6:
+ kernel = r1.run(f"ip -6 route show{vrfstr} {prefix}")
+ else:
+ kernel = r1.run(f"ip -4 route show{vrfstr} {prefix}")
+
+ if expected_p:
+ assert prefix in kernel, f"Failed to find \n'{prefix}'\n in \n'{kernel:.1920}'"
+ else:
+ assert (
+ prefix not in kernel
+ ), f"Failed found \n'{prefix}'\n in \n'{kernel:.1920}'"
+
+ if not expected_p:
+ return
+
+ for nh in nexthops:
+ if expected_nh:
+ assert f"{nh}" in kernel, f"Failed to find \n'{nh}'\n in \n'{kernel:.1920}'"
+ else:
+ assert (
+ f"{nh}" not in kernel
+ ), f"Failed found \n'{nh}'\n in \n'{kernel:.1920}'"
+
+
+def test_static_vrf(tgen):
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r1 = tgen.gears["r1"]
+
+ # Check initial configuration
+ check_kernel(r1, "198.51.100.1", ["192.0.2.2", "192.0.2.130"], None)
+ check_kernel(r1, "198.51.100.2", ["r1-eth0", "r1-eth1"], None)
+ check_kernel(r1, "203.0.113.1", ["192.0.2.130"], "red")
+ check_kernel(r1, "203.0.113.2", ["r1-eth1"], "red")
+ check_kernel(r1, "203.0.113.129", ["192.0.2.2"], "blue")
+ check_kernel(r1, "203.0.113.130", ["r1-eth0"], "blue")
+
+ # Delete VRF red
+ r1.net.del_iface("red")
+
+ # Check that "red" nexthops are removed, "blue" nexthops are still there
+ check_kernel(r1, "198.51.100.1", ["192.0.2.2"], None, expected_nh=False)
+ check_kernel(r1, "198.51.100.1", ["192.0.2.130"], None)
+ check_kernel(r1, "198.51.100.2", ["r1-eth0"], None, expected_nh=False)
+ check_kernel(r1, "198.51.100.2", ["r1-eth1"], None)
+ check_kernel(r1, "203.0.113.129", ["192.0.2.2"], "blue", expected_p=False)
+ check_kernel(r1, "203.0.113.130", ["r1-eth0"], "blue", expected_p=False)
+
+ # Delete VRF blue
+ r1.net.del_iface("blue")
+
+ # Check that "blue" nexthops are removed
+ check_kernel(r1, "198.51.100.1", ["192.0.2.130"], None, expected_p=False)
+ check_kernel(r1, "198.51.100.2", ["r1-eth1"], None, expected_p=False)
+
+ # Add VRF red back, attach "eth0" to it
+ r1.net.add_l3vrf("red", 10)
+ r1.net.attach_iface_to_l3vrf("r1-eth0", "red")
+
+ # Check that "red" nexthops are restored
+ check_kernel(r1, "198.51.100.1", ["192.0.2.2"], None)
+ check_kernel(r1, "198.51.100.2", ["r1-eth0"], None)
+
+ # Add VRF blue back, attach "eth1" to it
+ r1.net.add_l3vrf("blue", 20)
+ r1.net.attach_iface_to_l3vrf("r1-eth1", "blue")
+
+ # Check that everything is restored
+ check_kernel(r1, "198.51.100.1", ["192.0.2.2", "192.0.2.130"], None)
+ check_kernel(r1, "198.51.100.2", ["r1-eth0", "r1-eth1"], None)
+ check_kernel(r1, "203.0.113.1", ["192.0.2.130"], "red")
+ check_kernel(r1, "203.0.113.2", ["r1-eth1"], "red")
+ check_kernel(r1, "203.0.113.129", ["192.0.2.2"], "blue")
+ check_kernel(r1, "203.0.113.130", ["r1-eth0"], "blue")