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