Merge "conman:  Set WLAN retry delay properly after a new WLAN config."
diff --git a/conman/connection_manager.py b/conman/connection_manager.py
index b3e11e6..c9a4db1 100755
--- a/conman/connection_manager.py
+++ b/conman/connection_manager.py
@@ -76,11 +76,13 @@
   WIFI_SETCLIENT = ['wifi', 'setclient', '--persist']
   WIFI_STOPCLIENT = ['wifi', 'stopclient', '--persist']
 
-  def __init__(self, band, wifi, command_lines):
+  def __init__(self, band, wifi, command_lines, retry_s):
     self.band = band
     self.wifi = wifi
     self.logger = self.wifi.logger.getChild(self.band)
     self.command = command_lines.splitlines()
+    self.retry_s = retry_s
+    self.try_after = 0
     self.access_point_up = False
     self.ssid = None
     self.passphrase = None
@@ -155,6 +157,11 @@
       self.logger.info('WifiNo2GClient enabled; not starting 2.4 GHz client.')
       return
 
+    now = _gettime()
+    if now <= self.try_after:
+      self.logger.debug('Not retrying for another %.2fs', self.try_after - now)
+      return
+
     up = self.client_up
     if up:
       self.logger.debug('Wifi client already started on %s GHz', self.band)
@@ -166,6 +173,9 @@
     if self._actually_start_client() and self.client_up:
       self.wifi.status.connected_to_wlan = True
       self.logger.info('Started wifi client on %s GHz', self.band)
