Passed
Pull Request — master (#42)
by
unknown
03:02
created

build.main.Main.match_flows()   B

Complexity

Conditions 7

Size

Total Lines 28
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 39.826

Importance

Changes 0
Metric Value
eloc 16
dl 0
loc 28
rs 8
c 0
b 0
f 0
ccs 2
cts 16
cp 0.125
cc 7
nop 3
crap 39.826
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
from flask import jsonify, request
9 1
from kytos.core import KytosNApp, log, rest
10 1
from kytos.core.helpers import listen_to
11
12
13
# pylint: disable=too-many-public-methods
14 1
class Main(KytosNApp):
15
    """Main class of amlight/flow_stats NApp.
16
    This class is the entry point for this napp.
17
    """
18
19 1
    def setup(self):
20
        """Replace the '__init__' method for the KytosNApp subclass.
21
        The setup method is automatically called by the controller when your
22
        application is loaded.
23
        So, if you have any setup routine, insert it here.
24
        """
25 1
        log.info('Starting Kytos/Amlight flow manager')
26 1
        self.flows_stats_dict = {}
27
28 1
    def execute(self):
29
        """This method is executed right after the setup method execution.
30
        You can also use this method in loop mode if you add to the above setup
31
        method a line like the following example:
32
            self.execute_as_loop(30)  # 30-second interval.
33
        """
34
35 1
    def shutdown(self):
36
        """This method is executed when your napp is unloaded.
37
        If you have some cleanup procedure, insert it here.
38
        """
39
40 1
    def flow_from_id(self, flow_id):
41
        """Flow from given flow_id."""
42 1
        return self.flows_stats_dict.get(flow_id)
43
44 1
    def flow_stats_by_dpid_flow_id(self, dpids):
45
        """ Auxiliar funcion for v1/flow/stats endpoint implementation.
46
        """
47
        flow_stats_by_id = {}
48
        flows_stats_dict_copy = self.flows_stats_dict.copy()
49
        for flow_id, flow in flows_stats_dict_copy.items():
50
            dpid = flow.switch.dpid
51
            if dpid in dpids:
52
                if dpid not in flow_stats_by_id:
53
                    flow_stats_by_id[dpid] = {}
54
                flow_stats_by_id[dpid].update({flow_id: flow.stats.as_dict()})
55
        return flow_stats_by_id
56
57 1
    @rest('v1/flow/stats')
58 1
    def flow_stats(self):
59
        """Return the flows stats by dpid.
60
        Return the stats of all flows if dpid is None
61
        """
62 1
        args = request.args
63 1
        dpids = args.getlist("dpid", type=str)
64 1
        if len(dpids) == 0:
65
            dpids = [sw.dpid for sw in self.controller.switches.values()]
66 1
        flow_stats_by_id = self.flow_stats_by_dpid_flow_id(dpids)
67 1
        return jsonify(flow_stats_by_id)
68
69 1
    @rest('packet_count/<flow_id>')
70 1
    def packet_count(self, flow_id):
71
        """Packet count of an specific flow."""
72 1
        flow = self.flow_from_id(flow_id)
73 1
        if flow is None:
74 1
            return "Flow does not exist", 404
75 1
        packet_stats = {
76
            'flow_id': flow_id,
77
            'packet_counter': flow.stats.packet_count,
78
            'packet_per_second':
79
                flow.stats.packet_count / flow.stats.duration_sec
80
            }
81 1
        return jsonify(packet_stats)
82
83 1
    @rest('bytes_count/<flow_id>')
84 1
    def bytes_count(self, flow_id):
85
        """Bytes count of an specific flow."""
86 1
        flow = self.flow_from_id(flow_id)
87 1
        if flow is None:
88 1
            return "Flow does not exist", 404
89 1
        bytes_stats = {
90
            'flow_id': flow_id,
91
            'bytes_counter': flow.stats.byte_count,
92
            'bits_per_second':
93
                flow.stats.byte_count * 8 / flow.stats.duration_sec
94
            }
95 1
        return jsonify(bytes_stats)
96
97 1
    @rest('packet_count/per_flow/<dpid>')
98 1
    def packet_count_per_flow(self, dpid):
99
        """Per flow packet count."""
100 1
        return self.flows_counters('packet_count',
101
                                   dpid,
102
                                   counter='packet_counter',
103
                                   rate='packet_per_second')
104
105 1
    @rest('packet_count/sum/<dpid>')
106 1
    def packet_count_sum(self, dpid):
107
        """Sum of packet count flow stats."""
108 1
        return self.flows_counters('packet_count',
109
                                   dpid,
110
                                   total=True)
111
112 1
    @rest('bytes_count/per_flow/<dpid>')
113 1
    def bytes_count_per_flow(self, dpid):
114
        """Per flow bytes count."""
115 1
        return self.flows_counters('byte_count',
116
                                   dpid,
117
                                   counter='bytes_counter',
118
                                   rate='bits_per_second')
119
120 1
    @rest('bytes_count/sum/<dpid>')
121 1
    def bytes_count_sum(self, dpid):
122
        """Sum of bytes count flow stats."""
123 1
        return self.flows_counters('byte_count',
124
                                   dpid,
125
                                   total=True)
126
127 1
    def flows_counters(self, field, dpid, counter=None, rate=None,
128
                       total=False):
129
        """Calculate flows statistics.
130
        The returned statistics are both per flow and for the sum of flows
131
        """
132
        # pylint: disable=too-many-arguments
133
        # pylint: disable=unused-variable
134 1
        start_date = request.args.get('start_date', 0)
135 1
        end_date = request.args.get('end_date', 0)
136
        # pylint: enable=unused-variable
137
138 1
        if total:
139 1
            count_flows = 0
140
        else:
141 1
            count_flows = []
142 1
            if not counter:
143
                counter = field
144 1
            if not rate:
145
                rate = field
146
147
        # We don't have statistics persistence yet, so for now this only works
148
        # for start and end equals to zero
149 1
        flows = self.flow_stats_by_dpid_flow_id([dpid])
150 1
        flows = flows[dpid]
151
152 1
        for flow_id, stats in flows.items():
153 1
            count = stats[field]
154 1
            if total:
155 1
                count_flows += count
156
            else:
157 1
                per_second = count / stats['duration_sec']
158 1
                if rate.startswith('bits'):
159 1
                    per_second *= 8
160 1
                count_flows.append({'flow_id': flow_id,
161
                                    counter: count,
162
                                    rate: per_second})
163
164 1
        return jsonify(count_flows)
165
166 1
    @listen_to('kytos/of_core.flow_stats.received')
167 1
    def on_stats_received(self, event):
168
        """Capture flow stats messages for OpenFlow 1.3."""
169
        self.handle_stats_received(event)
170
171 1
    def handle_stats_received(self, event):
172
        """Handle flow stats messages for OpenFlow 1.3."""
173 1
        if 'replies_flows' in event.content:
174 1
            replies_flows = event.content['replies_flows']
175 1
            self.handle_stats_reply_received(replies_flows)
176
177 1
    def handle_stats_reply_received(self, replies_flows):
178
        """Update the set of flows stats"""
179
        self.flows_stats_dict.update({flow.id: flow for flow in replies_flows})
180