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