Re: [RFC PATCH 14/14] KVM-test: Add subtest of testing offload by ethtool

Amos Kong
2010-08-10T07:07:31Z

On Tue, Aug 3, 2010 at 3:10 AM, Lucas Meneghel Rodrigues <lmr@redhat.com> wrote:
> On Tue, 2010-07-20 at 09:36 +0800, Amos Kong wrote:
>> The latest case contains TX/RX/SG/TSO/GSO/GRO/LRO test. RTL8139 NIC doesn't
>> support TSO, LRO, it's too old, so drop offload test from rtl8139. LRO, GRO
>> are only supported by latest kernel, virtio nic doesn't support receive
>> offloading function.
>> Initialize the callbacks first and execute all the sub tests one by one, all
>> the result will be check at the end.
>> When execute this test, vhost should be enabled, then most of new feature can
>> be used. Vhost doestn't support VIRTIO_NET_F_MRG_RXBUF, so do not check large
>> packets in received offload test.
>> Transfer files by scp between host and guest, match new opened TCP port by
>> netstat. Capture the packages info by tcpdump, it contains package length.
>
> This test is heavily dependent on ethtool, so we need to make sure the
> package is going to be installed on linux guests. The default package
> selection for Fedora 13 does not include it for example. So, we need to
> modify linux guest kickstarts/XMLs to add ethtool to the default package
> selection.

OK.

