def process_event(self, event): """Trigger a state change in response to the provided event.""" current = self._current if current is None: raise excp.InvalidState( _("Can only process events after" " being initialized (not before)")) if self._states[current.name]['terminal']: raise excp.InvalidState( _("Can not transition from terminal " "state '%(state)s' on event '%(event)s'") % { 'state': current.name, 'event': event }) if event not in self._transitions[current.name]: raise excp.InvalidState( _("Can not transition from state '%(state)s' on " "event '%(event)s' (no defined transition)") % { 'state': current.name, 'event': event }) replacement = self._transitions[current.name][event] if current.on_exit is not None: current.on_exit(current.name, event) if replacement.on_enter is not None: replacement.on_enter(replacement.name, event) self._current = replacement # clear _target if we've reached it if (self._target_state is not None and self._target_state == replacement.name): self._target_state = None # if new state has a different target, update the target if self._states[replacement.name]['target'] is not None: self._target_state = self._states[replacement.name]['target']
def verify_node_for_deallocation(node, allocation): """Verify that allocation can be removed for the node. :param node: a node object :param allocation: an allocation object associated with the node """ if node.maintenance: # Allocations can always be removed in the maintenance mode. return if (node.target_provision_state and node.provision_state not in states.UPDATE_ALLOWED_STATES): msg = (_("Cannot remove allocation %(uuid)s for node %(node)s, " "because the node is in state %(state)s where updates are " "not allowed (and maintenance mode is off)") % { 'node': node.uuid, 'uuid': allocation.uuid, 'state': node.provision_state }) raise exception.InvalidState(msg) if node.provision_state == states.ACTIVE: msg = (_("Cannot remove allocation %(uuid)s for node %(node)s, " "because the node is active (and maintenance mode is off)") % { 'node': node.uuid, 'uuid': allocation.uuid }) raise exception.InvalidState(msg)
def add_state(self, state, on_enter=None, on_exit=None, target=None, terminal=None, stable=False): """Adds a given state to the state machine. :param stable: Use this to specify that this state is a stable/passive state. A state must have been previously defined as 'stable' before it can be used as a 'target' :param target: The target state for 'state' to go to. Before a state can be used as a target it must have been previously added and specified as 'stable' Further arguments are interpreted as for parent method ``add_state``. """ if target is not None: if target not in self._states: raise excp.InvalidState( _("Target state '%s' does not exist") % target) if not self._states[target]['stable']: raise excp.InvalidState( _("Target state '%s' is not a 'stable' state") % target) super(FSM, self).add_state(state, terminal=terminal, on_enter=on_enter, on_exit=on_exit) self._states[state].update({ 'stable': stable, 'target': target, })
def add_state(self, state, on_enter=None, on_exit=None, target=None, terminal=None): """Adds a given state to the state machine. The on_enter and on_exit callbacks, if provided will be expected to take two positional parameters, these being the state being exited (for on_exit) or the state being entered (for on_enter) and a second parameter which is the event that is being processed that caused the state transition. """ if state in self._states: raise excp.Duplicate(_("State '%s' already defined") % state) if on_enter is not None: if not six.callable(on_enter): raise ValueError(_("On enter callback must be callable")) if on_exit is not None: if not six.callable(on_exit): raise ValueError(_("On exit callback must be callable")) if target is not None and target not in self._states: raise excp.InvalidState( _("Target state '%s' does not exist") % target) self._states[state] = { 'terminal': bool(terminal), 'reactions': {}, 'on_enter': on_enter, 'on_exit': on_exit, 'target': target, } self._transitions[state] = OrderedDict()
def test_process_event_fsm_raises(self): self.task.process_event = task_manager.TaskManager.process_event self.fsm.process_event.side_effect = exception.InvalidState('test') self.assertRaises(exception.InvalidState, self.task.process_event, self.task, 'fake') self.assertEqual(0, self.task.spawn_after.call_count) self.assertFalse(self.task.node.save.called)
def _validate_target_state(self, target): """Validate the target state. A target state must be a valid state that is 'stable'. :param target: The target state :raises: exception.InvalidState if it is an invalid target state """ if target is None: return if target not in self._states: raise excp.InvalidState( _("Target state '%s' does not exist") % target) if not self.is_stable(target): raise excp.InvalidState( _("Target state '%s' is not a 'stable' state") % target)
def add_state(self, state, on_enter=None, on_exit=None, target=None, terminal=None, stable=False): """Adds a given state to the state machine. The on_enter and on_exit callbacks, if provided will be expected to take two positional parameters, these being the state being exited (for on_exit) or the state being entered (for on_enter) and a second parameter which is the event that is being processed that caused the state transition. :param stable: Use this to specify that this state is a stable/passive state. A state must have been previously defined as 'stable' before it can be used as a 'target' :param target: The target state for 'state' to go to. Before a state can be used as a target it must have been previously added and specified as 'stable' """ if state in self._states: raise excp.Duplicate(_("State '%s' already defined") % state) if on_enter is not None: if not six.callable(on_enter): raise ValueError(_("On enter callback must be callable")) if on_exit is not None: if not six.callable(on_exit): raise ValueError(_("On exit callback must be callable")) if target is not None and target not in self._states: raise excp.InvalidState( _("Target state '%s' does not exist") % target) if target is not None and not self._states[target]['stable']: raise excp.InvalidState( _("Target state '%s' is not a 'stable' state") % target) self._states[state] = { 'terminal': bool(terminal), 'reactions': {}, 'on_enter': on_enter, 'on_exit': on_exit, 'target': target, 'stable': stable, } self._transitions[state] = OrderedDict()
def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except (automaton_exceptions.InvalidState, automaton_exceptions.NotInitialized, automaton_exceptions.FrozenMachine, automaton_exceptions.NotFound) as e: raise excp.InvalidState(str(e)) except automaton_exceptions.Duplicate as e: raise excp.Duplicate(str(e))
def is_stable(self, state): """Is the state stable? :param state: the state of interest :raises: InvalidState if the state is invalid :returns: True if it is a stable state; False otherwise """ try: return self._states[state]['stable'] except KeyError: raise excp.InvalidState(_("State '%s' does not exist") % state)
def initialize(self, state=None): """Sets up the state machine. sets the current state to the specified state, or start_state if no state was specified.. """ if state is None: state = self._start_state if state not in self._states: raise excp.NotFound( _("Can not start from an undefined" " state '%s'") % (state)) if self._states[state]['terminal']: raise excp.InvalidState( _("Can not start from a terminal" " state '%s'") % (state)) self._current = _Jump(state, None, None)