Add fsm dot diagram generator

Add a tool which generates dot diagram from inspector state machine
using atomaton pydot convertor.

Also add generated svg diagram to docs.

Change-Id: I021812288f1833a6ebad2f90cbe862d40bd8d503
This commit is contained in:
Anton Arefiev 2017-02-22 17:26:12 +02:00
parent d2bbf3ec96
commit 886d05ee64
4 changed files with 285 additions and 1 deletions

View File

@ -0,0 +1,174 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.36.0 (20140111.2315)
-->
<!-- Title: Ironic Inspector states Pages: 1 -->
<svg width="832pt" height="318pt"
viewBox="0.00 0.00 832.00 318.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 314)">
<title>Ironic Inspector states</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-314 828,-314 828,4 -4,4"/>
<!-- enrolling -->
<g id="node1" class="node"><title>enrolling</title>
<ellipse fill="none" stroke="black" cx="34" cy="-251" rx="33.8507" ry="18"/>
<text text-anchor="middle" x="34" y="-248.2" font-family="Times,serif" font-size="11.00" fill="gray">enrolling</text>
</g>
<!-- error -->
<g id="node2" class="node"><title>error</title>
<ellipse fill="none" stroke="black" cx="151" cy="-150" rx="27" ry="18"/>
<text text-anchor="middle" x="151" y="-147.2" font-family="Times,serif" font-size="11.00" fill="red">error</text>
</g>
<!-- enrolling&#45;&gt;error -->
<g id="edge1" class="edge"><title>enrolling&#45;&gt;error</title>
<path fill="none" stroke="black" d="M52.466,-235.66C72.1644,-218.36 104.307,-190.13 126.269,-170.842"/>
<polygon fill="black" stroke="black" points="128.654,-173.405 133.858,-164.177 124.035,-168.146 128.654,-173.405"/>
<text text-anchor="middle" x="96" y="-206" font-family="Times,serif" font-size="10.00" fill="red">error</text>
</g>
<!-- processing -->
<g id="node3" class="node"><title>processing</title>
<ellipse fill="none" stroke="black" cx="517" cy="-150" rx="38.4712" ry="18"/>
<text text-anchor="middle" x="517" y="-147.2" font-family="Times,serif" font-size="11.00" fill="gray">processing</text>
</g>
<!-- enrolling&#45;&gt;processing -->
<g id="edge2" class="edge"><title>enrolling&#45;&gt;processing</title>
<path fill="none" stroke="black" d="M55.1347,-265.142C77.3021,-279.301 114.519,-299 150,-299 150,-299 150,-299 381,-299 418.718,-299 432.423,-293.732 460,-268 486.199,-243.553 501.392,-204.325 509.183,-177.88"/>
<polygon fill="black" stroke="black" points="512.599,-178.66 511.903,-168.088 505.854,-176.787 512.599,-178.66"/>
<text text-anchor="middle" x="264" y="-302" font-family="Times,serif" font-size="10.00">process</text>
</g>
<!-- error&#45;&gt;error -->
<g id="edge3" class="edge"><title>error&#45;&gt;error</title>
<path fill="none" stroke="black" d="M146.146,-167.782C145.322,-177.315 146.939,-186 151,-186 153.474,-186 155.042,-182.775 155.702,-178.098"/>
<polygon fill="black" stroke="black" points="159.206,-177.832 155.854,-167.782 152.207,-177.729 159.206,-177.832"/>
<text text-anchor="middle" x="151" y="-189" font-family="Times,serif" font-size="10.00" fill="red">abort</text>
</g>
<!-- error&#45;&gt;error -->
<g id="edge6" class="edge"><title>error&#45;&gt;error</title>
<path fill="none" stroke="black" d="M143.026,-167.42C138.481,-184.791 141.139,-204 151,-204 158.935,-204 162.206,-191.562 160.813,-177.652"/>
<polygon fill="black" stroke="black" points="164.188,-176.643 158.974,-167.42 157.298,-177.882 164.188,-176.643"/>
<text text-anchor="middle" x="151" y="-207" font-family="Times,serif" font-size="10.00" fill="red">error</text>
</g>
<!-- starting -->
<g id="node4" class="node"><title>starting</title>
<ellipse fill="none" stroke="black" cx="264" cy="-140" rx="30.1339" ry="18"/>
<text text-anchor="middle" x="264" y="-137.2" font-family="Times,serif" font-size="11.00" fill="gray">starting</text>
</g>
<!-- error&#45;&gt;starting -->
<g id="edge4" class="edge"><title>error&#45;&gt;starting</title>
<path fill="none" stroke="black" d="M177.86,-148.145C189.52,-147.266 203.459,-146.157 216,-145 218.666,-144.754 221.416,-144.49 224.184,-144.215"/>
<polygon fill="black" stroke="black" points="224.587,-147.693 234.18,-143.195 223.876,-140.729 224.587,-147.693"/>
<text text-anchor="middle" x="206" y="-149" font-family="Times,serif" font-size="10.00">start</text>
</g>
<!-- reapplying -->
<g id="node5" class="node"><title>reapplying</title>
<ellipse fill="none" stroke="black" cx="786" cy="-27" rx="37.7689" ry="18"/>
<text text-anchor="middle" x="786" y="-24.2" font-family="Times,serif" font-size="11.00" fill="gray">reapplying</text>
</g>
<!-- error&#45;&gt;reapplying -->
<g id="edge5" class="edge"><title>error&#45;&gt;reapplying</title>
<path fill="none" stroke="black" d="M160.369,-167.008C176.101,-196.263 213.05,-253 263,-253 263,-253 263,-253 649,-253 740.366,-253 771.925,-115.721 781.403,-55.1474"/>
<polygon fill="black" stroke="black" points="784.887,-55.5155 782.88,-45.1125 777.961,-54.4964 784.887,-55.5155"/>
<text text-anchor="middle" x="444" y="-256" font-family="Times,serif" font-size="10.00">reapply</text>
</g>
<!-- processing&#45;&gt;error -->
<g id="edge10" class="edge"><title>processing&#45;&gt;error</title>
<path fill="none" stroke="black" d="M488.02,-162.185C436.942,-182.754 325.428,-219.893 234,-196 213.801,-190.721 193.318,-179.235 177.876,-169.062"/>
<polygon fill="black" stroke="black" points="179.639,-166.028 169.406,-163.281 175.692,-171.81 179.639,-166.028"/>
<text text-anchor="middle" x="322" y="-206" font-family="Times,serif" font-size="10.00" fill="red">error</text>
</g>
<!-- finished -->
<g id="node6" class="node"><title>finished</title>
<ellipse fill="none" stroke="black" cx="648" cy="-77" rx="31.0408" ry="18"/>
<text text-anchor="middle" x="648" y="-74.2" font-family="Times,serif" font-size="11.00" fill="gray">finished</text>
</g>
<!-- processing&#45;&gt;finished -->
<g id="edge11" class="edge"><title>processing&#45;&gt;finished</title>
<path fill="none" stroke="black" d="M542.264,-136.269C563.176,-124.435 593.483,-107.285 616.12,-94.4746"/>
<polygon fill="black" stroke="black" points="618.016,-97.4231 624.996,-89.452 614.569,-91.3309 618.016,-97.4231"/>
<text text-anchor="middle" x="586" y="-120" font-family="Times,serif" font-size="10.00">finish</text>
</g>
<!-- starting&#45;&gt;error -->
<g id="edge15" class="edge"><title>starting&#45;&gt;error</title>
<path fill="none" stroke="black" d="M236.121,-132.968C223.823,-130.728 209.104,-129.423 196,-132 191.505,-132.884 186.892,-134.23 182.422,-135.798"/>
<polygon fill="black" stroke="black" points="181.011,-132.591 172.968,-139.487 183.556,-139.112 181.011,-132.591"/>
<text text-anchor="middle" x="206" y="-135" font-family="Times,serif" font-size="10.00" fill="red">error</text>
</g>
<!-- starting&#45;&gt;starting -->
<g id="edge17" class="edge"><title>starting&#45;&gt;starting</title>
<path fill="none" stroke="black" d="M253.722,-157.037C251.625,-166.858 255.051,-176 264,-176 269.593,-176 273.029,-172.429 274.307,-167.353"/>
<polygon fill="black" stroke="black" points="277.806,-167.027 274.278,-157.037 270.806,-167.047 277.806,-167.027"/>
<text text-anchor="middle" x="264" y="-179" font-family="Times,serif" font-size="10.00">start</text>
</g>
<!-- waiting -->
<g id="node7" class="node"><title>waiting</title>
<ellipse fill="none" stroke="black" cx="380" cy="-93" rx="30.1339" ry="18"/>
<text text-anchor="middle" x="380" y="-90.2" font-family="Times,serif" font-size="11.00" fill="gray">waiting</text>
</g>
<!-- starting&#45;&gt;waiting -->
<g id="edge16" class="edge"><title>starting&#45;&gt;waiting</title>
<path fill="none" stroke="black" d="M289.176,-130.033C305.5,-123.303 327.271,-114.327 345.291,-106.897"/>
<polygon fill="black" stroke="black" points="346.947,-110.001 354.858,-102.953 344.279,-103.529 346.947,-110.001"/>
<text text-anchor="middle" x="322" y="-122" font-family="Times,serif" font-size="10.00">wait</text>
</g>
<!-- reapplying&#45;&gt;error -->
<g id="edge12" class="edge"><title>reapplying&#45;&gt;error</title>
<path fill="none" stroke="black" d="M754.017,-17.2524C726.674,-9.51855 685.589,-0 649,-0 263,-0 263,-0 263,-0 202.716,-0 171.423,-79.2538 158.826,-122.545"/>
<polygon fill="black" stroke="black" points="155.447,-121.633 156.152,-132.204 162.194,-123.501 155.447,-121.633"/>
<text text-anchor="middle" x="444" y="-3" font-family="Times,serif" font-size="10.00" fill="red">error</text>
</g>
<!-- reapplying&#45;&gt;reapplying -->
<g id="edge13" class="edge"><title>reapplying&#45;&gt;reapplying</title>
<path fill="none" stroke="black" d="M773.242,-44.0373C770.638,-53.8579 774.891,-63 786,-63 792.943,-63 797.208,-59.4289 798.795,-54.3529"/>
<polygon fill="black" stroke="black" points="802.294,-54.0248 798.758,-44.0373 795.294,-54.0497 802.294,-54.0248"/>
<text text-anchor="middle" x="786" y="-66" font-family="Times,serif" font-size="10.00">reapply</text>
</g>
<!-- reapplying&#45;&gt;finished -->
<g id="edge14" class="edge"><title>reapplying&#45;&gt;finished</title>
<path fill="none" stroke="black" d="M748.627,-23.7035C732.542,-23.5857 713.745,-25.3404 698,-32 686.924,-36.6849 676.751,-45.0033 668.58,-53.2245"/>
<polygon fill="black" stroke="black" points="665.926,-50.9381 661.67,-60.6402 671.048,-55.7102 665.926,-50.9381"/>
<text text-anchor="middle" x="714" y="-35" font-family="Times,serif" font-size="10.00">finish</text>
</g>
<!-- finished&#45;&gt;starting -->
<g id="edge7" class="edge"><title>finished&#45;&gt;starting</title>
<path fill="none" stroke="black" d="M636.838,-94.1322C622.158,-117.186 592.709,-157.541 556,-177 507.861,-202.518 360.889,-225.049 312,-201 297.23,-193.735 285.796,-179.349 277.906,-166.372"/>
<polygon fill="black" stroke="black" points="280.748,-164.287 272.789,-157.293 274.65,-167.724 280.748,-164.287"/>
<text text-anchor="middle" x="444" y="-211" font-family="Times,serif" font-size="10.00">start</text>
</g>
<!-- finished&#45;&gt;reapplying -->
<g id="edge8" class="edge"><title>finished&#45;&gt;reapplying</title>
<path fill="none" stroke="black" d="M674.901,-67.4777C694.996,-60.0896 723.179,-49.7282 746,-41.3383"/>
<polygon fill="black" stroke="black" points="747.235,-44.6134 755.413,-37.8777 744.819,-38.0434 747.235,-44.6134"/>
<text text-anchor="middle" x="714" y="-60" font-family="Times,serif" font-size="10.00">reapply</text>
</g>
<!-- finished&#45;&gt;finished -->
<g id="edge9" class="edge"><title>finished&#45;&gt;finished</title>
<path fill="none" stroke="black" d="M637.014,-94.0373C634.771,-103.858 638.434,-113 648,-113 653.979,-113 657.652,-109.429 659.018,-104.353"/>
<polygon fill="black" stroke="black" points="662.517,-104.027 658.986,-94.0373 655.517,-104.048 662.517,-104.027"/>
<text text-anchor="middle" x="648" y="-116" font-family="Times,serif" font-size="10.00">finish</text>
</g>
<!-- waiting&#45;&gt;error -->
<g id="edge18" class="edge"><title>waiting&#45;&gt;error</title>
<path fill="none" stroke="black" d="M349.964,-90.9081C320.18,-89.6551 272.844,-90.1631 234,-102 213.818,-108.15 193.335,-120.136 177.889,-130.591"/>
<polygon fill="black" stroke="black" points="175.606,-127.916 169.416,-136.515 179.617,-133.653 175.606,-127.916"/>
<text text-anchor="middle" x="264" y="-105" font-family="Times,serif" font-size="10.00" fill="red">timeout</text>
</g>
<!-- waiting&#45;&gt;error -->
<g id="edge19" class="edge"><title>waiting&#45;&gt;error</title>
<path fill="none" stroke="black" d="M354.203,-83.6922C324.773,-74.0865 274.58,-62.2845 234,-76 207.376,-84.9985 184.362,-107.946 169.549,-125.923"/>
<polygon fill="black" stroke="black" points="166.803,-123.753 163.337,-133.765 172.29,-128.099 166.803,-123.753"/>
<text text-anchor="middle" x="264" y="-79" font-family="Times,serif" font-size="10.00" fill="red">abort</text>
</g>
<!-- waiting&#45;&gt;processing -->
<g id="edge20" class="edge"><title>waiting&#45;&gt;processing</title>
<path fill="none" stroke="black" d="M405.135,-103.19C425.532,-111.803 455.091,-124.283 478.559,-134.191"/>
<polygon fill="black" stroke="black" points="477.298,-137.458 487.872,-138.124 480.021,-131.009 477.298,-137.458"/>
<text text-anchor="middle" x="444" y="-127" font-family="Times,serif" font-size="10.00">process</text>
</g>
<!-- waiting&#45;&gt;starting -->
<g id="edge21" class="edge"><title>waiting&#45;&gt;starting</title>
<path fill="none" stroke="black" d="M350.002,-91.1267C337.839,-91.3523 323.808,-92.9812 312,-98 302.03,-102.238 292.742,-109.479 285.1,-116.767"/>
<polygon fill="black" stroke="black" points="282.547,-114.371 278.042,-123.961 287.543,-119.274 282.547,-114.371"/>
<text text-anchor="middle" x="322" y="-101" font-family="Times,serif" font-size="10.00">start</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -56,6 +56,17 @@ Usual hardware introspection flow is as follows:
Starting DHCP server and configuring PXE boot environment is not part of this
package and should be done separately.
State machine diagram
=====================
The diagram below shows the introspection states that an **ironic-inspector**
FSM goes through during the node introspection, discovery and reprocessing.
The diagram also shows events that trigger state transitions.
.. figure:: ./images/states.svg
:width: 660px
:align: center
:alt: ironic-inspector state machine diagram
.. _Ironic inspection documentation: http://docs.openstack.org/developer/ironic/deploy/install-guide.html#hardware-inspection
.. _Ironic: https://wiki.openstack.org/wiki/Ironic

