Example #1
0
class TRexClient(TRexCommon):
    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 __init__(self, **kwargs):
        super(TRexClient, self).__init__(**kwargs)
        self.impl = TRexCli(self.params)

    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
Example #2
0
File: TRex.py Project: pgagne/lnst
class TRexServer(TRexCommon):
    #TODO make ListParam
    flows = Param(mandatory=True)

    cores = Param(mandatory=True)

    def __init__(self, **kwargs):
        super(TRexServer, self).__init__(**kwargs)
        self.impl = TRexSrv(self.params)

    def __repr__(self):
        string = f"""
            TrexServer(
                trex_dir={self.impl.params.trex_dir},
                cores={self.impl.params.cores},
                flows={pformat(self.impl.params.flows)}
            )
        """
        return string

    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
Example #3
0
class ShortLivedConnectionsRecipe(CommonHWSubConfigMixin, BaseEnrtRecipe):
    host1 = HostReq()
    host1.eth0 = DeviceReq(label="to_switch", driver=RecipeParam("driver"))

    host2 = HostReq()
    host2.eth0 = DeviceReq(label="to_switch", driver=RecipeParam("driver"))

    perf_tests = Param(default=("TCP_RR", "TCP_CRR"))
    ip_versions = Param(default=("ipv4",))
    perf_parallel_streams = IntParam(default=2)
    perf_msg_sizes = ListParam(default=[1000, 5000, 7000, 10000, 12000])

    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.101"
        for i, host in enumerate([host1, host2], 10):
            host.eth0.down()
            host.eth0.ip_add(ipaddress(net_addr + "." + str(i) + "/24"))
            host.eth0.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
            ])
        ]
        return desc

    def test_wide_deconfiguration(self, config):
        del config.test_wide_devices

        super().test_wide_deconfiguration(config)

    def generate_perf_endpoints(self, config):
        return [(self.matched.host1.eth0, self.matched.host2.eth0)]

    @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]
Example #4
0
 def __init__(self, **kwargs):
     self.params = Parameters()
     for name, val in kwargs.items():
         if name == "params":
             raise RequirementError("'params' is a reserved keyword.")
         p = Param()
         p.val = val
         setattr(self.params, name, p)
Example #5
0
class TRexServer(TRexCommon):
    #TODO make ListParam
    flows = Param(mandatory=True)

    cores = Param(mandatory=True)

    def run(self):
        trex_server_conf = [{
            'port_limit': len(self.params.flows),
            'version': 2,
            'interfaces': [],
            'platform': {
                'dual_if': [{
                    'socket': 0,
                    'threads': self.params.cores
                }],
                'latency_thread_id': 0,
                'master_thread_id': 1
            },
            'port_info': []
        }]

        for src, dst in self.params.flows:
            short_pci_addr = src["pci_addr"].partition(':')[2]
            trex_server_conf[0]['interfaces'].append(short_pci_addr)
            trex_server_conf[0]['port_info'].append({
                'src_mac':
                str(src["mac_addr"]),
                'dest_mac':
                str(dst["mac_addr"])
            })

        with tempfile.NamedTemporaryFile(mode="w+") as cfg_file:
            yaml.dump(trex_server_conf, cfg_file)
            cfg_file.flush()
            os.fsync(cfg_file.file.fileno())

            os.chdir(self.params.trex_dir)
            server = subprocess.Popen([
                os.path.join(self.params.trex_dir, "t-rex-64"), "--cfg",
                cfg_file.name, "-i"
            ],
                                      stdin=open('/dev/null'),
                                      stdout=open('/dev/null', 'w'),
                                      stderr=subprocess.PIPE,
                                      close_fds=True)

            self.wait_for_interrupt()

            server.send_signal(signal.SIGINT)
            out, err = server.communicate()
            if err:
                logging.error(err)
                return False

        return True
Example #6
0
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 HypervisorsStatCPUMeasurementGenerator(BaseMeasurementGenerator):
    cpu_perf_tool = Param(default=StatCPUMeasurement)

    def generate_perf_measurements_combinations(self, config):
        combinations = super().generate_perf_measurements_combinations(config)
        for combination in combinations:
            yield [self.params.cpu_perf_tool(self.hypervisor_hosts)
                   ] + combination

    @property
    def hypervisor_hosts(self):
        return set()
Example #8
0
class FlowEndpointsStatCPUMeasurementGenerator(BaseMeasurementGenerator):
    cpu_perf_tool = Param(default=StatCPUMeasurement)

    def generate_perf_measurements_combinations(self, config):
        combinations = super().generate_perf_measurements_combinations(config)
        for combination in combinations:
            cpu_measurement_hosts = self.extract_endpoints(config, combination)
            yield [self.params.cpu_perf_tool(cpu_measurement_hosts)
                   ] + combination

    def extract_endpoints(self, config, measurements):
        endpoints = set()
        for measurement in measurements:
            if isinstance(measurement, BaseFlowMeasurement):
                for flow in measurement.flows:
                    endpoints.add(flow.generator)
                    endpoints.add(flow.receiver)
        return endpoints
Example #9
0
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
        ]
