Passed
Pull Request — master (#36)
by Vinicius
03:14
created

build.automate   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 231
Duplicated Lines 0 %

Test Coverage

Coverage 93.15%

Importance

Changes 0
Metric Value
eloc 175
dl 0
loc 231
rs 6
c 0
b 0
f 0
ccs 136
cts 146
cp 0.9315
wmc 55

12 Methods

Rating   Name   Duplication   Size   Complexity  
A Automate._check_trace() 0 11 5
A Automate.schedule_id() 0 6 2
A Automate.run_traces() 0 15 4
B Automate.get_circuit() 0 15 8
A Automate.__init__() 0 6 1
B Automate.run_important_traces() 0 42 5
F Automate.find_circuits() 0 57 17
A Automate.check_step() 0 5 1
A Automate.sheduler_shutdown() 0 3 1
A Automate.schedule_traces() 0 17 4
A Automate.schedule_important_traces() 0 17 4
A Automate.unschedule_ids() 0 8 3

How to fix   Complexity   

Complexity

Complex classes like build.automate often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""Automate circuit traces."""
2
3 1
import time
4
5 1
import requests
6 1
from kytos.core import KytosEvent, log
7 1
from napps.amlight.sdntrace_cp import settings
8 1
from napps.amlight.sdntrace_cp.scheduler import Scheduler
9 1
from napps.amlight.sdntrace_cp.utils import clean_circuits, format_result
10 1
from pyof.v0x01.common.phy_port import Port as Port10
11 1
from pyof.v0x04.common.port import PortNo as Port13
12
13
14 1
class Automate:
15
    """Find all circuits and automate trace execution."""
16
17 1
    def __init__(self, tracer):
18 1
        self._tracer = tracer
19 1
        self._circuits = []
20 1
        self.find_circuits()
21 1
        self.ids = set()
22 1
        self.scheduler = Scheduler()
23
24
    # pylint: disable=too-many-nested-blocks, too-many-branches
25 1
    def find_circuits(self):
26
        """Discover all circuits in a topology.
27
28
        Using the list of flows per switch, run control plane
29
        traces to find a list of circuits."""
30 1
        all_flows = {}
31 1
        circuits = []
32
33 1
        for switch in self._tracer.controller.switches.copy().values():
34 1
            all_flows[switch] = []
35 1
            if switch.ofp_version == '0x01':
36
                controller_port = Port10.OFPP_CONTROLLER
37
            else:
38 1
                controller_port = Port13.OFPP_CONTROLLER
39
40 1
            try:
41 1
                for flow in switch.generic_flows:
42 1
                    action_ok = False
43 1
                    in_port_ok = False
44 1
                    if 'in_port' in flow.match and flow.match['in_port'] != 0:
45 1
                        in_port_ok = True
46 1
                    if in_port_ok:
47 1
                        for action in flow.actions:
48 1
                            if action.action_type == 'output' \
49
                                    and action.port != controller_port:
50 1
                                action_ok = True
51 1
                    if action_ok:
52 1
                        all_flows[switch].append(flow)
53
            except AttributeError:
54
                pass
55
56 1
        for switch, flows in all_flows.items():
57 1
            for flow in flows:
58 1
                in_port = flow.match['in_port']
59 1
                vlan = None
60 1
                if 'vlan_vid' in flow.match:
61
                    vlan = flow.match['vlan_vid']
62 1
                if switch.ofp_version == '0x04':
63 1
                    in_port = in_port.value
64 1
                    if vlan:
65
                        vlan = vlan.value
66 1
                entries = {
67
                    'trace': {
68
                        'switch': {
69
                            'dpid': switch.dpid,
70
                            'in_port': in_port
71
                        },
72
                        'eth': {
73
                            'dl_vlan': vlan
74
                        }
75
                    }
76
                }
77 1
                result = self._tracer.tracepath(entries)
78 1
                circuits.append({'circuit': format_result(result),
79
                                 'entries': entries})
80
81 1
        self._circuits = clean_circuits(circuits, self._tracer.controller)
82
83 1
    def run_traces(self):
84
        """Run traces for all circuits."""
85
86 1
        results = []
87 1
        for circuit in self._circuits:
88 1
            entries = circuit['entries']
89 1
            result = self._tracer.tracepath(entries)
90 1
            try:
91 1
                result = format_result(result)
92 1
                if result != circuit['circuit']:
93 1
                    results.append(circuit)
94
            except KeyError:
95
                results.append(circuit)
96 1
        log.debug('Results %s, size %s', results, len(self._circuits))
97 1
        return results
98
99 1
    def get_circuit(self, circuit):
100
        """Find the given circuit in the list of circuits."""
101 1
        for steps in self._circuits:
102 1
            endpoint_a = steps['circuit'][0]
103 1
            endpoint_z = steps['circuit'][-1]
104
            # pylint: disable=too-many-boolean-expressions
105 1
            if (circuit['dpid_a'] == endpoint_a['dpid'] and
106
                    circuit['port_a'] == endpoint_a['in_port'] and
107
                    circuit['vlan_a'] == endpoint_a['in_vlan'] and
108
                    circuit['dpid_z'] == endpoint_z['dpid'] and
109
                    circuit['port_z'] == endpoint_z['out_port'] and
110
                    circuit['vlan_z'] == endpoint_z['out_vlan']):
111
112 1
                return steps['circuit']
113 1
        return None
114
115 1
    def _check_trace(self, circuit, trace):
116 1
        steps = self.get_circuit(circuit)
117 1
        if steps:
118 1
            if len(steps) != len(trace) - 1:
119 1
                return False
120 1
            for i, step in enumerate(steps):
121 1
                if not self.check_step(step, trace[i]):
122 1
                    return False
123
        else:
124 1
            return False
125 1
        return True
126
127 1
    def run_important_traces(self):
128
        """Run SDNTrace in data plane for important circuits as defined
129
            by user."""
130 1
        event = KytosEvent(name='amlight/kytos_courier.slack_send')
131 1
        content = {
132
            'channel': settings.SLACK_CHANNEL,
133
            'source': 'amlight/sdntrace_cp'
134
        }
135
136 1
        try:
137 1
            important_circuits = settings.IMPORTANT_CIRCUITS
138
        except AttributeError:
139
            return
140
141 1
        for circuit in important_circuits:
142 1
            entries = {
143
                'trace': {
144
                    'switch': {
145
                        'dpid': circuit['dpid_a'],
146
                        'in_port': circuit['port_a']
147
                    },
148
                    'eth': {
149
                        'dl_vlan': circuit['vlan_a']
150
                    }
151
                }
152
            }
153 1
            result = requests.put(settings.SDNTRACE_URL, json=entries)
154 1
            trace = result.json()
155 1
            trace_id = trace['result']['trace_id']
156 1
            step_type = None
157 1
            while step_type != 'last':
158 1
                time.sleep(5)
159 1
                result = requests.get(f'{settings.SDNTRACE_URL}/{trace_id}')
160 1
                trace = result.json()
161 1
                step_type = trace['result'][-1]['type']
162 1
            check = self._check_trace(circuit, trace['result'])
163 1
            if check is False:
164 1
                content['m_body'] = 'Trace in data plane different from ' \
165
                                    'trace in control plane for circuit ' \
166
                                    f'{circuit}'
167 1
                event.content['message'] = content
168 1
                self._tracer.controller.buffers.app.put(event)
169
170 1
    def schedule_id(self, id_):
171
        """Keep track of scheduled ids"""
172 1
        if not isinstance(id_, str):
173 1
            raise AttributeError("Invalid id type, for schedule.")
174 1
        self.ids.add(id_)
175 1
        return id_
176
177 1
    def schedule_traces(self, settings_=settings):
178
        """Check for invalid arguments from schedule"""
179 1
        if settings_.TRIGGER_SCHEDULE_TRACES:
180 1
            id_ = self.schedule_id('automatic_traces')
181 1
            trigger = settings_.SCHEDULE_TRIGGER
182 1
            kwargs = settings_.SCHEDULE_ARGS
183 1
            if (not isinstance(kwargs, dict) or
184
                    not isinstance(trigger, str)):
185 1
                raise AttributeError("Invalid attributes for "
186
                                     "job to be scheduled.")
187 1
            trigger_args = {}
188 1
            trigger_args['kwargs'] = {'trigger': trigger}
189 1
            trigger_args['kwargs'].update(kwargs)
190 1
            self.scheduler.add_callable(id_, self.run_traces,
191
                                        **trigger_args['kwargs'])
192 1
            return self.scheduler.get_job(id_)
193 1
        return None
194
195 1
    def schedule_important_traces(self, settings_=settings):
196
        """Check for invalid important arguments from schedule"""
197 1
        if settings_.TRIGGER_IMPORTANT_CIRCUITS:
198 1
            id_ = self.schedule_id('automatic_important_traces')
199 1
            trigger = settings_.IMPORTANT_CIRCUITS_TRIGGER
200 1
            kwargs = settings_.IMPORTANT_CIRCUITS_ARGS
201 1
            if (not isinstance(kwargs, dict) or
202
                    not isinstance(trigger, str)):
203 1
                raise AttributeError("Invalid attributes for "
204
                                     "job to be scheduled.")
205 1
            trigger_args = {}
206 1
            trigger_args['kwargs'] = {'trigger': trigger}
207 1
            trigger_args['kwargs'].update(kwargs)
208 1
            return self.scheduler.add_callable(id_,
209
                                               self.run_important_traces,
210
                                               **trigger_args['kwargs'])
211 1
        return None
212
213 1
    def unschedule_ids(self, id_set=None):
214
        """Remove ids to be unschedule"""
215 1
        if id_set is None:
216 1
            id_set = self.ids.copy()
217 1
        while id_set:
218 1
            id_ = id_set.pop()
219 1
            self.ids.discard(id_)
220 1
            self.scheduler.remove_job(id_)
221
222 1
    def sheduler_shutdown(self, wait):
223
        """Shutdown scheduler"""
224
        self.scheduler.shutdown(wait)
225
226 1
    @staticmethod
227 1
    def check_step(circuit_step, trace_step):
228
        """Check if a step in SDNTrace in data plane is what it should"""
229 1
        return (circuit_step['dpid'] == trace_step['dpid'] and
230
                circuit_step['in_port'] == trace_step['port'])
231