import logging, re from autotest_lib.client.common_lib import error from autotest_lib.client.bin import utils from autotest_lib.client.virt import virt_test_utils, virt_utils, aexpect 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. @todo: Not all guests have ethtool installed, so find a way to get it installed using yum/apt-get/ whatever """ def ethtool_get(f_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', } o = session.cmd("ethtool -k %s" % ethname) try: return re.findall("%s: (.*)" % feature_pattern.get(f_type), o)[0] except IndexError: logging.debug("Could not get %s status", f_type) def ethtool_set(f_type, status): """ Set ethernet device offload status @param f_type: Offload type name @param status: New status will be changed to """ logging.info("Try to set %s %s", f_type, status) if status not in ["off", "on"]: return False cmd = "ethtool -K %s %s %s" % (ethname, f_type, status) if ethtool_get(f_type) != status: try: session.cmd(cmd) return True except: return False if ethtool_get(f_type) != status: logging.error("Fail to set %s %s", f_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.cmd_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.cmd_output("rm -rf %s" % filename) dd_cmd = ("dd if=/dev/urandom of=%s bs=1M count=%s" % (filename, params.get("filesize"))) failure = (False, "Failed to create file using dd, cmd: %s" % dd_cmd) logging.info("Creating file in source host, cmd: %s", dd_cmd) tcpdump_cmd = "tcpdump -lep -s 0 tcp -vv port ssh" if src == "guest": tcpdump_cmd += " and src %s" % guest_ip copy_files_from = vm.copy_files_from try: session.cmd_output(dd_cmd, timeout=360) except aexpect.ShellCmdError, e: return failure else: tcpdump_cmd += " and dst %s" % guest_ip copy_files_from = vm.copy_files_to try: utils.system(dd_cmd) except error.CmdError, e: return failure # only capture the new tcp port after offload setup original_tcp_ports = re.findall("tcp.*:(\d+).*%s" % guest_ip, utils.system_output("/bin/netstat -nap")) for i in original_tcp_ports: tcpdump_cmd += " and not port %s" % i logging.debug("Listen using command: %s", tcpdump_cmd) session2.sendline(tcpdump_cmd) if not virt_utils.wait_for( lambda:session.cmd_status("pgrep tcpdump") == 0, 30): return (False, "Tcpdump process wasn't launched") logging.info("Start to transfer file") try: copy_files_from(filename, filename) except virt_utils.SCPError, e: return (False, "File transfer failed (%s)" % e) logging.info("Transfer file completed") session.cmd("killall tcpdump") try: tcpdump_string = session2.read_up_to_prompt(timeout=60) except aexpect.ExpectError: 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 = env.get_vm(params["main_vm"]) vm.verify_alive() session = vm.wait_for_login(timeout=int(params.get("login_timeout", 360))) # Let's just error the test if we identify that there's no ethtool installed session.cmd("ethtool -h") session2 = vm.wait_for_login(timeout=int(params.get("login_timeout", 360))) mtu = 1514 feature_status = {} filename = "/tmp/ethtool.dd" guest_ip = vm.get_address() ethname = virt_test_utils.get_linux_ifname(session, vm.get_mac_address(0)) supported_features = params.get("supported_features") if supported_features: supported_features = supported_features.split() else: supported_features = [] 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 f_type in supported_features: callback = test_matrix[f_type][0] for i in test_matrix[f_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[f_type][1]] + [f_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", f_type) if not ethtool_set(f_type, "off"): logging.error("Fail to disable %s", f_type) success = False if not callback(status="off"): raise error.TestFail("Test failed, %s: off", f_type) if not success: raise error.TestError("Enable/disable offload function fail") finally: ethtool_restore_params() session.close() session2.close()