conman:  Run ifplugd.action at startup for wifi interfaces.

We already did this for eth0, which usually comes up before ifplugd
starts monitoring it.  It is also appropriate to do this for wifi
interfaces, in case we are already connected to the WLAN before
ifplugd starts.

Change-Id: I59a2ac3dc12d0fd8a3661aedfee8ee2bb21c9b6a
diff --git a/conman/connection_manager.py b/conman/connection_manager.py
index eaaf7cf..7379d88 100755
--- a/conman/connection_manager.py
+++ b/conman/connection_manager.py
@@ -85,6 +85,10 @@
     if self.ssid is None:
       raise ValueError('Command file does not specify SSID')
 
+    if self.wifi.initial_ssid == self.ssid:
+      logging.debug('Connected to WLAN at startup')
+      self.client_up = True
+
   def start_access_point(self):
     """Start an access point."""
 
@@ -237,11 +241,21 @@
 
     # If the ethernet file doesn't exist for any reason when conman starts,
     # check explicitly and run ifplugd.action to create the file.
-    if not os.path.exists(os.path.join(self._interface_status_dir,
-                                       self.ETHERNET_STATUS_FILE)):
-      ethernet_up = self.is_ethernet_up()
-      self.bridge.ethernet = ethernet_up
+    if not os.path.exists(os.path.join(self._interface_status_dir, 'eth0')):
+      ethernet_up = self.is_interface_up('eth0')
       self.ifplugd_action('eth0', ethernet_up)