Example #10
0
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
Example #11
0
class OffloadSubConfigMixin(BaseSubConfigMixin):
    offload_combinations = Param(
        default=(dict(gro="on", gso="on", tso="on", tx="on", rx="on"), ))

    @property
    def offload_nics(self):
        raise NotImplementedError("Subclass must implement this property")

    def generate_sub_configurations(self, config):
        for parent_config in super().generate_sub_configurations(config):
            for offload_settings in self.params.offload_combinations:
                new_config = copy.copy(config)
                new_config.offload_settings = offload_settings

                yield new_config

    def apply_sub_configuration(self, config):
        super().apply_sub_configuration(config)

        offload_settings = getattr(config, "offload_settings", None)
        if offload_settings:
            ethtool_offload_string = ""
            for name, value in list(offload_settings.items()):
                ethtool_offload_string += " %s %s" % (name, value)

            for nic in self.offload_nics:
                if "sctp_stream" in self.params.perf_tests:
                    nic.netns.run(
                        "iptables -I OUTPUT ! -o %s -p sctp -j DROP" %
                        nic.name,
                        job_level=ResultLevel.NORMAL,
                    )

                nic.netns.run(
                    "ethtool -K {} {}".format(nic.name,
                                              ethtool_offload_string),
                    job_level=ResultLevel.NORMAL,
                )

    def generate_sub_configuration_description(self, config):
        description = super().generate_sub_configuration_description(config)
        description.append(
            "Currently configured offload combination: {}".format(" ".join([
                "{}={}".format(k, v)
                for k, v in config.offload_settings.items()
            ])))
        return description

    def remove_sub_configuration(self, config):
        offload_settings = getattr(config, "offload_settings", None)
        if offload_settings:
            ethtool_offload_string = ""
            for name, value in list(offload_settings.items()):
                ethtool_offload_string += " %s %s" % (name, "on")

            for nic in self.offload_nics:
                if "sctp_stream" in self.params.perf_tests:
                    nic.netns.run(
                        "iptables -D OUTPUT ! -o %s -p sctp -j DROP" %
                        nic.name,
                        job_level=ResultLevel.NORMAL,
                    )

                # set all the offloads back to 'on' state
                nic.netns.run(
                    "ethtool -K {} {}".format(nic.name,
                                              ethtool_offload_string),
                    job_level=ResultLevel.NORMAL,
                )

        return super().remove_sub_configuration(config)

    def generate_flow_combinations(self, config):
        for flows in super().generate_flow_combinations(config):
            if self._check_test_offload_conflicts(config, flows):
                # TODO log skip
                continue
            else:
                yield flows

    def _check_test_offload_conflicts(self, config, flows):
        for flow in flows:
            if (flow.type == "udp_stream"
                    and config.offload_settings.get("gro", "on") == "off"):
                return True
            elif (flow.type == "sctp_stream"
                  and "off" in config.offload_settings.values()
                  and config.offload_settings.get("gso", "on") == "on"):
                return True
        return False
