Passed
Branch master (113da4)
by Humberto
02:08
created

build.main.Main.handle_errors()   A

Complexity

Conditions 3

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 14
ccs 0
cts 7
cp 0
rs 10
c 0
b 0
f 0
cc 3
nop 2
crap 12
1
"""kytos/flow_manager NApp installs, lists and deletes switch flows."""
2
from collections import OrderedDict
3
from flask import jsonify, request
4
from kytos.core import KytosEvent, KytosNApp, log, rest
5
from kytos.core.helpers import listen_to
6
7
from napps.kytos.of_core.flow import FlowFactory
8
9
from .settings import FLOWS_DICT_MAX_SIZE
10
from .exceptions import InvalidCommandError
11
12
13
class Main(KytosNApp):
14
    """Main class to be used by Kytos controller."""
15
16
    def setup(self):
17
        """Replace the 'init' method for the KytosApp subclass.
18
19
        The setup method is automatically called by the run method.
20
        Users shouldn't call this method directly.
21
        """
22
        log.debug("flow-manager starting")
23
        self._flow_mods_sent = OrderedDict()
24
        self._flow_mods_sent_max_size = FLOWS_DICT_MAX_SIZE
25
26
    def execute(self):
27
        """Run once on NApp 'start' or in a loop.
28
29
        The execute method is called by the run method of KytosNApp class.
30
        Users shouldn't call this method directly.
31
        """
32
        pass
33
34
    def shutdown(self):
35
        """Shutdown routine of the NApp."""
36
        log.debug("flow-manager stopping")
37
38
    @rest('v2/flows')
39
    @rest('v2/flows/<dpid>')
40
    def list(self, dpid=None):
41
        """Retrieve all flows from a switch identified by dpid.
42
43
        If no dpid is specified, return all flows from all switches.
44
        """
45
        if dpid is None:
46
            switches = self.controller.switches.values()
47
        else:
48
            switches = [self.controller.get_switch_by_dpid(dpid)]
49
50
        switch_flows = {}
51
52
        for switch in switches:
53
            flows_dict = [flow.as_dict() for flow in switch.flows]
54
            switch_flows[switch.dpid] = {'flows': flows_dict}
55
56
        return jsonify(switch_flows)
57
58
    @rest('v2/flows', methods=['POST'])
59
    @rest('v2/flows/<dpid>', methods=['POST'])
60
    def add(self, dpid=None):
61
        """Install new flows in the switch identified by dpid.
62
63
        If no dpid is specified, install flows in all switches.
64
        """
65
        return self._send_flow_mods_from_request(dpid, "add")
66
67
    @rest('v2/delete', methods=['POST'])
68
    @rest('v2/delete/<dpid>', methods=['POST'])
69
    def delete(self, dpid=None):
70
        """Delete existing flows in the switch identified by dpid.
71
72
        If no dpid is specified, delete flows from all switches.
73
        """
74
        return self._send_flow_mods_from_request(dpid, "delete")
75
76
    def _get_all_switches_enabled(self):
77
        """Get a list of all switches enabled."""
78
        switches = self.controller.switches.values()
79
        return [switch for switch in switches if switch.is_enabled()]
80
81
    def _send_flow_mods_from_request(self, dpid, command):
82
        """Install FlowsMods from request."""
83
        flows_dict = request.get_json()
84
85
        if flows_dict is None:
86
            return jsonify({"response": 'flows dict is none.'}), 404
87
88
        if dpid:
89
            switch = self.controller.get_switch_by_dpid(dpid)
90
            if not switch:
91
                return jsonify({"response": 'dpid not found.'}), 404
92
            elif switch.is_enabled() is False:
93
                return jsonify({"response": 'switch is disabled.'}), 404
94
            else:
95
                self._install_flows(command, flows_dict, [switch])
96
        else:
97
            self._install_flows(command, flows_dict,
98
                                self._get_all_switches_enabled())
99
100
        return jsonify({"response": "FlowMod Messages Sent"})
101
102
    def _install_flows(self, command, flows_dict, switches=[]):
103
        """Execute all procedures to install flows in the switches.
104
105
        Args:
106
            command: Flow command to be installed
107
            flows_dict: Dictionary with flows to be installed in the switches.
108
            switches: A list of switches
109
        """
110
        for switch in switches:
111
            serializer = FlowFactory.get_class(switch)
112
            flows = flows_dict.get('flows', [])
113
            for flow_dict in flows:
114
                flow = serializer.from_dict(flow_dict, switch)
115
                if command == "delete":
116
                    flow_mod = flow.as_of_delete_flow_mod()
117
                elif command == "add":
118
                    flow_mod = flow.as_of_add_flow_mod()
119
                else:
120
                    raise InvalidCommandError
121
                self._send_flow_mod(flow.switch, flow_mod)
122
                self._add_flow_mod_sent(flow_mod.header.xid, flow)
123
124
                self._send_napp_event(switch, flow, command)
125
126
    def _add_flow_mod_sent(self, xid, flow):
127
        """Add the flow mod to the list of flow mods sent."""
128
        if len(self._flow_mods_sent) >= self._flow_mods_sent_max_size:
129
            self._flow_mods_sent.popitem(last=False)
130
        self._flow_mods_sent[xid] = flow
131
132
    def _send_flow_mod(self, switch, flow_mod):
133
        event_name = 'kytos/flow_manager.messages.out.ofpt_flow_mod'
134
135
        content = {'destination': switch.connection,
136
                   'message': flow_mod}
137
138
        event = KytosEvent(name=event_name, content=content)
139
        self.controller.buffers.msg_out.put(event)
140
141
    def _send_napp_event(self, switch, flow, command):
142
        """Send an Event to other apps informing about a FlowMod."""
143
        if command == 'add':
144
            name = 'kytos/flow_manager.flow.added'
145
        elif command == 'delete':
146
            name = 'kytos/flow_manager.flow.removed'
147
        elif command == 'error':
148
            name = 'kytos/flow_manager.flow.error'
149
        else:
150
            raise InvalidCommandError
151
        content = {'datapath': switch,
152
                   'flow': flow}
153
        event_app = KytosEvent(name, content)
154
        self.controller.buffers.app.put(event_app)
155
156
    @listen_to('.*.of_core.*.ofpt_error')
157
    def handle_errors(self, event):
158
        """Receive OpenFlow error and send a event.
159
160
            The event is sent only if the error is related to a request made
161
            by flow_manager.
162
        """
163
        xid = event.content["message"].header.xid.value
164
        try:
165
            flow = self._flow_mods_sent[xid]
166
        except KeyError:
167
            pass
168
        else:
169
            self._send_napp_event(flow.switch, flow, 'error')
170