diff mbox

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

Message ID 20100720013649.2212.7434.stgit@z
State Not Applicable
Headers show

Commit Message

Amos Kong July 20, 2010, 1:36 a.m. UTC
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.

Signed-off-by: Amos Kong <akong@redhat.com>
---
 0 files changed, 0 insertions(+), 0 deletions(-)

Comments

Lucas Meneghel Rodrigues Aug. 2, 2010, 7:10 p.m. UTC | #1
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.

> 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?


> +    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%
>
Jianjun Kong Aug. 10, 2010, 7:07 a.m. UTC | #2
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%
diff mbox

Patch

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()
+    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%