#!/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()