424 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			424 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
| # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
 | |
| # Use of this source code is governed by a BSD-style license that can be
 | |
| # found in the LICENSE file.
 | |
| 
 | |
| import contextlib
 | |
| import dbus
 | |
| import logging
 | |
| import random
 | |
| import time
 | |
| 
 | |
| from autotest_lib.client.bin import test, utils
 | |
| from autotest_lib.client.common_lib import error
 | |
| from autotest_lib.client.common_lib.cros import chrome
 | |
| from autotest_lib.client.cros.cellular import cellular
 | |
| from autotest_lib.client.cros.networking import cellular_proxy
 | |
| from autotest_lib.client.cros.networking import shill_context
 | |
| from autotest_lib.client.cros.networking import shill_proxy
 | |
| 
 | |
| # Number of seconds we wait for the cellular service to perform an action.
 | |
| DEVICE_TIMEOUT=45
 | |
| SERVICE_TIMEOUT=75
 | |
| 
 | |
| # Number of times and seconds between modem state checks to ensure that the
 | |
| # modem is not in a temporary transition state.
 | |
| NUM_MODEM_STATE_CHECKS=2
 | |
| MODEM_STATE_CHECK_PERIOD_SECONDS=5
 | |
| 
 | |
| # Number of seconds to sleep after a connect request in slow-connect mode.
 | |
| SLOW_CONNECT_WAIT_SECONDS=20
 | |
| 
 | |
| # Number of seconds to sleep after a disable request in slow-disable mode.
 | |
| SLOW_DISABLE_WAIT_SECONDS=2
 | |
| 
 | |
| 
 | |
| class TechnologyCommands():
 | |
|     """Control the modem mostly using shill Technology interfaces."""
 | |
|     def __init__(self, shill, command_delegate, slow_disable):
 | |
|         self.shill = shill
 | |
|         self.command_delegate = command_delegate
 | |
|         self.slow_disable = slow_disable
 | |
| 
 | |
|     def Enable(self):
 | |
|         self.shill.manager.EnableTechnology(
 | |
|                 shill_proxy.ShillProxy.TECHNOLOGY_CELLULAR)
 | |
| 
 | |
|     def Disable(self):
 | |
|         self.shill.manager.DisableTechnology(
 | |
|                 shill_proxy.ShillProxy.TECHNOLOGY_CELLULAR)
 | |
|         if self.slow_disable:
 | |
|             time.sleep(SLOW_DISABLE_WAIT_SECONDS)
 | |
|             #TODO(pholla): Make ModemManager return a response only after
 | |
|             #QC Modem is confirmed to be idle (b/160446543)
 | |
| 
 | |
|     def Connect(self, **kwargs):
 | |
|         self.command_delegate.Connect(**kwargs)
 | |
| 
 | |
|     def Disconnect(self):
 | |
|         return self.command_delegate.Disconnect()
 | |
| 
 | |
|     def __str__(self):
 | |
|         return 'Technology Commands'
 | |
| 
 | |
| 
 | |
| class ModemCommands():
 | |
|     """Control the modem using modem manager DBUS interfaces."""
 | |
|     def __init__(self, modem, slow_connect, slow_disable):
 | |
|         self.modem = modem
 | |
|         self.slow_connect = slow_connect
 | |
|         self.slow_disable = slow_disable
 | |
| 
 | |
|     def Enable(self):
 | |
|         self.modem.Enable(True)
 | |
| 
 | |
|     def Disable(self):
 | |
|         self.modem.Enable(False)
 | |
|         if self.slow_disable:
 | |
|             time.sleep(SLOW_DISABLE_WAIT_SECONDS)
 | |
|             #TODO(pholla): Make ModemManager return a response only after
 | |
|             #QC Modem is confirmed to be idle (b/160446543)
 | |
| 
 | |
|     def Connect(self, simple_connect_props):
 | |
|         logging.debug('Connecting with properties: %r' % simple_connect_props)
 | |
|         self.modem.Connect(simple_connect_props)
 | |
