git: 7064c94a02af - main - tests: add routing tests for switching between same prefixes

From: Alexander V. Chernikov <melifaro_at_FreeBSD.org>
Date: Sun, 07 Aug 2022 19:46:50 UTC
The branch main has been updated by melifaro:

URL: https://cgit.FreeBSD.org/src/commit/?id=7064c94a02af2f8665636a8594557b9e93ad71bf

commit 7064c94a02af2f8665636a8594557b9e93ad71bf
Author:     Alexander V. Chernikov <melifaro@FreeBSD.org>
AuthorDate: 2022-08-06 10:36:12 +0000
Commit:     Alexander V. Chernikov <melifaro@FreeBSD.org>
CommitDate: 2022-08-07 19:45:25 +0000

    tests: add routing tests for switching between same prefixes
    
    Differential Revision: https://reviews.freebsd.org/D36055
    MFC after:      2 weeks
---
 tests/atf_python/sys/net/tools.py        | 15 +++++-
 tests/atf_python/sys/net/vnet.py         |  8 +++-
 tests/sys/net/routing/Makefile           |  1 +
 tests/sys/net/routing/test_routing_l3.py | 81 ++++++++++++++++++++++++++++++++
 4 files changed, 102 insertions(+), 3 deletions(-)

diff --git a/tests/atf_python/sys/net/tools.py b/tests/atf_python/sys/net/tools.py
index c67941b414fc..23bb5f4b4128 100644
--- a/tests/atf_python/sys/net/tools.py
+++ b/tests/atf_python/sys/net/tools.py
@@ -41,7 +41,7 @@ class ToolsHelper(object):
     def get_routes(cls, family: str, fibnum: int = 0):
         family_key = {"inet": "-4", "inet6": "-6"}.get(family)
         out = cls.get_output(
-            "{} {} -rn -F {} --libxo json".format(cls.NETSTAT_PATH, family_key, fibnum)
+            "{} {} -rnW -F {} --libxo json".format(cls.NETSTAT_PATH, family_key, fibnum)
         )
         js = json.loads(out)
         js = js["statistics"]["route-information"]["route-table"]["rt-family"]
@@ -50,6 +50,19 @@ class ToolsHelper(object):
         else:
             return []
 
+    @classmethod
+    def get_nhops(cls, family: str, fibnum: int = 0):
+        family_key = {"inet": "-4", "inet6": "-6"}.get(family)
+        out = cls.get_output(
+            "{} {} -onW -F {} --libxo json".format(cls.NETSTAT_PATH, family_key, fibnum)
+        )
+        js = json.loads(out)
+        js = js["statistics"]["route-nhop-information"]["nhop-table"]["rt-family"]
+        if js:
+            return js[0]["nh-entry"]
+        else:
+            return []
+
     @classmethod
     def get_linklocals(cls):
         ret = {}
diff --git a/tests/atf_python/sys/net/vnet.py b/tests/atf_python/sys/net/vnet.py
index 663f7695a0cc..0d9f969b28d9 100644
--- a/tests/atf_python/sys/net/vnet.py
+++ b/tests/atf_python/sys/net/vnet.py
@@ -101,11 +101,15 @@ class VnetInterface(object):
         addr = ipaddress.ip_interface(_addr)
         if addr.version == 6:
             family = "inet6"
+            cmd = "/sbin/ifconfig {} {} {}".format(self.name, family, addr)
         else:
             family = "inet"
-        cmd = "/sbin/ifconfig {} {} {}".format(self.name, family, addr)
+            if self.addr_map[family]:
+                cmd = "/sbin/ifconfig {} alias {}".format(self.name, addr)
+            else:
+                cmd = "/sbin/ifconfig {} {} {}".format(self.name, family, addr)
         self.run_cmd(cmd)
-        self.addr_map[family][str(addr)] = addr
+        self.addr_map[family][str(addr.ip)] = addr
 
     def delete_addr(self, _addr: str):
         addr = ipaddress.ip_address(_addr)
diff --git a/tests/sys/net/routing/Makefile b/tests/sys/net/routing/Makefile
index d71ba828f958..45034ff211b1 100644
--- a/tests/sys/net/routing/Makefile
+++ b/tests/sys/net/routing/Makefile
@@ -7,6 +7,7 @@ TESTSDIR=       ${TESTSBASE}/sys/net/routing
 
 ATF_TESTS_C +=	test_rtsock_l3
 ATF_TESTS_C +=	test_rtsock_lladdr