+      self.bridge.ethernet = ethernet_up
+
+    # Do the same for wifi interfaces , but rather than explicitly setting that
+    # the wpa_supplicant link is up, attempt to attach to the wpa_supplicant
+    # control interface.
+    for wifi in self.wifi:
+      if not os.path.exists(
+          os.path.join(self._interface_status_dir, wifi.name)):
+        wifi_up = self.is_interface_up(wifi.name)
+        self.ifplugd_action(wifi.name, wifi_up)
+        if wifi_up:
+          wifi.attach_wpa_control(self._wpa_control_interface)
 
     for path, prefix in ((self._status_dir, self.GATEWAY_FILE_PREFIX),
                          (self._interface_status_dir, ''),
@@ -259,13 +273,16 @@
     self._interface_update_counter = 0
     self._try_wlan_after = {'5': 0, '2.4': 0}
 
-  def is_ethernet_up(self):
-    """Explicitly check whether ethernet is up.
+  def is_interface_up(self, interface_name):
+    """Explicitly check whether an interface is up.
 
     Only used on startup, and only if ifplugd file is missing.
 
+    Args:
+      interface_name:  The name of the interface to check.
+
     Returns:
-      Whether the ethernet link is up.
+      Whether the interface is up.
     """
     try:
       lines = subprocess.check_output(self.IP_LINK).splitlines()
@@ -273,7 +290,7 @@
       raise EnvironmentError('Failed to call "ip link": %r', e.message)
 
     for line in lines:
-      if 'eth0' in line and 'LOWER_UP' in line:
+      if interface_name in line and 'LOWER_UP' in line:
         return True
 
     return False
diff --git a/conman/connection_manager_test.py b/conman/connection_manager_test.py
index 4ebd48b..c22fbeb 100755
--- a/conman/connection_manager_test.py
+++ b/conman/connection_manager_test.py
@@ -193,13 +193,39 @@
   WIFI_SETCLIENT = ['echo', 'setclient']
   IFUP = ['echo', 'ifup']
   IFPLUGD_ACTION = ['echo', 'ifplugd.action']
-  # This simulates the output of 'ip link' when eth0 is up.
-  IP_LINK = ['echo', 'eth0 LOWER_UP']
 
   def __init__(self, *args, **kwargs):
+    self.interfaces_already_up = kwargs.pop('__test_interfaces_already_up',
+                                            ['eth0'])
+
+    wifi_interfaces_already_up = [ifc for ifc in self.interfaces_already_up
+                                  if ifc.startswith('wcli')]
+    for wifi in wifi_interfaces_already_up:
+      # wcli1 is always 5 GHz.  wcli0 always *includes* 2.4.
+      band = '5' if wifi == 'wcli1' else '2.4'
+      # This will happen in the super function, but in order for
+      # write_wlan_config to work we have to do it now.  This has to happen
+      # before the super function so that the files exist before the inotify
+      # registration.
+      self._config_dir = kwargs['config_dir']
+      self.write_wlan_config(band, 'my ssid', 'passphrase')
+
+      # Also create the wpa_supplicant socket to which to attach.
+      open(os.path.join(kwargs['wpa_control_interface'], wifi), 'w')
+
     super(ConnectionManager, self).__init__(*args, **kwargs)
+
+    for wifi in wifi_interfaces_already_up:
+      # pylint: disable=protected-access
+      self.interface_by_name(wifi)._initially_connected = True
+
     self.scan_has_results = False
 
+  @property
+  def IP_LINK(self):
+    return ['echo'] + ['%s LOWER_UP' % ifc
+                       for ifc in self.interfaces_already_up]
+
   def _update_access_point(self, wlan_configuration):
     client_was_up = wlan_configuration.client_up
     super(ConnectionManager, self)._update_access_point(wlan_configuration)
@@ -356,7 +382,7 @@
       self.run_once()
 
 
-def connection_manager_test(radio_config):
+def connection_manager_test(radio_config, **cm_kwargs):
   """Returns a decorator that does ConnectionManager test boilerplate."""
   def inner(f):
     """The actual decorator."""
@@ -388,7 +414,8 @@
                               wpa_control_interface=wpa_control_interface,
                               run_duration_s=run_duration_s,
                               interface_update_period=interface_update_period,
-                              wifi_scan_period_s=wifi_scan_period_s)
+                              wifi_scan_period_s=wifi_scan_period_s,
+                              **cm_kwargs)
 
         c.test_interface_update_period = interface_update_period
         c.test_wifi_scan_period = wifi_scan_period
@@ -720,6 +747,7 @@
   wvtest.WVPASS(c.wifi_for_band('5').current_route())
 
 
+
 @wvtest.wvtest
 @connection_manager_test(WIFI_SHOW_OUTPUT_ONE_RADIO_NO_5GHZ)
 def connection_manager_test_one_radio_no_5ghz(c):
@@ -757,5 +785,18 @@
   wvtest.WVPASS(c.wifi_for_band('2.4').current_route())
 
 
+@wvtest.wvtest
+@connection_manager_test(WIFI_SHOW_OUTPUT_ONE_RADIO,
+                         __test_interfaces_already_up=['eth0', 'wcli0'])
+def connection_manager_test_wifi_already_up(c):
+  """Test ConnectionManager when wifi is already up.
+
+  Args:
+    c:  The ConnectionManager set up by @connection_manager_test.
+  """
+  wvtest.WVPASS(c._connected_to_wlan(c.wifi_for_band('2.4')))
+  wvtest.WVPASS(c.wifi_for_band('2.4').current_route)
+
+
 if __name__ == '__main__':
   wvtest.wvtest_main()
diff --git a/conman/interface.py b/conman/interface.py
index 0a26959..9b9c653 100755
--- a/conman/interface.py
+++ b/conman/interface.py
@@ -306,6 +306,7 @@
     self.bands = kwargs.pop('bands', [])
     super(Wifi, self).__init__(*args, **kwargs)
     self._wpa_control = None
+    self.initial_ssid = None
 
   @property
   def wpa_supplicant(self):
@@ -331,8 +332,14 @@
         logging.error('Error attaching to wpa_supplicant: %s', e)
         return
 
-      self.wpa_supplicant = ('wpa_state=COMPLETED' in
-                             self._wpa_control.request('STATUS'))
+      for line in self._wpa_control.request('STATUS').splitlines():
+        if '=' not in line:
+          continue
+        key, value = line.split('=', 1)
+        if key == 'wpa_state':
+          self.wpa_supplicant = value == 'COMPLETED'
+        elif key == 'ssid' and not self._initialized:
+          self.initial_ssid = value
 
   def get_wpa_control(self, socket):
     return wpactrl.WPACtrl(socket)
@@ -367,3 +374,10 @@
             break
 
         self.update_routes()
+
+  def initialize(self):
+    """Unset self.initial_ssid, which is only relevant during initialization."""
+
+    self.initial_ssid = None
+    super(Wifi, self).initialize()
+
diff --git a/conman/interface_test.py b/conman/interface_test.py
index 6368a83..1e86125 100755
--- a/conman/interface_test.py
+++ b/conman/interface_test.py
@@ -89,7 +89,8 @@
 
   def request(self, request_type):
     if request_type == 'STATUS':
-      return 'foo\nwpa_state=COMPLETED\nbar' if self.connected else 'foo'
+      return ('foo\nwpa_state=COMPLETED\nssid=my ssid\nbar' if self.connected
+              else 'foo')
     else:
       raise ValueError('Invalid request_type %s' % request_type)
 
@@ -219,11 +220,15 @@
     w.detach_wpa_control()
     # pylint: disable=protected-access
     w._initially_connected = True
+    w._initialized = False
     w.attach_wpa_control(wpa_path)
     wpa_control = w._wpa_control
 
     # wpa_supplicant was already connected when we attached.
     wvtest.WVPASS(w.wpa_supplicant)
+    wvtest.WVPASSEQ(w.initial_ssid, 'my ssid')
+    w.initialize()
+    wvtest.WVPASSEQ(w.initial_ssid, None)
 
     # The wpa_supplicant process disconnects and terminates.
     wpa_control.add_event(Wifi.DISCONNECTED_EVENT)