Home | History | Annotate | Download | only in cellular_SuspendResume
      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.common_lib.cros import chrome
     13 from autotest_lib.client.cros import rtc
     14 from autotest_lib.client.cros.power import power_suspend
     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 cellular_SuspendResume(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.flim.FindCellularService(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                         cellular_SuspendResume.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 flim.FindCellularDevice
    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         device = None
    130         properties = None
    131         start_time = time.time()
    132         timeout = start_time + timeout
    133         while properties is None and time.time() < timeout:
    134             try:
    135                 device = self.flim.FindCellularDevice(timeout)
    136                 properties = device.GetProperties(utf8_strings=True)
    137             except dbus.exceptions.DBusException:
    138                 logging.debug('Mobile device not ready yet')
    139                 device = None
    140                 properties = None
    141 
    142             time.sleep(1)
    143 
    144         if not device:
    145             # If device is not found, spit the output of lsusb for debugging.
    146             lsusb_output = utils.system_output('lsusb', timeout=self.TIMEOUT)
    147             logging.debug('Mobile device not found. lsusb output:')
    148             logging.debug(lsusb_output)
    149             raise error.TestError('Mobile device not found.')
    150         return device
    151 
    152     # The suspend_mobile_enabled test suspends, then resumes the machine while
    153     # mobile is enabled.
    154     def scenario_suspend_mobile_enabled(self, **kwargs):
    155         device = self.__get_mobile_device()
    156         self.enable_device(device, True)
    157         if not self.mobile_service_available():
    158             raise error.TestError('Unable to find mobile service.')
    159         self.suspend_resume(20)
    160 
    161     # The suspend_mobile_disabled test suspends, then resumes the machine
    162     # while mobile is disabled.
    163     def scenario_suspend_mobile_disabled(self, **kwargs):
    164         device = self.__get_mobile_device()
    165         self.enable_device(device, False)
    166         self.suspend_resume(20)
    167 
    168         # This verifies that the device is in the same state before and after
    169         # the device is suspended/resumed.
    170         device = self.__get_mobile_device()
    171         logging.info('Checking to see if device is in the same state as prior '
    172                      'to suspend/resume')
    173         self.check_powered(device, False)
    174 
    175         # Turn on the device to make sure we can bring it back up.
    176         self.enable_device(device, True)
    177 
    178     # The suspend_mobile_disabled_twice subroutine is here because
    179     # of bug 9405.  The test will suspend/resume the device twice
    180     # while mobile is disabled.  We will then verify that mobile can be
    181     # enabled thereafter.
    182     def scenario_suspend_mobile_disabled_twice(self, **kwargs):
    183         device = self.__get_mobile_device()
    184         self.enable_device(device, False)
    185 
    186         for _ in [0, 1]:
    187             self.suspend_resume(20)
    188 
    189             # This verifies that the device is in the same state before
    190             # and after the device is suspended/resumed.
    191             device = self.__get_mobile_device()
    192             logging.info('Checking to see if device is in the same state as '
    193                          'prior to suspend/resume')
    194             self.check_powered(device, False)
    195 
    196         # Turn on the device to make sure we can bring it back up.
    197         self.enable_device(device, True)
    198 
    199 
    200     # This test randomly enables or disables the modem.  This is mainly used
    201     # for stress tests as it does not check the power state of the modem before
    202     # and after suspend/resume.
    203     def scenario_suspend_mobile_random(self, stress_iterations=10, **kwargs):
    204         logging.debug('Running suspend_mobile_random %d times' %
    205                       stress_iterations)
    206         device = self.__get_mobile_device()
    207         self.enable_device(device, choice([True, False]))
    208 
    209         # Suspend the device for a random duration, wake it,
    210         # wait for the service to appear, then wait for
    211         # some random duration before suspending again.
    212         for i in range(stress_iterations):
    213             logging.debug('Running iteration %d' % (i+1))
    214             self.suspend_resume(randint(10, 40))
    215             device = self.__get_mobile_device()
    216             self.enable_device(device, True)
    217             if not self.flim.FindCellularService(self.TIMEOUT*2):
    218                 raise error.TestError('Unable to find mobile service')
    219             time.sleep(randint(1, 30))
    220 
    221 
    222     # This verifies that autoconnect works.
    223     def scenario_autoconnect(self, **kwargs):
    224         device = self.__get_mobile_device()
    225         self.enable_device(device, True)
    226         service = self.flim.FindCellularService(self.TIMEOUT)
    227         if not service:
    228             raise error.TestError('Unable to find mobile service')
    229 
    230         props = service.GetProperties(utf8_strings=True)
    231         if props['AutoConnect']:
    232             expected_states = ['ready', 'online', 'portal']
    233         else:
    234             expected_states = ['idle']
    235 
    236         for _ in xrange(5):
    237             # Must wait at least 20 seconds to ensure that the suspend occurs
    238             self.suspend_resume(20)
    239 
    240             # wait for the device to come back
    241             device = self.__get_mobile_device()
    242 
    243             # verify the service state is correct
    244             service = self.flim.FindCellularService(self.TIMEOUT)
    245             if not service:
    246                 raise error.TestFail('Cannot find mobile service')
    247 
    248             state, _ = self.flim.WaitForServiceState(service,
    249                                                      expected_states,
    250                                                      self.TIMEOUT)
    251             if not state in expected_states:
    252                 raise error.TestFail('Mobile state %s not in %s as expected'
    253                                      % (state, ', '.join(expected_states)))
    254 
    255 
    256     # Returns 1 if modem_status returned output within duration.
    257     # otherwise, returns 0
    258     def _get_modem_status(self, duration=TIMEOUT):
    259         time_end = time.time() + duration
    260         while time.time() < time_end:
    261             status = utils.system_output('modem status', timeout=self.TIMEOUT)
    262             if reduce(lambda x, y: x & y(status),
    263                       cellular_SuspendResume.modem_status_checks,
    264                       True):
    265                 break
    266         else:
    267             return 0
    268         return 1
    269 
    270     # This is the wrapper around the running of each scenario with
    271     # initialization steps and final checks.
    272     def run_scenario(self, function_name, **kwargs):
    273         device = self.__get_mobile_device()
    274 
    275         # Initialize all tests with the power off.
    276         self.enable_device(device, False)
    277 
    278         function = getattr(self, function_name)
    279         logging.info('Running %s' % function_name)
    280         function(**kwargs)
    281 
    282         # By the end of each test, the mobile device should be up.
    283         # Here we verify that the power state of the device is up, and
    284         # that the mobile service can be found.
    285         device = self.__get_mobile_device()
    286         logging.info('Checking that modem is powered on after scenario %s.',
    287                      function_name)
    288         self.check_powered(device, True)
    289 
    290         logging.info('Scenario complete: %s.' % function_name)
    291 
    292         if not self._get_modem_status():
    293             raise error.TestFail('Failed to get modem_status after %s.'
    294                               % function_name)
    295         service = self.mobile_service_available()
    296         if not service:
    297             raise error.TestFail('Could not find mobile service at the end '
    298                                  'of test %s.' % function_name)
    299 
    300     def init_flimflam(self):
    301         # Initialize flimflam and device type specific functions.
    302         self.flim = flimflam.FlimFlam(dbus.SystemBus())
    303         self.flim.SetDebugTags(SHILL_LOG_SCOPES)
    304 
    305         self.flim.FindCellularService = self.flim.FindCellularService
    306         self.flim.FindCellularDevice = self.flim.FindCellularDevice
    307 
    308     def run_once(self, scenario_group='all', autoconnect=False, **kwargs):
    309 
    310         with chrome.Chrome():
    311             # Replace the test type with the list of tests
    312             if (scenario_group not in
    313                     cellular_SuspendResume.scenarios.keys()):
    314                 scenario_group = 'all'
    315             logging.info('Running scenario group: %s' % scenario_group)
    316             scenarios = cellular_SuspendResume.scenarios[scenario_group]
    317 
    318             self.init_flimflam()
    319 
    320             device = self.__get_mobile_device()
    321             if not device:
    322                 raise error.TestFail('Cannot find mobile device.')
    323             self.enable_device(device, True)
    324 
    325             service = self.flim.FindCellularService(self.TIMEOUT)
    326             if not service:
    327                 raise error.TestFail('Cannot find mobile service.')
    328 
    329             service.SetProperty('AutoConnect', dbus.Boolean(autoconnect))
    330 
    331             logging.info('Running scenarios with autoconnect %s.' % autoconnect)
    332 
    333             for t in scenarios:
    334                 self.run_scenario(t, **kwargs)
    335