Example #12
0
class GeneveTunnelRecipe(PauseFramesHWConfigMixin, OffloadSubConfigMixin,
                         BaseTunnelRecipe):
    """
    This class implements a recipe that configures a simple Geneve tunnel
    between two hosts.

    .. code-block:: none

                        .--------.
                 .------| switch |-----.
                 |      '--------'     |
                 |                     |
         .-------|------.      .-------|------.
         |    .--'-.    |      |    .--'-.    |
         |    |eth0|    |      |    |eth0|    |
         |    '----'    |      |    '----'    |
         |      | |     |      |      | |     |
         |  ----' '---  |      |  ----' '---  |
         |  gnv tunnel  |      |  gnv tunnel  |
         |  ----------  |      |  ----------  |
         |              |      |              |
         |    host1     |      |    host2     |
         '--------------'      '--------------'

    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 carrier_ipversion:
            This parameter specifies whether IPv4 or IPv6 addresses are
            used for the underlying (carrier) network. The value is either
            **ipv4** or **ipv6**
    """

    host1 = HostReq()
    host1.eth0 = 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"),
        dict(gro="off", gso="on", tso="on"),
        dict(gro="on", gso="off", tso="off"),
        dict(gro="on", gso="on", tso="off"),
    ))

    carrier_ipversion = ChoiceParam(type=StrParam,
                                    choices=set(["ipv4", "ipv6"]))

    def configure_underlying_network(self, configuration):
        """
        The underlying network for the tunnel consists of the Ethernet
        devices on the matched hosts.
        """
        host1, host2 = self.matched.host1, self.matched.host2
        for i, device in enumerate([host1.eth0, host2.eth0]):
            if self.params.carrier_ipversion == "ipv4":
                device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24"))
            else:
                device.ip_add(ipaddress("fc00::" + str(i + 1) + "/64"))
            device.up()
            configuration.test_wide_devices.append(device)

        self.wait_tentative_ips(configuration.test_wide_devices)
        configuration.tunnel_endpoints = (host1.eth0, host2.eth0)

    def create_tunnel(self, configuration):
        """
        The Geneve tunnel devices are configured with IPv4 and IPv6 addresses.
        """
        endpoint1, endpoint2 = configuration.tunnel_endpoints
        m1 = endpoint1.netns
        m2 = endpoint2.netns
        if self.params.carrier_ipversion == "ipv4":
            ip_filter = {"family": AF_INET}
        else:
            ip_filter = {"family": AF_INET6, "is_link_local": False}

        endpoint1_ip = endpoint1.ips_filter(**ip_filter)[0]
        endpoint2_ip = endpoint2.ips_filter(**ip_filter)[0]

        a_ip4 = Ip4Address("20.0.0.10/8")
        b_ip4 = Ip4Address("20.0.0.20/8")

        a_ip6 = Ip6Address("fee0::10/64")
        b_ip6 = Ip6Address("fee0::20/64")

        m1.gnv_tunnel = GeneveDevice(remote=endpoint2_ip, id=1234)
        m2.gnv_tunnel = GeneveDevice(remote=endpoint1_ip, id=1234)

        # A
        m1.gnv_tunnel.mtu = 1400
        m1.gnv_tunnel.up()
        m1.gnv_tunnel.ip_add(a_ip4)
        m1.gnv_tunnel.ip_add(a_ip6)

        # B
        m2.gnv_tunnel.mtu = 1400
        m2.gnv_tunnel.up()
        m2.gnv_tunnel.ip_add(b_ip4)
        m2.gnv_tunnel.ip_add(b_ip6)

        configuration.tunnel_devices.extend([m1.gnv_tunnel, m2.gnv_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.gnv_tunnel, self.matched.host2.gnv_tunnel)]
        """
        return [
            PingEndpoints(self.matched.host1.gnv_tunnel,
                          self.matched.host2.gnv_tunnel)
        ]

    def get_packet_assert_config(self, ping_config):
        """
        The packet assert test configuration contains filter for ip6 protocol
        and grep patterns to match the ICMP or ICMP6 echo requests encapsulated
        by Geneve.
        """
        if self.params.carrier_ipversion == "ipv4":
            ip_filter = {"family": AF_INET}
        else:
            ip_filter = {"family": AF_INET6, "is_link_local": False}

        m1_carrier = self.matched.host1.eth0
        m2_carrier = self.matched.host2.eth0
        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 = {}
        if self.params.carrier_ipversion == "ipv4":
            pa_kwargs["p_filter"] = "ip host {}".format(m1_carrier_ip)
            grep_pattern = "IP "
        else:
            pa_kwargs["p_filter"] = "ip6"
            grep_pattern = "IP6 "

        grep_pattern += "{}\.[0-9]+ > {}\.[0-9]+: Geneve.*vni 0x4d2: ".format(
            m1_carrier_ip, m2_carrier_ip)

        if isinstance(ip2, Ip4Address):
            grep_pattern += "IP {} > {}: ICMP".format(ip1, ip2)
        elif isinstance(ip2, Ip6Address):
            grep_pattern += "IP6 {} > {}: ICMP6".format(ip1, ip2)

        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.eth0, self.matched.host2.eth0]

    @property
    def pause_frames_dev_list(self):
        return [self.matched.host1.eth0, self.matched.host2.eth0]
Example #13
0
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]
Example #14
0
class NeperMeasurementGenerator(BaseMeasurementGenerator):

    perf_tests = Param(default=("tcp_rr", "tcp_crr", "udp_rr"))
    perf_duration = IntParam(default=60)
    perf_iterations = IntParam(default=5)
    perf_tool_cpu = IntParam(mandatory=False)
    perf_parallel_streams = IntParam(default=1)
    perf_msg_sizes = ListParam(default=[123])

    net_perf_tool = Param(default=NeperFlowMeasurement)

    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_flow(
                                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_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`
        """
        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=(self.params.perf_tool_cpu
                    if "perf_tool_cpu" in self.params else None),
        )
class VirtualOvsBridgeVlanInGuestMirroredRecipe(CommonHWSubConfigMixin,
                                                OffloadSubConfigMixin,
                                                VirtualEnrtRecipe):
    host1 = HostReq()
    host1.eth0 = DeviceReq(label="to_switch", driver=RecipeParam("driver"))
    host1.tap0 = DeviceReq(label="to_guest1")

    host2 = HostReq()
    host2.eth0 = DeviceReq(label="to_switch", driver=RecipeParam("driver"))
    host2.tap0 = DeviceReq(label="to_guest2")

    guest1 = HostReq()
    guest1.eth0 = DeviceReq(label="to_guest1")

    guest2 = HostReq()
    guest2.eth0 = DeviceReq(label="to_guest2")

    offload_combinations = Param(
        default=(dict(gro="on", gso="on", tso="on", tx="on", rx="on"),
                 dict(gro="off", gso="on", tso="on", tx="on", rx="on"),
                 dict(gro="on", gso="off", tso="off", tx="on", rx="on"),
                 dict(gro="on", gso="on", tso="off", tx="off", rx="on"),
                 dict(gro="on", gso="on", tso="on", tx="on", rx="off")))

    def test_wide_configuration(self):
        host1, host2, guest1, guest2 = (self.matched.host1, self.matched.host2,
                                        self.matched.guest1,
                                        self.matched.guest2)

        for host in [host1, host2]:
            host.eth0.down()
            host.tap0.down()
            host.br0 = OvsBridgeDevice()
            for dev in [host.eth0, host.tap0]:
                host.br0.port_add(device=dev)

        guest1.eth0.down()
        guest2.eth0.down()

        guest1.vlan0 = VlanDevice(realdev=guest1.eth0, vlan_id=10)
        guest2.vlan0 = VlanDevice(realdev=guest2.eth0, vlan_id=10)

        configuration = super().test_wide_configuration()
        configuration.test_wide_devices = [guest1.vlan0, guest2.vlan0]

        net_addr_1 = "192.168.10"
        net_addr6_1 = "fc00:0:0:1"
        for i, guest in enumerate([guest1, guest2]):
            guest.vlan0.ip_add(ipaddress(net_addr_1 + "." + str(i + 3) +
                                         "/24"))
            guest.vlan0.ip_add(
                ipaddress(net_addr6_1 + "::" + str(i + 3) + "/64"))

        for host in [host1, host2]:
            for dev in [host.eth0, host.tap0, host.br0]:
                dev.up()
        for guest in [guest1, guest2]:
            guest.eth0.up()
            guest.vlan0.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 {}.{}.vlan_id = {}".format(dev.host.hostid,
                                                       dev.name, dev.vlan_id)
                for dev in config.test_wide_devices
            ]), "\n".join([
                "Configured {}.{}.realdev = {}".format(
                    dev.host.hostid, dev.name,
                    '.'.join([dev.host.hostid, dev.realdev.name]))
                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]
            ])
        ]
        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.guest1.vlan0, self.matched.guest2.vlan0)
        ]

    def generate_perf_endpoints(self, config):
        return [(self.matched.guest1.vlan0, self.matched.guest2.vlan0)]

    @property
    def offload_nics(self):
        return [
            self.matched.host1.eth0, self.matched.host2.eth0,
            self.matched.guest1.eth0, self.matched.guest2.eth0
        ]

    @property
    def mtu_hw_config_dev_list(self):
        host1, host2, guest1, guest2 = (self.matched.host1, self.matched.host2,
                                        self.matched.guest1,
                                        self.matched.guest2)
        result = []
        for host in [host1, host2]:
            for dev in [host.eth0, host.tap0, host.br0]:
                result.append(dev)
        for guest in [guest1, guest2]:
            result.extend([guest.eth0, guest.vlan0])
        return result

    @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]
