Improve task_graph
Related-Blueprint: fuel-library-modularization Change-Id: I13f784229a5cd0c20a5496e8764a868750ed5c1d
This commit is contained in:
parent
36f30ae7f1
commit
d29e56a100
@ -1,58 +0,0 @@
|
|||||||
usage: task_graph.py [-h] [--workbook] [--clear_workbook] [--dot] [--png]
|
|
||||||
[--png_file PNG_FILE] [--debug] [--group GROUP]
|
|
||||||
[--topology]
|
|
||||||
FILE [FILE ...]
|
|
||||||
|
|
||||||
positional arguments:
|
|
||||||
FILE The list of files of directories where tasks can be
|
|
||||||
found
|
|
||||||
|
|
||||||
optional arguments:
|
|
||||||
-h, --help show this help message and exit
|
|
||||||
--workbook, -w Output the raw workbook
|
|
||||||
--clear_workbook, -c Output the clear workbook
|
|
||||||
--dot, -D Output the graph in dot format
|
|
||||||
--png, -p Write the graph in png format (default)
|
|
||||||
--open, -o Open the image after creation
|
|
||||||
--png_file PNG_FILE, -f PNG_FILE
|
|
||||||
Write graph image to this file
|
|
||||||
--debug, -d Print debug messages
|
|
||||||
--group GROUP, -g GROUP
|
|
||||||
Group or stage to build the graph for
|
|
||||||
--topology, -t Show the tasks topology (possible execution order)
|
|
||||||
|
|
||||||
This tools can be used to create a task graph image. Just pouint it
|
|
||||||
at the folder where tasks.yaml files can be found.
|
|
||||||
|
|
||||||
> utils/task_graph/task_graph.py deployment/puppet/osnailyfacter/modular
|
|
||||||
|
|
||||||
It will create task_graph.png file in the current directroy.
|
|
||||||
|
|
||||||
You can also use -w and -c options to inspect the workbook yamls
|
|
||||||
files and output graph as graphviz file with -o option.
|
|
||||||
|
|
||||||
You can filter graph by roles and stages with -g option like this
|
|
||||||
> utils/task_graph/task_graph.py -g post_deployment deployment/puppet/osnailyfacter/modular
|
|
||||||
|
|
||||||
And you can use -t option to view the possible order of task execution
|
|
||||||
with the current graph.
|
|
||||||
|
|
||||||
> utils/task_graph/task_graph.py -t deployment/puppet/osnailyfacter/modular
|
|
||||||
|
|
||||||
1 hiera primary-controller, controller, cinder, compute, ceph-osd, zabbix-server, primary-mongo, mongo
|
|
||||||
2 globals primary-controller, controller, cinder, compute, ceph-osd, zabbix-server, primary-mongo, mongo
|
|
||||||
3 logging primary-controller, controller, cinder, compute, ceph-osd, zabbix-server, primary-mongo, mongo
|
|
||||||
4 netconfig primary-controller, controller, cinder, compute, ceph-osd, zabbix-server, primary-mongo, mongo
|
|
||||||
5 firewall primary-controller, controller, cinder, compute, ceph-osd, zabbix-server, primary-mongo, mongo
|
|
||||||
6 hosts primary-controller, controller, cinder, compute, ceph-osd, zabbix-server, primary-mongo, mongo
|
|
||||||
7 top-role-compute compute
|
|
||||||
8 top-role-primary-mongo primary-mongo
|
|
||||||
9 top-role-mongo mongo
|
|
||||||
10 cluster primary-controller, controller
|
|
||||||
11 virtual_ips primary-controller, controller
|
|
||||||
12 zabbix primary-controller, controller, cinder, compute, ceph-osd, zabbix-server, primary-mongo, mongo
|
|
||||||
13 top-role-ceph-osd ceph-osd
|
|
||||||
14 top-role-cinder cinder
|
|
||||||
15 tools primary-controller, controller, cinder, compute, ceph-osd, zabbix-server, primary-mongo, mongo
|
|
||||||
16 top-role-controller primary-controller, controller
|
|
||||||
|
|
@ -14,13 +14,60 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
# usage: task_graph.py [-h] [--workbook] [--clear_workbook] [--dot] [--png]
|
||||||
|
# [--png_file PNG_FILE] [--open] [--debug]
|
||||||
|
# [--filter FILTER] [--topology]
|
||||||
|
# PLACE [PLACE ...]
|
||||||
|
#
|
||||||
|
# positional arguments:
|
||||||
|
# PLACE The list of files of directories wheretasks can be
|
||||||
|
# found
|
||||||
|
#
|
||||||
|
# optional arguments:
|
||||||
|
# -h, --help show this help message and exit
|
||||||
|
# --workbook, -w Output the raw workbook
|
||||||
|
# --clear_workbook, -c Output the clear workbook
|
||||||
|
# --dot, -D Output the graph in dot format
|
||||||
|
# --png, -p Write the graph in png format (default)
|
||||||
|
# --png_file PNG_FILE, -P PNG_FILE
|
||||||
|
# Write graph image to this file
|
||||||
|
# --open, -o Open the image after creation
|
||||||
|
# --debug, -d Print debug messages
|
||||||
|
# --filter FILTER, -f FILTER
|
||||||
|
# Filter tasks by this group or role
|
||||||
|
# --topology, -t Show the tasks topology(possible execution order)
|
||||||
|
#
|
||||||
|
# This tools can be used to create a task graph image. Just point it
|
||||||
|
# at the folder where tasks.yaml files can be found.
|
||||||
|
#
|
||||||
|
# > ./task_graph.py deployment/puppet/osnailyfacter/modular
|
||||||
|
#
|
||||||
|
# It will create task_graph.png file in the current directroy.
|
||||||
|
#
|
||||||
|
# You can also use -w and -c options to inspect the workbook yamls
|
||||||
|
# files and output graph as graphviz file with -o option.
|
||||||
|
#
|
||||||
|
# You can filter graph by roles and groups with -f option like this
|
||||||
|
# > ./task_graph.py -f controller deployment/puppet/osnailyfacter/modular
|
||||||
|
#
|
||||||
|
# And you can use -t option to view the possible order of task execution
|
||||||
|
# with the current graph.
|
||||||
|
#
|
||||||
|
# > ./task_graph.py -t deployment/puppet/osnailyfacter/modular
|
||||||
|
|
||||||
import fnmatch
|
import fnmatch
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
import networkx
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
try:
|
||||||
|
import pygraphviz
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
import networkx
|
||||||
|
|
||||||
|
|
||||||
class IO(object):
|
class IO(object):
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -32,12 +79,11 @@ class IO(object):
|
|||||||
sys.stdout.write(msg)
|
sys.stdout.write(msg)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def output(cls, line, fill=None):
|
def output(cls, line, fill=None, newline=True):
|
||||||
line = str(line)
|
line = str(line)
|
||||||
if fill:
|
if fill:
|
||||||
line = line[0:fill].ljust(fill)
|
line = line[0:fill].ljust(fill)
|
||||||
else:
|
if newline and not line.endswith("\n"):
|
||||||
if not line.endswith("\n"):
|
|
||||||
line += "\n"
|
line += "\n"
|
||||||
sys.stdout.write(line)
|
sys.stdout.write(line)
|
||||||
|
|
||||||
@ -73,7 +119,7 @@ class IO(object):
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
default=True,
|
default=True,
|
||||||
help='Write the graph in png format (default)')
|
help='Write the graph in png format (default)')
|
||||||
parser.add_argument("--png_file", "-f",
|
parser.add_argument("--png_file", "-P",
|
||||||
type=str,
|
type=str,
|
||||||
default='task_graph.png',
|
default='task_graph.png',
|
||||||
help='Write graph image to this file')
|
help='Write graph image to this file')
|
||||||
@ -85,12 +131,9 @@ class IO(object):
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
default=False,
|
default=False,
|
||||||
help='Print debug messages')
|
help='Print debug messages')
|
||||||
parser.add_argument("--group", "-g",
|
parser.add_argument("--filter", "-f",
|
||||||
help="Group to build the graph for",
|
help="Filter tasks by this group or role",
|
||||||
default=None)
|
default=None)
|
||||||
parser.add_argument("--stage", "-s",
|
|
||||||
help="Stage to build the graph for",
|
|
||||||
default='deployment')
|
|
||||||
parser.add_argument("--topology", "-t",
|
parser.add_argument("--topology", "-t",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Show the tasks topology"
|
help="Show the tasks topology"
|
||||||
@ -125,17 +168,17 @@ class IO(object):
|
|||||||
for task_file in cls.task_files(place):
|
for task_file in cls.task_files(place):
|
||||||
task_graph.load_yaml_file(task_file)
|
task_graph.load_yaml_file(task_file)
|
||||||
|
|
||||||
task_graph.process_data(stage=cls.args.stage, group=cls.args.group)
|
if cls.args.workbook:
|
||||||
|
IO.output(yaml.dump(task_graph.workbook))
|
||||||
|
return
|
||||||
|
|
||||||
|
task_graph.process_data(filter=cls.args.filter)
|
||||||
task_graph.build_graph()
|
task_graph.build_graph()
|
||||||
|
|
||||||
if cls.args.topology:
|
if cls.args.topology:
|
||||||
task_graph.show_topology()
|
task_graph.show_topology()
|
||||||
return
|
return
|
||||||
|
|
||||||
if cls.args.workbook:
|
|
||||||
IO.output(yaml.dump(task_graph.workbook))
|
|
||||||
return
|
|
||||||
|
|
||||||
if cls.args.clear_workbook:
|
if cls.args.clear_workbook:
|
||||||
IO.output(yaml.dump(task_graph.data))
|
IO.output(yaml.dump(task_graph.data))
|
||||||
return
|
return
|
||||||
@ -152,12 +195,20 @@ class TaskGraph(object):
|
|||||||
self.workbook = []
|
self.workbook = []
|
||||||
self.graph = networkx.DiGraph()
|
self.graph = networkx.DiGraph()
|
||||||
self._max_task_id_length = None
|
self._max_task_id_length = None
|
||||||
self._max_task_stage_length = None
|
|
||||||
|
|
||||||
self.options = {
|
self.options = {
|
||||||
'debug': False,
|
'debug': False,
|
||||||
'prog': 'dot',
|
'prog': 'dot',
|
||||||
'default_node': {
|
'default_node': {
|
||||||
|
'fillcolor': 'yellow',
|
||||||
|
},
|
||||||
|
'stage_node': {
|
||||||
|
'fillcolor': 'blue',
|
||||||
|
'shape': 'rectangle',
|
||||||
|
},
|
||||||
|
'group_node': {
|
||||||
|
'fillcolor': 'green',
|
||||||
|
'shape': 'rectangle',
|
||||||
},
|
},
|
||||||
'default_edge': {
|
'default_edge': {
|
||||||
},
|
},
|
||||||
@ -171,16 +222,11 @@ class TaskGraph(object):
|
|||||||
'global_node': {
|
'global_node': {
|
||||||
'style': 'filled',
|
'style': 'filled',
|
||||||
'shape': 'ellipse',
|
'shape': 'ellipse',
|
||||||
'fillcolor': 'yellow',
|
|
||||||
},
|
},
|
||||||
'global_edge': {
|
'global_edge': {
|
||||||
'style': 'solid',
|
'style': 'solid',
|
||||||
'arrowhead': 'vee',
|
'arrowhead': 'vee',
|
||||||
},
|
},
|
||||||
'non_task_types': [
|
|
||||||
'role',
|
|
||||||
'stage',
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
@ -189,46 +235,67 @@ class TaskGraph(object):
|
|||||||
self.workbook = []
|
self.workbook = []
|
||||||
|
|
||||||
def node_options(self, id):
|
def node_options(self, id):
|
||||||
|
if self.data.get(id, {}).get('type', None) == 'stage':
|
||||||
|
return self.options['stage_node']
|
||||||
|
if self.data.get(id, {}).get('type', None) == 'group':
|
||||||
|
return self.options['group_node']
|
||||||
return self.options['default_node']
|
return self.options['default_node']
|
||||||
|
|
||||||
def edge_options(self, id_from, id_to):
|
def edge_options(self, id_from, id_to):
|
||||||
return self.options['default_edge']
|
return self.options['default_edge']
|
||||||
|
|
||||||
def add_graph_node(self, id, options=None):
|
def add_graph_node(self, id, options=None):
|
||||||
if not options:
|
|
||||||
options = self.node_options(id)
|
|
||||||
if id not in self.data:
|
if id not in self.data:
|
||||||
return
|
return
|
||||||
|
if not options:
|
||||||
|
options = self.node_options(id)
|
||||||
self.graph.add_node(id, options)
|
self.graph.add_node(id, options)
|
||||||
|
|
||||||
def add_graph_edge(self, id_from, id_to, options=None):
|
def add_graph_edge(self, id_from, id_to, options=None):
|
||||||
if not options:
|
|
||||||
options = self.edge_options(id_from, id_to)
|
|
||||||
if id_from not in self.data:
|
if id_from not in self.data:
|
||||||
return
|
return
|
||||||
if id_to not in self.data:
|
if id_to not in self.data:
|
||||||
return
|
return
|
||||||
|
if not options:
|
||||||
|
options = self.edge_options(id_from, id_to)
|
||||||
self.graph.add_edge(id_from, id_to, options)
|
self.graph.add_edge(id_from, id_to, options)
|
||||||
|
|
||||||
def process_data(self, stage=None, group=None):
|
@staticmethod
|
||||||
|
def filter_by_group(node, filter=None):
|
||||||
|
# if group is not specified accept only the group tasks
|
||||||
|
# and show only them on the graph/list
|
||||||
|
# if there is a group, filter out group tasks
|
||||||
|
# and show only normal tasks in this group
|
||||||
|
type = node.get('type', None)
|
||||||
|
if not filter:
|
||||||
|
return type == 'group'
|
||||||
|
else:
|
||||||
|
if type == 'group':
|
||||||
|
return False
|
||||||
|
# always accept 'stage' tasks
|
||||||
|
if type == 'stage':
|
||||||
|
return True
|
||||||
|
# accept task only if it has matching group or role
|
||||||
|
# or its group or role is set to 'any'
|
||||||
|
if 'groups' in node:
|
||||||
|
if ('*' in node['groups']) or (filter in node['groups']):
|
||||||
|
return True
|
||||||
|
if 'role' in node:
|
||||||
|
if ('*' in node['role']) or (filter in node['role']):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def process_data(self, filter=None):
|
||||||
for node in self.workbook:
|
for node in self.workbook:
|
||||||
if not type(node) is dict:
|
if not type(node) is dict:
|
||||||
continue
|
continue
|
||||||
if not node.get('type', None) and node.get('id', None):
|
if not node.get('type', None) and node.get('id', None):
|
||||||
continue
|
continue
|
||||||
if node.get('type', None) in self.options['non_task_types']:
|
|
||||||
continue
|
|
||||||
if not 'stage' in node:
|
|
||||||
node['stage'] = 'deployment'
|
|
||||||
if not 'requires' in node:
|
if not 'requires' in node:
|
||||||
node['requires'] = []
|
node['requires'] = []
|
||||||
if not 'required_for' in node:
|
if not 'required_for' in node:
|
||||||
node['required_for'] = []
|
node['required_for'] = []
|
||||||
if not 'groups' in node:
|
if not self.filter_by_group(node, filter=filter):
|
||||||
node['groups'] = []
|
|
||||||
if stage and not node['stage'] == stage:
|
|
||||||
continue
|
|
||||||
if group and not group in node['groups']:
|
|
||||||
continue
|
continue
|
||||||
self.data[node['id']] = node
|
self.data[node['id']] = node
|
||||||
|
|
||||||
@ -247,30 +314,26 @@ class TaskGraph(object):
|
|||||||
self._max_task_id_length = len(max(self.data.keys(), key=len))
|
self._max_task_id_length = len(max(self.data.keys(), key=len))
|
||||||
return self._max_task_id_length
|
return self._max_task_id_length
|
||||||
|
|
||||||
@property
|
|
||||||
def max_task_stage_length(self):
|
|
||||||
if self._max_task_stage_length:
|
|
||||||
return self._max_task_stage_length
|
|
||||||
self._max_task_stage_length = max(map(lambda n: len(n['stage']),
|
|
||||||
self.data.values()))
|
|
||||||
return self._max_task_stage_length
|
|
||||||
|
|
||||||
def make_dot_graph(self):
|
def make_dot_graph(self):
|
||||||
return networkx.to_agraph(self.graph)
|
return networkx.to_agraph(self.graph)
|
||||||
|
|
||||||
def show_topology(self):
|
def show_topology(self):
|
||||||
number = 1
|
number = 1
|
||||||
for node in networkx.topological_sort(self.graph):
|
for node in networkx.topological_sort(self.graph):
|
||||||
groups = self.data.get(node, {}).get('groups', [])
|
type = self.data.get(node, {}).get('type', None)
|
||||||
groups = ', '.join(groups)
|
if type == 'stage':
|
||||||
stage = self.data.get(node, {}).get('stage', '')
|
node = '<' + node + '>'
|
||||||
IO.output(number, 5)
|
IO.output(number, fill=5, newline=False)
|
||||||
IO.output(node, self.max_task_id_length + 1)
|
IO.output(node, fill=self.max_task_id_length + 1)
|
||||||
IO.output(stage, self.max_task_stage_length + 1)
|
|
||||||
IO.output(groups)
|
|
||||||
number += 1
|
number += 1
|
||||||
|
|
||||||
def create_image(self, img_file):
|
def create_image(self, img_file):
|
||||||
|
try:
|
||||||
|
pygraphviz
|
||||||
|
except NameError:
|
||||||
|
IO.output('You need "pygraphviz" to draw the graph. '
|
||||||
|
'But you can use -t to view the topology.')
|
||||||
|
sys.exit(1)
|
||||||
graph = self.make_dot_graph()
|
graph = self.make_dot_graph()
|
||||||
for attr_name in self.options['global_graph']:
|
for attr_name in self.options['global_graph']:
|
||||||
graph.graph_attr[attr_name] = \
|
graph.graph_attr[attr_name] = \
|
||||||
|
Loading…
Reference in New Issue
Block a user