1*8161b823SAlexander V. Chernikovimport pytest 2*8161b823SAlexander V. Chernikovfrom atf_python.utils import BaseTest 3*8161b823SAlexander V. Chernikovfrom atf_python.sys.net.tools import ToolsHelper 4*8161b823SAlexander V. Chernikovfrom atf_python.sys.net.vnet import SingleVnetTestTemplate 5*8161b823SAlexander V. Chernikovfrom atf_python.sys.net.vnet import VnetTestTemplate 6*8161b823SAlexander V. Chernikovfrom atf_python.sys.net.vnet import VnetInstance 7*8161b823SAlexander V. Chernikov 8*8161b823SAlexander V. Chernikovimport errno 9*8161b823SAlexander V. Chernikovimport socket 10*8161b823SAlexander V. Chernikovimport subprocess 11*8161b823SAlexander V. Chernikovimport json 12*8161b823SAlexander V. Chernikov 13*8161b823SAlexander V. Chernikovfrom typing import List 14*8161b823SAlexander V. Chernikov 15*8161b823SAlexander V. Chernikov 16*8161b823SAlexander V. Chernikov# Test classes should be inherited 17*8161b823SAlexander V. Chernikov# from the BaseTest 18*8161b823SAlexander V. Chernikov 19*8161b823SAlexander V. Chernikov 20*8161b823SAlexander V. Chernikovclass TestExampleSimplest(BaseTest): 21*8161b823SAlexander V. Chernikov @pytest.mark.skip(reason="comment me to run the test") 22*8161b823SAlexander V. Chernikov def test_one(self): 23*8161b823SAlexander V. Chernikov assert ToolsHelper.get_output("uname -s").strip() == "FreeBSD" 24*8161b823SAlexander V. Chernikov 25*8161b823SAlexander V. Chernikov 26*8161b823SAlexander V. Chernikovclass TestExampleSimple(BaseTest): 27*8161b823SAlexander V. Chernikov # List of required kernel modules (kldstat -v) 28*8161b823SAlexander V. Chernikov # that needs to be present for the tests to run 29*8161b823SAlexander V. Chernikov REQUIRED_MODULES = ["null"] 30*8161b823SAlexander V. Chernikov 31*8161b823SAlexander V. Chernikov @pytest.mark.skip(reason="comment me to run the test") 32*8161b823SAlexander V. Chernikov def test_one(self): 33*8161b823SAlexander V. Chernikov """Optional test description 34*8161b823SAlexander V. Chernikov This and the following lines are not propagated 35*8161b823SAlexander V. Chernikov to the ATF test description. 36*8161b823SAlexander V. Chernikov """ 37*8161b823SAlexander V. Chernikov pass 38*8161b823SAlexander V. Chernikov 39*8161b823SAlexander V. Chernikov @pytest.mark.skip(reason="comment me to run the test") 40*8161b823SAlexander V. Chernikov # List of all requirements supported by an atf runner 41*8161b823SAlexander V. Chernikov # See atf-test-case(4) for the detailed description 42*8161b823SAlexander V. Chernikov @pytest.mark.require_user("root") 43*8161b823SAlexander V. Chernikov @pytest.mark.require_arch(["amd64", "i386"]) 44*8161b823SAlexander V. Chernikov @pytest.mark.require_files(["/path/file1", "/path/file2"]) 45*8161b823SAlexander V. Chernikov @pytest.mark.require_machine(["amd64", "i386"]) 46*8161b823SAlexander V. Chernikov @pytest.mark.require_memory("200M") 47*8161b823SAlexander V. Chernikov @pytest.mark.require_progs(["prog1", "prog2"]) 48*8161b823SAlexander V. Chernikov @pytest.mark.timeout(300) 49*8161b823SAlexander V. Chernikov def test_two(self): 50*8161b823SAlexander V. Chernikov pass 51*8161b823SAlexander V. Chernikov 52*8161b823SAlexander V. Chernikov @pytest.mark.skip(reason="comment me to run the test") 53*8161b823SAlexander V. Chernikov @pytest.mark.require_user("unprivileged") 54*8161b823SAlexander V. Chernikov def test_syscall_failure(self): 55*8161b823SAlexander V. Chernikov s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) 56*8161b823SAlexander V. Chernikov with pytest.raises(OSError) as exc_info: 57*8161b823SAlexander V. Chernikov s.bind(("::1", 42)) 58*8161b823SAlexander V. Chernikov assert exc_info.value.errno == errno.EACCES 59*8161b823SAlexander V. Chernikov 60*8161b823SAlexander V. Chernikov @pytest.mark.skip(reason="comment me to run the test") 61*8161b823SAlexander V. Chernikov @pytest.mark.parametrize( 62*8161b823SAlexander V. Chernikov "family_tuple", 63*8161b823SAlexander V. Chernikov [ 64*8161b823SAlexander V. Chernikov pytest.param([socket.AF_INET, None], id="AF_INET"), 65*8161b823SAlexander V. Chernikov pytest.param([socket.AF_INET6, None], id="AF_INET6"), 66*8161b823SAlexander V. Chernikov pytest.param([39, errno.EAFNOSUPPORT], id="FAMILY_39"), 67*8161b823SAlexander V. Chernikov ], 68*8161b823SAlexander V. Chernikov ) 69*8161b823SAlexander V. Chernikov def test_parametrize(self, family_tuple): 70*8161b823SAlexander V. Chernikov family, error = family_tuple 71*8161b823SAlexander V. Chernikov try: 72*8161b823SAlexander V. Chernikov s = socket.socket(family, socket.SOCK_STREAM) 73*8161b823SAlexander V. Chernikov s.close() 74*8161b823SAlexander V. Chernikov except OSError as e: 75*8161b823SAlexander V. Chernikov if error is None or error != e.errno: 76*8161b823SAlexander V. Chernikov raise 77*8161b823SAlexander V. Chernikov 78*8161b823SAlexander V. Chernikov # @pytest.mark.skip(reason="comment me to run the test") 79*8161b823SAlexander V. Chernikov def test_with_cleanup(self): 80*8161b823SAlexander V. Chernikov print("TEST BODY") 81*8161b823SAlexander V. Chernikov 82*8161b823SAlexander V. Chernikov def cleanup_test_with_cleanup(self, test_id): 83*8161b823SAlexander V. Chernikov print("CLEANUP HANDLER") 84*8161b823SAlexander V. Chernikov 85*8161b823SAlexander V. Chernikov 86*8161b823SAlexander V. Chernikovclass TestVnetSimple(SingleVnetTestTemplate): 87*8161b823SAlexander V. Chernikov """ 88*8161b823SAlexander V. Chernikov SingleVnetTestTemplate creates a topology with a single 89*8161b823SAlexander V. Chernikov vnet and a single epair between this vnet and the host system. 90*8161b823SAlexander V. Chernikov Additionally, lo0 interface is created inside the vnet. 91*8161b823SAlexander V. Chernikov 92*8161b823SAlexander V. Chernikov Both vnets and interfaces are aliased as vnetX and ifY. 93*8161b823SAlexander V. Chernikov They can be accessed via maps: 94*8161b823SAlexander V. Chernikov vnet: VnetInstance = self.vnet_map["vnet1"] 95*8161b823SAlexander V. Chernikov iface: VnetInterface = vnet.iface_alias_map["if1"] 96*8161b823SAlexander V. Chernikov 97*8161b823SAlexander V. Chernikov All prefixes from IPV4_PREFIXES and IPV6_PREFIXES are 98*8161b823SAlexander V. Chernikov assigned to the single epair interface inside the jail. 99*8161b823SAlexander V. Chernikov 100*8161b823SAlexander V. Chernikov One can rely on the fact that there are no IPv6 prefixes 101*8161b823SAlexander V. Chernikov in the tentative state when the test method is called. 102*8161b823SAlexander V. Chernikov """ 103*8161b823SAlexander V. Chernikov 104*8161b823SAlexander V. Chernikov IPV6_PREFIXES: List[str] = ["2001:db8::1/64"] 105*8161b823SAlexander V. Chernikov IPV4_PREFIXES: List[str] = ["192.0.2.1/24"] 106*8161b823SAlexander V. Chernikov 107*8161b823SAlexander V. Chernikov def setup_method(self, method): 108*8161b823SAlexander V. Chernikov """ 109*8161b823SAlexander V. Chernikov Optional pre-setup for all of the tests inside the class 110*8161b823SAlexander V. Chernikov """ 111*8161b823SAlexander V. Chernikov # Code to run before vnet setup 112*8161b823SAlexander V. Chernikov # 113*8161b823SAlexander V. Chernikov super().setup_method(method) 114*8161b823SAlexander V. Chernikov # 115*8161b823SAlexander V. Chernikov # Code to run after vnet setup 116*8161b823SAlexander V. Chernikov # Executed inside the vnet 117*8161b823SAlexander V. Chernikov 118*8161b823SAlexander V. Chernikov @pytest.mark.skip(reason="comment me to run the test") 119*8161b823SAlexander V. Chernikov @pytest.mark.require_user("root") 120*8161b823SAlexander V. Chernikov def test_ping(self): 121*8161b823SAlexander V. Chernikov assert subprocess.run("ping -c1 192.0.2.1".split()).returncode == 0 122*8161b823SAlexander V. Chernikov assert subprocess.run("ping -c1 2001:db8::1".split()).returncode == 0 123*8161b823SAlexander V. Chernikov 124*8161b823SAlexander V. Chernikov @pytest.mark.skip(reason="comment me to run the test") 125*8161b823SAlexander V. Chernikov def test_topology(self): 126*8161b823SAlexander V. Chernikov vnet = self.vnet_map["vnet1"] 127*8161b823SAlexander V. Chernikov iface = vnet.iface_alias_map["if1"] 128*8161b823SAlexander V. Chernikov print("Iface {} inside vnet {}".format(iface.name, vnet.name)) 129*8161b823SAlexander V. Chernikov 130*8161b823SAlexander V. Chernikov 131*8161b823SAlexander V. Chernikovclass TestVnetDual1(VnetTestTemplate): 132*8161b823SAlexander V. Chernikov """ 133*8161b823SAlexander V. Chernikov VnetTestTemplate creates topology described in the self.TOPOLOGY 134*8161b823SAlexander V. Chernikov 135*8161b823SAlexander V. Chernikov Each vnet (except vnet1) can have a handler function, named 136*8161b823SAlexander V. Chernikov vnetX_handler. This function will be run in a separate process 137*8161b823SAlexander V. Chernikov inside vnetX jail. The framework automatically creates a pipe 138*8161b823SAlexander V. Chernikov to allow communication between the main test and the vnet handler. 139*8161b823SAlexander V. Chernikov 140*8161b823SAlexander V. Chernikov This topology contains 2 VNETs connected with 2 epairs: 141*8161b823SAlexander V. Chernikov 142*8161b823SAlexander V. Chernikov [ VNET1 ] [ VNET2 ] 143*8161b823SAlexander V. Chernikov if1(epair) 2001:db8:a::1/64 <-> 2001:db8:a::2/64 if1(epair) 144*8161b823SAlexander V. Chernikov if2(epair) 2001:db8:b::1/64 <-> 2001:db8:b::2/64 if2(epair) 145*8161b823SAlexander V. Chernikov lo0 lo0 146*8161b823SAlexander V. Chernikov 147*8161b823SAlexander V. Chernikov """ 148*8161b823SAlexander V. Chernikov 149*8161b823SAlexander V. Chernikov TOPOLOGY = { 150*8161b823SAlexander V. Chernikov "vnet1": {"ifaces": ["if1", "if2"]}, 151*8161b823SAlexander V. Chernikov "vnet2": {"ifaces": ["if1", "if2"]}, 152*8161b823SAlexander V. Chernikov "if1": {"prefixes6": [("2001:db8:a::1/64", "2001:db8:a::2/64")]}, 153*8161b823SAlexander V. Chernikov "if2": {"prefixes6": [("2001:db8:b::1/64", "2001:db8:b::2/64")]}, 154*8161b823SAlexander V. Chernikov } 155*8161b823SAlexander V. Chernikov 156*8161b823SAlexander V. Chernikov def _get_iface_stat(self, os_ifname: str): 157*8161b823SAlexander V. Chernikov out = ToolsHelper.get_output( 158*8161b823SAlexander V. Chernikov "{} -I {} --libxo json".format(ToolsHelper.NETSTAT_PATH, os_ifname) 159*8161b823SAlexander V. Chernikov ) 160*8161b823SAlexander V. Chernikov js = json.loads(out) 161*8161b823SAlexander V. Chernikov return js["statistics"]["interface"][0] 162*8161b823SAlexander V. Chernikov 163*8161b823SAlexander V. Chernikov def vnet2_handler(self, vnet: VnetInstance): 164*8161b823SAlexander V. Chernikov """ 165*8161b823SAlexander V. Chernikov Test handler that runs in the vnet2 as a separate process. 166*8161b823SAlexander V. Chernikov 167*8161b823SAlexander V. Chernikov This handler receives an interface name, fetches received/sent packets 168*8161b823SAlexander V. Chernikov and returns this data back to the parent process. 169*8161b823SAlexander V. Chernikov """ 170*8161b823SAlexander V. Chernikov while True: 171*8161b823SAlexander V. Chernikov # receives 'ifX' with an infinite timeout 172*8161b823SAlexander V. Chernikov iface_alias = self.wait_object(vnet.pipe, None) 173*8161b823SAlexander V. Chernikov # Translates topology interface name to the actual OS-assigned name 174*8161b823SAlexander V. Chernikov os_ifname = vnet.iface_alias_map[iface_alias].name 175*8161b823SAlexander V. Chernikov self.send_object(vnet.pipe, self._get_iface_stat(os_ifname)) 176*8161b823SAlexander V. Chernikov 177*8161b823SAlexander V. Chernikov @pytest.mark.skip(reason="comment me to run the test") 178*8161b823SAlexander V. Chernikov @pytest.mark.require_user("root") 179*8161b823SAlexander V. Chernikov def test_ifstat(self): 180*8161b823SAlexander V. Chernikov """Checks that RX interface packets are properly accounted for""" 181*8161b823SAlexander V. Chernikov second_vnet = self.vnet_map["vnet2"] 182*8161b823SAlexander V. Chernikov pipe = second_vnet.pipe 183*8161b823SAlexander V. Chernikov 184*8161b823SAlexander V. Chernikov # Ping neighbor IP on if1 and verify that the counter was incremented 185*8161b823SAlexander V. Chernikov self.send_object(pipe, "if1") 186*8161b823SAlexander V. Chernikov old_stat = self.wait_object(pipe) 187*8161b823SAlexander V. Chernikov assert subprocess.run("ping -c5 2001:db8:a::2".split()).returncode == 0 188*8161b823SAlexander V. Chernikov self.send_object(pipe, "if1") 189*8161b823SAlexander V. Chernikov new_stat = self.wait_object(pipe) 190*8161b823SAlexander V. Chernikov assert new_stat["received-packets"] - old_stat["received-packets"] >= 5 191*8161b823SAlexander V. Chernikov 192*8161b823SAlexander V. Chernikov # Ping neighbor IP on if2 and verify that the counter was incremented 193*8161b823SAlexander V. Chernikov self.send_object(pipe, "if2") 194*8161b823SAlexander V. Chernikov old_stat = self.wait_object(pipe) 195*8161b823SAlexander V. Chernikov assert subprocess.run("ping -c5 2001:db8:b::2".split()).returncode == 0 196*8161b823SAlexander V. Chernikov self.send_object(pipe, "if2") 197*8161b823SAlexander V. Chernikov new_stat = self.wait_object(pipe) 198*8161b823SAlexander V. Chernikov assert new_stat["received-packets"] - old_stat["received-packets"] >= 5 199