Merge "Add concept of stable states to the state machine"
This commit is contained in:
commit
20c79de175
@ -17,6 +17,9 @@
|
||||
|
||||
This work will be turned into a library.
|
||||
See https://github.com/harlowja/automaton
|
||||
|
||||
This is being used in the implementation of:
|
||||
http://specs.openstack.org/openstack/ironic-specs/specs/kilo/new-ironic-state-machine.html
|
||||
"""
|
||||
|
||||
from collections import OrderedDict # noqa
|
||||
@ -71,7 +74,7 @@ class FSM(object):
|
||||
return self._states[self._current.name]['terminal']
|
||||
|
||||
def add_state(self, state, on_enter=None, on_exit=None,
|
||||
target=None, terminal=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
|
||||
@ -79,6 +82,13 @@ class FSM(object):
|
||||
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)
|
||||
@ -91,6 +101,9 @@ class FSM(object):
|
||||
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),
|
||||
@ -98,6 +111,7 @@ class FSM(object):
|
||||
'on_enter': on_enter,
|
||||
'on_exit': on_exit,
|
||||
'target': target,
|
||||
'stable': stable,
|
||||
}
|
||||
self._transitions[state] = OrderedDict()
|
||||
|
||||
|
@ -180,10 +180,10 @@ watchers['on_enter'] = on_enter
|
||||
machine = fsm.FSM()
|
||||
|
||||
# Add stable states
|
||||
machine.add_state(MANAGEABLE, **watchers)
|
||||
machine.add_state(AVAILABLE, **watchers)
|
||||
machine.add_state(ACTIVE, **watchers)
|
||||
machine.add_state(ERROR, **watchers)
|
||||
machine.add_state(MANAGEABLE, stable=True, **watchers)
|
||||
machine.add_state(AVAILABLE, stable=True, **watchers)
|
||||
machine.add_state(ACTIVE, stable=True, **watchers)
|
||||
machine.add_state(ERROR, stable=True, **watchers)
|
||||
|
||||
# From MANAGEABLE, a node may be made available
|
||||
# TODO(deva): add CLEAN* states to this path
|
||||
|
@ -115,3 +115,23 @@ class FSMTest(base.TestCase):
|
||||
m.add_state('broken')
|
||||
self.assertRaises(ValueError, m.add_state, 'b', on_enter=2)
|
||||
self.assertRaises(ValueError, m.add_state, 'b', on_exit=2)
|
||||
|
||||
def test_invalid_target_state(self):
|
||||
# Test to verify that adding a state which has a 'target' state that
|
||||
# does not exist will raise an exception
|
||||
self.assertRaises(excp.InvalidState,
|
||||
self.jumper.add_state, 'jump', target='unknown')
|
||||
|
||||
def test_target_state_not_stable(self):
|
||||
# Test to verify that adding a state that has a 'target' state which is
|
||||
# not a 'stable' state will raise an exception
|
||||
self.assertRaises(excp.InvalidState,
|
||||
self.jumper.add_state, 'jump', target='down')
|
||||
|
||||
def test_target_state_stable(self):
|
||||
# Test to verify that adding a new state with a 'target' state pointing
|
||||
# to a 'stable' state does not raise an exception
|
||||
m = fsm.FSM('working')
|
||||
m.add_state('working', stable=True)
|
||||
m.add_state('foo', target='working')
|
||||
m.initialize()
|
||||
|
Loading…
x
Reference in New Issue
Block a user