Passed
Pull Request — master (#85)
by
unknown
03:07
created

build.main.Main.tracepath()   D

Complexity

Conditions 13

Size

Total Lines 56
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 41
CRAP Score 13.0022

Importance

Changes 0
Metric Value
cc 13
eloc 48
nop 3
dl 0
loc 56
rs 4.2
c 0
b 0
f 0
ccs 41
cts 42
cp 0.9762
crap 13.0022

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

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