+ATF_TESTS_PYTEST +=	test_routing_l3.py
 ATF_TESTS_PYTEST +=	test_rtsock_multipath.py
 
 ${PACKAGE}FILES+=	generic_cleanup.sh
diff --git a/tests/sys/net/routing/test_routing_l3.py b/tests/sys/net/routing/test_routing_l3.py
new file mode 100755
index 000000000000..74017ae0459c
--- /dev/null
+++ b/tests/sys/net/routing/test_routing_l3.py
@@ -0,0 +1,81 @@
+import ipaddress
+
+import pytest
+from atf_python.sys.net.tools import ToolsHelper
+from atf_python.sys.net.vnet import VnetTestTemplate
+
+
+class TestIfOps(VnetTestTemplate):
+    TOPOLOGY = {
+        "vnet1": {"ifaces": ["if1", "if2"]},
+        "if1": {"prefixes4": [], "prefixes6": []},
+        "if2": {"prefixes4": [], "prefixes6": []},
+    }
+
+    @pytest.mark.parametrize("family", ["inet", "inet6"])
+    @pytest.mark.require_user("root")
+    def test_change_prefix_route(self, family):
+        """Tests that prefix route changes to the new one upon addr deletion"""
+        vnet = self.vnet_map["vnet1"]
+        first_iface = vnet.iface_alias_map["if1"]
+        second_iface = vnet.iface_alias_map["if2"]
+        if family == "inet":
+            first_addr = ipaddress.ip_interface("192.0.2.1/24")
+            second_addr = ipaddress.ip_interface("192.0.2.2/24")
+        else:
+            first_addr = ipaddress.ip_interface("2001:db8::1/64")
+            second_addr = ipaddress.ip_interface("2001:db8::2/64")
+
+        first_iface.setup_addr(str(first_addr))
+        second_iface.setup_addr(str(second_addr))
+
+        # At this time prefix should be pointing to the first interface
+        routes = ToolsHelper.get_routes(family)
+        px = [r for r in routes if r["destination"] == str(first_addr.network)][0]
+        assert px["interface-name"] == first_iface.name
+
+        # Now delete address from the first interface and verify switchover
+        first_iface.delete_addr(first_addr.ip)
+
+        routes = ToolsHelper.get_routes(family)
+        px = [r for r in routes if r["destination"] == str(first_addr.network)][0]
+        assert px["interface-name"] == second_iface.name
+
+    @pytest.mark.parametrize(
+        "family",
+        [
+            "inet",
+            pytest.param("inet6", marks=pytest.mark.xfail(reason="currently fails")),
+        ],
+    )
+    @pytest.mark.require_user("root")
+    def test_change_prefix_route_same_iface(self, family):
+        """Tests that prefix route changes to the new ifa upon addr deletion"""
+        vnet = self.vnet_map["vnet1"]
+        first_iface = vnet.iface_alias_map["if1"]
+
+        if family == "inet":
+            first_addr = ipaddress.ip_interface("192.0.2.1/24")
+            second_addr = ipaddress.ip_interface("192.0.2.2/24")
+        else:
+            first_addr = ipaddress.ip_interface("2001:db8::1/64")
+            second_addr = ipaddress.ip_interface("2001:db8::2/64")
+
+        first_iface.setup_addr(str(first_addr))
+        first_iface.setup_addr(str(second_addr))
+
+        # At this time prefix should be pointing to the first interface
+        routes = ToolsHelper.get_routes(family)
+        px = [r for r in routes if r["destination"] == str(first_addr.network)][0]
+        assert px["interface-name"] == first_iface.name
+
+        # Now delete address from the first interface and verify switchover
+        first_iface.delete_addr(str(first_addr.ip))
+
+        routes = ToolsHelper.get_routes(family)
+        px = [r for r in routes if r["destination"] == str(first_addr.network)][0]
+        nhop_kidx = px["nhop"]
+        assert px["interface-name"] == first_iface.name
+        nhops = ToolsHelper.get_nhops(family)
+        nh = [nh for nh in nhops if nh["index"] == nhop_kidx][0]
+        assert nh["ifa"] == str(second_addr.ip)