Files
deb-python-fuelclient/fuelclient/cli/actions/graph.py
Ivan Kliuk 655db5e962 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
2015-10-12 23:07:46 +03:00

164 lines
5.2 KiB
Python

# -*- coding: utf-8 -*-
# Copyright 2015 Mirantis, Inc.
#
# 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 os
import sys
import six
from fuelclient.cli.actions import base
import fuelclient.cli.arguments as Args
from fuelclient.cli import error
from fuelclient.objects import environment
class GraphAction(base.Action):
"""Manipulate deployment graph's representation."""
action_name = 'graph'
task_types = ('skipped', 'group', 'stage')
def __init__(self):
super(GraphAction, self).__init__()
self.args = (
Args.get_env_arg(),
Args.get_render_arg(
"Render graph from DOT to PNG"
),
Args.get_download_arg(
"Download graph of specific cluster"
),
Args.get_dir_arg(
"Select target dir to render graph."
),
Args.group(
Args.get_skip_tasks(),
Args.get_tasks()
),
Args.get_graph_endpoint(),
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),
('download', self.download),
)
@base.check_all("env")
def download(self, params):
"""Download deployment graph to stdout
fuel graph --env 1 --download
fuel graph --env 1 --download --tasks A B C
fuel graph --env 1 --download --skip X Y --end pre_deployment
fuel graph --env 1 --download --skip X Y --start post_deployment
Specify output:
fuel graph --env 1 --download > output/dir/file.gv
Get parents only for task A:
fuel graph --env 1 --download --parents-for A
"""
env = environment.Environment(params.env)
parents_for = getattr(params, 'parents-for')
used_params = "# params:\n"
for param in ('start', 'end', 'skip', 'tasks', 'parents-for',
'remove'):
used_params += "# - {0}: {1}\n".format(param,
getattr(params, param))
tasks = params.tasks
if not tasks or (params.skip or params.end or params.start):
tasks = env.get_tasks(
skip=params.skip, end=params.end,
start=params.start, include=params.tasks)
dotraph = env.get_deployment_tasks_graph(tasks,
parents_for=parents_for,
remove=params.remove)
sys.stdout.write(six.text_type(used_params))
sys.stdout.write(six.text_type(dotraph))
@base.check_all("render")
def render(self, params):
"""Render graph in PNG format
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 -
"""
if params.render == '-':
dot_data = sys.stdin.read()
out_filename = 'graph.gv'
elif not os.path.exists(params.render):
raise error.ArgumentException(
"Input file does not exist"
)
else:
out_filename = os.path.basename(params.render)
with open(params.render, 'r') as f:
dot_data = f.read()
target_dir = self.full_path_directory(
self.default_directory(params.dir),
''
)
target_file = os.path.join(
target_dir,
'{0}.png'.format(out_filename),
)
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, params.tred)
print('Graph saved in "{0}"'.format(target_file))
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:
import pygraphviz
except ImportError:
raise error.WrongEnvironmentError(
"This action requires Graphviz installed "
"together with 'pygraphviz' Python library")
graph = pygraphviz.AGraph(string=input_data)
if tred:
graph = graph.tred()
graph.draw(output_path, prog='dot', format='png')