class TestConnection(TestCase): """Connection tests.""" def setUp(self): """Instantiate a Connection.""" socket = MagicMock() switch = MagicMock() self.connection = Connection('addr', 123, socket, switch) switch.connection = self.connection def test__str__(self): """Test __str__ method.""" self.assertEqual(str(self.connection), "Connection('addr', 123)") def test__repr__(self): """Test __repr__ method.""" self.connection.socket = 'socket' self.connection.switch = 'switch' expected = "Connection('addr', 123, 'socket', 'switch', " + \ "<ConnectionState.NEW: 0>)" self.assertEqual(repr(self.connection), expected) def test_state(self): """Test state property.""" self.assertEqual(self.connection.state.value, 0) self.connection.state = ConnectionState.FINISHED self.assertEqual(self.connection.state.value, 4) def test_state__error(self): """Test state property to error case.""" with self.assertRaises(Exception): self.connection.state = 1000 def test_id(self): """Test id property.""" self.assertEqual(self.connection.id, ('addr', 123)) def test_send(self): """Test send method.""" self.connection.send(b'data') self.connection.socket.sendall.assert_called_with(b'data') def test_send__error(self): """Test send method to error case.""" self.connection.socket.sendall.side_effect = SocketError self.connection.send(b'data') self.assertIsNone(self.connection.socket) def test_close(self): """Test close method.""" self.connection.close() self.assertIsNone(self.connection.socket) def test_close__os_error(self): """Test close method to OSError case.""" self.connection.socket.shutdown.side_effect = OSError with self.assertRaises(OSError): self.connection.close() self.assertIsNotNone(self.connection.socket) def test_close__attribute_error(self): """Test close method to AttributeError case.""" self.connection.socket = None self.connection.close() self.assertIsNone(self.connection.socket) def test_is_alive(self): """Test is_alive method to True and False returns.""" self.assertTrue(self.connection.is_alive()) self.connection.state = ConnectionState.FINISHED self.assertFalse(self.connection.is_alive()) def test_is_new(self): """Test is_new method.""" self.assertTrue(self.connection.is_new()) def test_established_state(self): """Test set_established_state and is_established methods.""" self.connection.set_established_state() self.assertTrue(self.connection.is_established()) def test_setup_state(self): """Test set_setup_state and is_during_setup methods.""" self.connection.set_setup_state() self.assertTrue(self.connection.is_during_setup()) def test_update_switch(self): """Test update_switch method.""" switch = MagicMock() self.connection.update_switch(switch) self.assertEqual(self.connection.switch, switch) self.assertEqual(switch.connection, self.connection)
class KytosRequestHandler(BaseRequestHandler): """The socket/request handler class for our controller. It is instantiated once per connection between each switch and the controller. The setup method will dispatch a KytosEvent (``kytos/core.connection.new``) on the controller, that will be processed by a Core App. The finish method will close the connection and dispatch a KytonEvents (``kytos/core.connection.closed``) on the controller. """ known_ports = {6633: 'openflow'} def __init__(self, request, client_address, server): """Contructor takes the parameters below. Args: request (socket.socket): Request sent by client. client_address (tuple): Client address, tuple with host and port. server (socketserver.BaseServer): Server used to send messages to client. """ super().__init__(request, client_address, server) self.connection = None def setup(self): """Method used to setup the new connection. This method builds a new controller Connection, and places a ``kytos/core.connection.new`` KytosEvent in the app buffer. """ self.ip = self.client_address[0] self.port = self.client_address[1] log.info("New connection from %s:%s", self.ip, self.port) self.connection = Connection(self.ip, self.port, self.request) # noqa server_port = self.server.server_address[1] if server_port in self.known_ports: protocol_name = self.known_ports[server_port] else: protocol_name = f'{server_port:04d}' self.connection.protocol.name = protocol_name self.request.settimeout(30) self.exception = None event_name = \ f'kytos/core.{self.connection.protocol.name}.connection.new' event = KytosEvent(name=event_name, content={'source': self.connection}) self.server.controller.buffers.app.put(event) def handle(self): """Handle each request and places its data in the raw event buffer. This method loops reading the binary data from the connection socket, and placing a ``kytos/core.messages.new`` KytosEvent in the raw event buffer. """ curr_thread = current_thread() MAX_SIZE = 2**16 while True: try: new_data = self.request.recv(MAX_SIZE) except (SocketError, OSError, InterruptedError, ConnectionResetError) as exception: self.exception = exception log.debug('Socket handler exception while reading: %s', exception) break if new_data == b'': self.exception = 'Request closed by client.' break if not self.connection.is_alive(): continue log.debug("New data from %s:%s at thread %s", self.ip, self.port, curr_thread.name) content = {'source': self.connection, 'new_data': new_data} event_name = \ f'kytos/core.{self.connection.protocol.name}.raw.in' event = KytosEvent(name=event_name, content=content) self.server.controller.buffers.raw.put(event) def finish(self): """Method is called when the client connection is finished. This method closes the connection socket and generates a ``kytos/core.connection.lost`` KytosEvent in the App buffer. """ log.info("Connection lost with Client %s:%s. Reason: %s", self.ip, self.port, self.exception) self.connection.state = CONNECTION_STATE.FINISHED self.connection.close() content = {'source': self.connection} if self.exception: content['exception'] = self.exception event_name = \ f'kytos/core.{self.connection.protocol.name}.connection.lost' event = KytosEvent(name=event_name, content=content) self.server.controller.buffers.app.put(event)