|         if self.slow_connect:
 | |
|             time.sleep(SLOW_CONNECT_WAIT_SECONDS)
 | |
| 
 | |
|     def Disconnect(self):
 | |
|         """
 | |
|         Disconnect Modem.
 | |
| 
 | |
|         Returns:
 | |
|             True - to indicate that shill may autoconnect again.
 | |
|         """
 | |
|         try:
 | |
|             self.modem.Disconnect()
 | |
|         except dbus.DBusException as e:
 | |
|             if (e.get_dbus_name() !=
 | |
|                     'org.chromium.ModemManager.Error.OperationInitiated'):
 | |
|                 raise e
 | |
|         return True
 | |
| 
 | |
|     def __str__(self):
 | |
|         return 'Modem Commands'
 | |
| 
 | |
| 
 | |
| class DeviceCommands():
 | |
|     """Control the modem using shill device interfaces."""
 | |
|     def __init__(self, shill, device, slow_connect, slow_disable):
 | |
|         self.shill = shill
 | |
|         self.device = device
 | |
|         self.slow_connect = slow_connect
 | |
|         self.slow_disable = slow_disable
 | |
|         self.service = None
 | |
| 
 | |
|     def GetService(self):
 | |
|         service = self.shill.find_cellular_service_object()
 | |
|         if not service:
 | |
|             raise error.TestFail(
 | |
|                 'Service failed to appear when using device commands.')
 | |
|         return service
 | |
| 
 | |
|     def Enable(self):
 | |
|         self.device.Enable(timeout=DEVICE_TIMEOUT)
 | |
| 
 | |
|     def Disable(self):
 | |
|         self.service = None
 | |
|         self.device.Disable(timeout=DEVICE_TIMEOUT)
 | |
|         if self.slow_disable:
 | |
|             time.sleep(SLOW_DISABLE_WAIT_SECONDS)
 | |
|             #TODO(pholla): Make ModemManager return a response only after
 | |
|             #QC Modem is confirmed to be idle (b/160446543)
 | |
| 
 | |
|     def Connect(self, **kwargs):
 | |
|         self.GetService().Connect()
 | |
|         if self.slow_connect:
 | |
|             time.sleep(SLOW_CONNECT_WAIT_SECONDS)
 | |
| 
 | |
|     def Disconnect(self):
 | |
|         """
 | |
|         Disconnect Modem.
 | |
| 
 | |
|         Returns:
 | |
|             False - to indicate that shill may not autoconnect again.
 | |
|         """
 | |
|         self.GetService().Disconnect()
 | |
|         return False
 | |
| 
 | |
|     def __str__(self):
 | |
|         return 'Device Commands'
 | |
| 
 | |
| 
 | |
| class MixedRandomCommands():
 | |
|     """Control the modem using a mixture of commands on device, modems, etc."""
 | |
|     def __init__(self, commands_list):
 | |
|         self.commands_list = commands_list
 | |
| 
 | |
|     def PickRandomCommands(self):
 | |
|         return self.commands_list[random.randrange(len(self.commands_list))]
 | |
| 
 | |
|     def Enable(self):
 | |
|         cmds = self.PickRandomCommands()
 | |
|         logging.info('Enable with %s' % cmds)
 | |
|         cmds.Enable()
 | |
| 
 | |
|     def Disable(self):
 | |
|         cmds = self.PickRandomCommands()
 | |
|         logging.info('Disable with %s' % cmds)
 | |
|         cmds.Disable()
 | |
| 
 | |
|     def Connect(self, **kwargs):
 | |
|         cmds = self.PickRandomCommands()
 | |
|         logging.info('Connect with %s' % cmds)
 | |
|         cmds.Connect(**kwargs)
 | |
| 
 | |
|     def Disconnect(self):
 | |
|         cmds = self.PickRandomCommands()
 | |
|         logging.info('Disconnect with %s' % cmds)
 | |
|         return cmds.Disconnect()
 | |
| 
 | |
|     def __str__(self):
 | |
