def handle_port_desc(controller, switch, port_list): """Update interfaces on switch based on port_list information.""" for port in port_list: config = port.config if (port.supported == 0 and port.curr_speed.value == 0 and port.max_speed.value == 0): config = PortConfig.OFPPC_NO_FWD interface = switch.update_or_create_interface( port.port_no.value, name=port.name.value, address=port.hw_addr.value, state=port.state.value, features=port.curr, config=config, speed=port.curr_speed.value) event_name = 'kytos/of_core.switch.interface.created' interface_event = KytosEvent(name=event_name, content={'interface': interface}) port_event = KytosEvent(name='kytos/of_core.switch.port.created', content={ 'switch': switch.id, 'port': port.port_no.value, 'port_description': { 'alias': port.name.value, 'mac': port.hw_addr.value, 'state': port.state.value } }) controller.buffers.app.put(port_event) controller.buffers.app.put(interface_event)
def test_handle_multipart_reply(self): """Test handling ofpt_multipart_reply.""" event_name = 'kytos/of_core.v0x04.messages.in.ofpt_multipart_reply' switch = get_switch_mock("00:00:00:00:00:00:00:02") switch.connection = get_connection_mock( 0x04, get_switch_mock("00:00:00:00:00:00:00:01"), ConnectionState.ESTABLISHED) self.napp.controller.get_switch_or_create(switch.dpid, switch.connection) data = b'\x04\x13\x00\x68\xac\xc8\xdf\x58\x00\x01\x00\x00\x00\x00\x00' data += b'\x00\x00\x58\x00\x00\x00\x00\x00\x38\x25\xd9\x54\xc0\x03\xe8' data += b'\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00' data += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x12\x00\x00\x00\x00' data += b'\x00\x00\x02\xf4\x00\x01\x00\x10\x80\x00\x0a\x02\x88\xcc\x80' data += b'\x00\x0c\x02\x1e\xd7\x00\x04\x00\x18\x00\x00\x00\x00\x00\x00' data += b'\x00\x10\xff\xff\xff\xfd\xff\xff\x00\x00\x00\x00\x00\x00' # pylint: disable=protected-access xid = self.napp._multipart_replies_xids[switch.dpid] # pylint: enable=protected-access multipart_reply = MultipartReply(xid=xid) multipart_reply.unpack(data[8:]) stats_event = KytosEvent(name=event_name, content={ 'source': switch.connection, 'message': multipart_reply }) self.napp.handle_multipart_reply(stats_event) # test ofpmp_desc data = b'\x04\x12\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' data += b'\x00' multipart_desc = MultipartReply() multipart_desc.unpack(data[8:]) stats_desc_event = KytosEvent(name=event_name, content={ 'source': switch.connection, 'message': multipart_desc }) target_switch = switch.connection.switch self.napp.handle_multipart_reply(stats_desc_event) # pylint: disable=protected-access self.assertNotIn(xid, self.napp._multipart_replies_xids) # pylint: enable=protected-access self.assertGreater(len(target_switch.flows), 0) self.assertEqual(multipart_desc.body.mfr_desc.value, target_switch.description["manufacturer"]) self.assertEqual(multipart_desc.body.hw_desc.value, target_switch.description["hardware"]) self.assertEqual(multipart_desc.body.sw_desc.value, target_switch.description["software"]) self.assertEqual(multipart_desc.body.serial_num.value, target_switch.description["serial"]) self.assertEqual(multipart_desc.body.dp_desc.value, target_switch.description["data_path"])
def test__lt__(): """test less than operator.""" event_a = KytosEvent('a', priority=5) event_b = KytosEvent('b', priority=-10) assert event_b < event_a event_a = KytosEvent('a') event_b = KytosEvent('b') assert event_a < event_b
def test_handle_port_desc_multipart_reply(self): """Test handling to ofpt_PORT_DESC.""" event_name = 'kytos/of_core.v0x04.messages.in.ofpt_multipart_reply' switch = get_switch_mock() switch.connection = get_connection_mock( 0x04, get_switch_mock("00:00:00:00:00:00:00:02")) data = b'\x04\x13\x00\x90\x00\x00\x00\x00\x00\x0d\x00\x00\x00\x00\x00' data += b'\x00\x00\x00\x00\x07\x00\x00\x00\x00\xf2\x0b\xa4\xd0\x3f\x70' data += b'\x00\x00\x50\x6f\x72\x74\x37\x00\x00\x00\x00\x00\x00\x00\x00' data += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x28\x08' data += b'\x00\x00\x28\x00\x00\x00\x28\x08\x00\x00\x28\x08\x00\x00\x13' data += b'\x88\x00\x00\x13\x88\x00\x00\x00\x06\x00\x00\x00\x00\xf2\x0b' data += b'\xa4\x7d\xf8\xea\x00\x00\x50\x6f\x72\x74\x36\x00\x00\x00\x00' data += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04' data += b'\x00\x00\x28\x08\x00\x00\x28\x00\x00\x00\x28\x08\x00\x00\x28' data += b'\x08\x00\x00\x13\x88\x00\x00\x13\x88' port_desc = MultipartReply() port_desc.unpack(data[8:]) interface_1 = get_interface_mock("interface1", 6) interface_2 = get_interface_mock("interface2", 7) switch.connection.switch.interfaces = {6: interface_1, 7: interface_2} stats_event = KytosEvent(name=event_name, content={ 'source': switch.connection, 'message': port_desc }) self.napp.handle_multipart_reply(stats_event) # Send port_desc pack without interface switch = get_switch_mock() switch.connection = get_connection_mock( 0x04, get_switch_mock("00:00:00:00:00:00:00:02")) stats_event = KytosEvent(name=event_name, content={ 'source': switch.connection, 'message': port_desc }) self.napp.handle_multipart_reply(stats_event) expected_event = 'kytos/of_core.switch.port.created' expected_dpid = '00:00:00:00:00:00:00:02' for _ in range(0, 2): of_event_01 = self.napp.controller.buffers.app.get() of_event_02 = self.napp.controller.buffers.app.get() self.assertEqual(of_event_01.name, expected_event) self.assertEqual(of_event_01.content['switch'], expected_dpid) self.assertEqual(of_event_01.content['port'], 7) self.assertEqual(of_event_02.name, expected_event) self.assertEqual(of_event_02.content['switch'], expected_dpid) self.assertEqual(of_event_02.content['port'], 6)
def create_box(self): """Create a persistence box to store administrative changes.""" content = {'namespace': self.namespace, 'callback': self._create_box_callback, 'data': {}} event = KytosEvent(name='kytos.storehouse.create', content=content) self.controller.buffers.app.put(event)
def setting_path(self): """Set the primary elements needed to test the topology update process under a "real-simulated" scenario.""" topology = get_topology_with_metadata() event = KytosEvent(name='kytos.topology.updated', content={'topology': topology}) self.napp.update_topology(event)
def _find_user(self, uid): """Find a specific user using Storehouse.""" response = {} def _find_user_callback(_event, box, error): nonlocal response if not box: response = { "answer": f'User with uid {uid} not found', "code": HTTPStatus.NOT_FOUND.value } elif error: response = { "answer": "User data cannot be shown", "code": HTTPStatus.INTERNAL_SERVER_ERROR.value, } else: response = { "answer": {"data": box.data}, "code": HTTPStatus.OK.value, } content = { "box_id": uid, "namespace": self.namespace, "callback": _find_user_callback, } event = KytosEvent(name="kytos.storehouse.retrieve", content=content) self.controller.buffers.app.put(event) while True: time.sleep(0.1) if response: break return response["answer"], response["code"]
def _delete_user(self, uid): """Delete a user using Storehouse.""" response = {} def _delete_user_callback(_event, box, error): nonlocal response if not box: response = { "answer": f'User with uid {uid} not found', "code": HTTPStatus.NOT_FOUND.value } elif error: response = { "answer": "User has not been deleted", "code": HTTPStatus.INTERNAL_SERVER_ERROR.value, } else: response = { "answer": "User successfully deleted", "code": HTTPStatus.OK.value, } content = { "box_id": uid, "namespace": self.namespace, "callback": _delete_user_callback, } event = KytosEvent(name="kytos.storehouse.delete", content=content) self.controller.buffers.app.put(event) while True: time.sleep(0.1) if response: break return response["answer"], response["code"]
def unload_napp(self, username, napp_name): """Unload a specific NApp. Args: username (str): NApp username. napp_name (str): Name of the NApp to be unloaded. """ napp = self.napps.pop((username, napp_name), None) if napp is None: self.log.warning('NApp %s/%s was not loaded', username, napp_name) else: self.log.info("Shutting down NApp %s/%s...", username, napp_name) napp_id = NApp(username, napp_name).id event = KytosEvent(name='kytos/core.shutdown.' + napp_id) napp_shutdown_fn = self.events_listeners[event.name][0] # Call listener before removing it from events_listeners napp_shutdown_fn(event) # Remove rest endpoints from that napp self.api_server.remove_napp_endpoints(napp) # Removing listeners from that napp # pylint: disable=protected-access for event_type, napp_listeners in napp._listeners.items(): event_listeners = self.events_listeners[event_type] for listener in napp_listeners: event_listeners.remove(listener) if not event_listeners: del self.events_listeners[event_type]
def test_handle_01_features_reply(self): """Test handling features reply message.""" event_name = 'kytos/of_core.v0x01.messages.in.ofpt_features_reply' switch = get_switch_mock() switch.connection = get_connection_mock( 0x01, get_switch_mock("00:00:00:00:00:00:00:02"), ConnectionState.SETUP) switch.connection.protocol.state = 'waiting_features_reply' data = b'\x01\x06\x00\x80\x00\x00\x00\x00\x00\x00\x00\xff\x12\x34\x56' data += b'\x78\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\xa9\x00\x00' data += b'\x08\x43\x00\x07\xf2\x0b\xa4\xd0\x3f\x70\x50\x6f\x72\x74\x37' data += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' data += b'\x00\x00\x00\x10\x00\x00\x02\x88\x00\x00\x02\x80\x00\x00\x02' data += b'\x88\x00\x00\x02\x88\x00\x06\xf2\x0b\xa4\x7d\xf8\xea\x50\x6f' data += b'\x72\x74\x36\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' data += b'\x00\x00\x00\x00\x00\x00\x02\x00\x00\x02\x88\x00\x00\x02\x80' data += b'\x00\x00\x02\x88\x00\x00\x02\x88' features_reply = FReply_v0x01() features_reply.unpack(data[8:]) event = KytosEvent(name=event_name, content={ 'source': switch.connection, 'message': features_reply }) self.napp.handle_features_reply(event) expected = [ 'kytos/of_core.v0x01.messages.out.ofpt_stats_request', 'kytos/of_core.v0x01.messages.out.ofpt_set_config' ] for message in expected: of_event = self.napp.controller.buffers.msg_out.get() self.assertEqual(of_event.name, message)
def test_msg_out_queue_prio(self): """Test msg_out queue priorities.""" prios = [-10, 10, 0, -20] for prio in prios: self.kytos_buffers.msg_out.put(KytosEvent(priority=prio)) for prio in sorted(prios): assert self.kytos_buffers.msg_out.get().priority == prio
def connection_made(self, transport): """Handle new client connection, passing it to the controller. Build a new Kytos `Connection` and send a ``kytos/core.connection.new`` KytosEvent through the app buffer. """ self.transport = transport addr, port = transport.get_extra_info('peername') _, server_port = transport.get_extra_info('sockname') socket = transport.get_extra_info('socket') LOG.info("New connection from %s:%s", addr, port) self.connection = Connection(addr, port, socket) # This allows someone to inherit from KytosServer and start a server # on another port to handle a different protocol. if self.server.protocol_name: self.known_ports[server_port] = self.server.protocol_name 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 event_name = f'kytos/core.{protocol_name}.connection.new' event = KytosEvent(name=event_name, content={'source': self.connection}) self._loop.create_task(self.server.controller.buffers.raw.aput(event))
def connection_made(self, transport): """Handle new client connection, passing it to the controller. Build a new Kytos `Connection` and send a ``kytos/core.connection.new`` KytosEvent through the app buffer. """ self.transport = transport addr, port = transport.get_extra_info('peername') _, server_port = transport.get_extra_info('sockname') socket = transport.get_extra_info('socket') LOG.info("New connection from %s:%s", addr, port) self.connection = Connection(addr, port, socket) # ASYNC TODO: # if self.server.protocol_name: # self.known_ports[server_port] = self.server.protocol_name 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 # ASYNC TODO: # self.request.settimeout(70) event_name = 'kytos/core.connection.new' # f'kytos/core.{self.connection.protocol.name}.connection.new' event = KytosEvent(name=event_name, content={'source': self.connection}) self._loop.create_task(self.server.controller.buffers.raw.aput(event))
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 _create_superuser(self): """Create a superuser using Storehouse.""" def _create_superuser_callback(_event, box, error): if box and not error: LOG.info("Superuser successfully created") def get_username(): return input("Username: "******"Email: ") username = get_username() email = get_email() while True: password = getpass.getpass() re_password = getpass.getpass('Retype password: '******'Passwords do not match. Try again') user = { "username": username, "email": email, "password": hashlib.sha512(password.encode()).hexdigest(), } content = { "namespace": self.namespace, "box_id": user["username"], "data": user, "callback": _create_superuser_callback, } event = KytosEvent(name="kytos.storehouse.create", content=content) self.controller.buffers.app.put(event)
def _list_users(self): """List all users using Storehouse.""" response = {} def _list_users_callback(_event, boxes, error): nonlocal response if error: response = { "answer": "Users cannot be listed", "code": HTTPStatus.INTERNAL_SERVER_ERROR.value, } else: response = { "answer": { "users": boxes }, "code": HTTPStatus.OK.value, } content = { "namespace": self.namespace, "callback": _list_users_callback, } event = KytosEvent(name="kytos.storehouse.list", content=content) self.controller.buffers.app.put(event) while True: time.sleep(0.1) if response: break return response["answer"], response["code"]
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 test_handle_packet_in_raw_in(self): """Test handling packet_in raw in message.""" event_name = 'kytos/core.openflow.raw.in' switch = get_switch_mock() switch.connection = get_connection_mock( 0x04, get_switch_mock("00:00:00:00:00:00:00:02"), ConnectionState.ESTABLISHED) data = b'\x04\x0a\x00\x94\x00\x00\x00\x00\x00\x00\x00\x02\x00\x2a\x01' data += b'\x01\x00\x01\x02\x03\x00\x00\x00\x00\x00\x01\x00\x50\x80\x00' data += b'\x00\x04\x00\x00\x00\x06\x80\x00\x0a\x02\x08\x06\x80\x00\x06' data += b'\x06\xff\xff\xff\xff\xff\xff\x80\x00\x08\x06\xf2\x0b\xa4\x7d' data += b'\xf8\xea\x80\x00\x2a\x02\x00\x01\x80\x00\x2c\x04\x0a\x00\x00' data += b'\x01\x80\x00\x2e\x04\x0a\x00\x00\x03\x80\x00\x30\x06\xf2\x0b' data += b'\xa4\x7d\xf8\xea\x80\x00\x32\x06\x00\x00\x00\x00\x00\x00\x00' data += b'\x00\xff\xff\xff\xff\xff\xff\xf2\x0b\xa4\x7d\xf8\xea\x08\x06' data += b'\x00\x01\x08\x00\x06\x04\x00\x01\xf2\x0b\xa4\x7d\xf8\xea\x0a' data += b'\x00\x00\x01\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x03' event = KytosEvent(name=event_name, content={ 'source': switch.connection, 'new_data': data }) self.napp.handle_raw_in(event) of_event = self.napp.controller.buffers.msg_in.get() self.assertEqual(of_event.name, 'kytos/of_core.v0x04.messages.in.ofpt_packet_in')
def handle_port_desc(controller, switch, port_list): """Update interfaces on switch based on port_list information.""" for port in port_list: interface = switch.get_interface_by_port_no(port.port_no.value) if interface: interface.name = port.name.value interface.address = port.hw_addr.value interface.state = port.state.value interface.features = port.curr interface.set_custom_speed(port.curr_speed.value) else: interface = Interface(name=port.name.value, address=port.hw_addr.value, port_number=port.port_no.value, switch=switch, state=port.state.value, features=port.curr, speed=port.curr_speed.value) switch.update_interface(interface) port_event = KytosEvent(name='kytos/of_core.switch.port.created', content={ 'switch': switch.id, 'port': port.port_no.value, 'port_description': { 'alias': port.name.value, 'mac': port.hw_addr.value, 'state': port.state.value } }) controller.buffers.app.put(port_event)
def get_switch_or_create(self, dpid, connection): """Return switch or create it if necessary. Args: dpid (|DPID|): dpid object used to identify a switch. connection (:class:`~kytos.core.connection.Connection`): connection used by switch. If a switch has a connection that will be updated. Returns: :class:`~kytos.core.switch.Switch`: new or existent switch. """ self.create_or_update_connection(connection) switch = self.get_switch_by_dpid(dpid) event = None if switch is None: switch = Switch(dpid=dpid) self.add_new_switch(switch) event = KytosEvent(name='kytos/core.switch.new', content={'switch': switch}) old_connection = switch.connection switch.update_connection(connection) if old_connection is not connection: self.remove_connection(old_connection) if event: self.buffers.app.put(event) return switch
def list_stored_boxes(self): """List all persistence box stored in storehouse.""" name = 'kytos.storehouse.list' content = {'namespace': self.namespace, 'callback': self._get_or_create_a_box_from_list_of_boxes} event = KytosEvent(name=name, content=content) self.controller.buffers.app.put(event)
def test_update_topology_success_case(self): """Test update topology method to success case.""" topology = get_topology_mock() event = KytosEvent(name='kytos.topology.updated', content={'topology': topology}) self.napp.update_topology(event) self.assertEqual(self.napp._topology, topology)
def test_handle_new_switch(self): """Test handle new switch.""" event_name = '.*.switch.(new|reconnected)' switch = get_switch_mock(0x04) event = KytosEvent(name=event_name, content={'switch': switch}) self.napp.handle_new_switch(event) event_response = self.napp.controller.buffers.app.get() self.assertEqual(event_response.name, 'kytos/topology.updated')
def create_box(self): """Create a new box.""" content = {'namespace': self.namespace, 'callback': self._create_box_callback, 'data': {}} event = KytosEvent(name='kytos.storehouse.create', content=content) self.controller.buffers.app.put(event) log.info('Create box from storehouse.')
def test_save_metadata_on_store(self): """Test save metadata on store.""" event_name = 'kytos.storehouse.update' switch = get_switch_mock(0x04) event = KytosEvent(name=event_name, content={'switch': switch}) self.napp.save_metadata_on_store(event) event_response = self.napp.controller.buffers.app.get() self.assertEqual(event_response.name, 'kytos.storehouse.update')
def test_handle_connection_lost(self): """Test handle connection lost.""" event_name = '.*.connection.lost' source = Mock() stats_event = KytosEvent(name=event_name, content={'source': source}) self.napp.handle_connection_lost(stats_event) event_response = self.napp.controller.buffers.app.get() self.assertEqual(event_response.name, 'kytos/topology.updated')
def create_circuit(self): """Try to create a new circuit. Firstly, for EVPL: E-Line NApp verifies if UNI_A's requested C-VID and UNI_Z's requested C-VID are available from the interfaces' pools. This is checked when creating the UNI object. Then, E-Line NApp requests a primary and a backup path to the Pathfinder NApp using the attributes primary_links and backup_links submitted via REST # For each link composing paths in #3: # - E-Line NApp requests a S-VID available from the link VLAN pool. # - Using the S-VID obtained, generate abstract flow entries to be # sent to FlowManager Push abstract flow entries to FlowManager and FlowManager pushes OpenFlow entries to datapaths E-Line NApp generates an event to notify all Kytos NApps of a new EVC creation Finnaly, notify user of the status of its request. """ # Try to create the circuit object data = request.get_json() if not data: return jsonify("Bad request: The request do not have a json."), 400 try: evc = self.evc_from_dict(data) except ValueError as exception: return jsonify("Bad request: {}".format(exception)), 400 # verify duplicated evc if self.is_duplicated_evc(evc): return jsonify("Not Acceptable: This evc already exists."), 409 # store circuit in dictionary self.circuits[evc.id] = evc # save circuit self.storehouse.save_evc(evc) # Schedule the circuit deploy self.sched.add(evc) # Circuit has no schedule, deploy now if not evc.circuit_scheduler: evc.deploy() # Notify users event = KytosEvent(name='kytos.mef_eline.created', content=evc.as_dict()) self.controller.buffers.app.put(event) return jsonify({"circuit_id": evc.id}), 201
def get_stored_box(self, box_id): """Get persistence box from storehouse.""" content = {'namespace': self.namespace, 'callback': self._get_box_callback, 'box_id': box_id, 'data': {}} name = 'kytos.storehouse.retrieve' event = KytosEvent(name=name, content=content) self.controller.buffers.app.put(event)
def list_stored_boxes(self): """List all boxes using the current namespace.""" name = 'kytos.storehouse.list' content = {'namespace': self.namespace, 'callback': self._get_or_create_a_box_from_list_of_boxes} event = KytosEvent(name=name, content=content) self.controller.buffers.app.put(event) log.debug(f'Bootstraping storehouse box for {self.namespace}.')
def send_stop_signal(self): """Send a ``kytos/core.shutdown`` event to each buffer.""" LOG.info('Stop signal received by Kytos buffers.') LOG.info('Sending KytosShutdownEvent to all apps.') event = KytosEvent(name='kytos/core.shutdown') self.raw.put(event) self.msg_in.put(event) self.msg_out.put(event) self.app.put(event)