def disable_interface(self, interface_disable_id=None, dpid=None): """Administratively disable interfaces in the topology.""" error_list = [] # List of interfaces that were not deactivated. msg_error = "Some interfaces couldn't be found and deactivated: " if dpid is None: dpid = ":".join(interface_disable_id.split(":")[:-1]) try: switch = self.controller.switches[dpid] except KeyError as exc: return jsonify(f"Switch not found: {exc}"), 404 if interface_disable_id: interface_number = int(interface_disable_id.split(":")[-1]) try: switch.interfaces[interface_number].disable() except KeyError as exc: error_list.append(f"Switch {dpid} Interface {exc}") else: for interface in switch.interfaces.values(): interface.disable() if not error_list: log.info(f"Storing administrative state for disabled interfaces.") self.save_status_on_storehouse() return jsonify("Operation successful"), 200 return jsonify({msg_error: error_list}), 409
def _email_send(self, **kwargs): """ Send an email through SMTP """ try: d = { 'm_from': settings.m_from, 'm_to': settings.m_to, 'm_subject': settings.m_subject, 'm_body': settings.m_body, 'm_server_fqdn': settings.m_server_fqdn, 'm_server_port': settings.m_server_port } # overrides global setting values for k, v in kwargs.items(): d[k] = v msg = MIMEText(d['m_body']) msg['Subject'] = d['m_subject'] msg['From'] = d['m_from'] msg['To'] = d['m_to'] s = smtplib.SMTP(host=d['m_server_fqdn'], port=d['m_server_port']) # m_to has to be a list in case of multiple destinations s.sendmail(d['m_from'], d['m_to'].split(", "), msg.as_string()) s.quit() log.info('An email was sent to: {0}'.format(d['m_to'])) except smtplib.SMTPException as e: log.error(str(e)) raise
def _save_status_callback(self, _event, data, error): """Display the saved network status in the log.""" if error: log.error(f'Can\'t update persistence box {data.box_id}.') log.info('Network administrative status saved in ' f'{self.namespace}.{data.box_id}')
def execute(self) -> None: """Execute.""" log.info("Starting uvloop") self.loop = uvloop.new_event_loop() asyncio.set_event_loop(self.loop) self._wait_all_dpids(self.dpids) self.loop.run_until_complete(self.main_coroutine())
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 {data.namespace}.') self.box = data log.info(f'Box {self.box.box_id} was load from storehouse.')
def _send(self, d): """ Sends this 'd' dict via slacker """ try: # if settings wasn't properly setup in the first place if not self.has_failed: if not d.get('payload'): raise ValueError( "The dictionary should contain at least the 'payload' key-value" ) slack_msg = "" for k in ['source', 'tag', 'payload']: slack_msg = self._parse_str(slack_msg, d.get(k)) # fallback to #general ch = d.get('channel') if d.get('channel') else 'general' log.info('channel:{0} msg:{1}'.format(ch, slack_msg)) self.slack.chat.post_message(ch, slack_msg) except requests.exceptions.ConnectionError as e: err = "ConnectionError to Slack API. Make sure you are connected and can reach Slack API." log.error(err) raise ValueError(err) except self.error as e: err = "This slack channel {0} doesn't exist".format(ch) log.error(err) raise ValueError(err)
def handle_features_reply(self, event): """Handle kytos/of_core.messages.in.ofpt_features_reply event. This is the end of the Handshake workflow of the OpenFlow Protocol. Args: event (KytosEvent): Event with features reply message. """ connection = event.source version_utils = self.of_core_version_utils[connection.protocol.version] switch = version_utils.handle_features_reply(self.controller, event) if (connection.is_during_setup() and connection.protocol.state == 'waiting_features_reply'): connection.protocol.state = 'handshake_complete' connection.set_established_state() version_utils.send_desc_request(self.controller, switch) if settings.SEND_SET_CONFIG: version_utils.send_set_config(self.controller, switch) log.info('Connection %s, Switch %s: OPENFLOW HANDSHAKE COMPLETE', connection.id, switch.dpid) event_raw = KytosEvent( name='kytos/of_core.handshake.completed', content={'switch': switch}) self.controller.buffers.app.put(event_raw)
def deploy_to_path(self, path=None): """Install the flows for this circuit. Procedures to deploy: 0. Remove current flows installed 1. Decide if will deploy "path" or discover a new path 2. Choose vlan 3. Install NNI flows 4. Install UNI flows 5. Activate 6. Update current_path 7. Update links caches(primary, current, backup) """ self.remove_current_flows() if not self.should_deploy(path): path = self.discover_new_path() if not path: return False path.choose_vlans() self._install_nni_flows(path) self._install_uni_flows(path) self.activate() self.current_path = path self.sync() log.info(f"{self} was deployed.") return True
def setup(self): """Replace the '__init__' method for the KytosNApp subclass. The setup method is automatically called by the controller when your application is loaded. So, if you have any setup routine, insert it here. """ # self.nodes = dict(settings.HOST_NODE) # self.edges = dict(settings.HOST_EDGE) # self.topology_not_set = True self.trident = TridentServer() self.trident.set_ctx_controller(self.controller) sa_name = 'authenticated' # pkt = TridentPacket('10.0.0.2', '*', 0, 0, '*') # self.trident.update_sa(sa_name, pkt, 'Accept') # self.interface2DirLink = {} self.debug = False # self._system_ready = False self.lock = threading.Lock() self.sw_nodes = {} self.sw_links = {} # {(swid, port):(swid, port)} log.info('Main setup')
def run_important_traces(self): try: important_circuits = settings.IMPORTANT_CIRCUITS except AttributeError: return for circuit in important_circuits: entries = { 'trace': { 'switch': { 'dpid': circuit['dpid_a'], 'in_port': circuit['port_a'] }, 'eth': { 'dl_vlan': circuit['vlan_a'] } } } result = requests.put( 'http://localhost:8181/api/amlight/sdntrace/trace', json=entries) trace = result.json() trace_id = trace['result']['trace_id'] type = None while type != 'last': time.sleep(5) result = requests.get( 'http://localhost:8181/api/amlight/sdntrace/trace/%s' % trace_id) trace = result.json() type = trace['result'][-1]['type'] log.info(trace)
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.info(f'Box {self.box.box_id} was created in {self.namespace}.')
def delete_circuit(self, circuit_id): """Remove a circuit. First, the flows are removed from the switches, and then the EVC is disabled. """ log.debug('delete_circuit /v2/evc/%s', circuit_id) try: evc = self.circuits[circuit_id] except KeyError: result = f'circuit_id {circuit_id} not found' log.debug('delete_circuit result %s %s', result, 404) raise NotFound(result) if evc.archived: result = f'Circuit {circuit_id} already removed' log.debug('delete_circuit result %s %s', result, 404) raise NotFound(result) log.info('Removing %s', evc) evc.remove_current_flows() evc.deactivate() evc.disable() self.sched.remove(evc) evc.archive() evc.sync() log.info('EVC removed. %s', evc) result = {'response': f'Circuit {circuit_id} removed'} status = 200 log.debug('delete_circuit result %s %s', result, status) return jsonify(result), status
def shutdown(self): """This method is executed when your napp is unloaded. If you have some cleanup procedure, insert it here. """ self.plugin.stop() log.info("stop")
def _restore_status(self, switches_status, interfaces_status): """Restore the network administrative status saved in StoreHouse.""" # restore Switches for switch_id, state in switches_status.items(): try: if state: self.controller.switches[switch_id].enable() else: self.controller.switches[switch_id].disable() except KeyError: error = ('Error while restoring switches status. The ' f'{switch_id} does not exist.') raise KeyError(error) # restore interfaces for interface_id, state in interfaces_status.items(): switch_id = ":".join(interface_id.split(":")[:-1]) interface_number = int(interface_id.split(":")[-1]) try: switch = self.controller.switches[switch_id] if state: switch.interfaces[interface_number].enable() else: switch.interfaces[interface_number].disable() except KeyError: error = ('Error while restoring interface status. The ' f'interface {interface_id} does not exist.') raise KeyError(error) log.info('Network status restored.')
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_network_status(self): """Load network status saved in storehouse.""" try: status = self.storehouse.get_data() except FileNotFoundError as error: log.info(error) return if status: switches = status['network_status']['switches'] self.links_state = status['network_status']['links'] for switch_id, switch_att in switches.items(): # get switches status self.switches_state[switch_id] = switch_att['enabled'] iface = switch_att['interfaces'] # get interface status for iface_id, iface_att in iface.items(): enabled_value = iface_att['enabled'] lldp_value = iface_att['lldp'] self.interfaces_state[iface_id] = (enabled_value, lldp_value) else: error = 'There is no status saved to restore.' log.info(error)
def _slack_send(self, **kwargs): """ Send some key value pairs via slacker """ try: # if settings wasn't properly setup in the first place if not self.has_failed: if not kwargs.get('m_body'): err = "Missing the 'm_body' argument" log.error(err) raise ValueError(err) slack_msg = "" for k in ['source', 'm_body']: slack_msg = self._parse_str(slack_msg, kwargs.get(k)) # fallback to #general ch = '' if kwargs.get('channel'): ch = kwargs.get('channel') elif settings.slack_channel: ch = settings.slack_channel else: ch = 'general' log.info('channel:{0} msg:{1}'.format(ch, slack_msg)) self.slack.chat.post_message(ch, slack_msg) except requests.exceptions.ConnectionError: err = "ConnectionError to Slack API." log.error(err) raise except self.slack_error as e: log.error(str(e)) raise
def delete_circuit(self, circuit_id): """Remove a circuit. First, the flows are removed from the switches, and then the EVC is disabled. """ log.debug("delete_circuit /v2/evc/%s", circuit_id) try: evc = self.circuits[circuit_id] except KeyError: result = f"circuit_id {circuit_id} not found" log.debug("delete_circuit result %s %s", result, 404) raise NotFound(result) from NotFound if evc.archived: result = f"Circuit {circuit_id} already removed" log.debug("delete_circuit result %s %s", result, 404) raise NotFound(result) from NotFound log.info("Removing %s", evc) with evc.lock: evc.remove_current_flows() evc.deactivate() evc.disable() self.sched.remove(evc) evc.archive() evc.sync() log.info("EVC removed. %s", evc) result = {"response": f"Circuit {circuit_id} removed"} status = 200 log.debug("delete_circuit result %s %s", result, status) emit_event(self.controller, "deleted", evc_id=evc.id) return jsonify(result), status
def match_to_dict(self): """Convert a match in OF 1.3 to a dictionary.""" match = dict() for name in self.match: match[name] = self.match[name].value log.info('Match %s' % match) return match
def _build_lldp_packet_out(version, port_number, data): """Build a LLDP PacketOut message. Args: version (int): OpenFlow version port_number (int): Switch port number where the packet must be forwarded to. data (bytes): Binary data to be sent through the port. Returns: PacketOut message for the specific given OpenFlow version, if it is supported. None if the OpenFlow version is not supported. """ if version == 0x01: action_output_class = AO10 packet_out_class = PO10 elif version == 0x04: action_output_class = AO13 packet_out_class = PO13 else: log.info('Openflow version %s is not yet supported.', version) return None output_action = action_output_class() output_action.port = port_number packet_out = packet_out_class() packet_out.data = data packet_out.actions.append(output_action) return packet_out
def delete_circuit(self, circuit_id): """Remove a circuit. First, the flows are removed from the switches, and then the EVC is disabled. """ try: evc = self.circuits[circuit_id] except KeyError: result = {'response': f'circuit_id {circuit_id} not found'} status = 404 else: log.info(f'Removing {circuit_id}') if evc.archived: result = {'response': f'Circuit {circuit_id} already removed'} status = 404 else: evc.remove_current_flows() evc.deactivate() evc.disable() self.sched.remove(evc) evc.archive() evc.sync() result = {'response': f'Circuit {circuit_id} removed'} status = 200 return jsonify(result), status
def get_json_topology(self): """Return a json with topology details. Method responsible to return a json in /kytos/topology route. Returns: topology (string): json with topology details. """ log.info("trying to get the topology") nodes, links = [], [] switches = self.controller.switches switches_mac_address = [] for switch in switches.values(): for interface in switch.interfaces.values(): switches_mac_address.append(interface.address) for _, switch in switches.items(): nodes.append(switch.as_dict()) for _, interface in switch.interfaces.items(): link = { 'source': switch.id, 'target': interface.id, 'type': 'interface' } nodes.append(interface.as_dict()) links.append(link) for endpoint, _ in interface.endpoints: if isinstance(endpoint, HWAddress): if endpoint in switches_mac_address: continue link = { 'source': interface.id, 'target': endpoint.value, 'type': 'link', 'link_speed': interface.get_hr_speed() } host = { "type": 'host', "id": endpoint.value, "name": endpoint.value, "mac": endpoint.value } if host not in nodes: nodes.append(host) if not interface.is_link_between_switches(): links.append(link) else: link = { 'source': interface.id, 'target': endpoint.id, 'type': 'link', 'link_speed2': interface.get_hr_speed() } links.append(link) output = {'nodes': nodes, 'links': links} return json.dumps(output)
def setup(self): """Replace the '__init__' method for the KytosNApp subclass. Execute right after the NApp is loaded. """ self.metadata_cache = {} self.create_cache() log.info("Storehouse NApp started.")
def setup(self): """Init method for the napp.""" log.info("Kronos NApp started.") if settings.DEFAULT_BACKEND.lower() == 'influxdb': self.backend = InfluxBackend(settings) elif settings.DEFAULT_BACKEND.lower() == 'csv': self.backend = CSVBackend(settings)
def initREST(self): log.info("we are starting our own restful api") app = connexion.App(__name__, specification_dir='./swagger/') app.app.json_encoder = JSONEncoder app.add_api( 'swagger.yaml', arguments={'title': 'Cross-domain path and resource discovery'}) app.run(port=8080)
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 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 storehouse_create(event, box, error=False): """Log the return of an object creation in storehouse.""" # pylint: disable=unused-argument if error: msg = 'Flow info not installed' else: msg = 'Flow info installed sucessfully' log.info(msg)
def setup(self): """Init method for the napp.""" log.info("Time Series NApp started.") if settings.DEFAULT_BACKEND == 'INFLUXDB': self.backend = InfluxBackend(settings) elif settings.DEFAULT_BACKEND == 'CSV': self.backend = CSVBackend(settings)
def verify_storehouse(self, entities): """Request a list of box saved by specific entity.""" name = 'kytos.storehouse.list' content = {'namespace': f'kytos.topology.{entities}.metadata', 'callback': self.request_retrieve_entities} event = KytosEvent(name=name, content=content) self.controller.buffers.app.put(event) log.info(f'verify data in storehouse for {entities}.')
def setup(self): """ Default Kytos/Napps setup call. """ log.info("Starting Kytos SDNTrace App version %s!" % VERSION) # Create list of switches self.switches = Switches(self.controller.switches) # noqa: E501 pylint: disable=attribute-defined-outside-init # Instantiate TraceManager self.tracing = TraceManager(self.controller) # pylint: disable=W0201
def _spawn_trace(self, trace_id, trace_entries): """ Once a request is found by the run_traces method, instantiate a TracePath class and run the tracepath Args: trace_id: trace request id trace_entries: TraceEntries class """ log.info("Creating thread to trace request id %s..." % trace_id) tracer = TracePath(self, trace_id, trace_entries) tracer.tracepath() del self._running_traces[trace_id]