Example #16
0
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]
Example #17
0
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
Example #18
0
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
Example #19
0
class VxlanGpeTunnelRecipe(
    PauseFramesHWConfigMixin, OffloadSubConfigMixin, BaseTunnelRecipe
):
    """
    This class implements a recipe that configures a simple Vxlan GPE
    tunnel between two hosts.

    .. code-block:: none

                        .--------.
                 .------| switch |-----.
                 |      '--------'     |
                 |                     |
         .-------|------.      .-------|------.
         |    .--'-.    |      |    .--'-.    |
         |    |eth0|    |      |    |eth0|    |
         |    '----'    |      |    '----'    |
         |      | |     |      |      | |     |
         |  ----' '---  |      |  ----' '---  |
         | vxlan tunnel |      | vxlan tunnel |
         |  ----------  |      |  ----------  |
         |              |      |              |
         |    host1     |      |    host2     |
         '--------------'      '--------------'

    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 carrier_ipversion:
            This parameter specifies whether IPv4 or IPv6 addresses are
            used for the underlying (carrier) network. The value is either
            **ipv4** or **ipv6**
    """

    host1 = HostReq()
    host1.eth0 = 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"),
            dict(gro="off", gso="on", tso="on"),
            dict(gro="on", gso="off", tso="off"),
            dict(gro="on", gso="on", tso="off"),
        )
    )

    # TODO: ping over IPv6 does not work yet
    ip_versions = Param(default=("ipv4",))
    # TODO: IPv6 does not work as carrier network
    carrier_ipversion = ChoiceParam(type=StrParam, choices=set(["ipv4"]))

    def configure_underlying_network(self, configuration):
        """
        The underlying network for the tunnel consists of the Ethernet
        devices on the matched hosts.
        """
        host1, host2 = self.matched.host1, self.matched.host2
        for i, device in enumerate([host1.eth0, host2.eth0]):
            if self.params.carrier_ipversion == "ipv4":
                device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24"))
            else:
                device.ip_add(ipaddress("fc00::" + str(i + 1) + "/64"))
            device.up()
            configuration.test_wide_devices.append(device)

        self.wait_tentative_ips(configuration.test_wide_devices)
        configuration.tunnel_endpoints = (host1.eth0, host2.eth0)

    def create_tunnel(self, configuration):
        """
        The Vxlan tunnel devices are created with external flag specified
        so that the encapsulation can be defined externally by routes.

        The devices are configured to use VXLAN-GPE.

        Routes for IPv4 and IPv6 networks to be tunneled through the Vxlan are
        added.

        IPv4 and IPv6 addresses of the tunneled networks are configured on
        the loopback devices of the matched hosts.

        """
        endpoint1, endpoint2 = configuration.tunnel_endpoints
        m1 = endpoint1.netns
        m2 = endpoint2.netns
        if self.params.carrier_ipversion == "ipv4":
            ip_filter = {"family": AF_INET}
        else:
            ip_filter = {"family": AF_INET6, "is_link_local": False}

        endpoint1_ip = endpoint1.ips_filter(**ip_filter)[0]
        endpoint2_ip = endpoint2.ips_filter(**ip_filter)[0]

        m1_dummy_ip = ipaddress("172.16.10.1/32")
        m1_dummy_ip6 = ipaddress("fc00:a::1/128")
        m2_dummy_ip = ipaddress("172.16.20.1/32")
        m2_dummy_ip6 = ipaddress("fc00:b::1/128")

        m1.vxlan_tunnel = VxlanDevice(external=True, gpe=True, learning=0)
        m2.vxlan_tunnel = VxlanDevice(external=True, gpe=True, learning=0)
        m1.lo = LoopbackDevice()
        m2.lo = LoopbackDevice()

        # A
        m1.lo.ip_add(m1_dummy_ip)
        m1.lo.ip_add(m1_dummy_ip6)
        m1.vxlan_tunnel.mtu = 1400
        m1.vxlan_tunnel.up()

        # B
        m2.lo.ip_add(m2_dummy_ip)
        m2.lo.ip_add(m2_dummy_ip6)
        m2.vxlan_tunnel.mtu = 1400
        m2.vxlan_tunnel.up()

        tunnel_id = 1234
        encap = "ip" if self.params.carrier_ipversion == "ipv4" else "ip6"
        m1.run(
            "ip route add {} encap {} id {} dst {} dev {}".format(
                m2_dummy_ip, encap, tunnel_id, endpoint2_ip, m1.vxlan_tunnel.name
            )
        )
        m2.run(
            "ip route add {} encap {} id {} dst {} dev {}".format(
                m1_dummy_ip, encap, tunnel_id, endpoint1_ip, m2.vxlan_tunnel.name
            )
        )
        m1.run(
            "ip route add {} encap {} id {} dst {} dev {}".format(
                m2_dummy_ip6, encap, tunnel_id, endpoint2_ip, m1.vxlan_tunnel.name
            )
        )
        m2.run(
            "ip route add {} encap {} id {} dst {} dev {}".format(
                m1_dummy_ip6, encap, tunnel_id, endpoint1_ip, m2.vxlan_tunnel.name
            )
        )

        configuration.tunnel_devices.extend([m1.vxlan_tunnel, m2.vxlan_tunnel])
        self.wait_tentative_ips([m1.lo, m2.lo])

    def generate_ping_endpoints(self, config):
        """
        The ping endpoints for this recipe are the loopback devices that
        are configured with IP addresses of the tunnelled networks.

        Returned as::

            [PingEndpoints(self.matched.host1.lo, self.matched.host2.lo)]
        """
        return [PingEndpoints(self.matched.host1.lo, self.matched.host2.lo)]

    def get_packet_assert_config(self, ping_config):
        """
        The packet assert test configuration contains filter for source
        and destination addresses matching the carrier network with udp
        header bits specific to VXLAN tunneling. The grep patterns match
        the ICMP or ICMP6 echo requests encapsulated by Vxlan with
        VXLAN-GPE extension.
        """
        if self.params.carrier_ipversion == "ipv4":
            ip_filter = {"family": AF_INET}
        else:
            ip_filter = {"family": AF_INET6, "is_link_local": False}

        m1_carrier = self.matched.host1.eth0
        m2_carrier = self.matched.host2.eth0
        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"] = "src {} and dst {}".format(m2_carrier_ip, m1_carrier_ip)

        if isinstance(ip2, Ip4Address):
            grep_pattern = "VXLAN-GPE.*IP {} > {}: ICMP echo reply".format(ip2, ip1)
        elif isinstance(ip2, Ip6Address):
            grep_pattern = "VXLAN-GPE.*IP6 {} > {}: ICMP6, echo reply".format(ip2, ip1)
        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.eth0, self.matched.host2.eth0]

    @property
    def pause_frames_dev_list(self):
        return [self.matched.host1.eth0, self.matched.host2.eth0]
