1 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 import dbus 6 import logging 7 from random import choice, randint 8 import time 9 10 from autotest_lib.client.bin import test, utils 11 from autotest_lib.client.common_lib import error 12 from autotest_lib.client.cros import power_suspend, rtc 13 from autotest_lib.client.cros.networking.chrome_testing \ 14 import chrome_networking_test_context as cntc 15 16 # Special import to define the location of the flimflam library. 17 from autotest_lib.client.cros import flimflam_test_path 18 import flimflam 19 20 SHILL_LOG_SCOPES = 'cellular+dbus+device+dhcp+manager+modem+portal+service' 21 22 class network_MobileSuspendResume(test.test): 23 version = 1 24 TIMEOUT = 60 25 26 device_okerrors = [ 27 # Setting of device power can sometimes result with InProgress error 28 # if it is in the process of already doing so. 29 'org.chromium.flimflam.Error.InProgress', 30 ] 31 32 service_okerrors = [ 33 'org.chromium.flimflam.Error.InProgress', 34 'org.chromium.flimflam.Error.AlreadyConnected', 35 ] 36 37 scenarios = { 38 'all': [ 39 'scenario_suspend_mobile_enabled', 40 'scenario_suspend_mobile_disabled', 41 'scenario_suspend_mobile_disabled_twice', 42 'scenario_autoconnect', 43 ], 44 'stress': [ 45 'scenario_suspend_mobile_random', 46 ], 47 } 48 49 modem_status_checks = [ 50 lambda s: ('org/chromium/ModemManager' in s) or 51 ('org/freedesktop/ModemManager' in s) or 52 ('org/freedesktop/ModemManager1' in s), 53 lambda s: ('meid' in s) or ('EquipmentIdentifier' in s), 54 lambda s: 'Manufacturer' in s, 55 lambda s: 'Device' in s 56 ] 57 58 def filterexns(self, function, exn_list): 59 try: 60 function() 61 except dbus.exceptions.DBusException, e: 62 if e._dbus_error_name not in exn_list: 63 raise e 64 65 # This function returns True when mobile service is available. Otherwise, 66 # if the timeout period has been hit, it returns false. 67 def mobile_service_available(self, timeout=60): 68 service = self.FindMobileService(timeout) 69 if service: 70 logging.info('Mobile service is available.') 71 return service 72 logging.info('Mobile service is not available.') 73 return None 74 75 def get_powered(self, device): 76 properties = device.GetProperties(utf8_strings=True) 77 logging.debug(properties) 78 logging.info('Power state of mobile device is %s.', 79 ['off', 'on'][properties['Powered']]) 80 return properties['Powered'] 81 82 def _check_powered(self, device, check_enabled): 83 properties = device.GetProperties(utf8_strings=True) 84 power_state = (properties['Powered'] == 1) 85 return power_state if check_enabled else not power_state 86 87 def check_powered(self, device, check_enabled): 88 logging.info('Polling to check device state is %s.', 89 'enabled' if check_enabled else 'disabled') 90 utils.poll_for_condition( 91 lambda: self._check_powered(device, check_enabled), 92 exception=error.TestFail( 93 'Failed to verify the device is in power state %s.', 94 'enabled' if check_enabled else 'disabled'), 95 timeout=self.TIMEOUT) 96 logging.info('Verified device power state.') 97 98 def enable_device(self, device, enable): 99 lambda_func = lambda: device.Enable() if enable else device.Disable() 100 self.filterexns(lambda_func, 101 network_MobileSuspendResume.device_okerrors) 102 # Sometimes if we disable the modem then immediately enable the modem 103 # we hit a condition where the modem seems to ignore the enable command 104 # and keep the modem disabled. This is to prevent that from happening. 105 time.sleep(4) 106 return self.get_powered(device) == enable 107 108 def suspend_resume(self, duration=10): 109 suspender = power_suspend.Suspender(self.resultsdir, throw=True) 110 suspender.suspend(duration) 111 logging.info('Machine resumed') 112 113 # Race condition hack alert: Before we added this sleep, this 114 # test was very sensitive to the relative timing of the test 115 # and modem resumption. There is a window where flimflam has 116 # not yet learned that the old modem has gone away (it doesn't 117 # find this out until seconds after we resume) and the test is 118 # running. If the test finds and attempts to use the old 119 # modem, those operations will fail. There's no good 120 # hardware-independent way to see the modem go away and come 121 # back, so instead we sleep 122 time.sleep(4) 123 124 # __get_mobile_device is a hack wrapper around the FindMobileDevice 125 # that verifies that GetProperties can be called before proceeding. 126 # There appears to be an issue after suspend/resume where GetProperties 127 # returns with UnknownMethod called until some time later. 128 def __get_mobile_device(self, timeout=TIMEOUT): 129 properties = None 130 start_time = time.time() 131 timeout = start_time + timeout 132 while properties is None and time.time() < timeout: 133 try: 134 device = self.FindMobileDevice(timeout) 135 properties = device.GetProperties(utf8_strings=True) 136 except dbus.exceptions.DBusException: 137 logging.debug('Mobile device not ready yet') 138 properties = None 139 140 time.sleep(1) 141 if not device: 142 # If device is not found, spit the output of lsusb for debugging. 143 lsusb_output = utils.system_output('lsusb', timeout=self.TIMEOUT) 144 logging.debug('Mobile device not found. lsusb output:') 145 logging.debug(lsusb_output) 146 raise error.TestError('Mobile device not found.') 147 return device 148 149 # The suspend_mobile_enabled test suspends, then resumes the machine while 150 # mobile is enabled. 151 def scenario_suspend_mobile_enabled(self, **kwargs): 152 device = self.__get_mobile_device() 153 self.enable_device(device, True) 154 if not self.mobile_service_available(): 155 raise error.TestError('Unable to find mobile service.') 156 self.suspend_resume(20) 157 158 # The suspend_mobile_disabled test suspends, then resumes the machine 159 # while mobile is disabled. 160 def scenario_suspend_mobile_disabled(self, **kwargs): 161 device = self.__get_mobile_device() 162 self.enable_device(device, False) 163 self.suspend_resume(20) 164 165 # This verifies that the device is in the same state before and after 166 # the device is suspended/resumed. 167 device = self.__get_mobile_device() 168 logging.info('Checking to see if device is in the same state as prior ' 169 'to suspend/resume') 170 self.check_powered(device, False) 171 172 # Turn on the device to make sure we can bring it back up. 173 self.enable_device(device, True) 174 175 # The suspend_mobile_disabled_twice subroutine is here because 176 # of bug 9405. The test will suspend/resume the device twice 177 # while mobile is disabled. We will then verify that mobile can be 178 # enabled thereafter. 179 def scenario_suspend_mobile_disabled_twice(self, **kwargs): 180 device = self.__get_mobile_device() 181 self.enable_device(device, False) 182 183 for _ in [0, 1]: 184 self.suspend_resume(20) 185 186 # This verifies that the device is in the same state before 187 # and after the device is suspended/resumed. 188 device = self.__get_mobile_device() 189 logging.info('Checking to see if device is in the same state as ' 190 'prior to suspend/resume') 191 self.check_powered(device, False) 192 193 # Turn on the device to make sure we can bring it back up. 194 self.enable_device(device, True) 195 196 # Special override for connecting to wimax devices since it requires 197 # EAP parameters. 198 def connect_wimax(self, service=None, identity='test', 199 password='test', **kwargs): 200 service.SetProperty('EAP.Identity', identity) 201 service.SetProperty('EAP.Password', identity) 202 self.flim.ConnectService(service=service, **kwargs) 203 204 # This test randomly enables or disables the modem. This is mainly used 205 # for stress tests as it does not check the power state of the modem before 206 # and after suspend/resume. 207 def scenario_suspend_mobile_random(self, stress_iterations=10, **kwargs): 208 logging.debug('Running suspend_mobile_random %d times' % 209 stress_iterations) 210 device = self.__get_mobile_device() 211 self.enable_device(device, choice([True, False])) 212 213 # Suspend the device for a random duration, wake it, 214 # wait for the service to appear, then wait for 215 # some random duration before suspending again. 216 for i in range(stress_iterations): 217 logging.debug('Running iteration %d' % (i+1)) 218 self.suspend_resume(randint(10, 40)) 219 device = self.__get_mobile_device() 220 self.enable_device(device, True) 221 if not self.FindMobileService(self.TIMEOUT*2): 222 raise error.TestError('Unable to find mobile service') 223 time.sleep(randint(1, 30)) 224 225 226 # This verifies that autoconnect works. 227 def scenario_autoconnect(self, **kwargs): 228 device = self.__get_mobile_device() 229 self.enable_device(device, True) 230 service = self.FindMobileService(self.TIMEOUT) 231 if not service: 232 raise error.TestError('Unable to find mobile service') 233 234 props = service.GetProperties(utf8_strings=True) 235 if props['AutoConnect']: 236 expected_states = ['ready', 'online', 'portal'] 237 else: 238 expected_states = ['idle'] 239 240 for _ in xrange(5): 241 # Must wait at least 20 seconds to ensure that the suspend occurs 242 self.suspend_resume(20) 243 244 # wait for the device to come back 245 device = self.__get_mobile_device() 246 247 # verify the service state is correct 248 service = self.FindMobileService(self.TIMEOUT) 249 if not service: 250 raise error.TestFail('Cannot find mobile service') 251 252 state, _ = self.flim.WaitForServiceState(service, 253 expected_states, 254 self.TIMEOUT) 255 if not state in expected_states: 256 raise error.TestFail('Mobile state %s not in %s as expected' 257 % (state, ', '.join(expected_states))) 258 259 # Running modem status is not supported by all modems, specifically wimax 260 # type modems. 261 def _skip_modem_status(self, *args, **kwargs): 262 return 1 263 264 # Returns 1 if modem_status returned output within duration. 265 # otherwise, returns 0 266 def _get_modem_status(self, duration=TIMEOUT): 267 time_end = time.time() + duration 268 while time.time() < time_end: 269 status = utils.system_output('modem status', timeout=self.TIMEOUT) 270 if reduce(lambda x, y: x & y(status), 271 network_MobileSuspendResume.modem_status_checks, 272 True): 273 break 274 else: 275 return 0 276 return 1 277 278 # This is the wrapper around the running of each scenario with 279 # initialization steps and final checks. 280 def run_scenario(self, function_name, **kwargs): 281 device = self.__get_mobile_device() 282 283 # Initialize all tests with the power off. 284 self.enable_device(device, False) 285 286 function = getattr(self, function_name) 287 logging.info('Running %s' % function_name) 288 function(**kwargs) 289 290 # By the end of each test, the mobile device should be up. 291 # Here we verify that the power state of the device is up, and 292 # that the mobile service can be found. 293 device = self.__get_mobile_device() 294 logging.info('Checking that modem is powered on after scenario %s.', 295 function_name) 296 self.check_powered(device, True) 297 298 logging.info('Scenario complete: %s.' % function_name) 299 300 if not self.modem_status(): 301 raise error.TestFail('Failed to get modem_status after %s.' 302 % function_name) 303 service = self.mobile_service_available() 304 if not service: 305 raise error.TestFail('Could not find mobile service at the end ' 306 'of test %s.' % function_name) 307 308 def init_flimflam(self, device_type): 309 # Initialize flimflam and device type specific functions. 310 self.flim = flimflam.FlimFlam(dbus.SystemBus()) 311 self.flim.SetDebugTags(SHILL_LOG_SCOPES) 312 313 logging.debug('Using device type: %s' % device_type) 314 if device_type == flimflam.FlimFlam.DEVICE_WIMAX: 315 self.FindMobileService = self.flim.FindWimaxService 316 self.FindMobileDevice = self.flim.FindWimaxDevice 317 self.modem_status = self._skip_modem_status 318 self.connect_mobile_service= self.connect_wimax 319 elif device_type == flimflam.FlimFlam.DEVICE_CELLULAR: 320 self.FindMobileService = self.flim.FindCellularService 321 self.FindMobileDevice = self.flim.FindCellularDevice 322 self.modem_status = self._get_modem_status 323 self.connect_mobile_service = self.flim.ConnectService 324 else: 325 raise error.TestError('Device type %s not supported yet.' % 326 device_type) 327 328 def run_once(self, scenario_group='all', autoconnect=False, 329 device_type=flimflam.FlimFlam.DEVICE_CELLULAR, **kwargs): 330 331 with cntc.ChromeNetworkingTestContext(): 332 # Replace the test type with the list of tests 333 if (scenario_group not in 334 network_MobileSuspendResume.scenarios.keys()): 335 scenario_group = 'all' 336 logging.info('Running scenario group: %s' % scenario_group) 337 scenarios = network_MobileSuspendResume.scenarios[scenario_group] 338 339 self.init_flimflam(device_type) 340 341 device = self.__get_mobile_device() 342 if not device: 343 raise error.TestFail('Cannot find mobile device.') 344 self.enable_device(device, True) 345 346 service = self.FindMobileService(self.TIMEOUT) 347 if not service: 348 raise error.TestFail('Cannot find mobile service.') 349 350 service.SetProperty('AutoConnect', dbus.Boolean(autoconnect)) 351 352 logging.info('Running scenarios with autoconnect %s.' % autoconnect) 353 354 for t in scenarios: 355 self.run_scenario(t, **kwargs) 356