governance/tools/universe_dot.py
Radosław Piliszek 9128e50225 Fix gate
and a related script.

pydot2 is not maintained and stopped being installable.
pydot is maintained and seems to work the same.

Change-Id: Id248d2349c31b4cb4ad82c2afde10787c75081c7
2021-09-16 15:58:18 +00:00

143 lines
4.7 KiB
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.
"""
A tool that will translate a projects.yaml file into a visualized graph.
Usage: universe_dot.py <projects-file> <output-file> just-project? ...
To run make sure pydot (or pydot2) is installed, as well as six and pyyaml.
After these are installed run this like:
$ python tools/universe_dot.py reference/projects.yaml \
ironic_universe.dot ironic;
$ neato -Tsvg ironic_universe.dot > ironic_universe.svg
Then open ironic_universe.svg in your favorite web browser.
Or to create the whole openstack universe.
$ python tools/universe_dot.py reference/projects.yaml os_universe.dot
$ neato -Tsvg os_universe.dot > os_universe.svg
Then open os_universe.svg in your favorite web browser.
"""
import sys
import time
import pydot
import six
import yaml
if len(sys.argv) == 1:
print(__doc__.strip())
sys.exit(1)
projects_file = sys.argv[1]
output_dot_file = sys.argv[2]
just_projects = set(sys.argv[3:])
print("Reading projects from '%s'" % projects_file)
with open(projects_file, 'rb') as fh:
projects = yaml.safe_load(fh)
project_names = sorted(six.iterkeys(projects))
if just_projects:
print("Restricting to just %s projects" % list(just_projects))
for project_name in list(project_names):
if project_name not in just_projects:
project_names.remove(project_name)
project_names = tuple(project_names)
graph_nodes = {}
start = time.time()
print("Constructing nodes.")
print("Please wait...")
deliverable_node_attrs = {
'fontsize': '11',
}
project_node_attrs = {
'fontsize': '11',
'shape': 'diamond',
}
node_count = 0
edges_needed = 0
for project_name in project_names:
node = pydot.Node(name=project_name, **project_node_attrs)
node_count += 1
graph_nodes[project_name] = node
deliverables = projects[project_name].get('deliverables', [])
deliverable_names = sorted(six.iterkeys(deliverables))
for deliverable_name in deliverable_names:
edges_needed += 1
node_path = "%s/%s" % (project_name, deliverable_name)
if deliverable_name == project_name:
# Avoid creating a self-link, since its actually not, avoid
# this by creating a node with a hidden name, and a label that
# is its actual name...
node_deliverable_node_attrs = dict(deliverable_node_attrs)
node_deliverable_node_attrs['name'] = '__%s' % deliverable_name
node_deliverable_node_attrs['label'] = deliverable_name
else:
node_deliverable_node_attrs = dict(deliverable_node_attrs)
node_deliverable_node_attrs['name'] = deliverable_name
node = pydot.Node(**node_deliverable_node_attrs)
graph_nodes[node_path] = node
node_count += 1
print("Inserting %s nodes and %s edges into"
" a new graph." % (node_count, edges_needed))
print("Please wait...")
if just_projects:
nice_names = [name.title() for name in sorted(just_projects)]
graph_name = '%s Universe' % (" and ".join(nice_names))
else:
graph_name = 'OpenStack Universe'
graph_kwargs = {
'rankdir': 'LR',
'nodesep': '0.25',
'overlap': 'false',
'ranksep': '0.5',
'splines': 'true',
'ordering': 'in',
'graph_name': graph_name,
}
graph = pydot.Graph(**graph_kwargs)
for project_name in project_names:
print(" Inserting nodes for '%s'" % project_name)
node = graph_nodes[project_name]
graph.add_node(node)
deliverables = projects[project_name].get('deliverables', [])
deliverable_names = sorted(six.iterkeys(deliverables))
for deliverable_name in deliverable_names:
print(" Inserting node for '%s'" % deliverable_name)
node_path = "%s/%s" % (project_name, deliverable_name)
deliverable_node = graph_nodes[node_path]
graph.add_node(deliverable_node)
print(" Inserting edge for '%s' -> '%s'"
% (project_name, deliverable_name))
graph.add_edge(pydot.Edge(node, deliverable_node,
style='dotted'))
end = time.time()
print("Finished in %0.2f seconds" % (end - start))
print("Writing graph to '%s'" % output_dot_file)
with open(output_dot_file, "w") as fh:
fh.write(graph.to_string())