Example #20
0
class VlansRecipe(CommonHWSubConfigMixin, OffloadSubConfigMixin,
                  BaseEnrtRecipe):
    host1 = HostReq()
    host1.eth0 = 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", rx="on"),
                 dict(gro="off", gso="on", tso="on", tx="on", rx="on"),
                 dict(gro="on", gso="off", tso="off", tx="on", rx="on"),
                 dict(gro="on", gso="on", tso="off", tx="off", rx="on"),
                 dict(gro="on", gso="on", tso="on", tx="on", rx="off")))

    def test_wide_configuration(self):
        host1, host2 = self.matched.host1, self.matched.host2

        host1.eth0.down()
        host2.eth0.down()

        host1.vlan0 = VlanDevice(realdev=host1.eth0, vlan_id=10)
        host1.vlan1 = VlanDevice(realdev=host1.eth0, vlan_id=20)
        host1.vlan2 = VlanDevice(realdev=host1.eth0, 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])

        net_addr = "192.168"
        net_addr6 = "fc00:0:0"

        for i, host in enumerate([host1, host2]):
            host.vlan0.ip_add(
                ipaddress(net_addr + '.10' + '.' + str(i + 1) + "/24"))
            host.vlan0.ip_add(
                ipaddress(net_addr6 + ":1::" + str(i + 1) + "/64"))
            host.vlan1.ip_add(
                ipaddress(net_addr + '.20' + '.' + str(i + 1) + "/24"))
            host.vlan1.ip_add(
                ipaddress(net_addr6 + ":2::" + str(i + 1) + "/64"))
            host.vlan2.ip_add(
                ipaddress(net_addr + '.30' + '.' + str(i + 1) + "/24"))
            host.vlan2.ip_add(
                ipaddress(net_addr6 + ":3::" + str(i + 1) + "/64"))
            for dev in [host.eth0, host.vlan0, host.vlan1, host.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
            ]), "\n".join([
                "Configured {}.{}.vlan_id = {}".format(dev.host.hostid,
                                                       dev.name, dev.vlan_id)
                for dev in config.test_wide_devices
            ]), "\n".join([
                "Configured {}.{}.realdev = {}".format(
                    dev.host.hostid, dev.name,
                    '.'.join([dev.host.hostid, dev.realdev.name]))
                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):
        host1, host2 = self.matched.host1, self.matched.host2
        result = []
        for src in [host1.vlan0, host1.vlan1, host1.vlan2]:
            for dst in [host2.vlan0, host2.vlan1, host2.vlan2]:
                result += [(src, dst)]
        return result

    def generate_perf_endpoints(self, config):
        return [(self.matched.host1.vlan0, self.matched.host2.vlan0)]

    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.eth0, self.matched.host2.eth0]

    @property
    def mtu_hw_config_dev_list(self):
        result = []
        for host in [self.matched.host1, self.matched.host2]:
            for dev in [host.eth0, host.vlan0, host.vlan1, host.vlan2]:
                result.append(dev)
        return result

    @property
    def coalescing_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]

    def do_ping_tests(self, recipe_config):
        for ping_config in self.generate_ping_configurations(recipe_config):
            exp_fail = []
            for pconf in ping_config:
                cond = self.vlan_id_same(pconf.client_bind,
                                         pconf.destination_address)
                exp_fail.append(cond)
            result = self.ping_test(ping_config, exp_fail)
            self.ping_evaluate_and_report(ping_config, result)

    def ping_test(self, ping_config, exp_fail):
        results = {}

        running_ping_array = []
        for pingconf, fail in zip(ping_config, exp_fail):
            ping, client = self.ping_init(pingconf)
            running_ping = client.prepare_job(ping, fail=fail)
            running_ping.start(bg=True)
            running_ping_array.append((pingconf, running_ping))

        for _, pingjob in running_ping_array:
            try:
                pingjob.wait()
            finally:
                pingjob.kill()

        for pingconf, pingjob in running_ping_array:
            result = pingjob.result
            passed = pingjob.passed
            results[pingconf] = (result, passed)

        return results

    def single_ping_evaluate_and_report(self, ping_config, result):
        fmt = "From: <{0.client.hostid} ({0.client_bind})> To: " \
              "<{0.destination.hostid} ({0.destination_address})>"
        description = fmt.format(ping_config)
        if result[0].get("rate", 0) > 50:
            message = "Ping successful --- " + description
            self.add_result(result[1], message, result[0])
        else:
            message = "Ping unsuccessful --- " + description
            self.add_result(result[1], message, result[0])

    def vlan_id_same(self, src_addr, dst_addr):
        host1, host2 = self.matched.host1, self.matched.host2
        devs = []
        for dev in (host1.devices + host2.devices):
            if src_addr in dev.ips or dst_addr in dev.ips:
                devs.append(dev)
        try:
            return devs[0].vlan_id != devs[1].vlan_id
        except (IndexError, AttributeError):
            return False
