Home | History | Annotate | Download | only in network
      1 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import logging
      6 import math
      7 import os
      8 import re
      9 import time
     10 
     11 from contextlib import contextmanager
     12 from collections import namedtuple
     13 
     14 from autotest_lib.client.bin import utils
     15 from autotest_lib.client.common_lib import error
     16 from autotest_lib.client.common_lib.cros.network import interface
     17 from autotest_lib.client.common_lib.cros.network import iw_runner
     18 from autotest_lib.client.common_lib.cros.network import ping_runner
     19 from autotest_lib.client.cros import constants
     20 from autotest_lib.server import adb_utils
     21 from autotest_lib.server import autotest
     22 from autotest_lib.server import constants as server_constants
     23 from autotest_lib.server import site_linux_system
     24 from autotest_lib.server.cros.network import wpa_cli_proxy
     25 from autotest_lib.server.hosts import adb_host
     26 from autotest_lib.server.hosts import cast_os_host
     27 
     28 # Wake-on-WiFi feature strings
     29 WAKE_ON_WIFI_NONE = 'none'
     30 WAKE_ON_WIFI_PACKET = 'packet'
     31 WAKE_ON_WIFI_DARKCONNECT = 'darkconnect'
     32 WAKE_ON_WIFI_PACKET_DARKCONNECT = 'packet_and_darkconnect'
     33 WAKE_ON_WIFI_NOT_SUPPORTED = 'not_supported'
     34 
     35 # Wake-on-WiFi test timing constants
     36 SUSPEND_WAIT_TIME_SECONDS = 10
     37 RECEIVE_PACKET_WAIT_TIME_SECONDS = 10
     38 DARK_RESUME_WAIT_TIME_SECONDS = 25
     39 WAKE_TO_SCAN_PERIOD_SECONDS = 30
     40 NET_DETECT_SCAN_WAIT_TIME_SECONDS = 15
     41 WAIT_UP_TIMEOUT_SECONDS = 10
     42 DISCONNECT_WAIT_TIME_SECONDS = 10
     43 INTERFACE_DOWN_WAIT_TIME_SECONDS = 10
     44 
     45 ConnectTime = namedtuple('ConnectTime', 'state, time')
     46 
     47 XMLRPC_BRINGUP_TIMEOUT_SECONDS = 60
     48 SHILL_XMLRPC_LOG_PATH = '/var/log/shill_xmlrpc_server.log'
     49 SHILL_BRILLO_XMLRPC_LOG_PATH = '/data/shill_xmlrpc_server.log'
     50 ANDROID_XMLRPC_SERVER_AUTOTEST_PATH = (
     51         '../../../client/cros/networking/android_xmlrpc_server.py')
     52 # Log dirs/filenames are suffixed with serial number of the DUT
     53 ANDROID_XMLRPC_DEBUG_DIR_FMT = '/var/log/acts-%s'
     54 ANDROID_XMLRPC_LOG_FILE_FMT = '/var/log/android_xmlrpc_server-%s.log'
     55 # Local debug dir name is suffixed by the test name
     56 ANDROID_LOCAL_DEBUG_DIR_FMT = 'android_debug_%s'
     57 
     58 
     59 def _is_android_host(host):
     60     return host.get_os_type() == adb_host.OS_TYPE_ANDROID
     61 
     62 
     63 def _is_brillo_host(host):
     64     return host.get_os_type() == adb_host.OS_TYPE_BRILLO
     65 
     66 
     67 def _is_eureka_host(host):
     68     return host.get_os_type() == cast_os_host.OS_TYPE_CAST_OS
     69 
     70 
     71 def install_android_xmlrpc_server(host, server_port):
     72     """Install Android XMLRPC server script on |host|.
     73 
     74     @param host: host object representing a remote device.
     75     @param server_port string of port number to start server on.
     76 
     77     """
     78     current_dir = os.path.dirname(os.path.realpath(__file__))
     79     xmlrpc_server_script = os.path.join(
     80             current_dir, ANDROID_XMLRPC_SERVER_AUTOTEST_PATH)
     81     script_instance = constants.ANDROID_XMLRPC_SERVER_FMT % server_port
     82     target_file = (constants.ANDROID_XMLRPC_SERVER_TARGET_DIR + '/'
     83                    + script_instance)
     84     host.send_file(xmlrpc_server_script, target_file)
     85 
     86 
     87 def get_xmlrpc_proxy(host):
     88     """Get a shill XMLRPC proxy for |host|.
     89 
     90     The returned object has no particular type.  Instead, when you call
     91     a method on the object, it marshalls the objects passed as arguments
     92     and uses them to make RPCs on the remote server.  Thus, you should
     93     read shill_xmlrpc_server.py to find out what methods are supported.
     94 
     95     @param host: host object representing a remote device.
     96     @return proxy object for remote XMLRPC server.
     97 
     98     """
     99     # Make sure the client library is on the device so that the proxy
    100     # code is there when we try to call it.
    101     if host.is_client_install_supported:
    102         client_at = autotest.Autotest(host)
    103         client_at.install()
    104     # This is the default port for shill xmlrpc server.
    105     server_port = constants.SHILL_XMLRPC_SERVER_PORT
    106 
    107     if _is_brillo_host(host):
    108         xmlrpc_server_command = constants.SHILL_BRILLO_XMLRPC_SERVER_COMMAND
    109         log_path = SHILL_BRILLO_XMLRPC_LOG_PATH
    110         command_name = constants.SHILL_BRILLO_XMLRPC_SERVER_CLEANUP_PATTERN
    111         rpc_server_host = host
    112     elif _is_android_host(host):
    113         if not host.adb_serial:
    114             raise error.TestFail('No serial number detected')
    115         debug_dir = ANDROID_XMLRPC_DEBUG_DIR_FMT % host.adb_serial
    116         log_path = ANDROID_XMLRPC_LOG_FILE_FMT % host.adb_serial
    117         teststation = host.teststation
    118         hostname = teststation.hostname.split('.')[0]
    119         instance = re.search("(\d+)(?!.*\d)", hostname)
    120         val = int(instance.group())
    121         server_port = constants.SHILL_XMLRPC_SERVER_PORT + val
    122         xmlrpc_server_command = constants.ANDROID_XMLRPC_SERVER_COMMAND_FMT % (
    123                 constants.ANDROID_XMLRPC_SERVER_TARGET_DIR, server_port)
    124         command_name = (constants.ANDROID_XMLRPC_SERVER_CLEANUP_PATTERN %
    125                        str(server_port))
    126         xmlrpc_server_command = (
    127                 '%s -s %s -l %s -t %s -p %d' % (
    128                 xmlrpc_server_command, host.adb_serial, debug_dir,
    129                 hostname, server_port))
    130         install_android_xmlrpc_server(teststation, str(server_port))
    131         # For android, start the XML RPC server on the accompanying host.
    132         rpc_server_host = teststation
    133     else:
    134         xmlrpc_server_command = constants.SHILL_XMLRPC_SERVER_COMMAND
    135         log_path = SHILL_XMLRPC_LOG_PATH
    136         command_name = constants.SHILL_XMLRPC_SERVER_CLEANUP_PATTERN
    137         rpc_server_host = host
    138 
    139     # Start up the XMLRPC proxy on the client
    140     proxy = rpc_server_host.rpc_server_tracker.xmlrpc_connect(
    141             xmlrpc_server_command,
    142             server_port,
    143             command_name=command_name,
    144             ready_test_name=constants.SHILL_XMLRPC_SERVER_READY_METHOD,
    145             timeout_seconds=XMLRPC_BRINGUP_TIMEOUT_SECONDS,
    146             logfile=log_path
    147       )
    148     return proxy
    149 
    150 
    151 def _is_conductive(host):
    152     """Determine if the host is conductive based on AFE labels.
    153 
    154     @param host: A Host object.
    155     """
    156     if utils.host_could_be_in_afe(host.hostname):
    157         info = host.host_info_store.get()
    158         conductive = info.get_label_value('conductive')
    159         return conductive.lower() == 'true'
    160     return False
    161 
    162 
    163 class WiFiClient(site_linux_system.LinuxSystem):
    164     """WiFiClient is a thin layer of logic over a remote DUT in wifitests."""
    165 
    166     DEFAULT_PING_COUNT = 10
    167     COMMAND_PING = 'ping'
    168 
    169     MAX_SERVICE_GONE_TIMEOUT_SECONDS = 60
    170 
    171     # List of interface names we won't consider for use as "the" WiFi interface
    172     # on Android or CastOS hosts.
    173     WIFI_IF_BLACKLIST = ['p2p0', 'wfd0']
    174 
    175     UNKNOWN_BOARD_TYPE = 'unknown'
    176 
    177     # DBus device properties. Wireless interfaces should support these.
    178     ROAM_THRESHOLD = 'RoamThreshold'
    179     WAKE_ON_WIFI_FEATURES = 'WakeOnWiFiFeaturesEnabled'
    180     NET_DETECT_SCAN_PERIOD = 'NetDetectScanPeriodSeconds'
    181     WAKE_TO_SCAN_PERIOD = 'WakeToScanPeriodSeconds'
    182     FORCE_WAKE_TO_SCAN_TIMER = 'ForceWakeToScanTimer'
    183     MAC_ADDRESS_RANDOMIZATION = 'MACAddressRandomization'
    184 
    185     CONNECTED_STATES = ['ready', 'portal', 'online']
    186 
    187 
    188     @property
    189     def machine_id(self):
    190         """@return string unique to a particular board/cpu configuration."""
    191         if self._machine_id:
    192             return self._machine_id
    193 
    194         uname_result = self.host.run('uname -m', ignore_status=True)
    195         kernel_arch = ''
    196         if not uname_result.exit_status and uname_result.stdout.find(' ') < 0:
    197             kernel_arch = uname_result.stdout.strip()
    198         cpu_info = self.host.run('cat /proc/cpuinfo').stdout.splitlines()
    199         cpu_count = len(filter(lambda x: x.lower().startswith('bogomips'),
    200                                cpu_info))
    201         cpu_count_str = ''
    202         if cpu_count:
    203             cpu_count_str = 'x%d' % cpu_count
    204         ghz_value = ''
    205         ghz_pattern = re.compile('([0-9.]+GHz)')
    206         for line in cpu_info:
    207             match = ghz_pattern.search(line)
    208             if match is not None:
    209                 ghz_value = '_' + match.group(1)
    210                 break
    211 
    212         return '%s_%s%s%s' % (self.board, kernel_arch, ghz_value, cpu_count_str)
    213 
    214 
    215     @property
    216     def powersave_on(self):
    217         """@return bool True iff WiFi powersave mode is enabled."""
    218         result = self.host.run("iw dev %s get power_save" % self.wifi_if)
    219         output = result.stdout.rstrip()       # NB: chop \n
    220         # Output should be either "Power save: on" or "Power save: off".
    221         find_re = re.compile('([^:]+):\s+(\w+)')
    222         find_results = find_re.match(output)
    223         if not find_results:
    224             raise error.TestFail('Failed to find power_save parameter '
    225                                  'in iw results.')
    226 
    227         return find_results.group(2) == 'on'
    228 
    229 
    230     @property
    231     def shill(self):
    232         """@return shill RPCProxy object."""
    233         return self._shill_proxy
    234 
    235 
    236     @property
    237     def client(self):
    238         """Deprecated accessor for the client host.
    239 
    240         The term client is used very loosely in old autotests and this
    241         accessor should not be used in new code.  Use host() instead.
    242 
    243         @return host object representing a remote DUT.
    244 
    245         """
    246         return self.host
    247 
    248 
    249     @property
    250     def command_ip(self):
    251         """@return string path to ip command."""
    252         return self._command_ip
    253 
    254 
    255     @property
    256     def command_iptables(self):
    257         """@return string path to iptables command."""
    258         return self._command_iptables
    259 
    260 
    261     @property
    262     def command_ping6(self):
    263         """@return string path to ping6 command."""
    264         return self._command_ping6
    265 
    266 
    267     @property
    268     def command_wpa_cli(self):
    269         """@return string path to wpa_cli command."""
    270         return self._command_wpa_cli
    271 
    272 
    273     @property
    274     def conductive(self):
    275         """@return True if the rig is conductive; False otherwise."""
    276         if self._conductive is None:
    277             self._conductive = _is_conductive(self.host)
    278         return self._conductive
    279 
    280 
    281     @conductive.setter
    282     def conductive(self, value):
    283         """Set the conductive member to True or False.
    284 
    285         @param value: boolean value to set the conductive member to.
    286         """
    287         self._conductive = value
    288 
    289 
    290     @property
    291     def wifi_if(self):
    292         """@return string wifi device on machine (e.g. mlan0)."""
    293         return self._wifi_if
    294 
    295 
    296     @property
    297     def wifi_mac(self):
    298         """@return string MAC address of self.wifi_if."""
    299         return self._interface.mac_address
    300 
    301 
    302     @property
    303     def wifi_ip(self):
    304         """@return string IPv4 address of self.wifi_if."""
    305         return self._interface.ipv4_address
    306 
    307 
    308     @property
    309     def wifi_ip_subnet(self):
    310         """@return string IPv4 subnet prefix of self.wifi_if."""
    311         return self._interface.ipv4_subnet
    312 
    313 
    314     @property
    315     def wifi_signal_level(self):
    316         """Returns the signal level of this DUT's WiFi interface.
    317 
    318         @return int signal level of connected WiFi interface or None (e.g. -67).
    319 
    320         """
    321         return self._interface.signal_level
    322 
    323 
    324     @staticmethod
    325     def assert_bsses_include_ssids(found_bsses, expected_ssids):
    326         """Verifies that |found_bsses| includes |expected_ssids|.
    327 
    328         @param found_bsses list of IwBss objects.
    329         @param expected_ssids list of string SSIDs.
    330         @raise error.TestFail if any element of |expected_ssids| is not found.
    331 
    332         """
    333         for ssid in expected_ssids:
    334             if not ssid:
    335                 continue
    336 
    337             for bss in found_bsses:
    338                 if bss.ssid == ssid:
    339                     break
    340             else:
    341                 raise error.TestFail('SSID %s is not in scan results: %r' %
    342                                      (ssid, found_bsses))
    343 
    344 
    345     def wifi_noise_level(self, frequency_mhz):
    346         """Returns the noise level of this DUT's WiFi interface.
    347 
    348         @param frequency_mhz: frequency at which the noise level should be
    349                measured and reported.
    350         @return int signal level of connected WiFi interface in dBm (e.g. -67)
    351                 or None if the value is unavailable.
    352 
    353         """
    354         return self._interface.noise_level(frequency_mhz)
    355 
    356 
    357     def __init__(self, client_host, result_dir, use_wpa_cli):
    358         """
    359         Construct a WiFiClient.
    360 
    361         @param client_host host object representing a remote host.
    362         @param result_dir string directory to store test logs/packet caps.
    363         @param use_wpa_cli bool True if we want to use |wpa_cli| commands for
    364                Android testing.
    365 
    366         """
    367         super(WiFiClient, self).__init__(client_host, 'client',
    368                                          inherit_interfaces=True)
    369         self._command_ip = 'ip'
    370         self._command_iptables = 'iptables'
    371         self._command_ping6 = 'ping6'
    372         self._command_wpa_cli = 'wpa_cli'
    373         self._machine_id = None
    374         self._result_dir = result_dir
    375         self._conductive = None
    376 
    377         if ((_is_android_host(self.host) or _is_eureka_host(self.host)) and
    378             use_wpa_cli):
    379             # Look up the WiFi device (and its MAC) on the client.
    380             devs = self.iw_runner.list_interfaces(desired_if_type='managed')
    381             devs = [dev for dev in devs
    382                     if dev.if_name not in self.WIFI_IF_BLACKLIST]
    383             if not devs:
    384                 raise error.TestFail('No wlan devices found on %s.' %
    385                                      self.host.hostname)
    386 
    387             if len(devs) > 1:
    388                 logging.warning('Warning, found multiple WiFi devices on '
    389                                 '%s: %r', self.host.hostname, devs)
    390             self._wifi_if = devs[0].if_name
    391             self._shill_proxy = wpa_cli_proxy.WpaCliProxy(
    392                     self.host, self._wifi_if)
    393             self._wpa_cli_proxy = self._shill_proxy
    394         else:
    395             if _is_android_host(self.host):
    396                 adb_utils.install_apk_from_build(
    397                         self.host,
    398                         server_constants.SL4A_APK,
    399                         server_constants.SL4A_ARTIFACT,
    400                         package_name=server_constants.SL4A_PACKAGE)
    401 
    402             self._shill_proxy = get_xmlrpc_proxy(self.host)
    403             interfaces = self._shill_proxy.list_controlled_wifi_interfaces()
    404             if not interfaces:
    405                 logging.debug('No interfaces managed by shill. Rebooting host')
    406                 self.host.reboot()
    407                 raise error.TestError('No interfaces managed by shill on %s' %
    408                                       self.host.hostname)
    409             self._wifi_if = interfaces[0]
    410             self._wpa_cli_proxy = wpa_cli_proxy.WpaCliProxy(
    411                     self.host, self._wifi_if)
    412             self._raise_logging_level()
    413         self._interface = interface.Interface(self._wifi_if, host=self.host)
    414         logging.debug('WiFi interface is: %r',
    415                       self._interface.device_description)
    416         self._firewall_rules = []
    417         # All tests that use this object assume the interface starts enabled.
    418         self.set_device_enabled(self._wifi_if, True)
    419         # Turn off powersave mode by default.
    420         self.powersave_switch(False)
    421         # Invoke the |capabilities| property defined in the parent |Linuxsystem|
    422         # to workaround the lazy loading of the capabilities cache and supported
    423         # frequency list. This is needed for tests that may need access to these
    424         # when the DUT is unreachable (for ex: suspended).
    425         self.capabilities
    426 
    427 
    428     def _assert_method_supported(self, method_name):
    429         """Raise a TestNAError if the XMLRPC proxy has no method |method_name|.
    430 
    431         @param method_name: string name of method that should exist on the
    432                 XMLRPC proxy.
    433 
    434         """
    435         if not self._supports_method(method_name):
    436             raise error.TestNAError('%s() is not supported' % method_name)
    437 
    438 
    439     def _raise_logging_level(self):
    440         """Raises logging levels for WiFi on DUT."""
    441         self.host.run('wpa_debug excessive', ignore_status=True)
    442         self.host.run('ff_debug --level -5', ignore_status=True)
    443         self.host.run('ff_debug +wifi', ignore_status=True)
    444 
    445 
    446     def is_vht_supported(self):
    447         """Returns True if VHT supported; False otherwise"""
    448         return self.CAPABILITY_VHT in self.capabilities
    449 
    450 
    451     def is_5ghz_supported(self):
    452         """Returns True if 5Ghz bands are supported; False otherwise."""
    453         return self.CAPABILITY_5GHZ in self.capabilities
    454 
    455 
    456     def is_ibss_supported(self):
    457         """Returns True if IBSS mode is supported; False otherwise."""
    458         return self.CAPABILITY_IBSS in self.capabilities
    459 
    460 
    461     def is_frequency_supported(self, frequency):
    462         """Returns True if the given frequency is supported; False otherwise.
    463 
    464         @param frequency: int Wifi frequency to check if it is supported by
    465                           DUT.
    466         """
    467         return frequency in self.phys_for_frequency
    468 
    469 
    470     def _supports_method(self, method_name):
    471         """Checks if |method_name| is supported on the remote XMLRPC proxy.
    472 
    473         autotest will, for their own reasons, install python files in the
    474         autotest client package that correspond the version of the build
    475         rather than the version running on the autotest drone.  This
    476         creates situations where we call methods on the client XMLRPC proxy
    477         that don't exist in that version of the code.  This detects those
    478         situations so that we can degrade more or less gracefully.
    479 
    480         @param method_name: string name of method that should exist on the
    481                 XMLRPC proxy.
    482         @return True if method is available, False otherwise.
    483 
    484         """
    485         # Make no assertions about ADBHost support.  We don't use an XMLRPC
    486         # proxy with those hosts anyway.
    487         supported = (_is_android_host(self.host) or _is_eureka_host(self.host)
    488                      or method_name in self._shill_proxy.system.listMethods())
    489         if not supported:
    490             logging.warning('%s() is not supported on older images',
    491                             method_name)
    492         return supported
    493 
    494 
    495     def close(self):
    496         """Tear down state associated with the client."""
    497         self.stop_capture()
    498         self.powersave_switch(False)
    499         self.shill.clean_profiles()
    500         super(WiFiClient, self).close()
    501 
    502 
    503     def firewall_open(self, proto, src):
    504         """Opens up firewall to run netperf tests.
    505 
    506         By default, we have a firewall rule for NFQUEUE (see crbug.com/220736).
    507         In order to run netperf test, we need to add a new firewall rule BEFORE
    508         this NFQUEUE rule in the INPUT chain.
    509 
    510         @param proto a string, test traffic protocol, e.g. udp, tcp.
    511         @param src a string, subnet/mask.
    512 
    513         @return a string firewall rule added.
    514 
    515         """
    516         rule = 'INPUT -s %s/32 -p %s -m %s -j ACCEPT' % (src, proto, proto)
    517         self.host.run('%s -I %s' % (self._command_iptables, rule))
    518         self._firewall_rules.append(rule)
    519         return rule
    520 
    521 
    522     def firewall_cleanup(self):
    523         """Cleans up firewall rules."""
    524         for rule in self._firewall_rules:
    525             self.host.run('%s -D %s' % (self._command_iptables, rule))
    526         self._firewall_rules = []
    527 
    528 
    529     def sync_host_times(self):
    530         """Set time on our DUT to match local time."""
    531         epoch_seconds = time.time()
    532         self.shill.sync_time_to(epoch_seconds)
    533 
    534 
    535     def collect_debug_info(self, local_save_dir_prefix):
    536         """Collect any debug information needed from the DUT
    537 
    538         This invokes the |collect_debug_info| RPC method to trigger
    539         bugreport/logcat collection and then transfers the logs to the
    540         server.
    541         Only implemented for android DUT's for now.
    542 
    543         @param local_save_dir_prefix Used as a prefix for local save directory.
    544         """
    545         if _is_android_host(self.host):
    546             # First capture the bugreport to the test station
    547             self.shill.collect_debug_info(local_save_dir_prefix)
    548             # Now copy the file over from test station to the server.
    549             debug_dir = ANDROID_XMLRPC_DEBUG_DIR_FMT % self.host.adb_serial
    550             log_file = ANDROID_XMLRPC_LOG_FILE_FMT % self.host.adb_serial
    551             local_save_dir = ANDROID_LOCAL_DEBUG_DIR_FMT % local_save_dir_prefix
    552             result_dir = os.path.join(self._result_dir, local_save_dir);
    553             try:
    554                 self.host.teststation.get_file(debug_dir, result_dir)
    555                 self.host.teststation.get_file(log_file, result_dir)
    556             except Exception as e:
    557                 logging.error('Failed to fetch debug info from host: %s', e)
    558 
    559 
    560     def check_iw_link_value(self, iw_link_key, desired_value):
    561         """Assert that the current wireless link property is |desired_value|.
    562 
    563         @param iw_link_key string one of IW_LINK_KEY_* defined in iw_runner.
    564         @param desired_value string desired value of iw link property.
    565 
    566         """
    567         actual_value = self.get_iw_link_value(iw_link_key)
    568         desired_value = str(desired_value)
    569         if actual_value != desired_value:
    570             raise error.TestFail('Wanted iw link property %s value %s, but '
    571                                  'got %s instead.' % (iw_link_key,
    572                                                       desired_value,
    573                                                       actual_value))
    574 
    575 
    576     def get_iw_link_value(self, iw_link_key):
    577         """Get the current value of a link property for this WiFi interface.
    578 
    579         @param iw_link_key string one of IW_LINK_KEY_* defined in iw_runner.
    580 
    581         """
    582         return self.iw_runner.get_link_value(self.wifi_if, iw_link_key)
    583 
    584 
    585     def powersave_switch(self, turn_on):
    586         """Toggle powersave mode for the DUT.
    587 
    588         @param turn_on bool True iff powersave mode should be turned on.
    589 
    590         """
    591         mode = 'off'
    592         if turn_on:
    593             mode = 'on'
    594         # Turn ON interface and set power_save option.
    595         self.host.run('ifconfig %s up' % self.wifi_if)
    596         self.host.run('iw dev %s set power_save %s' % (self.wifi_if, mode))
    597 
    598 
    599     def timed_scan(self, frequencies, ssids, scan_timeout_seconds=10,
    600                    retry_timeout_seconds=10):
    601         """Request timed scan to discover given SSIDs.
    602 
    603         This method will retry for a default of |retry_timeout_seconds| until it
    604         is able to successfully kick off a scan.  Sometimes, the device on the
    605         DUT claims to be busy and rejects our requests. It will raise error
    606         if the scan did not complete within |scan_timeout_seconds| or it was
    607         not able to discover the given SSIDs.
    608 
    609         @param frequencies list of int WiFi frequencies to scan for.
    610         @param ssids list of string ssids to probe request for.
    611         @param scan_timeout_seconds: float number of seconds the scan
    612                 operation not to exceed.
    613         @param retry_timeout_seconds: float number of seconds to retry scanning
    614                 if the interface is busy.  This does not retry if certain
    615                 SSIDs are missing from the results.
    616         @return time in seconds took to complete scan request.
    617 
    618         """
    619         start_time = time.time()
    620         while time.time() - start_time < retry_timeout_seconds:
    621             scan_result = self.iw_runner.timed_scan(
    622                     self.wifi_if, frequencies=frequencies, ssids=ssids)
    623 
    624             if scan_result is not None:
    625                 break
    626 
    627             time.sleep(0.5)
    628         else:
    629             raise error.TestFail('Unable to trigger scan on client.')
    630 
    631         # Verify scan operation completed within given timeout
    632         if scan_result.time > scan_timeout_seconds:
    633             raise error.TestFail('Scan time %.2fs exceeds the scan timeout' %
    634                                  (scan_result.time))
    635 
    636         # Verify all ssids are discovered
    637         self.assert_bsses_include_ssids(scan_result.bss_list, ssids)
    638 
    639         logging.info('Wifi scan completed in %.2f seconds', scan_result.time)
    640         return scan_result.time
    641 
    642 
    643     def scan(self, frequencies, ssids, timeout_seconds=10, require_match=True):
    644         """Request a scan and (optionally) check that requested SSIDs appear in
    645         the results.
    646 
    647         This method will retry for a default of |timeout_seconds| until it is
    648         able to successfully kick off a scan.  Sometimes, the device on the DUT
    649         claims to be busy and rejects our requests.
    650 
    651         If |ssids| is non-empty, we will speficially probe for those SSIDs.
    652 
    653         If |require_match| is True, we will verify that every element
    654         of |ssids| was found in scan results.
    655 
    656         @param frequencies list of int WiFi frequencies to scan for.
    657         @param ssids list of string ssids to probe request for.
    658         @param timeout_seconds: float number of seconds to retry scanning
    659                 if the interface is busy.  This does not retry if certain
    660                 SSIDs are missing from the results.
    661         @param require_match: bool True if we must find |ssids|.
    662 
    663         """
    664         start_time = time.time()
    665         while time.time() - start_time < timeout_seconds:
    666             bss_list = self.iw_runner.scan(
    667                     self.wifi_if, frequencies=frequencies, ssids=ssids)
    668             if bss_list is not None:
    669                 break
    670 
    671             time.sleep(0.5)
    672         else:
    673             raise error.TestFail('Unable to trigger scan on client.')
    674 
    675         if require_match:
    676             self.assert_bsses_include_ssids(bss_list, ssids)
    677 
    678 
    679     def wait_for_bsses(self, ssid, num_bss_expected, timeout_seconds=15):
    680       """Wait for all BSSes associated with given SSID to be discovered in the
    681       scan.
    682 
    683       @param ssid string name of network being queried
    684       @param num_bss_expected int number of BSSes expected
    685       @param timeout_seconds int seconds to wait for BSSes to be discovered
    686 
    687       """
    688       start_time = time.time()
    689       while time.time() - start_time < timeout_seconds:
    690           bss_list = self.iw_runner.scan(
    691                   self.wifi_if, frequencies=[], ssids=[ssid])
    692 
    693           # Determine number of BSSes found in the scan result.
    694           num_bss_found = 0
    695           if bss_list is not None:
    696               for bss in bss_list:
    697                   if bss.ssid == ssid:
    698                       num_bss_found += 1
    699 
    700           # Verify all BSSes are found.
    701           if num_bss_found == num_bss_expected:
    702               break
    703 
    704           time.sleep(0.5)
    705       else:
    706           raise error.TestFail('Failed to discover all BSSes.')
    707 
    708 
    709     def wait_for_service_states(self, ssid, states, timeout_seconds):
    710         """Waits for a WiFi service to achieve one of |states|.
    711 
    712         @param ssid string name of network being queried
    713         @param states tuple list of states for which the caller is waiting
    714         @param timeout_seconds int seconds to wait for a state in |states|
    715 
    716         """
    717         logging.info('Waiting for %s to reach one of %r...', ssid, states)
    718         success, state, time  = self._shill_proxy.wait_for_service_states(
    719                 ssid, states, timeout_seconds)
    720         logging.info('...ended up in state \'%s\' (%s) after %f seconds.',
    721                      state, 'success' if success else 'failure', time)
    722         return success, state, time
    723 
    724 
    725     def do_suspend(self, seconds):
    726         """Puts the DUT in suspend power state for |seconds| seconds.
    727 
    728         @param seconds: The number of seconds to suspend the device.
    729 
    730         """
    731         logging.info('Suspending DUT for %d seconds...', seconds)
    732         self._shill_proxy.do_suspend(seconds)
    733         logging.info('...done suspending')
    734 
    735 
    736     def do_suspend_bg(self, seconds):
    737         """Suspend DUT using the power manager - non-blocking.
    738 
    739         @param seconds: The number of seconds to suspend the device.
    740 
    741         """
    742         logging.info('Suspending DUT (in background) for %d seconds...',
    743                      seconds)
    744         self._shill_proxy.do_suspend_bg(seconds)
    745 
    746 
    747     def clear_supplicant_blacklist(self):
    748         """Clear's the AP blacklist on the DUT.
    749 
    750         @return stdout and stderror returns passed from wpa_cli command.
    751 
    752         """
    753         result = self._wpa_cli_proxy.run_wpa_cli_cmd('blacklist clear',
    754                                                      check_result=False);
    755         logging.info('wpa_cli blacklist clear: out:%r err:%r', result.stdout,
    756                      result.stderr)
    757         return result.stdout, result.stderr
    758 
    759 
    760     def get_active_wifi_SSIDs(self):
    761         """Get a list of visible SSID's around the DUT
    762 
    763         @return list of string SSIDs
    764 
    765         """
    766         self._assert_method_supported('get_active_wifi_SSIDs')
    767         return self._shill_proxy.get_active_wifi_SSIDs()
    768 
    769 
    770     def roam_threshold(self, value):
    771         """Get a context manager to temporarily change wpa_supplicant's
    772         roaming threshold for the specified interface.
    773 
    774         The correct way to use this method is:
    775 
    776         with client.roam_threshold(40):
    777             ...
    778 
    779         @param value: the desired roam threshold for the test.
    780 
    781         @return a context manager for the threshold.
    782 
    783         """
    784         return TemporaryDeviceDBusProperty(self._shill_proxy,
    785                                            self.wifi_if,
    786                                            self.ROAM_THRESHOLD,
    787                                            value)
    788 
    789 
    790     def set_device_enabled(self, wifi_interface, value,
    791                            fail_on_unsupported=False):
    792         """Enable or disable the WiFi device.
    793 
    794         @param wifi_interface: string name of interface being modified.
    795         @param enabled: boolean; true if this device should be enabled,
    796                 false if this device should be disabled.
    797         @return True if it worked; False, otherwise.
    798 
    799         """
    800         if fail_on_unsupported:
    801             self._assert_method_supported('set_device_enabled')
    802         elif not self._supports_method('set_device_enabled'):
    803             return False
    804         return self._shill_proxy.set_device_enabled(wifi_interface, value)
    805 
    806 
    807     def add_arp_entry(self, ip_address, mac_address):
    808         """Add an ARP entry to the table associated with the WiFi interface.
    809 
    810         @param ip_address: string IP address associated with the new ARP entry.
    811         @param mac_address: string MAC address associated with the new ARP
    812                 entry.
    813 
    814         """
    815         self.host.run('ip neigh add %s lladdr %s dev %s nud perm' %
    816                       (ip_address, mac_address, self.wifi_if))
    817 
    818 
    819     def discover_tdls_link(self, mac_address):
    820         """Send a TDLS Discover to |peer_mac_address|.
    821 
    822         @param mac_address: string MAC address associated with the TDLS peer.
    823 
    824         @return bool True if operation initiated successfully, False otherwise.
    825 
    826         """
    827         return self._shill_proxy.discover_tdls_link(self.wifi_if, mac_address)
    828 
    829 
    830     def establish_tdls_link(self, mac_address):
    831         """Establish a TDLS link with |mac_address|.
    832 
    833         @param mac_address: string MAC address associated with the TDLS peer.
    834 
    835         @return bool True if operation initiated successfully, False otherwise.
    836 
    837         """
    838         return self._shill_proxy.establish_tdls_link(self.wifi_if, mac_address)
    839 
    840 
    841     def query_tdls_link(self, mac_address):
    842         """Query a TDLS link with |mac_address|.
    843 
    844         @param mac_address: string MAC address associated with the TDLS peer.
    845 
    846         @return string indicating current TDLS connectivity.
    847 
    848         """
    849         return self._shill_proxy.query_tdls_link(self.wifi_if, mac_address)
    850 
    851 
    852     def add_wake_packet_source(self, source_ip):
    853         """Add |source_ip| as a source that can wake us up with packets.
    854 
    855         @param source_ip: IP address from which to wake upon receipt of packets
    856 
    857         @return True if successful, False otherwise.
    858 
    859         """
    860         return self._shill_proxy.add_wake_packet_source(
    861             self.wifi_if, source_ip)
    862 
    863 
    864     def remove_wake_packet_source(self, source_ip):
    865         """Remove |source_ip| as a source that can wake us up with packets.
    866 
    867         @param source_ip: IP address to stop waking on packets from
    868 
    869         @return True if successful, False otherwise.
    870 
    871         """
    872         return self._shill_proxy.remove_wake_packet_source(
    873             self.wifi_if, source_ip)
    874 
    875 
    876     def remove_all_wake_packet_sources(self):
    877         """Remove all IPs as sources that can wake us up with packets.
    878 
    879         @return True if successful, False otherwise.
    880 
    881         """
    882         return self._shill_proxy.remove_all_wake_packet_sources(self.wifi_if)
    883 
    884 
    885     def wake_on_wifi_features(self, features):
    886         """Shill supports programming the NIC to wake on special kinds of
    887         incoming packets, or on changes to the available APs (disconnect,
    888         coming in range of a known SSID). This method allows you to configure
    889         what wake-on-WiFi mechanisms are active. It returns a context manager,
    890         because this is a system-wide setting and we don't want it to persist
    891         across different tests.
    892 
    893         If you enable wake-on-packet, then the IPs registered by
    894         add_wake_packet_source will be able to wake the system from suspend.
    895 
    896         The correct way to use this method is:
    897 
    898         with client.wake_on_wifi_features(WAKE_ON_WIFI_DARKCONNECT):
    899             ...
    900 
    901         @param features: string from the WAKE_ON_WIFI constants above.
    902 
    903         @return a context manager for the features.
    904 
    905         """
    906         return TemporaryDeviceDBusProperty(self._shill_proxy,
    907                                            self.wifi_if,
    908                                            self.WAKE_ON_WIFI_FEATURES,
    909                                            features)
    910 
    911 
    912     def net_detect_scan_period_seconds(self, period):
    913         """Sets the period between net detect scans performed by the NIC to look
    914         for whitelisted SSIDs to |period|. This setting only takes effect if the
    915         NIC is programmed to wake on SSID. Use as with roam_threshold.
    916 
    917         @param period: integer number of seconds between NIC net detect scans
    918 
    919         @return a context manager for the net detect scan period
    920 
    921         """
    922         return TemporaryDeviceDBusProperty(self._shill_proxy,
    923                                            self.wifi_if,
    924                                            self.NET_DETECT_SCAN_PERIOD,
    925                                            period)
    926 
    927 
    928     def wake_to_scan_period_seconds(self, period):
    929         """Sets the period between RTC timer wakeups where the system is woken
    930         from suspend to perform scans. This setting only takes effect if the
    931         NIC is programmed to wake on SSID. Use as with roam_threshold.
    932 
    933         @param period: integer number of seconds between wake to scan RTC timer
    934                 wakes.
    935 
    936         @return a context manager for the net detect scan period
    937 
    938         """
    939         return TemporaryDeviceDBusProperty(self._shill_proxy,
    940                                            self.wifi_if,
    941                                            self.WAKE_TO_SCAN_PERIOD,
    942                                            period)
    943 
    944 
    945     def force_wake_to_scan_timer(self, is_forced):
    946         """Sets the boolean value determining whether or not to force the use of
    947         the wake to scan RTC timer, which wakes the system from suspend
    948         periodically to scan for networks.
    949 
    950         @param is_forced: boolean whether or not to force the use of the wake to
    951                 scan timer
    952 
    953         @return a context manager for the net detect scan period
    954 
    955         """
    956         return TemporaryDeviceDBusProperty(self._shill_proxy,
    957                                            self.wifi_if,
    958                                            self.FORCE_WAKE_TO_SCAN_TIMER,
    959                                            is_forced)
    960 
    961 
    962     def mac_address_randomization(self, enabled):
    963         """Sets the boolean value determining whether or not to enable MAC
    964         address randomization. This instructs the NIC to randomize the last
    965         three octets of the MAC address used in probe requests while
    966         disconnected to make the DUT harder to track.
    967 
    968         @param enabled: boolean whether or not to enable MAC address
    969                 randomization
    970 
    971         @return a context manager for the MAC address randomization property
    972 
    973         """
    974         return TemporaryDeviceDBusProperty(self._shill_proxy,
    975                                            self.wifi_if,
    976                                            self.MAC_ADDRESS_RANDOMIZATION,
    977                                            enabled)
    978 
    979 
    980     def request_roam(self, bssid):
    981         """Request that we roam to the specified BSSID.
    982 
    983         Note that this operation assumes that:
    984 
    985         1) We're connected to an SSID for which |bssid| is a member.
    986         2) There is a BSS with an appropriate ID in our scan results.
    987 
    988         This method does not check for success of either the command or
    989         the roaming operation.
    990 
    991         @param bssid: string MAC address of bss to roam to.
    992 
    993         """
    994         self._wpa_cli_proxy.run_wpa_cli_cmd('roam %s' % bssid,
    995                                             check_result=False);
    996         return True
    997 
    998 
    999     def request_roam_dbus(self, bssid, interface):
   1000         """Request that we roam to the specified BSSID through dbus.
   1001 
   1002         Note that this operation assumes that:
   1003 
   1004         1) We're connected to an SSID for which |bssid| is a member.
   1005         2) There is a BSS with an appropriate ID in our scan results.
   1006 
   1007         @param bssid: string MAC address of bss to roam to.
   1008         @param interface: interface to use
   1009 
   1010         """
   1011         self._assert_method_supported('request_roam_dbus')
   1012         self._shill_proxy.request_roam_dbus(bssid, interface)
   1013 
   1014 
   1015     def wait_for_roam(self, bssid, timeout_seconds=10.0):
   1016         """Wait for a roam to the given |bssid|.
   1017 
   1018         @param bssid: string bssid to expect a roam to
   1019                 (e.g.  '00:11:22:33:44:55').
   1020         @param timeout_seconds: float number of seconds to wait for a roam.
   1021         @return True iff we detect an association to the given |bssid| within
   1022                 |timeout_seconds|.
   1023 
   1024         """
   1025         start_time = time.time()
   1026         success = False
   1027         duration = 0.0
   1028         while time.time() - start_time < timeout_seconds:
   1029             duration = time.time() - start_time
   1030             current_bssid = self.iw_runner.get_current_bssid(self.wifi_if)
   1031             logging.debug('Current BSSID is %s.', current_bssid)
   1032             if current_bssid == bssid:
   1033                 success = True
   1034                 break
   1035             time.sleep(0.5)
   1036 
   1037         logging.debug('%s to %s in %f seconds.',
   1038                       'Roamed ' if success else 'Failed to roam ',
   1039                       bssid,
   1040                       duration)
   1041         return success
   1042 
   1043 
   1044     def wait_for_ssid_vanish(self, ssid):
   1045         """Wait for shill to notice that there are no BSS's for an SSID present.
   1046 
   1047         Raise a test failing exception if this does not come to pass.
   1048 
   1049         @param ssid: string SSID of the network to require be missing.
   1050 
   1051         """
   1052         start_time = time.time()
   1053         while time.time() - start_time < self.MAX_SERVICE_GONE_TIMEOUT_SECONDS:
   1054             visible_ssids = self.get_active_wifi_SSIDs()
   1055             logging.info('Got service list: %r', visible_ssids)
   1056             if ssid not in visible_ssids:
   1057                 return
   1058 
   1059             self.scan(frequencies=[], ssids=[], timeout_seconds=30)
   1060         else:
   1061             raise error.TestFail('shill should mark the BSS as not present')
   1062 
   1063 
   1064     def reassociate(self, timeout_seconds=10):
   1065         """Reassociate to the connected network.
   1066 
   1067         @param timeout_seconds: float number of seconds to wait for operation
   1068                 to complete.
   1069 
   1070         """
   1071         logging.info('Attempt to reassociate')
   1072         with self.iw_runner.get_event_logger() as logger:
   1073             logger.start()
   1074             # Issue reattach command to wpa_supplicant
   1075             self._wpa_cli_proxy.run_wpa_cli_cmd('reattach');
   1076 
   1077             # Wait for the timeout seconds for association to complete
   1078             time.sleep(timeout_seconds)
   1079 
   1080             # Stop iw event logger
   1081             logger.stop()
   1082 
   1083             # Get association time based on the iw event log
   1084             reassociate_time = logger.get_reassociation_time()
   1085             if reassociate_time is None or reassociate_time > timeout_seconds:
   1086                 raise error.TestFail(
   1087                         'Failed to reassociate within given timeout')
   1088             logging.info('Reassociate time: %.2f seconds', reassociate_time)
   1089 
   1090 
   1091     def wait_for_connection(self, ssid, timeout_seconds=30, freq=None,
   1092                             ping_ip=None, desired_subnet=None):
   1093         """Verifies a connection to network ssid, optionally verifying
   1094         frequency, ping connectivity and subnet.
   1095 
   1096         @param ssid string ssid of the network to check.
   1097         @param timeout_seconds int number of seconds to wait for
   1098                 connection on the given frequency.
   1099         @param freq int frequency of network to check.
   1100         @param ping_ip string ip address to ping for verification.
   1101         @param desired_subnet string expected subnet in which client
   1102                 ip address should reside.
   1103 
   1104         @returns a named tuple of (state, time)
   1105         """
   1106         POLLING_INTERVAL_SECONDS = 1.0
   1107         start_time = time.time()
   1108         duration = lambda: time.time() - start_time
   1109         success = False
   1110         while duration() < timeout_seconds:
   1111             success, state, conn_time  = self.wait_for_service_states(
   1112                     ssid, self.CONNECTED_STATES,
   1113                     int(math.ceil(timeout_seconds - duration())))
   1114             if not success:
   1115                 time.sleep(POLLING_INTERVAL_SECONDS)
   1116                 continue
   1117 
   1118             if freq:
   1119                 actual_freq = self.get_iw_link_value(
   1120                         iw_runner.IW_LINK_KEY_FREQUENCY)
   1121                 if str(freq) != actual_freq:
   1122                     logging.debug('Waiting for desired frequency %s (got %s).',
   1123                                   freq, actual_freq)
   1124                     time.sleep(POLLING_INTERVAL_SECONDS)
   1125                     continue
   1126 
   1127             if desired_subnet:
   1128                 actual_subnet = self.wifi_ip_subnet
   1129                 if actual_subnet != desired_subnet:
   1130                     logging.debug('Waiting for desired subnet %s (got %s).',
   1131                                   desired_subnet, actual_subnet)
   1132                     time.sleep(POLLING_INTERVAL_SECONDS)
   1133                     continue
   1134 
   1135             if ping_ip:
   1136                 ping_config = ping_runner.PingConfig(ping_ip)
   1137                 self.ping(ping_config)
   1138 
   1139             return ConnectTime(state, conn_time)
   1140 
   1141         freq_error_str = (' on frequency %d Mhz' % freq) if freq else ''
   1142         raise error.TestFail(
   1143                 'Failed to connect to "%s"%s in %f seconds (state=%s)' %
   1144                 (ssid, freq_error_str, duration(), state))
   1145 
   1146 
   1147     @contextmanager
   1148     def assert_disconnect_count(self, count):
   1149         """Context asserting |count| disconnects for the context lifetime.
   1150 
   1151         Creates an iw logger during the lifetime of the context and asserts
   1152         that the client disconnects exactly |count| times.
   1153 
   1154         @param count int the expected number of disconnections.
   1155 
   1156         """
   1157         with self.iw_runner.get_event_logger() as logger:
   1158             logger.start()
   1159             yield
   1160             logger.stop()
   1161             if logger.get_disconnect_count() != count:
   1162                 raise error.TestFail(
   1163                     'Client disconnected %d times; expected %d' %
   1164                     (logger.get_disconnect_count(), count))
   1165 
   1166 
   1167     def assert_no_disconnects(self):
   1168         """Context asserting no disconnects for the context lifetime."""
   1169         return self.assert_disconnect_count(0)
   1170 
   1171 
   1172     @contextmanager
   1173     def assert_disconnect_event(self):
   1174         """Context asserting at least one disconnect for the context lifetime.
   1175 
   1176         Creates an iw logger during the lifetime of the context and asserts
   1177         that the client disconnects at least one time.
   1178 
   1179         """
   1180         with self.iw_runner.get_event_logger() as logger:
   1181             logger.start()
   1182             yield
   1183             logger.stop()
   1184             if logger.get_disconnect_count() == 0:
   1185                 raise error.TestFail('Client did not disconnect')
   1186 
   1187 
   1188     def get_num_card_resets(self):
   1189         """Get card reset count."""
   1190         reset_msg = '[m]wifiex_sdio_card_reset'
   1191         result = self.host.run('grep -c %s /var/log/messages' % reset_msg,
   1192                                ignore_status=True)
   1193         if result.exit_status == 1:
   1194             return 0
   1195         count = int(result.stdout.strip())
   1196         return count
   1197 
   1198 
   1199     def get_disconnect_reasons(self):
   1200         """Get disconnect reason codes."""
   1201         disconnect_reason_msg = "updated DisconnectReason to ";
   1202         disconnect_reason_cleared = "clearing DisconnectReason for ";
   1203         result = self.host.run('grep -E "(%s|%s)" /var/log/net.log' %
   1204                                (disconnect_reason_msg,
   1205                                disconnect_reason_cleared),
   1206                                ignore_status=True)
   1207         if result.exit_status == 1:
   1208             return None
   1209 
   1210         lines = result.stdout.strip().split('\n')
   1211         disconnect_reasons = []
   1212         disconnect_reason_regex = re.compile(' to (\D?\d+)')
   1213 
   1214         found = False
   1215         for line in reversed(lines):
   1216           match = disconnect_reason_regex.search(line)
   1217           if match is not None:
   1218             disconnect_reasons.append(match.group(1))
   1219             found = True
   1220           else:
   1221             if (found):
   1222                 break
   1223         return list(reversed(disconnect_reasons))
   1224 
   1225 
   1226     def release_wifi_if(self):
   1227         """Release the control over the wifi interface back to normal operation.
   1228 
   1229         This will give the ownership of the wifi interface back to shill and
   1230         wpa_supplicant.
   1231 
   1232         """
   1233         self.set_device_enabled(self._wifi_if, True)
   1234 
   1235 
   1236     def claim_wifi_if(self):
   1237         """Claim the control over the wifi interface from this wifi client.
   1238 
   1239         This claim the ownership of the wifi interface from shill and
   1240         wpa_supplicant. The wifi interface should be UP when this call returns.
   1241 
   1242         """
   1243         # Disabling a wifi device in shill will remove that device from
   1244         # wpa_supplicant as well.
   1245         self.set_device_enabled(self._wifi_if, False)
   1246 
   1247         # Wait for shill to bring down the wifi interface.
   1248         is_interface_down = lambda: not self._interface.is_up
   1249         utils.poll_for_condition(
   1250                 is_interface_down,
   1251                 timeout=INTERFACE_DOWN_WAIT_TIME_SECONDS,
   1252                 sleep_interval=0.5,
   1253                 desc='Timeout waiting for interface to go down.')
   1254         # Bring up the wifi interface to allow the test to use the interface.
   1255         self.host.run('%s link set %s up' % (self.cmd_ip, self.wifi_if))
   1256 
   1257 
   1258     def set_sched_scan(self, enable, fail_on_unsupported=False):
   1259         """enable/disable scheduled scan.
   1260 
   1261         @param enable bool flag indicating to enable/disable scheduled scan.
   1262 
   1263         """
   1264         if fail_on_unsupported:
   1265             self._assert_method_supported('set_sched_scan')
   1266         elif not self._supports_method('set_sched_scan'):
   1267             return False
   1268         return self._shill_proxy.set_sched_scan(enable)
   1269 
   1270 
   1271     def check_connected_on_last_resume(self):
   1272         """Checks whether the DUT was connected on its last resume.
   1273 
   1274         Checks that the DUT was connected after waking from suspend by parsing
   1275         the last instance shill log message that reports shill's connection
   1276         status on resume. Fails the test if this log message reports that
   1277         the DUT woke up disconnected.
   1278 
   1279         """
   1280         # As of build R43 6913.0.0, the shill log message from the function
   1281         # OnAfterResume is called as soon as shill resumes from suspend, and
   1282         # will report whether or not shill is connected. The log message will
   1283         # take one of the following two forms:
   1284         #
   1285         #       [...] [INFO:wifi.cc(1941)] OnAfterResume: connected
   1286         #       [...] [INFO:wifi.cc(1941)] OnAfterResume: not connected
   1287         #
   1288         # where 1941 is an arbitrary PID number. By checking if the last
   1289         # instance of this message contains the substring "not connected", we
   1290         # can determine whether or not shill was connected on its last resume.
   1291         connection_status_log_regex_str = 'INFO:wifi\.cc.*OnAfterResume'
   1292         not_connected_substr = 'not connected'
   1293         connected_substr = 'connected'
   1294 
   1295         cmd = ('grep -E %s /var/log/net.log | tail -1' %
   1296                connection_status_log_regex_str)
   1297         connection_status_log = self.host.run(cmd).stdout
   1298         if not connection_status_log:
   1299             raise error.TestFail('Could not find resume connection status log '
   1300                                  'message.')
   1301 
   1302         logging.debug('Connection status message:\n%s', connection_status_log)
   1303         if not_connected_substr in connection_status_log:
   1304             raise error.TestFail('Client was not connected upon waking from '
   1305                                  'suspend.')
   1306 
   1307         if not connected_substr in connection_status_log:
   1308             raise error.TestFail('Last resume log message did not contain '
   1309                                  'connection status.')
   1310 
   1311         logging.info('Client was connected upon waking from suspend.')
   1312 
   1313 
   1314     def check_wake_on_wifi_throttled(self):
   1315         """
   1316         Checks whether wake on WiFi was throttled on the DUT on the last dark
   1317         resume. Check for this by parsing shill logs for a throttling message.
   1318 
   1319         """
   1320         # We are looking for an dark resume error log message indicating that
   1321         # wake on WiFi was throttled. This is an example of the error message:
   1322         #     [...] [ERROR:wake_on_wifi.cc(1304)] OnDarkResume: Too many dark \
   1323         #       resumes; disabling wake on WiFi temporarily
   1324         dark_resume_log_regex_str = 'ERROR:wake_on_wifi\.cc.*OnDarkResume:.*'
   1325         throttled_msg_substr = ('Too many dark resumes; disabling wake on '
   1326                                    'WiFi temporarily')
   1327 
   1328         cmd = ('grep -E %s /var/log/net.log | tail -1' %
   1329                dark_resume_log_regex_str)
   1330         last_dark_resume_error_log = self.host.run(cmd).stdout
   1331         if not last_dark_resume_error_log:
   1332             raise error.TestFail('Could not find a dark resume log message.')
   1333 
   1334         logging.debug('Last dark resume log message:\n%s',
   1335                 last_dark_resume_error_log)
   1336         if not throttled_msg_substr in last_dark_resume_error_log:
   1337             raise error.TestFail('Wake on WiFi was not throttled on the last '
   1338                                  'dark resume.')
   1339 
   1340         logging.info('Wake on WiFi was throttled on the last dark resume.')
   1341 
   1342 
   1343     def shill_debug_log(self, message):
   1344         """Logs a message to the shill log (i.e. /var/log/net.log). This
   1345            message will be logged at the DEBUG level.
   1346 
   1347         @param message: the message to be logged.
   1348 
   1349         """
   1350         logging.info(message)
   1351 
   1352         # Skip this command if running on Android, since Android does not
   1353         # have a /usr/bin/logger (or an equivalent that takes the same
   1354         # parameters as the Linux logger does.)
   1355         if not isinstance(self.host, adb_host.ADBHost):
   1356             logger_command = ('/usr/bin/logger'
   1357                               ' --tag shill'
   1358                               ' --priority daemon.debug'
   1359                               ' "%s"' % utils.sh_escape(message))
   1360             self.host.run(logger_command)
   1361 
   1362 
   1363     def is_wake_on_wifi_supported(self):
   1364         """Returns true iff wake-on-WiFi is supported by the DUT."""
   1365 
   1366         if (self.shill.get_dbus_property_on_device(
   1367                     self.wifi_if, self.WAKE_ON_WIFI_FEATURES) ==
   1368              WAKE_ON_WIFI_NOT_SUPPORTED):
   1369             return False
   1370         return True
   1371 
   1372 
   1373     def set_dhcp_property(self, dhcp_prop_name, dhcp_prop_value):
   1374         """Sets the given DHCP_Property to the value provided.
   1375 
   1376         @param dhcp_prop_name: the dhcp_property to be set
   1377         @param dhcp_prop_value: value to assign to the dhcp_prop_name
   1378         @return a context manager for the setting
   1379 
   1380         """
   1381         return TemporaryManagerDBusProperty(self._shill_proxy,
   1382                                             dhcp_prop_name,
   1383                                             dhcp_prop_value)
   1384 
   1385 
   1386 class TemporaryDeviceDBusProperty:
   1387     """Utility class to temporarily change a dbus property for the WiFi device.
   1388 
   1389     Since dbus properties are global and persistent settings, we want
   1390     to make sure that we change them back to what they were before the test
   1391     started.
   1392 
   1393     """
   1394 
   1395     def __init__(self, shill_proxy, interface, prop_name, value):
   1396         """Construct a TemporaryDeviceDBusProperty context manager.
   1397 
   1398 
   1399         @param shill_proxy: the shill proxy to use to communicate via dbus
   1400         @param interface: device whose property to change (e.g. 'wlan0')
   1401         @param prop_name: the name of the property we want to set
   1402         @param value: the desired value of the property
   1403 
   1404         """
   1405         self._shill = shill_proxy
   1406         self._interface = interface
   1407         self._prop_name = prop_name
   1408         self._value = value
   1409         self._saved_value = None
   1410 
   1411 
   1412     def __enter__(self):
   1413         logging.info('- Setting property %s on device %s',
   1414                      self._prop_name,
   1415                      self._interface)
   1416 
   1417         self._saved_value = self._shill.get_dbus_property_on_device(
   1418                 self._interface, self._prop_name)
   1419         if self._saved_value is None:
   1420             raise error.TestFail('Device or property not found.')
   1421         if not self._shill.set_dbus_property_on_device(self._interface,
   1422                                                        self._prop_name,
   1423                                                        self._value):
   1424             raise error.TestFail('Could not set property')
   1425 
   1426         logging.info('- Changed value from %s to %s',
   1427                      self._saved_value,
   1428                      self._value)
   1429 
   1430 
   1431     def __exit__(self, exception, value, traceback):
   1432         logging.info('- Resetting property %s', self._prop_name)
   1433 
   1434         if not self._shill.set_dbus_property_on_device(self._interface,
   1435                                                        self._prop_name,
   1436                                                        self._saved_value):
   1437             raise error.TestFail('Could not reset property')
   1438 
   1439 
   1440 class TemporaryManagerDBusProperty:
   1441     """Utility class to temporarily change a Manager dbus property.
   1442 
   1443     Since dbus properties are global and persistent settings, we want
   1444     to make sure that we change them back to what they were before the test
   1445     started.
   1446 
   1447     """
   1448 
   1449     def __init__(self, shill_proxy, prop_name, value):
   1450         """Construct a TemporaryManagerDBusProperty context manager.
   1451 
   1452         @param shill_proxy: the shill proxy to use to communicate via dbus
   1453         @param prop_name: the name of the property we want to set
   1454         @param value: the desired value of the property
   1455 
   1456         """
   1457         self._shill = shill_proxy
   1458         self._prop_name = prop_name
   1459         self._value = value
   1460         self._saved_value = None
   1461 
   1462 
   1463     def __enter__(self):
   1464         logging.info('- Setting Manager property: %s', self._prop_name)
   1465 
   1466         self._saved_value = self._shill.get_manager_property(
   1467                 self._prop_name)
   1468         if self._saved_value is None:
   1469             self._saved_value = ""
   1470             if not self._shill.set_optional_manager_property(self._prop_name,
   1471                                                              self._value):
   1472                 raise error.TestFail('Could not set optional manager property.')
   1473         else:
   1474             if not self._shill.set_manager_property(self._prop_name, self._value):
   1475                 raise error.TestFail('Could not set manager property')
   1476 
   1477         logging.info('- Changed value from [%s] to [%s]',
   1478                      self._saved_value,
   1479                      self._value)
   1480 
   1481 
   1482     def __exit__(self, exception, value, traceback):
   1483         logging.info('- Resetting property %s to [%s]',
   1484                      self._prop_name,
   1485                      self._saved_value)
   1486 
   1487         if not self._shill.set_manager_property(self._prop_name,
   1488                                                 self._saved_value):
   1489             raise error.TestFail('Could not reset manager property')
   1490