ironic/ironic/common/fsm.py
Riccardo Pittau 78c121a5d7 Stop using six library
Since we've dropped support for Python 2.7, it's time to look at
the bright future that Python 3.x will bring and stop forcing
compatibility with older versions.
This patch removes the six library from requirements, not
looking back.

Change-Id: Ib546f16965475c32b2f8caabd560e2c7d382ac5a
2019-12-23 09:38:25 +01:00

159 lines
5.9 KiB
Python

# Copyright (C) 2014 Yahoo! Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import functools
from automaton import exceptions as automaton_exceptions
from automaton import machines
"""State machine modelling.
This is being used in the implementation of:
http://specs.openstack.org/openstack/ironic-specs/specs/kilo/new-ironic-state-machine.html
"""
from ironic.common import exception as excp
from ironic.common.i18n import _
def _translate_excp(func):
"""Decorator to translate automaton exceptions into ironic exceptions."""
@functools.wraps(func)
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))
return wrapper
class FSM(machines.FiniteMachine):
"""An ironic state-machine class with some ironic specific additions."""
def __init__(self):
super(FSM, self).__init__()
self._target_state = None
# For now make these raise ironic state machine exceptions until
# a later period where these should(?) be using the raised automaton
# exceptions directly.
add_transition = _translate_excp(machines.FiniteMachine.add_transition)
@property
def target_state(self):
return self._target_state
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)
@_translate_excp
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``.
"""
self._validate_target_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 _post_process_event(self, event, result):
# Clear '_target_state' if we've reached it
if (self._target_state is not None
and self._target_state == self._current.name):
self._target_state = None
# If new state has a different target, update the '_target_state'
if self._states[self._current.name]['target'] is not None:
self._target_state = self._states[self._current.name]['target']
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)
@_translate_excp
def initialize(self, start_state=None, target_state=None):
"""Initialize the FSM.
:param start_state: the FSM is initialized to start from this state
:param target_state: if specified, the FSM is initialized to this
target state. Otherwise use the default target
state
"""
super(FSM, self).initialize(start_state=start_state)
current_state = self._current.name
self._validate_target_state(target_state)
self._target_state = (target_state
or self._states[current_state]['target'])
@_translate_excp
def process_event(self, event, target_state=None):
"""process the event.
:param event: the event to be processed
:param target_state: if specified, the 'final' target state for the
event. Otherwise, use the default target state
"""
super(FSM, self).process_event(event)
if target_state:
# NOTE(rloo): _post_process_event() was invoked at the end of
# the above super().process_event() call. At this
# point, the default target state is being used but
# we want to use the specified state instead.
self._validate_target_state(target_state)
self._target_state = target_state