>> Signed-off-by: Amos Kong <akong@redhat.com>
>> ---
>>  0 files changed, 0 insertions(+), 0 deletions(-)
>>
>> diff --git a/client/tests/kvm/tests/ethtool.py b/client/tests/kvm/tests/ethtool.py
>> new file mode 100644
>> index 0000000..7274eae
>> --- /dev/null
>> +++ b/client/tests/kvm/tests/ethtool.py
>> @@ -0,0 +1,205 @@
>> +import time, os, logging, commands, re
>> +from autotest_lib.client.common_lib import error
>> +from autotest_lib.client.bin import utils
>> +import kvm_test_utils, kvm_utils, kvm_net_utils
>> +
>> +def run_ethtool(test, params, env):
>> +    """
>> +    Test offload functions of ethernet device by ethtool
>> +
>> +    1) Log into a guest
>> +    2) Initialize the callback of sub functions
>> +    3) Enable/disable sub function of NIC
>> +    4) Execute callback function
>> +    5) Check the return value
>> +    6) Restore original configuration
>> +
>> +    @param test: Kvm test object
>> +    @param params: Dictionary with the test parameters.
>> +    @param env: Dictionary with test environment.
>> +    """
>> +    def ethtool_get(type):
>> +        feature_pattern = {
>> +            'tx':  'tx.*checksumming',
>> +            'rx':  'rx.*checksumming',
>> +            'sg':  'scatter.*gather',
>> +            'tso': 'tcp.*segmentation.*offload',
>> +            'gso': 'generic.*segmentation.*offload',
>> +            'gro': 'generic.*receive.*offload',
>> +            'lro': 'large.*receive.*offload',
>> +            }
>> +        s, o = session.get_command_status_output("ethtool -k %s" % ethname)
>> +        try:
>> +            return re.findall("%s: (.*)" % feature_pattern.get(type), o)[0]
>> +        except IndexError:
>> +            logging.debug("Could not get %s status" % type)
>> +
>> +    def ethtool_set(type, status):
>> +        """
>> +        Set ethernet device offload status
>> +
>> +        @param type: Offload type name
>> +        @param status: New status will be changed to
>> +        """
>> +        logging.info("Try to set %s %s" % (type, status))
>> +        if status not in ["off", "on"]:
>> +            return False
>> +        cmd = "ethtool -K %s %s %s" % (ethname, type, status)
>> +        if ethtool_get(type) != status:
>> +            return session.get_command_status(cmd) == 0
>> +        if ethtool_get(type) != status:
>> +            logging.error("Fail to set %s %s" % (type, status))
>> +            return False
>> +        return True
>> +
>> +    def ethtool_save_params():
>> +        logging.info("Save ethtool configuration")
>> +        for i in supported_features:
>> +            feature_status[i] = ethtool_get(i)
>> +
>> +    def ethtool_restore_params():
>> +        logging.info("Restore ethtool configuration")
>> +        for i in supported_features:
>> +            ethtool_set(i, feature_status[i])
>> +
>> +    def compare_md5sum(name):
>> +        logging.info("Compare md5sum of the files on guest and host")
>> +        host_result = utils.hash_file(name, method="md5")
>> +        try:
>> +            o = session.get_command_output("md5sum %s" % name)
>> +            guest_result = re.findall("\w+", o)[0]
>> +        except IndexError:
>> +            logging.error("Could not get file md5sum in guest")
>> +            return False
>> +        logging.debug("md5sum: guest(%s), host(%s)" % (guest_result,
>> +                                                   host_result))
>> +        return guest_result == host_result
>> +
>> +    def transfer_file(src="guest"):
>> +        """
>> +        Transfer file by scp, use tcpdump to capture packets, then check the
>> +        return string.
>> +
>> +        @param src: Source host of transfer file
>> +        @return: Tuple (status, error msg/tcpdump result)
>> +        """
>> +        session2.get_command_status("rm -rf %s" % filename)
>> +        dd_cmd = "dd if=/dev/urandom of=%s bs=1M count=%s" % (filename,
>> +                                                   params.get("filesize"))
>> +        logging.info("Creat file in source host, cmd: %s" % dd_cmd)
>> +        tcpdump_cmd = "tcpdump -lep -s 0 tcp -vv port ssh"
>> +        if src == "guest":
>> +            s = session.get_command_status(dd_cmd, timeout=360)
>> +            tcpdump_cmd += " and src %s" % guest_ip
>> +            copy_files_fun = vm.copy_files_from
>> +        else:
>> +            s, o = commands.getstatusoutput(dd_cmd)
>> +            tcpdump_cmd += " and dst %s" % guest_ip
>> +            copy_files_fun = vm.copy_files_to
>> +        if s != 0:
>> +            return (False, "Fail to create file by dd, cmd: %s" % dd_cmd)
>> +
>> +        # only capture the new tcp port after offload setup
>> +        original_tcp_ports = re.findall("tcp.*:(\d+).*%s" % guest_ip,
>> +                                       commands.getoutput("/bin/netstat -nap"))
>> +        for i in original_tcp_ports:
>> +            tcpdump_cmd += " and not port %s" % i
>> +        logging.debug("Listen by command: %s" % tcpdump_cmd)
>> +        session2.sendline(tcpdump_cmd)
>> +        if not kvm_utils.wait_for(lambda: session.get_command_status(
>> +                                           "pgrep tcpdump") == 0, 30):
>> +            return (False, "Tcpdump process wasn't launched")
>> +
>> +        logging.info("Start to transfer file")
>> +        if not copy_files_fun(filename, filename):
>> +            return (False, "Child process transfer file failed")
>> +        logging.info("Transfer file completed")
>> +        if session.get_command_status("killall tcpdump") != 0:
>> +            return (False, "Could not kill all tcpdump process")
>> +        s, tcpdump_string = session2.read_up_to_prompt(timeout=60)
>> +        if not s:
>> +            return (False, "Fail to read tcpdump's output")
>> +
>> +        if not compare_md5sum(filename):
>> +            return (False, "Files' md5sum mismatched")
>> +        return (True, tcpdump_string)
>> +
>> +    def tx_callback(status="on"):
>> +        s, o = transfer_file(src="guest")
>> +        if not s:
>> +            logging.error(o)
>> +            return False
>> +        return True
>> +
>> +    def rx_callback(status="on"):
>> +        s, o = transfer_file(src="host")
>> +        if not s:
>> +            logging.error(o)
>> +            return False
>> +        return True
>> +
>> +    def so_callback(status="on"):
>> +        s, o = transfer_file(src="guest")
>> +        if not s:
>> +            logging.error(o)
>> +            return False
>> +        logging.info("Check if contained large frame")
>> +        # mtu: default IPv4 MTU is 1500 Bytes, ethernet header is 14 Bytes
>> +        return (status == "on") ^ (len([i for i in re.findall(
>> +                                   "length (\d*):", o) if int(i) > mtu]) == 0)
>> +
>> +    def ro_callback(status="on"):
>> +        s, o = transfer_file(src="host")
>> +        if not s:
>> +            logging.error(o)
>> +            return False
>> +        return True
>> +
>> +    vm = kvm_test_utils.get_living_vm(env, params.get("main_vm"))
>> +    session = kvm_test_utils.wait_for_login(vm,
>> +                  timeout=int(params.get("login_timeout", 360)))
>> +    session2 = kvm_test_utils.wait_for_login(vm,
>> +                  timeout=int(params.get("login_timeout", 360)))
>> +    mtu = 1514
>> +    feature_status = {}
>> +    filename = "/tmp/ethtool.dd"
>> +    guest_ip = vm.get_address()
>> +    ethname = kvm_net_utils.get_linux_ifname(session, vm.get_macaddr(0))
>> +    supported_features = params.get("supported_features").split()
>
> ^ We could use ethtool to query the machine for the supported features,
> and if it's not possible to do that (virtio_net), we resort to
> supported_feature being set on the config file, what do you think?

