WiFi test cleanups

- move config file handling into WiFiTest class
- move router autodetect to router classes
- auto-detect BSD router wireless and wired nics

Review URL: http://codereview.chromium.org/1512032
diff --git a/server/site_bsd_router.py b/server/site_bsd_router.py
index f7679a8..8ba9a44 100644
--- a/server/site_bsd_router.py
+++ b/server/site_bsd_router.py
@@ -4,6 +4,19 @@
 
 import logging, re
 
+def isBSDRouter(router):
+    router_uname = router.run('uname').stdout
+    return re.search('BSD', router_uname)
+
+def find_ifnet(host, pattern):
+    list = host.run("ifconfig -l").stdout
+    for ifnet in list.split():
+        status = host.run("ifconfig %s" % ifnet).stdout
+        m = re.search(pattern, status)
+        if m:
+            return ifnet
+    return None
+
 class NotImplemented(Exception):
     def __init__(self, what):
         self.what = what
@@ -26,10 +39,20 @@
 
     def __init__(self, host, params, defssid):
         self.router = host
-        # TODO(sleffler) default to 1st available wireless nic
-        self.phydev = params['phydev']
-        # TODO(sleffler) default to 1st available wired nic
-        self.wiredif = params['wiredev']
+        # default to 1st available wireless nic
+        if "phydev" not in params:
+            self.phydev = find_ifnet(host, ".*media:.IEEE.802.11.*")
+            if self.phydev is None:
+                raise Exception("No wireless NIC found")
+        else:
+            self.phydev = params['phydev']
+        # default to 1st available wired nic
+        if "wiredev" not in params:
+            self.wiredif = find_ifnet(host, ".*media:.Ethernet.*")
+            if self.wiredif is None:
+                raise Exception("No wired NIC found")
+        else:
+            self.wiredif = params['wiredev']
         self.defssid = defssid;
         self.wlanif = None
         self.bridgeif = None
@@ -49,7 +72,6 @@
             ignore_status=True)
         self.router.run("killall hostapd >/dev/null 2>&1", ignore_status=True)
 
-
     def create(self, params):
         """ Create a wifi device of the specified type """
 
diff --git a/server/site_linux_router.py b/server/site_linux_router.py
index f3f5e38..4c00c6a 100644
--- a/server/site_linux_router.py
+++ b/server/site_linux_router.py
@@ -2,6 +2,12 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import re
+
+def isLinuxRouter(router):
+    router_uname = router.run('uname').stdout
+    return re.search('Linux', router_uname)
+
 class LinuxRouter(object):
     """
     Linux/mac80211-style WiFi Router support for WiFiTest class.
diff --git a/server/site_tests/network_WiFiMatFunc/control b/server/site_tests/network_WiFiMatFunc/control
index 1732c23..4294d41 100644
--- a/server/site_tests/network_WiFiMatFunc/control
+++ b/server/site_tests/network_WiFiMatFunc/control
@@ -8,7 +8,7 @@
 TEST_CATEGORY = "Stress"
 TEST_CLASS = "network"
 TEST_TYPE = "Server"
-SYNC_COUNT = 2
+SYNC_COUNT = 1
 DOC = """
 WiFiMatFunc is a suite of 3-machine tests to validate basic WiFi functionality.
 One client, one server, and one programmable WiFi AP/Router are required
@@ -27,26 +27,22 @@
 
 import os
 
-fd = open(os.path.join(job.configdir, "wifi_testbed_config"))
-config = eval(fd.read())
-client = config['client']
-client['addr'] = machines[0]    # NB: take client identity from command line
-client['host'] = hosts.create_host(client['addr'])
+config = site_wifitest.read_wifi_testbed_config(
+    os.path.join(job.configdir, "wifi_testbed_config"),
+    client_addr = machines[0])    # NB: take client identity from command line
+
 server = config['server']
-if 'addr' in server:
-    server['host'] = hosts.create_host(server['addr'])
 router = config['router']
-router['host'] = hosts.create_host(router['addr'])
 
 logging.info("Client %s, Server %s, AP %s" % \
-    (client['addr'], getattr(server, 'addr', 'N/A'), router['addr']))
+    (machines[0], server.get('addr', 'N/A'), router['addr']))
 
-tagname = router['addr']
+tagname = config['tagname']
 
 dir = os.path.join(job.serverdir, "site_tests", "network_WiFiMatFunc")
 # TODO(sleffler) allow test list override from command line
 tests = site_wifitest.read_tests(dir, '[0-9]*')
 
 for t in tests:
-    wt = site_wifitest.WiFiTest(t['name'], t['steps'], router, client, server)
+    wt = site_wifitest.WiFiTest(t['name'], t['steps'], config)
     wt.run()
diff --git a/server/site_tests/network_WiFiSecMat/control b/server/site_tests/network_WiFiSecMat/control
index c8eea28..8e2a8f7 100644
--- a/server/site_tests/network_WiFiSecMat/control
+++ b/server/site_tests/network_WiFiSecMat/control
@@ -8,7 +8,7 @@
 TEST_CATEGORY = "Stress"
 TEST_CLASS = "network"
 TEST_TYPE = "Server"
-SYNC_COUNT = 2
+SYNC_COUNT = 1
 DOC = """
 WiFiMatFunc is a suite of 3-machine tests to validate basic WiFi functionality.
 One client, one server, and one programmable WiFi AP/Router are required
@@ -27,25 +27,22 @@
 
 import os
 
-fd = open(os.path.join(job.configdir, "wifi_testbed_config"))
-config = eval(fd.read())
-client = config['client']
-client['addr'] = machines[0]    # NB: take client identity from command line
-client['host'] = hosts.create_host(client['addr'])
+config = site_wifitest.read_wifi_testbed_config(
+    os.path.join(job.configdir, "wifi_testbed_config"),
+    client_addr = machines[0])    # NB: take client identity from command line
+
 server = config['server']
-server['host'] = hosts.create_host(server['addr'])
 router = config['router']
-router['host'] = hosts.create_host(router['addr'])
 
 logging.info("Client %s, Server %s, AP %s" % \
-    (client['addr'], server['addr'], router['addr']))
+    (machines[0], server.get('addr', 'N/A'), router['addr']))
 
-tagname = router['addr']
+tagname = config['tagname']
 
 dir = os.path.join(job.serverdir, "site_tests", "network_WiFiSecMat")
 # TODO(sleffler) allow test list override from command line
 tests = site_wifitest.read_tests(dir, '[0-9]*')
 
 for t in tests:
-    wt = site_wifitest.WiFiTest(t['name'], t['steps'], router, client, server)
+    wt = site_wifitest.WiFiTest(t['name'], t['steps'], config)
     wt.run()
diff --git a/server/site_wifitest.py b/server/site_wifitest.py
index 13672b1..0fb080a 100644
--- a/server/site_wifitest.py
+++ b/server/site_wifitest.py
@@ -4,7 +4,7 @@
 
 import common, fnmatch, logging, os, re, string, threading, time
 
-from autotest_lib.server import autotest, subcommand
+from autotest_lib.server import autotest, hosts, subcommand
 from autotest_lib.server import site_bsd_router
 from autotest_lib.server import site_linux_router
 
@@ -63,46 +63,61 @@
     wpa_supplicant directly.
     """
 
-    def __init__(self, name, steps, router, client, server):
+    def __init__(self, name, steps, config):
         self.name = name
         self.steps = steps
-        self.router = router['host']
+
+        router = config['router']
+        self.router = hosts.create_host(router['addr'])
+        # NB: truncate SSID to 32 characters
+        self.defssid = self.__get_defssid(router['addr'])[0:32]
+
+        if 'type' not in router:
+            # auto-detect router type
+            if site_linux_router.isLinuxRouter(self.router):
+                router['type'] = 'linux'
+            if site_bsd_router.isBSDRouter(self.router):
+                router['type'] = 'bsd'
+            else:
+                raise Exception('Unable to autodetect router type')
+        if router['type'] == 'linux':
+            self.wifi = site_linux_router.LinuxRouter(self.router, router,
+                self.defssid)
+        elif router['type'] == 'bsd':
+            self.wifi = site_bsd_router.BSDRouter(self.router, router,
+                self.defssid)
+        else:
+            raise Exception('Unsupported router')
+
         #
         # The client machine must be reachable from the control machine.
-        # The address on the wifi network is retrieved after it each time
-        # it associates to the router.
+        # The address on the wifi network is retrieved each time it
+        # associates to the router.
         #
-        self.client = client['host']
+        client = config['client']
+        self.client = hosts.create_host(client['addr'])
         self.client_at = autotest.Autotest(self.client)
         self.client_wifi_ip = None       # client's IP address on wifi net
+        # interface name on client
+        self.client_wlanif = client.get('wlandev', "wlan0")
+
         #
         # The server machine may be multi-homed or only on the wifi
         # network.  When only on the wifi net we suppress server_*
         # requests since we cannot initiate them from the control machine.
         #