94
tools/states_to_dot.py Executable file
View File

@ -0,0 +1,94 @@
#!/usr/bin/env python
# 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
from automaton.converters import pydot
from ironic_inspector import introspection_state as states
def print_header(text):
print("*" * len(text))
print(text)
print("*" * len(text))
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 introspection states, we distinguish
the error states from the other states by highlighting the node.
Error stable states are labelled with red.
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 = {}
attrs['fontcolor'] = 'red' if 'error' in state else 'gray'
return attrs
def edge_attrs(start_state, event, end_state):
"""Attributes used for drawing the edges (transitions).
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 {}
attrs = {}
attrs['fontsize'] = 10
attrs['label'] = event
if end_state is 'error':
attrs['fontcolor'] = 'red'
return attrs
source = states.FSM
graph_name = '"Ironic Inspector states"'
graph_attrs = {'size': 0}
dot_graph = pydot.convert(
source, graph_name, graph_attrs=graph_attrs,
node_attrs_cb=node_attrs, edge_attrs_cb=edge_attrs,
add_start_state=False)
dot_graph.write(options.filename, format=options.format)
print(dot_graph.to_string())
print_header("Created %s at '%s'" % (options.format, options.filename))
if __name__ == '__main__':
main()

View File

@ -45,6 +45,11 @@ commands =
envdir = {toxworkdir}/venv
commands = oslo-config-generator --config-file config-generator.conf
[testenv:genstates]
deps = {[testenv]deps}
pydot2
commands = {toxinidir}/tools/states_to_dot.py -f {toxinidir}/doc/source/images/states.svg --format svg
[flake8]
max-complexity=15
# [H106] Dont put vim configuration in source files.