class Main(KytosNApp): """Main class of kytos/topology NApp. This class is the entry point for this napp. """ def setup(self): """Initiate a new topology and preload configurations.""" self.topology = Topology() def execute(self): """Do nothing.""" pass def shutdown(self): """Do nothing.""" log.info('NApp kytos/topology shutting down.') @rest('v2/devices') def get_devices(self): """Return a json with all the devices in the topology. For now, a device can be a Switch or a Host. """ out = {'devices': {d.id: d.as_dict() for d in self.topology.devices}} return jsonify(out) @rest('v2/links') def get_links(self): """Return a json with all the links in the topology. Links are directed connections between devices. """ links = [] for link in self.topology.links: links.append({'source': link[0], 'target': link[1]}) return jsonify({'links': links}) @rest('v2/') def get_topology(self): """Return the latest known topology. This topology is updated when there are network events. """ return jsonify(self.topology.to_dict()) @listen_to('.*.switch.(new|reconnected)') def handle_new_switch(self, event): """Create a new Device on the Topology. Handle the event of a new created switch and update the topology with this new device. """ switch = event.content['switch'] switch.id_ = switch.id self.topology.add_device(switch) log.debug('Switch %s added to the Topology.', switch.id) self.notify_topology_update() @listen_to('.*.connection.lost') def handle_connection_lost(self, event): """Remove a Device from the topology. Remove the disconnected Device and every link that has one of its interfaces. """ switch = event.content['source'].switch if switch: self.topology.remove_device(switch) log.debug('Switch %s removed from the Topology.', switch.id) self.notify_topology_update() @listen_to('.*.switch.interface.modified') def handle_interface_modified(self, event): """Update the topology based on a Port Modified event. If a interface has its link down we remove this interface from the topology. """ interface = event.content['interface'] # If the state bitmap is odd, it means there is no link. # Otherwise, we assume the interface was not disconnected. if interface.state % 2: self.topology.remove_interface_links(interface.id) self.notify_topology_update() @listen_to('.*.switch.interface.deleted') def handle_interface_deleted(self, event): """Update the topology based on a Port Delete event.""" interface = event.content['interface'] self.topology.remove_interface_links(interface.id) self.notify_topology_update() def add_host(self, event): """Update the topology with a new Host.""" interface = event.content['port'] mac = event.content['reachable_mac'] host = Host(mac) link = self.topology.get_link(interface.id) if link is not None: return self.topology.add_link(interface.id, host.id) self.topology.add_device(host) if settings.DISPLAY_FULL_DUPLEX_LINKS: self.topology.add_link(host.id, interface.id) @listen_to('.*.interface.is.nni') def add_links(self, event): """Update the topology with links related to the NNI interfaces.""" interface_a = event.content['interface_a'] interface_b = event.content['interface_b'] self.topology.remove_interface_links(interface_a.id) self.topology.remove_interface_links(interface_b.id) self.topology.add_link(*sorted([interface_a.id, interface_b.id])) if settings.DISPLAY_FULL_DUPLEX_LINKS: self.topology.add_link( *sorted([interface_b.id, interface_a.id], reverse=True)) interface_a.nni = True interface_b.nni = True # Update interfaces from link as NNI self.notify_topology_update() def notify_topology_update(self): """Send an event to notify about updates on the topology.""" name = 'kytos/topology.updated' event = KytosEvent(name=name, content={'topology': self.topology}) self.controller.buffers.app.put(event)
class Main(KytosNApp): """Main class of kytos/topology NApp. This class is the entry point for this napp. """ def setup(self): """Initiate a new topology and preload configurations.""" self.topology = Topology() topology_json = settings.PRELOAD_TOPOLOGY_PATH if Path(topology_json.exists()): with open(Path(topology_json), 'r') as data: data = json.loads(data.read()) try: self.topology = Topology.from_json(data) except Exception: self.topology = Topology() else: self.topology = Topology() def execute(self): """Do nothing.""" pass def shutdown(self): """Do nothing.""" log.info('NApp kytos/topology shutting down.') @rest('devices') def get_device(self): """Return a json with all the devices in the topology. Responsible for the /api/kytos/topology/devices endpoint. e.g. [<list of devices>] Returns: string: json with all the devices in the topology """ return json.dumps([device.to_json() for device in self.topology.devices]) @rest('links') def get_links(self): """Return a json with all the links in the topology. Responsible for the /api/kytos/topology/links endpoint. Returns: string: json with all the links in the topology. """ return json.dumps([link.to_json() for link in self.topology.links]) @rest('') def get_topology(self): """Return full topology. Responsible for the /api/kytos/topology endpoint. Returns: string: json with the full topology. """ return json.dumps(self.topology.to_json()) @listen_to('.*.switch.new') def handle_new_switch(self, event): """Create a new Device on the Topology. Handle the event of a new created switch and instantiate a new Device on the topology. """ switch = event.content['switch'] device = Device(switch.id) self.topology.add_device(device) log.debug('Switch %s added to the Topology.', device.id_) self.notify_topology_update() @listen_to('.*.switch.port.created') def handle_port_created(self, event): """Listen an event and create the respective port, if needed.""" device = self.topology.get_device(event.content['switch']) if device is None: return port = device.get_port(event.content['port']) if port is not None: msg = 'The port %s already exists on the switch %s. ' msg += 'It cannot be created again.' log.debug(msg, event.content['port'], device.id_) return port = Port(number=event.content['port']) port.properties = event.content['port_description'] if 'mac' in port.properties: port.mac = port.properties['mac'] if 'alias' in port.properties and port.properties['alias']: port.alias = port.properties['alias'] device.add_port(port) @listen_to('.*.switch.port.modified') def handle_port_modified(self, event): """Update port properties based on a Port Modified event.""" # Get Switch device = self.topology.get_device(event.content['switch']) if device is None: log.error('Device %s not found.', event.content['switch']) return # Get Switch Port port = device.get_port(event.content['port']) if port is None: msg = 'Port %s not found on switch %s. Creating new port.' log(msg, event.content['port'], device.id_) self.handle_port_created(event) return port.properties = event.content['port_description'] if 'mac' in port.properties: port.mac = port.properties['mac'] @listen_to('.*.switch.port.deleted') def handle_port_deleted(self, event): """Delete a port from a switch. It also does the necessary cleanup on the topology. """ # Get Switch device = self.topology.get_device(event.content['switch']) if device is None: log.error('Device %s not found.', event.content['switch']) return # Get Switch Port port = device.get_port(event.content['port']) if port is None: msg = 'Port %s not found on switch %s. Nothing to delete.' log(msg, event.content['port'], device.id_) return # Create the interface object interface = Interface(device, port) # Get Link from Interface link = self.topology.get_link(interface) # Destroy the link self.topology.unset_link(link) # Remove the port device.remove_port(port) @listen_to('.*.reachable.mac') def set_link(self, event): """Set a new link if needed.""" device_a = self.topology.get_device(event.content['switch']) if device_a is None: device_a = Device(event.content['switch']) self.topology.add_device(device_a) if not device_a.has_port(event.content['port']): port = Port(number=event.content['port']) device_a.add_port(port) interface_a = device_a.get_interface_for_port(event.content['port']) link = self.topology.get_link(interface_a) if link is not None: return # Try getting one interface for that specific mac. mac = event.content['reachable_mac'] device_b = None interface_b = None for device in self.topology.devices: for port in device.ports: if port.mac == mac: interface_b = device.get_interface_for_port(port) if interface_b is None: device_b = Device(mac, dtype=DeviceType.HOST) port = Port(mac=mac) device_b.add_port(port) self.topology.add_device(device_b) interface_b = Interface(device_b, port) self.topology.set_link(interface_a, interface_b) @listen_to('.*.interface.is.nni') def set_interface_as_nni(self, event): """Set an existing interface as NNI (and the interface linked to it). If the interface is already a NNI, then nothing is done. If the interface was not set as NNI, then it will be set and also an 'kytos.topology.updated' event will be raised. Args: event (KytosEvent): a dict with interface_a and interface_b keys, each one containing switch id and port number. """ interface_a = event.content['interface_a'] interface_b = event.content['interface_b'] # Get Switch A switch_a = self.topology.get_device(interface_a['switch']) if switch_a is None: switch_a = Device(switch_a) self.topology.add_device(switch_a) # Get Port A port_a = switch_a.get_port(interface_a['port']) if port_a is None: port_a = Port(interface_a['port']) switch_a.add_port(port_a) # Interface A interface_a = Interface(switch_a, port_a) # Get Switch B switch_b = self.topology.get_device(interface_b['switch']) if switch_b is None: switch_b = Device(switch_b) self.topology.add_device(switch_b) # Get Port B port_b = switch_b.get_port(interface_b['port']) if port_b is None: port_b = Port(interface_b['port']) switch_b.add_port(port_b) # Interface A interface_b = Interface(switch_b, port_b) # Get Link from Interface link_a = self.topology.get_link(interface_a) link_b = self.topology.get_link(interface_b) if link_a is not None and link_a is link_b: return link = self.topology.set_link(interface_a, interface_b, force=True) link.set_nnis() # Update interfaces from link as NNI self.notify_topology_update() def notify_topology_update(self): """Send an event to notify about updates on the Topology.""" name = 'kytos/topology.updated' event = KytosEvent(name=name, content={'topology': self.topology}) self.controller.buffers.app.put(event)