Home | History | Annotate | Download | only in cellular
      1 # Copyright (c) 2014 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 contextlib
      6 import dbus
      7 import logging
      8 import sys
      9 import traceback
     10 
     11 import common
     12 from autotest_lib.client.bin import utils
     13 from autotest_lib.client.common_lib import error
     14 from autotest_lib.client.cros import backchannel
     15 from autotest_lib.client.cros.cellular import cell_tools
     16 from autotest_lib.client.cros.cellular import mm
     17 from autotest_lib.client.cros.cellular.pseudomodem import pseudomodem_context
     18 from autotest_lib.client.cros.cellular.wardmodem import wardmodem
     19 from autotest_lib.client.cros.networking import cellular_proxy
     20 from autotest_lib.client.cros.networking import shill_proxy
     21 
     22 # Import 'flimflam_test_path' first in order to import flimflam.
     23 # pylint: disable=W0611
     24 from autotest_lib.client.cros import flimflam_test_path
     25 import flimflam
     26 
     27 class CellularTestEnvironment(object):
     28     """Setup and verify cellular test environment.
     29 
     30     This context manager configures the following:
     31         - Sets up backchannel.
     32         - Shuts down other devices except cellular.
     33         - Shill and MM logging is enabled appropriately for cellular.
     34         - Initializes members that tests should use to access test environment
     35           (eg. |shill|, |flimflam|, |modem_manager|, |modem|).
     36 
     37     Then it verifies the following is valid:
     38         - The backchannel is using an Ethernet device.
     39         - The SIM is inserted and valid.
     40         - There is one and only one modem in the device.
     41         - The modem is registered to the network.
     42         - There is a cellular service in shill and it's not connected.
     43 
     44     Don't use this base class directly, use the appropriate subclass.
     45 
     46     Setup for over-the-air tests:
     47         with CellularOTATestEnvironment() as test_env:
     48             # Test body
     49 
     50     Setup for pseudomodem tests:
     51         with CellularPseudoMMTestEnvironment(
     52                 pseudomm_args=({'family': '3GPP'})) as test_env:
     53             # Test body
     54 
     55     Setup for wardmodem tests:
     56         with CellularWardModemTestEnvironment(
     57                 wardmodem_modem='e362') as test_env:
     58             # Test body
     59 
     60     """
     61 
     62     def __init__(self, use_backchannel=True, shutdown_other_devices=True,
     63                  modem_pattern=''):
     64         """
     65         @param use_backchannel: Set up the backchannel that can be used to
     66                 communicate with the DUT.
     67         @param shutdown_other_devices: If True, shutdown all devices except
     68                 cellular.
     69         @param modem_pattern: Search string used when looking for the modem.
     70 
     71         """
     72         # Tests should use this main loop instead of creating their own.
     73         self.mainloop = dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
     74         self.bus = dbus.SystemBus(mainloop=self.mainloop)
     75 
     76         self.shill = None
     77         self.flim = None  # Only use this for legacy tests.
     78         self.modem_manager = None
     79         self.modem = None
     80         self.modem_path = None
     81         self._backchannel = None
     82 
     83         self._modem_pattern = modem_pattern
     84 
     85         self._nested = None
     86         self._context_managers = []
     87         if use_backchannel:
     88             self._backchannel = backchannel.Backchannel()
     89             self._context_managers.append(self._backchannel)
     90         if shutdown_other_devices:
     91             self._context_managers.append(
     92                     cell_tools.OtherDeviceShutdownContext('cellular'))
     93 
     94 
     95     @contextlib.contextmanager
     96     def _disable_shill_autoconnect(self):
     97         self._enable_shill_cellular_autoconnect(False)
     98         yield
     99         self._enable_shill_cellular_autoconnect(True)
    100 
    101 
    102     def __enter__(self):
    103         try:
    104             # Temporarily disable shill autoconnect to cellular service while
    105             # the test environment is setup to prevent a race condition
    106             # between disconnecting the modem in _verify_cellular_service()
    107             # and shill autoconnect.
    108             with self._disable_shill_autoconnect():
    109                 self._nested = contextlib.nested(*self._context_managers)
    110                 self._nested.__enter__()
    111 
    112                 self._initialize_shill()
    113 
    114                 # Perform SIM verification now to ensure that we can enable the
    115                 # modem in _initialize_modem_components(). ModemManager does not
    116                 # allow enabling a modem without a SIM.
    117                 self._verify_sim()
    118                 self._initialize_modem_components()
    119 
    120                 self._setup_logging()
    121 
    122                 self._verify_backchannel()
    123                 self._wait_for_modem_registration()
    124                 self._verify_cellular_service()
    125 
    126                 return self
    127         except (error.TestError, dbus.DBusException,
    128                 shill_proxy.ShillProxyError) as e:
    129             except_type, except_value, except_traceback = sys.exc_info()
    130             lines = traceback.format_exception(except_type, except_value,
    131                                                except_traceback)
    132             logging.error('Error during test initialization:\n' +
    133                           ''.join(lines))
    134             self.__exit__(*sys.exc_info())
    135             raise error.TestError('INIT_ERROR: %s' % str(e))
    136         except:
    137             self.__exit__(*sys.exc_info())
    138             raise
    139 
    140 
    141     def __exit__(self, exception, value, traceback):
    142         if self._nested:
    143             return self._nested.__exit__(exception, value, traceback)
    144         self.shill = None
    145         self.flim = None
    146         self.modem_manager = None
    147         self.modem = None
    148         self.modem_path = None
    149 
    150 
    151     def _get_shill_cellular_device_object(self):
    152         modem_device = self.shill.find_cellular_device_object()
    153         if not modem_device:
    154             raise error.TestError('Cannot find cellular device in shill. '
    155                                   'Is the modem plugged in?')
    156         return modem_device
    157 
    158 
    159     def _enable_modem(self):
    160         modem_device = self._get_shill_cellular_device_object()
    161         try:
    162             modem_device.Enable()
    163         except dbus.DBusException as e:
    164             if (e.get_dbus_name() !=
    165                     shill_proxy.ShillProxy.ERROR_IN_PROGRESS):
    166                 raise
    167 
    168         utils.poll_for_condition(
    169             lambda: modem_device.GetProperties()['Powered'],
    170             exception=error.TestError(
    171                     'Failed to enable modem.'),
    172             timeout=shill_proxy.ShillProxy.DEVICE_ENABLE_DISABLE_TIMEOUT)
    173 
    174 
    175     def _enable_shill_cellular_autoconnect(self, enable):
    176         shill = cellular_proxy.CellularProxy.get_proxy(self.bus)
    177         shill.manager.SetProperty(
    178                 shill_proxy.ShillProxy.
    179                 MANAGER_PROPERTY_NO_AUTOCONNECT_TECHNOLOGIES,
    180                 '' if enable else 'cellular')
    181 
    182 
    183     def _is_unsupported_error(self, e):
    184         return (e.get_dbus_name() ==
    185                 shill_proxy.ShillProxy.ERROR_NOT_SUPPORTED or
    186                 (e.get_dbus_name() ==
    187                  shill_proxy.ShillProxy.ERROR_FAILURE and
    188                  'operation not supported' in e.get_dbus_message()))
    189 
    190 
    191     def _reset_modem(self):
    192         modem_device = self._get_shill_cellular_device_object()
    193         try:
    194             # Cromo/MBIM modems do not support being reset.
    195             self.shill.reset_modem(modem_device, expect_service=False)
    196         except dbus.DBusException as e:
    197             if not self._is_unsupported_error(e):
    198                 raise
    199 
    200 
    201     def _initialize_shill(self):
    202         """Get access to shill."""
    203         # CellularProxy.get_proxy() checks to see if shill is running and
    204         # responding to DBus requests. It returns None if that's not the case.
    205         self.shill = cellular_proxy.CellularProxy.get_proxy(self.bus)
    206         if self.shill is None:
    207             raise error.TestError('Cannot connect to shill, is shill running?')
    208 
    209         # Keep this around to support older tests that haven't migrated to
    210         # cellular_proxy.
    211         self.flim = flimflam.FlimFlam()
    212 
    213 
    214     def _initialize_modem_components(self):
    215         """Reset the modem and get access to modem components."""
    216         # Enable modem first so shill initializes the modemmanager proxies so
    217         # we can call reset on it.
    218         self._enable_modem()
    219         self._reset_modem()
    220 
    221         # PickOneModem() makes sure there's a modem manager and that there is
    222         # one and only one modem.
    223         self.modem_manager, self.modem_path = \
    224                 mm.PickOneModem(self._modem_pattern)
    225         self.modem = self.modem_manager.GetModem(self.modem_path)
    226         if self.modem is None:
    227             raise error.TestError('Cannot get modem object at %s.' %
    228                                   self.modem_path)
    229 
    230 
    231     def _setup_logging(self):
    232         self.shill.set_logging_for_cellular_test()
    233         self.modem_manager.SetDebugLogging()
    234 
    235 
    236     def _verify_sim(self):
    237         """Verify SIM is valid.
    238 
    239         Make sure a SIM in inserted and that it is not locked.
    240 
    241         @raise error.TestError if SIM does not exist or is locked.
    242 
    243         """
    244         modem_device = self._get_shill_cellular_device_object()
    245         props = modem_device.GetProperties()
    246 
    247         # No SIM in CDMA modems.
    248         family = props[
    249                 cellular_proxy.CellularProxy.DEVICE_PROPERTY_TECHNOLOGY_FAMILY]
    250         if (family ==
    251                 cellular_proxy.CellularProxy.
    252                 DEVICE_PROPERTY_TECHNOLOGY_FAMILY_CDMA):
    253             return
    254 
    255         # Make sure there is a SIM.
    256         if not props[cellular_proxy.CellularProxy.DEVICE_PROPERTY_SIM_PRESENT]:
    257             raise error.TestError('There is no SIM in the modem.')
    258 
    259         # Make sure SIM is not locked.
    260         lock_status = props.get(
    261                 cellular_proxy.CellularProxy.DEVICE_PROPERTY_SIM_LOCK_STATUS,
    262                 None)
    263         if lock_status is None:
    264             raise error.TestError('Failed to read SIM lock status.')
    265         locked = lock_status.get(
    266                 cellular_proxy.CellularProxy.PROPERTY_KEY_SIM_LOCK_ENABLED,
    267                 None)
    268         if locked is None:
    269             raise error.TestError('Failed to read SIM LockEnabled status.')
    270         elif locked:
    271             raise error.TestError(
    272                     'SIM is locked, test requires an unlocked SIM.')
    273 
    274 
    275     def _verify_backchannel(self):
    276         """Verify backchannel is on an ethernet device.
    277 
    278         @raise error.TestError if backchannel is not on an ethernet device.
    279 
    280         """
    281         if self._backchannel is None:
    282             return
    283 
    284         if not self._backchannel.is_using_ethernet():
    285             raise error.TestError('An ethernet connection is required between '
    286                                   'the test server and the device under test.')
    287 
    288 
    289     def _wait_for_modem_registration(self):
    290         """Wait for the modem to register with the network.
    291 
    292         @raise error.TestError if modem is not registered.
    293 
    294         """
    295         utils.poll_for_condition(
    296             self.modem.ModemIsRegistered,
    297             exception=error.TestError(
    298                     'Modem failed to register with the network.'),
    299             timeout=cellular_proxy.CellularProxy.SERVICE_REGISTRATION_TIMEOUT)
    300 
    301 
    302     def _verify_cellular_service(self):
    303         """Make sure a cellular service exists.
    304 
    305         The cellular service should not be connected to the network.
    306 
    307         @raise error.TestError if cellular service does not exist or if
    308                 there are multiple cellular services.
    309 
    310         """
    311         service = self.shill.wait_for_cellular_service_object()
    312 
    313         try:
    314             service.Disconnect()
    315         except dbus.DBusException as e:
    316             if (e.get_dbus_name() !=
    317                     cellular_proxy.CellularProxy.ERROR_NOT_CONNECTED):
    318                 raise
    319         success, state, _ = self.shill.wait_for_property_in(
    320                 service,
    321                 cellular_proxy.CellularProxy.SERVICE_PROPERTY_STATE,
    322                 ('idle',),
    323                 cellular_proxy.CellularProxy.SERVICE_DISCONNECT_TIMEOUT)
    324         if not success:
    325             raise error.TestError(
    326                     'Cellular service needs to start in the "idle" state. '
    327                     'Current state is "%s". '
    328                     'Modem disconnect may have failed.' %
    329                     state)
    330 
    331 
    332 class CellularOTATestEnvironment(CellularTestEnvironment):
    333     """Setup and verify cellular over-the-air (OTA) test environment. """
    334     def __init__(self, **kwargs):
    335         super(CellularOTATestEnvironment, self).__init__(**kwargs)
    336 
    337 
    338 class CellularPseudoMMTestEnvironment(CellularTestEnvironment):
    339     """Setup and verify cellular pseudomodem test environment. """
    340     def __init__(self, pseudomm_args=None, **kwargs):
    341         """
    342         @param pseudomm_args: Tuple of arguments passed to the pseudomodem, see
    343                 pseudomodem_context.py for description of each argument in the
    344                 tuple: (flags_map, block_output, bus)
    345 
    346         """
    347         super(CellularPseudoMMTestEnvironment, self).__init__(**kwargs)
    348         self._context_managers.append(
    349                 pseudomodem_context.PseudoModemManagerContext(
    350                         True, bus=self.bus, *pseudomm_args))
    351 
    352 
    353 class CellularWardModemTestEnvironment(CellularTestEnvironment):
    354     """Setup and verify cellular ward modem test environment. """
    355     def __init__(self, wardmodem_modem=None, **kwargs):
    356         """
    357         @param wardmodem_modem: Customized ward modem to use instead of the
    358                 default implementation, see wardmodem.py.
    359 
    360         """
    361         super(CellularWardModemTestEnvironment, self).__init__(**kwargs)
    362         self._context_managers.append(
    363                 wardmodem.WardModemContext(args=['--modem', wardmodem_modem]))
    364