235 lines
8.2 KiB
Python
235 lines
8.2 KiB
Python
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()
|