Add transitive reduction filter for task graphs

Remove 'pydot_ng' that doesn't support transitive reduction filtering.
  Add '--tred' option for CLI.
  Usage example:

      fuel graph --render graph.gv --tred
      fuel graph --render graph.gv --dir ./output/dir/ --tred

Change-Id: Ibfd7112dc0751df9bcadd0ab46e29f5159cbb3a5
Closes-bug: #1443446
This commit is contained in:
Ivan Kliuk 2015-10-12 21:51:03 +03:00
parent 769df968e1
commit 655db5e962
3 changed files with 79 additions and 39 deletions

View File

@ -53,6 +53,7 @@ class GraphAction(base.Action):
Args.get_graph_startpoint(),
Args.get_remove_type_arg(self.task_types),
Args.get_parents_arg(),
Args.get_tred_arg("Apply transitive reduction filter for graph."),
)
self.flag_func_map = (
('render', self.render),
@ -68,8 +69,8 @@ class GraphAction(base.Action):
fuel graph --env 1 --download --skip X Y --end pre_deployment
fuel graph --env 1 --download --skip X Y --start post_deployment
Sepcify output:
fuel graph --env 1 --download > outpup/dir/file.gv
Specify output:
fuel graph --env 1 --download > output/dir/file.gv
Get parents only for task A:
@ -105,6 +106,11 @@ class GraphAction(base.Action):
fuel graph --render graph.gv
fuel graph --render graph.gv --dir ./output/dir/
To apply transitive reduction filter on rendered graph:
fuel graph --render graph.gv --tred
fuel graph --render graph.gv --dir ./output/dir/ --tred
Read graph from stdin
some_process | fuel graph --render -
"""
@ -132,49 +138,26 @@ class GraphAction(base.Action):
if not os.access(os.path.dirname(target_file), os.W_OK):
raise error.ActionException(
'Path {0} is not writable'.format(target_file))
render_graph(dot_data, target_file)
render_graph(dot_data, target_file, params.tred)
print('Graph saved in "{0}"'.format(target_file))
def render_graph(input_data, output_path):
"""Renders DOT graph using pydot or pygraphviz depending on their presence.
If none of the libraries is available and are fully functional it is not
possible to render graphs.
def render_graph(input_data, output_path, tred=False):
"""Renders DOT graph using pygraphviz.
:param input_data: DOT graph representation
:param output_path: path to the rendered graph
:param tred: applies transitive reduction of graph
"""
try:
_render_with_pydot(input_data, output_path)
import pygraphviz
except ImportError:
try:
_render_with_pygraphiz(input_data, output_path)
except ImportError:
raise error.WrongEnvironmentError(
"This action require Graphviz installed toghether with "
"'pydot_ng' or 'pygraphviz' Python library")
def _render_with_pydot(input_data, output_path):
"""Renders graph using pydot_ng library."""
import pydot_ng as pydot
graph = pydot.graph_from_dot_data(input_data)
if not graph:
raise error.BadDataException(
"Passed data does not contain graph in DOT format")
try:
graph.write_png(output_path)
except pydot.InvocationException as e:
raise error.WrongEnvironmentError(
"There was an error with rendering graph:\n{0}".format(e))
"This action requires Graphviz installed "
"together with 'pygraphviz' Python library")
graph = pygraphviz.AGraph(string=input_data)
if tred:
graph = graph.tred()
def _render_with_pygraphiz(input_data, output_path):
"""Renders graph using pygraphviz library."""
import pygraphviz as pgv
graph = pgv.AGraph(string=input_data)
graph.draw(output_path, prog='dot', format='png')

View File

@ -488,6 +488,10 @@ def get_render_arg(help_msg):
help=help_msg)
def get_tred_arg(help_msg):
return get_boolean_arg("tred", help=help_msg)
def get_node_arg(help_msg):
default_kwargs = {
"action": NodeAction,

View File

@ -151,7 +151,25 @@ class TestGraphAction(base.UnitTestCase):
)
m_open.assert_called_with('graph.gv', 'r')
m_render.assert_called_once_with(graph_data, '/path/graph.gv.png')
m_render.assert_called_once_with(
graph_data, '/path/graph.gv.png', False)
@mock.patch('fuelclient.cli.actions.graph.open', create=True)
@mock.patch('fuelclient.cli.actions.graph.render_graph')
@mock.patch('fuelclient.cli.actions.graph.os.access')
@mock.patch('fuelclient.cli.actions.graph.os.path.exists')
def test_render_with_tred(self, m_exists, m_access, m_render, m_open):
graph_data = 'some-dot-data'
m_exists.return_value = True
m_open().__enter__().read.return_value = graph_data
self.execute(
['fuel', 'graph', '--render', 'graph.gv', '--tred']
)
m_open.assert_called_with('graph.gv', 'r')
m_render.assert_called_once_with(
graph_data, '/path/graph.gv.png', True)
@mock.patch('fuelclient.cli.actions.graph.os.path.exists')
def test_render_no_file(self, m_exists):
@ -177,8 +195,29 @@ class TestGraphAction(base.UnitTestCase):
)
self.m_full_path.assert_called_once_with(output_dir, '')
m_render.assert_called_once_with(graph_data,
'/output/dir/graph.gv.png')
m_render.assert_called_once_with(
graph_data, '/output/dir/graph.gv.png', False)
@mock.patch('fuelclient.cli.actions.graph.open', create=True)
@mock.patch('fuelclient.cli.actions.graph.render_graph')
@mock.patch('fuelclient.cli.actions.graph.os.access')
@mock.patch('fuelclient.cli.actions.graph.os.path.exists')
def test_render_with_output_path_with_tred(
self, m_exists, m_access, m_render, m_open):
output_dir = '/output/dir'
graph_data = 'some-dot-data'
m_exists.return_value = True
m_open().__enter__().read.return_value = graph_data
self.m_full_path.return_value = output_dir
self.execute(
['fuel', 'graph', '--render', 'graph.gv', '--dir', output_dir,
'--tred']
)
self.m_full_path.assert_called_once_with(output_dir, '')
m_render.assert_called_once_with(
graph_data, '/output/dir/graph.gv.png', True)
@mock.patch('fuelclient.cli.actions.graph.os.access')
@mock.patch('fuelclient.cli.actions.graph.render_graph')
@ -190,7 +229,21 @@ class TestGraphAction(base.UnitTestCase):
['fuel', 'graph', '--render', '-', ]
)
m_render.assert_called_once_with(graph_data, '/path/graph.gv.png')
m_render.assert_called_once_with(
graph_data, '/path/graph.gv.png', False)
@mock.patch('fuelclient.cli.actions.graph.os.access')
@mock.patch('fuelclient.cli.actions.graph.render_graph')
def test_render_from_stdin_with_tred(self, m_render, m_access):
graph_data = u'graph data'
with mock.patch('sys.stdin', new=io.StringIO(graph_data)):
self.execute(
['fuel', 'graph', '--render', '-', '--tred']
)
m_render.assert_called_once_with(
graph_data, '/path/graph.gv.png', True)
@mock.patch('fuelclient.cli.actions.graph.open', create=True)
@mock.patch('fuelclient.cli.actions.graph.os.path.exists')