class TestPMD(BaseTestModule): coremask = StrParam(mandatory=True) pmd_coremask = StrParam(mandatory=True) #TODO make ListParam nics = Param(mandatory=True) peer_macs = Param(mandatory=True) def format_command(self): testpmd_args = [ "testpmd", "-c", self.params.coremask, "-n", "4", "--socket-mem", "1024,0" ] for nic in self.params.nics: testpmd_args.extend(["-w", nic]) testpmd_args.extend([ "--", "-i", "--forward-mode", "mac", "--coremask", self.params.pmd_coremask ]) for i, mac in enumerate(self.params.peer_macs): testpmd_args.extend(["--eth-peer", "{},{}".format(i, mac)]) return " ".join(testpmd_args) def run(self): cmd = self.format_command() logging.debug("Running command \"{}\" as subprocess".format(cmd)) process = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) process.stdin.write(str.encode("start tx_first\n")) process.stdin.flush() self.wait_for_interrupt() process.stdin.write(str.encode("stop\n")) process.stdin.flush() process.stdin.write(str.encode("quit\n")) process.stdin.flush() out, err = process.communicate() self._res_data = {"stdout": out, "stderr": err} return True
class TRexClient(TRexCommon): ports = Param(mandatory=True) flows = Param(mandatory=True) module = StrParam(default="UDPSimple") duration = IntParam(mandatory=True) warmup_time = IntParam(default=5) msg_size = IntParam(default=64) server_hostname = StrParam(default="localhost") trex_stl_path = 'trex_client/interactive' def __init__(self, **kwargs): super(TRexClient, self).__init__(**kwargs) self.impl = TRexCli(self.params) def __repr__(self): string = f""" TrexClient( trex_dir={self.impl.params.trex_dir}, module={self.impl.params.module}, msg_size={self.impl.params.msg_size}, ports={self.impl.params.ports}, server_hostname={self.impl.params.server_hostname}, flows={pformat(self.impl.params.flows)} ) """ return string def runtime_estimate(self): _duration_overhead = 5 return (self.params.duration + self.params.warmup_time + _duration_overhead) def run(self): self._res_data = {} try: rc = self.impl.run() except TRexError as e: #TRex errors aren't picklable so we wrap them like this raise TestModuleError(str(e)) self._res_data = self.impl.get_results() return rc
class IperfServer(IperfBase): bind = IpParam() port = IntParam() cpu_bind = IntParam() opts = StrParam() oneoff = BoolParam(default=False) _role = "server" def _compose_cmd(self): bind = "" port = "" if "bind" in self.params: bind = "-B {}".format(self.params.bind) if "port" in self.params: port = "-p {}".format(self.params.port) if "cpu_bind" in self.params: cpu = "-A {:d}".format(self.params.cpu_bind) else: cpu = "" if "oneoff" in self.params and self.params.oneoff: oneoff = "-1" cmd = "iperf3 -s {bind} -J {port} {cpu} {oneoff} {opts}".format( bind=bind, port=port, cpu=cpu, oneoff=oneoff, opts=self.params.opts if "opts" in self.params else "") return cmd
class LinuxPerf(BaseTestModule): output_file = StrParam(mandatory=True) cpus = ListParam(type=IntParam()) events = ListParam(type=StrParam()) def run(self) -> bool: self._res_data = {} if not is_installed("perf"): self._res_data["msg"] = "perf is not installed on this machine!" logging.error(self._res_data["msg"]) return False # can't use lnst.Common.ExecCmd.exec_cmd directly, because expected returncode is not zero cmd: str = self._compose_cmd() logging.debug(f"Executing: \"{cmd}\"") process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) self.wait_for_interrupt() stdout, stderr = process.communicate() if stdout: log_output(logging.debug, "Stdout", stdout.decode()) if stderr: log_output(logging.debug, "Stderr", stderr.decode()) self._res_data["filename"] = os.path.abspath(self.params.output_file) return process.returncode == -2 def _compose_cmd(self) -> str: cmd: str = "perf record" cmd += f" --output={self.params.output_file}" if "cpus" in self.params: cmd += f" --cpu={','.join(map(str, self.params.cpus))}" if "events" in self.params: cmd += f" --event={','.join(self.params.events)}" return cmd
class PingFloodRecipe(PingTestAndEvaluate): driver = StrParam(default='ixgbe') host1 = HostReq() host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) host2 = HostReq() host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) src_addr = StrParam(default="192.168.1.1/24") dst_addr = StrParam(default="192.168.1.2/24") count = IntParam(default=100) interval = StrParam(default=0.2) size = IntParam(mandatory=False) mtu = IntParam(mandatory=False) def test(self): host1, host2 = self.matched.host1, self.matched.host2 host1.eth0.ip_add(ipaddress(self.params.src_addr)) host2.eth0.ip_add(ipaddress(self.params.dst_addr)) if "mtu" in self.params: host1.eth0.mtu = self.params.mtu host2.eth0.mtu = self.params.mtu host1.eth0.up() host2.eth0.up() ip1 = host1.eth0.ips[0] ip2 = host2.eth0.ips[0] cn = self.params.count iv = self.params.interval if "size" in self.params: sz = self.params.size else: sz = None pcfg = PingConf(host1, ip1, host2, ip2, count=cn, interval=iv, size=sz) result = self.ping_test([pcfg]) self.ping_evaluate_and_report(pcfg, result)
class Netserver(BaseTestModule): bind = IpParam(mandatory=True) port = IntParam() opts = StrParam() def wait_on_interrupt(self): try: handler = signal.getsignal(signal.SIGINT) signal.signal(signal.SIGINT, signal.default_int_handler) while True: time.sleep(1) except KeyboardInterrupt: pass finally: signal.signal(signal.SIGINT, handler) def run(self): if not is_installed("netserver"): res_data = {} res_data["msg"] = "Netserver is not installed on this machine!" logging.error(res_data["msg"]) self._res_data = res_data return False cmd = "netserver -D{bind}{port} {opts}".format( bind=" -L " + str(self.params.bind), port=" -p " + str(self.params.port) if "port" in self.params else "", opts=self.params.opts if "opts" in self.params else "") logging.debug("compiled command: %s" % cmd) logging.debug("running as server...") proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) self.wait_on_interrupt() proc.kill() return True
class VlansOverBondRecipe(PerfReversibleFlowMixin, VlanPingEvaluatorMixin, CommonHWSubConfigMixin, OffloadSubConfigMixin, BaremetalEnrtRecipe): """ This recipe implements Enrt testing for a network scenario that looks as follows .. code-block:: none .--------. .-------------+ switch +--------. | .---+ | | | | '--------' | .-----|---------|----. | | .---'--. .---'--. | .--'---. .-|-| eth0 |--| eth1 |-|-. .-------| eth0 |------. | | '------' '------' | | | '------' | | | bond0 | | | / | \ | | '-------/--|--\------' | | vlan0 vlan1 vlan2 | | / | \ | | id=10 id=20 id=30 | | vlan0 vlan1 vlan2 | | | | id=10 id=20 id=30 | | | | | | | | host1 | | host2 | '------------------------' '---------------------' The recipe provides additional recipe parameters to configure the bonding device. :param bonding_mode: (mandatory test parameter) the bonding mode to be configured on the bond0 device :param miimon_value: (mandatory test parameter) the miimon interval to be configured on the bond0 device All sub configurations are included via Mixin classes. The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. """ host1 = HostReq() host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) host1.eth1 = DeviceReq(label="net1", driver=RecipeParam("driver")) host2 = HostReq() host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) vlan0_id = IntParam(default=10) vlan1_id = IntParam(default=20) vlan2_id = IntParam(default=30) offload_combinations = Param(default=( dict(gro="on", gso="on", tso="on", tx="on"), dict(gro="off", gso="on", tso="on", tx="on"), dict(gro="on", gso="off", tso="off", tx="on"), dict(gro="on", gso="on", tso="off", tx="off"))) bonding_mode = StrParam(mandatory=True) miimon_value = IntParam(mandatory=True) def test_wide_configuration(self): """ Test wide configuration for this recipe involves creating one bonding device on the first host. This device bonds two NICs matched by the recipe. The bonding mode and miimon interval is configured on the bonding device according to the recipe parameters. Then three VLAN (802.1Q) tunnels are created on top of the bonding device on the first host and on the matched NIC on the second host. The tunnels are configured with VLAN ids from vlan0_id, vlan1_id and vlan2_id params (by default: 10, 20, 30). An IPv4 and IPv6 address is configured on each tunnel endpoint. | host1.vlan0 = 192.168.10.1/24 and fc00:0:0:1::1/64 | host1.vlan1 = 192.168.20.1/24 and fc00:0:0:2::1/64 | host1.vlan2 = 192.168.30.1/24 and fc00:0:0:3::1/64 | host2.vlan0 = 192.168.10.2/24 and fc00:0:0:1::2/64 | host2.vlan1 = 192.168.20.2/24 and fc00:0:0:2::2/64 | host2.vlan2 = 192.168.30.2/24 and fc00:0:0:3::2/64 """ host1, host2 = self.matched.host1, self.matched.host2 host1.bond0 = BondDevice(mode=self.params.bonding_mode, miimon=self.params.miimon_value) for dev in [host1.eth0, host1.eth1]: dev.down() host1.bond0.slave_add(dev) host1.vlan0 = VlanDevice(realdev=host1.bond0, vlan_id=self.params.vlan0_id) host1.vlan1 = VlanDevice(realdev=host1.bond0, vlan_id=self.params.vlan1_id) host1.vlan2 = VlanDevice(realdev=host1.bond0, vlan_id=self.params.vlan2_id) host2.vlan0 = VlanDevice(realdev=host2.eth0, vlan_id=self.params.vlan0_id) host2.vlan1 = VlanDevice(realdev=host2.eth0, vlan_id=self.params.vlan1_id) host2.vlan2 = VlanDevice(realdev=host2.eth0, vlan_id=self.params.vlan2_id) configuration = super().test_wide_configuration() configuration.test_wide_devices = [] for host in [host1, host2]: configuration.test_wide_devices.extend([host.vlan0, host.vlan1, host.vlan2]) configuration.test_wide_devices.append(host1.bond0) net_addr = "192.168" net_addr6 = "fc00:0:0" for i, host in enumerate([host1, host2]): host.vlan0.ip_add(ipaddress('{}.10.{}/24'.format(net_addr, i+1))) host.vlan1.ip_add(ipaddress('{}.20.{}/24'.format(net_addr, i+1))) host.vlan2.ip_add(ipaddress('{}.30.{}/24'.format(net_addr, i+1))) host.vlan0.ip_add(ipaddress('{}:1::{}/64'.format(net_addr6, i+1))) host.vlan1.ip_add(ipaddress('{}:2::{}/64'.format(net_addr6, i+1))) host.vlan2.ip_add(ipaddress('{}:3::{}/64'.format(net_addr6, i+1))) for dev in [host1.eth0, host1.eth1, host1.bond0, host1.vlan0, host1.vlan1, host1.vlan2, host2.eth0, host2.vlan0, host2.vlan1, host2.vlan2]: dev.up() self.wait_tentative_ips(configuration.test_wide_devices) return configuration def generate_test_wide_description(self, config): """ Test wide description is extended with the configured VLAN tunnels, their IP addresses and the bonding device configuration. """ host1, host2 = self.matched.host1, self.matched.host2 desc = super().generate_test_wide_description(config) desc += [ "\n".join([ "Configured {}.{}.ips = {}".format( dev.host.hostid, dev.name, dev.ips ) for dev in config.test_wide_devices if isinstance(dev, Vlan) ]), "\n".join([ "Configured {}.{}.vlan_id = {}".format( dev.host.hostid, dev.name, dev.vlan_id ) for dev in config.test_wide_devices if isinstance(dev, Vlan) ]), "\n".join([ "Configured {}.{}.realdev = {}".format( dev.host.hostid, dev.name, '.'.join([dev.host.hostid, dev.realdev.name]) ) for dev in config.test_wide_devices if isinstance(dev, Vlan) ]), "Configured {}.{}.slaves = {}".format( host1.hostid, host1.bond0.name, ['.'.join([host1.hostid, slave.name]) for slave in host1.bond0.slaves] ), "Configured {}.{}.mode = {}".format( host1.hostid, host1.bond0.name, host1.bond0.mode ), "Configured {}.{}.miimon = {}".format( host1.hostid, host1.bond0.name, host1.bond0.miimon ) ] return desc def test_wide_deconfiguration(self, config): del config.test_wide_devices super().test_wide_deconfiguration(config) def generate_ping_endpoints(self, config): """ The ping endpoints for this recipe are the matching VLAN tunnel endpoints of the hosts. Returned as:: [PingEndpoints(host1.vlan0, host2.vlan0), PingEndpoints(host1.vlan1, host2.vlan1), PingEndpoints(host1.vlan2, host2.vlan2)] """ host1, host2 = self.matched.host1, self.matched.host2 return [PingEndpoints(host1.vlan0, host2.vlan0), PingEndpoints(host1.vlan1, host2.vlan1), PingEndpoints(host1.vlan2, host2.vlan2)] def generate_perf_endpoints(self, config): """ The perf endpoints for this recipe are the VLAN tunnel endpoints with VLAN id from parameter vlan_ids[0] (by default: 10): host1.vlan0 and host2.vlan0 Returned as:: [(self.matched.host1.vlan0, self.matched.host2.vlan0)] """ return [(self.matched.host1.vlan0, self.matched.host2.vlan0)] @property def offload_nics(self): """ The `offload_nics` property value for this scenario is a list of the physical devices carrying data of the configured VLAN tunnels: host1.eth0, host1.eth1 and host2.eth0 For detailed explanation of this property see :any:`OffloadSubConfigMixin` class and :any:`OffloadSubConfigMixin.offload_nics`. """ host1, host2 = self.matched.host1, self.matched.host2 return [host1.eth0, host1.eth1, host2.eth0] @property def mtu_hw_config_dev_list(self): """ The `mtu_hw_config_dev_list` property value for this scenario is a list of all configured VLAN tunnel devices and the underlying bonding or physical devices: | host1.bond0, host1.vlan0, host1.vlan1, host1.vlan2 | host2.eth0, host2.vlan0, host2.vlan1, host2.vlan2 For detailed explanation of this property see :any:`MTUHWConfigMixin` class and :any:`MTUHWConfigMixin.mtu_hw_config_dev_list`. """ host1, host2 = self.matched.host1, self.matched.host2 result = [] for host in [host1, host2]: for dev in [host.vlan0, host.vlan1, host.vlan2]: result.append(dev) result.extend([host1.bond0, host2.eth0]) return result @property def coalescing_hw_config_dev_list(self): """ The `coalescing_hw_config_dev_list` property value for this scenario is a list of the physical devices carrying data of the configured VLAN tunnels: host1.eth0, host1.eth1 and host2.eth0 For detailed explanation of this property see :any:`CoalescingHWConfigMixin` class and :any:`CoalescingHWConfigMixin.coalescing_hw_config_dev_list`. """ host1, host2 = self.matched.host1, self.matched.host2 return [host1.eth0, host1.eth1, host2.eth0] @property def dev_interrupt_hw_config_dev_list(self): """ The `dev_interrupt_hw_config_dev_list` property value for this scenario is a list of the physical devices carrying data of the configured VLAN tunnels: host1.eth0, host1.eth1 and host2.eth0 For detailed explanation of this property see :any:`DevInterruptHWConfigMixin` class and :any:`DevInterruptHWConfigMixin.dev_interrupt_hw_config_dev_list`. """ host1, host2 = self.matched.host1, self.matched.host2 return [host1.eth0, host1.eth1, host2.eth0] @property def parallel_stream_qdisc_hw_config_dev_list(self): """ The `parallel_stream_qdisc_hw_config_dev_list` property value for this scenario is a list of the physical devices carrying data of the configured VLAN tunnels: host1.eth0, host1.eth1 and host2.eth0 For detailed explanation of this property see :any:`ParallelStreamQDiscHWConfigMixin` class and :any:`ParallelStreamQDiscHWConfigMixin.parallel_stream_qdisc_hw_config_dev_list`. """ host1, host2 = self.matched.host1, self.matched.host2 return [host1.eth0, host1.eth1, host2.eth0] @property def pause_frames_dev_list(self): """ The `pause_frames_dev_list` property value for this scenario is a list of the physical devices carrying data of the configured VLAN tunnels: host1.eth0, host1.eth1 and host2.eth0 For detailed explanation of this property see :any:`PauseFramesHWConfigMixin` and :any:`PauseFramesHWConfigMixin.pause_frames_dev_list`. """ host1, host2 = self.matched.host1, self.matched.host2 return [host1.eth0, host1.eth1, host2.eth0]
class TRexClient(TRexCommon): #make Int List ports = Param(mandatory=True) flows = Param(mandatory=True) duration = IntParam(mandatory=True) warmup_time = IntParam(default=5) msg_size = IntParam(default=64) server_hostname = StrParam(default="localhost") trex_stl_path = 'trex_client/interactive' def runtime_estimate(self): _duration_overhead = 5 return (self.params.duration + self.params.warmup_time + _duration_overhead) def run(self): sys.path.insert(0, os.path.join(self.params.trex_dir, self.trex_stl_path)) from trex.stl import api as trex_api try: return self._run(trex_api) except trex_api.TRexError as e: #TRex errors aren't picklable so we wrap them like this raise TestModuleError(str(e)) def _run(self, trex_api): client = trex_api.STLClient(server=self.params.server_hostname) client.connect() self._res_data = {} try: client.acquire(ports=self.params.ports, force=True) except: self._res_data["msg"] = "Failed to acquire ports" return False try: client.reset(ports=self.params.ports) except: client.release(ports=self.params.ports) self._res_data["msg"] = "Failed to reset ports" return False for i, (src, dst) in enumerate(self.params.flows): L2 = trex_api.Ether(src=str(src["mac_addr"]), dst=str(dst["mac_addr"])) L3 = trex_api.IP(src=str(src["ip_addr"]), dst=str(dst["ip_addr"])) L4 = trex_api.UDP() base_pkt = L2 / L3 / L4 pad = max(0, self.params.msg_size - len(base_pkt)) * 'x' packet = base_pkt / pad trex_packet = trex_api.STLPktBuilder(pkt=packet) trex_stream = trex_api.STLStream( packet=trex_packet, mode=trex_api.STLTXCont(percentage=100)) port = self.params.ports[i] client.add_streams(trex_stream, ports=[port]) client.set_port_attr(ports=self.params.ports, promiscuous=True) measurements = [] client.start(ports=self.params.ports) time.sleep(self.params.warmup_time) client.clear_stats(ports=self.params.ports) self._res_data["start_time"] = time.time() for i in range(self.params.duration): time.sleep(1) measurements.append( dict(timestamp=time.time(), measurement=client.get_stats(ports=self.params.ports, sync_now=True))) client.stop(ports=self.params.ports) client.release(ports=self.params.ports) self._res_data["data"] = measurements return True
class VirtualOvsBridgeVlansOverBondRecipe(VlanPingEvaluatorMixin, CommonHWSubConfigMixin, OffloadSubConfigMixin, VirtualEnrtRecipe): host1 = HostReq() host1.eth0 = DeviceReq(label="to_switch", driver=RecipeParam("driver")) host1.eth1 = DeviceReq(label="to_switch", driver=RecipeParam("driver")) host1.tap0 = DeviceReq(label="to_guest1") host1.tap1 = DeviceReq(label="to_guest2") host2 = HostReq() host2.eth0 = DeviceReq(label="to_switch", driver=RecipeParam("driver")) host2.eth1 = DeviceReq(label="to_switch", driver=RecipeParam("driver")) host2.tap0 = DeviceReq(label="to_guest3") host2.tap1 = DeviceReq(label="to_guest4") guest1 = HostReq() guest1.eth0 = DeviceReq(label="to_guest1") guest2 = HostReq() guest2.eth0 = DeviceReq(label="to_guest2") guest3 = HostReq() guest3.eth0 = DeviceReq(label="to_guest3") guest4 = HostReq() guest4.eth0 = DeviceReq(label="to_guest4") offload_combinations = Param( default=(dict(gro="on", gso="on", tso="on", tx="on"), dict(gro="off", gso="on", tso="on", tx="on"), dict(gro="on", gso="off", tso="off", tx="on"), dict(gro="on", gso="on", tso="off", tx="off"))) bonding_mode = StrParam(mandatory=True) miimon_value = IntParam(mandatory=True) def test_wide_configuration(self): host1, host2, guest1, guest2, guest3, guest4 = (self.matched.host1, self.matched.host2, self.matched.guest1, self.matched.guest2, self.matched.guest3, self.matched.guest4) for host, port_name in [(host1, "bond_port1"), (host2, "bond_port2")]: for dev in [host.eth0, host.eth1, host.tap0, host.tap1]: dev.down() host.br0 = OvsBridgeDevice() for dev, tag in [(host.tap0, "10"), (host.tap1, "20")]: host.br0.port_add(device=dev, port_options={'tag': tag}) #miimon cannot be set due to colon in argument name --> #other_config:bond-miimon-interval host.br0.bond_add(port_name, (host.eth0, host.eth1), bond_mode=self.params.bonding_mode) guest1.eth0.down() guest2.eth0.down() guest3.eth0.down() guest4.eth0.down() configuration = super().test_wide_configuration() configuration.test_wide_devices = [ guest1.eth0, guest2.eth0, guest3.eth0, guest4.eth0 ] net_addr_1 = "192.168.10" net_addr6_1 = "fc00:0:0:1" net_addr_2 = "192.168.20" net_addr6_2 = "fc00:0:0:2" for i, guest in enumerate([guest1, guest3]): guest.eth0.ip_add(ipaddress(net_addr_1 + "." + str(i + 1) + "/24")) guest.eth0.ip_add( ipaddress(net_addr6_1 + "::" + str(i + 1) + "/64")) for i, guest in enumerate([guest2, guest4]): guest.eth0.ip_add(ipaddress(net_addr_2 + "." + str(i + 1) + "/24")) guest.eth0.ip_add( ipaddress(net_addr6_2 + "::" + str(i + 1) + "/64")) for host in [host1, host2]: for dev in [host.eth0, host.eth1, host.tap0, host.tap1, host.br0]: dev.up() for guest in [guest1, guest2, guest3, guest4]: guest.eth0.up() if "perf_tool_cpu" in self.params: logging.info("'perf_tool_cpu' param (%d) to be set to None" % self.params.perf_tool_cpu) self.params.perf_tool_cpu = None self.wait_tentative_ips(configuration.test_wide_devices) return configuration def generate_test_wide_description(self, config): host1, host2 = self.matched.host1, self.matched.host2 desc = super().generate_test_wide_description(config) desc += [ "\n".join([ "Configured {}.{}.ips = {}".format(dev.host.hostid, dev.name, dev.ips) for dev in config.test_wide_devices ]), "\n".join([ "Configured {}.{}.ports = {}".format(dev.host.hostid, dev.name, dev.ports) for dev in [host1.br0, host2.br0] ]), "\n".join([ "Configured {}.{}.bonds = {}".format(dev.host.hostid, dev.name, dev.bonds) for dev in [host1.br0, host2.br0] ]) ] return desc def test_wide_deconfiguration(self, config): del config.test_wide_devices super().test_wide_deconfiguration(config) def generate_ping_endpoints(self, config): guest1, guest2, guest3, guest4 = (self.matched.guest1, self.matched.guest2, self.matched.guest3, self.matched.guest4) dev_combinations = product([guest1.eth0, guest2.eth0], [guest3.eth0, guest4.eth0]) return [ PingEndpoints(comb[0], comb[1], reachable=((comb[0].host, comb[1].host) in [(guest1, guest3), (guest2, guest4)])) for comb in dev_combinations ] def generate_perf_endpoints(self, config): return [(self.matched.guest1.eth0, self.matched.guest3.eth0)] @property def offload_nics(self): host1, host2, guest1, guest2, guest3, guest4 = (self.matched.host1, self.matched.host2, self.matched.guest1, self.matched.guest2, self.matched.guest3, self.matched.guest4) result = [] for machine in host1, host2, guest1, guest2, guest3, guest4: result.append(machine.eth0) result.extend([host1.eth1, host2.eth1]) return result @property def mtu_hw_config_dev_list(self): host1, host2, guest1, guest2, guest3, guest4 = (self.matched.host1, self.matched.host2, self.matched.guest1, self.matched.guest2, self.matched.guest3, self.matched.guest4) result = [] for host in [host1, host2]: for dev in [host.eth0, host.eth1, host.tap0, host.tap1, host.br0]: result.append(dev) for guest in [guest1, guest2, guest3, guest4]: result.append(guest.eth0) return result @property def dev_interrupt_hw_config_dev_list(self): return [ self.matched.host1.eth0, self.matched.host1.eth1, self.matched.host2.eth0, self.matched.host2.eth1 ] @property def parallel_stream_qdisc_hw_config_dev_list(self): return [ self.matched.host1.eth0, self.matched.host1.eth1, self.matched.host2.eth0, self.matched.host2.eth1 ]
class BaseEnrtRecipe(SctpFirewallPerfTestMixin, BaseSubConfigMixin, PingTestAndEvaluate, PerfRecipe): """Base Recipe class for the ENRT recipe package This class defines the shared *test* method defining the common test procedure in a very generic way. This common test procedure involves a single main *test_wide* configuration that is different for every specific scenario. After the main configuration there is usually a loop of several minor *sub* configrations types that can take different values to slightly change the tested use cases. Finally, for each combination of a **test_wide** + **sub** configuration we do a several ping connection test and several performance measurement tests. **test_wide** and **sub** configurations are implemented with **context manager** methods which ensure that if any exceptions are raised (for example because of a bug in the recipe) that deconfiguration is called. Both **test_wide** and **sub** configurations are to be implemented in different classes, the BaseEnrtRecipe class only defines the common API and the base versions of the relevant methods. Test wide configuration is implemented via the following methods: * :any:`test_wide_configuration` * :any:`test_wide_deconfiguration` * :any:`generate_test_wide_description` Sub configurations are **mixed into** classes defining the specific scenario that is being tested. Various sub configurations are implemented as individual Python **Mixin** classes in the :any:`ConfigMixins<config_mixins>` package. These make use of Pythons collaborative inheritance by calling the `super` function in a specific way. The "machinery" for that is defined in the :any:`BaseSubConfigMixin` class. It is then used in this class from the `test` method loop. :param driver: The driver parameter is used to modify the hw network requirements, specifically to request Devices using the specified driver. This is common enough in the Enrt recipes that it can be part of the Base class. :type driver: :any:`StrParam` (default "ixgbe") :param ip_versions: Parameter that determines which IP protocol versions will be tested. :type ip_versions: Tuple[Str] (default ("ipv4", "ipv6")) :param ping_parallel: Parameter used by the :any:`generate_ping_configurations` generator. Tells the generator method to create :any:`PingConf` objects that will be run in parallel. :type ping_parallel: :any:`BoolParam` (default False) :param ping_bidirect: Parameter used by the :any:`generate_ping_configurations` generator. Tells the generator method to create :any:`PingConf` objects for both directions between the ping endpoints. :type ping_bidirect: :any:`BoolParam` (default False) :param ping_count: Parameter used by the :any:`generate_ping_configurations` generator. Tells the generator how many pings should be sent for each ping test. :type ping_count: :any:`IntParam` (default 100) :param ping_interval: Parameter used by the :any:`generate_ping_configurations` generator. Tells the generator how fast should the pings be sent in each ping test. :type ping_interval: :any:`FloatParam` (default 0.2) :param ping_psize: Parameter used by the :any:`generate_ping_configurations` generator. Tells the generator how big should the pings packets be in each ping test. :type ping_psize: :any:`IntParam` (default None) :param perf_tests: Parameter used by the :any:`generate_flow_combinations` generator. Tells the generator what types of network flow measurements to generate perf test configurations for. :type perf_tests: Tuple[str] (default ("tcp_stream", "udp_stream", "sctp_stream")) :param perf_tool_cpu: Parameter used by the :any:`generate_flow_combinations` generator. To indicate that the flow measurement should be pinned to a specific CPU core. :type perf_tool_cpu: :any:`IntParam` (optional parameter) :param perf_duration: Parameter used by the :any:`generate_perf_configurations` generator. To specify the duration of the performance measurements, in seconds. :type perf_duration: :any:`IntParam` (default 60) :param perf_iterations: Parameter used by the :any:`generate_perf_configurations` generator. To specify how many times should each performance measurement be repeated to generate cumulative results which can be statistically analyzed. :type perf_iterations: :any:`IntParam` (default 5) :param perf_parallel_streams: Parameter used by the :any:`generate_flow_combinations` generator. To specify how many parallel streams of the same network flow should be measured at the same time. :type perf_parallel_streams: :any:`IntParam` (default 1) :param perf_msg_sizes: Parameter used by the :any:`generate_flow_combinations` generator. To specify what different message sizes (in bytes) used generated for the network flow should be tested - each message size resulting in a separate performance measurement. :type perf_msg_sizes: List[Int] (default [123]) :param net_perf_tool: Parameter used by the :any:`generate_perf_configurations` generator to create a PerfRecipeConf object. Specifies a network flow measurement class that accepts :any:`PerfFlow` objects and can be used to measure those specified flows :type net_perf_tool: :any:`BaseFlowMeasurement` (default IperfFlowMeasurement) :param cpu_perf_tool: Parameter used by the :any:`generate_perf_configurations` generator to create a PerfRecipeConf object. Specifies a cpu measurement class that can be used to measure CPU utilization on specified hosts. :type cpu_perf_tool: :any:`BaseCPUMeasurement` (default StatCPUMeasurement) """ driver = StrParam(default="ixgbe") #common test parameters ip_versions = Param(default=("ipv4", "ipv6")) #common ping test params ping_parallel = BoolParam(default=False) ping_bidirect = BoolParam(default=False) ping_count = IntParam(default=100) ping_interval = FloatParam(default=0.2) ping_psize = IntParam(default=56) #common perf test params perf_tests = Param(default=("tcp_stream", "udp_stream", "sctp_stream")) perf_tool_cpu = IntParam(mandatory=False) perf_duration = IntParam(default=60) perf_iterations = IntParam(default=5) perf_parallel_streams = IntParam(default=1) perf_msg_sizes = ListParam(default=[123]) net_perf_tool = Param(default=IperfFlowMeasurement) cpu_perf_tool = Param(default=StatCPUMeasurement) def test(self): """Main test loop shared by all the Enrt recipes The test loop involves a single application of a **test_wide** configuration, then a loop over multiple **sub** configurations that involves: * creating the combined sub configuration of all available SubConfig Mixin classes via :any:`generate_sub_configurations` * applying the generated sub configuration via the :any:`_sub_context` context manager method * running tests * removing the current sub configuration via the :any:`_sub_context` context manager method """ with self._test_wide_context() as main_config: for sub_config in self.generate_sub_configurations(main_config): with self._sub_context(sub_config) as recipe_config: self.do_tests(recipe_config) @contextmanager def _test_wide_context(self): config = self.test_wide_configuration() self.describe_test_wide_configuration(config) try: yield config finally: self.test_wide_deconfiguration(config) def test_wide_configuration(self): """Creates an empty :any:`EnrtConfiguration` object This is again used in potential collaborative inheritance design that may potentially be useful for Enrt recipes. Derived classes will each individually add their own values to the instance created here. This way the complete test wide configuration is tracked in a single object. :return: returns a config object that tracks the applied configuration that can be used during testing to inspect the current state and make test decisions based on it. :rtype: :any:`EnrtConfiguration` Example:: class Derived: def test_wide_configuration(): config = super().test_wide_configuration() # ... configure something config.something = what_was_configured return config """ return EnrtConfiguration() def test_wide_deconfiguration(self, config): """Base deconfiguration method. In the base class this should maybe only check if there's any leftover configuration and warn about it. In derived classes this can be overriden to take care of deconfiguring what was configured in the respective test_wide_configuration method. Example:: class Derived: def test_wide_deconfiguration(config): # ... deconfigure something del config.something #cleanup tracking return super().test_wide_deconfiguration() """ #TODO check if anything is still applied and throw exception? return def describe_test_wide_configuration(self, config): """Describes the current test wide configuration Creates a new result object that contains the description of the full test wide configuration applied by all the :any:`test_wide_configuration` methods in the class hierarchy. The description needs to be generated by the :any:`generate_test_wide_description` method. Additionally the description contains the state of all the parameters and their values passed to the recipe class instance during initialization. """ description = self.generate_test_wide_description(config) self.add_result( True, "Summary of used Recipe parameters:\n{}".format( pprint.pformat(self.params._to_dict()))) self.add_result(True, "\n".join(description)) def generate_test_wide_description(self, config): """Generates the test wide configuration description Another class inteded to be used with the collaborative version of the `super` method to cumulatively desribe the full test wide configuration that was applied through multiple classes. The base class version of this method creates the initial list of strings containing just the header line. Each string added to this list will later be printed on its own line. :return: list of strings, each representing a single line :rtype: List[str] Example:: class Derived: def generate_sub_configuration_description(config): desc = super().generate_sub_configuration_description(config) desc.append("Configured something: {}".format(config.something)) return desc """ return [ "Testwide configuration for recipe {} description:".format( self.__class__.__name__) ] @contextmanager def _sub_context(self, config): self.apply_sub_configuration(config) self.describe_sub_configuration(config) try: yield config finally: self.remove_sub_configuration(config) def describe_sub_configuration(self, config): description = self.generate_sub_configuration_description(config) self.add_result(True, "\n".join(description)) def do_tests(self, recipe_config): """Entry point for actual tests The common scenario is to do ping and performance tests, however the method can be overriden to add more tests if needed. """ self.do_ping_tests(recipe_config) self.do_perf_tests(recipe_config) def do_ping_tests(self, recipe_config): """Ping testing loop Loops over all various ping configurations generated by the :any:`generate_ping_configurations` method, then uses the PingRecipe methods to execute, report and evaluate the results. """ for ping_configs in self.generate_ping_configurations(recipe_config): result = self.ping_test(ping_configs) self.ping_report_and_evaluate(result) def describe_perf_test_tweak(self, perf_config): description = self.generate_perf_test_tweak_description(perf_config) self.add_result(True, "\n".join(description)) def do_perf_tests(self, recipe_config): """Performance testing loop Loops over all various perf configurations generated by the :any:`generate_perf_configurations` method, then uses the PerfRecipe methods to execute, report and evaluate the results. """ for perf_config in self.generate_perf_configurations(recipe_config): self.apply_perf_test_tweak(perf_config) self.describe_perf_test_tweak(perf_config) try: result = self.perf_test(perf_config) self.perf_report_and_evaluate(result) finally: self.remove_perf_test_tweak(perf_config) def generate_ping_configurations(self, config): """Base ping test configuration generator The generator loops over all endpoint pairs to test ping between (generated by the :any:`generate_ping_endpoints` method) then over all the selected :any:`ip_versions` and finally over all the IP addresses that fit those criteria. :return: list of Ping configurations to test in parallel :rtype: List[:any:`PingConf`] """ for endpoints in self.generate_ping_endpoints(config): for ipv in self.params.ip_versions: if ipv == "ipv6" and not endpoints.reachable: continue ip_filter = {} if ipv == "ipv4": ip_filter.update(family=AF_INET) elif ipv == "ipv6": ip_filter.update(family=AF_INET6) ip_filter.update(is_link_local=False) endpoint1, endpoint2 = endpoints.endpoints endpoint1_ips = endpoint1.ips_filter(**ip_filter) endpoint2_ips = endpoint2.ips_filter(**ip_filter) if len(endpoint1_ips) != len(endpoint2_ips): raise LnstError( "Source/destination ip lists are of different size.") ping_conf_list = [] for src_addr, dst_addr in zip(endpoint1_ips, endpoint2_ips): pconf = PingConf( client=endpoint1.netns, client_bind=src_addr, destination=endpoint2.netns, destination_address=dst_addr, count=self.params.ping_count, interval=self.params.ping_interval, size=self.params.ping_psize, ) ping_evaluators = self.generate_ping_evaluators( pconf, endpoints) pconf.register_evaluators(ping_evaluators) ping_conf_list.append(pconf) if self.params.ping_bidirect: ping_conf_list.append(self._create_reverse_ping(pconf)) if not self.params.ping_parallel: break yield ping_conf_list def generate_ping_endpoints(self, config): """Generator for ping endpoints To be overriden by a derived class. :return: list of device pairs :rtype: List[Tuple[:any:`Device`, :any:`Device`]] """ return [] def generate_ping_evaluators(self, pconf, endpoints): return [RatePingEvaluator(min_rate=50)] def generate_perf_configurations(self, config): """Base perf test configuration generator The generator loops over all flow combinations to measure performance for (generated by the :any:`generate_flow_combinations` method). In addition to that during each flow combination measurement we add CPU utilization measurement to run on the background. Finally for each generated perf test configuration we register measurement evaluators based on the :any:`cpu_perf_evaluators` and :any:`net_perf_evaluators` properties. :return: list of Perf test configurations :rtype: List[:any:`PerfRecipeConf`] """ for flows in self.generate_flow_combinations(config): perf_recipe_conf = dict( recipe_config=config, flows=flows, ) flows_measurement = self.params.net_perf_tool( flows, perf_recipe_conf) cpu_measurement_hosts = set() for flow in flows: cpu_measurement_hosts.add(flow.generator) cpu_measurement_hosts.add(flow.receiver) cpu_measurement = self.params.cpu_perf_tool( cpu_measurement_hosts, perf_recipe_conf, ) perf_conf = PerfRecipeConf( measurements=[cpu_measurement, flows_measurement], iterations=self.params.perf_iterations, ) perf_conf.register_evaluators(cpu_measurement, self.cpu_perf_evaluators) perf_conf.register_evaluators(flows_measurement, self.net_perf_evaluators) yield perf_conf def generate_flow_combinations(self, config): """Base flow combination generator The generator loops over all endpoint pairs to test performance between (generated by the :any:`generate_perf_endpoints` method) then over all the selected :any:`ip_versions` and uses the first IP address fitting these criteria. Then the generator loops over the selected performance tests as selected via :any:`perf_tests`, then message sizes from :any:`msg_sizes`. :return: list of Flow combinations to measure in parallel :rtype: List[:any:`PerfFlow`] """ for client_nic, server_nic in self.generate_perf_endpoints(config): for ipv in self.params.ip_versions: ip_filter = {} if ipv == "ipv4": ip_filter.update(family=AF_INET) elif ipv == "ipv6": ip_filter.update(family=AF_INET6) ip_filter.update(is_link_local=False) client_bind = client_nic.ips_filter(**ip_filter)[0] server_bind = server_nic.ips_filter(**ip_filter)[0] for perf_test in self.params.perf_tests: for size in self.params.perf_msg_sizes: yield [ self._create_perf_flow( perf_test, client_nic, client_bind, server_nic, server_bind, size, ) ] def _create_perf_flow(self, perf_test, client_nic, client_bind, server_nic, server_bind, msg_size) -> PerfFlow: """ Wrapper to create a PerfFlow. Mixins that want to change this behavior (for example, to reverse the direction) can override this method as an alternative to overriding :any:`generate_flow_combinations` """ cpupin = self.params.perf_tool_cpu if "perf_tool_cpu" in self.params else None return PerfFlow( type=perf_test, generator=client_nic.netns, generator_bind=client_bind, generator_nic=client_nic, receiver=server_nic.netns, receiver_bind=server_bind, receiver_nic=server_nic, msg_size=msg_size, duration=self.params.perf_duration, parallel_streams=self.params.perf_parallel_streams, cpupin=cpupin, ) def generate_perf_endpoints(self, config): """Generator for perf endpoints To be overriden by a derived class. :return: list of device pairs :rtype: List[Tuple[:any:`Device`, :any:`Device`]] """ return [] @property def cpu_perf_evaluators(self): """CPU measurement evaluators To be overriden by a derived class. Returns the list of evaluators to use for CPU utilization measurement evaluation. :return: a list of cpu evaluator objects :rtype: List[BaseEvaluator] """ return [] @property def net_perf_evaluators(self): """Network flow measurement evaluators To be overriden bby a derived class. Returns the list of evaluators to use for Network flow measurement evaluation. :return: a list of flow evaluator objects :rtype: List[BaseEvaluator] """ return [NonzeroFlowEvaluator()] def wait_tentative_ips(self, devices): def condition(): return all( [not ip.is_tentative for dev in devices for ip in dev.ips]) self.ctl.wait_for_condition(condition, timeout=5) def _create_reverse_ping(self, pconf): return PingConf( client=pconf.destination, client_bind=pconf.destination_address, destination=pconf.client, destination_address=pconf.client_bind, count=pconf.ping_count, interval=pconf.ping_interval, size=pconf.ping_psize, )
class IperfMeasurementGenerator(BaseMeasurementGenerator): """ :param perf_tests: Parameter used by the :any:`generate_flow_combinations` generator. Tells the generator what types of network flow measurements to generate perf test configurations for. :type perf_tests: Tuple[str] (default ("tcp_stream", "udp_stream", "sctp_stream")) :param perf_tool_cpu: Parameter used by the :any:`generate_flow_combinations` generator. To indicate that the flow measurement should be pinned to a specific CPU core. :type perf_tool_cpu: :any:`IntParam` (optional parameter) :param perf_duration: Parameter used by the :any:`generate_perf_configurations` generator. To specify the duration of the performance measurements, in seconds. :type perf_duration: :any:`IntParam` (default 60) :param perf_iterations: Parameter used by the :any:`generate_perf_configurations` generator. To specify how many times should each performance measurement be repeated to generate cumulative results which can be statistically analyzed. :type perf_iterations: :any:`IntParam` (default 5) :param perf_parallel_streams: Parameter used by the :any:`generate_flow_combinations` generator. To specify how many parallel streams of the same network flow should be measured at the same time. :type perf_parallel_streams: :any:`IntParam` (default 1) :param perf_parallel_processes: Parameter used by the :any:`generate_flow_combinations` generator. To specify how many parallel net_perf_tool processes of the same network flow should be measured at the same time. :type perf_parallel_processes: :any:`IntParam` (default 1) :param perf_msg_sizes: Parameter used by the :any:`generate_flow_combinations` generator. To specify what different message sizes (in bytes) used generated for the network flow should be tested - each message size resulting in a separate performance measurement. :type perf_msg_sizes: List[Int] (default [123]) """ # common perf test params perf_tests = Param(default=("tcp_stream", "udp_stream", "sctp_stream")) perf_tool_cpu = ListParam(mandatory=False) perf_tool_cpu_policy = StrParam(mandatory=False) perf_duration = IntParam(default=60) perf_iterations = IntParam(default=5) perf_parallel_streams = IntParam(default=1) perf_parallel_processes = IntParam(default=1) perf_msg_sizes = ListParam(default=[123]) net_perf_tool = Param(default=IperfFlowMeasurement) def generate_perf_measurements_combinations(self, config): combinations = super().generate_perf_measurements_combinations(config) for flow_combination in self.generate_flow_combinations(config): combinations.append([self.params.net_perf_tool(flow_combination)]) return combinations def generate_flow_combinations(self, config): """Base flow combination generator The generator loops over all endpoint pairs to test performance between (generated by the :any:`generate_perf_endpoints` method) then over all the selected :any:`ip_versions` and uses the first IP address fitting these criteria. Then the generator loops over the selected performance tests as selected via :any:`perf_tests`, then message sizes from :any:`msg_sizes`. :return: list of Flow combinations to measure in parallel :rtype: List[:any:`PerfFlow`] """ for client_nic, server_nic in self.generate_perf_endpoints(config): for ipv in self.params.ip_versions: ip_filter = {} if ipv == "ipv4": ip_filter.update(family=AF_INET) elif ipv == "ipv6": ip_filter.update(family=AF_INET6) ip_filter.update(is_link_local=False) client_bind = client_nic.ips_filter(**ip_filter)[0] server_bind = server_nic.ips_filter(**ip_filter)[0] for perf_test in self.params.perf_tests: for size in self.params.perf_msg_sizes: yield self._create_perf_flows( perf_test, client_nic, client_bind, server_nic, server_bind, size, ) def generate_perf_endpoints(self, config): """Generator for perf endpoints To be overriden by a derived class. :return: list of device pairs :rtype: List[Tuple[:any:`Device`, :any:`Device`]] """ return [] def _create_perf_flows( self, perf_test, client_nic, client_bind, server_nic, server_bind, msg_size, ) -> List[PerfFlow]: flows = [] port_offset=12000 for i in range(self.params.perf_parallel_processes): flows.append( self._create_perf_flow( perf_test, client_nic, client_bind, server_nic, server_bind, port_offset + i, msg_size, self._cpupin_based_on_policy(i), ) ) return flows def _cpupin_based_on_policy(self, process_no=None): if process_no is None: return None try: cpus = self.params.perf_tool_cpu except: return None try: policy = self.params.perf_tool_cpu_policy except: return cpus if policy == 'round-robin': return [cpus[process_no % len(cpus)]] elif policy == 'all': return cpus else: raise Exception(f'Unknown perf_tool_cpu_policy {policy}') def _create_perf_flow( self, perf_test, client_nic, client_bind, server_nic, server_bind, server_port, msg_size, cpupin, ) -> PerfFlow: """ Wrapper to create a PerfFlow. Mixins that want to change this behavior (for example, to reverse the direction) can override this method as an alternative to overriding :any:`generate_flow_combinations` """ return PerfFlow( type=perf_test, generator=client_nic.netns, generator_bind=client_bind, generator_nic=client_nic, receiver=server_nic.netns, receiver_bind=server_bind, receiver_nic=server_nic, receiver_port=server_port, msg_size=msg_size, duration=self.params.perf_duration, parallel_streams=self.params.perf_parallel_streams, cpupin=cpupin, )
class OvSDPDKPvPRecipe(BasePvPRecipe): m1 = HostReq() m1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) m1.eth1 = DeviceReq(label="net1", driver=RecipeParam("driver")) m2 = HostReq(with_guest="yes") m2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) m2.eth1 = DeviceReq(label="net1", driver=RecipeParam("driver")) guest_dpdk_cores = StrParam(mandatory=True) guest_testpmd_cores = StrParam(mandatory=True) host2_pmd_cores = StrParam(mandatory=True) host2_l_cores = StrParam(mandatory=True) socket_mem = IntParam(default=2048) cpu_perf_tool = Param(default=StatCPUMeasurement) perf_duration = IntParam(default=60) perf_iterations = IntParam(default=5) perf_msg_size = IntParam(default=64) #doesn't do anything for now... perf_streams = IntParam(default=1) def test(self): self.check_dependencies() ping_config = self.gen_ping_config() self.warmup(ping_config) config = OVSPvPTestConf() self.pvp_test(config) def check_dependencies(self): pass def gen_ping_config(self): return [(self.matched.m1, self.matched.m1.eth0, self.matched.m2.eth0), (self.matched.m1, self.matched.m1.eth1, self.matched.m2.eth1), (self.matched.m2, self.matched.m2.eth0, self.matched.m1.eth0), (self.matched.m2, self.matched.m2.eth1, self.matched.m2.eth1)] def test_wide_configuration(self, config): config.generator.host = self.matched.m1 config.generator.nics.append(self.matched.m1.eth0) config.generator.nics.append(self.matched.m1.eth1) self.matched.m1.eth0.ip_add(ipaddress("192.168.1.1/24")) self.matched.m1.eth1.ip_add(ipaddress("192.168.1.3/24")) self.base_dpdk_configuration(config.generator) config.dut.host = self.matched.m2 config.dut.nics.append(self.matched.m2.eth0) config.dut.nics.append(self.matched.m2.eth1) self.matched.m2.eth0.ip_add(ipaddress("192.168.1.2/24")) self.matched.m2.eth1.ip_add(ipaddress("192.168.1.4/24")) self.base_dpdk_configuration(config.dut) self.ovs_dpdk_bridge_configuration(config.dut) self.init_guest_virtctl(config.dut, config.guest) self.shutdown_guest(config.guest) self.configure_guest_xml(config.dut, config.guest) self.ovs_dpdk_bridge_vm_configuration(config.dut, config.guest) self.ovs_dpdk_bridge_flow_configuration(config.dut) guest = self.create_guest(config.dut, config.guest) self.guest_vfio_modprobe(config.guest) self.base_dpdk_configuration(config.guest) config.guest.testpmd = guest.run(TestPMD( coremask=self.params.guest_testpmd_cores, pmd_coremask=self.params.guest_dpdk_cores, nics=[nic.bus_info for nic in config.guest.nics], peer_macs=[nic.hwaddr for nic in config.generator.nics]), bg=True) time.sleep(5) return config def generate_perf_config(self, config): flows = [] for src_nic, dst_nic in zip(config.generator.nics, config.dut.nics): src_bind = dict(mac_addr=src_nic.hwaddr, pci_addr=src_nic.bus_info, ip_addr=src_nic.ips[0]) dst_bind = dict(mac_addr=dst_nic.hwaddr, pci_addr=dst_nic.bus_info, ip_addr=dst_nic.ips[0]) flows.append( PerfFlow(type="pvp_loop_rate", generator=config.generator.host, generator_bind=src_bind, receiver=config.dut.host, receiver_bind=dst_bind, msg_size=self.params.perf_msg_size, duration=self.params.perf_duration, parallel_streams=self.params.perf_streams, cpupin=None)) return PerfRecipeConf(measurements=[ self.params.cpu_perf_tool( [config.generator.host, config.dut.host, config.guest.host]), TRexFlowMeasurement(flows, self.params.trex_dir) ], iterations=self.params.perf_iterations) def test_wide_deconfiguration(self, config): try: self.guest_deconfigure(config.guest) except: log_exc_traceback() try: config.dut.host.run("ovs-ofctl del-flows br0") for vm_port, port_id in config.dut.vm_ports: config.dut.host.run( "ovs-vsctl del-port br0 {}".format(vm_port)) for dpdk_port, port_id in config.dut.dpdk_ports: config.dut.host.run( "ovs-vsctl del-port br0 {}".format(dpdk_port)) config.dut.host.run("ovs-vsctl del-br br0") config.dut.host.run("service openvswitch restart") self.base_dpdk_deconfiguration(config.dut, ["openvswitch"]) except: log_exc_traceback() try: # returning the guest to the original running state self.shutdown_guest(config.guest) config.guest.virtctl.vm_start(config.guest.name) except: log_exc_traceback() try: for nic in config.generator.nics: config.generator.host.run("driverctl unset-override {}".format( nic.bus_info)) config.generator.host.run("service irqbalance start") except: log_exc_traceback() def ovs_dpdk_bridge_configuration(self, host_conf): host = host_conf.host host.run("systemctl enable openvswitch") host.run("systemctl start openvswitch") host.run( "ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-init=true" ) host.run( "ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-socket-mem={}" .format(self.params.socket_mem)) host.run( "ovs-vsctl --no-wait set Open_vSwitch . other_config:pmd-cpu-mask={}" .format(self.params.host2_pmd_cores)) host.run( "ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-lcore-mask={}" .format(self.params.host2_l_cores)) host.run("systemctl restart openvswitch") # TODO use an actual OvS Device object # TODO config.dut.nics.append(CachedRemoteDevice(m2.ovs)) host.run("ovs-vsctl add-br br0 -- set bridge br0 datapath_type=netdev") host_conf.dpdk_ports = [] for i, nic in enumerate(host_conf.nics): host.run("ovs-vsctl add-port br0 dpdk{i} -- " "set interface dpdk{i} type=dpdk ofport_request=1{i} " "options:dpdk-devargs={pci_addr}".format( i=i, pci_addr=nic.bus_info)) host_conf.dpdk_ports.append(("dpdk{}".format(i), "1{}".format(i))) def configure_guest_xml(self, host_conf, guest_conf): # Initialize guest XML guest_xml = self.init_guest_xml(guest_conf) guest_conf.virtio_devs = [] for i, nic in enumerate(host_conf.nics): path = self._xml_add_vhostuser_dev(guest_xml, "vhost_nic{i}".format(i=i), nic.hwaddr) virtio_dev = VirtioDevice(VirtioType.VHOST_USER, str(nic.hwaddr), config={"path": path}) guest_conf.virtio_devs.append(virtio_dev) cpu = guest_xml.find("cpu") numa = ET.SubElement(cpu, 'numa') ET.SubElement(numa, 'cell', id='0', cpus='0', memory=str(self.params.guest_mem_size), unit='KiB', memAccess='shared') memoryBacking = ET.SubElement(guest_xml, "memoryBacking") hugepages = ET.SubElement(memoryBacking, "hugepages") ET.SubElement(hugepages, "page", size="2", unit="M", nodeset="0") return guest_xml def ovs_dpdk_bridge_vm_configuration(self, host_conf, guest_conf): host = host_conf.host host_conf.vm_ports = [] for i, vhuser_nic in enumerate(guest_conf.virtio_devs): host.run("ovs-vsctl add-port br0 guest_nic{i} -- " "set interface guest_nic{i} type=dpdkvhostuserclient " "ofport_request=2{i} " "options:vhost-server-path={path}".format( i=i, path=vhuser_nic.config.get("path"))) host_conf.vm_ports.append( ("guest_nic{}".format(i), "2{}".format(i))) def ovs_dpdk_bridge_flow_configuration(self, host_conf): host = host_conf.host host.run("ovs-ofctl del-flows br0") for dpdk_port, vm_port in zip(host_conf.dpdk_ports, host_conf.vm_ports): host.run("ovs-ofctl add-flow br0 in_port={},action={}".format( dpdk_port[1], vm_port[1])) host.run("ovs-ofctl add-flow br0 in_port={},action={}".format( vm_port[1], dpdk_port[1])) def guest_vfio_modprobe(self, guest_conf): guest = guest_conf.host guest.run("modprobe -r vfio_iommu_type1") guest.run("modprobe -r vfio") guest.run("modprobe vfio enable_unsafe_noiommu_mode=1") guest.run("modprobe vfio-pci") def guest_deconfigure(self, guest_conf): guest = guest_conf.host if not guest: return testpmd = guest_conf.testpmd if testpmd: testpmd.kill(signal.SIGINT) testpmd.wait() self.base_dpdk_deconfiguration(guest_conf) def _xml_add_vhostuser_dev(self, guest_xml, name, mac_addr): vhost_server_path = "/tmp/{}".format(name) devices = guest_xml.find("devices") interface = ET.SubElement(devices, 'interface', type='vhostuser') ET.SubElement(interface, 'mac', address=str(mac_addr)) ET.SubElement(interface, 'model', type='virtio') ET.SubElement(interface, 'source', type='unix', path=vhost_server_path, mode='server') return vhost_server_path
class BasePvPRecipe(PingTestAndEvaluate, PerfRecipe): """ Base PvP Recipe: TODO: Describe stages and configurations """ driver = StrParam(mandatory=True) trex_dir = StrParam(mandatory=True) """ Guest configuration parameters """ guest_name = StrParam(mandatory=True) guest_cpus = StrParam(mandatory=True) guest_emulatorpin_cpu = StrParam(mandatory=True) guest_mem_size = IntParam(default=16777216) """ Packet generator """ """ Perf tool configuration parameters """ cpu_perf_tool = Param(default=StatCPUMeasurement) perf_duration = IntParam(default=60) perf_iterations = IntParam(default=5) perf_msg_size = IntParam(default=64) perf_parallel_streams = IntParam(default=1) nr_hugepages = IntParam(default=13000) # TODO: Allow 1G hugepages as well def warmup(self, ping_config): """ Generate warmup pings This ensures any in-between switches learn the corresponding MAC addresses Args: ping_config: array of tuples containing [OriginHost, OriginDevice, DestDevice]. """ try: self.warmup_configuration(ping_config) self.warmup_pings(ping_config) finally: self.warmup_deconfiguration(ping_config) def warmup_configuration(self, ping_config): if len(ping_config) > 255: raise LnstError("Too many warmup elements.") for i, elem in enumerate(ping_config): orig = elem[1] dest = elem[2] orig.ip_add(ipaddress('192.168.{}.1/24'.format(i))) dest.ip_add(ipaddress('192.168.{}.2/24'.format(i))) orig.up() dest.up() def warmup_pings(self, ping_config): jobs = [] for i, elem in enumerate(ping_config): host = elem[0] orig = elem[1] dest = elem[2] jobs.append(host.run(Ping(interface=orig.ips[0], dst=dest.ips[0]))) for job in jobs: job.wait() # TODO eval def warmup_deconfiguration(self, ping_config): for i, elem in enumerate(ping_config): orig = elem[1] dest = elem[2] orig.ip_flush() dest.ip_flush() def base_dpdk_configuration(self, dpdk_host_cfg): """ Base DPDK configuration in a host Args: dpdk_host_cfg: An instance of BaseHostConf """ host = dpdk_host_cfg.host for nic in dpdk_host_cfg.nics: nic.enable_readonly_cache() # TODO service should be a host method host.run("service irqbalance stop") # This will pin all irqs to cpu #0 self._pin_irqs(host, 0) host.run("echo -n {} /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages" .format(self.params.nr_hugepages)) host.run("modprobe vfio-pci") for nic in dpdk_host_cfg.nics: host.run("driverctl set-override {} vfio-pci".format(nic.bus_info)) def base_dpdk_deconfiguration(self, dpdk_host_cfg, service_list=[]): """ Undo Base DPDK configuration in a host Args: dpdk_host_cfg: An instance of BaseHostConf service_list: list of services using dpdk that might stop driverctl from being able to unset-override the host's interfaces. They will get restarted. """ host = dpdk_host_cfg.host # TODO service should be a host method host.run("service irqbalance start") for nic in dpdk_host_cfg.nics: job = host.run("driverctl unset-override {}".format(nic.bus_info), bg=True) for service in service_list: host.run("systemctl restart {}". format(service)) if not job.wait(10): job.kill() """ Guest Management """ def init_guest_virtctl(self, host_conf, guest_conf): """ Initialize Libvirt Control Args: host_conf: An instance of BaseHostConf with the host info guest_conf: An instance of BaseGuestConf with the guest info """ host = host_conf.host guest_conf.name = self.params.guest_name guest_conf.virtctl = host.init_class(LibvirtControl) def shutdown_guest(self, guest_conf): """ Shutdown a guest Args: guest_conf: An instance of BaseGuestConf with the guest info """ virtctl = guest_conf.virtctl if virtctl: virtctl.vm_shutdown(guest_conf.name) self.ctl.wait_for_condition(lambda: not virtctl.is_vm_running(guest_conf.name)) def init_guest_xml(self, guest_conf): """ Initialize the guest XML configuration with some basic values Args: guest_conf: An instance of BaseGuestConf with the guest info """ virtctl = guest_conf.virtctl guest_xml = ET.fromstring(virtctl.vm_XMLDesc(guest_conf.name)) guest_conf.libvirt_xml = guest_xml cputune = ET.SubElement(guest_xml, "cputune") for i, cpu_id in enumerate(self.params.guest_cpus.split(',')): ET.SubElement(cputune, "vcpupin", vcpu=str(i), cpuset=str(cpu_id)) ET.SubElement(cputune, "emulatorpin", cpuset=str(self.params.guest_emulatorpin_cpu)) return guest_xml def create_guest(self, host_conf, guest_conf): """ Create a guest Args: host_conf: The host_conf (instance of BaseHostConf) guest_conf: The host_conf (instance of BaseGuestConf) """ host = host_conf.host virtctl = guest_conf.virtctl guest_xml = guest_conf.libvirt_xml str_xml = ET.tostring(guest_xml, encoding='utf8', method='xml') virtctl.createXML(str_xml.decode('utf8')) guest_ip_job = host.run("gethostip -d {}".format(guest_conf.name)) guest_ip = guest_ip_job.stdout.strip() if not guest_ip: raise LnstError("Could not determine guest's IP address") guest = self.ctl.connect_host(guest_ip, timeout=60, machine_id="guest1") guest_conf.host = guest for i, vnic in enumerate(guest_conf.virtio_devs): if not vnic.hwaddr: raise LnstError("Virtio NIC HW Address not configured") guest.map_device("eth{}".format(i), dict(hwaddr=vnic.hwaddr)) device = getattr(guest, "eth{}".format(i)) guest_conf.nics.append(device) return guest def pvp_test(self, config): """ Perform the PvP test Args: config: An instance of BasePvPTestConf """ try: self.test_wide_configuration(config) perf_config = self.generate_perf_config(config) result = self.perf_test(perf_config) self.perf_report_and_evaluate(result) finally: self.test_wide_deconfiguration(config) def _pin_irqs(self, host, cpu): mask = 1 << cpu host.run("MASK={:x}; " "for i in `ls -d /proc/irq/[0-9]*` ; " "do echo $MASK > ${{i}}/smp_affinity ; " "done".format(mask)) """ Methods to be overridden """ def generate_perf_config(self, config): """ Generate the perf configuration Args: config: The global test configuration Returns: An instance of Perf.Recipe.RecipeConf """ pass def test_wide_deconfiguration(self, config): pass def test_wide_configuration(self, config): pass @property def cpu_perf_evaluators(self): """CPU measurement evaluators To be overriden by a derived class. Returns the list of evaluators to use for CPU utilization measurement evaluation. :return: a list of cpu evaluator objects :rtype: List[BaseEvaluator] """ return [] @property def net_perf_evaluators(self): """Network flow measurement evaluators To be overriden bby a derived class. Returns the list of evaluators to use for Network flow measurement evaluation. :return: a list of flow evaluator objects :rtype: List[BaseEvaluator] """ return [NonzeroFlowEvaluator()] def apply_perf_test_tweak(self, config): pass def describe_perf_test_tweak(self, config): pass def remove_perf_test_tweak(self, config): pass
class IperfClient(IperfBase): server = HostnameOrIpParam(mandatory=True) duration = IntParam(default=10) udp = BoolParam(default=False) sctp = BoolParam(default=False) port = IntParam() blksize = IntParam() mss = IntParam() cpu_bind = IntParam() parallel = IntParam() opts = StrParam() _role = "client" def __init__(self, **kwargs): super(IperfClient, self).__init__(**kwargs) if self.params.udp and self.params.sctp: raise TestModuleError( "Parameters udp and sctp are mutually exclusive!") def runtime_estimate(self): _duration_overhead = 5 return self.params.duration + _duration_overhead def _compose_cmd(self): port = "" if "port" in self.params: port = "-p {:d}".format(self.params.port) if "blksize" in self.params: blksize = "-l {:d}".format(self.params.blksize) else: blksize = "" if "mss" in self.params: mss = "-M {:d}".format(self.params.mss) else: mss = "" if "cpu_bind" in self.params: cpu = "-A {:d}".format(self.params.cpu_bind) else: cpu = "" if "parallel" in self.params: parallel = "-P {:d}".format(self.params.parallel) else: parallel = "" if self.params.udp: test = "--udp" elif self.params.sctp: test = "--sctp" elif self.params.mptcp: test = "--multipath" else: test = "" cmd = ("iperf3 -c {server} -b 0/1000 -J -t {duration}" " {cpu} {test} {mss} {blksize} {parallel} {port}" " {opts}".format( server=self.params.server, duration=self.params.duration, cpu=cpu, test=test, mss=mss, blksize=blksize, parallel=parallel, port=port, opts=self.params.opts if "opts" in self.params else "")) return cmd
class Netperf(BaseTestModule): _nonomni_tests = ["SCTP_STREAM", "SCTP_STREAM_MANY", "SCTP_RR"] _omni_tests = ["TCP_STREAM", "TCP_RR", "UDP_STREAM", "UDP_RR"] _supported_tests = _nonomni_tests + _omni_tests server = IpParam(mandatory=True) testname = StrParam(mandatory=True) duration = IntParam(mandatory=True) bind = IpParam() port = IntParam() testoptions = StrParam() confidence = StrParam() cpu_util = StrParam() num_parallel = IntParam(default=1) runs = IntParam(default=1) debug = IntParam(default=0) opts = StrParam() max_deviation = Param() threshold = Param() threshold_deviation = Param() threshold_interval = Param() def __init__(self, **kwargs): super(Netperf, self).__init__(**kwargs) if self.params.testname not in self._supported_tests: supported_tests = ', '.join(self._supported_tests) logging.warning( "Only %s tests are now officialy supported " "by LNST. You can use other tests, but test result may not " "be correct." % supported_tests) if "confidence" in self.params: tmp = self.params.confidence.split(",") if tmp[0] not in ["99", "95"]: raise TestModuleError("Confidence level must be 95 or 99.") try: int(tmp[1]) except ValueError: raise TestModuleError( "Confidence interval must be an integer.") if "cpu_util" in self.params: if self.params.cpu_util not in ["both", "local", "remote"]: raise TestModuleError( "cpu_util can be 'both', 'local' or 'remote'") if "threshold_deviation" in self.params: self._check_threshold_param(self.params.threshold_deviation, "threshold_deviation") else: self.params.threshold_deviation = {"rate": 0.0, "unit": "bps"} if "threshold" in self.params: self._check_threshold_param(self.params.threshold, "threshold") rate = self.params.threshold["rate"] deviation = self.params.threshold_deviation["rate"] self.params.threshold_interval = (rate - deviation, rate + deviation) if "max_deviation" in self.params: if not isinstance(self.params.max_deviation, dict): raise TestModuleError( "max_deviation is expected to be dictionary") if 'type' not in self.params.max_deviation: raise TestModuleError( "max_deviation 'type' has to be specified ('percent' or 'absolute')" ) if self.params.max_deviation['type'] not in [ 'percent', 'absolute' ]: raise TestModuleError( "max_deviation 'type' can be 'percent' or 'absolute'") if self.params.max_deviation['type'] is 'percent': if 'value' not in self.params.max_deviation: raise TestModuleError( "max_deviation 'value' has to be specified") self.params.max_deviation['value'] = float( self.params.max_deviation['value']) if self.params.max_deviation['type'] is 'absolute': if not isinstance(self.params.max_deviation, dict): raise TestModuleError( "max_deviation 'value' is expected to be dictionary for 'absolute' type" ) self.params.max_deviation['value'] = self._parse_threshold( self.params.max_deviation['value'], "max_deviation 'value'") def _check_threshold_param(self, threshold, name): if not isinstance(threshold, dict): raise TestModuleError("%s is expected to be dictionary", name) if 'rate' not in threshold: raise TestModuleError("%s expects 'rate' key in dictionary", name) threshold['rate'] = float(threshold['rate']) if 'unit' not in threshold: raise TestModuleError("%s expects 'unit' key in dictionary", name) if self.params.testname in [ "TCP_STREAM", "UDP_STREAM", "SCTP_STREAM", "SCTP_STREAM_MANY" ]: if threshold['unit'] is not 'bps': raise TestModuleError("unit can be 'bps' for STREAMs") else: if threshold['unit'] is not ['tps']: raise TestModuleError("unit can be 'tps' for RRs") def _is_omni(self): return self.params.testname in self._omni_tests def _compose_cmd(self): """ composes commands for netperf and netserver based on xml recipe """ cmd = "netperf -H %s -f k" % self.params.server if self._is_omni(): # -P 0 disables banner header of output cmd += " -P 0" if "bind" in self.params: """ application is bound to this address """ cmd += " -L %s" % self.params.bind if "port" in self.params: """ client connects on this port """ cmd += " -p %s" % self.params.port if "duration" in self.params: """ test will last this duration """ cmd += " -l %s" % self.params.duration if "testname" in self.params: """ test that will be performed """ cmd += " -t %s" % self.params.testname if "confidence" in self.params and self.params.num_parallel <= 1: """ confidence level that Netperf should try to achieve """ cmd += " -I %s" % self.params.confidence if self.params.runs >= 3: cmd += " -i %d,%d" % (self.params.runs, self.params.runs) self.params.runs = 1 if "cpu_util" in self.params: if self.params.cpu_util.lower() == "both": cmd += " -c -C" elif self.params.cpu_util.lower() == "local": cmd += " -c" elif self.params.cpu_util.lower() == "remote": cmd += " -C" if self.params.debug > 0: cmd += " -%s" % ('d' * self.params.debug) if "netperf_opts" in self.params: """ custom options for netperf """ cmd += " %s" % self.params.netperf_opts if self.params.num_parallel > 1: """ wait 1 second before starting the data transfer taken from the super_netperf script, can be removed if it doesn't make sense """ cmd += " -s 1" # Print only relevant output if self._is_omni(): cmd += ' -- -k "THROUGHPUT, LOCAL_CPU_UTIL, REMOTE_CPU_UTIL, CONFIDENCE_LEVEL, THROUGHPUT_CONFID"' if "testoptions" in self.params: if self._is_omni(): cmd += " %s" % self.params.testoptions else: cmd += " -- %s" % self.params.testoptions return cmd def _parse_output(self, output): res_val = None if self._is_omni(): res_val = self._parse_omni_output(output) else: res_val = self._parse_non_omni_output(output) if "confidence" in self.params: confidence = self._parse_confidence(output) res_val["confidence"] = confidence return res_val def _parse_omni_output(self, output): res_val = {} pattern_throughput = "THROUGHPUT=(\d+\.\d+)" throughput = re.search(pattern_throughput, output) if throughput is None: rate_in_kb = 0.0 else: rate_in_kb = float(throughput.group(1)) res_val["rate"] = rate_in_kb * 1000 res_val["unit"] = "bps" if "cpu_util" in self.params: if self.params.cpu_util == "local" or self.params.cpu_util == "both": pattern_loc_cpu_util = "LOCAL_CPU_UTIL=([-]?\d+\.\d+)" loc_cpu_util = re.search(pattern_loc_cpu_util, output) res_val["LOCAL_CPU_UTIL"] = float(loc_cpu_util.group(1)) if self.params.cpu_util == "remote" or self.params.cpu_util == "both": pattern_rem_cpu_util = "REMOTE_CPU_UTIL=([-]?\d+\.\d+)" rem_cpu_util = re.search(pattern_rem_cpu_util, output) res_val["REMOTE_CPU_UTIL"] = float(rem_cpu_util.group(1)) return res_val def _parse_non_omni_output(self, output): res_val = {} # pattern for SCTP streams and other tests # decimal decimal decimal float (float) pattern = "\d+\s+\d+\s+\d+\s+\d+\.\d+\s+(\d+(?:\.\d+){0,1})" if "cpu_util" in self.params: # cpu utilization data in format: float float pattern += "\s+(\d+(?:\.\d+){0,1})\s+(\d+(?:\.\d+){0,1})" r2 = re.search(pattern, output.lower()) if r2 is None: rate_in_kb = 0.0 else: rate_in_kb = float(r2.group(1)) if "cpu_util" in self.params: res_val["LOCAL_CPU_UTIL"] = float(r2.group(2)) res_val["REMOTE_CPU_UTIL"] = float(r2.group(3)) res_val["rate"] = rate_in_kb * 1000 res_val["unit"] = "bps" return res_val def _parse_confidence(self, output): if self._is_omni(): return self._parse_confidence_omni(output) else: return self._parse_confidence_non_omni(output) def _parse_confidence_omni(self, output): pattern_throughput_confid = "THROUGHPUT_CONFID=([-]?\d+\.\d+)" pattern_confidence_level = "CONFIDENCE_LEVEL=(\d+)" throughput_confid = re.search(pattern_throughput_confid, output) confidence_level = re.search(pattern_confidence_level, output) if throughput_confid is not None and confidence_level is not None: throughput_confid = float(throughput_confid.group(1)) confidence_level = int(confidence_level.group(1)) real_confidence = (confidence_level, throughput_confid / 2) return real_confidence else: return (0, 0.0) def _parse_confidence_non_omni(self, output): normal_pattern = r'\+/-(\d+\.\d*)% @ (\d+)% conf\.' warning_pattern = r'!!! Confidence intervals: Throughput\s+: (\d+\.\d*)%' normal_confidence = re.search(normal_pattern, output) warning_confidence = re.search(warning_pattern, output) if normal_confidence is None: logging.error("Failed to parse confidence!!") return (0, 0.0) if warning_confidence is None: real_confidence = (float(normal_confidence.group(2)), float(normal_confidence.group(1))) else: real_confidence = (float(normal_confidence.group(2)), float(warning_confidence.group(1)) / 2) return real_confidence def _sum_results(self, first, second): result = {} #add rates if first["unit"] == second["unit"]: result["unit"] = first["unit"] result["rate"] = first["rate"] + second["rate"] # netperf measures the complete cpu utilization of the machine, # so both second and first should be +- the same number if "LOCAL_CPU_UTIL" in first and "LOCAL_CPU_UTIL" in second: result["LOCAL_CPU_UTIL"] = first["LOCAL_CPU_UTIL"] if "REMOTE_CPU_UTIL" in first and "REMOTE_CPU_UTIL" in second: result["REMOTE_CPU_UTIL"] = first["REMOTE_CPU_UTIL"] #ignoring confidence because it doesn't make sense to sum those return result def _pretty_rate(self, rate, unit=None): pretty_rate = {} if unit is None: if rate < 1000: pretty_rate["unit"] = "bits/sec" pretty_rate["rate"] = rate elif rate < 1000**2: pretty_rate["unit"] = "kbits/sec" pretty_rate["rate"] = rate / 1000 elif rate < 1000**3: pretty_rate["unit"] = "mbits/sec" pretty_rate["rate"] = rate / (1000**2) elif rate < 1000**4: pretty_rate["unit"] = "gbits/sec" pretty_rate["rate"] = rate / (1000**3) elif rate < 1000**5: pretty_rate["unit"] = "tbits/sec" pretty_rate["rate"] = rate / (1000**4) else: if unit == "bits/sec": pretty_rate["unit"] = "bits/sec" pretty_rate["rate"] = rate elif unit == "Kbits/sec": pretty_rate["unit"] = "Kbits/sec" pretty_rate["rate"] = rate / 1024 elif unit == "kbits/sec": pretty_rate["unit"] = "kbits/sec" pretty_rate["rate"] = rate / 1000 elif unit == "Mbits/sec": pretty_rate["unit"] = "Mbits/sec" pretty_rate["rate"] = rate / (1024**2) elif unit == "mbits/sec": pretty_rate["unit"] = "mbits/sec" pretty_rate["rate"] = rate / (1000**2) elif unit == "Gbits/sec": pretty_rate["unit"] = "Gbits/sec" pretty_rate["rate"] = rate / (1024**3) elif unit == "gbits/sec": pretty_rate["unit"] = "gbits/sec" pretty_rate["rate"] = rate / (1000**3) elif unit == "Tbits/sec": pretty_rate["unit"] = "Tbits/sec" pretty_rate["rate"] = rate / (1024**4) elif unit == "tbits/sec": pretty_rate["unit"] = "tbits/sec" pretty_rate["rate"] = rate / (1000**4) return pretty_rate def _run_client(self, cmd): logging.debug("running as client...") res_data = {} res_data["testname"] = self.params.testname rv = 0 results = [] rates = [] for i in range(1, self.params.runs + 1): if self.params.runs > 1: logging.info("Netperf starting run %d" % i) clients = [] client_results = [] for i in range(0, self.params.num_parallel): clients.append(ShellProcess(cmd)) for client in clients: ret_code = None try: ret_code = client.wait() rv += ret_code except OSError as e: if e.errno == errno.EINTR: client.kill() output = client.read_nonblocking() logging.debug(output) if ret_code is not None and ret_code == 0: client_results.append(self._parse_output(output)) if len(client_results) > 0: #accumulate all the parallel results into one result = client_results[0] for res in client_results[1:]: result = self._sum_results(result, res) results.append(result) rates.append(results[-1]["rate"]) if results > 1: res_data["results"] = results if len(rates) > 0: rate = sum(rates) / len(rates) else: rate = 0.0 if len(rates) > 1: # setting deviation to 2xstd_deviation because of the 68-95-99.7 # rule this seems comparable to the -I 99 netperf setting res_data["std_deviation"] = std_deviation(rates) rate_deviation = 2 * res_data["std_deviation"] elif len(rates) == 1 and "confidence" in self.params: result = results[0] rate_deviation = rate * (float(result["confidence"][1]) / 100) else: rate_deviation = 0.0 res_data["rate"] = rate res_data["rate_deviation"] = rate_deviation rate_pretty = self._pretty_rate(rate) rate_dev_pretty = self._pretty_rate(rate_deviation, unit=rate_pretty["unit"]) if rv != 0 and self.params.runs == 1: res_data["msg"] = "Could not get performance throughput!" logging.info(res_data["msg"]) return (False, res_data) elif rv != 0 and self.params.runs > 1: res_data["msg"] = "At least one of the Netperf runs failed, "\ "check the logs and result data for more "\ "information." logging.info(res_data["msg"]) return (False, res_data) res_val = False res_data["msg"] = "Measured rate was %.2f +-%.2f %s" %\ (rate_pretty["rate"], rate_dev_pretty["rate"], rate_pretty["unit"]) if rate > 0.0: res_val = True else: res_val = False return (res_val, res_data) if "max_deviation" in self.params: if self.params.max_deviation["type"] == "percent": percentual_deviation = (rate_deviation / rate) * 100 if percentual_deviation > self.params.max_deviation["value"]: res_val = False res_data["msg"] = "Measured rate %.2f +-%.2f %s has bigger "\ "deviation than allowed (+-%.2f %%)" %\ (rate_pretty["rate"], rate_dev_pretty["rate"], rate_pretty["unit"], self.params.max_deviation["value"]) return (res_val, res_data) elif self.params.max_deviation["type"] == "absolute": if rate_deviation > self.params.max_deviation["value"]["rate"]: pretty_deviation = self._pretty_rate( self.params.max_deviation["value"]["rate"]) res_val = False res_data["msg"] = "Measured rate %.2f +-%.2f %s has bigger "\ "deviation than allowed (+-%.2f %s)" %\ (rate_pretty["rate"], rate_dev_pretty["rate"], rate_pretty["unit"], pretty_deviation["rate"], pretty_deviation["unit"]) return (res_val, res_data) if "threshold_interval" in self.params: result_interval = (rate - rate_deviation, rate + rate_deviation) threshold_pretty = self._pretty_rate(self.params.threshold["rate"]) threshold_dev_pretty = self._pretty_rate( self.params.threshold_deviation["rate"], unit=threshold_pretty["unit"]) if self.params.threshold_interval[0] > result_interval[1]: res_val = False res_data["msg"] = "Measured rate %.2f +-%.2f %s is lower "\ "than threshold %.2f +-%.2f %s" %\ (rate_pretty["rate"], rate_dev_pretty["rate"], rate_pretty["unit"], threshold_pretty["rate"], threshold_dev_pretty["rate"], threshold_pretty["unit"]) return (res_val, res_data) else: res_val = True res_data["msg"] = "Measured rate %.2f +-%.2f %s is higher "\ "than threshold %.2f +-%.2f %s" %\ (rate_pretty["rate"], rate_dev_pretty["rate"], rate_pretty["unit"], threshold_pretty["rate"], threshold_dev_pretty["rate"], threshold_pretty["unit"]) return (res_val, res_data) return (res_val, res_data) def run(self): cmd = self._compose_cmd() logging.debug("compiled command: %s" % cmd) if not is_installed("netperf"): res_data = {} res_data["msg"] = "Netperf is not installed on this machine!" logging.error(res_data["msg"]) self._res_data = res_data return False (rv, res_data) = self._run_client(cmd) self._res_data = res_data if rv is False: return False return True
class NeperBase(BaseTestModule): _supported_workloads = set(['tcp_rr', 'tcp_crr', 'udp_rr']) workload = ChoiceParam(type=StrParam, choices=_supported_workloads, mandatory=True) port = IntParam() control_port = IntParam() cpu_bind = IntParam() num_flows = IntParam() num_threads = IntParam() test_length = IntParam() request_size = IntParam() response_size = IntParam() opts = StrParam() def __init__(self, **kwargs): self._samples_file = None super(NeperBase, self).__init__(**kwargs) def _parse_result(self, res: subprocess.CompletedProcess) -> Dict: data = {} for match in NEPER_OUT_RE.finditer(res.stdout): if match is not None: k, v = match.groups() if v == '': v = None data[k] = v return data def run(self): self._res_data = {} if not NEPER_PATH.joinpath(self.params.workload).exists(): self._res_data['msg'] = f"neper workload {self.params.workload}" \ f" is not installed on this machine!" logging.error(self._res_data['msg']) return False with tempfile.NamedTemporaryFile('r', prefix='neper-samples-', suffix='.csv', newline='') as sf: cmd = self._compose_cmd(sf.name) logging.debug(f"compiled command: {cmd}") logging.debug(f"running as {self._role}") self._res_data["start_time"] = time.time() res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True, close_fds=True, cwd=NEPER_PATH) self._res_data["stderr"] = res.stderr self._res_data["data"] = self._parse_result(res) if res.stderr != "": self._res_data[ "msg"] = f"errors reported by {self.params.workload}" logging.error(self._res_data["msg"]) logging.error(self._res_data["stderr"]) if res.returncode > 0: self._res_data["msg"] = "{} returncode = {}".format( self._role, res.returncode) logging.error(self._res_data["msg"]) return False self._res_data["samples"] = [r for r in csv.DictReader(sf)] return True def _compose_cmd(self, samples_path: str) -> str: cmd = [f"./{self.params.workload}", f"--all-samples={samples_path}"] if self._role == "client": cmd.append("-c") if "num_threads" in self.params: cmd.append(f"-T {self.params.num_threads}") if "num_flows" in self.params: cmd.append(f"-F {self.params.num_flows}") if "port" in self.params: cmd.append(f"-P {self.params.port}") if "control_port" in self.params: cmd.append(f"-C {self.params.control_port}") if "bind" in self.params: cmd.append(f"-H {self.params.bind}") if "server" in self.params: cmd.append(f"-H {self.params.server}") if "request_size" in self.params: cmd.append(f"-Q {self.params.request_size}") if "response_size" in self.params: cmd.append(f"-R {self.params.response_size}") if "test_length" in self.params: cmd.append(f"-l {self.params.test_length}") if "opts" in self.params: cmd.append(self.params.opts) if "cpu_bind" in self.params: cmd.insert(0, f"taskset -c {self.params.cpu_bind}") return " ".join(cmd)
class VhostNetPvPRecipe(BasePvPRecipe): generator_req = HostReq() generator_req.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) generator_req.eth1 = DeviceReq(label="net1", driver=RecipeParam("driver")) host_req = HostReq(with_guest="yes") host_req.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) host_req.eth1 = DeviceReq(label="net1", driver=RecipeParam("driver")) host1_dpdk_cores = StrParam(mandatory=True) vhost_cpus = StrParam( mandatory=True) # The CPUs used by vhost-net kernel threads # TODO: Study the possibility of adding more forwarding engines # like xdp or tc guest_fwd = StrParam(default='bridge') host_fwd = StrParam(default='bridge') guest_macs = Param(default=['02:fa:fe:fa:fe:01', '02:fa:fe:fa:fe:02']) generator_dpdk_cores = StrParam(mandatory=True) cpu_perf_tool = Param(default=StatCPUMeasurement) def test(self): self.check_params() self.warmup(self.gen_ping_config()) config = VhostPvPTestConf() self.pvp_test(config) def check_params(self): # Check emulatorpin range contains vhost cores emulator_min, emulator_max = self.params.guest_emulatorpin_cpu.split( '-') vhost_cpus = self.params.vhost_cpus.split(',') for vcpu in vhost_cpus: if vcpu > emulator_max or vcpu < emulator_min: raise ParamError("Emulator pin must contain vhost cpus") def gen_ping_config(self): return [(self.matched.generator_req, self.matched.generator_req.eth0, self.matched.host_req.eth0), (self.matched.generator_req, self.matched.generator_req.eth1, self.matched.host_req.eth1), (self.matched.host_req, self.matched.host_req.eth0, self.matched.generator_req.eth0), (self.matched.host_req, self.matched.host_req.eth1, self.matched.host_req.eth1)] def test_wide_configuration(self, config): config.generator.host = self.matched.generator_req config.generator.nics.append(self.matched.generator_req.eth0) config.generator.nics.append(self.matched.generator_req.eth1) self.matched.generator_req.eth0.ip_add(ipaddress("192.168.1.1/24")) self.matched.generator_req.eth1.ip_add(ipaddress("192.168.1.2/24")) self.matched.generator_req.eth0.up() self.matched.generator_req.eth1.up() self.base_dpdk_configuration(config.generator) config.dut.host = self.matched.host_req config.dut.nics.append(self.matched.host_req.eth0) config.dut.nics.append(self.matched.host_req.eth1) self.matched.host_req.eth0.up() self.matched.host_req.eth1.up() self.host_forwarding_configuration(config.dut) self.init_guest_virtctl(config.dut, config.guest) self.shutdown_guest(config.guest) self.configure_guest_xml(config.dut, config.guest) self.create_guest(config.dut, config.guest) self.guest_forwarding(config.guest) self.host_forwarding_vm_configuration(config.dut, config.guest) return config def generate_perf_config(self, config): flows = [] for i in range(0, min(len(config.generator.nics), len(config.guest.nics))): src_nic = config.generator.nics[i] src_ip = src_nic.ips[0] dst_nic = config.guest.nics[i] dst_ip = config.generator.nics[((i + 1) % len(config.generator.nics))].ips[0] src_bind = dict(mac_addr=src_nic.hwaddr, pci_addr=src_nic.bus_info, ip_addr=src_ip) dst_bind = dict(mac_addr=dst_nic.hwaddr, pci_addr=dst_nic.bus_info, ip_addr=dst_ip) flows.append( PerfFlow(type="pvp_loop_rate", generator=config.generator.host, generator_bind=src_bind, receiver=config.guest.host, receiver_bind=dst_bind, msg_size=self.params.perf_msg_size, duration=self.params.perf_duration, parallel_streams=self.params.perf_streams, cpupin=None)) return PerfRecipeConf( measurements=[ self.params.cpu_perf_tool([ config.generator.host, config.dut.host, config.guest.host ]), TRexFlowMeasurement( flows, self.params.trex_dir, self.params.host1_dpdk_cores.split(","), ), ], iterations=self.params.perf_iterations, ) def test_wide_deconfiguration(self, config): try: self.guest_deconfigure(config.guest) except: log_exc_traceback() try: self.host_forwarding_vm_deconfiguration(config.dut, config.guest) except: log_exc_traceback() try: self.host_forwarding_deconfiguration(config.dut) except: log_exc_traceback() try: self.base_dpdk_deconfiguration(config.generator) except: log_exc_traceback() try: #returning the guest to the original running state self.shutdown_guest(config.guest) if config.guest.virtctl: config.guest.virtctl.vm_start(config.guest.name) except: log_exc_traceback() try: config.generator.host.run("service irqbalance start") except: log_exc_traceback() def host_forwarding_vm_configuration(self, host_conf, guest_conf): """ VM - specific forwarding configuration Pin vhost-net kernel threads to the cpus specfied by vhost_cpus param """ # Get a comma separated list of the vhost-net kernel threads' PIDs vhost_pids = host_conf.host.run( """ ps --ppid 2 | grep "vhost-$(pidof qemu-kvm)" """ """ | awk '{if (length(pidstring) == 0) { """ """ pidstring=$1 """ """ } else { """ """ pidstring = sprintf("%s,%s", pidstring, $1) """ """ }}; """ """ END{ print pidstring }'""") for pid, cpu in zip(vhost_pids.stdout.strip().split(','), self.params.vhost_cpus.split(',')): mask = 1 << int(cpu) host_conf.host.run('taskset -p {:x} {}'.format(mask, pid)) def host_forwarding_vm_deconfiguration(self, host_conf, guest_conf): """ VM - specific forwarding deconfiguration """ pass def host_forwarding_configuration(self, host_conf): if (self.params.host_fwd == 'bridge'): host_conf.bridges = [] host_conf.host.br0 = BridgeDevice() host_conf.host.br1 = BridgeDevice() host_conf.host.br0.slave_add(host_conf.nics[0]) host_conf.host.br1.slave_add(host_conf.nics[1]) host_conf.host.br0.up() host_conf.host.br1.up() host_conf.bridges.append(host_conf.host.br0) host_conf.bridges.append(host_conf.host.br1) else: # TBD return def host_forwarding_deconfiguration(self, host_conf): if (self.params.host_fwd == 'bridge'): if host_conf.host.br0: host_conf.host.br0.slave_del(host_conf.nics[0]) if host_conf.host.br1: host_conf.host.br1.slave_del(host_conf.nics[1]) else: # TBD return def configure_guest_xml(self, host_conf, guest_conf): guest_xml = self.init_guest_xml(guest_conf) virtctl = guest_conf.virtctl guest_xml = ET.fromstring(virtctl.vm_XMLDesc(guest_conf.name)) guest_conf.libvirt_xml = guest_xml guest_conf.virtio_devs = [] for i, nic in enumerate(host_conf.nics): self._xml_add_vhostnet_dev(guest_xml, "vhostnet-{i}".format(i=i), host_conf.bridges[i], self.params.guest_macs[i]) vhost_device = VirtioDevice( VirtioType.VHOST_NET, self.params.guest_macs[i], config={"bridge": host_conf.bridges[i]}) guest_conf.virtio_devs.append(vhost_device) return guest_xml def guest_forwarding(self, guest_conf): guest = guest_conf.host if (self.params.guest_fwd == 'bridge'): guest.bridge = BridgeDevice() guest.bridge.name = 'guestbr0' for nic in guest_conf.nics: guest.bridge.slave_add(nic) nic.up() guest.run("echo 1 > /proc/sys/net/ipv4/ip_forward") def guest_deconfigure(self, guest_conf): if guest_conf.host: guest_conf.host.run("echo 0 > /proc/sys/net/ipv4/ip_forward") def _xml_add_vhostnet_dev(self, guest_xml, name, bridge, mac_addr): devices = guest_xml.find("devices") interface = ET.SubElement(devices, 'interface', type='bridge') ET.SubElement(interface, 'source', bridge=str(bridge.name)) ET.SubElement(interface, 'mac', address=str(mac_addr)) ET.SubElement(interface, 'model', type='virtio') ET.SubElement(interface, 'driver', name='vhost') # TODO: Add driver suboptions return guest_xml
class TeamVsBondRecipe(PerfReversibleFlowMixin, CommonHWSubConfigMixin, OffloadSubConfigMixin, BaremetalEnrtRecipe): host1 = HostReq() host1.eth0 = DeviceReq(label="tnet", driver=RecipeParam("driver")) host1.eth1 = DeviceReq(label="tnet", driver=RecipeParam("driver")) host2 = HostReq() host2.eth0 = DeviceReq(label="tnet", driver=RecipeParam("driver")) host2.eth1 = DeviceReq(label="tnet", driver=RecipeParam("driver")) offload_combinations = Param(default=( dict(gro="on", gso="on", tso="on", tx="on"), dict(gro="off", gso="on", tso="on", tx="on"), dict(gro="on", gso="off", tso="off", tx="on"), dict(gro="on", gso="on", tso="off", tx="off"))) runner_name = StrParam(mandatory = True) bonding_mode = StrParam(mandatory = True) miimon_value = IntParam(mandatory = True) def test_wide_configuration(self): host1, host2 = self.matched.host1, self.matched.host2 host1.team0 = TeamDevice(config={'runner': {'name': self.params.runner_name}}) host2.bond0 = BondDevice(mode=self.params.bonding_mode, miimon=self.params.miimon_value) configuration = super().test_wide_configuration() configuration.test_wide_devices = [host1.team0, host2.bond0] net_addr_1 = "192.168.10" net_addr6_1 = "fc00:0:0:1" for i, (host, dev) in enumerate([(host1, host1.team0), (host2, host2.bond0)]): host.eth0.down() host.eth1.down() dev.slave_add(host.eth0) dev.slave_add(host.eth1) dev.ip_add(ipaddress(net_addr_1 + "." + str(i+1) + "/24")) dev.ip_add(ipaddress(net_addr6_1 + "::" + str(i+1) + "/64")) for host, dev in [(host1, host1.team0), (host2, host2.bond0)]: host.eth0.up() host.eth1.up() dev.up() self.wait_tentative_ips(configuration.test_wide_devices) return configuration def generate_test_wide_description(self, config): host1, host2 = self.matched.host1, self.matched.host2 desc = super().generate_test_wide_description(config) desc += [ "\n".join([ "Configured {}.{}.ips = {}".format( dev.host.hostid, dev.name, dev.ips ) for dev in config.test_wide_devices ]), "\n".join([ "Configured {}.{}.slaves = {}".format( dev.host.hostid, dev.name, ['.'.join([dev.host.hostid, slave.name]) for slave in dev.slaves] ) for dev in config.test_wide_devices ]), "Configured {}.{}.runner_name = {}".format( host1.hostid, host1.team0.name, host1.team0.config ), "Configured {}.{}.mode = {}".format( host2.hostid, host2.bond0.name, host2.bond0.mode ), "Configured {}.{}.miimon = {}".format( host2.hostid, host2.bond0.name, host2.bond0.miimon ) ] return desc def test_wide_deconfiguration(self, config): del config.test_wide_devices super().test_wide_deconfiguration(config) def generate_ping_endpoints(self, config): return [ PingEndpoints(self.matched.host1.team0, self.matched.host2.bond0), PingEndpoints(self.matched.host2.bond0, self.matched.host1.team0) ] def generate_perf_endpoints(self, config): return [(self.matched.host1.team0, self.matched.host2.bond0)] @property def offload_nics(self): return [self.matched.host1.team0, self.matched.host2.bond0] @property def mtu_hw_config_dev_list(self): return [self.matched.host1.team0, self.matched.host2.bond0] @property def coalescing_hw_config_dev_list(self): host1, host2 = self.matched.host1, self.matched.host2 return [host1.eth0, host1.eth1, host2.eth0, host2.eth1] @property def dev_interrupt_hw_config_dev_list(self): host1, host2 = self.matched.host1, self.matched.host2 return [host1.eth0, host1.eth1, host2.eth0, host2.eth1] @property def parallel_stream_qdisc_hw_config_dev_list(self): host1, host2 = self.matched.host1, self.matched.host2 return [host1.eth0, host1.eth1, host2.eth0, host2.eth1]
class DoubleBondRecipe(CommonHWSubConfigMixin, OffloadSubConfigMixin, BaseEnrtRecipe): host1 = HostReq() host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) host1.eth1 = DeviceReq(label="net1", driver=RecipeParam("driver")) host2 = HostReq() host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) host2.eth1 = DeviceReq(label="net1", driver=RecipeParam("driver")) offload_combinations = Param( default=(dict(gro="on", gso="on", tso="on", tx="on"), dict(gro="off", gso="on", tso="on", tx="on"), dict(gro="on", gso="off", tso="off", tx="on"), dict(gro="on", gso="on", tso="off", tx="off"))) bonding_mode = StrParam(mandatory=True) miimon_value = IntParam(mandatory=True) def test_wide_configuration(self): host1, host2 = self.matched.host1, self.matched.host2 net_addr = "192.168.101" net_addr6 = "fc00:0:0:0" for i, host in enumerate([host1, host2]): host.bond0 = BondDevice(mode=self.params.bonding_mode, miimon=self.params.miimon_value) for dev in [host.eth0, host.eth1]: dev.down() host.bond0.slave_add(dev) host.bond0.ip_add(ipaddress(net_addr + "." + str(i + 1) + "/24")) host.bond0.ip_add(ipaddress(net_addr6 + "::" + str(i + 1) + "/64")) for dev in [host.eth0, host.eth1, host.bond0]: dev.up() configuration = super().test_wide_configuration() configuration.test_wide_devices = [host1.bond0, host2.bond0] self.wait_tentative_ips(configuration.test_wide_devices) return configuration def generate_test_wide_description(self, config): host1, host2 = self.matched.host1, self.matched.host2 desc = super().generate_test_wide_description(config) desc += [ "\n".join([ "Configured {}.{}.ips = {}".format(dev.host.hostid, dev.name, dev.ips) for dev in config.test_wide_devices ]), "\n".join([ "Configured {}.{}.slaves = {}".format( dev.host.hostid, dev.name, [ '.'.join([dev.host.hostid, slave.name]) for slave in dev.slaves ]) for dev in config.test_wide_devices ]), "\n".join([ "Configured {}.{}.mode = {}".format(dev.host.hostid, dev.name, dev.mode) for dev in config.test_wide_devices ]), "\n".join([ "Configured {}.{}.miimon = {}".format(dev.host.hostid, dev.name, dev.miimon) for dev in config.test_wide_devices ]) ] return desc def test_wide_deconfiguration(self, config): del config.test_wide_devices super().test_wide_deconfiguration(config) def generate_ping_endpoints(self, config): return [(self.matched.host1.bond0, self.matched.host2.bond0)] def generate_perf_endpoints(self, config): return [(self.matched.host1.bond0, self.matched.host2.bond0)] def wait_tentative_ips(self, devices): def condition(): return all( [not ip.is_tentative for dev in devices for ip in dev.ips]) self.ctl.wait_for_condition(condition, timeout=5) @property def offload_nics(self): return [self.matched.host1.bond0, self.matched.host2.bond0] @property def mtu_hw_config_dev_list(self): return [self.matched.host1.bond0, self.matched.host2.bond0] @property def coalescing_hw_config_dev_list(self): host1, host2 = self.matched.host1, self.matched.host2 return [host1.eth0, host1.eth1, host2.eth0, host2.eth1] @property def dev_interrupt_hw_config_dev_list(self): host1, host2 = self.matched.host1, self.matched.host2 return [host1.eth0, host1.eth1, host2.eth0, host2.eth1] @property def parallel_stream_qdisc_hw_config_dev_list(self): host1, host2 = self.matched.host1, self.matched.host2 return [host1.eth0, host1.eth1, host2.eth0, host2.eth1]
class PacketAssert(BaseTestModule): interface = DeviceParam(mandatory=True) p_filter = StrParam(default='') grep_for = ListParam(default=[]) promiscuous = BoolParam(default=False) _grep_exprs = [] _p_recv = 0 def _prepare_grep_exprs(self): for expr in self.params.grep_for: if expr is not None: self._grep_exprs.append(expr) def _compose_cmd(self): cmd = "tcpdump" if not self.params.promiscuous: cmd += " -p" iface = self.params.interface.name filt = self.params.p_filter cmd += " -nn -i %s \"%s\"" % (iface, filt) return cmd def _check_line(self, line): if line != "": for exp in self._grep_exprs: if not re.search(exp, line): return self._p_recv += 1 def _is_real_err(self, err): ignore_exprs = [r"tcpdump: verbose output suppressed, use -v or -vv for full protocol decode", r"listening on %s, link-type .* \(.*\), capture size [0-9]* bytes" % self.params.interface.name, r"\d+ packets captured", r"\d+ packets received by filter", r"\d+ packets dropped by kernel"] for line in err.split('\n'): if not line: continue match = False for expr in ignore_exprs: if re.search(expr, line): match = True break if not match: return True return False def run(self): self._res_data = {} if not is_installed("tcpdump"): self._res_data["msg"] = "tcpdump is not installed on this machine!" logging.error(self._res_data["msg"]) return False self._prepare_grep_exprs() cmd = self._compose_cmd() logging.debug("compiled command: {}".format(cmd)) packet_assert_process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) try: self.wait_for_interrupt() except: raise LnstError("Could not handle interrupt properly.") with packet_assert_process.stdout, packet_assert_process.stderr: stderr=packet_assert_process.stderr.read().decode() stdout=packet_assert_process.stdout.read().decode() self._res_data["stderr"] = stderr if self._is_real_err(stderr): self._res_data["msg"] = "errors reported by tcpdump" logging.error(self._res_data["msg"]) logging.error(self._res_data["stderr"]) return False for line in stdout.split("\n"): self._check_line(line) logging.debug("Capturing finised. Received %d packets." % self._p_recv) self._res_data["p_recv"] = self._p_recv return True
class PacketAssert(BaseTestModule): interface = DeviceParam(mandatory=True) p_filter = StrParam(default="") grep_for = ListParam(default=[]) promiscuous = BoolParam(default=False) _grep_exprs = [] _p_recv = 0 def _prepare_grep_exprs(self): for expr in self.params.grep_for: if expr is not None: self._grep_exprs.append(expr) def _compose_cmd(self): cmd = "tcpdump" if not self.params.promiscuous: cmd += " -p" iface = self.params.interface.name filt = self.params.p_filter cmd += ' -nn -i %s "%s"' % (iface, filt) return cmd def _check_line(self, line): if line != "": for exp in self._grep_exprs: if not re.search(exp, line): return self._p_recv += 1 def run(self): self._res_data = {} if not is_installed("tcpdump"): self._res_data["msg"] = "tcpdump is not installed on this machine!" logging.error(self._res_data["msg"]) return False self._prepare_grep_exprs() cmd = self._compose_cmd() logging.debug("compiled command: {}".format(cmd)) packet_assert_process = subprocess.Popen( cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, ) try: self.wait_for_interrupt() except: raise LnstError("Could not handle interrupt properly.") stdout, stderr = packet_assert_process.communicate() stdout = stdout.decode() stderr = stderr.decode() self._res_data["stderr"] = stderr # tcpdump always reports information to stderr, there may be actual # errors but also just generic debug information logging.debug(self._res_data["stderr"]) for line in stdout.split("\n"): self._check_line(line) logging.debug("Capturing finised. Received %d packets." % self._p_recv) self._res_data["p_recv"] = self._p_recv if packet_assert_process.returncode != 0: return False else: return True
class TestRecipe(BaseRecipe): ipv = StrParam(default="both") mtu = IntParam(default=1500) mapping_file = StrParam() pr_user_comment = StrParam() nperf_cpupin = IntParam() nperf_reserve = IntParam(default=20) nperf_mode = StrParam(default="default") netperf_duration = IntParam(default=1) netperf_confidence = StrParam(default="99,5") netperf_runs = IntParam(default=5) netperf_cpu_util = IntParam() netperf_num_parallel = IntParam(default=2) netperf_debug = IntParam(default=0) netperf_max_deviation = Param(default={'type': 'percent', 'value': 20}) test_if1 = Param() test_if2 = Param() def __init__(self, **kwargs): super(TestRecipe, self).__init__(**kwargs) if "mapping_file" in self.params: self.perf_api = PerfRepoAPI(self.params.mapping_file) self.perf_api.connect_PerfRepo() def initial_setup(self): machines = [] if "pr_user_comment" in self.params: machines = [machines.append(m) for m in self.matched] self.pr_comment = generate_perfrepo_comment( machines, self.params.pr_user_comment) if "nperf_cpupin" in self.params: for m in self.matched: m.run("service irqbalance stop") for m in self.matched: for d in m.devices: if re.match(r'^eth[0-9]+$', d.name): pin_dev_irqs(m, d, 0) self.nperf_opts = "" if "test_if2" in self.params: self.nperf_opts = "-L %s" % (self.params.test_if2.ips[0]) if "nperf_cpupin" in self.params and self.params.netperf_mode != "multi": self.nperf_opts += " -T%s,%s" % (self.params.netperf_cpupin, self.params.netperf_cpupin) self.nperf_opts6 = "" if "test_if2" in self.params: self.nperf_opts6 = "-L %s" % (self.params.test_if2.ips[1]) self.nperf_opts6 += " -6" if "nperf_cpupin" in self.params and self.params.netperf_mode != "multi": self.nperf_popts6 += " -T%s,%s" % (self.params.netperf_cpupin, self.params.netperf_cpupin) time.sleep(15) def clean_setup(self): if "nperf_cpupin" in self.params: for m in self.matched: m.run("service irqbalance start") def generate_netperf_cli(self, dst_addr, testname): kwargs = {} for key, val in self.params: param_name = re.split(r'netperf_', key) if len(param_name) > 1: kwargs[param_name[1]] = val kwargs['server'] = dst_addr kwargs['testname'] = testname if str(dst_addr).find(":") is -1: kwargs['opts'] = self.nperf_opts else: kwargs['opts'] = self.nperf_opts6 return Netperf(**kwargs) def netperf_run(self, netserver, netperf, perfrepo_result=None): srv_proc = self.matched.m1.run(netserver, bg=True) if perfrepo_result: if not hasattr(self, 'perf_api'): raise RecipeError("no class variable called perf_api") baseline = self.perf_api.get_baseline_of_result(perfrepo_result) #TODO: #netperf_baseline_template(netperf, baseline) time.sleep(2) res_data = self.matched.m2.run( netperf, timeout=(self.params.netperf_duration + self.params.nperf_reserve) * self.params.netperf_runs) if perfrepo_result: netperf_result_template(perfrepo_result, res_data) if hasattr(self, 'pr_comment'): perfrepo_result.set_comment(self.params.pr_comment) self.perf_api.save_result(perfrepo_result) srv_proc.kill(2) return res_data, srv_proc def network_setup(self): pass def core_test(self): pass def test(self): self.network_setup() self.initial_setup() self.core_test() self.clean_setup()
class IpsecEspAeadRecipe(CommonHWSubConfigMixin, BaseEnrtRecipe, PacketAssertTestAndEvaluate): """ This recipe implements Enrt testing for a simple IPsec scenario that looks as follows .. code-block:: none +--------+ +------+ switch +-----+ | +--------+ | +--+-+ +-+--+ +-|eth0|-+ +-|eth0|-+ | +----+ | | +----+ | | host1 | | host2 | +--------+ +--------+ The recipe provides additional recipe parameter to configure IPsec tunel. :param ipsec_mode: mode of the ipsec tunnel All sub configurations are included via Mixin classes. The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. """ host1 = HostReq() host1.eth0 = DeviceReq(label="to_switch", driver=RecipeParam("driver")) host2 = HostReq() host2.eth0 = DeviceReq(label="to_switch", driver=RecipeParam("driver")) algorithm = [('rfc4106(gcm(aes))', 160, 96)] spi_values = ["0x00001000", "0x00001001"] ipsec_mode = StrParam(default="transport") def test_wide_configuration(self): """ Test wide configuration for this recipe involves just adding an IPv4 and IPv6 address to the matched eth0 nics on both hosts and route between them. host1.eth0 = 192.168.101.1/24 and fc00::1/64 host2.eth0 = 192.168.101.2/24 and fc00::2/64 """ host1, host2 = self.matched.host1, self.matched.host2 configuration = super().test_wide_configuration() configuration.test_wide_devices = [host1.eth0, host2.eth0] net_addr = "192.168." net_addr6 = "fc00:" for i, host in enumerate([host1, host2]): host.eth0.down() host.eth0.ip_add(ipaddress(net_addr + str(i + 99) + ".1/24")) host.eth0.ip_add(ipaddress(net_addr6 + str(i + 1) + "::1/64")) host.eth0.up() self.wait_tentative_ips(configuration.test_wide_devices) if self.params.ping_parallel or self.params.ping_bidirect: logging.debug("Parallelism in pings is not supported for this " "recipe, ping_parallel/ping_bidirect will be ignored.") for host, dst in [(host1, host2), (host2, host1)]: for family in [AF_INET, AF_INET6]: host.run("ip route add %s dev %s" % (dst.eth0.ips_filter(family=family)[0], host.eth0.name)) configuration.endpoint1 = host1.eth0 configuration.endpoint2 = host2.eth0 return configuration def generate_test_wide_description(self, config): """ Test wide description is extended with the configured IP addresses, specified IPsec algorithm, key length and integrity check value length. """ desc = super().generate_test_wide_description(config) desc += [ "\n".join([ f"Configured {dev.host.hostid}.{dev.name}.ips = {dev.ips}" for dev in config.test_wide_devices ]).join([f"Configured IPsec {self.params.ipsec_mode} mode with {algo} algorithm " f"using key length of {key_len} and icv length of {icv_len}" for algo, key_len, icv_len in self.algorithm]) ] return desc def test_wide_deconfiguration(self, config): del config.test_wide_devices super().test_wide_deconfiguration(config) def generate_sub_configurations(self, config): """ Test wide configuration is extended with subconfiguration containing IPsec tunnel with predefined parameters for both IP versions. """ ipsec_mode = self.params.ipsec_mode spi_values = self.spi_values for subconf in ConfMixin.generate_sub_configurations(self, config): for ipv in self.params.ip_versions: if ipv == "ipv4": family = AF_INET elif ipv == "ipv6": family = AF_INET6 ip1 = subconf.endpoint1.ips_filter(family=family)[0] ip2 = subconf.endpoint2.ips_filter(family=family)[0] for algo, key_len, icv_len in self.algorithm: g_key = generate_key(key_len) new_config = copy.copy(subconf) new_config.ips = (ip1, ip2) new_config.ipsec_settings = (algo, g_key, icv_len, ipsec_mode, spi_values) yield new_config def apply_sub_configuration(self, config): """ Subconfiguration containing IPsec tunnel is applied through XfrmTools class. """ super().apply_sub_configuration(config) ns1, ns2 = config.endpoint1.netns, config.endpoint2.netns ip1, ip2 = config.ips ipsec_sets = config.ipsec_settings configure_ipsec_esp_aead(ns1, ip1, ns2, ip2, *ipsec_sets) def remove_sub_configuration(self, config): ns1, ns2 = config.endpoint1.netns, config.endpoint2.netns for ns in (ns1, ns2): ns.run("ip xfrm policy flush") ns.run("ip xfrm state flush") super().remove_sub_configuration(config) def generate_ping_configurations(self, config): """ The ping endpoints for this recipe are the configured endpoints of the IPsec tunnel on both hosts. """ ns1, ns2 = config.endpoint1.netns, config.endpoint2.netns ip1, ip2 = config.ips count = self.params.ping_count interval = self.params.ping_interval size = self.params.ping_psize common_args = {'count': count, 'interval': interval, 'size': size} ping_conf = PingConf(client=ns1, client_bind=ip1, destination=ns2, destination_address=ip2, **common_args) yield [ping_conf] def generate_flow_combinations(self, config): """ Flow combinations are generated based on the tunnel endpoints and test parameters. """ nic1, nic2 = config.endpoint1, config.endpoint2 ns1, ns2 = config.endpoint1.netns, config.endpoint2.netns ip1, ip2 = config.ips for perf_test in self.params.perf_tests: for size in self.params.perf_msg_sizes: flow = PerfFlow( type=perf_test, generator=ns1, generator_bind=ip1, generator_nic=nic1, receiver=ns2, receiver_bind=ip2, receiver_nic=nic2, msg_size=size, duration=self.params.perf_duration, parallel_streams=self.params.perf_parallel_streams, cpupin=self.params.perf_tool_cpu if ( "perf_tool_cpu" in self.params) else None ) yield [flow] if ("perf_reverse" in self.params and self.params.perf_reverse): reverse_flow = self._create_reverse_flow(flow) yield [reverse_flow] def ping_test(self, ping_configs): """ Ping test is utilizing PacketAssert class to search for the appropriate ESP IP packet. Result of ping test is handed to the super class' method. Returned as:: (ping_result, pa_config, pa_result) """ m1, m2 = ping_configs[0].client, ping_configs[0].destination ip1, ip2 = (ping_configs[0].client_bind, ping_configs[0].destination_address) if1_name = self.get_dev_by_ip(m1, ip1).name if2 = self.get_dev_by_ip(m2, ip2) pa_kwargs = {} pa_kwargs["p_filter"] = "esp" pa_kwargs["grep_for"] = ['ESP\(spi=' + self.spi_values[1]] if ping_configs[0].count: pa_kwargs["p_min"] = ping_configs[0].count pa_config = PacketAssertConf(m2, if2, **pa_kwargs) dump = m1.run("tcpdump -i %s -nn -vv" % if1_name, bg=True) self.packet_assert_test_start(pa_config) self.ctl.wait(2) ping_result = super().ping_test(ping_configs) self.ctl.wait(2) pa_result = self.packet_assert_test_stop() dump.kill(signal=signal.SIGINT) return (ping_result, pa_config, pa_result) def ping_report_and_evaluate(self, results): super().ping_report_and_evaluate(results[0]) self.packet_assert_evaluate_and_report(results[1], results[2]) def get_dev_by_ip(self, netns, ip): for dev in netns.device_database: if ip in dev.ips: return dev raise LnstError("Could not match ip %s to any device of %s." % (ip, netns.name)) @property def mtu_hw_config_dev_list(self): return [self.matched.host1.eth0, self.matched.host2.eth0] @property def dev_interrupt_hw_config_dev_list(self): return [self.matched.host1.eth0, self.matched.host2.eth0] @property def parallel_stream_qdisc_hw_config_dev_list(self): return [self.matched.host1.eth0, self.matched.host2.eth0]
class GreTunnelOverBondRecipe(MTUHWConfigMixin, PauseFramesHWConfigMixin, OffloadSubConfigMixin, BaseTunnelRecipe): """ This class implements a recipe that configures a GRE tunnel between two hosts that are connected through a bond device. .. code-block:: none .--------. .------.---| switch |---.------. | | '--------' | | | | | | .----|-- ---|----. .----|-- ---|----. | .--'-. .-'--. | | .--'-. .-'--. | | |eth0| |eth1| | | |eth0| |eth1| | | '----' '----' | | '----' '----' | | \ / | | \ / | | .--\--/-. | | .--\--/-. | | | bond0 | | | | bond0 | | | '-------' | | '-------' | | | | | | | | | | ---' '---- | | ---' '---- | | gre tunnel | | gre tunnel | | ---------- | | ---------- | | | | | | host1 | | host1 | '----------------' '----------------' The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. The test wide configuration is implemented in the :any:`BaseTunnelRecipe` class. The recipe provides additional parameter: :param bonding_mode: (mandatory test parameter) the bonding mode to be configured on the bond0 device. :param miimon_value: (mandatory test parameter) the miimon interval to be configured on the bond0 device. """ host1 = HostReq() host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) host1.eth1 = DeviceReq(label="net1", driver=RecipeParam("driver")) host2 = HostReq() host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) host2.eth1 = DeviceReq(label="net1", driver=RecipeParam("driver")) bonding_mode = StrParam(mandatory=True) miimon_value = IntParam(mandatory=True) offload_combinations = Param(default=( dict(gro="on", gso="on", tso="on"), dict(gro="off", gso="on", tso="on"), dict(gro="on", gso="off", tso="off"), dict(gro="on", gso="on", tso="off"), )) def configure_underlying_network(self, configuration): """ The underlying network for the tunnel consists of two Ethernet devices bonded together by bonding device on both of the matched hosts. """ host1, host2 = self.matched.host1, self.matched.host2 host1.bond = BondDevice(mode=self.params.bonding_mode, miimon=self.params.miimon_value) host2.bond = BondDevice(mode=self.params.bonding_mode, miimon=self.params.miimon_value) for host, devices in [ (host1, [host1.eth0, host1.eth1]), (host2, [host2.eth0, host2.eth1]), ]: for dev in devices: dev.down() host.bond.slave_add(dev) for i, device in enumerate([host1.bond, host2.bond]): device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24")) configuration.test_wide_devices.append(device) for dev in [ host1.eth0, host1.eth1, host1.bond, host2.eth0, host2.eth0, host2.bond, ]: dev.up() configuration.tunnel_endpoints = (host1.bond, host2.bond) def create_tunnel(self, configuration): """ The GRE tunnel devices are configured with local and remote ip addresses matching the bond device IP addresses. The GRE tunnel devices are configured with IPv4 and IPv6 addresses of individual networks. Routes are configured accordingly. """ endpoint1, endpoint2 = configuration.tunnel_endpoints m1 = endpoint1.netns m2 = endpoint2.netns ip_filter = {"family": AF_INET} endpoint1_ip = endpoint1.ips_filter(**ip_filter)[0] endpoint2_ip = endpoint2.ips_filter(**ip_filter)[0] a_ip4 = Ip4Address("192.168.6.2/24") a_net4 = "192.168.6.0/24" b_ip4 = Ip4Address("192.168.7.2/24") b_net4 = "192.168.7.0/24" a_ip6 = Ip6Address("6001:db8:ac10:fe01::2/64") a_net6 = "6001:db8:ac10:fe01::0/64" b_ip6 = Ip6Address("7001:db8:ac10:fe01::2/64") b_net6 = "7001:db8:ac10:fe01::0/64" m1.gre_tunnel = GreDevice(local=endpoint1_ip, remote=endpoint2_ip) m2.gre_tunnel = GreDevice(local=endpoint2_ip, remote=endpoint1_ip) # A m1.gre_tunnel.up() m1.gre_tunnel.ip_add(a_ip4) m1.gre_tunnel.ip_add(a_ip6) m1.run("ip -4 route add {} dev {}".format(b_net4, m1.gre_tunnel.name)) m1.run("ip -6 route add {} dev {}".format(b_net6, m1.gre_tunnel.name)) # B m2.gre_tunnel.up() m2.gre_tunnel.ip_add(b_ip4) m2.gre_tunnel.ip_add(b_ip6) m2.run("ip -4 route add {} dev {}".format(a_net4, m2.gre_tunnel.name)) m2.run("ip -6 route add {} dev {}".format(a_net6, m2.gre_tunnel.name)) configuration.tunnel_devices.extend([m1.gre_tunnel, m2.gre_tunnel]) self.wait_tentative_ips(configuration.tunnel_devices) def generate_ping_endpoints(self, config): """ The ping endpoints for this recipe are simply the tunnel endpoints Returned as:: [PingEndpoints(self.matched.host1.gre_tunnel, self.matched.host2.gre_tunnel)] """ return [ PingEndpoints(self.matched.host1.gre_tunnel, self.matched.host2.gre_tunnel) ] def get_packet_assert_config(self, ping_config): """ The packet assert test configuration contains filter for gre protocol and grep patterns to match the ICMP or ICMP6 echo requests. """ ip_filter = {"family": AF_INET} m1_carrier = self.matched.host1.bond m2_carrier = self.matched.host2.bond m1_carrier_ip = m1_carrier.ips_filter(**ip_filter)[0] m2_carrier_ip = m2_carrier.ips_filter(**ip_filter)[0] ip1 = ping_config.client_bind ip2 = ping_config.destination_address pa_kwargs = {} pa_kwargs["p_filter"] = "proto gre" if isinstance(ip2, Ip4Address): pat1 = "{} > {}: GREv0, .* IP {} > {}: ICMP echo request".format( m1_carrier_ip, m2_carrier_ip, ip1, ip2) pat2 = "{} > {}: GREv0 \| {} > {}: ICMP echo request".format( m1_carrier_ip, m2_carrier_ip, ip1, ip2) grep_pattern = ["({})|({})".format(pat1, pat2)] elif isinstance(ip2, Ip6Address): pat1 = "{} > {}: GREv0, .* IP6 {} > {}: ICMP6, echo request".format( m1_carrier_ip, m2_carrier_ip, ip1, ip2) pat2 = "{} > {}: GREv0 \| {} > {}: ICMP6, echo request".format( m1_carrier_ip, m2_carrier_ip, ip1, ip2) grep_pattern = ["({})|({})".format(pat1, pat2)] else: raise Exception( "The destination address is nor IPv4 or IPv6 address") pa_kwargs["grep_for"] = grep_pattern if ping_config.count: pa_kwargs["p_min"] = ping_config.count m2 = ping_config.destination pa_config = PacketAssertConf(m2, m2_carrier, **pa_kwargs) return pa_config @property def offload_nics(self): return [self.matched.host1.bond, self.matched.host2.bond] @property def pause_frames_dev_list(self): return [self.matched.host1.eth0, self.matched.host2.eth0] @property def mtu_hw_config_dev_list(self): return [self.matched.host1.gre_tunnel, self.matched.host2.gre_tunnel]
class VirtualBridgeVlansOverBondRecipe(VlanPingEvaluatorMixin, CommonHWSubConfigMixin, OffloadSubConfigMixin, BaseEnrtRecipe): host1 = HostReq() host1.eth0 = DeviceReq(label="to_switch", driver=RecipeParam("driver")) host1.eth1 = DeviceReq(label="to_switch", driver=RecipeParam("driver")) host1.tap0 = DeviceReq(label="to_guest1") host1.tap1 = DeviceReq(label="to_guest2") host2 = HostReq() host2.eth0 = DeviceReq(label="to_switch", driver=RecipeParam("driver")) host2.eth1 = DeviceReq(label="to_switch", driver=RecipeParam("driver")) host2.tap0 = DeviceReq(label="to_guest3") host2.tap1 = DeviceReq(label="to_guest4") guest1 = HostReq() guest1.eth0 = DeviceReq(label="to_guest1") guest2 = HostReq() guest2.eth0 = DeviceReq(label="to_guest2") guest3 = HostReq() guest3.eth0 = DeviceReq(label="to_guest3") guest4 = HostReq() guest4.eth0 = DeviceReq(label="to_guest4") offload_combinations = Param( default=(dict(gro="on", gso="on", tso="on", tx="on"), dict(gro="off", gso="on", tso="on", tx="on"), dict(gro="on", gso="off", tso="off", tx="on"), dict(gro="on", gso="on", tso="off", tx="off"))) bonding_mode = StrParam(mandatory=True) miimon_value = IntParam(mandatory=True) def test_wide_configuration(self): host1, host2, guest1, guest2, guest3, guest4 = (self.matched.host1, self.matched.host2, self.matched.guest1, self.matched.guest2, self.matched.guest3, self.matched.guest4) for host in [host1, host2]: for dev in [host.eth0, host.eth1, host.tap0, host.tap1]: dev.down() host.bond0 = BondDevice(mode=self.params.bonding_mode, miimon=self.params.miimon_value) host.bond0.slave_add(host.eth0) host.bond0.slave_add(host.eth1) host.br0 = BridgeDevice() host.br0.slave_add(host.tap0) host.br1 = BridgeDevice() host.br1.slave_add(host.tap1) for guest in (guest1, guest2, guest3, guest4): guest.eth0.down() host1.vlan0 = VlanDevice(realdev=host1.bond0, vlan_id=10, master=host1.br0) host1.vlan1 = VlanDevice(realdev=host1.bond0, vlan_id=20, master=host1.br1) host2.vlan0 = VlanDevice(realdev=host2.bond0, vlan_id=10, master=host2.br0) host2.vlan1 = VlanDevice(realdev=host2.bond0, vlan_id=20, master=host2.br1) configuration = super().test_wide_configuration() configuration.test_wide_devices = [ host1.br0, host2.br0, guest1.eth0, guest2.eth0, guest3.eth0, guest4.eth0 ] net_addr = "192.168" net_addr6 = "fc00:0:0" for host, (guest_a, guest_b), n in [(host1, (guest1, guest2), 1), (host2, (guest3, guest4), 3)]: host.br0.ip_add(ipaddress(net_addr + ".10." + str(n) + "/24")) host.br1.ip_add(ipaddress(net_addr + ".20." + str(n) + "/24")) guest_a.eth0.ip_add( ipaddress(net_addr + ".10." + str(n + 1) + "/24")) guest_a.eth0.ip_add( ipaddress(net_addr6 + ":1::" + str(n + 1) + "/64")) guest_b.eth0.ip_add( ipaddress(net_addr + ".20." + str(n + 1) + "/24")) guest_b.eth0.ip_add( ipaddress(net_addr6 + ":2::" + str(n + 1) + "/64")) for host in [host1, host2]: for dev in [ host.eth0, host.eth1, host.tap0, host.tap1, host.bond0, host.vlan0, host.vlan1, host.br0, host.br1 ]: dev.up() for guest in [guest1, guest2, guest3, guest4]: guest.eth0.up() if "perf_tool_cpu" in self.params: logging.info("'perf_tool_cpu' param (%d) to be set to None" % self.params.perf_tool_cpu) self.params.perf_tool_cpu = None self.wait_tentative_ips(configuration.test_wide_devices) return configuration def generate_test_wide_description(self, config): host1, host2 = self.matched.host1, self.matched.host2 desc = super().generate_test_wide_description(config) desc += [ "\n".join([ "Configured {}.{}.ips = {}".format(dev.host.hostid, dev.name, dev.ips) for dev in config.test_wide_devices ]), "\n".join([ "Configured {}.{}.slaves = {}".format( dev.host.hostid, dev.name, [ '.'.join([dev.host.hostid, slave.name]) for slave in dev.slaves ]) for dev in [ host1.bond0, host2.bond0, host1.br0, host1.br1, host2.br0, host2.br1 ] ]), "\n".join([ "Configured {}.{}.mode = {}".format(dev.host.hostid, dev.name, dev.mode) for dev in [host1.bond0, host2.bond0] ]), "\n".join([ "Configured {}.{}.miimon = {}".format(dev.host.hostid, dev.name, dev.miimon) for dev in [host1.bond0, host2.bond0] ]), "\n".join([ "Configured {}.{}.vlan_id = {}".format(dev.host.hostid, dev.name, dev.vlan_id) for dev in [host1.vlan0, host1.vlan1, host2.vlan0, host2.vlan1] ]), "\n".join([ "Configured {}.{}.realdev = {}".format( dev.host.hostid, dev.name, '.'.join([dev.host.hostid, dev.realdev.name])) for dev in [host1.vlan0, host1.vlan1, host2.vlan0, host2.vlan1] ]) ] return desc def test_wide_deconfiguration(self, config): del config.test_wide_devices super().test_wide_deconfiguration(config) def generate_ping_endpoints(self, config): guest1, guest2, guest3, guest4 = (self.matched.guest1, self.matched.guest2, self.matched.guest3, self.matched.guest4) dev_combinations = product([guest1.eth0, guest2.eth0], [guest3.eth0, guest4.eth0]) return [ PingEndpoints(comb[0], comb[1], reachable=((comb[0].host, comb[1].host) in [(guest1, guest3), (guest2, guest4)])) for comb in dev_combinations ] def generate_perf_endpoints(self, config): return [(self.matched.guest1.eth0, self.matched.guest3.eth0)] @property def offload_nics(self): host1, host2, guest1, guest2, guest3, guest4 = (self.matched.host1, self.matched.host2, self.matched.guest1, self.matched.guest2, self.matched.guest3, self.matched.guest4) result = [] for machine in host1, host2, guest1, guest2, guest3, guest4: result.append(machine.eth0) result.extend([host1.eth1, host2.eth1]) return result @property def mtu_hw_config_dev_list(self): host1, host2, guest1, guest2, guest3, guest4 = (self.matched.host1, self.matched.host2, self.matched.guest1, self.matched.guest2, self.matched.guest3, self.matched.guest4) result = [] for host in [host1, host2]: for dev in [ host.bond0, host.tap0, host.tap1, host.br0, host.br1, host.vlan0, host.vlan1 ]: result.append(dev) for guest in [guest1, guest2, guest3, guest4]: result.append(guest.eth0) return result @property def dev_interrupt_hw_config_dev_list(self): return [ self.matched.host1.eth0, self.matched.host1.eth1, self.matched.host2.eth0, self.matched.host2.eth1 ] @property def parallel_stream_qdisc_hw_config_dev_list(self): return [ self.matched.host1.eth0, self.matched.host1.eth1, self.matched.host2.eth0, self.matched.host2.eth1 ]
class BondRecipe(PerfReversibleFlowMixin, CommonHWSubConfigMixin, OffloadSubConfigMixin, BaremetalEnrtRecipe): """ This recipe implements Enrt testing for a network scenario that looks as follows .. code-block:: none .--------. .----------------+ | | .-------+ switch +-------. | | '--------' | .-------------------. | | | bond0 | | | | .---'--. .---'--. | .---'--. .----|-| eth0 |-| eth1 |-|----. .----| eth0 |----. | | '------' '------' | | | '------' | | '-------------------' | | | | | | | | host1 | | host2 | '-----------------------------' '----------------' The recipe provides additional recipe parameters to configure the bonding device. :param bonding_mode: (mandatory test parameter) the bonding mode to be configured on the bond0 device. :param miimon_value: (mandatory test parameter) the miimon interval to be configured on the bond0 device. All sub configurations are included via Mixin classes. The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. """ host1 = HostReq() host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) host1.eth1 = DeviceReq(label="net1", driver=RecipeParam("driver")) host2 = HostReq() host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) offload_combinations = Param( default=(dict(gro="on", gso="on", tso="on", tx="on"), dict(gro="off", gso="on", tso="on", tx="on"), dict(gro="on", gso="off", tso="off", tx="on"), dict(gro="on", gso="on", tso="off", tx="off"))) bonding_mode = StrParam(mandatory=True) miimon_value = IntParam(mandatory=True) def test_wide_configuration(self): """ Test wide configuration for this recipe involves creating a bonding device using the two matched physical devices as slaves on host1. The bonding mode and miimon interval is configured on the bonding device according to the recipe parameters. IPv4 and IPv6 addresses are added to the bonding device and to the matched ethernet device on host2. | host1.bond0 = 192.168.101.1/24 and fc00::1/64 | host2.eth0 = 192.168.101.2/24 and fc00::2/64 """ host1, host2 = self.matched.host1, self.matched.host2 host1.bond0 = BondDevice(mode=self.params.bonding_mode, miimon=self.params.miimon_value) configuration = super().test_wide_configuration() configuration.test_wide_devices = [] for dev in [host1.eth0, host1.eth1]: dev.down() host1.bond0.slave_add(dev) net_addr = "192.168.101" net_addr6 = "fc00:0:0:0" for i, dev in enumerate([host1.bond0, host2.eth0]): dev.ip_add(ipaddress(net_addr + "." + str(i + 1) + "/24")) dev.ip_add(ipaddress(net_addr6 + "::" + str(i + 1) + "/64")) configuration.test_wide_devices.append(dev) for dev in [host1.eth0, host1.eth1, host1.bond0, host2.eth0]: dev.up() self.wait_tentative_ips(configuration.test_wide_devices) return configuration def generate_test_wide_description(self, config): """ Test wide description is extended with the configured IP addresses, the configured bonding slave interfaces, bonding mode and the miimon interval. """ host1, host2 = self.matched.host1, self.matched.host2 desc = super().generate_test_wide_description(config) desc += [ "\n".join([ "Configured {}.{}.ips = {}".format(dev.host.hostid, dev.name, dev.ips) for dev in config.test_wide_devices ]), "Configured {}.{}.slaves = {}".format( host1.hostid, host1.bond0.name, [ '.'.join([host1.hostid, slave.name]) for slave in host1.bond0.slaves ]), "Configured {}.{}.mode = {}".format(host1.hostid, host1.bond0.name, host1.bond0.mode), "Configured {}.{}.miimon = {}".format(host1.hostid, host1.bond0.name, host1.bond0.miimon) ] return desc def test_wide_deconfiguration(self, config): del config.test_wide_devices super().test_wide_deconfiguration(config) def generate_ping_endpoints(self, config): """ The ping endpoints for this recipe are the configured bonding device on host1 and the matched ethernet device on host2. Returned as:: [PingEndpoints(self.matched.host1.bond0, self.matched.host2.eth0) """ return [ PingEndpoints(self.matched.host1.bond0, self.matched.host2.eth0) ] def generate_perf_endpoints(self, config): """ The perf endpoints for this recipe are the configured bonding device on host1 and the matched ethernet device on host2. The traffic egresses the bonding device. | host1.bond0 | host2.eth0 Returned as:: [(self.matched.host1.bond0, self.matched.host2.eth0)] """ return [(self.matched.host1.bond0, self.matched.host2.eth0)] @property def offload_nics(self): """ The `offload_nics` property value for this scenario is a list containing the configured bonding device on host1 and the matched ethernet device on host2. | host1.bond0 | host2.eth0 For detailed explanation of this property see :any:`OffloadSubConfigMixin` class and :any:`OffloadSubConfigMixin.offload_nics`. """ return [self.matched.host1.bond0, self.matched.host2.eth0] @property def mtu_hw_config_dev_list(self): """ The `mtu_hw_config_dev_list` property value for this scenario is a list containing the configured bonding device on host1 and the matched ethernet device on host2. | host1.bond0 | host2.eth0 For detailed explanation of this property see :any:`MTUHWConfigMixin` class and :any:`MTUHWConfigMixin.mtu_hw_config_dev_list`. """ return [self.matched.host1.bond0, self.matched.host2.eth0] @property def coalescing_hw_config_dev_list(self): """ The `coalescing_hw_config_dev_list` property value for this scenario is a list containing the matched physical devices used to create the bonding device on host1 and the matched ethernet device on host2. | host1.eth0, host.eth1 | host2.eth0 For detailed explanation of this property see :any:`CoalescingHWConfigMixin` class and :any:`CoalescingHWConfigMixin.coalescing_hw_config_dev_list`. """ return [ self.matched.host1.eth0, self.matched.host1.eth1, self.matched.host2.eth0 ] @property def dev_interrupt_hw_config_dev_list(self): """ The `dev_interrupt_hw_config_dev_list` property value for this scenario is a list containing the matched physical devices used to create the bonding device on host1 and the matched ethernet device on host2. | host1.eth0, host1.eth1 | host2.eth0 For detailed explanation of this property see :any:`DevInterruptHWConfigMixin` class and :any:`CoalescingHWConfigMixin.coalescing_hw_config_dev_list`. """ return [ self.matched.host1.eth0, self.matched.host1.eth1, self.matched.host2.eth0 ] @property def parallel_stream_qdisc_hw_config_dev_list(self): """ The `parallel_stream_qdisc_hw_config_dev_list` property value for this scenario is a list containing the matched physical devices used to create the bonding device on host1 and the matched ethernet device on host2. | host1.eth0, host.eth1 | host2.eth0 For detailed explanation of this property see :any:`ParallelStreamQDiscHWConfigMixin` class and :any:`ParallelStreamQDiscHWConfigMixin.parallel_stream_qdisc_hw_config_dev_list`. """ return [ self.matched.host1.eth0, self.matched.host1.eth1, self.matched.host2.eth0 ] @property def pause_frames_dev_list(self): """ The `pause_frames_dev_list` property value for this scenario is a list containing the matched physical devices used to create the bonding device on host1 and the matched ethernet device on host2. | host1.eth0, host.eth1 | host2.eth0 For detailed explanation of this property see :any:`PauseFramesHWConfigMixin` and :any:`PauseFramesHWConfigMixin.pause_frames_dev_list`. """ return [ self.matched.host1.eth0, self.matched.host1.eth1, self.matched.host2.eth0 ]
class IpsecEspAhCompRecipe(CommonHWSubConfigMixin, BaremetalEnrtRecipe, PacketAssertTestAndEvaluate): host1 = HostReq() host1.eth0 = DeviceReq(label="to_switch", driver=RecipeParam("driver")) host2 = HostReq() host2.eth0 = DeviceReq(label="to_switch", driver=RecipeParam("driver")) ciphers = Param(default=[('aes', 128), ('aes', 256)]) hashes = Param(default=[('hmac(md5)', 128), ('sha256', 256)]) ipsec_mode = StrParam(default="transport") spi_values = ["0x00000001", "0x00000002", "0x00000003", "0x00000004"] def test_wide_configuration(self): host1, host2 = self.matched.host1, self.matched.host2 configuration = super().test_wide_configuration() configuration.test_wide_devices = [host1.eth0, host2.eth0] net_addr = "192.168." net_addr6 = "fc00:" for i, host in enumerate([host1, host2]): host.eth0.down() host.eth0.ip_add(ipaddress(net_addr + str(i + 99) + ".1/24")) host.eth0.ip_add(ipaddress(net_addr6 + str(i + 1) + "::1/64")) host.eth0.up() self.wait_tentative_ips(configuration.test_wide_devices) if self.params.ping_parallel or self.params.ping_bidirect: logging.debug( "Parallelism in pings is not supported for this" "recipe, ping_parallel/ping_bidirect will be ignored.") for host, dst in [(host1, host2), (host2, host1)]: for family in [AF_INET, AF_INET6]: host.run( "ip route add %s dev %s" % (dst.eth0.ips_filter(family=family)[0], host.eth0.name)) configuration.endpoint1 = host1.eth0 configuration.endpoint2 = host2.eth0 return configuration def generate_test_wide_description(self, config): host1, host2 = self.matched.host1, self.matched.host2 desc = super().generate_test_wide_description(config) desc += [ "\n".join([ "Configured {}.{}.ips = {}".format(dev.host.hostid, dev.name, dev.ips) for dev in config.test_wide_devices ]) ] return desc def test_wide_deconfiguration(self, config): del config.test_wide_devices super().test_wide_deconfiguration(config) def generate_sub_configurations(self, config): ipsec_mode = self.params.ipsec_mode spi_values = self.spi_values for subconf in ConfMixin.generate_sub_configurations(self, config): for ipv in self.params.ip_versions: if ipv == "ipv4": family = AF_INET elif ipv == "ipv6": family = AF_INET6 ip1 = config.endpoint1.ips_filter(family=family)[0] ip2 = config.endpoint2.ips_filter(family=family)[0] for ciph_alg, ciph_len in self.params.ciphers: for hash_alg, hash_len in self.params.hashes: ciph_key = generate_key(ciph_len) hash_key = generate_key(hash_len) new_config = copy.copy(subconf) new_config.ips = (ip1, ip2) new_config.ipsec_settings = (ciph_alg, ciph_key, hash_alg, hash_key, ipsec_mode, spi_values) yield new_config def apply_sub_configuration(self, config): super().apply_sub_configuration(config) ns1, ns2 = config.endpoint1.netns, config.endpoint2.netns ip1, ip2 = config.ips ipsec_sets = config.ipsec_settings configure_ipsec_esp_ah_comp(ns1, ip1, ns2, ip2, *ipsec_sets) def remove_sub_configuration(self, config): ns1, ns2 = config.endpoint1.netns, config.endpoint2.netns for ns in (ns1, ns2): ns.run("ip xfrm policy flush") ns.run("ip xfrm state flush") super().remove_sub_configuration(config) def generate_ping_configurations(self, config): ns1, ns2 = config.endpoint1.netns, config.endpoint2.netns ip1, ip2 = config.ips count = self.params.ping_count interval = self.params.ping_interval size = self.params.ping_psize common_args = {'count': count, 'interval': interval, 'size': size} ping_conf = PingConf(client=ns1, client_bind=ip1, destination=ns2, destination_address=ip2, **common_args) yield [ping_conf] def generate_flow_combinations(self, config): ns1, ns2 = config.endpoint1.netns, config.endpoint2.netns ip1, ip2 = config.ips for perf_test in self.params.perf_tests: for size in self.params.perf_msg_sizes: flow = PerfFlow( type=perf_test, generator=ns1, generator_bind=ip1, generator_nic=config.endpoint1, receiver=ns2, receiver_bind=ip2, receiver_nic=config.endpoint2, msg_size=size, duration=self.params.perf_duration, parallel_streams=self.params.perf_parallel_streams, cpupin=self.params.perf_tool_cpu if ("perf_tool_cpu" in self.params) else None) yield [flow] if ("perf_reverse" in self.params and self.params.perf_reverse): reverse_flow = self._create_reverse_flow(flow) yield [reverse_flow] def ping_test(self, ping_configs): m1, m2 = ping_configs[0].client, ping_configs[0].destination ip1, ip2 = (ping_configs[0].client_bind, ping_configs[0].destination_address) if1_name = self.get_dev_by_ip(m1, ip1).name if2 = self.get_dev_by_ip(m2, ip2) pa_kwargs = {} pa_kwargs["p_filter"] = "ah" pa_kwargs["grep_for"] = [ "AH\(spi=" + self.spi_values[2], "ESP\(spi=" + self.spi_values[1] ] if ping_configs[0].count: pa_kwargs["p_min"] = 2 * ping_configs[0].count pa_config = PacketAssertConf(m2, if2, **pa_kwargs) dump = m1.run("tcpdump -i %s -nn -vv" % if1_name, bg=True) self.packet_assert_test_start(pa_config) self.ctl.wait(2) ping_result = super().ping_test(ping_configs) self.ctl.wait(2) pa_result = self.packet_assert_test_stop() dump.kill(signal=signal.SIGINT) m1.run("ip -s xfrm pol") m1.run("ip -s xfrm state") dump2 = m1.run("tcpdump -i %s -nn -vv" % if1_name, bg=True) no_trans = self.params.ipsec_mode != 'transport' ping_configs2 = copy.copy(ping_configs) ping_configs2[0].size = 1500 if no_trans: pa_kwargs2 = copy.copy(pa_kwargs) pa_kwargs2["p_filter"] = '' pa_kwargs2["grep_for"] = ["IPComp"] if ping_configs2[0].count: pa_kwargs2["p_min"] = ping_configs2[0].count pa_config2 = PacketAssertConf(m2, if2, **pa_kwargs2) self.packet_assert_test_start(pa_config2) self.ctl.wait(2) ping_result2 = super().ping_test(ping_configs2) self.ctl.wait(2) if no_trans: pa_result2 = self.packet_assert_test_stop() dump2.kill(signal=signal.SIGINT) result = ((ping_result, pa_config, pa_result), ) if no_trans: result += ((ping_result2, pa_config2, pa_result2), ) return result def ping_report_and_evaluate(self, results): for res in results: super().ping_report_and_evaluate(res[0]) self.packet_assert_evaluate_and_report(res[1], res[2]) def get_dev_by_ip(self, netns, ip): for dev in netns.device_database: if ip in dev.ips: return dev raise LnstError("Could not match ip %s to any device of %s." % (ip, netns.name)) @property def mtu_hw_config_dev_list(self): return [self.matched.host1.eth0, self.matched.host2.eth0] @property def dev_interrupt_hw_config_dev_list(self): return [self.matched.host1.eth0, self.matched.host2.eth0] @property def parallel_stream_qdisc_hw_config_dev_list(self): return [self.matched.host1.eth0, self.matched.host2.eth0]
class TRexCommon(BaseTestModule): trex_dir = StrParam(mandatory=True)
class DoubleTeamRecipe(CommonHWSubConfigMixin, OffloadSubConfigMixin, BaseEnrtRecipe): host1 = HostReq() host1.eth0 = DeviceReq(label="tnet", driver=RecipeParam("driver")) host1.eth1 = DeviceReq(label="tnet", driver=RecipeParam("driver")) host2 = HostReq() host2.eth0 = DeviceReq(label="tnet", driver=RecipeParam("driver")) host2.eth1 = DeviceReq(label="tnet", driver=RecipeParam("driver")) offload_combinations = Param(default=( dict(gro="on", gso="on", tso="on", tx="on"), dict(gro="off", gso="on", tso="on", tx="on"), dict(gro="on", gso="off", tso="off", tx="on"), dict(gro="on", gso="on", tso="off", tx="off"))) perf_reverse = BoolParam(default=True) runner_name = StrParam(mandatory=True) def test_wide_configuration(self): host1, host2 = self.matched.host1, self.matched.host2 net_addr_1 = "192.168.10" net_addr6_1 = "fc00:0:0:1" for i, host in enumerate([host1, host2]): #The config argument needs to be used with a team device #normally (e.g to specify the runner mode), but it is not used #here due to a bug in the TeamDevice module host.team0 = TeamDevice() for dev in [host.eth0, host.eth1]: dev.down() host.team0.slave_add(dev) host.team0.ip_add(ipaddress(net_addr_1 + "." + str(i+1) + "/24")) host.team0.ip_add(ipaddress(net_addr6_1 + "::" + str(i+1) + "/64")) for dev in [host.eth0, host.eth1, host.team0]: dev.up() configuration = super().test_wide_configuration() configuration.test_wide_devices = [host1.team0, host2.team0] self.wait_tentative_ips(configuration.test_wide_devices) return configuration def generate_test_wide_description(self, config): host1, host2 = self.matched.host1, self.matched.host2 desc = super().generate_test_wide_description(config) desc += [ "\n".join([ "Configured {}.{}.ips = {}".format( dev.host.hostid, dev.name, dev.ips ) for dev in config.test_wide_devices ]), "\n".join([ "Configured {}.{}.slaves = {}".format( dev.host.hostid, dev.name, ['.'.join([dev.host.hostid, slave.name]) for slave in dev.slaves] ) for dev in config.test_wide_devices ]), "\n".join([ "Configured {}.{}.runner_name = {}".format( dev.host.hostid, dev.name, dev.config ) for dev in config.test_wide_devices ]) ] return desc def test_wide_deconfiguration(self, config): del config.test_wide_devices super().test_wide_deconfiguration(config) def generate_ping_endpoints(self, config): return [(self.matched.host1.team0, self.matched.host2.team0), (self.matched.host2.team0, self.matched.host1.team0)] def generate_perf_endpoints(self, config): return [(self.matched.host1.team0, self.matched.host2.team0), (self.matched.host2.team0, self.matched.host1.team0)] def wait_tentative_ips(self, devices): def condition(): return all( [not ip.is_tentative for dev in devices for ip in dev.ips] ) self.ctl.wait_for_condition(condition, timeout=5) @property def offload_nics(self): return [self.matched.host1.team0, self.matched.host2.team0] @property def mtu_hw_config_dev_list(self): return [self.matched.host1.team0, self.matched.host2.team0] @property def coalescing_hw_config_dev_list(self): host1, host2 = self.matched.host1, self.matched.host2 return [host1.eth0, host1.eth1, host2.eth0, host2.eth1] @property def dev_interrupt_hw_config_dev_list(self): host1, host2 = self.matched.host1, self.matched.host2 return [host1.eth0, host1.eth1, host2.eth0, host2.eth1] @property def parallel_stream_qdisc_hw_config_dev_list(self): host1, host2 = self.matched.host1, self.matched.host2 return [host1.eth0, host1.eth1, host2.eth0, host2.eth1]
class VlansOverTeamRecipe(PerfReversibleFlowMixin, VlanPingEvaluatorMixin, CommonHWSubConfigMixin, OffloadSubConfigMixin, BaseEnrtRecipe): host1 = HostReq() host1.eth0 = DeviceReq(label="tnet", driver=RecipeParam("driver")) host1.eth1 = DeviceReq(label="tnet", driver=RecipeParam("driver")) host2 = HostReq() host2.eth0 = DeviceReq(label="tnet", driver=RecipeParam("driver")) offload_combinations = Param( default=(dict(gro="on", gso="on", tso="on", tx="on"), dict(gro="off", gso="on", tso="on", tx="on"), dict(gro="on", gso="off", tso="off", tx="on"), dict(gro="on", gso="on", tso="off", tx="off"))) runner_name = StrParam(mandatory=True) def test_wide_configuration(self): host1, host2 = self.matched.host1, self.matched.host2 host1.team0 = TeamDevice( config={'runner': { 'name': self.params.runner_name }}) for dev in [host1.eth0, host1.eth1]: dev.down() host1.team0.slave_add(dev) host1.vlan0 = VlanDevice(realdev=host1.team0, vlan_id=10) host1.vlan1 = VlanDevice(realdev=host1.team0, vlan_id=20) host1.vlan2 = VlanDevice(realdev=host1.team0, vlan_id=30) host2.vlan0 = VlanDevice(realdev=host2.eth0, vlan_id=10) host2.vlan1 = VlanDevice(realdev=host2.eth0, vlan_id=20) host2.vlan2 = VlanDevice(realdev=host2.eth0, vlan_id=30) configuration = super().test_wide_configuration() configuration.test_wide_devices = [] for host in [host1, host2]: configuration.test_wide_devices.extend( [host.vlan0, host.vlan1, host.vlan2]) configuration.test_wide_devices.append(host1.team0) net_addr = "192.168" net_addr6 = "fc00:0:0" for i, host in enumerate([host1, host2]): host.vlan0.ip_add(ipaddress('{}.10.{}/24'.format(net_addr, i + 1))) host.vlan1.ip_add(ipaddress('{}.20.{}/24'.format(net_addr, i + 1))) host.vlan2.ip_add(ipaddress('{}.30.{}/24'.format(net_addr, i + 1))) host.vlan0.ip_add(ipaddress('{}:1::{}/64'.format(net_addr6, i + 1))) host.vlan1.ip_add(ipaddress('{}:2::{}/64'.format(net_addr6, i + 1))) host.vlan2.ip_add(ipaddress('{}:3::{}/64'.format(net_addr6, i + 1))) for dev in [ host1.eth0, host1.eth1, host1.team0, host1.vlan0, host1.vlan1, host1.vlan2, host2.eth0, host2.vlan0, host2.vlan1, host2.vlan2 ]: dev.up() self.wait_tentative_ips(configuration.test_wide_devices) return configuration def generate_test_wide_description(self, config): host1, host2 = self.matched.host1, self.matched.host2 desc = super().generate_test_wide_description(config) desc += [ "\n".join([ "Configured {}.{}.ips = {}".format(dev.host.hostid, dev.name, dev.ips) for dev in config.test_wide_devices if isinstance(dev, Vlan) ]), "\n".join([ "Configured {}.{}.vlan_id = {}".format(dev.host.hostid, dev.name, dev.vlan_id) for dev in config.test_wide_devices if isinstance(dev, Vlan) ]), "\n".join([ "Configured {}.{}.realdev = {}".format( dev.host.hostid, dev.name, '.'.join([dev.host.hostid, dev.realdev.name])) for dev in config.test_wide_devices if isinstance(dev, Vlan) ]), "Configured {}.{}.slaves = {}".format( host1.hostid, host1.team0.name, [ '.'.join([host1.hostid, slave.name]) for slave in host1.team0.slaves ]), "Configured {}.{}.runner_name = {}".format(host1.hostid, host1.team0.name, host1.team0.config) ] return desc def test_wide_deconfiguration(self, config): del config.test_wide_devices super().test_wide_deconfiguration(config) def generate_ping_endpoints(self, config): host1, host2 = self.matched.host1, self.matched.host2 return [ PingEndpoints(host1.vlan0, host2.vlan0), PingEndpoints(host1.vlan1, host2.vlan1), PingEndpoints(host1.vlan2, host2.vlan2) ] def generate_perf_endpoints(self, config): return [(self.matched.host1.vlan0, self.matched.host2.vlan0)] @property def offload_nics(self): host1, host2 = self.matched.host1, self.matched.host2 return [host1.eth0, host1.eth1, host2.eth0] @property def mtu_hw_config_dev_list(self): host1, host2 = self.matched.host1, self.matched.host2 result = [] for host in [host1, host2]: for dev in [host.vlan0, host.vlan1, host.vlan2]: result.append(dev) result.extend([host1.team0, host2.eth0]) return result @property def coalescing_hw_config_dev_list(self): host1, host2 = self.matched.host1, self.matched.host2 return [host1.eth0, host1.eth1, host2.eth0] @property def dev_interrupt_hw_config_dev_list(self): host1, host2 = self.matched.host1, self.matched.host2 return [host1.eth0, host1.eth1, host2.eth0] @property def parallel_stream_qdisc_hw_config_dev_list(self): host1, host2 = self.matched.host1, self.matched.host2 return [host1.eth0, host1.eth1, host2.eth0]