Index: client/tests/kvm/tests/ethtool.py |
diff --git a/client/tests/kvm/tests/ethtool.py b/client/tests/kvm/tests/ethtool.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..56b1c70e70fbfb9c2e3d9afcaa9f9691ba5bc3ee |
--- /dev/null |
+++ b/client/tests/kvm/tests/ethtool.py |
@@ -0,0 +1,222 @@ |
+import logging, commands, re |
+from autotest_lib.client.common_lib import error |
+from autotest_lib.client.bin import utils |
+import kvm_test_utils, kvm_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. |
+ |
+ @todo: Not all guests have ethtool installed, so |
+ find a way to get it installed using yum/apt-get/ |
+ whatever |
+ """ |
+ 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, |
+ utils.system_output("/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))) |
+ # Let's just error the test if we identify that there's no ethtool installed |
+ if session.get_command_status("ethtool -h"): |
+ raise error.TestError("Command ethtool not installed on guest") |
+ 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_test_utils.get_linux_ifname(session, vm.get_mac_address(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() |