Completed
Push — master ( d1f0a7...3b2ece )
by Edward
21:04 queued 05:38
created

Grapher   A

Complexity

Total Complexity 4

Size/Duplication

Total Lines 28
Duplicated Lines 0 %
Metric Value
wmc 4
dl 0
loc 28
rs 10

1 Method

Rating   Name   Duplication   Size   Complexity  
B generate_graph() 0 27 4
1
#!/usr/bin/env python
2
3
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
4
# contributor license agreements.  See the NOTICE file distributed with
5
# this work for additional information regarding copyright ownership.
6
# The ASF licenses this file to You under the Apache License, Version 2.0
7
# (the "License"); you may not use this file except in compliance with
8
# the License.  You may obtain a copy of the License at
9
#
10
#     http://www.apache.org/licenses/LICENSE-2.0
11
#
12
# Unless required by applicable law or agreed to in writing, software
13
# distributed under the License is distributed on an "AS IS" BASIS,
14
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
# See the License for the specific language governing permissions and
16
# limitations under the License.
17
18
19
"""
20
Visualize the links created by rules.
21
22
1. requires graphviz
23
        pip install graphviz
24
        apt-get install graphviz
25
26
To run :
27
    ./st2-analyze-links.py --action_ref <action-ref>
28
29
The command must run on a StackStorm box.
30
"""
31
32
import eventlet
33
import os
34
import sets
35
import sys
36
37
from oslo_config import cfg
38
39
from st2common import config
40
from st2common.persistence.rule import Rule
41
from st2common.service_setup import db_setup
42
43
try:
44
    from graphviz import Digraph
45
except ImportError:
46
    msg = ('Missing "graphviz" dependency. You can install it using pip: \n'
47
           'pip install graphviz')
48
    raise ImportError(msg)
49
50
51
def do_register_cli_opts(opts, ignore_errors=False):
52
    for opt in opts:
53
        try:
54
            cfg.CONF.register_cli_opt(opt)
55
        except:
56
            if not ignore_errors:
57
                raise
58
59
60
def _monkey_patch():
61
    eventlet.monkey_patch(
62
        os=True,
63
        select=True,
64
        socket=True,
65
        thread=False if '--use-debugger' in sys.argv else True,
66
        time=True)
67
68
69
class RuleLink(object):
70
71
    def __init__(self, source_action_ref, rule_ref, dest_action_ref):
72
        self._source_action_ref = source_action_ref
73
        self._rule_ref = rule_ref
74
        self._dest_action_ref = dest_action_ref
75
76
    def __str__(self):
77
        return '(%s -> %s -> %s)' % (self._source_action_ref, self._rule_ref, self._dest_action_ref)
78
79
80
class LinksAnalyzer(object):
81
82
    def __init__(self):
83
        self._rule_link_by_action_ref = {}
84
        self._rules = {}
85
86
    def analyze(self, root_action_ref, link_tigger_ref):
87
        rules = Rule.query(trigger=link_tigger_ref, enabled=True)
88
        # pprint.pprint([rule.ref for rule in rules])
89
        for rule in rules:
90
            source_action_ref = self._get_source_action_ref(rule)
91
            if not source_action_ref:
92
                print 'No source_action_ref for rule %s' % rule.ref
93
                continue
94
            rule_links = self._rules.get(source_action_ref, None)
95
            if rule_links is None:
96
                rule_links = []
97
                self._rules[source_action_ref] = rule_links
98
            rule_links.append(RuleLink(source_action_ref=source_action_ref, rule_ref=rule.ref,
99
                                       dest_action_ref=rule.action.ref))
100
        analyzed = self._do_analyze(action_ref=root_action_ref)
101
        for (depth, rule_link) in analyzed:
102
            print '%s%s' % ('  ' * depth, rule_link)
103
        return analyzed
104
105
    def _get_source_action_ref(self, rule):
106
        criteria = rule.criteria
107
        source_action_ref = criteria.get('trigger.action_name', None)
108
        if not source_action_ref:
109
            source_action_ref = criteria.get('trigger.action_ref', None)
110
        return source_action_ref['pattern'] if source_action_ref else None
111
112
    def _do_analyze(self, action_ref, rule_links=None, processed=None, depth=0):
113
        if processed is None:
114
            processed = sets.Set()
115
        if rule_links is None:
116
            rule_links = []
117
        processed.add(action_ref)
118
        for rule_link in self._rules.get(action_ref, []):
119
            rule_links.append((depth, rule_link))
120
            if rule_link._dest_action_ref in processed:
121
                continue
122
            self._do_analyze(rule_link._dest_action_ref, rule_links=rule_links,
123
                             processed=processed, depth=depth + 1)
124
        return rule_links
125
126
127
class Grapher(object):
128
    def generate_graph(self, rule_links, out_file):
129
        graph_label = 'Rule based visualizer'
130
131
        graph_attr = {
132
            'rankdir': 'TD',
133
            'labelloc': 't',
134
            'fontsize': '15',
135
            'label': graph_label
136
        }
137
        node_attr = {}
138
        dot = Digraph(comment='Rule based links visualization',
139
                      node_attr=node_attr, graph_attr=graph_attr, format='png')
140
141
        nodes = sets.Set()
142
        for _, rule_link in rule_links:
143
            print rule_link._source_action_ref
144
            if rule_link._source_action_ref not in nodes:
145
                nodes.add(rule_link._source_action_ref)
146
                dot.node(rule_link._source_action_ref, rule_link._source_action_ref)
147
            if rule_link._dest_action_ref not in nodes:
148
                nodes.add(rule_link._dest_action_ref)
149
                dot.node(rule_link._dest_action_ref, rule_link._dest_action_ref)
150
            dot.edge(rule_link._source_action_ref, rule_link._dest_action_ref, constraint='true',
151
                     label=rule_link._rule_ref)
152
        output_path = os.path.join(os.getcwd(), out_file)
153
        dot.format = 'png'
154
        dot.render(output_path)
155
156
157
def main():
158
    _monkey_patch()
159
160
    cli_opts = [
161
        cfg.StrOpt('action_ref', default=None,
162
                   help='Root action to begin analysis.'),
163
        cfg.StrOpt('link_trigger_ref', default='core.st2.generic.actiontrigger',
164
                   help='Root action to begin analysis.'),
165
        cfg.StrOpt('out_file', default='pipeline')
166
    ]
167
    do_register_cli_opts(cli_opts)
168
    config.parse_args()
169
    db_setup()
170
    rule_links = LinksAnalyzer().analyze(cfg.CONF.action_ref, cfg.CONF.link_trigger_ref)
171
    Grapher().generate_graph(rule_links, cfg.CONF.out_file)
172
173
174
if __name__ == '__main__':
175
    main()
176