Test Failed
Pull Request — master (#49)
by
unknown
03:08
created

build.main.Main.get_stored_flows()   A

Complexity

Conditions 5

Size

Total Lines 15
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 20.741

Importance

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