-        self.server = getattr(server, 'host', None)
-        if self.server is not None:
+        server = config['server']
+        # NB: server may not be reachable on the control network
+        if 'addr' in server:
+            self.server = hosts.create_host(server['addr'])
             self.server_at = autotest.Autotest(self.server)
             # if not specified assume the same as the control address
             self.server_wifi_ip = getattr(server, 'wifi_addr', self.server.ip)
         else:
-            # NB: must be set if not reachable from control
+            self.server = None;
+            # NB: wifi address must be set if not reachable from control
             self.server_wifi_ip = server['wifi_addr']
 
-        # NB: truncate SSID to 32 characters
-        self.defssid = self.__get_defssid()[0:32]
-        # interface name on client
-        self.wlanif = "wlan0"
-
-        # auto-detect router type
-        router_uname = self.router.run('uname').stdout
-        if re.search('Linux', router_uname):
-            self.wifi = site_linux_router.LinuxRouter(self.router, router, self.defssid)
-        elif re.search('BSD', router_uname):
-            self.wifi = site_bsd_router.BSDRouter(self.router, router, self.defssid)
-        else:
-            raise Exception('Unsupported router')
-
         # potential bg thread for ping untilstop
         self.ping_thread = None
 
@@ -113,13 +128,12 @@
         self.wifi.destroy({})
 
 
-    def __get_defssid(self):
+    def __get_defssid(self, ipaddr):
         #
         # Calculate ssid based on test name; this lets us track progress
         # by watching beacon frames.
         #
-        return re.sub('[^a-zA-Z0-9_]', '_', \
-            "%s_%s" % (self.name, self.router.ip))
+        return re.sub('[^a-zA-Z0-9_]', '_', "%s_%s" % (self.name, ipaddr))
 
 
     def run(self):
@@ -250,7 +264,7 @@
         print "%s: %s" % (self.name, result.stdout[0:-1])
 
         # fetch IP address of wireless device
-        self.client_wifi_ip = self.__get_ipaddr(self.client, self.wlanif)
+        self.client_wifi_ip = self.__get_ipaddr(self.client, self.client_wlanif)
         logging.info("%s: client WiFi-IP is %s", self.name, self.client_wifi_ip)
 
 
@@ -303,7 +317,7 @@
 
     def client_check_bintval(self, params):
         """ Verify negotiated beacon interval """
-        result = self.router.run("ifconfig %s" % self.wlanif)
+        result = self.router.run("ifconfig %s" % self.client_wlanif)
         want = params[0]
         m = re.search('bintval ([0-9]*)', result.stdout)
         if m is None:
@@ -316,7 +330,7 @@
 
     def client_check_dtimperiod(self, params):
         """ Verify negotiated DTIM period """
-        result = self.router.run("ifconfig %s" % self.wlanif)
+        result = self.router.run("ifconfig %s" % self.client_wlanif)
         want = params[0]
         m = re.search('dtimperiod ([0-9]*)', result.stdout)
         if m is None:
@@ -329,7 +343,7 @@
 
     def client_check_rifs(self, params):
         """ Verify negotiated RIFS setting """
-        result = self.router.run("ifconfig %s" % self.wlanif)
+        result = self.router.run("ifconfig %s" % self.client_wlanif)
         m = re.search('[^-]rifs', result.stdout)
         if m is None:
             raise AssertionError
@@ -337,7 +351,7 @@
 
     def client_check_shortgi(self, params):
         """ Verify negotiated Short GI setting """
-        result = self.router.run("ifconfig %s" % self.wlanif)
+        result = self.router.run("ifconfig %s" % self.client_wlanif)
         m = re.search('[^-]shortgi', result.stdout)
         if m is None:
             raise AssertionError
@@ -634,3 +648,29 @@
     # use filenames to sort
     return sorted(tests, cmp=__byfile)
 
+
+def read_wifi_testbed_config(file, client_addr=None, server_addr=None,
+        router_addr=None):
+    # read configuration file
+    fd = open(file)
+    config = eval(fd.read())
+
+    # client must be reachable on the control network
+    client = config['client']
+    if client_addr is not None:
+        client['addr'] = client_addr;
+
+    # router must be reachable on the control network
+    router = config['router']
+    if router_addr is not None:
+        server['router'] = router_addr;
+
+    server = config['server']
+    if server_addr is not None:
+        server['addr'] = server_addr;
+    # TODO(sleffler) check for wifi_addr when no control address
+
+    # tag jobs w/ the router's address on the control network
+    config['tagname'] = router['addr']
+
+    return config