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