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