How to query which offload features are supported by nic dev by ethtool ?
'# ethtool -k eth0' can only list the function status.
I try to enable all the function, by this order 'tx rx gs tso ufo gso
gro lro', if some of them failed, it should not be supported ? This
method is not dependable in test environment.

>> +    test_matrix = {
>> +        # type:(callback,    (dependence), (exclude)
>> +        "tx":  (tx_callback, (), ()),
>> +        "rx":  (rx_callback, (), ()),
>> +        "sg":  (tx_callback, ("tx",), ()),
>> +        "tso": (so_callback, ("tx", "sg",), ("gso",)),
>> +        "gso": (so_callback, (), ("tso",)),
>> +        "gro": (ro_callback, ("rx",), ("lro",)),
>> +        "lro": (rx_callback, (), ("gro",)),
>> +        }
>> +    ethtool_save_params()
>> +    success = True
>> +    try:
>> +        for type in supported_features:
>> +            callback = test_matrix[type][0]
>> +            for i in test_matrix[type][2]:
>> +                if not ethtool_set(i, "off"):
>> +                    logging.error("Fail to disable %s" % i)
>> +                    success = False
>> +            for i in [f for f in test_matrix[type][1]] + [type]:
>> +                if not ethtool_set(i, "on"):
>> +                    logging.error("Fail to enable %s" % i)
>> +                    success = False
>> +            if not callback():
>> +                raise error.TestFail("Test failed, %s: on" % type)
>> +
>> +            if not ethtool_set(type, "off"):
>> +                logging.error("Fail to disable %s" % type)
>> +                success = False
>> +            if not callback(status="off"):
>> +                raise error.TestFail("Test failed, %s: off" % type)
>> +        if not success:
>> +            raise error.TestError("Enable/disable offload function fail")
>> +    finally:
>> +        ethtool_restore_params()
>> +        session.close()
>> +        session2.close()
>> diff --git a/client/tests/kvm/tests_base.cfg.sample b/client/tests/kvm/tests_base.cfg.sample
>> index b25980e..6f0e295 100644
>> --- a/client/tests/kvm/tests_base.cfg.sample
>> +++ b/client/tests/kvm/tests_base.cfg.sample
>> @@ -412,6 +412,19 @@ variants:
>>          netperf_cmd = %s/netperf-2.4.5/src/netperf -t %s -H %s -l 60 -- -m 1
>>          protocols = "TCP_STREAM TCP_MAERTS TCP_RR TCP_CRR UDP_RR TCP_SENDFILE UDP_STREAM"
>>
>> +    - ethtool: install setup unattended_install.cdrom
>> +        type = ethtool
>> +        filesize = 512
>> +        nic_mode = tap
>> +        variants:
>> +            # gso gro lro is only supported by latest kernel
>> +            - nic_virtio:
>> +                pci_model = virtio
>> +                supported_features = "tx sg tso gso"
>> +            - nic_e1000:
>> +                pci_model = e1000
>> +                supported_features = "tx rx sg tso gso gro lro"
>> +
>>      - physical_resources_check: install setup unattended_install.cdrom
>>          type = physical_resources_check
>>          catch_uuid_cmd = dmidecode | awk -F: '/UUID/ {print $2}'
>> @@ -1088,7 +1101,7 @@ variants:
>>
>>      # Windows section
>>      - @Windows:
>> -        no autotest linux_s3 vlan ioquit unattended_install.(url|nfs|remote_ks) jumbo file_transfer nicdriver_unload nic_promisc multicast mac_change
>> +        no autotest linux_s3 vlan ioquit unattended_install.(url|nfs|remote_ks) jumbo file_transfer nicdriver_unload nic_promisc multicast mac_change ethtool
>>          shutdown_command = shutdown /s /f /t 0
>>          reboot_command = shutdown /r /f /t 0
>>          status_test_command = echo %errorlevel%