autotest: Refactor router configuration
1) Add new HostapConfig object which abstracts and aids router
configuration.
2) Change new WiFi tests to configure routers through a configure()
method on the WiFiTestContextManager. This facillitates packet
captures since we start the captures at configuration time. Routing
this call through the context lets us share a common point to
start client/server/router captures, similar to site_wifitest.
TEST=network_WiFi_SimpleConnect/control.* pass. Manually inspected the
802.11n parameters for correctness.
BUG=chromium:231429
Change-Id: Ifa4b92ce96094451864769bb5b3df806017773a1
Reviewed-on: https://gerrit.chromium.org/gerrit/48668
Tested-by: Christopher Wiley <wiley@chromium.org>
Reviewed-by: Paul Stewart <pstew@chromium.org>
Commit-Queue: Christopher Wiley <wiley@chromium.org>
diff --git a/server/cros/wlan/hostap_config.py b/server/cros/wlan/hostap_config.py
new file mode 100644
index 0000000..231be60
--- /dev/null
+++ b/server/cros/wlan/hostap_config.py
@@ -0,0 +1,164 @@
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import logging
+
+from autotest_lib.client.common_lib import error
+
+
+class HostapConfig(object):
+ """Parameters for router configuration."""
+
+ # A mapping of frequency to channel number. This includes some
+ # frequencies used outside the US.
+ CHANNEL_MAP = {2412: 1,
+ 2417: 2,
+ 2422: 3,
+ 2427: 4,
+ 2432: 5,
+ 2437: 6,
+ 2442: 7,
+ 2447: 8,
+ 2452: 9,
+ 2457: 10,
+ 2462: 11,
+ # 12, 13 are only legitimate outside the US.
+ 2467: 12,
+ 2472: 13,
+ # 34 valid in Japan.
+ 5170: 34,
+ # 36-116 valid in the US, except 38, 42, and 46, which have
+ # mixed international support.
+ 5180: 36,
+ 5190: 38,
+ 5200: 40,
+ 5210: 42,
+ 5220: 44,
+ 5230: 46,
+ 5240: 48,
+ 5260: 52,
+ 5280: 56,
+ 5300: 60,
+ 5320: 64,
+ 5500: 100,
+ 5520: 104,
+ 5540: 108,
+ 5560: 112,
+ 5580: 116,
+ # 120, 124, 128 valid in Europe/Japan.
+ 5600: 120,
+ 5620: 124,
+ 5640: 128,
+ # 132+ valid in US.
+ 5660: 132,
+ 5680: 136,
+ 5700: 140,
+ 5745: 149,
+ 5765: 153,
+ 5785: 157,
+ 5805: 161,
+ 5825: 165}
+
+ MODE_11A = 'a'
+ MODE_11B = 'b'
+ MODE_11G = 'g'
+ MODE_11N_MIXED = 'n-mixed'
+ MODE_11N_PURE = 'n-only'
+
+ N_CAPABILITY_WMM = object()
+ N_CAPABILITY_HT20 = object()
+ N_CAPABILITY_HT40 = object()
+ N_CAPABILITY_HT40_PLUS = object()
+ N_CAPABILITY_HT40_MINUS = object()
+ N_CAPABILITY_GREENFIELD = object()
+ N_CAPABILITY_SHORT_GI = object()
+
+
+ def __init__(self, mode=None, channel=None, frequency=None,
+ n_capabilities=None, hide_ssid=None):
+ """Construct a HostapConfig.
+
+ You may specify channel or frequency, but not both. Both options
+ are checked for validity (i.e. you can't specify an invalid channel
+ or a frequency that will not be accepted).
+
+ @param mode string MODE_11x defined above.
+ @param channel int channel number.
+ @param frequency int frequency of channel.
+ @param n_capabilities list of N_CAPABILITY_x defined above.
+ @param hide_ssid True if we should set up a hidden SSID.
+
+ """
+ super(HostapConfig, self).__init__()
+ if channel is not None and frequency is not None:
+ raise error.TestError('Specify either frequency or channel '
+ 'but not both.')
+
+ if channel is None and frequency is None:
+ raise error.TestError('Specify either frequency or channel.')
+
+ for real_frequency, real_channel in self.CHANNEL_MAP.iteritems():
+ if frequency == real_frequency or channel == real_channel:
+ self.frequency = real_frequency
+ self.channel = real_channel
+ break
+ else:
+ raise error.TestError('Invalid channel %r or frequency %r '
+ 'specified.' % channel, frequency)
+
+ self.is_11n = False
+ self.require_ht = False
+ if mode in (self.MODE_11N_MIXED, self.MODE_11N_PURE) or n_capabilities:
+ if mode == self.MODE_11N_PURE:
+ self.require_ht = True
+ # For their own historical reasons, hostapd wants it this way.
+ if self.frequency > 5000:
+ mode = self.MODE_11A
+ else:
+ mode = self.MODE_11G
+ self.is_11n = True
+ if self.frequency > 5000 and mode != self.MODE_11A:
+ raise error.TestError('Must use 11a or 11n mode for '
+ 'frequency > 5Ghz')
+
+ if self.frequency < 5000 and mode == self.MODE_11A:
+ raise error.TestError('Cannot use 11a with frequency %d.' %
+ self.frequency)
+
+ if not mode in (self.MODE_11A, self.MODE_11B, self.MODE_11G, None):
+ raise error.TestError('Invalid router mode %r' % mode)
+
+ self.wmm_enabled = False
+ self.hw_mode = mode or self.MODE_11B
+ self.ssid_suffix = '_ch%d' % self.channel
+ if n_capabilities is None:
+ n_capabilities = []
+ self.n_capabilities = set()
+ for cap in n_capabilities:
+ if cap == self.N_CAPABILITY_HT40:
+ self.wmm_enabled = True
+ self.n_capabilities.add('[HT40-]')
+ self.n_capabilities.add('[HT40+]')
+ elif cap == self.N_CAPABILITY_HT40_PLUS:
+ self.wmm_enabled = True
+ self.n_capabilities.add('[HT40+]')
+ elif cap == self.N_CAPABILITY_HT40_MINUS:
+ self.wmm_enabled = True
+ self.n_capabilities.add('[HT40-]')
+ elif cap == self.N_CAPABILITY_GREENFIELD:
+ logging.warning('Greenfield flag is ignored for hostap...')
+ #TODO(wiley) Why does this not work?
+ #self.n_capabilities.add('[GF]')
+ elif cap == self.N_CAPABILITY_SHORT_GI:
+ self.n_capabilities.add('[SHORT-GI-20]')
+ self.n_capabilities.add('[SHORT-GI-40]')
+ elif cap == self.N_CAPABILITY_HT20:
+ # This isn't a real thing. HT mode implies 20 supported.
+ self.wmm_enabled = True
+ elif cap == self.N_CAPABILITY_WMM:
+ self.wmm_enabled = True
+ else:
+ raise error.TestError('Unknown capability: %r' % cap)
+
+ self.hide_ssid = hide_ssid
diff --git a/server/cros/wlan/wifi_test_context_manager.py b/server/cros/wlan/wifi_test_context_manager.py
index 88ade4d..fb11662 100644
--- a/server/cros/wlan/wifi_test_context_manager.py
+++ b/server/cros/wlan/wifi_test_context_manager.py
@@ -87,6 +87,19 @@
return self.server.wifi_ip
+ def configure(self, configuration_parameters):
+ """Configure a router with the given parameters.
+
+ Configures an AP according to the specified parameters and
+ enables whatever packet captures are appropriate.
+
+ @param configuration_parameters HostapConfig object.
+
+ """
+ self.router.hostap_configure(configuration_parameters)
+ # TODO(wiley) enable packet captures here.
+
+
def setup(self):
"""Construct the state used in a WiFi test."""
if utils.host_is_in_lab_zone(self.client.host.hostname):
diff --git a/server/site_linux_router.py b/server/site_linux_router.py
index da0f85e..4065644 100644
--- a/server/site_linux_router.py
+++ b/server/site_linux_router.py
@@ -160,6 +160,7 @@
@param params dict of site_wifitest parameters.
"""
+ logging.info('Starting hostapd with parameters: %r', conf)
# Figure out the correct interface.
interface = self._get_wlanif(self.hostapd['frequency'],
self.phytype,
@@ -225,6 +226,57 @@
"""Kill all hostapd instances."""
self.kill_hostapd_instance(None)
+
+ def __get_default_hostap_config(self):
+ """@return dict of default options for hostapd."""
+ conf = self.hostapd['conf']
+ # default RTS and frag threshold to ``off''
+ conf['rts_threshold'] = '2347'
+ conf['fragm_threshold'] = '2346'
+ conf['driver'] = self.hostapd['driver']
+ return conf
+
+
+ def hostap_configure(self, configuration, multi_interface=None):
+ """Build up a hostapd configuration file and start hostapd.
+
+ Also setup a local server if this router supports them.
+
+ @param configuration HosetapConfig object.
+ @param multi_interface bool True iff multiple interfaces allowed.
+
+ """
+ if multi_interface is None and (self.hostapd['configured'] or
+ self.station['configured']):
+ self.deconfig()
+ # Start with the default hostapd config parameters.
+ conf = self.__get_default_hostap_config()
+ conf['ssid'] = (self.defssid + configuration.ssid_suffix)[-32:]
+ conf['channel'] = configuration.channel
+ self.hostapd['frequency'] = configuration.frequency
+ conf['hw_mode'] = configuration.hw_mode
+ if configuration.hide_ssid:
+ conf['ignore_broadcast_ssid'] = 1
+ if configuration.is_11n:
+ conf['ieee80211n'] = 1
+ conf['ht_capab'] = ''.join(configuration.n_capabilities)
+ if configuration.wmm_enabled:
+ conf['wmm_enabled'] = 1
+ if configuration.require_ht:
+ conf['require_ht'] = 1
+ # TODO(wiley) beacon interval support
+ self.start_hostapd(conf, {})
+ # Configure transmit power
+ tx_power_params = {'interface': conf['interface']}
+ # TODO(wiley) support for setting transmit power
+ self.set_txpower(tx_power_params)
+ if self.force_local_server:
+ self.start_local_server(conf['interface'])
+ self._post_start_hook({})
+ logging.info('AP configured.')
+ self.hostapd['configured'] = True
+
+
def hostap_config(self, params):
"""Configure the AP per test requirements.
@@ -244,18 +296,10 @@
local_server = params.pop('local_server', False)
- # Construct the hostapd.conf file and start hostapd.
- conf = self.hostapd['conf']
- # default RTS and frag threshold to ``off''
- conf['rts_threshold'] = '2347'
- conf['fragm_threshold'] = '2346'
-
+ conf = self.__get_default_hostap_config()
tx_power_params = {}
htcaps = set()
- conf['driver'] = params.get('hostapd_driver',
- self.hostapd['driver'])
-
for k, v in params.iteritems():
if k == 'ssid':
conf['ssid'] = v
diff --git a/server/site_tests/network_WiFi_SimpleConnect/control.check11a b/server/site_tests/network_WiFi_SimpleConnect/control.check11a
index d55c080..8ab9451 100644
--- a/server/site_tests/network_WiFi_SimpleConnect/control.check11a
+++ b/server/site_tests/network_WiFi_SimpleConnect/control.check11a
@@ -13,19 +13,18 @@
"""
+from autotest_lib.server.cros.wlan import hostap_config
+
+
def run(machine):
- # TODO(tgao): do we need to test connection on more channels?
- channels = [{'channel': '5240',
- 'mode': '11a',
- 'ssid_suffix': 'ch48'},
- {'channel': '5320',
- 'mode': '11a',
- 'ssid_suffix': 'ch64'}]
+ a_mode = hostap_config.HostapConfig.MODE_11A
+ configurations = [hostap_config.HostapConfig(channel=48, mode=a_mode),
+ hostap_config.HostapConfig(channel=64, mode=a_mode)]
host = hosts.create_host(machine)
job.run_test('network_WiFi_SimpleConnect',
host=host,
raw_cmdline_args=args,
- additional_params=channels)
+ additional_params=configurations)
parallel_simple(run, machines)
diff --git a/server/site_tests/network_WiFi_SimpleConnect/control.check11b b/server/site_tests/network_WiFi_SimpleConnect/control.check11b
index a3f7759..1274866 100644
--- a/server/site_tests/network_WiFi_SimpleConnect/control.check11b
+++ b/server/site_tests/network_WiFi_SimpleConnect/control.check11b
@@ -13,21 +13,19 @@
"""
+from autotest_lib.server.cros.wlan import hostap_config
+
+
def run(machine):
- channels = [{'channel': '2412',
- 'mode': '11b',
- 'ssid_suffix': 'ch1'},
- {'channel': '2437',
- 'mode': '11b',
- 'ssid_suffix': 'ch6'},
- {'channel': '2462',
- 'mode': '11b',
- 'ssid_suffix': 'ch11'} ]
+ b_mode = hostap_config.HostapConfig.MODE_11B
+ configurations = [hostap_config.HostapConfig(channel=1, mode=b_mode),
+ hostap_config.HostapConfig(channel=6, mode=b_mode),
+ hostap_config.HostapConfig(channel=11, mode=b_mode)]
host = hosts.create_host(machine)
job.run_test('network_WiFi_SimpleConnect',
host=host,
raw_cmdline_args=args,
- additional_params=channels)
+ additional_params=configurations)
parallel_simple(run, machines)
diff --git a/server/site_tests/network_WiFi_SimpleConnect/control.check11g b/server/site_tests/network_WiFi_SimpleConnect/control.check11g
index 0f83c22..0f23ffa 100644
--- a/server/site_tests/network_WiFi_SimpleConnect/control.check11g
+++ b/server/site_tests/network_WiFi_SimpleConnect/control.check11g
@@ -13,24 +13,19 @@
"""
+from autotest_lib.server.cros.wlan import hostap_config
+
+
def run(machine):
- channels = [{'channel': '2412',
- 'mode': '11g',
- 'pureg': None,
- 'ssid_suffix': 'ch1'},
- {'channel': '2437',
- 'mode': '11g',
- 'pureg': None,
- 'ssid_suffix': 'ch6'},
- {'channel': '2462',
- 'mode': '11g',
- 'pureg': None,
- 'ssid_suffix': 'ch11'}]
+ g_mode = hostap_config.HostapConfig.MODE_11G
+ configurations = [hostap_config.HostapConfig(channel=1, mode=g_mode),
+ hostap_config.HostapConfig(channel=6, mode=g_mode),
+ hostap_config.HostapConfig(channel=11, mode=g_mode)]
host = hosts.create_host(machine)
job.run_test('network_WiFi_SimpleConnect',
host=host,
raw_cmdline_args=args,
- additional_params=channels)
+ additional_params=configurations)
parallel_simple(run, machines)
diff --git a/server/site_tests/network_WiFi_SimpleConnect/control.check24HT20 b/server/site_tests/network_WiFi_SimpleConnect/control.check24HT20
index 7e526e7..e1b4fef 100644
--- a/server/site_tests/network_WiFi_SimpleConnect/control.check24HT20
+++ b/server/site_tests/network_WiFi_SimpleConnect/control.check24HT20
@@ -13,24 +13,22 @@
"""
+from autotest_lib.server.cros.wlan import hostap_config
+
+
def run(machine):
- channels = [{'channel': '2412',
- 'ht20': None,
- 'puren': None,
- 'ssid_suffix': 'ch1'},
- {'channel': '2437',
- 'ht20': None,
- 'puren': None,
- 'ssid_suffix': 'ch6'},
- {'channel': '2462',
- 'ht20': None,
- 'puren': None,
- 'ssid_suffix': 'ch11'}]
+ caps = [hostap_config.HostapConfig.N_CAPABILITY_GREENFIELD,
+ hostap_config.HostapConfig.N_CAPABILITY_HT20]
+ n = hostap_config.HostapConfig.MODE_11N_PURE
+ configurations = [
+ hostap_config.HostapConfig(channel=1, mode=n, n_capabilities=caps),
+ hostap_config.HostapConfig(channel=6, mode=n, n_capabilities=caps),
+ hostap_config.HostapConfig(channel=11, mode=n, n_capabilities=caps)]
host = hosts.create_host(machine)
job.run_test('network_WiFi_SimpleConnect',
host=host,
raw_cmdline_args=args,
- additional_params=channels)
+ additional_params=configurations)
parallel_simple(run, machines)
diff --git a/server/site_tests/network_WiFi_SimpleConnect/control.check24HT40 b/server/site_tests/network_WiFi_SimpleConnect/control.check24HT40
index 9d0aaae..d968c1a 100644
--- a/server/site_tests/network_WiFi_SimpleConnect/control.check24HT40
+++ b/server/site_tests/network_WiFi_SimpleConnect/control.check24HT40
@@ -13,13 +13,21 @@
"""
+
+
def run(machine):
- channels = [{'channel': '2437', 'ht40': None, 'puren': None}]
+ from autotest_lib.server.cros.wlan import hostap_config
+ caps = [hostap_config.HostapConfig.N_CAPABILITY_GREENFIELD,
+ hostap_config.HostapConfig.N_CAPABILITY_HT40]
+ n = hostap_config.HostapConfig.MODE_11N_PURE
+ configurations = [hostap_config.HostapConfig(frequency=2437,
+ mode=n,
+ n_capabilities=caps)]
host = hosts.create_host(machine)
job.run_test('network_WiFi_SimpleConnect',
host=host,
raw_cmdline_args=args,
- additional_params=channels)
+ additional_params=configurations)
parallel_simple(run, machines)
diff --git a/server/site_tests/network_WiFi_SimpleConnect/control.check5HT20 b/server/site_tests/network_WiFi_SimpleConnect/control.check5HT20
index 6b49ef5..52aef3c 100644
--- a/server/site_tests/network_WiFi_SimpleConnect/control.check5HT20
+++ b/server/site_tests/network_WiFi_SimpleConnect/control.check5HT20
@@ -13,13 +13,20 @@
"""
+from autotest_lib.server.cros.wlan import hostap_config
+
+
def run(machine):
- channels = [{'channel': '5240', 'ht20': None, 'puren': None}]
+ caps = [hostap_config.HostapConfig.N_CAPABILITY_GREENFIELD,
+ hostap_config.HostapConfig.N_CAPABILITY_HT20]
+ n = hostap_config.HostapConfig.MODE_11N_PURE
+ configurations = [hostap_config.HostapConfig(frequency=5240, mode=n,
+ n_capabilities=caps)]
host = hosts.create_host(machine)
job.run_test('network_WiFi_SimpleConnect',
host=host,
raw_cmdline_args=args,
- additional_params=channels)
+ additional_params=configurations)
parallel_simple(run, machines)
diff --git a/server/site_tests/network_WiFi_SimpleConnect/control.check5HT40 b/server/site_tests/network_WiFi_SimpleConnect/control.check5HT40
index 2b00af3..d635c7c 100644
--- a/server/site_tests/network_WiFi_SimpleConnect/control.check5HT40
+++ b/server/site_tests/network_WiFi_SimpleConnect/control.check5HT40
@@ -14,13 +14,21 @@
"""
+from autotest_lib.server.cros.wlan import hostap_config
+
+
def run(machine):
- channels = [{'channel': '5240', 'ht40-': None, 'puren': None}]
+ caps = [hostap_config.HostapConfig.N_CAPABILITY_GREENFIELD,
+ hostap_config.HostapConfig.N_CAPABILITY_HT40_MINUS]
+ n = hostap_config.HostapConfig.MODE_11N_PURE
+ configurations = [hostap_config.HostapConfig(frequency=5240,
+ mode=n,
+ n_capabilities=caps)]
host = hosts.create_host(machine)
job.run_test('network_WiFi_SimpleConnect',
host=host,
raw_cmdline_args=args,
- additional_params=channels)
+ additional_params=configurations)
parallel_simple(run, machines)
diff --git a/server/site_tests/network_WiFi_SimpleConnect/network_WiFi_SimpleConnect.py b/server/site_tests/network_WiFi_SimpleConnect/network_WiFi_SimpleConnect.py
index 0fa8fad..a584474 100644
--- a/server/site_tests/network_WiFi_SimpleConnect/network_WiFi_SimpleConnect.py
+++ b/server/site_tests/network_WiFi_SimpleConnect/network_WiFi_SimpleConnect.py
@@ -17,13 +17,13 @@
@param additional_params list of dicts describing router configs.
"""
- self._channels = additional_params
+ self._configurations = additional_params
def run_once_impl(self):
"""Sets up a router, connects to it, pings it, and repeats."""
- for channel in self._channels:
- self.context.router.config(channel)
+ for configuration in self._configurations:
+ self.context.configure(configuration)
assoc_params = xmlrpc_datatypes.AssociationParameters()
assoc_params.ssid = self.context.router.get_ssid()
self.assert_connect_wifi(assoc_params)