+      self.try_after = now
+    else:
+      self.try_after = now + self.retry_s
 
   def _actually_start_client(self):
     """Actually run wifi setclient.
@@ -348,7 +358,6 @@
         self._stop_wifi(wifi.bands[0], True, True)
 
     self._interface_update_counter = 0
-    self._try_wlan_after = {'5': 0, '2.4': 0}
 
     for wifi in self.wifi:
       ratchet_name = '%s provisioning' % wifi.name
@@ -524,18 +533,16 @@
       # case 5 is unavailable for some reason.
       for band in wifi.bands:
         wlan_configuration = self._wlan_configuration.get(band, None)
-        if wlan_configuration and _gettime() >= self._try_wlan_after[band]:
+        if wlan_configuration:
           logger.info('Trying to join WLAN on %s.', wifi.name)
           wlan_configuration.start_client()
           if self._connected_to_wlan(wifi):
             logger.info('Joined WLAN on %s.', wifi.name)
             wifi.status.connected_to_wlan = True
-            self._try_wlan_after[band] = 0
             break
           else:
             logger.error('Failed to connect to WLAN on %s.', wifi.name)
             wifi.status.connected_to_wlan = False
-            self._try_wlan_after[band] = _gettime() + self._wlan_retry_s
       else:
         # If we are aren't on the WLAN, can ping the ACS, and haven't gotten a
         # new WLAN configuration yet, there are two possibilities:
@@ -559,9 +566,8 @@
           if self._wlan_configuration:
             logger.info('ACS has not updated WLAN configuration; will retry '
                         ' with old config.')
-            for w in self.wifi:
-              for b in w.bands:
-                self._try_wlan_after[b] = now
+            for wlan_configuration in self._wlan_configuration.itervalues():
+              wlan_configuration.try_after = now
             continue
           # We don't want to want to log this spammily, so do exponential
           # backoff.
@@ -716,7 +722,8 @@
           wifi = self.wifi_for_band(band)
           if wifi:
             self._update_wlan_configuration(
-                self.WLANConfiguration(band, wifi, contents))
+                self.WLANConfiguration(band, wifi, contents,
+                                       self._wlan_retry_s))
       elif filename.startswith(self.ACCESS_POINT_FILE_PREFIX):
         match = re.match(self.ACCESS_POINT_FILE_REGEXP, filename)
         if match:
diff --git a/conman/connection_manager_test.py b/conman/connection_manager_test.py
index e9807cd..bec59f7 100755
--- a/conman/connection_manager_test.py
+++ b/conman/connection_manager_test.py
@@ -108,7 +108,7 @@
       '--bridge=br0', '-s', 'my ssid=1', '--interface-suffix', '_suffix',
   ])
   config = connection_manager.WLANConfiguration(
-      '5', interface_test.Wifi('wcli0', 20), cmd)
+      '5', interface_test.Wifi('wcli0', 20), cmd, 10)
 
   wvtest.WVPASSEQ('my ssid=1', config.ssid)
   wvtest.WVPASSEQ('abcdWIFI_PSK=qwer', config.passphrase)
@@ -270,20 +270,21 @@
         # Test that missing directories are created by ConnectionManager.
         shutil.rmtree(tmp_dir)
 
-        c = ConnectionManager(tmp_dir=tmp_dir,
-                              config_dir=config_dir,
-                              moca_tmp_dir=moca_tmp_dir,
-                              run_duration_s=run_duration_s,
-                              interface_update_period=interface_update_period,
-                              wlan_retry_s=0,
-                              wifi_scan_period_s=wifi_scan_period_s,
-                              associate_wait_s=associate_wait_s,
-                              dhcp_wait_s=dhcp_wait_s,
-                              acs_connection_check_wait_s=acs_cc_wait_s,
-                              acs_start_wait_s=acs_start_wait_s,
-                              acs_finish_wait_s=acs_finish_wait_s,
-                              bssid_cycle_length_s=1,
-                              **cm_kwargs)
+        kwargs = dict(tmp_dir=tmp_dir,
+                      config_dir=config_dir,
+                      moca_tmp_dir=moca_tmp_dir,
+                      run_duration_s=run_duration_s,
+                      interface_update_period=interface_update_period,
+                      wlan_retry_s=0,
+                      wifi_scan_period_s=wifi_scan_period_s,
+                      associate_wait_s=associate_wait_s,
+                      dhcp_wait_s=dhcp_wait_s,
+                      acs_connection_check_wait_s=acs_cc_wait_s,
+                      acs_start_wait_s=acs_start_wait_s,
+                      acs_finish_wait_s=acs_finish_wait_s,
+                      bssid_cycle_length_s=1)
+        kwargs.update(cm_kwargs)
+        c = ConnectionManager(**kwargs)
 
         f(c)
       except Exception:
@@ -680,7 +681,7 @@
   wvtest.WVPASSEQ(last_bss_info.ssid, 's3')
   wvtest.WVPASSEQ(last_bss_info.bssid, 'ff:ee:dd:cc:bb:aa')
   # Attempt to interrupt provisioning, make sure it doesn't work.
-  c._try_wlan_after[band] = 0
+  c._wlan_configuration[band].try_after = 0
   # Second iteration: check that we try s3 again since there's no gateway yet.
   c.run_once()
   last_bss_info = c.wifi_for_band(band).last_attempted_bss_info
@@ -1103,5 +1104,20 @@
   wvtest.WVFAIL(c.client_up('2.4'))
 
 
+@test_common.wvtest
+@connection_manager_test(WIFI_SHOW_OUTPUT_MARVELL8897,
+                         wlan_configs={'5': False}, wlan_retry_s=30,
+                         __test_interfaces_already_up=[])
+def test_regression_b29364958(c):
+  def count_setclient_calls():
+    return len([1 for cmd, _ in subprocess.CALL_HISTORY
+                if 'wifi' in cmd and 'setclient' in cmd])
+
+  wvtest.WVPASSEQ(1, count_setclient_calls())
+  for _ in range(10):
+    c.run_once()
+  wvtest.WVPASSEQ(1, count_setclient_calls())
+
+
 if __name__ == '__main__':
   wvtest.wvtest_main()
diff --git a/conman/test/fake_python/subprocess/__init__.py b/conman/test/fake_python/subprocess/__init__.py
index 3d73d4d..ff8ad0a 100644
--- a/conman/test/fake_python/subprocess/__init__.py
+++ b/conman/test/fake_python/subprocess/__init__.py
@@ -11,6 +11,8 @@
 logger.setLevel(logging.DEBUG)
 
 
+CALL_HISTORY = []
+
 # Values are only for when the module name does not match the command name.
 _COMMAND_NAMES = {
     'connection_check': None,
@@ -50,6 +52,8 @@
 
 def _call(command, **kwargs):
   """Fake subprocess call."""
+  CALL_HISTORY.append((command, kwargs))
+
   if type(command) not in (tuple, list):
     raise Exception('Fake subprocess.call only supports list/tuple commands, '
                     'got: %s', command)
@@ -104,7 +108,9 @@
 
 
 def reset():
-  """Reset any module-level state."""
+  """Reset all state."""
+  global CALL_HISTORY
+  CALL_HISTORY = []
   for command in _COMMANDS.itervalues():
     if isinstance(command, types.ModuleType):
       reload(command)