Passed
Pull Request — master (#88)
by Gleyberson
02:23
created

build.main   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 204
Duplicated Lines 0 %

Test Coverage

Coverage 85.34%

Importance

Changes 0
Metric Value
wmc 36
eloc 124
dl 0
loc 204
ccs 99
cts 116
cp 0.8534
rs 9.52
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A Main.list() 0 19 3
A Main._send_flow_mods_from_request() 0 20 5
A Main._send_flow_mod() 0 8 1
A Main.delete() 0 10 1
A Main._get_all_switches_enabled() 0 4 1
A Main._send_napp_event() 0 15 4
A Main.shutdown() 0 3 1
A Main.setup() 0 9 1
A Main.execute() 0 2 1
A Main._install_flows() 0 23 5
C Main.handle_errors() 0 41 10
A Main.add() 0 8 1
A Main._add_flow_mod_sent() 0 5 2
1
"""kytos/flow_manager NApp installs, lists and deletes switch flows."""
2 1
from collections import OrderedDict
3
4 1
from flask import jsonify, request
5 1
from pyof.v0x01.asynchronous.error_msg import BadActionCode as BadActionCode01
6 1
from pyof.v0x01.common.phy_port import PortConfig as PortConfig01
7 1
from pyof.v0x04.asynchronous.error_msg import BadActionCode as BadActionCode04
8 1
from pyof.v0x04.common.port import PortConfig as PortConfig04
9
10 1
from kytos.core import KytosEvent, KytosNApp, log, rest
11 1
from kytos.core.helpers import listen_to
12 1
from napps.kytos.of_core.flow import FlowFactory
13
14 1
from .exceptions import InvalidCommandError
15 1
from .settings import FLOWS_DICT_MAX_SIZE
16
17
18 1
class Main(KytosNApp):
19
    """Main class to be used by Kytos controller."""
20
21 1
    def setup(self):
22
        """Replace the 'init' method for the KytosApp subclass.
23
24
        The setup method is automatically called by the run method.
25
        Users shouldn't call this method directly.
26
        """
27 1
        log.debug("flow-manager starting")
28 1
        self._flow_mods_sent = OrderedDict()
29 1
        self._flow_mods_sent_max_size = FLOWS_DICT_MAX_SIZE
30
31 1
    def execute(self):
32
        """Run once on NApp 'start' or in a loop.
33
34
        The execute method is called by the run method of KytosNApp class.
35
        Users shouldn't call this method directly.
36
        """
37
38 1
    def shutdown(self):
39
        """Shutdown routine of the NApp."""
40
        log.debug("flow-manager stopping")
41
42 1
    @rest('v2/flows')
43 1
    @rest('v2/flows/<dpid>')
44 1
    def list(self, dpid=None):
45
        """Retrieve all flows from a switch identified by dpid.
46
47
        If no dpid is specified, return all flows from all switches.
48
        """
49 1
        if dpid is None:
50 1
            switches = self.controller.switches.values()
51
        else:
52 1
            switches = [self.controller.get_switch_by_dpid(dpid)]
53
54 1
        switch_flows = {}
55
56 1
        for switch in switches:
57 1
            flows_dict = [flow.as_dict() for flow in switch.flows]
58 1
            switch_flows[switch.dpid] = {'flows': flows_dict}
59
60 1
        return jsonify(switch_flows)
61
62 1
    @rest('v2/flows', methods=['POST'])
63 1
    @rest('v2/flows/<dpid>', methods=['POST'])
64 1
    def add(self, dpid=None):
65
        """Install new flows in the switch identified by dpid.
66
67
        If no dpid is specified, install flows in all switches.
68
        """
69 1
        return self._send_flow_mods_from_request(dpid, "add")
70
71 1
    @rest('v2/delete', methods=['POST'])
72 1
    @rest('v2/delete/<dpid>', methods=['POST'])
73 1
    @rest('v2/flows', methods=['DELETE'])
74 1
    @rest('v2/flows/<dpid>', methods=['DELETE'])
75 1
    def delete(self, dpid=None):
76
        """Delete existing flows in the switch identified by dpid.
77
78
        If no dpid is specified, delete flows from all switches.
79
        """
80 1
        return self._send_flow_mods_from_request(dpid, "delete")
81
82 1
    def _get_all_switches_enabled(self):
83
        """Get a list of all switches enabled."""
84 1
        switches = self.controller.switches.values()
85 1
        return [switch for switch in switches if switch.is_enabled()]
86
87 1
    def _send_flow_mods_from_request(self, dpid, command):
88
        """Install FlowsMods from request."""
89 1
        flows_dict = request.get_json()
90
91 1
        if flows_dict is None:
92 1
            return jsonify({"response": 'flows dict is none.'}), 404
93
94 1
        if dpid:
95 1
            switch = self.controller.get_switch_by_dpid(dpid)
96 1
            if not switch:
97 1
                return jsonify({"response": 'dpid not found.'}), 404
98 1
            elif switch.is_enabled() is False:
99 1
                return jsonify({"response": 'switch is disabled.'}), 404
100
            else:
101 1
                self._install_flows(command, flows_dict, [switch])
102
        else:
103 1
            self._install_flows(command, flows_dict,
104
                                self._get_all_switches_enabled())
105
106 1
        return jsonify({"response": "FlowMod Messages Sent"})
107
108 1
    def _install_flows(self, command, flows_dict, switches=[]):
109
        """Execute all procedures to install flows in the switches.
110
111
        Args:
112
            command: Flow command to be installed
113
            flows_dict: Dictionary with flows to be installed in the switches.
114
            switches: A list of switches
115
        """
116 1
        for switch in switches:
117 1
            serializer = FlowFactory.get_class(switch)
118 1
            flows = flows_dict.get('flows', [])
119 1
            for flow_dict in flows:
120 1
                flow = serializer.from_dict(flow_dict, switch)
121 1
                if command == "delete":
122
                    flow_mod = flow.as_of_delete_flow_mod()
123 1
                elif command == "add":
124 1
                    flow_mod = flow.as_of_add_flow_mod()
125
                else:
126
                    raise InvalidCommandError
127 1
                self._send_flow_mod(flow.switch, flow_mod)
128 1
                self._add_flow_mod_sent(flow_mod.header.xid, flow)
129
130 1
                self._send_napp_event(switch, flow, command)
131
132 1
    def _add_flow_mod_sent(self, xid, flow):
133
        """Add the flow mod to the list of flow mods sent."""
134 1
        if len(self._flow_mods_sent) >= self._flow_mods_sent_max_size:
135
            self._flow_mods_sent.popitem(last=False)
136 1
        self._flow_mods_sent[xid] = flow
137
138 1
    def _send_flow_mod(self, switch, flow_mod):
139 1
        event_name = 'kytos/flow_manager.messages.out.ofpt_flow_mod'
140
141 1
        content = {'destination': switch.connection,
142
                   'message': flow_mod}
143
144 1
        event = KytosEvent(name=event_name, content=content)
145 1
        self.controller.buffers.msg_out.put(event)
146
147 1
    def _send_napp_event(self, switch, flow, command, **kwargs):
148
        """Send an Event to other apps informing about a FlowMod."""
149 1
        if command == 'add':
150 1
            name = 'kytos/flow_manager.flow.added'
151 1
        elif command == 'delete':
152 1
            name = 'kytos/flow_manager.flow.removed'
153 1
        elif command == 'error':
154 1
            name = 'kytos/flow_manager.flow.error'
155
        else:
156
            raise InvalidCommandError
157 1
        content = {'datapath': switch,
158
                   'flow': flow}
159 1
        content.update(kwargs)
160 1
        event_app = KytosEvent(name, content)
161 1
        self.controller.buffers.app.put(event_app)
162
163 1
    @listen_to('.*.of_core.*.ofpt_error')
164
    def handle_errors(self, event):
165
        """Receive OpenFlow error and send a event.
166
167
        The event is sent only if the error is related to a request made
168
        by flow_manager.
169
        """
170 1
        message = event.content["message"]
171 1
        connection = event.source
172 1
        switch = connection.switch
173
174 1
        error_data = message.data.pack()
175
        # Get the packet responsible for the error
176 1
        error_packet = connection.protocol.unpack(error_data)
177
178 1
        if (message.code == BadActionCode01.OFPBAC_BAD_OUT_PORT or
179
           message.code == BadActionCode04.OFPBAC_BAD_OUT_PORT):
180
181
            for action in error_packet.actions:
182
                try:
183
                    iface = switch.get_interface_by_port_no(action.port.value)
184
                except AttributeError:
185
                    iface = switch.get_interface_by_port_no(action.port)
186
187
                # Check interface to drop packets forwarded to it
188
                if iface:
189
                    if connection.protocol.version == 0x01:
190
                        iface.config = PortConfig01.OFPPC_NO_FWD
191
                    elif connection.protocol.version == 0x04:
192
                        iface.config = PortConfig04.OFPPC_NO_FWD
193
194 1
        xid = message.header.xid.value
195 1
        error_type = message.error_type
196 1
        error_code = message.code
197 1
        try:
198 1
            flow = self._flow_mods_sent[xid]
199
        except KeyError:
200
            pass
201
        else:
202 1
            self._send_napp_event(flow.switch, flow, 'error',
203
                                  error_type=error_type, error_code=error_code)
204