Passed
Pull Request — master (#73)
by
unknown
03:04
created

build.main   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 259
Duplicated Lines 0 %

Test Coverage

Coverage 91.03%

Importance

Changes 0
Metric Value
eloc 175
dl 0
loc 259
rs 5.04
c 0
b 0
f 0
ccs 142
cts 156
cp 0.9103
wmc 57

12 Methods

Rating   Name   Duplication   Size   Complexity  
A Main.execute() 0 2 1
A Main.shutdown() 0 7 1
A Main.setup() 0 14 1
A Main.get_traces() 0 10 2
A Main.update_circuits() 0 5 2
A Main.trace_step() 0 22 4
A Main.has_loop() 0 8 4
D Main.match_and_apply() 0 30 12
C Main.tracepath() 0 53 10
A Main.trace() 0 11 2
C Main.match_flows() 0 30 9
C Main.do_match() 0 23 9

How to fix   Complexity   

Complexity

Complex classes like build.main 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
"""Main module of amlight/sdntrace_cp Kytos Network Application.
2
3
Run tracepaths on OpenFlow in the Control Plane
4
"""
5
6 1
import ipaddress
7 1
from datetime import datetime
8
9 1
from flask import jsonify, request
10 1
from kytos.core import KytosNApp, log, rest
11 1
from napps.amlight.sdntrace_cp import settings
12 1
from napps.amlight.sdntrace_cp.automate import Automate
13 1
from napps.amlight.sdntrace_cp.utils import (convert_entries,
14
                                             convert_list_entries,
15
                                             find_endpoint, get_stored_flows,
16
                                             prepare_json)
17
18
19 1
class Main(KytosNApp):
20
    """Main class of amlight/sdntrace_cp NApp.
21
22
    This application gets the list of flows from the switches
23
    and uses it to trace paths without using the data plane.
24
    """
25
26 1
    def setup(self):
27
        """Replace the '__init__' method for the KytosNApp subclass.
28
29
        The setup method is automatically called by the controller when your
30
        application is loaded.
31
32
        """
33 1
        log.info("Starting Kytos SDNTrace CP App!")
34
35 1
        self.traces = {}
36 1
        self.last_id = 30000
37 1
        self.automate = Automate(self)
38 1
        self.automate.schedule_traces()
39 1
        self.automate.schedule_important_traces()
40
41 1
    def execute(self):
42
        """This method is executed right after the setup method execution.
43
44
        You can also use this method in loop mode if you add to the above setup
45
        method a line like the following example:
46
47
            self.execute_as_loop(30)  # 30-second interval.
48
        """
49
50 1
    def shutdown(self):
51
        """This method is executed when your napp is unloaded.
52
53
        If you have some cleanup procedure, insert it here.
54
        """
55
        self.automate.unschedule_ids()
56
        self.automate.sheduler_shutdown(wait=False)
57
58 1
    @rest('/trace', methods=['PUT'])
59 1
    def trace(self):
60
        """Trace a path."""
61 1
        result = []
62 1
        entries = request.get_json()
63 1
        entries = convert_entries(entries)
64 1
        if not entries:
65
            return "Bad request", 400
66 1
        stored_flows = get_stored_flows()
67 1
        result = self.tracepath(entries, stored_flows)
68 1
        return jsonify(prepare_json(result))
69
70 1
    @rest('/traces', methods=['PUT'])
71 1
    def get_traces(self):
72
        """For bulk requests."""
73 1
        entries = request.get_json()
74 1
        entries = convert_list_entries(entries)
75 1
        stored_flows = get_stored_flows()
76 1
        results = []
77 1
        for entry in entries:
78 1
            results.append(self.tracepath(entry, stored_flows))
79 1
        return jsonify(prepare_json(results))
80
81 1
    def tracepath(self, entries, stored_flows):
82
        """Trace a path for a packet represented by entries."""
83 1
        self.last_id += 1
84 1
        trace_id = self.last_id
85 1
        trace_result = []
86 1
        trace_type = 'starting'
87 1
        do_trace = True
88 1
        while do_trace:
89 1
            if 'dpid' not in entries or 'in_port' not in entries:
90 1
                break
91 1
            trace_step = {'in': {'dpid': entries['dpid'],
92
                                 'port': entries['in_port'],
93
                                 'time': str(datetime.now()),
94
                                 'type': trace_type}}
95 1
            if 'dl_vlan' in entries:
96 1
                trace_step['in'].update({'vlan': entries['dl_vlan'][-1]})
97
98 1
            switch = self.controller.get_switch_by_dpid(entries['dpid'])
99 1
            if not switch:
100 1
                trace_step['in']['type'] = 'last'
101 1
                trace_result.append(trace_step)
102 1
                break
103 1
            result = self.trace_step(switch, entries, stored_flows)
104 1
            if result:
105 1
                out = {'port': result['out_port']}
106 1
                if 'dl_vlan' in result['entries']:
107 1
                    out.update({'vlan': result['entries']['dl_vlan'][-1]})
108 1
                trace_step.update({
109
                    'out': out
110
                })
111 1
                if 'dpid' in result:
112 1
                    next_step = {'dpid': result['dpid'],
113
                                 'port': result['in_port']}
114 1
                    entries = result['entries']
115 1
                    entries['dpid'] = result['dpid']
116 1
                    entries['in_port'] = result['in_port']
117 1
                    if self.has_loop(next_step, trace_result):
118
                        # Loop
119 1
                        trace_step['in']['type'] = 'loop'
120 1
                        do_trace = False
121
                    else:
122 1
                        trace_type = 'intermediary'
123
                else:
124 1
                    trace_step['in']['type'] = 'last'
125 1
                    do_trace = False
126
            else:
127 1
                trace_step['in']['type'] = 'incomplete'
128 1
                do_trace = False
129 1
            trace_result.append(trace_step)
130 1
        self.traces.update({
131
            trace_id: trace_result
132
        })
133 1
        return trace_result
134
135 1
    @staticmethod
136 1
    def has_loop(trace_step, trace_result):
137
        """Check if there is a loop in the trace result."""
138 1
        for trace in trace_result:
139 1
            if trace['in']['dpid'] == trace_step['dpid'] and \
140
                            trace['in']['port'] == trace_step['port']:
141 1
                return True
142 1
        return False
143
144 1
    def trace_step(self, switch, entries, stored_flows):
145
        """Perform a trace step.
146
147
        Match the given fields against the switch's list of flows."""
148 1
        flow, entries, port = self.match_and_apply(
149
                                                    switch,
150
                                                    entries,
151
                                                    stored_flows
152
                                                )
153
154 1
        if not flow or not port:
155 1
            return None
156
157 1
        endpoint = find_endpoint(switch, port)
158 1
        if endpoint is None:
159 1
            return {'out_port': port,
160
                    'entries': entries}
161
162 1
        return {'dpid': endpoint.switch.dpid,
163
                'in_port': endpoint.port_number,
164
                'out_port': port,
165
                'entries': entries}
166
167 1
    def update_circuits(self):
168
        """Update the list of circuits after a flow change."""
169
        # pylint: disable=unused-argument
170 1
        if settings.FIND_CIRCUITS_IN_FLOWS:
171 1
            self.automate.find_circuits()
172
173 1
    @classmethod
174 1
    def do_match(cls, flow, args):
175
        """Match a packet against this flow (OF1.3)."""
176
        # pylint: disable=consider-using-dict-items
177 1
        if ('match' not in flow['flow']) or (len(flow['flow']['match']) == 0):
178 1
            return False
179 1
        for name in flow['flow']['match']:
180 1
            field_flow = flow['flow']['match'][name]
181 1
            if name not in args:
182
                return False
183 1
            if name == 'dl_vlan':
184 1
                field = args[name][-1]
185
            else:
186 1
                field = args[name]
187 1
            if name not in ('ipv4_src', 'ipv4_dst', 'ipv6_src', 'ipv6_dst'):
188 1
                if field_flow != field:
189 1
                    return False
190
            else:
191
                packet_ip = int(ipaddress.ip_address(field))
192
                ip_addr = flow['flow']['match'][name]
193
                if packet_ip & ip_addr.netmask != ip_addr.address:
194
                    return False
195 1
        return flow
196
197 1
    def match_flows(self, switch, args, stored_flows, many=True):
198
        # pylint: disable=bad-staticmethod-argument
199
        """
200
        Match the packet in request against the stored flows from flow_manager.
201
        Try the match with each flow, in other. If many is True, tries the
202
        match with all flows, if False, tries until the first match.
203
        :param args: packet data
204
        :param many: Boolean, indicating whether to continue after matching the
205
                first flow or not
206
        :return: If many, the list of matched flows, or the matched flow
207
        """
208 1
        if switch.dpid not in stored_flows:
209
            return None
210 1
        response = []
211 1
        if switch.dpid not in stored_flows:
212
            return None
213 1
        try:
214 1
            for flow in stored_flows[switch.dpid]:
215 1
                match = Main.do_match(flow, args)
216 1
                if match:
217 1
                    if many:
218
                        response.append(match)
219
                    else:
220 1
                        response = match
221 1
                        break
222
        except AttributeError:
223
            return None
224 1
        if not many and isinstance(response, list):
225 1
            return None
226 1
        return response
227
228
    # pylint: disable=redefined-outer-name
229 1
    def match_and_apply(self, switch, args, stored_flows):
230
        # pylint: disable=bad-staticmethod-argument
231
        """Match flows and apply actions.
232
        Match given packet (in args) against
233
        the stored flows (from flow_manager) and,
234
        if a match flow is found, apply its actions."""
235 1
        flow = self.match_flows(switch, args, stored_flows, False)
236 1
        port = None
237 1
        actions = []
238
        # pylint: disable=too-many-nested-blocks
239 1
        if not flow or switch.ofp_version != '0x04':
240 1
            return flow, args, port
241 1
        if 'actions' in flow['flow']:
242 1
            actions = flow['flow']['actions']
243 1
        for action in actions:
244 1
            action_type = action['action_type']
245 1
            if action_type == 'output':
246 1
                port = action['port']
247 1
            if action_type == 'push_vlan':
248 1
                if 'dl_vlan' not in args:
249
                    args['dl_vlan'] = []
250 1
                args['dl_vlan'].append(0)
251 1
            if action_type == 'pop_vlan':
252 1
                if 'dl_vlan' in args:
253 1
                    args['dl_vlan'].pop()
254 1
                    if len(args['dl_vlan']) == 0:
255 1
                        del args['dl_vlan']
256 1
            if action_type == 'set_vlan':
257 1
                args['dl_vlan'][-1] = action['vlan_id']
258
        return flow, args, port
259