Example #21
0
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
        ]
Example #22
0
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,
        )
Example #23
0
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]
Example #24
0
class VlansRecipe(VlanPingEvaluatorMixin, CommonHWSubConfigMixin,
                  OffloadSubConfigMixin, BaremetalEnrtRecipe):
    """
    This recipe implements Enrt testing for a network scenario that looks
    as follows

    .. code-block:: none

                             .--------.
                    .--------+ switch +-------.
                    |        '--------'       |
                .---'--.                   .--'---.
        .-------| eth0 |------.    .-------| eth0 |------.
        |       '------'      |    |       '------'      |
        |      /   |    \     |    |      /   |    \     |
        | vlan0  vlan1  vlan2 |    | vlan0  vlan1  vlan2 |
        | id=10  id=20  id=30 |    | id=10  id=20  id=30 |
        |                     |    |                     |
        |        host1        |    |        host2        |
        '---------------------'    '---------------------'

    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"))

    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", rx="on"),
                 dict(gro="off", gso="on", tso="on", tx="on", rx="on"),
                 dict(gro="on", gso="off", tso="off", tx="on", rx="on"),
                 dict(gro="on", gso="on", tso="off", tx="off", rx="on"),
                 dict(gro="on", gso="on", tso="on", tx="on", rx="off")))

    def test_wide_configuration(self):
        """
        Test wide configuration for this recipe involves creating three
        VLAN (802.1Q) tunnels on top of the matched host's NIC with vlan
        ids from parameters vlan0_id, vlan1_id and vlan2_id (by default: 10, 20,
        30). The same tunnels are configured on the second host.

        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.eth0.down()
        host2.eth0.down()

        host1.vlan0 = VlanDevice(realdev=host1.eth0,
                                 vlan_id=self.params.vlan0_id)
        host1.vlan1 = VlanDevice(realdev=host1.eth0,
                                 vlan_id=self.params.vlan1_id)
        host1.vlan2 = VlanDevice(realdev=host1.eth0,
                                 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])

        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 [host.eth0, host.vlan0, host.vlan1, host.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
        and their IP addresses
        """
        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 {}.{}.vlan_id = {}".format(dev.host.hostid,
                                                       dev.name, dev.vlan_id)
                for dev in config.test_wide_devices
            ]), "\n".join([
                "Configured {}.{}.realdev = {}".format(
                    dev.host.hostid, dev.name,
                    '.'.join([dev.host.hostid, dev.realdev.name]))
                for dev in config.test_wide_devices
            ])
        ]
        return desc

    def test_wide_deconfiguration(self, config):
        ""  # overriding the parent docstring
        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 vlan0_id (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 and host2.eth0

        For detailed explanation of this property see :any:`OffloadSubConfigMixin`
        class and :any:`OffloadSubConfigMixin.offload_nics`.
        """
        return [self.matched.host1.eth0, 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 of all configured VLAN tunnel devices and the underlying physical
        devices:

        | host1.eth0, 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`.
        """
        result = []
        for host in [self.matched.host1, self.matched.host2]:
            for dev in [host.eth0, host.vlan0, host.vlan1, host.vlan2]:
                result.append(dev)
        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 and 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.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 and host2.eth0

        For detailed explanation of this property see :any:`DevInterruptHWConfigMixin`
        class and :any:`DevInterruptHWConfigMixin.dev_interrupt_hw_config_dev_list`.
        """
        return [self.matched.host1.eth0, 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 of the physical devices carrying data of the
        configured VLAN tunnels:

        host1.eth0 and 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.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 and 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.host2.eth0]
Example #25
0
class GreTunnelOverVlanRecipe(MTUHWConfigMixin, PauseFramesHWConfigMixin,
                              OffloadSubConfigMixin, BaseTunnelRecipe):
    """
    This class implements a recipe that configures a GRE tunnel between
    two hosts that are connected through a vlan device.

    .. code-block:: none

                        .--------.
                 .------| switch |-----.
                 |      '--------'     |
                 |                     |
         .-------|------.      .-------|------.
         |    .--'-.    |      |    .--'-.    |
         |    |eth0|    |      |    |eth0|    |
         |    '----'    |      |    '----'    |
         |       |      |      |       |      |
         | vlan0(id=10) |      | vlan0(id=10) |
         |      | |     |      |      | |     |
         |      | |     |      |      | |     |
         |  ----' '---  |      |  ----' '---  |
         |  gre tunnel  |      |  gre tunnel  |
         |  ----------  |      |  ----------  |
         |              |      |              |
         |    host1     |      |    host2     |
         '--------------'      '--------------'

    The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class.

    The test wide configuration is implemented in the :any:`BaseTunnelRecipe`
    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"))
    host2.eth1 = DeviceReq(label="net1", driver=RecipeParam("driver"))

    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 on the matched hosts. A VLAN is configured on top of each
        device.
        """
        host1, host2 = self.matched.host1, self.matched.host2

        host1.vlan10 = VlanDevice(realdev=host1.eth0, vlan_id=10)
        host2.vlan10 = VlanDevice(realdev=host2.eth0, vlan_id=10)

        for i, device in enumerate([host1.vlan10, host2.vlan10]):
            device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24"))
            configuration.test_wide_devices.append(device)

        for dev in [
                host1.eth0,
                host1.vlan10,
                host2.eth0,
                host2.vlan10,
        ]:
            dev.up()

        configuration.tunnel_endpoints = (host1.vlan10, host2.vlan10)

    def create_tunnel(self, configuration):
        """
        The GRE tunnel devices are configured with local and remote ip addresses
        matching the VLAN 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.vlan10
        m2_carrier = self.matched.host2.vlan10
        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.eth0, self.matched.host2.eth0]

    @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]
Example #26
0
class VirtualBridgeVlanInGuestRecipe(CommonHWSubConfigMixin,
                                     OffloadSubConfigMixin, BaseEnrtRecipe):
    host1 = HostReq()
    host1.eth0 = DeviceReq(label="to_switch", driver=RecipeParam("driver"))
    host1.tap0 = DeviceReq(label="to_guest")

    host2 = HostReq()
    host2.eth0 = DeviceReq(label="to_switch", driver=RecipeParam("driver"))

    guest1 = HostReq()
    guest1.eth0 = DeviceReq(label="to_guest")

    offload_combinations = Param(
        default=(dict(gro="on", gso="on", tso="on", tx="on", rx="on"),
                 dict(gro="off", gso="on", tso="on", tx="on", rx="on"),
                 dict(gro="on", gso="off", tso="off", tx="on", rx="on"),
                 dict(gro="on", gso="on", tso="off", tx="off", rx="on"),
                 dict(gro="on", gso="on", tso="on", tx="on", rx="off")))

    def test_wide_configuration(self):
        host1, host2, guest1 = (self.matched.host1, self.matched.host2,
                                self.matched.guest1)

        host1.br0 = BridgeDevice()
        for dev in [host1.eth0, host1.tap0]:
            dev.down()
            host1.br0.slave_add(dev)

        host2.eth0.down()
        guest1.eth0.down()

        host2.vlan0 = VlanDevice(realdev=host2.eth0, vlan_id=10)
        guest1.vlan0 = VlanDevice(realdev=guest1.eth0, vlan_id=10)

        configuration = super().test_wide_configuration()
        configuration.test_wide_devices = [
            guest1.vlan0, host1.br0, host2.vlan0
        ]

        net_addr_1 = "192.168.10"
        net_addr6_1 = "fc00:0:0:1"

        host1.br0.ip_add(ipaddress(net_addr_1 + ".1/24"))
        for i, machine in enumerate([host2, guest1]):
            machine.vlan0.ip_add(
                ipaddress(net_addr_1 + "." + str(i + 2) + "/24"))
            machine.vlan0.ip_add(
                ipaddress(net_addr6_1 + "::" + str(i + 2) + "/64"))

        for dev in [
                host1.eth0, host1.tap0, host1.br0, host2.eth0, host2.vlan0,
                guest1.eth0, guest1.vlan0
        ]:
            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 {}.{}.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.br0.name, [
                    '.'.join([host1.hostid, slave.name])
                    for slave in host1.br0.slaves
                ])
        ]
        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.guest1.vlan0, self.matched.host2.vlan0)]

    def generate_perf_endpoints(self, config):
        return [(self.matched.guest1.vlan0, self.matched.host2.vlan0)]

    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.eth0, self.matched.host2.eth0,
            self.matched.guest1.eth0
        ]

    @property
    def mtu_hw_config_dev_list(self):
        host1, host2, guest1 = (self.matched.host1, self.matched.host2,
                                self.matched.guest1)
        return [
            host1.eth0, host1.tap0, host1.br0, guest1.eth0, host2.eth0,
            host2.vlan0, guest1.vlan0
        ]

    @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]
Example #27
0
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,
        )
Example #28
0
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
Example #29
0
class SimpleMacsecRecipe(CommonHWSubConfigMixin, BaremetalEnrtRecipe):
    host1 = HostReq()
    host1.eth0 = DeviceReq(label="to_switch", driver=RecipeParam("driver"))

    host2 = HostReq()
    host2.eth0 = DeviceReq(label="to_switch", driver=RecipeParam("driver"))

    macsec_encryption = Param(default=['on', 'off'])
    ids = ['00', '01']
    keys = [
        "7a16780284000775d4f0a3c0f0e092c0", "3212ef5c4cc5d0e4210b17208e88779e"
    ]

    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.0"
        for i, host in enumerate([host1, host2]):
            host.eth0.down()
            host.eth0.ip_add(ipaddress(net_addr + '.' + str(i + 1) + "/24"))

        self.wait_tentative_ips(configuration.test_wide_devices)

        configuration.endpoint1 = host1.eth0
        configuration.endpoint2 = host2.eth0
        configuration.host1 = host1
        configuration.host2 = host2

        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):
        for subconf in ConfMixin.generate_sub_configurations(self, config):
            for encryption in self.params.macsec_encryption:
                new_config = copy.copy(subconf)
                new_config.encrypt = encryption
                new_config.ip_vers = self.params.ip_versions
                yield new_config

    def apply_sub_configuration(self, config):
        super().apply_sub_configuration(config)
        net_addr = "192.168.100"
        net_addr6 = "fc00:0:0:0"
        host1, host2 = config.host1, config.host2
        k_ids = list(zip(self.ids, self.keys))
        hosts_and_keys = [(host1, host2, k_ids), (host2, host1, k_ids[::-1])]
        for host_a, host_b, k_ids in hosts_and_keys:
            host_a.msec0 = MacsecDevice(realdev=host_a.eth0,
                                        encrypt=config.encrypt)
            rx_kwargs = dict(port=1, address=host_b.eth0.hwaddr)
            tx_sa_kwargs = dict(sa=0,
                                pn=1,
                                enable='on',
                                id=k_ids[0][0],
                                key=k_ids[0][1])
            rx_sa_kwargs = rx_kwargs.copy()
            rx_sa_kwargs.update(tx_sa_kwargs)
            rx_sa_kwargs['id'] = k_ids[1][0]
            rx_sa_kwargs['key'] = k_ids[1][1]
            host_a.msec0.rx('add', **rx_kwargs)
            host_a.msec0.tx_sa('add', **tx_sa_kwargs)
            host_a.msec0.rx_sa('add', **rx_sa_kwargs)
        for i, host in enumerate([host1, host2]):
            host.msec0.ip_add(ipaddress(net_addr + "." + str(i + 1) + "/24"))
            host.msec0.ip_add(ipaddress(net_addr6 + "::" + str(i + 1) + "/64"))
            host.eth0.up()
            host.msec0.up()
            self.wait_tentative_ips([host.eth0, host.msec0])

    def remove_sub_configuration(self, config):
        host1, host2 = config.host1, config.host2
        for host in (host1, host2):
            host.msec0.destroy()
            del host.msec0
        config.endpoint1.down()
        config.endpoint2.down()
        super().remove_sub_configuration(config)

    def generate_ping_configurations(self, config):
        client_nic = config.host1.msec0
        server_nic = config.host2.msec0
        ip_vers = self.params.ip_versions

        count = self.params.ping_count
        interval = self.params.ping_interval
        size = self.params.ping_psize
        common_args = {'count': count, 'interval': interval, 'size': size}

        for ipv in ip_vers:
            kwargs = {}
            if ipv == "ipv4":
                kwargs.update(family=AF_INET)
            elif ipv == "ipv6":
                kwargs.update(family=AF_INET6)
                kwargs.update(is_link_local=False)

            client_ips = client_nic.ips_filter(**kwargs)
            server_ips = server_nic.ips_filter(**kwargs)
            if ipv == "ipv6":
                client_ips = client_ips[::-1]
                server_ips = server_ips[::-1]

            if len(client_ips) != len(server_ips) or (len(client_ips) *
                                                      len(server_ips) == 0):
                raise LnstError("Source/destination ip lists are of "
                                "different size or empty.")

            for src_addr, dst_addr in zip(client_ips, server_ips):
                pconf = PingConf(client=client_nic.netns,
                                 client_bind=src_addr,
                                 destination=server_nic.netns,
                                 destination_address=dst_addr,
                                 **common_args)

                yield [pconf]

    def generate_perf_endpoints(self, config):
        return [(self.matched.host1.msec0, self.matched.host2.msec0)]

    @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]
Example #30
0
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()