Home | History | Annotate | Download | only in network_MobileSuspendResume
      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