18161b823SAlexander V. Chernikovimport pytest 28161b823SAlexander V. Chernikovfrom atf_python.utils import BaseTest 38161b823SAlexander V. Chernikovfrom atf_python.sys.net.tools import ToolsHelper 48161b823SAlexander V. Chernikovfrom atf_python.sys.net.vnet import SingleVnetTestTemplate 58161b823SAlexander V. Chernikovfrom atf_python.sys.net.vnet import VnetTestTemplate 68161b823SAlexander V. Chernikovfrom atf_python.sys.net.vnet import VnetInstance 78161b823SAlexander V. Chernikov 88161b823SAlexander V. Chernikovimport errno 98161b823SAlexander V. Chernikovimport socket 108161b823SAlexander V. Chernikovimport subprocess 118161b823SAlexander V. Chernikovimport json 128161b823SAlexander V. Chernikov 138161b823SAlexander V. Chernikovfrom typing import List 148161b823SAlexander V. Chernikov 158161b823SAlexander V. Chernikov 168161b823SAlexander V. Chernikov# Test classes should be inherited 178161b823SAlexander V. Chernikov# from the BaseTest 188161b823SAlexander V. Chernikov 198161b823SAlexander V. Chernikov 208161b823SAlexander V. Chernikovclass TestExampleSimplest(BaseTest): 218161b823SAlexander V. Chernikov @pytest.mark.skip(reason="comment me to run the test") 228161b823SAlexander V. Chernikov def test_one(self): 238161b823SAlexander V. Chernikov assert ToolsHelper.get_output("uname -s").strip() == "FreeBSD" 248161b823SAlexander V. Chernikov 258161b823SAlexander V. Chernikov 268161b823SAlexander V. Chernikovclass TestExampleSimple(BaseTest): 278161b823SAlexander V. Chernikov # List of required kernel modules (kldstat -v) 288161b823SAlexander V. Chernikov # that needs to be present for the tests to run 298161b823SAlexander V. Chernikov REQUIRED_MODULES = ["null"] 308161b823SAlexander V. Chernikov 318161b823SAlexander V. Chernikov @pytest.mark.skip(reason="comment me to run the test") 328161b823SAlexander V. Chernikov def test_one(self): 338161b823SAlexander V. Chernikov """Optional test description 348161b823SAlexander V. Chernikov This and the following lines are not propagated 358161b823SAlexander V. Chernikov to the ATF test description. 368161b823SAlexander V. Chernikov """ 378161b823SAlexander V. Chernikov pass 388161b823SAlexander V. Chernikov 398161b823SAlexander V. Chernikov @pytest.mark.skip(reason="comment me to run the test") 408161b823SAlexander V. Chernikov # List of all requirements supported by an atf runner 418161b823SAlexander V. Chernikov # See atf-test-case(4) for the detailed description 428161b823SAlexander V. Chernikov @pytest.mark.require_user("root") 438161b823SAlexander V. Chernikov @pytest.mark.require_arch(["amd64", "i386"]) 448161b823SAlexander V. Chernikov @pytest.mark.require_files(["/path/file1", "/path/file2"]) 458161b823SAlexander V. Chernikov @pytest.mark.require_machine(["amd64", "i386"]) 468161b823SAlexander V. Chernikov @pytest.mark.require_memory("200M") 478161b823SAlexander V. Chernikov @pytest.mark.require_progs(["prog1", "prog2"]) 488161b823SAlexander V. Chernikov @pytest.mark.timeout(300) 498161b823SAlexander V. Chernikov def test_two(self): 508161b823SAlexander V. Chernikov pass 518161b823SAlexander V. Chernikov 528161b823SAlexander V. Chernikov @pytest.mark.skip(reason="comment me to run the test") 53*864ea9abSAlexander V. Chernikov def test_get_properties(self, request): 54*864ea9abSAlexander V. Chernikov """Shows fetching of test src dir and ATF-set variables""" 55*864ea9abSAlexander V. Chernikov print() 56*864ea9abSAlexander V. Chernikov print("SRC_DIR={}".format(request.fspath.dirname)) 57*864ea9abSAlexander V. Chernikov print("ATF VARS:") 58*864ea9abSAlexander V. Chernikov for k, v in self.atf_vars.items(): 59*864ea9abSAlexander V. Chernikov print(" {}: {}".format(k, v)) 60*864ea9abSAlexander V. Chernikov print() 61*864ea9abSAlexander V. Chernikov 62*864ea9abSAlexander V. Chernikov @pytest.mark.skip(reason="comment me to run the test") 638161b823SAlexander V. Chernikov @pytest.mark.require_user("unprivileged") 648161b823SAlexander V. Chernikov def test_syscall_failure(self): 658161b823SAlexander V. Chernikov s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) 668161b823SAlexander V. Chernikov with pytest.raises(OSError) as exc_info: 678161b823SAlexander V. Chernikov s.bind(("::1", 42)) 688161b823SAlexander V. Chernikov assert exc_info.value.errno == errno.EACCES 698161b823SAlexander V. Chernikov 708161b823SAlexander V. Chernikov @pytest.mark.skip(reason="comment me to run the test") 718161b823SAlexander V. Chernikov @pytest.mark.parametrize( 728161b823SAlexander V. Chernikov "family_tuple", 738161b823SAlexander V. Chernikov [ 748161b823SAlexander V. Chernikov pytest.param([socket.AF_INET, None], id="AF_INET"), 758161b823SAlexander V. Chernikov pytest.param([socket.AF_INET6, None], id="AF_INET6"), 768161b823SAlexander V. Chernikov pytest.param([39, errno.EAFNOSUPPORT], id="FAMILY_39"), 778161b823SAlexander V. Chernikov ], 788161b823SAlexander V. Chernikov ) 798161b823SAlexander V. Chernikov def test_parametrize(self, family_tuple): 808161b823SAlexander V. Chernikov family, error = family_tuple 818161b823SAlexander V. Chernikov try: 828161b823SAlexander V. Chernikov s = socket.socket(family, socket.SOCK_STREAM) 838161b823SAlexander V. Chernikov s.close() 848161b823SAlexander V. Chernikov except OSError as e: 858161b823SAlexander V. Chernikov if error is None or error != e.errno: 868161b823SAlexander V. Chernikov raise 878161b823SAlexander V. Chernikov 888161b823SAlexander V. Chernikov # @pytest.mark.skip(reason="comment me to run the test") 898161b823SAlexander V. Chernikov def test_with_cleanup(self): 908161b823SAlexander V. Chernikov print("TEST BODY") 918161b823SAlexander V. Chernikov 928161b823SAlexander V. Chernikov def cleanup_test_with_cleanup(self, test_id): 938161b823SAlexander V. Chernikov print("CLEANUP HANDLER") 948161b823SAlexander V. Chernikov 958161b823SAlexander V. Chernikov 968161b823SAlexander V. Chernikovclass TestVnetSimple(SingleVnetTestTemplate): 978161b823SAlexander V. Chernikov """ 988161b823SAlexander V. Chernikov SingleVnetTestTemplate creates a topology with a single 998161b823SAlexander V. Chernikov vnet and a single epair between this vnet and the host system. 1008161b823SAlexander V. Chernikov Additionally, lo0 interface is created inside the vnet. 1018161b823SAlexander V. Chernikov 1028161b823SAlexander V. Chernikov Both vnets and interfaces are aliased as vnetX and ifY. 1038161b823SAlexander V. Chernikov They can be accessed via maps: 1048161b823SAlexander V. Chernikov vnet: VnetInstance = self.vnet_map["vnet1"] 1058161b823SAlexander V. Chernikov iface: VnetInterface = vnet.iface_alias_map["if1"] 1068161b823SAlexander V. Chernikov 1078161b823SAlexander V. Chernikov All prefixes from IPV4_PREFIXES and IPV6_PREFIXES are 1088161b823SAlexander V. Chernikov assigned to the single epair interface inside the jail. 1098161b823SAlexander V. Chernikov 1108161b823SAlexander V. Chernikov One can rely on the fact that there are no IPv6 prefixes 1118161b823SAlexander V. Chernikov in the tentative state when the test method is called. 1128161b823SAlexander V. Chernikov """ 1138161b823SAlexander V. Chernikov 1148161b823SAlexander V. Chernikov IPV6_PREFIXES: List[str] = ["2001:db8::1/64"] 1158161b823SAlexander V. Chernikov IPV4_PREFIXES: List[str] = ["192.0.2.1/24"] 1168161b823SAlexander V. Chernikov 1178161b823SAlexander V. Chernikov def setup_method(self, method): 1188161b823SAlexander V. Chernikov """ 1198161b823SAlexander V. Chernikov Optional pre-setup for all of the tests inside the class 1208161b823SAlexander V. Chernikov """ 1218161b823SAlexander V. Chernikov # Code to run before vnet setup 1228161b823SAlexander V. Chernikov # 1238161b823SAlexander V. Chernikov super().setup_method(method) 1248161b823SAlexander V. Chernikov # 1258161b823SAlexander V. Chernikov # Code to run after vnet setup 1268161b823SAlexander V. Chernikov # Executed inside the vnet 1278161b823SAlexander V. Chernikov 1288161b823SAlexander V. Chernikov @pytest.mark.skip(reason="comment me to run the test") 1298161b823SAlexander V. Chernikov @pytest.mark.require_user("root") 1308161b823SAlexander V. Chernikov def test_ping(self): 1318161b823SAlexander V. Chernikov assert subprocess.run("ping -c1 192.0.2.1".split()).returncode == 0 1328161b823SAlexander V. Chernikov assert subprocess.run("ping -c1 2001:db8::1".split()).returncode == 0 1338161b823SAlexander V. Chernikov 1348161b823SAlexander V. Chernikov @pytest.mark.skip(reason="comment me to run the test") 1358161b823SAlexander V. Chernikov def test_topology(self): 1368161b823SAlexander V. Chernikov vnet = self.vnet_map["vnet1"] 1378161b823SAlexander V. Chernikov iface = vnet.iface_alias_map["if1"] 1388161b823SAlexander V. Chernikov print("Iface {} inside vnet {}".format(iface.name, vnet.name)) 1398161b823SAlexander V. Chernikov 1408161b823SAlexander V. Chernikov 1418161b823SAlexander V. Chernikovclass TestVnetDual1(VnetTestTemplate): 1428161b823SAlexander V. Chernikov """ 1438161b823SAlexander V. Chernikov VnetTestTemplate creates topology described in the self.TOPOLOGY 1448161b823SAlexander V. Chernikov 1458161b823SAlexander V. Chernikov Each vnet (except vnet1) can have a handler function, named 1468161b823SAlexander V. Chernikov vnetX_handler. This function will be run in a separate process 1478161b823SAlexander V. Chernikov inside vnetX jail. The framework automatically creates a pipe 1488161b823SAlexander V. Chernikov to allow communication between the main test and the vnet handler. 1498161b823SAlexander V. Chernikov 1508161b823SAlexander V. Chernikov This topology contains 2 VNETs connected with 2 epairs: 1518161b823SAlexander V. Chernikov 1528161b823SAlexander V. Chernikov [ VNET1 ] [ VNET2 ] 1538161b823SAlexander V. Chernikov if1(epair) 2001:db8:a::1/64 <-> 2001:db8:a::2/64 if1(epair) 1548161b823SAlexander V. Chernikov if2(epair) 2001:db8:b::1/64 <-> 2001:db8:b::2/64 if2(epair) 1558161b823SAlexander V. Chernikov lo0 lo0 1568161b823SAlexander V. Chernikov 1578161b823SAlexander V. Chernikov """ 1588161b823SAlexander V. Chernikov 1598161b823SAlexander V. Chernikov TOPOLOGY = { 1608161b823SAlexander V. Chernikov "vnet1": {"ifaces": ["if1", "if2"]}, 1618161b823SAlexander V. Chernikov "vnet2": {"ifaces": ["if1", "if2"]}, 1628161b823SAlexander V. Chernikov "if1": {"prefixes6": [("2001:db8:a::1/64", "2001:db8:a::2/64")]}, 1638161b823SAlexander V. Chernikov "if2": {"prefixes6": [("2001:db8:b::1/64", "2001:db8:b::2/64")]}, 1648161b823SAlexander V. Chernikov } 1658161b823SAlexander V. Chernikov 1668161b823SAlexander V. Chernikov def _get_iface_stat(self, os_ifname: str): 1678161b823SAlexander V. Chernikov out = ToolsHelper.get_output( 1688161b823SAlexander V. Chernikov "{} -I {} --libxo json".format(ToolsHelper.NETSTAT_PATH, os_ifname) 1698161b823SAlexander V. Chernikov ) 1708161b823SAlexander V. Chernikov js = json.loads(out) 1718161b823SAlexander V. Chernikov return js["statistics"]["interface"][0] 1728161b823SAlexander V. Chernikov 1738161b823SAlexander V. Chernikov def vnet2_handler(self, vnet: VnetInstance): 1748161b823SAlexander V. Chernikov """ 1758161b823SAlexander V. Chernikov Test handler that runs in the vnet2 as a separate process. 1768161b823SAlexander V. Chernikov 1778161b823SAlexander V. Chernikov This handler receives an interface name, fetches received/sent packets 1788161b823SAlexander V. Chernikov and returns this data back to the parent process. 1798161b823SAlexander V. Chernikov """ 1808161b823SAlexander V. Chernikov while True: 1818161b823SAlexander V. Chernikov # receives 'ifX' with an infinite timeout 1828161b823SAlexander V. Chernikov iface_alias = self.wait_object(vnet.pipe, None) 1838161b823SAlexander V. Chernikov # Translates topology interface name to the actual OS-assigned name 1848161b823SAlexander V. Chernikov os_ifname = vnet.iface_alias_map[iface_alias].name 1858161b823SAlexander V. Chernikov self.send_object(vnet.pipe, self._get_iface_stat(os_ifname)) 1868161b823SAlexander V. Chernikov 1878161b823SAlexander V. Chernikov @pytest.mark.skip(reason="comment me to run the test") 1888161b823SAlexander V. Chernikov @pytest.mark.require_user("root") 1898161b823SAlexander V. Chernikov def test_ifstat(self): 1908161b823SAlexander V. Chernikov """Checks that RX interface packets are properly accounted for""" 1918161b823SAlexander V. Chernikov second_vnet = self.vnet_map["vnet2"] 1928161b823SAlexander V. Chernikov pipe = second_vnet.pipe 1938161b823SAlexander V. Chernikov 1948161b823SAlexander V. Chernikov # Ping neighbor IP on if1 and verify that the counter was incremented 1958161b823SAlexander V. Chernikov self.send_object(pipe, "if1") 1968161b823SAlexander V. Chernikov old_stat = self.wait_object(pipe) 1978161b823SAlexander V. Chernikov assert subprocess.run("ping -c5 2001:db8:a::2".split()).returncode == 0 1988161b823SAlexander V. Chernikov self.send_object(pipe, "if1") 1998161b823SAlexander V. Chernikov new_stat = self.wait_object(pipe) 2008161b823SAlexander V. Chernikov assert new_stat["received-packets"] - old_stat["received-packets"] >= 5 2018161b823SAlexander V. Chernikov 2028161b823SAlexander V. Chernikov # Ping neighbor IP on if2 and verify that the counter was incremented 2038161b823SAlexander V. Chernikov self.send_object(pipe, "if2") 2048161b823SAlexander V. Chernikov old_stat = self.wait_object(pipe) 2058161b823SAlexander V. Chernikov assert subprocess.run("ping -c5 2001:db8:b::2".split()).returncode == 0 2068161b823SAlexander V. Chernikov self.send_object(pipe, "if2") 2078161b823SAlexander V. Chernikov new_stat = self.wait_object(pipe) 2088161b823SAlexander V. Chernikov assert new_stat["received-packets"] - old_stat["received-packets"] >= 5 209