|         return 'Mixed Commands'
 | |
| 
 | |
| 
 | |
| class cellular_ModemControl(test.test):
 | |
|     version = 1
 | |
| 
 | |
|     def CompareModemPowerState(self, modem, expected_state):
 | |
|         """Compare modem manager power state of a modem to an expected state."""
 | |
|         return modem.IsEnabled() == expected_state
 | |
| 
 | |
|     def CompareDevicePowerState(self, device, expected_state):
 | |
|         """Compare the shill device power state to an expected state."""
 | |
|         state = self.test_env.shill.get_dbus_property(
 | |
|                 device, shill_proxy.ShillProxy.DEVICE_PROPERTY_POWERED)
 | |
|         logging.info('Device Enabled = %s' % state)
 | |
|         return state == expected_state
 | |
| 
 | |
|     def CompareServiceState(self, service, expected_states):
 | |
|         """Compare the shill service state to a set of expected states."""
 | |
|         if not service:
 | |
|             logging.info('Service not found.')
 | |
|             return False
 | |
| 
 | |
|         state = self.test_env.shill.get_dbus_property(
 | |
|                 service, shill_proxy.ShillProxy.SERVICE_PROPERTY_STATE)
 | |
|         logging.info('Service State = %s' % state)
 | |
|         return state in expected_states
 | |
| 
 | |
|     def EnsureNotConnectingOrDisconnecting(self):
 | |
|         """
 | |
|         Ensure modem is not connecting or disconnecting.
 | |
| 
 | |
|         Raises:
 | |
|             error.TestFail if it timed out waiting for the modem to finish
 | |
|             connecting or disconnecting.
 | |
|         """
 | |
|         # Shill retries a failed connect attempt with a different APN so
 | |
|         # check a few times to ensure the modem is not in between connect
 | |
|         # attempts.
 | |
|         for _ in range(NUM_MODEM_STATE_CHECKS):
 | |
|             utils.poll_for_condition(
 | |
|                 lambda: not self.test_env.modem.IsConnectingOrDisconnecting(),
 | |
|                 error.TestFail('Timed out waiting for modem to finish ' +
 | |
|                                'connecting or disconnecting.'),
 | |
|                 timeout=SERVICE_TIMEOUT)
 | |
|             time.sleep(MODEM_STATE_CHECK_PERIOD_SECONDS)
 | |
| 
 | |
|     def EnsureDisabled(self):
 | |
|         """
 | |
|         Ensure modem disabled, device powered off, and no service.
 | |
| 
 | |
|         Raises:
 | |
|             error.TestFail if the states are not consistent.
 | |
|         """
 | |
|         utils.poll_for_condition(
 | |
|             lambda: self.CompareModemPowerState(self.test_env.modem, False),
 | |
|             error.TestFail('Modem failed to enter state Disabled.'))
 | |
|         utils.poll_for_condition(
 | |
|             lambda: self.CompareDevicePowerState(self.device, False),
 | |
|             error.TestFail('Device failed to enter state Powered=False.'))
 | |
|         utils.poll_for_condition(
 | |
|             lambda: not self.test_env.shill.find_cellular_service_object(),
 | |
|             error.TestFail('Service should not be available.'),
 | |
|             timeout=SERVICE_TIMEOUT)
 | |
| 
 | |
|     def EnsureEnabled(self, check_idle):
 | |
|         """
 | |
|         Ensure modem enabled, device powered and service exists.
 | |
| 
 | |
|         Args:
 | |
|             check_idle: if True, then ensure that the service is idle
 | |
|                         (i.e. not connected) otherwise ignore the
 | |
|                         service state
 | |
| 
 | |
|         Raises:
 | |
|             error.TestFail if the states are not consistent.
 | |
|         """
 | |
|         utils.poll_for_condition(
 | |
|             lambda: self.CompareModemPowerState(self.test_env.modem, True),
 | |
|             error.TestFail('Modem failed to enter state Enabled'))
 | |
