ironic/tools/states_to_dot.py
Julia Kreger c4e3100d5c Add hold steps
* Updates API version to 1.85 to permit an ``unhold`` verb
* Adds the ``deploy hold`` and ``clean hold`` provision states
  to the internal state machine.
* Adds on documentation on steps to help provide greater clarity
  to Ironic's users on how to utilize steps. It should be noted
  this documentation also includes the power state reserved step
  names from the DPU functionality patch.
* Fixes the state machine diagram. Changes type to PNG as SVG
  rendering is broken due to python libraries utilized for SVG
  generation which do not work on more recent Python versions.

Change-Id: I34f58f4e77e7757b89247fd64f5fcde26f679453
2023-06-30 14:34:26 -07:00

136 lines
4.5 KiB
Python
Executable File

#!/usr/bin/env python3
# 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 optparse
import os
import sys
from automaton.converters import pydot
from ironic.common import states
top_dir = os.path.abspath(os.path.join(os.path.dirname(__file__),
os.pardir))
sys.path.insert(0, top_dir)
def print_header(text):
print("*" * len(text))
print(text)
print("*" * len(text))
def map_color(text, key='fontcolor'):
"""Map the text to a color.
The text is mapped to a color.
:param text: string of text to be mapped to a color. 'error' and
'fail' in the text will map to 'red'.
:param key: in returned dictionary, the key to use that corresponds to
the color
:returns: A dictionary with one entry, key = color. If no color is
associated with the text, an empty dictionary.
"""
# If the text contains 'error'/'fail' then we'll return red...
if 'error' in text or 'fail' in text:
return {key: 'red'}
else:
return {}
def main():
parser = optparse.OptionParser()
parser.add_option("-f", "--file", dest="filename",
help="write output to FILE", metavar="FILE")
parser.add_option("-T", "--format", dest="format",
help="output in given format (default: png)",
default='png')
parser.add_option("--no-labels", dest="labels",
help="do not include labels",
action='store_false', default=True)
(options, args) = parser.parse_args()
if options.filename is None:
options.filename = 'states.%s' % options.format
def node_attrs(state):
"""Attributes used for drawing the nodes (states).
The user can perform actions on stable states (and in a few other
cases), so we distinguish the stable states from the other states by
highlighting the node. Non-stable states are labelled with gray.
This is a callback method used by pydot.convert().
:param state: name of state
:returns: A dictionary with graphic attributes used for displaying
the state.
"""
attrs = map_color(state)
if source.is_stable(state):
attrs['penwidth'] = 1.7
else:
if 'fontcolor' not in attrs:
attrs['fontcolor'] = 'gray'
return attrs
def edge_attrs(start_state, event, end_state):
"""Attributes used for drawing the edges (transitions).
There are two types of transitions; the ones that the user can
initiate and the ones that are done internally by the conductor.
The user-initiated ones are shown with '(via API'); the others are
in gray.
This is a callback method used by pydot.convert().
:param start_state: name of the start state
:param event: the event, a string
:param end_state: name of the end state (unused)
:returns: A dictionary with graphic attributes used for displaying
the transition.
"""
if not options.labels:
return {}
translations = {'delete': 'deleted', 'deploy': 'active'}
attrs = {}
attrs['fontsize'] = 12
attrs['label'] = translations.get(event, event)
if (source.is_stable(start_state) or 'fail' in start_state
or event in ('abort', 'delete')):
attrs['label'] += " (via API)"
else:
attrs['fontcolor'] = 'gray'
return attrs
source = states.machine
graph_name = '"Ironic states"'
graph_attrs = {'size': 0}
g = pydot.convert(source, graph_name, graph_attrs=graph_attrs,
node_attrs_cb=node_attrs, edge_attrs_cb=edge_attrs)
print_header(graph_name)
print(g.to_string().strip())
g.write(options.filename, format=options.format)
print_header("Created %s at '%s'" % (options.format, options.filename))
if __name__ == '__main__':
main()