Test Failed
Pull Request — master (#38)
by
unknown
02:53
created

build.main.GenericFlow.__init__()   A

Complexity

Conditions 3

Size

Total Lines 16
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 16
dl 0
loc 16
rs 9.6
c 0
b 0
f 0
ccs 13
cts 13
cp 1
cc 3
nop 13
crap 3

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
"""Main module of amlight/flow_stats Kytos Network Application.
2
3
This NApp does operations with flows not covered by Kytos itself.
4
"""
5
# pylint: disable=too-many-return-statements,too-many-instance-attributes
6
# pylint: disable=too-many-arguments,too-many-branches,too-many-statements
7
8 1
import hashlib
9 1
import ipaddress
10 1
import json
11 1
from threading import Lock
12
13 1
from flask import jsonify, request
14 1
from kytos.core import KytosEvent, KytosNApp, log, rest
15 1
from kytos.core.helpers import listen_to
16 1
from napps.amlight.flow_stats.utils import format_request
17 1
from napps.kytos.of_core.v0x04.flow import Action as Action13
18 1
from napps.kytos.of_core.v0x04.match_fields import MatchFieldFactory
19
20
# pylint: disable=too-many-public-methods
21 1
class Main(KytosNApp):
22
    """Main class of amlight/flow_stats NApp.
23
24
    This class is the entry point for this napp.
25
    """
26 1
27
    def setup(self):
28
        """Replace the '__init__' method for the KytosNApp subclass.
29
30 1
        The setup method is automatically called by the controller when your
31 1
        application is loaded.
32 1
33 1
        So, if you have any setup routine, insert it here.
34 1
        """
35 1
        log.info('Starting Kytos/Amlight flow manager')
36 1
        self.switch_stats_xid = {}
37 1
        self.switch_stats_lock = {}
38 1
39 1
    def execute(self):
40 1
        """This method is executed right after the setup method execution.
41 1
42
        You can also use this method in loop mode if you add to the above setup
43 1
        method a line like the following example:
44
45
            self.execute_as_loop(30)  # 30-second interval.
46 1
        """
47 1
48
    def shutdown(self):
49
        """This method is executed when your napp is unloaded.
50
51
        If you have some cleanup procedure, insert it here.
52
        """
53
54
    def flow_from_id(self, flow_id):
55 1
        """Flow from given flow_id."""
56 1
        for switch in self.controller.switches.copy().values():
57 1
            try:
58 1
                for flow in switch.flows: 
59 1
                    if flow.id == flow_id:
60 1
                        return flow
61 1
            except KeyError:
62 1
                pass
63 1
        return None
64 1
65
    @rest('flow/match/<dpid>')
66 1
    def flow_match(self, dpid):
67
        """Return first flow matching request."""
68 1
        switch = self.controller.get_switch_by_dpid(dpid)
69
        flow = self.match_flows(switch, format_request(request.args), False)
70 1
        if flow:
71 1
            return jsonify(flow.to_dict())
72 1
        return "No match", 404
73 1
74 1
    @rest('flow/stats/<dpid>')
75 1
    def flow_stats(self, dpid):
76 1
        """Return all flows matching request."""
77 1
        switch = self.controller.get_switch_by_dpid(dpid)
78 1
        if not switch:
79 1
            return f"switch {dpid} not found", 404
80 1
        flows = self.match_flows(switch, format_request(request.args), True)
81 1
        flows = [flow.to_dict() for flow in flows]
82
        return jsonify(flows)
83 1
84
    @staticmethod
85 1
    def match_flows(switch, args, many=True):
86
        # pylint: disable=bad-staticmethod-argument
87
        """
88 1
        Match the packet in request against the flows installed in the switch.
89 1
90 1
        Try the match with each flow, in other. If many is True, tries the
91 1
        match with all flows, if False, tries until the first match.
92
        :param args: packet data
93 1
        :param many: Boolean, indicating whether to continue after matching the
94
                first flow or not
95
        :return: If many, the list of matched flows, or the matched flow
96
        """
97
        response = []
98
        try:
99
            for flow in switch.flows: 
100
                match = flow.do_match(args)
101
                if match:
102
                    if many:
103
                        response.append(match)
104
                    else:
105
                        response = match
106
                        break
107
        except AttributeError:
108
            return None
109
        if not many and isinstance(response, list):
110
            return None
111
        return response
112
113
    @staticmethod
114
    def match_and_apply(switch, args):
115
        # pylint: disable=bad-staticmethod-argument
116
        """Match flows and apply actions.
117 1
118 1
        Match given packet (in args) against the switch flows and,
119
        if a match flow is found, apply its actions."""
120 1
        flow = Main.match_flows(switch, args, False)
121 1
        port = None
122 1
        actions = None
123 1
        # pylint: disable=too-many-nested-blocks
124 1
        if flow:
125 1
            actions = flow.actions
126 1
            if switch.ofp_version == '0x04':
127 1
                for action in actions:
128 1
                    action_type = action.action_type
129 1
                    if action_type == 'output':
130
                        port = action.port
131
                    if action_type == 'push_vlan':
132
                        if 'vlan_vid' not in args:
133
                            args['vlan_vid'] = []
134
                        args['vlan_vid'].append(0)
135 1
                    if action_type == 'pop_vlan':
136 1
                        if 'vlan_vid' in args:
137
                            args['vlan_vid'].pop()
138
                            if len(args['vlan_vid']) == 0:
139
                                del args['vlan_vid']
140
                    if action_type == 'set_vlan':
141 1
                        args['vlan_vid'][-1] = action.vlan_id
142
        return flow, args, port
143 1
144 1
    @rest('packet_count/<flow_id>')
145
    def packet_count(self, flow_id):
146
        """Packet count of an specific flow."""
147
        flow = self.flow_from_id(flow_id)
148 1
        if flow is None:
149 1
            return "Flow does not exist", 404
150 1
        packet_stats = {
151 1
            'flow_id': flow_id,
152 1
            'packet_counter': flow.stats.packet_count,
153 1
            'packet_per_second': flow.stats.packet_count / flow.stats.duration_sec
154 1
            }
155 1
        return jsonify(packet_stats)
156 1
157
    @rest('bytes_count/<flow_id>')
158 1
    def bytes_count(self, flow_id):
159 1
        """Bytes count of an specific flow."""
160 1
        flow = self.flow_from_id(flow_id)
161 1
        if flow is None:
162 1
            return "Flow does not exist", 404
163
        bytes_stats = {
164 1
            'flow_id': flow_id,
165 1
            'bytes_counter': flow.stats.byte_count,
166 1
            'bits_per_second': flow.stats.byte_count * 8 / flow.stats.duration_sec
167 1
            }
168 1
        return jsonify(bytes_stats)
169 1
170 1
    @rest('packet_count/per_flow/<dpid>')
171
    def packet_count_per_flow(self, dpid):
172 1
        """Per flow packet count."""
173
        return self.flows_counters('packet_count',
174
                                   dpid,
175
                                   counter='packet_counter',
176
                                   rate='packet_per_second')
177
178 1
    @rest('packet_count/sum/<dpid>')
179
    def packet_count_sum(self, dpid):
180
        """Sum of packet count flow stats."""
181
        return self.flows_counters('packet_count',
182
                                   dpid,
183
                                   total=True)
184
185
    @rest('bytes_count/per_flow/<dpid>')
186
    def bytes_count_per_flow(self, dpid):
187
        """Per flow bytes count."""
188
        return self.flows_counters('byte_count',
189
                                   dpid,
190
                                   counter='bytes_counter',
191
                                   rate='bits_per_second')
192
193
    @rest('bytes_count/sum/<dpid>')
194
    def bytes_count_sum(self, dpid):
195
        """Sum of bytes count flow stats."""
196
        return self.flows_counters('byte_count',
197
                                   dpid,
198
                                   total=True)
199
200 1
    def flows_counters(self, field, dpid, counter=None, rate=None,
201
                       total=False):
202
        """Calculate flows statistics.
203
204
        The returned statistics are both per flow and for the sum of flows
205
        """
206 1
        # pylint: disable=too-many-arguments
207
        # pylint: disable=unused-variable
208
        start_date = request.args.get('start_date', 0)
209
        end_date = request.args.get('end_date', 0)
210
        # pylint: enable=unused-variable
211
212
        if total:
213
            count_flows = 0
214 1
        else:
215 1
            count_flows = []
216
            if not counter:
217 1
                counter = field
218 1
            if not rate:
219
                rate = field
220 1
221
        # We don't have statistics persistence yet, so for now this only works
222
        # for start and end equals to zero
223
        flows = self.controller.get_switch_by_dpid(dpid).flows 
224
225
        for flow in flows:
226
            count = getattr(flow, field)
227
            if total:
228
                count_flows += count
229 1
            else:
230
                per_second = count / flow.stats.duration_sec
231
                if rate.startswith('bits'):
232
                    per_second *= 8
233
                count_flows.append({'flow_id': flow.id,
234
                                    counter: count,
235 1
                                    rate: per_second})
236
237 1
        return jsonify(count_flows)
238 1
239 1
    @listen_to('kytos/of_core.flow_stats.received')
240 1
    def on_stats_received(self, event):
241 1
        """Capture flow stats messages for OpenFlow 1.3."""
242
        self.handle_stats_received(event)
243
244 1
    def handle_stats_received(self, event):
245
        """Handle flow stats messages for OpenFlow 1.3."""
246 1
        switch = event.content['switch']
247 1
        if 'replies_flows' in event.content:
248
            replies_flows = event.content['replies_flows']
249 1
            self.handle_stats_reply_received(switch, replies_flows)
250 1
251 1
    def handle_stats_reply_received(self, switch, replies_flows):
252 1
        """Iterate on the replies and set the list of flows"""
253 1
        switch.flows = replies_flows
254
        switch.flows.sort(
255 1
                    key=lambda f: (f.priority, f.stats.duration_sec),
256 1
                    reverse=True
257
                    )
258