Passed
Pull Request — master (#42)
by
unknown
06:46
created

build.main.Main._flow_stats()   A

Complexity

Conditions 4

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 15.2377

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 11
rs 9.95
c 0
b 0
f 0
ccs 1
cts 9
cp 0.1111
cc 4
nop 2
crap 15.2377
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
        if flow_id in self.flows_stats_dict:
43
            return self.flows_stats_dict[flow_id]
44 1
        return None
45
46 1
    def _flow_stats(self, dpids):
47
        """ Auxiliar funcion for flow/stats endpoint implementation.
48
        """
49
        flow_stats_by_id = {}
50
        for flow_id, flow in self.flows_stats_dict.items():
51
            dpid = flow.switch.dpid
52
            if dpid in dpids:
53
                if dpid not in flow_stats_by_id:
54
                    flow_stats_by_id[dpid] = {}
55
                flow_stats_by_id[dpid].update({flow_id: flow.stats.as_dict()})
56
        return flow_stats_by_id
57
58 1
    @rest('flow/stats')
59 1
    def flow_stats(self):
60
        """Return the flows stats by dpid.
61
        Return the stats of all flows if dpid is None
62
        """
63 1
        args = request.args
64 1
        dpids = args.getlist("dpid", type=str)
65 1
        if len(dpids) == 0:
66
            dpids = [sw.dpid for sw in self.controller.switches.values()]
67 1
        flow_stats_by_id = self._flow_stats(dpids)
68 1
        return jsonify(flow_stats_by_id)
69
70 1
    @rest('packet_count/<flow_id>')
71 1
    def packet_count(self, flow_id):
72
        """Packet count of an specific flow."""
73 1
        flow = self.flow_from_id(flow_id)
74 1
        if flow is None:
75 1
            return "Flow does not exist", 404
76 1
        packet_stats = {
77
            'flow_id': flow_id,
78
            'packet_counter': flow.stats.packet_count,
79
            'packet_per_second':
80
                flow.stats.packet_count / flow.stats.duration_sec
81
            }
82 1
        return jsonify(packet_stats)
83
84 1
    @rest('bytes_count/<flow_id>')
85 1
    def bytes_count(self, flow_id):
86
        """Bytes count of an specific flow."""
87 1
        flow = self.flow_from_id(flow_id)
88 1
        if flow is None:
89 1
            return "Flow does not exist", 404
90 1
        bytes_stats = {
91
            'flow_id': flow_id,
92
            'bytes_counter': flow.stats.byte_count,
93
            'bits_per_second':
94
                flow.stats.byte_count * 8 / flow.stats.duration_sec
95
            }
96 1
        return jsonify(bytes_stats)
97
98 1
    @rest('packet_count/per_flow/<dpid>')
99 1
    def packet_count_per_flow(self, dpid):
100
        """Per flow packet count."""
101 1
        return self.flows_counters('packet_count',
102
                                   dpid,
103
                                   counter='packet_counter',
104
                                   rate='packet_per_second')
105
106 1
    @rest('packet_count/sum/<dpid>')
107 1
    def packet_count_sum(self, dpid):
108
        """Sum of packet count flow stats."""
109 1
        return self.flows_counters('packet_count',
110
                                   dpid,
111
                                   total=True)
112
113 1
    @rest('bytes_count/per_flow/<dpid>')
114 1
    def bytes_count_per_flow(self, dpid):
115
        """Per flow bytes count."""
116 1
        return self.flows_counters('byte_count',
117
                                   dpid,
118
                                   counter='bytes_counter',
119
                                   rate='bits_per_second')
120
121 1
    @rest('bytes_count/sum/<dpid>')
122 1
    def bytes_count_sum(self, dpid):
123
        """Sum of bytes count flow stats."""
124 1
        return self.flows_counters('byte_count',
125
                                   dpid,
126
                                   total=True)
127
128 1
    def flows_counters(self, field, dpid, counter=None, rate=None,
129
                       total=False):
130
        """Calculate flows statistics.
131
        The returned statistics are both per flow and for the sum of flows
132
        """
133
        # pylint: disable=too-many-arguments
134
        # pylint: disable=unused-variable
135 1
        start_date = request.args.get('start_date', 0)
136 1
        end_date = request.args.get('end_date', 0)
137
        # pylint: enable=unused-variable
138
139 1
        if total:
140 1
            count_flows = 0
141
        else:
142 1
            count_flows = []
143 1
            if not counter:
144
                counter = field
145 1
            if not rate:
146
                rate = field
147
148
        # We don't have statistics persistence yet, so for now this only works
149
        # for start and end equals to zero
150 1
        flows = self._flow_stats([dpid])
151 1
        flows = flows[dpid]
152
153 1
        for flow_id, stats in flows.items():
154 1
            count = stats[field]
155 1
            if total:
156 1
                count_flows += count
157
            else:
158 1
                per_second = count / stats['duration_sec']
159 1
                if rate.startswith('bits'):
160 1
                    per_second *= 8
161 1
                count_flows.append({'flow_id': flow_id,
162
                                    counter: count,
163
                                    rate: per_second})
164
165 1
        return jsonify(count_flows)
166
167 1
    @listen_to('kytos/of_core.flow_stats.received')
168 1
    def on_stats_received(self, event):
169
        """Capture flow stats messages for OpenFlow 1.3."""
170
        self.handle_stats_received(event)
171
172 1
    def handle_stats_received(self, event):
173
        """Handle flow stats messages for OpenFlow 1.3."""
174 1
        if 'replies_flows' in event.content:
175 1
            replies_flows = event.content['replies_flows']
176 1
            self.handle_stats_reply_received(replies_flows)
177
178 1
    def handle_stats_reply_received(self, replies_flows):
179
        """Update the set of flows stats"""
180 1
        self.flows_stats_dict.update({flow.id: flow for flow in replies_flows})
181
        # self.flows_stats_dict = {flow.id:flow for flow in replies_flows}
182