Home | History | Annotate | Download | only in tests
      1 import logging, re
      2 from autotest_lib.client.common_lib import error
      3 from autotest_lib.client.bin import utils
      4 from autotest_lib.client.virt import virt_test_utils, virt_utils, aexpect
      5 
      6 
      7 def run_ethtool(test, params, env):
      8     """
      9     Test offload functions of ethernet device by ethtool
     10 
     11     1) Log into a guest.
     12     2) Initialize the callback of sub functions.
     13     3) Enable/disable sub function of NIC.
     14     4) Execute callback function.
     15     5) Check the return value.
     16     6) Restore original configuration.
     17 
     18     @param test: KVM test object.
     19     @param params: Dictionary with the test parameters.
     20     @param env: Dictionary with test environment.
     21 
     22     @todo: Not all guests have ethtool installed, so
     23         find a way to get it installed using yum/apt-get/
     24         whatever
     25     """
     26     def ethtool_get(f_type):
     27         feature_pattern = {
     28             'tx':  'tx.*checksumming',
     29             'rx':  'rx.*checksumming',
     30             'sg':  'scatter.*gather',
     31             'tso': 'tcp.*segmentation.*offload',
     32             'gso': 'generic.*segmentation.*offload',
     33             'gro': 'generic.*receive.*offload',
     34             'lro': 'large.*receive.*offload',
     35             }
     36         o = session.cmd("ethtool -k %s" % ethname)
     37         try:
     38             return re.findall("%s: (.*)" % feature_pattern.get(f_type), o)[0]
     39         except IndexError:
     40             logging.debug("Could not get %s status", f_type)
     41 
     42 
     43     def ethtool_set(f_type, status):
     44         """
     45         Set ethernet device offload status
     46 
     47         @param f_type: Offload type name
     48         @param status: New status will be changed to
     49         """
     50         logging.info("Try to set %s %s", f_type, status)
     51         if status not in ["off", "on"]:
     52             return False
     53         cmd = "ethtool -K %s %s %s" % (ethname, f_type, status)
     54         if ethtool_get(f_type) != status:
     55             try:
     56                 session.cmd(cmd)
     57                 return True
     58             except:
     59                 return False
     60         if ethtool_get(f_type) != status:
     61             logging.error("Fail to set %s %s", f_type, status)
     62             return False
     63         return True
     64 
     65 
     66     def ethtool_save_params():
     67         logging.info("Save ethtool configuration")
     68         for i in supported_features:
     69             feature_status[i] = ethtool_get(i)
     70 
     71 
     72     def ethtool_restore_params():
     73         logging.info("Restore ethtool configuration")
     74         for i in supported_features:
     75             ethtool_set(i, feature_status[i])
     76 
     77 
     78     def compare_md5sum(name):
     79         logging.info("Compare md5sum of the files on guest and host")
     80         host_result = utils.hash_file(name, method="md5")
     81         try:
     82             o = session.cmd_output("md5sum %s" % name)
     83             guest_result = re.findall("\w+", o)[0]
     84         except IndexError:
     85             logging.error("Could not get file md5sum in guest")
     86             return False
     87         logging.debug("md5sum: guest(%s), host(%s)", guest_result, host_result)
     88         return guest_result == host_result
     89 
     90 
     91     def transfer_file(src="guest"):
     92         """
     93         Transfer file by scp, use tcpdump to capture packets, then check the
     94         return string.
     95 
     96         @param src: Source host of transfer file
     97         @return: Tuple (status, error msg/tcpdump result)
     98         """
     99         session2.cmd_output("rm -rf %s" % filename)
    100         dd_cmd = ("dd if=/dev/urandom of=%s bs=1M count=%s" %
    101                   (filename, params.get("filesize")))
    102         failure = (False, "Failed to create file using dd, cmd: %s" % dd_cmd)
    103         logging.info("Creating file in source host, cmd: %s", dd_cmd)
    104         tcpdump_cmd = "tcpdump -lep -s 0 tcp -vv port ssh"
    105         if src == "guest":
    106             tcpdump_cmd += " and src %s" % guest_ip
    107             copy_files_from = vm.copy_files_from
    108             try:
    109                 session.cmd_output(dd_cmd, timeout=360)
    110             except aexpect.ShellCmdError, e:
    111                 return failure
    112         else:
    113             tcpdump_cmd += " and dst %s" % guest_ip
    114             copy_files_from = vm.copy_files_to
    115             try:
    116                 utils.system(dd_cmd)
    117             except error.CmdError, e:
    118                 return failure
    119 
    120         # only capture the new tcp port after offload setup
    121         original_tcp_ports = re.findall("tcp.*:(\d+).*%s" % guest_ip,
    122                                       utils.system_output("/bin/netstat -nap"))
    123         for i in original_tcp_ports:
    124             tcpdump_cmd += " and not port %s" % i
    125         logging.debug("Listen using command: %s", tcpdump_cmd)
    126         session2.sendline(tcpdump_cmd)
    127         if not virt_utils.wait_for(
    128                            lambda:session.cmd_status("pgrep tcpdump") == 0, 30):
    129             return (False, "Tcpdump process wasn't launched")
    130 
    131         logging.info("Start to transfer file")
    132         try:
    133             copy_files_from(filename, filename)
    134         except virt_utils.SCPError, e:
    135             return (False, "File transfer failed (%s)" % e)
    136         logging.info("Transfer file completed")
    137         session.cmd("killall tcpdump")
    138         try:
    139             tcpdump_string = session2.read_up_to_prompt(timeout=60)
    140         except aexpect.ExpectError:
    141             return (False, "Fail to read tcpdump's output")
    142 
    143         if not compare_md5sum(filename):
    144             return (False, "Files' md5sum mismatched")
    145         return (True, tcpdump_string)
    146 
    147 
    148     def tx_callback(status="on"):
    149         s, o = transfer_file(src="guest")
    150         if not s:
    151             logging.error(o)
    152             return False
    153         return True
    154 
    155 
    156     def rx_callback(status="on"):
    157         s, o = transfer_file(src="host")
    158         if not s:
    159             logging.error(o)
    160             return False
    161         return True
    162 
    163 
    164     def so_callback(status="on"):
    165         s, o = transfer_file(src="guest")
    166         if not s:
    167             logging.error(o)
    168             return False
    169         logging.info("Check if contained large frame")
    170         # MTU: default IPv4 MTU is 1500 Bytes, ethernet header is 14 Bytes
    171         return (status == "on") ^ (len([i for i in re.findall(
    172                                    "length (\d*):", o) if int(i) > mtu]) == 0)
    173 
    174 
    175     def ro_callback(status="on"):
    176         s, o = transfer_file(src="host")
    177         if not s:
    178             logging.error(o)
    179             return False
    180         return True
    181 
    182 
    183     vm = env.get_vm(params["main_vm"])
    184     vm.verify_alive()
    185     session = vm.wait_for_login(timeout=int(params.get("login_timeout", 360)))
    186     # Let's just error the test if we identify that there's no ethtool installed
    187     session.cmd("ethtool -h")
    188     session2 = vm.wait_for_login(timeout=int(params.get("login_timeout", 360)))
    189     mtu = 1514
    190     feature_status = {}
    191     filename = "/tmp/ethtool.dd"
    192     guest_ip = vm.get_address()
    193     ethname = virt_test_utils.get_linux_ifname(session, vm.get_mac_address(0))
    194     supported_features = params.get("supported_features")
    195     if supported_features:
    196         supported_features = supported_features.split()
    197     else:
    198         supported_features = []
    199     test_matrix = {
    200         # type:(callback,    (dependence), (exclude)
    201         "tx":  (tx_callback, (), ()),
    202         "rx":  (rx_callback, (), ()),
    203         "sg":  (tx_callback, ("tx",), ()),
    204         "tso": (so_callback, ("tx", "sg",), ("gso",)),
    205         "gso": (so_callback, (), ("tso",)),
    206         "gro": (ro_callback, ("rx",), ("lro",)),
    207         "lro": (rx_callback, (), ("gro",)),
    208         }
    209     ethtool_save_params()
    210     success = True
    211     try:
    212         for f_type in supported_features:
    213             callback = test_matrix[f_type][0]
    214             for i in test_matrix[f_type][2]:
    215                 if not ethtool_set(i, "off"):
    216                     logging.error("Fail to disable %s", i)
    217                     success = False
    218             for i in [f for f in test_matrix[f_type][1]] + [f_type]:
    219                 if not ethtool_set(i, "on"):
    220                     logging.error("Fail to enable %s", i)
    221                     success = False
    222             if not callback():
    223                 raise error.TestFail("Test failed, %s: on", f_type)
    224 
    225             if not ethtool_set(f_type, "off"):
    226                 logging.error("Fail to disable %s", f_type)
    227                 success = False
    228             if not callback(status="off"):
    229                 raise error.TestFail("Test failed, %s: off", f_type)
    230         if not success:
    231             raise error.TestError("Enable/disable offload function fail")
    232     finally:
    233         ethtool_restore_params()
    234         session.close()
    235         session2.close()
    236