• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import logging, re
2from autotest_lib.client.common_lib import error
3from autotest_lib.client.bin import utils
4from autotest_lib.client.virt import virt_test_utils, virt_utils, aexpect
5
6
7def 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