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