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 5import contextlib 6import logging 7import math 8import os 9import re 10import time 11 12from contextlib import contextmanager 13from collections import namedtuple 14 15from autotest_lib.client.bin import utils 16from autotest_lib.client.common_lib import error 17from autotest_lib.client.common_lib.cros.network import interface 18from autotest_lib.client.common_lib.cros.network import iw_runner 19from autotest_lib.client.common_lib.cros.network import ping_runner 20from autotest_lib.client.cros import constants 21from autotest_lib.server import adb_utils 22from autotest_lib.server import autotest 23from autotest_lib.server import constants as server_constants 24from autotest_lib.server import site_linux_system 25from autotest_lib.server.cros.network import wpa_cli_proxy 26from autotest_lib.server.hosts import adb_host 27from autotest_lib.server.hosts import cast_os_host 28 29# Wake-on-WiFi feature strings 30WAKE_ON_WIFI_NONE = 'none' 31WAKE_ON_WIFI_PACKET = 'packet' 32WAKE_ON_WIFI_DARKCONNECT = 'darkconnect' 33WAKE_ON_WIFI_PACKET_DARKCONNECT = 'packet_and_darkconnect' 34WAKE_ON_WIFI_NOT_SUPPORTED = 'not_supported' 35 36# Wake-on-WiFi test timing constants 37SUSPEND_WAIT_TIME_SECONDS = 10 38RECEIVE_PACKET_WAIT_TIME_SECONDS = 10 39DARK_RESUME_WAIT_TIME_SECONDS = 25 40WAKE_TO_SCAN_PERIOD_SECONDS = 30 41NET_DETECT_SCAN_WAIT_TIME_SECONDS = 15 42WAIT_UP_TIMEOUT_SECONDS = 10 43DISCONNECT_WAIT_TIME_SECONDS = 10 44INTERFACE_DOWN_WAIT_TIME_SECONDS = 10 45 46ConnectTime = namedtuple('ConnectTime', 'state, time') 47 48XMLRPC_BRINGUP_TIMEOUT_SECONDS = 60 49SHILL_XMLRPC_LOG_PATH = '/var/log/shill_xmlrpc_server.log' 50SHILL_BRILLO_XMLRPC_LOG_PATH = '/data/shill_xmlrpc_server.log' 51ANDROID_XMLRPC_SERVER_AUTOTEST_PATH = ( 52 '../../../client/cros/networking/android_xmlrpc_server.py') 53# Log dirs/filenames are suffixed with serial number of the DUT 54ANDROID_XMLRPC_DEBUG_DIR_FMT = '/var/log/acts-%s' 55ANDROID_XMLRPC_LOG_FILE_FMT = '/var/log/android_xmlrpc_server-%s.log' 56# Local debug dir name is suffixed by the test name 57ANDROID_LOCAL_DEBUG_DIR_FMT = 'android_debug_%s' 58 59 60def _is_android_host(host): 61 return host.get_os_type() == adb_host.OS_TYPE_ANDROID 62 63 64def _is_brillo_host(host): 65 return host.get_os_type() == adb_host.OS_TYPE_BRILLO 66 67 68def _is_eureka_host(host): 69 return host.get_os_type() == cast_os_host.OS_TYPE_CAST_OS 70 71 72def 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 88def 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 152def _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 164class 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 1404class 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 1458class 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