|         utils.poll_for_condition(
 | |
|             lambda: self.CompareDevicePowerState(self.device, True),
 | |
|             error.TestFail('Device failed to enter state Powered=True.'),
 | |
|             timeout=30)
 | |
| 
 | |
|         service = self.test_env.shill.wait_for_cellular_service_object()
 | |
|         if check_idle:
 | |
|             utils.poll_for_condition(
 | |
|                 lambda: self.CompareServiceState(service, ['idle']),
 | |
|                 error.TestFail('Service failed to enter idle state.'),
 | |
|                 timeout=SERVICE_TIMEOUT)
 | |
| 
 | |
|     def EnsureConnected(self):
 | |
|         """
 | |
|         Ensure modem connected, device powered on, service connected.
 | |
| 
 | |
|         Raises:
 | |
|             error.TestFail if the states are not consistent.
 | |
|         """
 | |
|         self.EnsureEnabled(check_idle=False)
 | |
|         utils.poll_for_condition(
 | |
|             lambda: self.CompareServiceState(
 | |
|                     self.test_env.shill.find_cellular_service_object(),
 | |
|                     ['ready', 'portal', 'online']),
 | |
|             error.TestFail('Service failed to connect.'),
 | |
|             timeout=SERVICE_TIMEOUT)
 | |
| 
 | |
| 
 | |
|     def TestCommands(self, commands):
 | |
|         """
 | |
|         Manipulate the modem using modem, device or technology commands.
 | |
| 
 | |
|         Changes the state of the modem in various ways including
 | |
|         disable while connected and then verifies the state of the
 | |
|         modem manager and shill.
 | |
| 
 | |
|         Raises:
 | |
|             error.TestFail if the states are not consistent.
 | |
| 
 | |
|         """
 | |
|         logging.info('Testing using %s' % commands)
 | |
| 
 | |
|         logging.info('Enabling')
 | |
|         commands.Enable()
 | |
|         self.EnsureEnabled(check_idle=not self.autoconnect)
 | |
| 
 | |
|         technology_family = self.test_env.modem.GetCurrentTechnologyFamily()
 | |
|         if technology_family == cellular.TechnologyFamily.CDMA:
 | |
|             simple_connect_props = {'number': r'#777'}
 | |
|         else:
 | |
|             simple_connect_props = {'number': r'#777', 'apn': self.FindAPN()}
 | |
| 
 | |
|         # Icera modems behave weirdly if we cancel the operation while the
 | |
|         # modem is connecting. Work around the issue by waiting until the
 | |
|         # connect operation completes.
 | |
|         # TODO(benchan): Remove this workaround once the issue is addressed
 | |
|         # on the modem side.
 | |
|         self.EnsureNotConnectingOrDisconnecting()
 | |
| 
 | |
|         logging.info('Disabling')
 | |
|         commands.Disable()
 | |
|         self.EnsureDisabled()
 | |
| 
 | |
|         logging.info('Enabling again')
 | |
|         commands.Enable()
 | |
|         self.EnsureEnabled(check_idle=not self.autoconnect)
 | |
| 
 | |
|         if not self.autoconnect:
 | |
|             logging.info('Connecting')
 | |
|             commands.Connect(simple_connect_props=simple_connect_props)
 | |
|         else:
 | |
|             logging.info('Expecting AutoConnect to connect')
 | |
|         self.EnsureConnected()
 | |
| 
 | |
|         logging.info('Disconnecting')
 | |
|         will_autoreconnect = commands.Disconnect()
 | |
| 
 | |
|         if not (self.autoconnect and will_autoreconnect):
 | |
|             # Icera modems behave weirdly if we cancel the operation while the
 | |
|             # modem is disconnecting. Work around the issue by waiting until
 | |
|             # the disconnect operation completes.
 | |
|             # TODO(benchan): Remove this workaround once the issue is addressed
 | |
|             # on the modem side.
 | |
|             self.EnsureNotConnectingOrDisconnecting()
 | |
| 
 | |
|             self.EnsureEnabled(check_idle=True)
 | |
