You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
274 lines
8.6 KiB
274 lines
8.6 KiB
# Copyright (c) 2012 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 dbus
|
|
import dbus.service
|
|
import gobject
|
|
import logging
|
|
|
|
import pm_errors
|
|
import pm_constants
|
|
import utils
|
|
|
|
from autotest_lib.client.cros.cellular import mm1_constants
|
|
|
|
class StateMachine(dbus.service.Object):
|
|
"""
|
|
StateMachine is the abstract base class for the complex state machines
|
|
that are involved in the pseudo modem manager.
|
|
|
|
Every state transition is managed by a function that has been mapped to a
|
|
specific modem state. For example, the method that handles the case where
|
|
the modem is in the ENABLED state would look like:
|
|
|
|
def _HandleEnabledState(self):
|
|
# Do stuff.
|
|
|
|
The correct method will be dynamically located and executed by the step
|
|
function according to the dictionary returned by the subclass'
|
|
implementation of StateMachine._GetModemStateFunctionMap.
|
|
|
|
Using the StateMachine in |interactive| mode:
|
|
In interactive mode, the state machine object exposes a dbus object under
|
|
the object path |pm_constants.TESTING_PATH|/|self._GetIsmObjectName()|,
|
|
where |self._GetIsmObjectName()| returns the dbus object name to be used.
|
|
|
|
In this mode, the state machine waits for a dbus method call
|
|
|pm_constants.I_TESTING_ISM|.|Advance| when a state transition is possible
|
|
before actually executing the transition.
|
|
|
|
"""
|
|
def __init__(self, modem):
|
|
super(StateMachine, self).__init__(None, None)
|
|
self._modem = modem
|
|
self._started = False
|
|
self._done = False
|
|
self._interactive = False
|
|
self._trans_func_map = self._GetModemStateFunctionMap()
|
|
|
|
|
|
def __exit__(self):
|
|
self.remove_from_connection()
|
|
|
|
|
|
@property
|
|
def cancelled(self):
|
|
"""
|
|
@returns: True, if the state machine has been cancelled or has
|
|
transitioned to a terminal state. False, otherwise.
|
|
|
|
"""
|
|
return self._done
|
|
|
|
|
|
def Cancel(self):
|
|
"""
|
|
Tells the state machine to stop transitioning to further states.
|
|
|
|
"""
|
|
self._done = True
|
|
|
|
|
|
def EnterInteractiveMode(self, bus):
|
|
"""
|
|
Run this machine in interactive mode.
|
|
|
|
This function must be called before |Start|. In this mode, the machine
|
|
waits for an |Advance| call before each step.
|
|
|
|
@param bus: The bus on which the testing interface must be exported.
|
|
|
|
"""
|
|
if not bus:
|
|
self.warning('Cannot enter interactive mode without a |bus|.')
|
|
return
|
|
|
|
self._interactive = True
|
|
self._ism_object_path = '/'.join([pm_constants.TESTING_PATH,
|
|
self._GetIsmObjectName()])
|
|
self.add_to_connection(bus, self._ism_object_path)
|
|
self._interactive = True
|
|
self._waiting_for_advance = False
|
|
logging.info('Running state machine in interactive mode')
|
|
logging.info('Exported test object at %s', self._ism_object_path)
|
|
|
|
|
|
def Start(self):
|
|
""" Start the state machine. """
|
|
self.Step()
|
|
|
|
|
|
@utils.log_dbus_method()
|
|
@dbus.service.method(pm_constants.I_TESTING_ISM, out_signature='b')
|
|
def Advance(self):
|
|
"""
|
|
Advance a step on a state machine running in interactive mode.
|
|
|
|
@returns: True if the state machine was advanced. False otherwise.
|
|
@raises: TestError if called on a non-interactive state machine.
|
|
|
|
"""
|
|
if not self._interactive:
|
|
raise pm_errors.TestError(
|
|
'Can not advance a non-interactive state machine')
|
|
|
|
if not self._waiting_for_advance:
|
|
logging.warning('%s received an unexpected advance request',
|
|
self._GetIsmObjectName())
|
|
return False
|
|
logging.info('%s state machine advancing', self._GetIsmObjectName())
|
|
self._waiting_for_advance = False
|
|
if not self._next_transition(self):
|
|
self._done = True
|
|
self._ScheduleNextStep()
|
|
return True
|
|
|
|
|
|
@dbus.service.signal(pm_constants.I_TESTING_ISM)
|
|
def Waiting(self):
|
|
"""
|
|
Signal sent out by an interactive machine when it is waiting for remote
|
|
dbus call on the |Advance| function.
|
|
|
|
"""
|
|
logging.info('%s state machine waiting', self._GetIsmObjectName())
|
|
|
|
|
|
@utils.log_dbus_method()
|
|
@dbus.service.method(pm_constants.I_TESTING_ISM, out_signature='b')
|
|
def IsWaiting(self):
|
|
"""
|
|
Determine whether the state machine is waiting for user action.
|
|
|
|
@returns: True if machine is waiting for |Advance| call.
|
|
|
|
"""
|
|
return self._waiting_for_advance
|
|
|
|
|
|
def Step(self):
|
|
"""
|
|
Executes the next corresponding state transition based on the modem
|
|
state.
|
|
|
|
"""
|
|
logging.info('StateMachine: Step')
|
|
if self._done:
|
|
logging.info('StateMachine: Terminating.')
|
|
return
|
|
|
|
if not self._started:
|
|
if not self._ShouldStartStateMachine():
|
|
logging.info('StateMachine cannot start.')
|
|
return
|
|
self._started = True
|
|
|
|
state = self._GetCurrentState()
|
|
func = self._trans_func_map.get(state, self._GetDefaultHandler())
|
|
if not self._interactive:
|
|
if func and func(self):
|
|
self._ScheduleNextStep()
|
|
else:
|
|
self._done = True
|
|
return
|
|
|
|
assert not self._waiting_for_advance
|
|
if func:
|
|
self._next_transition = func
|
|
self._waiting_for_advance = True
|
|
self.Waiting() # Wait for user to |Advance| the machine.
|
|
else:
|
|
self._done = True
|
|
|
|
|
|
def _ScheduleNextStep(self):
|
|
"""
|
|
Schedules the next state transition to execute on the idle loop.
|
|
subclasses can override this method to implement custom logic, such as
|
|
delays.
|
|
|
|
"""
|
|
gobject.idle_add(StateMachine.Step, self)
|
|
|
|
|
|
def _GetIsmObjectName(self):
|
|
"""
|
|
The name of the dbus object exposed by this object with |I_TESTING_ISM|
|
|
interface.
|
|
|
|
By default, this is the name of the most concrete class of the object.
|
|
|
|
"""
|
|
return self.__class__.__name__
|
|
|
|
|
|
def _GetDefaultHandler(self):
|
|
"""
|
|
Returns the function to handle a modem state, for which the value
|
|
returned by StateMachine._GetModemStateFunctionMap is None. The
|
|
returned function's signature must match:
|
|
|
|
StateMachine -> Boolean
|
|
|
|
This function by default returns None. If no function exists to handle
|
|
a modem state, the default behavior is to terminate the state machine.
|
|
|
|
"""
|
|
return None
|
|
|
|
|
|
def _GetModemStateFunctionMap(self):
|
|
"""
|
|
Returns a mapping from modem states to corresponding transition
|
|
functions to execute. The returned function's signature must match:
|
|
|
|
StateMachine -> Boolean
|
|
|
|
The first argument to the function is a state machine, which will
|
|
typically be passed a value of |self|. The return value, if True,
|
|
indicates that the state machine should keep executing further state
|
|
transitions. A return value of False indicates that the state machine
|
|
will transition to a terminal state.
|
|
|
|
This method must be implemented by a subclass. Subclasses can further
|
|
override this method to provide custom functionality.
|
|
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
|
|
def _ShouldStartStateMachine(self):
|
|
"""
|
|
This method will be called when the state machine is in a starting
|
|
state. This method should return True, if the state machine can
|
|
successfully begin its state transitions, False if it should not
|
|
proceed. This method can also raise an exception in the failure case.
|
|
|
|
In the success case, this method should also execute any necessary
|
|
initialization steps.
|
|
|
|
This method must be implemented by a subclass. Subclasses can
|
|
further override this method to provide custom functionality.
|
|
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
|
|
def _GetCurrentState(self):
|
|
"""
|
|
Get the current state of the state machine.
|
|
|
|
This method is called to get the current state of the machine when
|
|
deciding what the next transition should be.
|
|
By default, the state machines are tied to the modem state, and this
|
|
function simply returns the modem state.
|
|
|
|
Subclasses can override this function to use custom states in the state
|
|
machine.
|
|
|
|
@returns: The modem state.
|
|
|
|
"""
|
|
return self._modem.Get(mm1_constants.I_MODEM, 'State')
|