autotest: Migrates network_WiFiRoaming/003SSIDMultiSwitchBack to new method.
Replaces a portion of network_WiFiRoaming/00SSIDMultiSwitchBack test
with script and control file in network_WiFi_SSIDMultiSwitchBack
directory. The 'low signal' tests will be submitted in a different CL.
New code conforms to test infrastructure changes provided to address
crbug.com/224443.
This roaming test configures 2 radios (different frequencies and BSSIDs) on
the same SSID, connects to one, and cuts its power to simulate an
out-of-range event. It then verifies that the DUT discovers this and
connects to the other radio. Wrote the test to use the
BeagleBone-connected RF attenuators (currently requires a specific
setup: chromeos3-grover-host1) to change the AP's transmit power rather
than the previous 'iw' command that didn't work, anyway.
This CL required various plumbing efforts in the test infrastructure:
- mulit_interface. Augmented RvRTestContextManager.configure to take, and
pass-through, multi_interface (did is_ibss while I was there) to allow
configuration of an AP to use 2 radios. Modified LinuxRouter to respond
to its multi_interface parameter.
- ap_num. Added ap_num to WifiTestContextManager.wait_for_connection and
.assert_ping_from_dut so that I could ping the second radio on an AP.
- port and MAX_ATTENUATION. Added port-specific attenuation calls to
Attenuator and made set_variable_attenuation public so that I could set
max attenuation (which is relative to the fixed_loss of the attenuator --
added MAX_ATTENUATION, as well).
BUG=chromium:263887
TEST=autotest (this one).
Change-Id: I00d3b445a22b0a94579f3b033b8f6039764d8c2a
Reviewed-on: https://chromium-review.googlesource.com/168457
Reviewed-by: Wade Guthrie <wdg@chromium.org>
Commit-Queue: Wade Guthrie <wdg@chromium.org>
Tested-by: Wade Guthrie <wdg@chromium.org>
diff --git a/server/cros/network/rvr_test_context_manager.py b/server/cros/network/rvr_test_context_manager.py
index df5f1aa..6fa4cc3 100644
--- a/server/cros/network/rvr_test_context_manager.py
+++ b/server/cros/network/rvr_test_context_manager.py
@@ -70,7 +70,16 @@
self._attenuator.cleanup()
- def configure(self, ap_config):
- """Configures AP and variable attenuators for a WiFi RvR test."""
- super(RvRTestContextManager, self).configure(ap_config)
+ def configure(self, ap_config, multi_interface=None, is_ibss=None):
+ """Configures AP and variable attenuators for a WiFi RvR test.
+
+ @param ap_config: parameters for access-point
+ @param multi_interface: bool indicates whether AP's second radio
+ is being configured by this command
+ @is_ibss: bool indicates whether this is an IBSS endpoint
+
+ """
+ super(RvRTestContextManager, self).configure(
+ configuration_parameters=ap_config,
+ multi_interface=multi_interface, is_ibss=is_ibss)
self._attenuator.config(self.client.host.hostname, ap_config.frequency)
diff --git a/server/cros/network/wifi_test_context_manager.py b/server/cros/network/wifi_test_context_manager.py
index 281c3b0..a10ee72 100644
--- a/server/cros/network/wifi_test_context_manager.py
+++ b/server/cros/network/wifi_test_context_manager.py
@@ -295,11 +295,12 @@
self.server.ping(ping_config)
- def wait_for_connection(self, ssid, freq=None):
+ def wait_for_connection(self, ssid, freq=None, ap_num=None):
"""Verifies a connection to network ssid on frequency freq.
@param ssid string ssid of the network to check.
@param freq int frequency of network to check.
+ @param ap_num int AP to which to connect
"""
success, state, elapsed_seconds = self.client.wait_for_service_states(
@@ -309,7 +310,7 @@
'Failed to connect to "%s" in %f seconds (state=%s)' %
(ssid, elapsed_seconds, state))
- self.assert_ping_from_dut()
+ self.assert_ping_from_dut(ap_num=ap_num)
if freq:
self.client.check_iw_link_value(
wifi_client.WiFiClient.IW_LINK_KEY_FREQUENCY, freq)
diff --git a/server/site_attenuator.py b/server/site_attenuator.py
index a7d9a33..442cf35 100644
--- a/server/site_attenuator.py
+++ b/server/site_attenuator.py
@@ -43,6 +43,10 @@
# We only use 2 ports out of the 4 available.
PORTS = [0, 1]
+ # The scripts that run on the attenuator limit the attenuation to this
+ # plus the fixed attenuation for the specific port.
+ MAX_VARIABLE_ATTENUATION = 95
+
# TODO(tgao): refactor & merge this w/ site_wifitest.install_script()
def _copy_script(self, script_name, *support_scripts):
@@ -53,6 +57,7 @@
@return a string, script_name if it's copied successfully.
@raises ScriptNotFound if any source script is not found.
+
"""
if script_name in self._installed_scripts:
return self._installed_scripts[script_name]
@@ -88,6 +93,7 @@
@param port: an integer, Beaglebone I/O port number (0 or 1).
@param cleanup: a boolean, True == unexport GPIO pins/reset port.
+
"""
# TODO(tgao): bundle these scripts as part of a test image?
flag = '-c' if cleanup else ''
@@ -105,6 +111,7 @@
"""Initialize.
@param host: an Autotest host object, representing the attenuator.
+
"""
self._host = host
self._installed_scripts = dict()
@@ -130,33 +137,54 @@
"""Reads current attenuation level in dB.
@param port: an integer, Beaglebone I/O port number (0 or 1).
+
"""
self._host.run('python "%s" -p %d 2>&1' % (self._config_script, port))
- def _set_variable_attenuation(self, variable_db):
+ def set_variable_attenuation_on_port(self, port, variable_db):
+ """Sets desired variable attenuation in dB.
+
+ @param port: port to attenuate.
+ @param variable_db: an integer, variable attenuation in dB.
+
+ """
+ fixed_db = self.fixed_loss[port]
+ total_db = fixed_db + variable_db
+ self._host.run('python "%s" -p %d -f %d -t %d 2>&1' %
+ (self._config_script, port, fixed_db, total_db))
+
+
+ def set_variable_attenuation(self, variable_db):
"""Sets desired variable attenuation in dB.
@param variable_db: an integer, variable attenuation in dB.
- @returns an integer, total attenuation in dB.
+
"""
for port in self.PORTS:
- fixed_db = self.fixed_loss[port]
- total_db = fixed_db + variable_db
- self._host.run('python "%s" -p %d -f %d -t %d 2>&1' %
- (self._config_script, port, fixed_db, total_db))
- return total_db
+ self.set_variable_attenuation_on_port(port, variable_db)
+
+
+ def set_total_attenuation_on_port(self, port, total_db):
+ """Sets desired total attenuation in dB.
+
+ @param port: port to attenuate.
+ @param total_db: an integer, total attenuation in dB.
+
+ """
+ self._host.run('python "%s" -p %d -f %d -t %d 2>&1' %
+ (self._config_script, port, self.fixed_loss[port],
+ total_db))
def set_total_attenuation(self, total_db):
"""Sets desired total attenuation in dB.
@param total_db: an integer, total attenuation in dB.
+
"""
for port in self.PORTS:
- self._host.run('python "%s" -p %d -f %d -t %d 2>&1' %
- (self._config_script, port, self.fixed_loss[port],
- total_db))
+ self.set_total_attenuation_on_port(port, total_db)
@staticmethod
@@ -168,6 +196,7 @@
@param freq an integer, frequency in MHz.
@returns an integer, approximate frequency from FREQ_LOSS_MAP.
+
"""
old_offset = None
approx_freq = None
@@ -203,6 +232,6 @@
logging.info('Looking up fixed path loss on freq %d', freq_used)
self.fixed_loss = self.FREQ_LOSS_MAP[freq_used][hostname]
- self._set_variable_attenuation(0)
+ self.set_variable_attenuation(0)
diff --git a/server/site_linux_router.py b/server/site_linux_router.py
index 19b0fa0..09a8718 100644
--- a/server/site_linux_router.py
+++ b/server/site_linux_router.py
@@ -357,14 +357,17 @@
conf['obss_interval'] = configuration.obss_interval
conf.update(configuration.get_security_hostapd_conf())
- self.start_hostapd(conf, {})
+ # TODO(wiley): Remove this multi_interface flag when the bridge router
+ # class is gone.
+ params = {'multi_interface': 1} if multi_interface else {}
+ self.start_hostapd(conf, params)
# 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({})
+ self._post_start_hook(params)
logging.info('AP configured.')
self.hostapd['configured'] = True
diff --git a/server/site_tests/network_WiFi_SSIDMultiSwitchBack/control b/server/site_tests/network_WiFi_SSIDMultiSwitchBack/control
new file mode 100644
index 0000000..5bae492
--- /dev/null
+++ b/server/site_tests/network_WiFi_SSIDMultiSwitchBack/control
@@ -0,0 +1,23 @@
+# 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.
+
+AUTHOR = 'Wade Guthrie <wdg@google.com>'
+TIME = 'SHORT'
+NAME = 'network_WiFi_SSIDMultiSwitchBack'
+TEST_CATEGORY = 'Functional'
+TEST_CLASS = 'network'
+TEST_TYPE = 'Server'
+DOC = """
+The SSIDSwitchBack test verifies that the connection manager is able
+to switch APs when one disappears.
+"""
+
+def run(machine):
+ host = hosts.create_host(machine)
+ job.run_test('network_WiFi_SSIDMultiSwitchBack',
+ host=host,
+ raw_cmdline_args=args)
+
+
+parallel_simple(run, machines)
diff --git a/server/site_tests/network_WiFi_SSIDMultiSwitchBack/network_WiFi_SSIDMultiSwitchBack.py b/server/site_tests/network_WiFi_SSIDMultiSwitchBack/network_WiFi_SSIDMultiSwitchBack.py
new file mode 100644
index 0000000..025819d
--- /dev/null
+++ b/server/site_tests/network_WiFi_SSIDMultiSwitchBack/network_WiFi_SSIDMultiSwitchBack.py
@@ -0,0 +1,105 @@
+# 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
+import time
+
+from autotest_lib.client.common_lib.cros.network import xmlrpc_datatypes
+from autotest_lib.server import site_attenuator
+from autotest_lib.server.cros.network import hostap_config
+from autotest_lib.server.cros.network import rvr_test_base
+
+class network_WiFi_SSIDMultiSwitchBack(rvr_test_base.RvRTestBase):
+ """Tests roaming to an AP when the old one's signal is too weak.
+
+ This test uses a dual-radio Stumpy as the AP and configures the radios to
+ broadcast two BSS's with different frequencies on the same SSID. The DUT
+ connects to the first radio, the test attenuates that radio, and the DUT
+ is supposed to roam to the second radio.
+
+ This test requires a particular configuration of test equipment:
+
+ +--------- StumpyCell/AP ----------+
+ | chromeX.grover.hostY.router.cros |
+ | |
+ | [Radio 0] [Radio 1] |
+ +--------A-----B----C-----D--------+
+ +------ BeagleBone ------+ | | | |
+ | chromeX.grover.hostY. | | X | X
+ | attenuator.cros [Port0]-[attenuator] |
+ | [Port1]----- | ----[attenuator]
+ | [Port2]-X | |
+ | [Port3]-X +-----+ |
+ | | | |
+ +------------------------+ | |
+ +--------------E----F--------------+
+ | [Radio 0] |
+ | |
+ | chromeX.grover.hostY.cros |
+ +-------------- DUT ---------------+
+
+ Where antennas A, C, and E are the primary antennas for AP/radio0,
+ AP/radio1, and DUT/radio0, respectively; and antennas B, D, and F are the
+ auxilliary antennas for AP/radio0, AP/radio1, and DUT/radio0,
+ respectively. The BeagleBone controls 2 attenuators that are connected
+ to the primary antennas of AP/radio0 and 1 which are fed into the primary
+ and auxilliary antenna ports of DUT/radio 0. Ports 2 and 3 of the
+ BeagleBone as well as the auxillary antennae of AP/radio0 and 1 are
+ terminated.
+
+ This arrangement ensures that the attenuator port numbers are assigned to
+ the primary radio, first, and the secondary radio, second. If this happens,
+ the ports will be numbered in the order in which the AP's channels are
+ configured (port 0 is first, port 1 is second, etc.).
+
+ This test is a de facto test that the ports are configured in that
+ arrangement since swapping Port0 and Port1 would cause us to attenuate the
+ secondary radio, providing no impetus for the DUT to switch radios and
+ causing the test to fail to connect at radio 1's frequency.
+
+ """
+
+ version = 1
+
+ FREQUENCY_0 = 2412
+ FREQUENCY_1 = 2462
+ PORT_0 = 0 # Port created first (FREQUENCY_0)
+ PORT_1 = 1 # Port created second (FREQUENCY_1)
+
+
+ def run_once(self):
+ """Test body."""
+
+ logging.info("- Configure first AP & connect")
+ self.context.configure(hostap_config.HostapConfig(
+ frequency=network_WiFi_SSIDMultiSwitchBack.FREQUENCY_0,
+ mode=hostap_config.HostapConfig.MODE_11G))
+ router_ssid = self.context.router.get_ssid()
+ self.context.assert_connect_wifi(xmlrpc_datatypes.AssociationParameters(
+ ssid=router_ssid))
+ self.context.assert_ping_from_dut()
+
+ logging.info('- Configure second AP')
+ self.context.configure(hostap_config.HostapConfig(
+ ssid=router_ssid,
+ frequency=network_WiFi_SSIDMultiSwitchBack.FREQUENCY_1,
+ mode=hostap_config.HostapConfig.MODE_11G),
+ multi_interface=True)
+
+ logging.info('- Drop the power on the first AP')
+ self.context.attenuator.set_variable_attenuation_on_port(
+ network_WiFi_SSIDMultiSwitchBack.PORT_0,
+ site_attenuator.Attenuator.MAX_VARIABLE_ATTENUATION)
+ time.sleep(50) # Give DUT time to scan and roam.
+
+ logging.info("- Wait for a connection on the second AP")
+ # Instead of explicitly connecting, just wait to see if the DUT
+ # connects to the second AP by itself
+ self.context.wait_for_connection(
+ ssid=router_ssid,
+ freq=network_WiFi_SSIDMultiSwitchBack.FREQUENCY_1,
+ ap_num=1)
+
+ # Clean up.
+ self.context.router.deconfig()