OLD | NEW |
(Empty) | |
| 1 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. |
| 4 |
| 5 import common, fnmatch, logging, os, re, string, threading, time |
| 6 |
| 7 from autotest_lib.server import autotest, subcommand |
| 8 from autotest_lib.server import site_bsd_router |
| 9 #from autotest_lib.server import site_linux_router |
| 10 |
| 11 class HelperThread(threading.Thread): |
| 12 # Class that wraps a ping command in a thread so it can run in the bg. |
| 13 def __init__(self, client, cmd): |
| 14 threading.Thread.__init__(self) |
| 15 self.client = client |
| 16 self.cmd = cmd |
| 17 |
| 18 def run(self): |
| 19 # NB: set ignore_status as we're always terminated w/ pkill |
| 20 self.client.run(self.cmd, ignore_status=True) |
| 21 |
| 22 |
| 23 class WiFiTest(object): |
| 24 """ |
| 25 WiFi Test. |
| 26 |
| 27 Each test is specified as a dict. There must be a "name" entry that |
| 28 gives the test name (a string) and a "steps" entry that has an ordered |
| 29 tuple of test steps, where each step is a tuple [func, {args}]. |
| 30 |
| 31 Step funcs are one of: |
| 32 config configure the router/AP using the specified params |
| 33 (ssid automatically supplied); params are give as |
| 34 BSD ifconfig(8) parameters and translated to match |
| 35 the target router/AP's cli syntax |
| 36 deconfig de-configure/shut-off the router/AP |
| 37 connect connect client to AP using specified parameters |
| 38 (ssid automatically supplied) |
| 39 disconnect disconnect client from AP |
| 40 client_check_config check the client network connection to verify |
| 41 state is setup as expected |
| 42 sleep pause on the autotest server for a time |
| 43 client_ping ping the server on the client machine |
| 44 server_ping ping the client on the server machine |
| 45 client_iperf run iperf on the client to the server |
| 46 server_iperf run iperf on the server to the client |
| 47 client_netperf run netperf on the client to the server |
| 48 server_netperf run netperf on the server to the client |
| 49 |
| 50 Steps that are done on the client or server machine are implemented in |
| 51 this class. Steps that are done on the wifi router are implemented in |
| 52 a separate class that knows how to control the router. There are presently |
| 53 two classes: BSDRouter for routers based on FreeBSD and LinuxRouter for |
| 54 those based on Linux/mac80211. Additional router support can be added |
| 55 by adding a new class and auto-selecting it in __init__. |
| 56 |
| 57 The WiFiTest class could be generalized to handle clients other than |
| 58 ChromeOS; this would useful for systems that use Network Manager or |
| 59 wpa_supplicant directly. |
| 60 """ |
| 61 |
| 62 def __init__(self, name, steps, router, client, server): |
| 63 self.name = name |
| 64 self.steps = steps |
| 65 self.router = router['host'] |
| 66 self.client = client['host'] |
| 67 self.client_at = autotest.Autotest(self.client) |
| 68 self.client_wifi_ip = None # client's IP address on wifi net |
| 69 self.server = server['host'] |
| 70 self.server_at = autotest.Autotest(self.server) |
| 71 # server's IP address on wifi net (XXX assume same for now) |
| 72 self.server_wifi_ip = self.server.ip |
| 73 |
| 74 # NB: truncate SSID to 32 characters |
| 75 self.defssid = self.__get_defssid()[0:32] |
| 76 |
| 77 # XXX auto-detect router type |
| 78 self.wifi = site_bsd_router.BSDRouter(self.router, router, self.defssid) |
| 79 |
| 80 # potential bg thread for ping untilstop |
| 81 self.ping_thread = None |
| 82 |
| 83 |
| 84 def setup(self): |
| 85 self.job.setup_dep(['netperf']) |
| 86 # XXX enable once built by build_autotest |
| 87 # self.job.setup_dep(['iperf']) |
| 88 # create a empty srcdir to prevent the error that checks .version |
| 89 if not os.path.exists(self.srcdir): |
| 90 os.mkdir(self.srcdir) |
| 91 |
| 92 |
| 93 def cleanup(self, params): |
| 94 """ Cleanup state: disconnect client and destroy ap """ |
| 95 self.disconnect({}) |
| 96 self.wifi.destroy({}) |
| 97 |
| 98 |
| 99 def __get_defssid(self): |
| 100 # |
| 101 # Calculate ssid based on test name; this lets us track progress |
| 102 # by watching beacon frames. |
| 103 # |
| 104 # XXX truncate to 32 chars |
| 105 return re.sub('[^a-zA-Z0-9_]', '_', \ |
| 106 "%s_%s" % (self.name, self.router.ip)) |
| 107 |
| 108 |
| 109 def run(self): |
| 110 """ |
| 111 Run a WiFi test. Each step is interpreted as a method either |
| 112 in this class or the ancillary router class and invoked with |
| 113 the supplied parameter dictionary. |
| 114 """ |
| 115 for s in self.steps: |
| 116 method = s[0] |
| 117 if len(s) > 1: |
| 118 params = s[1] |
| 119 else: |
| 120 params = {} |
| 121 |
| 122 logging.info("%s: step '%s' params %s" % \ |
| 123 (self.name, method, params)) |
| 124 |
| 125 func = getattr(self, method, None) |
| 126 if func is None: |
| 127 func = getattr(self.wifi, method, None) |
| 128 if func is not None: |
| 129 try: |
| 130 func(params) |
| 131 except Exception, e: |
| 132 logging.error("%s: Step '%s' failed: %s; abort test" % \ |
| 133 (self.name, method, str(e))) |
| 134 self.cleanup(params) |
| 135 break |
| 136 else: |
| 137 logging.error("%s: Step '%s' unknown; abort test" % \ |
| 138 (self.name, method)) |
| 139 self.cleanup(params) |
| 140 break |
| 141 |
| 142 |
| 143 def __get_connect_script(self, params): |
| 144 return '\ |
| 145 import dbus, dbus.mainloop.glib, gobject, logging, re, sys, time\n\ |
| 146 \ |
| 147 ssid = "' + params['ssid'] + '"\n\ |
| 148 security = "' + params['security'] + '"\n\ |
| 149 psk = "' + params.get('psk', "") + '"\n\ |
| 150 assoc_timeout = ' + params.get('assoc_timeout', "15") + '\n\ |
| 151 config_timeout = ' + params.get('config_timeout', "15") + '\n\ |
| 152 \ |
| 153 bus_loop = dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)\n\ |
| 154 bus = dbus.SystemBus(mainloop=bus_loop)\n\ |
| 155 manager = dbus.Interface(bus.get_object("org.moblin.connman", "/"),\n\ |
| 156 "org.moblin.connman.Manager")\n\ |
| 157 \ |
| 158 try:\n\ |
| 159 path = manager.GetService(({\n\ |
| 160 "Type": "wifi",\n\ |
| 161 "Mode": "managed",\n\ |
| 162 "SSID": ssid,\n\ |
| 163 "Security": security,\n\ |
| 164 "Passphrase": psk }))\n\ |
| 165 service = dbus.Interface(\n\ |
| 166 bus.get_object("org.moblin.connman", path),\n\ |
| 167 "org.moblin.connman.Service")\n\ |
| 168 except Exception, e:\n\ |
| 169 print "FAIL(GetService): ssid %s exception %s" %(ssid, e)\n\ |
| 170 sys.exit(1)\n\ |
| 171 \ |
| 172 try:\n\ |
| 173 service.Connect()\n\ |
| 174 except Exception, e:\n\ |
| 175 print "FAIL(Connect): ssid %s exception %s" %(ssid, e)\n\ |
| 176 sys.exit(2)\n\ |
| 177 \ |
| 178 status = ""\n\ |
| 179 assoc_time = 0\n\ |
| 180 # wait up to assoc_timeout seconds to associate\n\ |
| 181 while assoc_time < assoc_timeout:\n\ |
| 182 properties = service.GetProperties()\n\ |
| 183 status = properties.get("State", None)\n\ |
| 184 # print>>sys.stderr, "time %3.1f state %s" % (assoc_time, status)\n\ |
| 185 if status == "failure":\n\ |
| 186 print "FAIL(assoc): ssid %s assoc %3.1f secs props %s" %(ssid, assoc_tim
e, properties)\n\ |
| 187 sys.exit(3)\n\ |
| 188 if status == "configuration" or status == "ready":\n\ |
| 189 break\n\ |
| 190 time.sleep(.5)\n\ |
| 191 assoc_time += .5\n\ |
| 192 if assoc_time >= assoc_timeout:\n\ |
| 193 print "TIMEOUT(assoc): ssid %s assoc %3.1f secs" %(ssid, assoc_time)\n\ |
| 194 sys.exit(4)\n\ |
| 195 \ |
| 196 # wait another config_timeout seconds to get an ip address\n\ |
| 197 config_time = 0\n\ |
| 198 if status != "ready":\n\ |
| 199 while config_time < config_timeout:\n\ |
| 200 properties = service.GetProperties()\n\ |
| 201 status = properties.get("State", None)\n\ |
| 202 # print>>sys.stderr, "time %3.1f state %s" % (config_time, status)\n\ |
| 203 if status == "failure":\n\ |
| 204 print "FAIL(config): ssid %s assoc %3.1f config %3.1f secs" \\\n\ |
| 205 %(ssid, assoc_time, config_time)\n\ |
| 206 sys.exit(5)\n\ |
| 207 if status == "ready":\n\ |
| 208 break\n\ |
| 209 time.sleep(.5)\n\ |
| 210 config_time += .5\n\ |
| 211 if config_time >= config_timeout:\n\ |
| 212 print "TIMEOUT(config): ssid %s assoc %3.1f config %3.1f secs"\\\n\ |
| 213 %(ssid, assoc_time, config_time)\n\ |
| 214 sys.exit(6)\n\ |
| 215 print "assoc %3.1f secs config %3.1f secs" % (assoc_time, config_time)\n\ |
| 216 sys.exit(0)' |
| 217 |
| 218 |
| 219 def __get_ipaddr(self, host, ifnet): |
| 220 # XXX gotta be a better way to do this |
| 221 result = host.run("ifconfig %s" % ifnet) |
| 222 m = re.search('inet addr:([^ ]*)', result.stdout) |
| 223 if m is None: |
| 224 raise Except, "No inet address found" |
| 225 return m.group(1) |
| 226 |
| 227 |
| 228 def connect(self, params): |
| 229 """ Connect client to AP/router """ |
| 230 if 'ssid' not in params: |
| 231 params['ssid'] = self.defssid |
| 232 script = self.__get_connect_script(params) |
| 233 result = self.client.run("python<<'EOF'\n%s\nEOF\n" % script) |
| 234 print "%s: %s" % (self.name, result.stdout[0:-1]) |
| 235 |
| 236 # fetch IP address of wireless device |
| 237 # XXX wlan0 hard coded |
| 238 self.client_wifi_ip = self.__get_ipaddr(self.client, "wlan0") |
| 239 logging.info("%s: client WiFi-IP is %s" % \ |
| 240 (self.name, self.client_wifi_ip)) |
| 241 |
| 242 |
| 243 def __make_disconnect_script(self, params): |
| 244 return '\ |
| 245 import dbus, dbus.mainloop.glib, gobject, logging, sys, time\n\ |
| 246 \n\ |
| 247 interface = "' + params.get('psk', "") + '"\n\ |
| 248 \n\ |
| 249 bus_loop = dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)\n\ |
| 250 bus = dbus.SystemBus(mainloop=bus_loop)\n\ |
| 251 manager = dbus.Interface(bus.get_object("org.moblin.connman", "/"),\n\ |
| 252 "org.moblin.connman.Manager")\n\ |
| 253 \n\ |
| 254 sys.exit(0)' |
| 255 |
| 256 |
| 257 def disconnect(self, params): |
| 258 """ Disconnect previously connected client """ |
| 259 self.client_ping_bg_stop({}) |
| 260 # script = self.__make_disconnect_script(params) |
| 261 # self.client.run("python<<'EOF'\n%s\nEOF\n" % script) |
| 262 |
| 263 |
| 264 def sleep(self, params): |
| 265 time.sleep(float(params['time'])) |
| 266 |
| 267 |
| 268 def __ping_args(self, params): |
| 269 args = "" |
| 270 if 'count' in params: |
| 271 args += " -c %s" % params['count'] |
| 272 if 'size' in params: |
| 273 args += " -s %s" % params['size'] |
| 274 if 'bcast' in params: |
| 275 args += " -b" |
| 276 if 'flood' in params: |
| 277 args += " -f" |
| 278 if 'interval' in params: |
| 279 args += " -i %s" % params['interval'] |
| 280 if 'qos' in params: |
| 281 ac = string.lower(params['qos']) |
| 282 if ac == 'be': |
| 283 args += " -Q 0x04" |
| 284 elif ac == 'bk': |
| 285 args += " -Q 0x02" |
| 286 elif ac == 'vi': |
| 287 args += " -Q 0x08" |
| 288 elif ac == 'vo': |
| 289 args += " -Q 0x10" |
| 290 else: |
| 291 args += " -Q %s" % ac |
| 292 return args |
| 293 |
| 294 |
| 295 def __get_pingstats(self, str): |
| 296 stats = {} |
| 297 m = re.search('([0-9]*) packets transmitted,[ ]*([0-9]*)[ ]' |
| 298 'received, ([0-9]*)', str) |
| 299 if m is not None: |
| 300 stats['xmit'] = m.group(1) |
| 301 stats['recv'] = m.group(2) |
| 302 stats['loss'] = m.group(3) |
| 303 m = re.search('rtt min[^=]*= ([0-9.]*)/([0-9.]*)/([0-9.]*)', str) |
| 304 if m is not None: |
| 305 stats['min'] = m.group(1) |
| 306 stats['avg'] = m.group(2) |
| 307 stats['max'] = m.group(3) |
| 308 return stats |
| 309 |
| 310 |
| 311 def __print_pingstats(self, label, stats): |
| 312 logging.info("%s: %s%s/%s, %s%% loss, rtt %s/%s/%s" % \ |
| 313 (self.name, label, stats['xmit'], stats['recv'], stats['loss'], |
| 314 stats['min'], stats['avg'], stats['max'])) |
| 315 |
| 316 |
| 317 def client_ping(self, params): |
| 318 """ Ping the server from the client """ |
| 319 ping_ip = params.get('ping_ip', self.server_wifi_ip) |
| 320 count = params.get('count', 10) |
| 321 # set timeout for 3s / ping packet |
| 322 result = self.client.run("ping %s %s" % \ |
| 323 (self.__ping_args(params), ping_ip), timeout=3*int(count)) |
| 324 |
| 325 self.__print_pingstats("client_ping ", |
| 326 self.__get_pingstats(result.stdout)) |
| 327 |
| 328 |
| 329 def client_ping_bg(self, params): |
| 330 """ Ping the server from the client """ |
| 331 ping_ip = params.get('ping_ip', self.server_wifi_ip) |
| 332 cmd = "ping %s %s" % (self.__ping_args(params), ping_ip) |
| 333 self.ping_thread = HelperThread(self.client, cmd) |
| 334 self.ping_thread.start() |
| 335 |
| 336 |
| 337 def client_ping_bg_stop(self, params): |
| 338 if self.ping_thread is not None: |
| 339 self.client.run("pkill ping") |
| 340 self.ping_thread.join() |
| 341 self.ping_thread = None |
| 342 |
| 343 |
| 344 def server_ping(self, params): |
| 345 """ Ping the client from the server """ |
| 346 ping_ip = params.get('ping_ip', self.client_wifi_ip) |
| 347 # XXX 30 second timeout |
| 348 result = self.server.run("ping %s %s" % \ |
| 349 (self.__ping_args(params), ping_ip), timeout=30) |
| 350 |
| 351 self.__print_pingstats("server_ping ", |
| 352 self.__get_pingstats(result.stdout)) |
| 353 |
| 354 |
| 355 |
| 356 def __run_iperf(self, client_ip, server_ip, params): |
| 357 template = ''.join(["job.run_test('iperf', \ |
| 358 server_ip='%s', client_ip='%s', role='%s'%s"]) |
| 359 if 'udp' in params: |
| 360 template += ", udp=True" |
| 361 if 'bidir' in params: |
| 362 template += ", bidirectional=True" |
| 363 if 'time' in params: |
| 364 template += ", test_time=%s" % params['time'] |
| 365 template += ")" |
| 366 |
| 367 server_control_file = template % (server_ip, client_ip, 'server') |
| 368 server_command = subcommand.subcommand(self.server_at.run, |
| 369 [server_control_file, self.server.hostname]) |
| 370 |
| 371 client_control_file = template % (server_ip, client_ip, 'client') |
| 372 client_command = subcommand.subcommand(self.client_at.run, |
| 373 [client_control_file, self.client.hostname]) |
| 374 |
| 375 logging.info("%s: iperf %s => %s" % (self.name, client_ip, server_ip)) |
| 376 |
| 377 # XXX 30 sec timeout for now |
| 378 subcommand.parallel([server_command, client_command], timeout=30) |
| 379 |
| 380 |
| 381 def client_iperf(self, params): |
| 382 """ Run iperf on the client against the server """ |
| 383 self.__run_iperf(self.client_wifi_ip, self.server_wifi_ip, params) |
| 384 |
| 385 |
| 386 def server_iperf(self, params): |
| 387 """ Run iperf on the server against the client """ |
| 388 self.__run_iperf(self.server_wifi_ip, self.client_wifi_ip, params) |
| 389 |
| 390 |
| 391 def __run_netperf(self, client_ip, server_ip, params): |
| 392 template = ''.join(["job.run_test('netperf2', \ |
| 393 server_ip='%s', client_ip='%s', role='%s'"]) |
| 394 if 'test' in params: |
| 395 template += ", test='%s'" % params['test'] |
| 396 if 'bidir' in params: |
| 397 template += ", bidi=True" |
| 398 if 'time' in params: |
| 399 template += ", test_time=%s" % params['time'] |
| 400 template += ")" |
| 401 |
| 402 server_control_file = template % (server_ip, client_ip, 'server') |
| 403 server_command = subcommand.subcommand(self.server_at.run, |
| 404 [server_control_file, self.server.hostname]) |
| 405 |
| 406 client_control_file = template % (server_ip, client_ip, 'client') |
| 407 client_command = subcommand.subcommand(self.client_at.run, |
| 408 [client_control_file, self.client.hostname]) |
| 409 |
| 410 logging.info("%s: netperf %s => %s" % (self.name, client_ip, server_ip)) |
| 411 |
| 412 # XXX 30 sec timeout for now |
| 413 subcommand.parallel([server_command, client_command], timeout=60) |
| 414 |
| 415 |
| 416 def client_netperf(self, params): |
| 417 """ Run netperf on the client against the server """ |
| 418 self.__run_netperf(self.client_wifi_ip, self.server_wifi_ip, params) |
| 419 |
| 420 |
| 421 def server_netperf(self, params): |
| 422 """ Run netperf on the server against the client """ |
| 423 self.__run_netperf(self.server_wifi_ip, self.client_wifi_ip, params) |
| 424 |
| 425 |
| 426 def __byfile(a, b): |
| 427 if a['file'] < b['file']: |
| 428 return -1 |
| 429 elif a['file'] > b['file']: |
| 430 return 1 |
| 431 else: |
| 432 return 0 |
| 433 |
| 434 def read_tests(dir, pat): |
| 435 """ |
| 436 Collect WiFi test tuples from files. File names are used to |
| 437 sort the test objects so the convention is to name them NNN<test> |
| 438 where NNN is a decimal number used to sort and <test> is an |
| 439 identifying name for the test; e.g. 000Check11b |
| 440 """ |
| 441 tests = [] |
| 442 for file in os.listdir(dir): |
| 443 if fnmatch.fnmatch(file, pat): |
| 444 fd = open(os.path.join(dir, file)); |
| 445 try: |
| 446 test = eval(fd.read()) |
| 447 except Exception, e: |
| 448 logging.error("%s: %s" % (os.path.join(dir, file), str(e))) |
| 449 raise e |
| 450 test['file'] = file |
| 451 tests.append(test) |
| 452 # use filenames to sort |
| 453 return sorted(tests, cmp=__byfile) |
| 454 |
OLD | NEW |