def _load_config(self): """Load Gauge config.""" try: conf_hash, _faucet_config_files, faucet_conf_hashes, new_confs = watcher_parser( self.config_file, self.logname, self.prom_client) watchers = [ watcher_factory(watcher_conf)(watcher_conf, self.logname, self.prom_client) for watcher_conf in new_confs ] self.prom_client.reregister_nonflow_vars() except InvalidConfigError as err: self.config_watcher.update(self.config_file) self.logger.error('invalid config: %s', err) return for old_watchers in self.watchers.values(): self._stop_watchers(old_watchers) new_watchers = {} for watcher in watchers: watcher_dpid = watcher.dp.dp_id watcher_type = watcher.conf.type if watcher_dpid not in new_watchers: new_watchers[watcher_dpid] = {} if watcher_type not in new_watchers[watcher_dpid]: new_watchers[watcher_dpid][watcher_type] = [] new_watchers[watcher_dpid][watcher_type].append(watcher) timestamp = time.time() for watcher_dpid, watchers in new_watchers.items(): ryu_dp = self.dpset.get(watcher_dpid) if ryu_dp: self._start_watchers(ryu_dp, watchers, timestamp) self.watchers = new_watchers self.config_watcher.update(self.config_file, {self.config_file: conf_hash}) self.faucet_config_watchers = [] for faucet_config_file, faucet_conf_hash in faucet_conf_hashes.items(): faucet_config_watcher = ConfigWatcher() faucet_config_watcher.update(faucet_config_file, faucet_conf_hash) self.faucet_config_watchers.append(faucet_config_watcher) self.logger.info('watching FAUCET config %s', faucet_config_file) self.logger.info('config complete')
class Gauge(RyuAppBase): """Ryu app for polling Faucet controlled datapaths for stats/state. It can poll multiple datapaths. The configuration files for each datapath should be listed, one per line, in the file set as the environment variable GAUGE_CONFIG. It logs to the file set as the environment variable GAUGE_LOG, """ _CONTEXTS = {'dpset': dpset.DPSet} logname = 'gauge' exc_logname = logname + '.exception' def __init__(self, *args, **kwargs): super(Gauge, self).__init__(*args, **kwargs) self.prom_client = GaugePrometheusClient() self.watchers = {} self.config_watcher = ConfigWatcher() def start(self): super(Gauge, self).start() self._load_config() self.threads.extend([ hub.spawn(thread) for thread in (self._config_file_stat,)]) def _get_watchers(self, ryu_dp, handler_name): """Get Watchers instances to response to an event. Args: ryu_dp (ryu.controller.controller.Datapath): datapath. handler_name (string): handler name to log if datapath unknown. Returns: dict of Watchers instances or None. """ dp_id = ryu_dp.id if dp_id in self.watchers: return self.watchers[dp_id] ryu_dp.close() self.logger.error( '%s: unknown datapath %s', handler_name, dpid_log(dp_id)) return None @kill_on_exception(exc_logname) def _load_config(self): """Load Gauge config.""" for watcher_dpid, old_watchers in list(self.watchers.items()): self._stop_watchers(watcher_dpid, old_watchers) new_confs = watcher_parser(self.config_file, self.logname, self.prom_client) new_watchers = {} for conf in new_confs: watcher = watcher_factory(conf)(conf, self.logname, self.prom_client) watcher_dpid = watcher.dp.dp_id watcher_type = watcher.conf.type if watcher_dpid not in new_watchers: new_watchers[watcher_dpid] = {} if watcher_type not in new_watchers[watcher_dpid]: new_watchers[watcher_dpid][watcher_type] = [] new_watchers[watcher_dpid][watcher_type].append(watcher) for watcher_dpid, watchers in list(new_watchers.items()): ryu_dp = self.dpset.get(watcher_dpid) if ryu_dp: self._start_watchers(ryu_dp, watcher_dpid, watchers) self.watchers = new_watchers self.config_watcher.update(self.config_file) self.logger.info('config complete') @kill_on_exception(exc_logname) def _update_watcher(self, ryu_dp, name, msg): """Call watcher with event data.""" rcv_time = time.time() watchers = self._get_watchers(ryu_dp, '_update_watcher') if watchers is None: return if name in watchers: for watcher in watchers[name]: watcher.update(rcv_time, ryu_dp.id, msg) @kill_on_exception(exc_logname) def _config_file_stat(self): """Periodically stat config files for any changes.""" while True: if self.config_watcher.files_changed(): if self.stat_reload: self.send_event('Gauge', EventReconfigure()) self._thread_jitter(3) @set_ev_cls(EventReconfigure, MAIN_DISPATCHER) def reload_config(self, _): """Handle request for Gauge config reload.""" self.logger.warning('reload config requested') self._load_config() def _start_watchers(self, ryu_dp, dp_id, watchers): """Start watchers for DP if active.""" for watchers_by_name in list(watchers.values()): for i, watcher in enumerate(watchers_by_name): is_active = i == 0 watcher.report_dp_status(1) watcher.start(ryu_dp, is_active) if is_active: self.logger.info( '%s %s watcher starting', dpid_log(dp_id), watcher.conf.type) @kill_on_exception(exc_logname) def _handler_datapath_up(self, ryu_dp): """Handle DP up. Args: ryu_dp (ryu.controller.controller.Datapath): datapath. """ watchers = self._get_watchers(ryu_dp, '_handler_datapath_up') if watchers is None: return self.logger.info('%s up', dpid_log(ryu_dp.id)) ryu_dp.send_msg(valve_of.faucet_config(datapath=ryu_dp)) ryu_dp.send_msg(valve_of.gauge_async(datapath=ryu_dp)) self._start_watchers(ryu_dp, ryu_dp.id, watchers) def _stop_watchers(self, dp_id, watchers): """Stop watchers for DP.""" for watchers_by_name in list(watchers.values()): for watcher in watchers_by_name: watcher.report_dp_status(0) if watcher.is_active(): self.logger.info( '%s %s watcher stopping', dpid_log(dp_id), watcher.conf.type) watcher.stop() @kill_on_exception(exc_logname) def _handler_datapath_down(self, ryu_dp): """Handle DP down. Args: ryu_dp (ryu.controller.controller.Datapath): datapath. """ watchers = self._get_watchers(ryu_dp, '_handler_datapath_down') if watchers is None: return self.logger.info('%s down', dpid_log(ryu_dp.id)) self._stop_watchers(ryu_dp.id, watchers) @set_ev_cls(dpset.EventDP, dpset.DPSET_EV_DISPATCHER) @kill_on_exception(exc_logname) def handler_connect_or_disconnect(self, ryu_event): """Handle DP dis/connect. Args: ryu_event (ryu.controller.event.EventReplyBase): DP reconnection. """ ryu_dp = ryu_event.dp if ryu_event.enter: self._handler_datapath_up(ryu_dp) else: self._handler_datapath_down(ryu_dp) @set_ev_cls(dpset.EventDPReconnected, dpset.DPSET_EV_DISPATCHER) @kill_on_exception(exc_logname) def handler_reconnect(self, ryu_event): """Handle a DP reconnection event. Args: ryu_event (ryu.controller.event.EventReplyBase): DP reconnection. """ ryu_dp = ryu_event.dp self._handler_datapath_up(ryu_dp) @set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER) # pylint: disable=no-member @kill_on_exception(exc_logname) def port_status_handler(self, ryu_event): """Handle port status change event. Args: ryu_event (ryu.controller.event.EventReplyBase): port status change event. """ self._update_watcher( ryu_event.msg.datapath, 'port_state', ryu_event.msg) @set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER) # pylint: disable=no-member @kill_on_exception(exc_logname) def port_stats_reply_handler(self, ryu_event): """Handle port stats reply event. Args: ryu_event (ryu.controller.event.EventReplyBase): port stats event. """ self._update_watcher( ryu_event.msg.datapath, 'port_stats', ryu_event.msg) @set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER) # pylint: disable=no-member @kill_on_exception(exc_logname) def flow_stats_reply_handler(self, ryu_event): """Handle flow stats reply event. Args: ryu_event (ryu.controller.event.EventReplyBase): flow stats event. """ self._update_watcher( ryu_event.msg.datapath, 'flow_table', ryu_event.msg)
class Gauge(OSKenAppBase): """Ryu app for polling Faucet controlled datapaths for stats/state. It can poll multiple datapaths. The configuration files for each datapath should be listed, one per line, in the file set as the environment variable GAUGE_CONFIG. It logs to the file set as the environment variable GAUGE_LOG, """ logname = 'gauge' exc_logname = logname + '.exception' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.watchers = {} self.config_watcher = ConfigWatcher() self.faucet_config_watchers = [] self.prom_client = GaugePrometheusClient(reg=self._reg) self.thread_managers = (self.prom_client, ) @kill_on_exception(exc_logname) def _check_thread_exception(self): super()._check_thread_exception() def _get_watchers(self, ryu_event): """Get Watchers instances to response to an event. Args: ryu_event (ryu.controller.event.EventReplyBase): DP event. Returns: """ return self._get_datapath_obj(self.watchers, ryu_event) @kill_on_exception(exc_logname) def _load_config(self): """Load Gauge config.""" try: conf_hash, _faucet_config_files, faucet_conf_hashes, new_confs = watcher_parser( self.config_file, self.logname, self.prom_client) watchers = [ watcher_factory(watcher_conf)(watcher_conf, self.logname, self.prom_client) for watcher_conf in new_confs ] self.prom_client.reregister_nonflow_vars() except InvalidConfigError as err: self.config_watcher.update(self.config_file) self.logger.error('invalid config: %s', err) return for old_watchers in self.watchers.values(): self._stop_watchers(old_watchers) new_watchers = {} for watcher in watchers: watcher_dpid = watcher.dp.dp_id watcher_type = watcher.conf.type if watcher_dpid not in new_watchers: new_watchers[watcher_dpid] = {} if watcher_type not in new_watchers[watcher_dpid]: new_watchers[watcher_dpid][watcher_type] = [] new_watchers[watcher_dpid][watcher_type].append(watcher) timestamp = time.time() for watcher_dpid, watchers in new_watchers.items(): ryu_dp = self.dpset.get(watcher_dpid) if ryu_dp: self._start_watchers(ryu_dp, watchers, timestamp) self.watchers = new_watchers self.config_watcher.update(self.config_file, {self.config_file: conf_hash}) self.faucet_config_watchers = [] for faucet_config_file, faucet_conf_hash in faucet_conf_hashes.items(): faucet_config_watcher = ConfigWatcher() faucet_config_watcher.update(faucet_config_file, faucet_conf_hash) self.faucet_config_watchers.append(faucet_config_watcher) self.logger.info('watching FAUCET config %s', faucet_config_file) self.logger.info('config complete') @kill_on_exception(exc_logname) def _update_watcher(self, name, ryu_event): """Call watcher with event data.""" watchers, _ryu_dp, msg = self._get_watchers(ryu_event) if watchers is None: return if name in watchers: for watcher in watchers[name]: watcher.update(ryu_event.timestamp, msg) def _config_files_changed(self): for config_watcher in [self.config_watcher ] + self.faucet_config_watchers: if config_watcher.files_changed(): return True return False @set_ev_cls(EventReconfigure, MAIN_DISPATCHER) def reload_config(self, ryu_event): """Handle request for Gauge config reload.""" super().reload_config(ryu_event) self._load_config() @staticmethod def _start_watchers(ryu_dp, watchers, timestamp): """Start watchers for DP if active.""" for watchers_by_name in watchers.values(): for i, watcher in enumerate(watchers_by_name): is_active = i == 0 watcher.report_dp_status(1) watcher.start(ryu_dp, is_active) if isinstance(watcher, GaugePortStatePoller): for port in ryu_dp.ports.values(): msg = parser.OFPPortStatus(ryu_dp, desc=port, reason=ofp.OFPPR_ADD) watcher.update(timestamp, msg) @kill_on_exception(exc_logname) def _datapath_connect(self, ryu_event): """Handle DP up. Args: ryu_event (ryu.controller.event.EventReplyBase): DP event. """ watchers, ryu_dp, _ = self._get_watchers(ryu_event) if watchers is None: return self.logger.info('%s up', dpid_log(ryu_dp.id)) ryu_dp.send_msg(valve_of.faucet_config(datapath=ryu_dp)) ryu_dp.send_msg(valve_of.faucet_async(datapath=ryu_dp, packet_in=False)) self._start_watchers(ryu_dp, watchers, time.time()) @staticmethod def _stop_watchers(watchers): """Stop watchers for DP.""" for watchers_by_name in watchers.values(): for watcher in watchers_by_name: watcher.report_dp_status(0) if watcher.is_active(): watcher.stop() @kill_on_exception(exc_logname) def _datapath_disconnect(self, ryu_event): """Handle DP down. Args: ryu_event (ryu.controller.event.EventReplyBase): DP event. """ watchers, ryu_dp, _ = self._get_watchers(ryu_event) if watchers is None: return self.logger.info('%s down', dpid_log(ryu_dp.id)) self._stop_watchers(watchers) _WATCHER_HANDLERS = { ofp_event.EventOFPPortStatus: 'port_state', # pylint: disable=no-member ofp_event.EventOFPPortStatsReply: 'port_stats', # pylint: disable=no-member ofp_event.EventOFPFlowStatsReply: 'flow_table', # pylint: disable=no-member ofp_event.EventOFPMeterStatsReply: 'meter_stats', # pylint: disable=no-member } @set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER) # pylint: disable=no-member @set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER) # pylint: disable=no-member @set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER) # pylint: disable=no-member @set_ev_cls(ofp_event.EventOFPMeterStatsReply, MAIN_DISPATCHER) # pylint: disable=no-member @kill_on_exception(exc_logname) def update_watcher_handler(self, ryu_event): """Handle any kind of stats/change event. Args: ryu_event (ryu.controller.event.EventReplyBase): stats/change event. """ self._update_watcher(self._WATCHER_HANDLERS[type(ryu_event)], ryu_event)
class Gauge(RyuAppBase): """Ryu app for polling Faucet controlled datapaths for stats/state. It can poll multiple datapaths. The configuration files for each datapath should be listed, one per line, in the file set as the environment variable GAUGE_CONFIG. It logs to the file set as the environment variable GAUGE_LOG, """ logname = 'gauge' exc_logname = logname + '.exception' prom_client = None def __init__(self, *args, **kwargs): super(Gauge, self).__init__(*args, **kwargs) self.watchers = {} self.config_watcher = ConfigWatcher() self.prom_client = GaugePrometheusClient(reg=self._reg) self.thread_managers = (self.prom_client,) @kill_on_exception(exc_logname) def _check_thread_exception(self): super(Gauge, self)._check_thread_exception() def _get_watchers(self, ryu_event): """Get Watchers instances to response to an event. Args: ryu_event (ryu.controller.event.EventReplyBase): DP event. Returns: """ return self._get_datapath_obj(self.watchers, ryu_event) @kill_on_exception(exc_logname) def _load_config(self): """Load Gauge config.""" try: new_confs = watcher_parser(self.config_file, self.logname, self.prom_client) except InvalidConfigError as err: self.logger.error('invalid config: %s', err) return for old_watchers in self.watchers.values(): self._stop_watchers(old_watchers) new_watchers = {} for conf in new_confs: watcher = watcher_factory(conf)(conf, self.logname, self.prom_client) watcher_dpid = watcher.dp.dp_id watcher_type = watcher.conf.type if watcher_dpid not in new_watchers: new_watchers[watcher_dpid] = {} if watcher_type not in new_watchers[watcher_dpid]: new_watchers[watcher_dpid][watcher_type] = [] new_watchers[watcher_dpid][watcher_type].append(watcher) timestamp = time.time() for watcher_dpid, watchers in new_watchers.items(): ryu_dp = self.dpset.get(watcher_dpid) if ryu_dp: self._start_watchers(ryu_dp, watchers, timestamp) self.watchers = new_watchers self.config_watcher.update(self.config_file) self.logger.info('config complete') @kill_on_exception(exc_logname) def _update_watcher(self, name, ryu_event): """Call watcher with event data.""" watchers, ryu_dp, msg = self._get_watchers(ryu_event) if watchers is None: return if name in watchers: for watcher in watchers[name]: watcher.update(ryu_event.timestamp, ryu_dp.id, msg) def _config_files_changed(self): return self.config_watcher.files_changed() @set_ev_cls(EventReconfigure, MAIN_DISPATCHER) def reload_config(self, ryu_event): """Handle request for Gauge config reload.""" super(Gauge, self).reload_config(ryu_event) self._load_config() def _start_watchers(self, ryu_dp, watchers, timestamp): """Start watchers for DP if active.""" for watchers_by_name in watchers.values(): for i, watcher in enumerate(watchers_by_name): is_active = i == 0 watcher.report_dp_status(1) if isinstance(watcher, GaugePortStatePoller): for port in ryu_dp.ports.values(): msg = parser.OFPPortStatus( ryu_dp, desc=port, reason=ofp.OFPPR_ADD) watcher.update(timestamp, ryu_dp.id, msg) watcher.start(ryu_dp, is_active) @kill_on_exception(exc_logname) def _datapath_connect(self, ryu_event): """Handle DP up. Args: ryu_event (ryu.controller.event.EventReplyBase): DP event. """ watchers, ryu_dp, _ = self._get_watchers(ryu_event) if watchers is None: return self.logger.info('%s up', dpid_log(ryu_dp.id)) ryu_dp.send_msg(valve_of.faucet_config(datapath=ryu_dp)) ryu_dp.send_msg(valve_of.faucet_async(datapath=ryu_dp, packet_in=False)) self._start_watchers(ryu_dp, watchers, time.time()) def _stop_watchers(self, watchers): """Stop watchers for DP.""" for watchers_by_name in watchers.values(): for watcher in watchers_by_name: watcher.report_dp_status(0) if watcher.is_active(): watcher.stop() @kill_on_exception(exc_logname) def _datapath_disconnect(self, ryu_event): """Handle DP down. Args: ryu_event (ryu.controller.event.EventReplyBase): DP event. """ watchers, ryu_dp, _ = self._get_watchers(ryu_event) if watchers is None: return self.logger.info('%s down', dpid_log(ryu_dp.id)) self._stop_watchers(watchers) _WATCHER_HANDLERS = { ofp_event.EventOFPPortStatus: 'port_state', # pylint: disable=no-member ofp_event.EventOFPPortStatsReply: 'port_stats', # pylint: disable=no-member ofp_event.EventOFPFlowStatsReply: 'flow_table', # pylint: disable=no-member } @set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER) # pylint: disable=no-member @set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER) # pylint: disable=no-member @set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER) # pylint: disable=no-member @kill_on_exception(exc_logname) def update_watcher_handler(self, ryu_event): """Handle port status change event. Args: ryu_event (ryu.controller.event.EventReplyBase): port status change event. """ self._update_watcher(self._WATCHER_HANDLERS[type(ryu_event)], ryu_event)