def setup(self): """Replace the 'init' method for the KytosApp subclass. The setup method is automatically called by the run method. Users shouldn't call this method directly. """ log.debug("flow-manager starting") self._flow_mods_sent = OrderedDict() self._flow_mods_sent_max_size = FLOWS_DICT_MAX_SIZE self.cookie_ignored_range = [] self.tab_id_ignored_range = [] if _valid_consistency_ignored(CONSISTENCY_COOKIE_IGNORED_RANGE): self.cookie_ignored_range = CONSISTENCY_COOKIE_IGNORED_RANGE if _valid_consistency_ignored(CONSISTENCY_TABLE_ID_IGNORED_RANGE): self.tab_id_ignored_range = CONSISTENCY_TABLE_ID_IGNORED_RANGE # Storehouse client to save and restore flow data: self.storehouse = StoreHouse(self.controller) self._storehouse_lock = Lock() # Format of stored flow data: # {'flow_persistence': {'dpid_str': {'flow_list': [ # {'command': '<add|delete>', # 'flow': {flow_dict}}]}}} self.stored_flows = {} self.resent_flows = set()
def send_packet_out(controller, switch, port, data): """ Just prepare the PacketOut to be used by the Tracer. Args: controller: Kytos controller switch: OpenFlow datapath port: in_port data: Ethernet frame Return: output_action = ActionOutput """ output_action = ActionOutput() output_action.port = settings.OFPP_TABLE packet_out = PacketOut() packet_out.actions.append(output_action) packet_out.in_port = port packet_out.data = bytes(data) event_out = KytosEvent() event_out.name = 'kytos/of_lldp.messages.out.ofpt_packet_out' event_out.content = { 'destination': switch.connection, 'message': packet_out } log.debug('PacketOut %s sent' % event_out.content) controller.buffers.msg_out.put(event_out)
def update_port_status(self, port_status, source): """Dispatch 'port.*' events. Current events: created|deleted|up|down|link_up|link_down|modified Args: port_status: python openflow (pyof) PortStatus object. source: kytos.core.switch.Connection instance. Dispatch: `kytos/of_core.switch.port.[created|modified|deleted]`: { switch : <switch.id>, port: <port.port_no> port_description: {<description of the port>} } """ reason = port_status.reason.enum_ref(port_status.reason.value).name port = port_status.desc event_name = 'kytos/of_core.switch.interface.' if reason == 'OFPPR_ADD': status = 'created' interface = Interface(name=port.name.value, address=port.hw_addr.value, port_number=port.port_no.value, switch=source.switch, state=port.state.value, features=port.curr) source.switch.update_interface(interface) elif reason == 'OFPPR_MODIFY': status = 'modified' interface = Interface(name=port.name.value, address=port.hw_addr.value, port_number=port.port_no.value, switch=source.switch, state=port.state.value, features=port.curr) source.switch.update_interface(interface) self._send_specific_port_mod(port, interface) elif reason == 'OFPPR_DELETE': status = 'deleted' interface = source.switch.get_interface_by_port_no( port.port_no.value) source.switch.remove_interface(interface) event_name += status content = {'interface': interface} event = KytosEvent(name=event_name, content=content) self.controller.buffers.app.put(event) msg = 'The port %s from switch %s was %s.' log.debug(msg, port_status.desc.port_no, source.switch.id, status)
def update_port_status(self, event): """Dispatch 'port.[created|modified|deleted]' event. Listen: `kytos/of_core.v0x0[14].messages.in.ofpt_port_status` Dispatch: `kytos/of_topology.switch.port.[created|modified|deleted]`: { switch : <switch.id>, port: <port.port_no> port_description: {<description of the port>} } """ port_status = event.message reason = port_status.reason.value name = 'kytos/of_topology.switch.port.' + reason.lower() content = { 'switch': event.source.id, 'port': port_status.desc.port_no, 'port_description': vars(port_status.desc) } event = KytosEvent(name=name, content=content) self.controller.buffers.app.put(event) msg = 'The port %s (%s) from switch %s was %s.' log.debug(msg, port_status.desc.port_no, event.source.id, reason)
def packet_in(event, packet_in_msg): """ Process OpenFlow 1.0 PacketIn messages Args: event: PacketIN event packet_in_msg: PacketIn msg Return: ethernet: PacketIn data in_port: in_port switch: OpenFlow datapath 0, 0, 0 if it is not a trace probe """ ethernet = Ethernet() ethernet.unpack(packet_in_msg.data.value) if settings.COLOR_VALUE in ethernet.source.value: log.debug("OpenFlow 1.0 PacketIn Trace Msg Received") in_port = packet_in_msg.in_port.value switch = event.source.switch return ethernet, in_port, switch log.debug("PacketIn is not a Data Trace Probe") return 0, 0, 0
def update_links(self, event): """Dispatch 'reacheable.mac' event. Listen: `kytos/of_core.v0x0[14].messages.in.ofpt_packet_in` Dispatch: `reachable.mac`: { switch : <switch.id>, port: <port.port_no> reachable_mac: <mac_address> } """ ethernet = Ethernet() ethernet.unpack(event.message.data.value) name = 'kytos/of_topology.reachable.mac' content = { 'switch': event.source.switch.id, 'port': event.message.in_port, 'reachable_mac': ethernet.source } event = KytosEvent(name, content) self.controller.buffers.app.put(event) msg = 'The MAC %s is reachable from switch/port %s/%s.' log.debug(msg, ethernet.source, event.source.switch.id, event.message.in_port)
def install_table_miss_flow(self, event): """Install the TableMiss Flow in OF1.3 switches. This is needed because those drop packets by default. """ dpid = event.content['dpid'] switch = self.controller.get_switch_by_dpid(dpid) try: version = switch.connection.protocol.version except AttributeError: version = None log.debug(f'The OpenFlow version was not found for switch {dpid}.') if version != 0x04: return flow = {} flow['priority'] = 0 flow['table_id'] = settings.TABLE_ID flow['actions'] = [{ 'action_type': 'output', 'port': Port13.OFPP_CONTROLLER }] destination = switch.id endpoint = f'{settings.FLOW_MANAGER_URL}/flows/{destination}' data = {'flows': [flow]} requests.post(endpoint, json=data)
def send_packet_out(controller, switch, port, data): """ Just prepare the PacketOut to be used by the Tracer. Args: controller: Kytos controller switch: OpenFlow datapath port: in_port data: Ethernet frame Return: output_action = ActionOutput """ output_action = ActionOutput() output_action.port = settings.OFPP_TABLE packet_out = PacketOut() packet_out.actions.append(output_action) packet_out.in_port = port packet_out.data = bytes(data) event_out = KytosEvent() event_out.name = 'kytos/of_lldp.messages.out.ofpt_packet_out' event_out.content = {'destination': switch.connection, 'message': packet_out} log.debug('PacketOut %s sent' % event_out.content) controller.buffers.msg_out.put(event_out)
def _create_box_callback(self, _event, data, error): """Execute the callback to handle create_box.""" if error: log.error(f"Can't create box with namespace {self.namespace}") self.box = data log.debug(f"Box {self.box.box_id} was created in {self.namespace}.")
def _get_box_callback(self, _event, data, error): """Handle get_box method saving the box or logging with the error.""" if error: log.error(f'Box {data.box_id} not found in {self.namespace}.') self.box = data log.debug(f'Box {self.box.box_id} was load from storehouse.')
def handle_link_down(self, event): """Change circuit when link is down or under_mantenance.""" log.debug("Event handle_link_down %s", event) for evc in self.circuits.values(): if evc.is_affected_by_link(event.content['link']): log.info('handling evc %s' % evc) evc.handle_link_down()
def load_evcs(self, event): """Try to load the unloaded EVCs from storehouse.""" with self._lock: log.debug("Event load_evcs %s", event) circuits = self.storehouse.get_data() if not self._circuits_by_interface: self.load_circuits_by_interface(circuits) interface_id = '{}:{}'.format(event.content['switch'], event.content['port']) for circuit_id in self._circuits_by_interface.get( interface_id, []): if circuit_id in circuits and circuit_id not in self.circuits: try: evc = self._evc_from_dict(circuits[circuit_id]) except ValueError as exception: log.error(f'Could not load EVC {circuit_id} ' f'because {exception}') continue evc.deactivate() evc.current_path = Path([]) evc.sync() self.circuits.setdefault(circuit_id, evc) self.sched.add(evc)
def setup(self): """Replace the 'init' method for the KytosApp subclass. The setup method is automatically called by the run method. Users shouldn't call this method directly. """ log.debug("flow-manager starting")
def list_schedules(self): """Endpoint to return all schedules stored for all circuits. Return a JSON with the following template: [{"schedule_id": <schedule_id>, "circuit_id": <circuit_id>, "schedule": <schedule object>}] """ log.debug('list_schedules /v2/evc/schedule') circuits = self.storehouse.get_data().values() if not circuits: result = {} status = 200 return jsonify(result), status result = [] status = 200 for circuit in circuits: circuit_scheduler = circuit.get("circuit_scheduler") if circuit_scheduler: for scheduler in circuit_scheduler: value = { "schedule_id": scheduler.get("id"), "circuit_id": circuit.get("id"), "schedule": scheduler } result.append(value) log.debug('list_schedules result %s %s', result, status) return jsonify(result), status
def handle_packet_in(self, event): """Handle PacketIn Event. Install flows allowing communication between switch ports. Args: event (KytosPacketIn): Received Event """ log.debug("PacketIn Received") packet_in = event.content['message'] ethernet = Ethernet() ethernet.unpack(packet_in.data.value) # Ignore LLDP packets or packets not generated by table-miss flows if (ethernet.destination in settings.lldp_macs or packet_in.reason != PacketInReason.OFPR_NO_MATCH): return # Learn the port where the sender is connected in_port = packet_in.in_port.value switch = event.source.switch switch.update_mac_table(ethernet.source, in_port) ports = switch.where_is_mac(ethernet.destination) # Add a flow to the switch if the destination is known if ports: flow_mod = FlowMod() flow_mod.command = FlowModCommand.OFPFC_ADD flow_mod.match = Match() flow_mod.match.dl_src = ethernet.source.value flow_mod.match.dl_dst = ethernet.destination.value flow_mod.match.dl_type = ethernet.ether_type flow_mod.actions.append(ActionOutput(port=ports[0])) event_out = KytosEvent(name=('kytos/of_l2ls.messages.out.' 'ofpt_flow_mod'), content={ 'destination': event.source, 'message': flow_mod }) self.controller.buffers.msg_out.put(event_out) # Send the packet to correct destination or flood it packet_out = PacketOut() packet_out.buffer_id = packet_in.buffer_id packet_out.in_port = packet_in.in_port packet_out.data = packet_in.data port = ports[0] if ports else Port.OFPP_FLOOD packet_out.actions.append(ActionOutput(port=port)) event_out = KytosEvent(name=('kytos/of_l2ls.messages.out.' 'ofpt_packet_out'), content={ 'destination': event.source, 'message': packet_out }) self.controller.buffers.msg_out.put(event_out)
def handle_link_down(self): """Handle circuit when link down. Returns: bool: True if the re-deploy was successly otherwise False. """ success = False if self.is_using_primary_path(): success = self.deploy_to_backup_path() elif self.is_using_backup_path(): success = self.deploy_to_primary_path() if not success and self.dynamic_backup_path: success = self.deploy_to_path() if success: log.debug(f"{self} deployed after link down.") else: self.deactivate() self.current_path = Path([]) self.sync() log.debug(f'Failed to re-deploy {self} after link down.') return success
def request(self, conn): """Ask for switch description. It is done only once per switch.""" dpid = conn.switch.dpid if dpid not in self._desc: req = StatsRequest(body_type=StatsTypes.OFPST_DESC) self._send_event(req, conn) log.debug('Desc request for switch %s sent.', dpid)
def request(self, conn): """Ask for flow stats.""" body = AggregateStatsRequest() # Port.OFPP_NONE and All Tables req = StatsRequest(body_type=StatsType.OFPST_AGGREGATE, body=body) self._send_event(req, conn) log.debug('Aggregate Stats request for switch %s sent.', conn.switch.dpid)
def _negotiate(self, connection, message): """Handle hello messages. This method will handle the incoming hello message by client and deal with negotiation. Parameters: event (KytosMessageInHello): KytosMessageInHelloEvent """ if message.versions: version = self._get_version_from_bitmask(message.versions) else: version = self._get_version_from_header(message.header.version) log.debug('connection %s: negotiated version - %s', connection.id, str(version)) if version is None: self.fail_negotiation(connection, message) raise NegotiationException() version_utils = self.of_core_version_utils[version] version_utils.say_hello(self.controller, connection) connection.protocol.name = 'openflow' connection.protocol.version = version connection.protocol.unpack = unpack connection.protocol.state = 'sending_features' self.send_features_request(connection) log.debug('Connection %s: Hello complete', connection.id)
def handle_link_up(self, event): """Change circuit when link is up or end_maintenance.""" log.debug("Event handle_link_up %s", event) for evc in self.circuits.values(): if evc.is_enabled() and not evc.archived: with evc.lock: evc.handle_link_up(event.content["link"])
def update_instance(self, event, data, error): """Display in Kytos console if the data was updated.""" entities = event.content.get('namespace', '').split('.')[-2] if error: log.error(f'Error trying to update storehouse {entities}.') else: log.debug(f'Storehouse update to entities: {entities}.')
def load_evcs(self, event): """Try to load the unloaded EVCs from storehouse.""" log.debug("Event load_evcs %s", event) circuits = self.storehouse.get_data() if not self._circuits_by_interface: self.load_circuits_by_interface(circuits) interface_id = '{}:{}'.format(event.content['switch'], event.content['port']) for circuit_id in self._circuits_by_interface.get(interface_id, []): if circuit_id in circuits and circuit_id not in self.circuits: try: evc = self._evc_from_dict(circuits[circuit_id]) except ValueError as exception: log.info( f'Could not load EVC {circuit_id} because {exception}') continue log.info(f'Loading EVC {circuit_id}') if evc.archived: continue new_evc = self.circuits.setdefault(circuit_id, evc) if new_evc == evc: if evc.is_enabled(): log.info(f'Trying to deploy EVC {circuit_id}') evc.deploy() self.sched.add(evc)
def execute(self): """Execute once when the napp is running.""" if self._lock.locked(): return log.debug("Starting consistency routine") with self._lock: self.execute_consistency() log.debug("Finished consistency routine")
def handle_link_down(self, event): """Change circuit when link is down or under_mantenance.""" log.debug("Event handle_link_down %s", event) for evc in self.circuits.values(): with evc.lock: if evc.is_affected_by_link(event.content['link']): log.debug(f'Handling evc {evc.id} on link down') evc.handle_link_down()
def _save_evc_callback(self, _event, data, error): """Display the save EVC result in the log.""" self._lock.release() log.debug(f'Lock {self._lock} released.') if error: log.error(f'Can\'t update the {self.box.box_id}') log.info(f'Box {data.box_id} was updated.')
def load_from_store(self, event, box, error): """Save the data retrived from storehouse.""" entities = event.content.get('namespace', '').split('.')[-2] if error: log.error('Error while get a box from storehouse.') else: self.store_items[entities] = box log.debug('Data updated')
def listener(self, event): """Store switch descriptions.""" msg = event.content['message'] if msg.body_type.value in self._stats: stats = self._stats[msg.body_type.value] stats.listen(event.source.switch.dpid, msg.body) else: log.debug('No listener for %s in %s.', msg.body_type.value, list(self._stats.keys()))
def setup(self): """Replace the 'init' method for the KytosApp subclass. The setup method is automatically called by the run method. Users shouldn't call this method directly. """ log.debug("flow-manager starting") self._flow_mods_sent = OrderedDict() self._flow_mods_sent_max_size = FLOWS_DICT_MAX_SIZE
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 listen(self, dpid, desc): """Store switch description.""" self._desc[dpid] = desc switch = self.controller.get_switch_by_dpid(dpid) switch.update_description(desc) log.debug( 'Adding switch %s: mfr_desc = %s, hw_desc = %s,' ' sw_desc = %s, serial_num = %s', dpid, desc.mfr_desc, desc.hw_desc, desc.sw_desc, desc.serial_num)
def handle_packet_in(self, event): """Handle PacketIn Event. Install flows allowing communication between switch ports. Args: event (KytosPacketIn): Received Event """ log.debug("PacketIn Received") packet_in = event.content['message'] ethernet = Ethernet() ethernet.unpack(packet_in.data.value) # Ignore LLDP packets or packets not generated by table-miss flows if (ethernet.destination in settings.LLDP_MACS or packet_in.reason != PacketInReason.OFPR_NO_MATCH): return switch = event.source.switch version = switch.ofp_version # Learn the port where the sender is connected if version == '0x01': in_port = packet_in.in_port.value else: in_port = packet_in.in_port switch.update_mac_table(ethernet.source, in_port) ports = switch.where_is_mac(ethernet.destination) # Add a flow to the switch if the destination is known if ports: flow_mod = self._create_flow_mod(version, ethernet, ports[0]) event_out = KytosEvent(name=('kytos/of_l2ls.messages.out.' 'ofpt_flow_mod'), content={ 'destination': event.source, 'message': flow_mod }) self.controller.buffers.msg_out.put(event_out) # Send the packet to correct destination or flood it packet_out = self._create_packet_out(version, packet_in, ports) event_out = KytosEvent(name=('kytos/of_l2ls.messages.out.' 'ofpt_packet_out'), content={ 'destination': event.source, 'message': packet_out }) self.controller.buffers.msg_out.put(event_out)