Separate states code from states constants

Currently, the nova virt driver for ironic has a file containing nothing
but our states as constants. A recent bug was caused, in part, by these
not being properly updated. The goal here is to move ironic state
machine code and constants into separate files -- once merged, I will
update the nova driver to use a copy of this file (and add a comment to
the file here saying it's synced over there). This should help prevent
this kinda issue in the future and in the long run cause less duplicated
work.

Assisted-by: Claude Code (claude)
Signed-off-by: Jay Faulkner <jay@jvf.cc>
Change-Id: Ief4533b69899c893f150ef3a7006fb99f7e42964
This commit is contained in:
Jay Faulkner 2025-12-02 13:12:58 -08:00
parent 7495f77258
commit c69caf28e8
No known key found for this signature in database
GPG key ID: CD454452290B456A
8 changed files with 406 additions and 378 deletions

View file

@ -47,6 +47,7 @@ from ironic.common import exception
from ironic.common.i18n import _
from ironic.common import metrics_utils
from ironic.common import policy
from ironic.common import state_machine
from ironic.common import states as ir_states
from ironic.conductor import steps as conductor_steps
import ironic.conf
@ -1240,7 +1241,7 @@ class NodeStatesController(rest.RestController):
# This code does upfront state sanity checking, in that we're past
# basic RBAC policy checking, and we're shifting gears to content
# validation.
m = ir_states.machine.copy()
m = state_machine.machine.copy()
m.initialize(rpc_node.provision_state)
if not m.is_actionable_event(ir_states.VERBS.get(target, target)):
# Normally, we let the task manager recognize and deal with

View file

@ -38,6 +38,7 @@ from ironic.common import exception
from ironic.common import faults
from ironic.common.i18n import _
from ironic.common import policy
from ironic.common import state_machine
from ironic.common import states
from ironic.common import utils
from ironic.conductor import steps as conductor_steps
@ -957,7 +958,7 @@ def check_for_invalid_state_and_allow_filter(provision_state):
if (api.request.version.minor
< versions.MINOR_9_PROVISION_STATE_FILTER):
raise exception.NotAcceptable()
valid_states = states.machine.states
valid_states = state_machine.machine.states
if provision_state not in valid_states:
raise exception.InvalidParameterValue(
_('Provision state "%s" is not valid') % provision_state)

View file

@ -0,0 +1,370 @@
# Copyright (c) 2012 NTT DOCOMO, INC.
# Copyright 2010 OpenStack Foundation
# 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.
"""
Mapping of bare metal node states.
Setting the node `power_state` is handled by the conductor's power
synchronization thread. Based on the power state retrieved from the driver
for the node, the state is set to POWER_ON or POWER_OFF, accordingly.
Should this fail, the `power_state` value is left unchanged, and the node
is placed into maintenance mode.
The `power_state` can also be set manually via the API. A failure to change
the state leaves the current state unchanged. The node is NOT placed into
maintenance mode in this case.
"""
from oslo_log import log as logging
from ironic.common import fsm
from ironic.common import states as st
LOG = logging.getLogger(__name__)
#####################
# State machine model
#####################
def on_exit(old_state, event):
"""Used to log when a state is exited."""
LOG.debug("Exiting old state '%s' in response to event '%s'",
old_state, event)
def on_enter(new_state, event):
"""Used to log when entering a state."""
LOG.debug("Entering new state '%s' in response to event '%s'",
new_state, event)
watchers = {}
watchers['on_exit'] = on_exit
watchers['on_enter'] = on_enter
machine = fsm.FSM()
# Add stable states
for state in st.STABLE_STATES:
machine.add_state(state, stable=True, **watchers)
# Add verifying state
machine.add_state(st.VERIFYING, target=st.MANAGEABLE, **watchers)
# Add deploy* states
# NOTE(tenbrae): Juno shows a target_provision_state of DEPLOYDONE
# this is changed in Kilo to ACTIVE
machine.add_state(st.DEPLOYING, target=st.ACTIVE, **watchers)
machine.add_state(st.DEPLOYWAIT, target=st.ACTIVE, **watchers)
machine.add_state(st.DEPLOYFAIL, target=st.ACTIVE, **watchers)
machine.add_state(st.DEPLOYHOLD, target=st.ACTIVE, **watchers)
# Add clean* states
machine.add_state(st.CLEANING, target=st.AVAILABLE, **watchers)
machine.add_state(st.CLEANWAIT, target=st.AVAILABLE, **watchers)
machine.add_state(st.CLEANFAIL, target=st.AVAILABLE, **watchers)
machine.add_state(st.CLEANHOLD, target=st.AVAILABLE, **watchers)
# Add delete* states
machine.add_state(st.DELETING, target=st.AVAILABLE, **watchers)
# From AVAILABLE, a deployment may be started
machine.add_transition(st.AVAILABLE, st.DEPLOYING, 'deploy')
# Add inspect* states.
machine.add_state(st.INSPECTING, target=st.MANAGEABLE, **watchers)
machine.add_state(st.INSPECTFAIL, target=st.MANAGEABLE, **watchers)
machine.add_state(st.INSPECTWAIT, target=st.MANAGEABLE, **watchers)
# Add adopt* states
machine.add_state(st.ADOPTING, target=st.ACTIVE, **watchers)
machine.add_state(st.ADOPTFAIL, target=st.ACTIVE, **watchers)
# rescue states
machine.add_state(st.RESCUING, target=st.RESCUE, **watchers)
machine.add_state(st.RESCUEWAIT, target=st.RESCUE, **watchers)
machine.add_state(st.RESCUEFAIL, target=st.RESCUE, **watchers)
machine.add_state(st.UNRESCUING, target=st.ACTIVE, **watchers)
machine.add_state(st.UNRESCUEFAIL, target=st.ACTIVE, **watchers)
# A deployment may fail
machine.add_transition(st.DEPLOYING, st.DEPLOYFAIL, 'fail')
# A failed deployment may be retried
# ironic/conductor/manager.py:do_node_deploy()
machine.add_transition(st.DEPLOYFAIL, st.DEPLOYING, 'rebuild')
# NOTE(tenbrae): Juno allows a client to send "active" to initiate a rebuild
machine.add_transition(st.DEPLOYFAIL, st.DEPLOYING, 'deploy')
# A deployment may also wait on external callbacks
machine.add_transition(st.DEPLOYING, st.DEPLOYWAIT, 'wait')
machine.add_transition(st.DEPLOYING, st.DEPLOYHOLD, 'hold')
machine.add_transition(st.DEPLOYWAIT, st.DEPLOYHOLD, 'hold')
machine.add_transition(st.DEPLOYWAIT, st.DEPLOYING, 'resume')
# A deployment waiting on callback may time out
machine.add_transition(st.DEPLOYWAIT, st.DEPLOYFAIL, 'fail')
# Return the node into a deploying state from holding
machine.add_transition(st.DEPLOYHOLD, st.DEPLOYWAIT, 'unhold')
# A node in deploy hold may also be aborted
machine.add_transition(st.DEPLOYHOLD, st.DEPLOYFAIL, 'abort')
# A deployment may complete
machine.add_transition(st.DEPLOYING, st.ACTIVE, 'done')
# An active instance may be re-deployed
# ironic/conductor/manager.py:do_node_deploy()
machine.add_transition(st.ACTIVE, st.DEPLOYING, 'rebuild')
# An active instance may be deleted
# ironic/conductor/manager.py:do_node_tear_down()
machine.add_transition(st.ACTIVE, st.DELETING, 'delete')
# While a deployment is waiting, it may be deleted
# ironic/conductor/manager.py:do_node_tear_down()
machine.add_transition(st.DEPLOYWAIT, st.DELETING, 'delete')
# A failed deployment may also be deleted
# ironic/conductor/manager.py:do_node_tear_down()
machine.add_transition(st.DEPLOYFAIL, st.DELETING, 'delete')
# This state can also transition to error
machine.add_transition(st.DELETING, st.ERROR, 'fail')
# When finished deleting, a node will begin cleaning
machine.add_transition(st.DELETING, st.CLEANING, 'clean')
# If cleaning succeeds, it becomes available for scheduling
machine.add_transition(st.CLEANING, st.AVAILABLE, 'done')
# If cleaning fails, wait for operator intervention
machine.add_transition(st.CLEANING, st.CLEANFAIL, 'fail')
machine.add_transition(st.CLEANWAIT, st.CLEANFAIL, 'fail')
# While waiting for a clean step to be finished, cleaning may be aborted
machine.add_transition(st.CLEANWAIT, st.CLEANFAIL, 'abort')
# Cleaning may also wait on external callbacks
machine.add_transition(st.CLEANING, st.CLEANWAIT, 'wait')
machine.add_transition(st.CLEANING, st.CLEANHOLD, 'hold')
machine.add_transition(st.CLEANWAIT, st.CLEANHOLD, 'hold')
machine.add_transition(st.CLEANWAIT, st.CLEANING, 'resume')
# A node in a clean hold step may also be aborted
machine.add_transition(st.CLEANHOLD, st.CLEANFAIL, 'abort')
# Return the node back to cleaning
machine.add_transition(st.CLEANHOLD, st.CLEANWAIT, 'unhold')
# An operator may want to move a CLEANFAIL node to MANAGEABLE, to perform
# other actions like cleaning
machine.add_transition(st.CLEANFAIL, st.MANAGEABLE, 'manage')
# From MANAGEABLE, a node may move to available after going through automated
# cleaning
machine.add_transition(st.MANAGEABLE, st.CLEANING, 'provide')
# From MANAGEABLE, a node may be manually cleaned, going back to manageable
# after cleaning is completed
machine.add_transition(st.MANAGEABLE, st.CLEANING, 'clean')
machine.add_transition(st.CLEANING, st.MANAGEABLE, 'manage')
# From AVAILABLE, a node may be made unavailable by managing it
machine.add_transition(st.AVAILABLE, st.MANAGEABLE, 'manage')
# An errored instance can be rebuilt
# ironic/conductor/manager.py:do_node_deploy()
machine.add_transition(st.ERROR, st.DEPLOYING, 'rebuild')
# or deleted
# ironic/conductor/manager.py:do_node_tear_down()
machine.add_transition(st.ERROR, st.DELETING, 'delete')
# Added transitions for inspection.
# Initiate inspection.
machine.add_transition(st.MANAGEABLE, st.INSPECTING, 'inspect')
# ironic/conductor/manager.py:inspect_hardware().
machine.add_transition(st.INSPECTING, st.MANAGEABLE, 'done')
# Inspection may fail.
machine.add_transition(st.INSPECTING, st.INSPECTFAIL, 'fail')
# Transition for asynchronous inspection
machine.add_transition(st.INSPECTING, st.INSPECTWAIT, 'wait')
# Inspection is done
machine.add_transition(st.INSPECTWAIT, st.MANAGEABLE, 'done')
# Inspection failed.
machine.add_transition(st.INSPECTWAIT, st.INSPECTFAIL, 'fail')
# Inspection is aborted.
machine.add_transition(st.INSPECTWAIT, st.INSPECTFAIL, 'abort')
# Inspection is continued.
machine.add_transition(st.INSPECTWAIT, st.INSPECTING, 'resume')
# Move the node to manageable state for any other
# action.
machine.add_transition(st.INSPECTFAIL, st.MANAGEABLE, 'manage')
# Reinitiate the inspect after inspectfail.
machine.add_transition(st.INSPECTFAIL, st.INSPECTING, 'inspect')
# A provisioned node may have a rescue initiated.
machine.add_transition(st.ACTIVE, st.RESCUING, 'rescue')
# A rescue may succeed.
machine.add_transition(st.RESCUING, st.RESCUE, 'done')
# A rescue may also wait on external callbacks
machine.add_transition(st.RESCUING, st.RESCUEWAIT, 'wait')
machine.add_transition(st.RESCUEWAIT, st.RESCUING, 'resume')
# A rescued node may be re-rescued.
machine.add_transition(st.RESCUE, st.RESCUING, 'rescue')
# A rescued node may be deleted.
machine.add_transition(st.RESCUE, st.DELETING, 'delete')
# A rescue may fail.
machine.add_transition(st.RESCUEWAIT, st.RESCUEFAIL, 'fail')
machine.add_transition(st.RESCUING, st.RESCUEFAIL, 'fail')
# While waiting for a rescue step to be finished, rescuing may be aborted
machine.add_transition(st.RESCUEWAIT, st.RESCUEFAIL, 'abort')
# A failed rescue may be re-rescued.
machine.add_transition(st.RESCUEFAIL, st.RESCUING, 'rescue')
# A failed rescue may be unrescued.
machine.add_transition(st.RESCUEFAIL, st.UNRESCUING, 'unrescue')
# A failed rescue may be deleted.
machine.add_transition(st.RESCUEFAIL, st.DELETING, 'delete')
# A rescuewait node may be deleted.
machine.add_transition(st.RESCUEWAIT, st.DELETING, 'delete')
# A rescued node may be unrescued.
machine.add_transition(st.RESCUE, st.UNRESCUING, 'unrescue')
# An unrescuing node may succeed
machine.add_transition(st.UNRESCUING, st.ACTIVE, 'done')
# An unrescuing node may fail
machine.add_transition(st.UNRESCUING, st.UNRESCUEFAIL, 'fail')
# A failed unrescue may be re-rescued
machine.add_transition(st.UNRESCUEFAIL, st.RESCUING, 'rescue')
# A failed unrescue may be re-unrescued
machine.add_transition(st.UNRESCUEFAIL, st.UNRESCUING, 'unrescue')
# A failed unrescue may be deleted.
machine.add_transition(st.UNRESCUEFAIL, st.DELETING, 'delete')
# Start power credentials verification
machine.add_transition(st.ENROLL, st.VERIFYING, 'manage')
# Verification can succeed
machine.add_transition(st.VERIFYING, st.MANAGEABLE, 'done')
# Verification can fail with setting last_error and rolling back to ENROLL
machine.add_transition(st.VERIFYING, st.ENROLL, 'fail')
# Node Adoption is being attempted
machine.add_transition(st.MANAGEABLE, st.ADOPTING, 'adopt')
# Adoption can succeed and the node should be set to ACTIVE
machine.add_transition(st.ADOPTING, st.ACTIVE, 'done')
# Node adoptions can fail and as such nodes shall be set
# into a dedicated state to hold the nodes.
machine.add_transition(st.ADOPTING, st.ADOPTFAIL, 'fail')
# Node adoption can be retried when it previously failed.
machine.add_transition(st.ADOPTFAIL, st.ADOPTING, 'adopt')
# A node that failed adoption can be moved back to manageable
machine.add_transition(st.ADOPTFAIL, st.MANAGEABLE, 'manage')
# Add service* states
machine.add_state(st.SERVICING, target=st.ACTIVE, **watchers)
machine.add_state(st.SERVICEWAIT, target=st.ACTIVE, **watchers)
machine.add_state(st.SERVICEFAIL, target=st.ACTIVE, **watchers)
machine.add_state(st.SERVICEHOLD, target=st.ACTIVE, **watchers)
# A node in service an be returned to active
machine.add_transition(st.SERVICING, st.ACTIVE, 'done')
# A node in active can be serviced
machine.add_transition(st.ACTIVE, st.SERVICING, 'service')
# A node in servicing can be failed
machine.add_transition(st.SERVICING, st.SERVICEFAIL, 'fail')
# A node in service can enter a wait state
machine.add_transition(st.SERVICING, st.SERVICEWAIT, 'wait')
# A node in service can be held
machine.add_transition(st.SERVICING, st.SERVICEHOLD, 'hold')
machine.add_transition(st.SERVICEWAIT, st.SERVICEHOLD, 'hold')
# A held node in service can get more service steps to start over
machine.add_transition(st.SERVICEHOLD, st.SERVICING, 'service')
# A held node in service can be removed from service
machine.add_transition(st.SERVICEHOLD, st.SERVICEWAIT, 'unhold')
# A node in service wait can resume
machine.add_transition(st.SERVICEWAIT, st.SERVICING, 'resume')
# A node in service wait can failed
machine.add_transition(st.SERVICEWAIT, st.SERVICEFAIL, 'fail')
# A node in service hold can failed
machine.add_transition(st.SERVICEHOLD, st.SERVICEFAIL, 'fail')
# A node in service wait can be aborted
machine.add_transition(st.SERVICEWAIT, st.SERVICEFAIL, 'abort')
# A node in service hold can be aborted
machine.add_transition(st.SERVICEHOLD, st.SERVICEFAIL, 'abort')
# A node in service fail can re-enter service
machine.add_transition(st.SERVICEFAIL, st.SERVICING, 'service')
# A node in service fail can be rescued
machine.add_transition(st.SERVICEFAIL, st.RESCUING, 'rescue')
# A node in service fail can enter wait state
machine.add_transition(st.SERVICEFAIL, st.SERVICEWAIT, 'wait')
# A node in service fail can be held
machine.add_transition(st.SERVICEFAIL, st.SERVICEHOLD, 'hold')
# A node in service fail may be deleted.
machine.add_transition(st.SERVICEFAIL, st.DELETING, 'delete')
# A node in service fail may be aborted (returned to active)
machine.add_transition(st.SERVICEFAIL, st.ACTIVE, 'abort')
# A node in service wait may be deleted.
machine.add_transition(st.SERVICEWAIT, st.DELETING, 'delete')

View file

@ -1,44 +1,26 @@
# Copyright (c) 2012 NTT DOCOMO, INC.
# Copyright 2010 OpenStack Foundation
# 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
#
# 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
#
# 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.
# 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.
"""
Mapping of bare metal node states.
Constants for bare metal node states.
Setting the node `power_state` is handled by the conductor's power
synchronization thread. Based on the power state retrieved from the driver
for the node, the state is set to POWER_ON or POWER_OFF, accordingly.
Should this fail, the `power_state` value is left unchanged, and the node
is placed into maintenance mode.
The `power_state` can also be set manually via the API. A failure to change
the state leaves the current state unchanged. The node is NOT placed into
maintenance mode in this case.
This module contains only state constant definitions with no executable code.
For the state machine implementation, see ironic.common.states.
"""
from oslo_log import log as logging
from ironic.common import fsm
LOG = logging.getLogger(__name__)
#####################
# Provisioning states
#####################
# TODO(tenbrae): add add'l state mappings here
VERBS = {
'active': 'deploy',
'deploy': 'deploy',
@ -140,9 +122,7 @@ DELETING = 'deleting'
DELETED = 'deleted'
""" Node tear down was successful.
In Juno, target_provision_state was set to this value during node tear down.
In Kilo, this will be a transitory value of provision_state, and never
This is a transitory value of provision_state, and never
represented in target_provision_state.
"""
@ -151,6 +131,7 @@ CLEANING = 'cleaning'
UNDEPLOY = 'undeploy'
""" Node tear down process has started.
This is an alias for DELETED.
"""
@ -305,6 +286,14 @@ FAILURE_STATES = frozenset((DEPLOYFAIL, CLEANFAIL, INSPECTFAIL,
RESCUEFAIL, UNRESCUEFAIL, ADOPTFAIL,
SERVICEFAIL))
# NOTE(JayF) This isn't used in Ironic, but is used in Nova as a copy of this
# file will be proposed into the nova driver.
ALL_STATES = frozenset((ACTIVE, ADOPTFAIL, ADOPTING, AVAILABLE, CLEANFAIL,
CLEANHOLD, CLEANING, CLEANWAIT, DELETED, DELETING, DEPLOYDONE, DEPLOYFAIL,
DEPLOYHOLD, DEPLOYING, DEPLOYWAIT, ENROLL, ERROR, INSPECTFAIL, INSPECTING,
INSPECTWAIT, MANAGEABLE, RESCUE, RESCUEFAIL, RESCUING, RESCUEWAIT,
SERVICEFAIL, SERVICEHOLD, SERVICING, SERVICEWAIT, UNRESCUEFAIL,
UNRESCUING, VERIFYING)) # noqa
##############
# Power states
@ -338,11 +327,8 @@ ALLOCATING = 'allocating'
###########################
PROVISIONING = "provisioning"
CLEANING = "cleaning"
DEPLOYING = "deploying"
TAKEOVER = "takeover"
INTROSPECTION = "introspection"
RESCUE = "rescue"
CONDUCTOR = "conductor"
TRANSITION = "transition"
STARTFAIL = "startup failure"
@ -351,336 +337,3 @@ ADOPTION = "adoption"
CONSOLE = "console"
MONITORING = "monitoring"
VERIFY = "verify"
#####################
# State machine model
#####################
def on_exit(old_state, event):
"""Used to log when a state is exited."""
LOG.debug("Exiting old state '%s' in response to event '%s'",
old_state, event)
def on_enter(new_state, event):
"""Used to log when entering a state."""
LOG.debug("Entering new state '%s' in response to event '%s'",
new_state, event)
watchers = {}
watchers['on_exit'] = on_exit
watchers['on_enter'] = on_enter
machine = fsm.FSM()
# Add stable states
for state in STABLE_STATES:
machine.add_state(state, stable=True, **watchers)
# Add verifying state
machine.add_state(VERIFYING, target=MANAGEABLE, **watchers)
# Add deploy* states
# NOTE(tenbrae): Juno shows a target_provision_state of DEPLOYDONE
# this is changed in Kilo to ACTIVE
machine.add_state(DEPLOYING, target=ACTIVE, **watchers)
machine.add_state(DEPLOYWAIT, target=ACTIVE, **watchers)
machine.add_state(DEPLOYFAIL, target=ACTIVE, **watchers)
machine.add_state(DEPLOYHOLD, target=ACTIVE, **watchers)
# Add clean* states
machine.add_state(CLEANING, target=AVAILABLE, **watchers)
machine.add_state(CLEANWAIT, target=AVAILABLE, **watchers)
machine.add_state(CLEANFAIL, target=AVAILABLE, **watchers)
machine.add_state(CLEANHOLD, target=AVAILABLE, **watchers)
# Add delete* states
machine.add_state(DELETING, target=AVAILABLE, **watchers)
# From AVAILABLE, a deployment may be started
machine.add_transition(AVAILABLE, DEPLOYING, 'deploy')
# Add inspect* states.
machine.add_state(INSPECTING, target=MANAGEABLE, **watchers)
machine.add_state(INSPECTFAIL, target=MANAGEABLE, **watchers)
machine.add_state(INSPECTWAIT, target=MANAGEABLE, **watchers)
# Add adopt* states
machine.add_state(ADOPTING, target=ACTIVE, **watchers)
machine.add_state(ADOPTFAIL, target=ACTIVE, **watchers)
# rescue states
machine.add_state(RESCUING, target=RESCUE, **watchers)
machine.add_state(RESCUEWAIT, target=RESCUE, **watchers)
machine.add_state(RESCUEFAIL, target=RESCUE, **watchers)
machine.add_state(UNRESCUING, target=ACTIVE, **watchers)
machine.add_state(UNRESCUEFAIL, target=ACTIVE, **watchers)
# A deployment may fail
machine.add_transition(DEPLOYING, DEPLOYFAIL, 'fail')
# A failed deployment may be retried
# ironic/conductor/manager.py:do_node_deploy()
machine.add_transition(DEPLOYFAIL, DEPLOYING, 'rebuild')
# NOTE(tenbrae): Juno allows a client to send "active" to initiate a rebuild
machine.add_transition(DEPLOYFAIL, DEPLOYING, 'deploy')
# A deployment may also wait on external callbacks
machine.add_transition(DEPLOYING, DEPLOYWAIT, 'wait')
machine.add_transition(DEPLOYING, DEPLOYHOLD, 'hold')
machine.add_transition(DEPLOYWAIT, DEPLOYHOLD, 'hold')
machine.add_transition(DEPLOYWAIT, DEPLOYING, 'resume')
# A deployment waiting on callback may time out
machine.add_transition(DEPLOYWAIT, DEPLOYFAIL, 'fail')
# Return the node into a deploying state from holding
machine.add_transition(DEPLOYHOLD, DEPLOYWAIT, 'unhold')
# A node in deploy hold may also be aborted
machine.add_transition(DEPLOYHOLD, DEPLOYFAIL, 'abort')
# A deployment may complete
machine.add_transition(DEPLOYING, ACTIVE, 'done')
# An active instance may be re-deployed
# ironic/conductor/manager.py:do_node_deploy()
machine.add_transition(ACTIVE, DEPLOYING, 'rebuild')
# An active instance may be deleted
# ironic/conductor/manager.py:do_node_tear_down()
machine.add_transition(ACTIVE, DELETING, 'delete')
# While a deployment is waiting, it may be deleted
# ironic/conductor/manager.py:do_node_tear_down()
machine.add_transition(DEPLOYWAIT, DELETING, 'delete')
# A failed deployment may also be deleted
# ironic/conductor/manager.py:do_node_tear_down()
machine.add_transition(DEPLOYFAIL, DELETING, 'delete')
# This state can also transition to error
machine.add_transition(DELETING, ERROR, 'fail')
# When finished deleting, a node will begin cleaning
machine.add_transition(DELETING, CLEANING, 'clean')
# If cleaning succeeds, it becomes available for scheduling
machine.add_transition(CLEANING, AVAILABLE, 'done')
# If cleaning fails, wait for operator intervention
machine.add_transition(CLEANING, CLEANFAIL, 'fail')
machine.add_transition(CLEANWAIT, CLEANFAIL, 'fail')
# While waiting for a clean step to be finished, cleaning may be aborted
machine.add_transition(CLEANWAIT, CLEANFAIL, 'abort')
# Cleaning may also wait on external callbacks
machine.add_transition(CLEANING, CLEANWAIT, 'wait')
machine.add_transition(CLEANING, CLEANHOLD, 'hold')
machine.add_transition(CLEANWAIT, CLEANHOLD, 'hold')
machine.add_transition(CLEANWAIT, CLEANING, 'resume')
# A node in a clean hold step may also be aborted
machine.add_transition(CLEANHOLD, CLEANFAIL, 'abort')
# Return the node back to cleaning
machine.add_transition(CLEANHOLD, CLEANWAIT, 'unhold')
# An operator may want to move a CLEANFAIL node to MANAGEABLE, to perform
# other actions like cleaning
machine.add_transition(CLEANFAIL, MANAGEABLE, 'manage')
# From MANAGEABLE, a node may move to available after going through automated
# cleaning
machine.add_transition(MANAGEABLE, CLEANING, 'provide')
# From MANAGEABLE, a node may be manually cleaned, going back to manageable
# after cleaning is completed
machine.add_transition(MANAGEABLE, CLEANING, 'clean')
machine.add_transition(CLEANING, MANAGEABLE, 'manage')
# From AVAILABLE, a node may be made unavailable by managing it
machine.add_transition(AVAILABLE, MANAGEABLE, 'manage')
# An errored instance can be rebuilt
# ironic/conductor/manager.py:do_node_deploy()
machine.add_transition(ERROR, DEPLOYING, 'rebuild')
# or deleted
# ironic/conductor/manager.py:do_node_tear_down()
machine.add_transition(ERROR, DELETING, 'delete')
# Added transitions for inspection.
# Initiate inspection.
machine.add_transition(MANAGEABLE, INSPECTING, 'inspect')
# ironic/conductor/manager.py:inspect_hardware().
machine.add_transition(INSPECTING, MANAGEABLE, 'done')
# Inspection may fail.
machine.add_transition(INSPECTING, INSPECTFAIL, 'fail')
# Transition for asynchronous inspection
machine.add_transition(INSPECTING, INSPECTWAIT, 'wait')
# Inspection is done
machine.add_transition(INSPECTWAIT, MANAGEABLE, 'done')
# Inspection failed.
machine.add_transition(INSPECTWAIT, INSPECTFAIL, 'fail')
# Inspection is aborted.
machine.add_transition(INSPECTWAIT, INSPECTFAIL, 'abort')
# Inspection is continued.
machine.add_transition(INSPECTWAIT, INSPECTING, 'resume')
# Move the node to manageable state for any other
# action.
machine.add_transition(INSPECTFAIL, MANAGEABLE, 'manage')
# Reinitiate the inspect after inspectfail.
machine.add_transition(INSPECTFAIL, INSPECTING, 'inspect')
# A provisioned node may have a rescue initiated.
machine.add_transition(ACTIVE, RESCUING, 'rescue')
# A rescue may succeed.
machine.add_transition(RESCUING, RESCUE, 'done')
# A rescue may also wait on external callbacks
machine.add_transition(RESCUING, RESCUEWAIT, 'wait')
machine.add_transition(RESCUEWAIT, RESCUING, 'resume')
# A rescued node may be re-rescued.
machine.add_transition(RESCUE, RESCUING, 'rescue')
# A rescued node may be deleted.
machine.add_transition(RESCUE, DELETING, 'delete')
# A rescue may fail.
machine.add_transition(RESCUEWAIT, RESCUEFAIL, 'fail')
machine.add_transition(RESCUING, RESCUEFAIL, 'fail')
# While waiting for a rescue step to be finished, rescuing may be aborted
machine.add_transition(RESCUEWAIT, RESCUEFAIL, 'abort')
# A failed rescue may be re-rescued.
machine.add_transition(RESCUEFAIL, RESCUING, 'rescue')
# A failed rescue may be unrescued.
machine.add_transition(RESCUEFAIL, UNRESCUING, 'unrescue')
# A failed rescue may be deleted.
machine.add_transition(RESCUEFAIL, DELETING, 'delete')
# A rescuewait node may be deleted.
machine.add_transition(RESCUEWAIT, DELETING, 'delete')
# A rescued node may be unrescued.
machine.add_transition(RESCUE, UNRESCUING, 'unrescue')
# An unrescuing node may succeed
machine.add_transition(UNRESCUING, ACTIVE, 'done')
# An unrescuing node may fail
machine.add_transition(UNRESCUING, UNRESCUEFAIL, 'fail')
# A failed unrescue may be re-rescued
machine.add_transition(UNRESCUEFAIL, RESCUING, 'rescue')
# A failed unrescue may be re-unrescued
machine.add_transition(UNRESCUEFAIL, UNRESCUING, 'unrescue')
# A failed unrescue may be deleted.
machine.add_transition(UNRESCUEFAIL, DELETING, 'delete')
# Start power credentials verification
machine.add_transition(ENROLL, VERIFYING, 'manage')
# Verification can succeed
machine.add_transition(VERIFYING, MANAGEABLE, 'done')
# Verification can fail with setting last_error and rolling back to ENROLL
machine.add_transition(VERIFYING, ENROLL, 'fail')
# Node Adoption is being attempted
machine.add_transition(MANAGEABLE, ADOPTING, 'adopt')
# Adoption can succeed and the node should be set to ACTIVE
machine.add_transition(ADOPTING, ACTIVE, 'done')
# Node adoptions can fail and as such nodes shall be set
# into a dedicated state to hold the nodes.
machine.add_transition(ADOPTING, ADOPTFAIL, 'fail')
# Node adoption can be retried when it previously failed.
machine.add_transition(ADOPTFAIL, ADOPTING, 'adopt')
# A node that failed adoption can be moved back to manageable
machine.add_transition(ADOPTFAIL, MANAGEABLE, 'manage')
# Add service* states
machine.add_state(SERVICING, target=ACTIVE, **watchers)
machine.add_state(SERVICEWAIT, target=ACTIVE, **watchers)
machine.add_state(SERVICEFAIL, target=ACTIVE, **watchers)
machine.add_state(SERVICEHOLD, target=ACTIVE, **watchers)
# A node in service an be returned to active
machine.add_transition(SERVICING, ACTIVE, 'done')
# A node in active can be serviced
machine.add_transition(ACTIVE, SERVICING, 'service')
# A node in servicing can be failed
machine.add_transition(SERVICING, SERVICEFAIL, 'fail')
# A node in service can enter a wait state
machine.add_transition(SERVICING, SERVICEWAIT, 'wait')
# A node in service can be held
machine.add_transition(SERVICING, SERVICEHOLD, 'hold')
machine.add_transition(SERVICEWAIT, SERVICEHOLD, 'hold')
# A held node in service can get more service steps to start over
machine.add_transition(SERVICEHOLD, SERVICING, 'service')
# A held node in service can be removed from service
machine.add_transition(SERVICEHOLD, SERVICEWAIT, 'unhold')
# A node in service wait can resume
machine.add_transition(SERVICEWAIT, SERVICING, 'resume')
# A node in service wait can failed
machine.add_transition(SERVICEWAIT, SERVICEFAIL, 'fail')
# A node in service hold can failed
machine.add_transition(SERVICEHOLD, SERVICEFAIL, 'fail')
# A node in service wait can be aborted
machine.add_transition(SERVICEWAIT, SERVICEFAIL, 'abort')
# A node in service hold can be aborted
machine.add_transition(SERVICEHOLD, SERVICEFAIL, 'abort')
# A node in service fail can re-enter service
machine.add_transition(SERVICEFAIL, SERVICING, 'service')
# A node in service fail can be rescued
machine.add_transition(SERVICEFAIL, RESCUING, 'rescue')
# A node in service fail can enter wait state
machine.add_transition(SERVICEFAIL, SERVICEWAIT, 'wait')
# A node in service fail can be held
machine.add_transition(SERVICEFAIL, SERVICEHOLD, 'hold')
# A node in service fail may be deleted.
machine.add_transition(SERVICEFAIL, DELETING, 'delete')
# A node in service fail may be aborted (returned to active)
machine.add_transition(SERVICEFAIL, ACTIVE, 'abort')
# A node in service wait may be deleted.
machine.add_transition(SERVICEWAIT, DELETING, 'delete')

View file

@ -114,6 +114,7 @@ import tenacity
from ironic.common import driver_factory
from ironic.common import exception
from ironic.common.i18n import _
from ironic.common import state_machine
from ironic.common import states
from ironic.conductor import notification_utils as notify
from ironic import objects
@ -220,7 +221,7 @@ class TaskManager(object):
self._retry = retry
self._patient = patient
self.fsm = states.machine.copy()
self.fsm = state_machine.machine.copy()
self._purpose = purpose
self._debug_timer = timeutils.StopWatch()
@ -500,7 +501,7 @@ class TaskManager(object):
if self.node is None:
# Rare case if resource released before notification
task = copy.copy(self)
task.fsm = states.machine.copy()
task.fsm = state_machine.machine.copy()
task.node = self._saved_node
else:
task = self

View file

@ -26,6 +26,7 @@ import tenacity
from ironic.common import driver_factory
from ironic.common import exception
from ironic.common import fsm
from ironic.common import state_machine
from ironic.common import states
from ironic.conductor import notification_utils
from ironic.conductor import task_manager
@ -743,7 +744,7 @@ class TaskManagerTestCase(db_base.DbTestCase):
on_error_handler.assert_called_once_with(expected_exception,
'fake-argument')
@mock.patch.object(states.machine, 'copy', autospec=True)
@mock.patch.object(state_machine.machine, 'copy', autospec=True)
def test_init_prepares_fsm(
self, copy_mock, get_volconn_mock, get_voltgt_mock,
get_portgroups_mock, get_ports_mock,

View file

@ -20,6 +20,7 @@ from oslo_config import cfg
from testtools import matchers
from ironic.common import exception
from ironic.common import state_machine
from ironic.common import states
from ironic.conductor import cleaning
from ironic.conductor import servicing
@ -236,7 +237,7 @@ class HeartbeatMixinTest(AgentDeployMixinBaseTest):
states.DEPLOYING, states.CLEANING, states.RESCUING,
states.DEPLOYHOLD, states.CLEANHOLD, states.SERVICEHOLD,
states.SERVICING, states.SERVICEWAIT}
for state in set(states.machine.states) - allowed:
for state in set(state_machine.machine.states) - allowed:
for m in (next_step_mock, log_mock):
m.reset_mock()
with task_manager.acquire(self.context, self.node.uuid,
@ -258,7 +259,7 @@ class HeartbeatMixinTest(AgentDeployMixinBaseTest):
group='conductor')
allowed = {states.DEPLOYWAIT, states.CLEANWAIT,
states.SERVICEWAIT}
for state in set(states.machine.states) - allowed:
for state in set(state_machine.machine.states) - allowed:
next_step_mock.reset_mock()
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:

View file

@ -20,7 +20,7 @@ import sys
from automaton.converters import pydot
from ironic.common import states
from ironic.common import state_machine
top_dir = os.path.abspath(os.path.join(os.path.dirname(__file__),
os.pardir))
@ -118,7 +118,7 @@ def main():
attrs['fontcolor'] = 'gray'
return attrs
source = states.machine
source = state_machine.machine
graph_name = '"Ironic states"'
graph_attrs = {'size': 0}
g = pydot.convert(source, graph_name, graph_attrs=graph_attrs,