|             logging.info('Connecting manually, since AutoConnect was on')
 | |
|             commands.Connect(simple_connect_props=simple_connect_props)
 | |
|         self.EnsureConnected()
 | |
| 
 | |
|         logging.info('Disabling')
 | |
|         commands.Disable()
 | |
|         self.EnsureDisabled()
 | |
| 
 | |
|     def FindAPN(self):
 | |
|         default = 'None'
 | |
|         service = self.test_env.shill.find_cellular_service_object()
 | |
|         last_good_apn = self.test_env.shill.get_dbus_property(
 | |
|                 service,
 | |
|                 cellular_proxy.CellularProxy.SERVICE_PROPERTY_LAST_GOOD_APN)
 | |
|         if not last_good_apn:
 | |
|             return default
 | |
|         return last_good_apn.get(
 | |
|                 cellular_proxy.CellularProxy.APN_INFO_PROPERTY_APN, default)
 | |
| 
 | |
|     def run_once(self, test_env, autoconnect, mixed_iterations=2,
 | |
|                  slow_connect=False, slow_disable=False):
 | |
|         self.test_env = test_env
 | |
|         self.autoconnect = autoconnect
 | |
| 
 | |
|         with test_env:
 | |
|             self.device = self.test_env.shill.find_cellular_device_object()
 | |
| 
 | |
|             modem_commands = ModemCommands(self.test_env.modem,
 | |
|                                            slow_connect,
 | |
|                                            slow_disable)
 | |
|             technology_commands = TechnologyCommands(self.test_env.shill,
 | |
|                                                      modem_commands,
 | |
|                                                      slow_disable)
 | |
|             device_commands = DeviceCommands(self.test_env.shill,
 | |
|                                              self.device,
 | |
|                                              slow_connect,
 | |
|                                              slow_disable)
 | |
| 
 | |
|             # shill disables autoconnect on any cellular service before a user
 | |
|             # logs in (CL:851267). To test the autoconnect scenario, we need a
 | |
|             # user session to run the test.
 | |
|             chrome_context = chrome.Chrome()
 | |
| 
 | |
|             # Set up the autoconnect context after starting a user session so
 | |
|             # that we ensure the autoconnect property is set on the cellular
 | |
|             # service that may be in the user profile.
 | |
|             autoconnect_context = shill_context.ServiceAutoConnectContext(
 | |
|                     self.test_env.shill.wait_for_cellular_service_object,
 | |
|                     self.autoconnect)
 | |
| 
 | |
|             with contextlib.nested(chrome_context, autoconnect_context):
 | |
|                 # Start with cellular disabled.
 | |
|                 self.test_env.shill.manager.DisableTechnology(
 | |
|                         shill_proxy.ShillProxy.TECHNOLOGY_CELLULAR)
 | |
|                 self.EnsureDisabled()
 | |
| 
 | |
|                 # Run the device commands test first to make sure we have
 | |
|                 # a valid APN needed to connect using the modem commands.
 | |
|                 self.TestCommands(device_commands)
 | |
|                 self.TestCommands(technology_commands)
 | |
|                 self.TestCommands(modem_commands)
 | |
| 
 | |
|                 # Run several times using commands mixed from each type
 | |
|                 mixed = MixedRandomCommands([modem_commands,
 | |
|                                              technology_commands,
 | |
|                                              device_commands])
 | |
|                 for _ in range(mixed_iterations):
 | |
|                     self.TestCommands(mixed)
 | |
| 
 | |
|                 # Ensure cellular is re-enabled in order to restore AutoConnect
 | |
|                 # settings when ServiceAutoConnectContext exits.
 | |
|                 # TODO(benchan): Refactor this logic into
 | |
|                 # ServiceAutoConnectContext and update other users of
 | |
|                 # ServiceAutoConnectContext.
 | |
|                 self.test_env.shill.manager.EnableTechnology(
 | |
|                         shill_proxy.ShillProxy.TECHNOLOGY_CELLULAR)
 | |
|                 self.EnsureEnabled(check_idle=False)
 |