mirror of
				https://github.com/KevinMidboe/mktxp-no-cli.git
				synced 2025-10-29 17:50:23 +00:00 
			
		
		
		
	cli options, fixes
This commit is contained in:
		| @@ -5,7 +5,7 @@ | ||||
|  | ||||
|  | ||||
|  | ||||
| <img src="http://www.akpdev.com/images/mktxp_b_t.png" width="460" height="600"> | ||||
| <img src="http://www.akpdev.com/images/mktxp_b_t.png" width="460" height="620"> | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -28,8 +28,7 @@ class MKTXPProcessor: | ||||
|     def start(): | ||||
|         router_metrics_handler = RouterMetricsHandler() | ||||
|         REGISTRY.register(CollectorsHandler(router_metrics_handler)) | ||||
|         port=config_handler.mktxp_port() | ||||
|         MKTXPProcessor.run(port=port) | ||||
|         MKTXPProcessor.run(port=config_handler._entry().port) | ||||
|  | ||||
|     @staticmethod | ||||
|     def run(server_class=HTTPServer, handler_class=MetricsHandler, port = None): | ||||
|   | ||||
| @@ -13,3 +13,7 @@ | ||||
|  | ||||
| [MKTXP] | ||||
|     port = 49090 | ||||
|     socket_timeout = 2 | ||||
|     initial_delay_on_failure = 120 | ||||
|     max_delay_on_failure = 900 | ||||
|     delay_inc_div = 5 | ||||
|   | ||||
| @@ -33,7 +33,8 @@ class MKTXPConfigKeys: | ||||
|     PASSWD_KEY = 'password' | ||||
|  | ||||
|     SSL_KEY = 'use_ssl' | ||||
|     SSL_CERTIFICATE = 'ssl_certificate' | ||||
|     NO_SSL_CERTIFICATE = 'no_ssl_certificate' | ||||
|     SSL_CERTIFICATE_VERIFY = 'ssl_certificate_verify' | ||||
|  | ||||
|     FE_DHCP_KEY = 'dhcp' | ||||
|     FE_DHCP_LEASE_KEY = 'dhcp_lease'     | ||||
| @@ -46,6 +47,12 @@ class MKTXPConfigKeys: | ||||
|     FE_CAPSMAN_KEY = 'capsman' | ||||
|     FE_CAPSMAN_CLIENTS_KEY = 'capsman_clients' | ||||
|  | ||||
|     MKTXP_SOCKET_TIMEOUT = 'socket_timeout'     | ||||
|     MKTXP_SOCKET_TIMEOUT = 'socket_timeout'    | ||||
|     MKTXP_INITIAL_DELAY = 'initial_delay_on_failure' | ||||
|     MKTXP_MAX_DELAY = 'max_delay_on_failure' | ||||
|     MKTXP_INC_DIV = 'delay_inc_div' | ||||
|  | ||||
|     # UnRegistered entries placeholder | ||||
|     NO_ENTRIES_REGISTERED = 'NoEntriesRegistered' | ||||
|  | ||||
| @@ -53,15 +60,21 @@ class MKTXPConfigKeys: | ||||
|     ROUTERBOARD_NAME = 'routerboard_name' | ||||
|     ROUTERBOARD_ADDRESS = 'routerboard_address' | ||||
|  | ||||
|     # Default ports | ||||
|     # Default values | ||||
|     DEFAULT_API_PORT = 8728 | ||||
|     DEFAULT_API_SSL_PORT = 8729 | ||||
|     DEFAULT_MKTXP_PORT = 49090  | ||||
|     DEFAULT_MKTXP_PORT = 49090 | ||||
|     DEFAULT_MKTXP_SOCKET_TIMEOUT = 2 | ||||
|     DEFAULT_MKTXP_INITIAL_DELAY = 120 | ||||
|     DEFAULT_MKTXP_MAX_DELAY = 900 | ||||
|     DEFAULT_MKTXP_INC_DIV = 5 | ||||
|  | ||||
|     BOOLEAN_KEYS = [ENABLED_KEY, SSL_KEY, SSL_CERTIFICATE,   | ||||
|     BOOLEAN_KEYS = (ENABLED_KEY, SSL_KEY, NO_SSL_CERTIFICATE, SSL_CERTIFICATE_VERIFY,  | ||||
|                       FE_DHCP_KEY, FE_DHCP_LEASE_KEY, FE_DHCP_POOL_KEY, FE_INTERFACE_KEY,  | ||||
|                       FE_MONITOR_KEY, FE_ROUTE_KEY, FE_WIRELESS_KEY, FE_WIRELESS_CLIENTS_KEY, FE_CAPSMAN_KEY, FE_CAPSMAN_CLIENTS_KEY] | ||||
|                       FE_MONITOR_KEY, FE_ROUTE_KEY, FE_WIRELESS_KEY, FE_WIRELESS_CLIENTS_KEY, FE_CAPSMAN_KEY, FE_CAPSMAN_CLIENTS_KEY) | ||||
|      | ||||
|     STR_KEYS = [HOST_KEY, USER_KEY, PASSWD_KEY] | ||||
|     INT_KEYS = [PORT_KEY, MKTXP_SOCKET_TIMEOUT, MKTXP_INITIAL_DELAY, MKTXP_MAX_DELAY, MKTXP_INC_DIV] | ||||
|  | ||||
|     # MKTXP config entry nane | ||||
|     MKTXP_CONFIG_ENTRY_NAME = 'MKTXP' | ||||
| @@ -70,12 +83,15 @@ class MKTXPConfigKeys: | ||||
| class ConfigEntry: | ||||
|     MKTXPEntry = namedtuple('MKTXPEntry', [MKTXPConfigKeys.ENABLED_KEY, MKTXPConfigKeys.HOST_KEY, MKTXPConfigKeys.PORT_KEY,  | ||||
|                          MKTXPConfigKeys.USER_KEY, MKTXPConfigKeys.PASSWD_KEY,  | ||||
|                          MKTXPConfigKeys.SSL_KEY, MKTXPConfigKeys.SSL_CERTIFICATE,  | ||||
|                          MKTXPConfigKeys.SSL_KEY, MKTXPConfigKeys.NO_SSL_CERTIFICATE, MKTXPConfigKeys.SSL_CERTIFICATE_VERIFY, | ||||
|                           | ||||
|                          MKTXPConfigKeys.FE_DHCP_KEY, MKTXPConfigKeys.FE_DHCP_LEASE_KEY, MKTXPConfigKeys.FE_DHCP_POOL_KEY, MKTXPConfigKeys.FE_INTERFACE_KEY,  | ||||
|                          MKTXPConfigKeys.FE_MONITOR_KEY, MKTXPConfigKeys.FE_ROUTE_KEY, MKTXPConfigKeys.FE_WIRELESS_KEY, MKTXPConfigKeys.FE_WIRELESS_CLIENTS_KEY, | ||||
|                          MKTXPConfigKeys.FE_CAPSMAN_KEY, MKTXPConfigKeys.FE_CAPSMAN_CLIENTS_KEY | ||||
|                          ]) | ||||
|     _MKTXPEntry = namedtuple('_MKTXPEntry', [MKTXPConfigKeys.PORT_KEY, MKTXPConfigKeys.MKTXP_SOCKET_TIMEOUT,  | ||||
|                                             MKTXPConfigKeys.MKTXP_INITIAL_DELAY, MKTXPConfigKeys.MKTXP_MAX_DELAY, MKTXPConfigKeys.MKTXP_INC_DIV]) | ||||
|  | ||||
|  | ||||
| class OSConfig(metaclass = ABCMeta): | ||||
|     ''' OS-related config | ||||
| @@ -139,7 +155,7 @@ class MKTXPConfigHandler: | ||||
|         ''' (Force-)Read conf data from disk | ||||
|         ''' | ||||
|         self.config = ConfigObj(self.usr_conf_data_path) | ||||
|         self.mktxp_config = ConfigObj(self.mktxp_conf_path) | ||||
|         self._config = ConfigObj(self.mktxp_conf_path) | ||||
|  | ||||
|     def _create_os_path(self, os_path, resource_path): | ||||
|         if not os.path.exists(os_path): | ||||
| @@ -190,37 +206,55 @@ class MKTXPConfigHandler: | ||||
|     def entry(self, entry_name): | ||||
|         ''' Given an entry name, reads and returns the entry info | ||||
|         ''' | ||||
|         entry_reader = self._entry_reader(entry_name) | ||||
|         entry_reader = self.entry_reader(entry_name) | ||||
|         return ConfigEntry.MKTXPEntry(**entry_reader) | ||||
|  | ||||
|     def mktxp_port(self): | ||||
|         if self.mktxp_config.get(MKTXPConfigKeys.MKTXP_CONFIG_ENTRY_NAME) and \ | ||||
|                 self.mktxp_config[MKTXPConfigKeys.MKTXP_CONFIG_ENTRY_NAME].get(MKTXPConfigKeys.PORT_KEY): | ||||
|                 return self.mktxp_config[MKTXPConfigKeys.MKTXP_CONFIG_ENTRY_NAME].as_int(MKTXPConfigKeys.PORT_KEY)         | ||||
|         return MKTXPConfigKeys.DEFAULT_MKTXP_PORT | ||||
|     def _entry(self): | ||||
|         ''' MKTXP internal config entry | ||||
|         ''' | ||||
|         _entry_reader = self._entry_reader() | ||||
|         return ConfigEntry._MKTXPEntry(**_entry_reader) | ||||
|  | ||||
|  | ||||
|     # Helpers | ||||
|     def _entry_reader(self, entry_name): | ||||
|         entry = {} | ||||
|     def entry_reader(self, entry_name): | ||||
|         entry_reader = {} | ||||
|         for key in MKTXPConfigKeys.BOOLEAN_KEYS: | ||||
|             if self.config[entry_name].get(key): | ||||
|                 entry[key] = self.config[entry_name].as_bool(key) | ||||
|                 entry_reader[key] = self.config[entry_name].as_bool(key) | ||||
|             else: | ||||
|                 entry[key] = False | ||||
|                 entry_reader[key] = False | ||||
|  | ||||
|         for key in MKTXPConfigKeys.STR_KEYS: | ||||
|             entry[key] = self.config[entry_name][key] | ||||
|             entry_reader[key] = self.config[entry_name][key] | ||||
|  | ||||
|         # port | ||||
|         if self.config[entry_name].get(MKTXPConfigKeys.PORT_KEY): | ||||
|             entry[MKTXPConfigKeys.PORT_KEY] = self.config[entry_name].as_int(MKTXPConfigKeys.PORT_KEY) | ||||
|             entry_reader[MKTXPConfigKeys.PORT_KEY] = self.config[entry_name].as_int(MKTXPConfigKeys.PORT_KEY) | ||||
|         else: | ||||
|             if entry[MKTXPConfigKeys.SSL_KEY]: | ||||
|                 entry[MKTXPConfigKeys.PORT_KEY] = MKTXPConfigKeys.DEFAULT_API_SSL_PORT | ||||
|             else: | ||||
|                 entry[MKTXPConfigKeys.PORT_KEY] = MKTXPConfigKeys.DEFAULT_API_PORT | ||||
|             entry_reader[MKTXPConfigKeys.PORT_KEY] = self._default_value_for_key(MKTXPConfigKeys.SSL_KEY, entry_reader[MKTXPConfigKeys.SSL_KEY]) | ||||
|  | ||||
|         return entry | ||||
|         return entry_reader | ||||
|  | ||||
|     def _entry_reader(self): | ||||
|         _entry_reader = {} | ||||
|         entry_name = MKTXPConfigKeys.MKTXP_CONFIG_ENTRY_NAME | ||||
|         for key in MKTXPConfigKeys.INT_KEYS: | ||||
|             if self._config[entry_name].get(key): | ||||
|                 _entry_reader[key] = self._config[entry_name].as_int(key) | ||||
|             else: | ||||
|                 _entry_reader[key] = self._default_value_for_key(key) | ||||
|         return _entry_reader | ||||
|  | ||||
|     def _default_value_for_key(self, key, value = None): | ||||
|         return { | ||||
|                 MKTXPConfigKeys.SSL_KEY: lambda value: MKTXPConfigKeys.DEFAULT_API_SSL_PORT if value else MKTXPConfigKeys.DEFAULT_API_PORT, | ||||
|                 MKTXPConfigKeys.PORT_KEY: lambda value: MKTXPConfigKeys.DEFAULT_MKTXP_PORT, | ||||
|                 MKTXPConfigKeys.MKTXP_SOCKET_TIMEOUT: lambda value: MKTXPConfigKeys.DEFAULT_MKTXP_SOCKET_TIMEOUT, | ||||
|                 MKTXPConfigKeys.MKTXP_INITIAL_DELAY: lambda value: MKTXPConfigKeys.DEFAULT_MKTXP_INITIAL_DELAY, | ||||
|                 MKTXPConfigKeys.MKTXP_MAX_DELAY: lambda value: MKTXPConfigKeys.DEFAULT_MKTXP_MAX_DELAY, | ||||
|                 MKTXPConfigKeys.MKTXP_INC_DIV: lambda value: MKTXPConfigKeys.DEFAULT_MKTXP_INC_DIV | ||||
|                 }[key](value) | ||||
|  | ||||
|  | ||||
| # Simplest possible Singleton impl | ||||
|   | ||||
| @@ -11,18 +11,19 @@ | ||||
| ## GNU General Public License for more details. | ||||
|  | ||||
|  | ||||
| [SampleRouter] | ||||
|     enabled = False | ||||
|  | ||||
|     hostname = localhost | ||||
|     port = 8728 | ||||
|  | ||||
|     username = user | ||||
| [Sample-Router] | ||||
|     enabled = False         # turns metrics collection for this RouterOS device on / off | ||||
|      | ||||
|     hostname = localhost    # RouterOS IP address | ||||
|     port = 8728             # RouterOS IP Port | ||||
|      | ||||
|     username = username     # RouterOS user, needs to have 'read' and 'api' permissions | ||||
|     password = password | ||||
|  | ||||
|     use_ssl = False | ||||
|     ssl_certificate = False | ||||
|  | ||||
|      | ||||
|     use_ssl = False                 # enables connection via API-SSL servis | ||||
|     no_ssl_certificate = False      # enables API_SSL connect without router SSL certificate | ||||
|     ssl_certificate_verify = False  # turns SSL certificate verification on / off | ||||
|      | ||||
|     dhcp = True | ||||
|     dhcp_lease = True | ||||
|     pool = True | ||||
| @@ -32,4 +33,6 @@ | ||||
|     wireless = True | ||||
|     wireless_clients = True | ||||
|     capsman = True | ||||
|     capsman_clients = True | ||||
|     capsman_clients = True | ||||
|  | ||||
|      | ||||
| @@ -72,8 +72,10 @@ class MKTXPDispatcher: | ||||
|  | ||||
|  | ||||
|     def show_entries(self, args): | ||||
|         if args['configpath']: | ||||
|             print(f'MKTX config path: {config_handler.usr_conf_data_path}') | ||||
|         if args['config']: | ||||
|             print(f'MKTXP data config: {config_handler.usr_conf_data_path}') | ||||
|             print(f'MKTXP internal config: {config_handler.mktxp_conf_path}') | ||||
|  | ||||
|         else: | ||||
|             for entryname in config_handler.registered_entries(): | ||||
|                 if args['entry_name'] and entryname != args['entry_name']: | ||||
|   | ||||
| @@ -101,8 +101,8 @@ class MKTXPOptionsParser: | ||||
|                                         description = 'Displays MKTXP config router entries', | ||||
|                                         formatter_class=MKTXPHelpFormatter) | ||||
|         self._add_entry_name(show_parser, registered_only = True, required = False, help = "Config entry name") | ||||
|         show_parser.add_argument('-cp', '--configpath', dest='configpath', | ||||
|                                         help = "Shows MKTXP config file path", | ||||
|         show_parser.add_argument('-cfg', '--config', dest='config', | ||||
|                                         help = "Shows MKTXP config files paths", | ||||
|                                         action = 'store_true') | ||||
|  | ||||
|         # Add command | ||||
| @@ -137,7 +137,7 @@ class MKTXPOptionsParser: | ||||
|         optional_args_group.add_argument('-ssl', '--use-ssl', dest='use_ssl', | ||||
|                 help = "Connect via RouterOS api-ssl service", | ||||
|                 action = 'store_true') | ||||
|         optional_args_group.add_argument('-ssl-cert', '--use-ssl-certificate', dest='ssl_certificate', | ||||
|         optional_args_group.add_argument('-no-ssl-cert', '--no-ssl-certificate', dest='no_ssl_certificate', | ||||
|                 help = "Connect with configured RouterOS SSL ceritficate", | ||||
|                 action = 'store_true') | ||||
|  | ||||
|   | ||||
| @@ -25,7 +25,7 @@ class CapsmanCollector(BaseCollector): | ||||
|         remote_caps_labels = ['identity', 'version', 'base_mac', 'board', 'base_mac'] | ||||
|         remote_caps_records = router_metric.capsman_remote_caps_records(remote_caps_labels) | ||||
|         if not remote_caps_records: | ||||
|             return | ||||
|             return range(0) | ||||
|  | ||||
|         remote_caps_metrics = BaseCollector.info_collector('capsman_remote_caps', 'CAPsMAN remote caps', remote_caps_records, remote_caps_labels) | ||||
|         yield remote_caps_metrics | ||||
| @@ -33,7 +33,7 @@ class CapsmanCollector(BaseCollector): | ||||
|         registration_labels = ['interface', 'ssid', 'mac_address', 'tx_rate', 'rx_rate', 'rx_signal', 'uptime', 'bytes'] | ||||
|         registration_records = router_metric.capsman_registration_table_records(registration_labels) | ||||
|         if not registration_records: | ||||
|             return | ||||
|             return range(0) | ||||
|  | ||||
|         # calculate number of registrations per interface | ||||
|         registration_per_interface = {} | ||||
| @@ -53,40 +53,28 @@ class CapsmanCollector(BaseCollector): | ||||
|             dhcp_lease_labels = ['mac_address', 'host_name', 'comment'] | ||||
|             dhcp_lease_records = router_metric.dhcp_lease_records(dhcp_lease_labels) | ||||
|             for registration_record in registration_records: | ||||
|                 dhcp_lease_record = next((dhcp_lease_record for dhcp_lease_record in dhcp_lease_records if dhcp_lease_record['mac_address']==registration_record['mac_address'])) | ||||
|                 if dhcp_lease_record: | ||||
|                     registration_record['name'] = dhcp_lease_record.get('comment', dhcp_lease_record.get('host_name', dhcp_lease_record.get('mac_address'))) | ||||
|                 else: | ||||
|                     registration_record['name'] = registration_record['mac_address']             | ||||
|                 try: | ||||
|                     dhcp_lease_record = next((dhcp_lease_record for dhcp_lease_record in dhcp_lease_records if dhcp_lease_record['mac_address']==registration_record['mac_address'])) | ||||
|                     registration_record['name'] = dhcp_lease_record.get('comment', dhcp_lease_record.get('host_name', dhcp_lease_record.get('mac_address')))                    | ||||
|                 except StopIteration: | ||||
|                     registration_record['name'] = f"{registration_record['mac_address']}: No DHCP registration"              | ||||
|                  | ||||
|                 # split out tx/rx bytes | ||||
|                 registration_record['tx_bytes'] = registration_record['bytes'].split(',')[0] | ||||
|                 registration_record['rx_bytes'] = registration_record['bytes'].split(',')[1] | ||||
|                 del registration_record['bytes'] | ||||
|  | ||||
|             tx_byte_metrics = BaseCollector.counter_collector('capsman_traffic_tx_bytes', 'Number of sent packet bytes', registration_records, 'tx_bytes', ['name']) | ||||
|             tx_byte_metrics = BaseCollector.counter_collector('capsman_clients_tx_bytes', 'Number of sent packet bytes', registration_records, 'tx_bytes', ['name']) | ||||
|             yield tx_byte_metrics | ||||
|  | ||||
|             rx_byte_metrics = BaseCollector.counter_collector('capsman_traffic_rx_bytes', 'Number of received packet bytes', registration_records, 'rx_bytes', ['name']) | ||||
|             rx_byte_metrics = BaseCollector.counter_collector('capsman_clients_rx_bytes', 'Number of received packet bytes', registration_records, 'rx_bytes', ['name']) | ||||
|             yield rx_byte_metrics | ||||
|  | ||||
|             signal_strength_metrics = BaseCollector.gauge_collector('capsman_registered_signal_strength', 'Registered devices signal strength', registration_records, 'rx_signal', ['name']) | ||||
|             signal_strength_metrics = BaseCollector.gauge_collector('capsman_clients_signal_strength', 'Client devices signal strength', registration_records, 'rx_signal', ['name']) | ||||
|             yield signal_strength_metrics | ||||
|  | ||||
|             registration_metrics = BaseCollector.info_collector('capsman_registered_devices', 'Registered devices info',  | ||||
|             registration_metrics = BaseCollector.info_collector('capsman_clients_devices', 'Registered client devices info',  | ||||
|                                     registration_records, ['name', 'rx_signal', 'ssid', 'tx_rate', 'rx_rate', 'interface', 'mac_address', 'uptime']) | ||||
|             yield registration_metrics | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -24,7 +24,7 @@ class DHCPCollector(BaseCollector): | ||||
|         dhcp_lease_labels = ['active_address', 'mac_address', 'host_name', 'comment', 'server', 'expires_after'] | ||||
|         dhcp_lease_records = router_metric.dhcp_lease_records(dhcp_lease_labels) | ||||
|         if not dhcp_lease_records: | ||||
|             return | ||||
|             return range(0) | ||||
|  | ||||
|         # calculate number of leases per DHCP server | ||||
|         dhcp_lease_servers = {} | ||||
|   | ||||
| @@ -23,7 +23,7 @@ class HealthCollector(BaseCollector): | ||||
|         health_labels = ['voltage', 'temperature'] | ||||
|         health_records = router_metric.health_records(health_labels) | ||||
|         if not health_records: | ||||
|             return | ||||
|             return range(0) | ||||
|  | ||||
|         voltage_metrics = BaseCollector.gauge_collector('system_routerboard_voltage', 'Supplied routerboard voltage', health_records, 'voltage') | ||||
|         yield voltage_metrics | ||||
|   | ||||
| @@ -23,7 +23,7 @@ class IdentityCollector(BaseCollector): | ||||
|         identity_labels = ['name'] | ||||
|         identity_records = router_metric.identity_records(identity_labels) | ||||
|         if not identity_records: | ||||
|             return | ||||
|             return range(0) | ||||
|  | ||||
|         identity_metrics = BaseCollector.info_collector('system_identity', 'System identity', identity_records, identity_labels) | ||||
|         yield identity_metrics | ||||
|   | ||||
| @@ -23,7 +23,11 @@ class InterfaceCollector(BaseCollector): | ||||
|         interface_traffic_labels = ['name', 'comment', 'rx_byte', 'tx_byte', 'rx_packet', 'tx_packet', 'rx_error', 'tx_error', 'rx_drop', 'tx_drop'] | ||||
|         interface_traffic_records = router_metric.interface_traffic_records(interface_traffic_labels) | ||||
|         if not interface_traffic_records: | ||||
|             return | ||||
|             return range(0) | ||||
|  | ||||
|         for interface_traffic_record in interface_traffic_records: | ||||
|             if interface_traffic_record.get('comment'): | ||||
|                 interface_traffic_record['name'] = f"{interface_traffic_record['name']} ({interface_traffic_record['comment']})" | ||||
|  | ||||
|         rx_byte_metric = BaseCollector.counter_collector('interface_rx_byte', 'Number of received bytes', interface_traffic_records, 'rx_byte', ['name']) | ||||
|         yield rx_byte_metric | ||||
|   | ||||
| @@ -21,10 +21,10 @@ class MonitorCollector(BaseCollector): | ||||
|     '''     | ||||
|     @staticmethod | ||||
|     def collect(router_metric): | ||||
|         monitor_labels = ['status', 'rate', 'full_duplex', 'name'] | ||||
|         monitor_records = router_metric.interface_monitor_records(monitor_labels) | ||||
|         monitor_labels = ('status', 'rate', 'full_duplex', 'name') | ||||
|         monitor_records = router_metric.interface_monitor_records(monitor_labels, include_comments = True) | ||||
|         if not monitor_records: | ||||
|             return | ||||
|             return range(0) | ||||
|  | ||||
|         # translate records to appropriate values | ||||
|         for monitor_record in monitor_records: | ||||
|   | ||||
| @@ -25,7 +25,7 @@ class PoolCollector(BaseCollector): | ||||
|         # initialize all pool counts, including those currently not used | ||||
|         pool_records = router_metric.pool_records(['name']) | ||||
|         if not pool_records: | ||||
|             return | ||||
|             return range(0) | ||||
|  | ||||
|         pool_used_labels = ['pool'] | ||||
|         pool_used_counts = {pool_record['name']: 0 for pool_record in pool_records} | ||||
|   | ||||
| @@ -28,7 +28,7 @@ class SystemResourceCollector(BaseCollector): | ||||
|                            'architecture_name', 'board_name'] | ||||
|         resource_records = router_metric.system_resource_records(resource_labels) | ||||
|         if not resource_records: | ||||
|             return | ||||
|             return range(0) | ||||
|  | ||||
|         # translate records to appropriate values | ||||
|         translated_fields = ['uptime']         | ||||
|   | ||||
| @@ -24,7 +24,7 @@ class RouteCollector(BaseCollector): | ||||
|         route_labels = ['connect', 'dynamic', 'static', 'bgp', 'ospf'] | ||||
|         route_records = router_metric.route_records(route_labels) | ||||
|         if not route_records: | ||||
|             return | ||||
|             return range(0) | ||||
|          | ||||
|         # compile total routes records | ||||
|         total_routes = len(route_records) | ||||
|   | ||||
| @@ -11,6 +11,7 @@ | ||||
| ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| ## GNU General Public License for more details. | ||||
|  | ||||
| import re | ||||
| from mktxp.collectors.base_collector import BaseCollector | ||||
| from mktxp.router_metric import RouterMetric | ||||
|  | ||||
| @@ -20,25 +21,76 @@ class WLANCollector(BaseCollector): | ||||
|     '''     | ||||
|     @staticmethod | ||||
|     def collect(router_metric): | ||||
|         monitor_labels = ['channel', 'noise_floor', 'overall_tx_ccq'] | ||||
|         monitor_labels = ['channel', 'noise_floor', 'overall_tx_ccq', 'registered_clients'] | ||||
|         monitor_records = router_metric.interface_monitor_records(monitor_labels, 'wireless') | ||||
|         if not monitor_records: | ||||
|             return | ||||
|             return range(0) | ||||
|  | ||||
|         # sanitize records for relevant labels | ||||
|         noise_floor_records = [monitor_record for monitor_record in monitor_records if monitor_record.get('noise_floor')] | ||||
|         tx_ccq_records = [monitor_record for monitor_record in monitor_records if monitor_record.get('overall_tx_ccq')] | ||||
|         registered_clients_records  = [monitor_record for monitor_record in monitor_records if monitor_record.get('registered_clients')] | ||||
|  | ||||
|         if noise_floor_records: | ||||
|             noise_floor_metrics = BaseCollector.gauge_collector('wlan_noise_floor', 'Noise floor threshold', noise_floor_records, 'noise_floor', ['channel']) | ||||
|             yield noise_floor_metrics | ||||
|  | ||||
|         if tx_ccq_records: | ||||
|             overall_tx_ccq_metrics = BaseCollector.gauge_collector('wlan_overall_tx_ccq', ' Client Connection Quality for transmitting', tx_ccq_records, 'overall_tx_ccq', ['channel']) | ||||
|             overall_tx_ccq_metrics = BaseCollector.gauge_collector('wlan_overall_tx_ccq', 'Client Connection Quality for transmitting', tx_ccq_records, 'overall_tx_ccq', ['channel']) | ||||
|             yield overall_tx_ccq_metrics | ||||
|  | ||||
|         if registered_clients_records: | ||||
|             registered_clients_metrics = BaseCollector.gauge_collector('wlan_registered_clients', 'Number of registered clients', registered_clients_records, 'registered_clients', ['channel']) | ||||
|             yield registered_clients_metrics | ||||
|  | ||||
|  | ||||
|         # the client info metrics | ||||
|         if router_metric.router_entry.wireless_clients: | ||||
|             # TBD | ||||
|             pass | ||||
|             registration_labels = ['interface', 'ssid', 'mac_address', 'tx_rate', 'rx_rate', 'uptime', 'bytes', 'signal_to_noise', 'tx_ccq', 'signal_strength'] | ||||
|             registration_records = router_metric.wireless_registration_table_records(registration_labels) | ||||
|             if not registration_records: | ||||
|                 return range(0) | ||||
|  | ||||
|             dhcp_lease_labels = ['mac_address', 'host_name', 'comment'] | ||||
|             dhcp_lease_records = router_metric.dhcp_lease_records(dhcp_lease_labels) | ||||
|    | ||||
|             for registration_record in registration_records: | ||||
|                 try: | ||||
|                     dhcp_lease_record = next((dhcp_lease_record for dhcp_lease_record in dhcp_lease_records if dhcp_lease_record['mac_address']==registration_record['mac_address'])) | ||||
|                     registration_record['name'] = dhcp_lease_record.get('comment', dhcp_lease_record.get('host_name', dhcp_lease_record.get('mac_address'))) | ||||
|                 except StopIteration: | ||||
|                     registration_record['name'] = registration_record['mac_address']             | ||||
|  | ||||
|                 # split out tx/rx bytes | ||||
|                 registration_record['tx_bytes'] = registration_record['bytes'].split(',')[0] | ||||
|                 registration_record['rx_bytes'] = registration_record['bytes'].split(',')[1] | ||||
|  | ||||
|                 # average signal strength | ||||
|                 registration_record['signal_strength'] = re.search(r'-\d+', registration_record['signal_strength']).group() | ||||
|  | ||||
|                 del registration_record['bytes'] | ||||
|  | ||||
|             tx_byte_metrics = BaseCollector.counter_collector('wlan_clients_tx_bytes', 'Number of sent packet bytes', registration_records, 'tx_bytes', ['name']) | ||||
|             yield tx_byte_metrics | ||||
|  | ||||
|             rx_byte_metrics = BaseCollector.counter_collector('wlan_clients_rx_bytes', 'Number of received packet bytes', registration_records, 'rx_bytes', ['name']) | ||||
|             yield rx_byte_metrics | ||||
|  | ||||
|             signal_strength_metrics = BaseCollector.gauge_collector('wlan_clients_signal_strength', 'Average strength of the client signal recevied by AP', registration_records, 'signal_strength', ['name']) | ||||
|             yield signal_strength_metrics | ||||
|  | ||||
|             signal_to_noise_metrics = BaseCollector.gauge_collector('wlan_clients_signal_to_noise', 'Client devices signal to noise ratio', registration_records, 'signal_to_noise', ['name']) | ||||
|             yield signal_to_noise_metrics | ||||
|  | ||||
|             tx_ccq_metrics = BaseCollector.gauge_collector('wlan_clients_tx_ccq', 'Client Connection Quality (CCQ) for transmit', registration_records, 'tx_ccq', ['name']) | ||||
|             yield tx_ccq_metrics | ||||
|  | ||||
|             registration_metrics = BaseCollector.info_collector('wlan_clients_devices', 'Client devices info',  | ||||
|                                     registration_records, ['name', 'rx_signal', 'ssid', 'tx_rate', 'rx_rate', 'interface', 'mac_address', 'uptime']) | ||||
|             yield registration_metrics | ||||
|  | ||||
|  | ||||
|             return range(0) | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -30,6 +30,11 @@ class CollectorsHandler: | ||||
|  | ||||
|     def collect(self): | ||||
|         for router_metric in self.metrics_handler.router_metrics: | ||||
|             if not router_metric.api_connection.is_connected(): | ||||
|                 # let's pick up on things in the next run | ||||
|                 router_metric.api_connection.connect() | ||||
|                 continue | ||||
|  | ||||
|             yield from IdentityCollector.collect(router_metric) | ||||
|             yield from SystemResourceCollector.collect(router_metric) | ||||
|             yield from HealthCollector.collect(router_metric) | ||||
| @@ -55,3 +60,4 @@ class CollectorsHandler: | ||||
|             if router_metric.router_entry.capsman: | ||||
|                 yield from CapsmanCollector.collect(router_metric) | ||||
|  | ||||
|         return range(0) | ||||
|   | ||||
| @@ -15,7 +15,7 @@ import ssl | ||||
| import socket | ||||
| from datetime import datetime | ||||
| from routeros_api import RouterOsApiPool | ||||
|  | ||||
| from mktxp.cli.config.config import config_handler | ||||
|  | ||||
| class RouterAPIConnectionError(Exception): | ||||
|     pass | ||||
| @@ -26,10 +26,11 @@ class RouterAPIConnection: | ||||
|     '''             | ||||
|     def __init__(self, router_name, router_entry): | ||||
|         self.router_name = router_name | ||||
|         self.router_entry  = router_entry | ||||
|         self.router_entry = router_entry | ||||
|         self.last_failure_timestamp = self.successive_failure_count = 0 | ||||
|          | ||||
|         ctx = None | ||||
|         if self.router_entry.use_ssl and not self.router_entry.ssl_certificate: | ||||
|         if self.router_entry.use_ssl and self.router_entry.no_ssl_certificate: | ||||
|             ctx = ssl.create_default_context() | ||||
|             ctx.set_ciphers('ADH:@SECLEVEL=0')        | ||||
|  | ||||
| @@ -40,9 +41,10 @@ class RouterAPIConnection: | ||||
|                 port = self.router_entry.port, | ||||
|                 plaintext_login = True, | ||||
|                 use_ssl = self.router_entry.use_ssl, | ||||
|                 ssl_verify = self.router_entry.ssl_certificate_verify, | ||||
|                 ssl_context = ctx) | ||||
|          | ||||
|         self.connection.socket_timeout = 2 | ||||
|         self.connection.socket_timeout = config_handler._entry().socket_timeout | ||||
|         self.api = None | ||||
|  | ||||
|     def is_connected(self): | ||||
| @@ -51,24 +53,63 @@ class RouterAPIConnection: | ||||
|         try: | ||||
|             self.api.get_resource('/system/identity').get() | ||||
|             return True | ||||
|         except (socket.error, socket.timeout, Exception) as ex: | ||||
|             print(f'Connection to router {self.router_name}@{self.router_entry.hostname} has been lost: {ex}') | ||||
|             self.api = None | ||||
|         except (socket.error, socket.timeout, Exception) as exc: | ||||
|             self._set_connect_state(success = False, exc = exc) | ||||
|             return False | ||||
|  | ||||
|     def connect(self): | ||||
|         if self.is_connected(): | ||||
|         connect_time = datetime.now() | ||||
|         if self.is_connected() or self._in_connect_timeout(connect_time.timestamp()): | ||||
|             return | ||||
|         current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") | ||||
|         try: | ||||
|             print(f'Connecting to router {self.router_name}@{self.router_entry.hostname}') | ||||
|             self.api = self.connection.get_api() | ||||
|             print(f'{current_time} Connection to router {self.router_name}@{self.router_entry.hostname} has been established') | ||||
|         except (socket.error, socket.timeout, Exception) as ex: | ||||
|             print(f'{current_time} Connection to router {self.router_name}@{self.router_entry.hostname} has failed: {ex}') | ||||
|             raise  | ||||
|             self._set_connect_state(success = True, connect_time = connect_time) | ||||
|         except (socket.error, socket.timeout, Exception) as exc: | ||||
|             self._set_connect_state(success = False, connect_time = connect_time, exc = exc) | ||||
|             #raise RouterAPIConnectionError | ||||
|  | ||||
|     def router_api(self): | ||||
|         if not self.is_connected(): | ||||
|             self.connect() | ||||
|         return self.api | ||||
|  | ||||
|     def _in_connect_timeout(self, connect_timestamp, quiet = True): | ||||
|         connect_delay = self._connect_delay() | ||||
|         if (connect_timestamp - self.last_failure_timestamp) < connect_delay: | ||||
|             if not quiet:  | ||||
|                 print(f'{self.router_name}@{self.router_entry.hostname}: in connect timeout, {int(connect_delay - (connect_timestamp - self.last_failure_timestamp))}secs remaining') | ||||
|                 print(f'Successive failure count: {self.successive_failure_count}') | ||||
|             return True | ||||
|         if not quiet:  | ||||
|             print(f'{self.router_name}@{self.router_entry.hostname}: OK to connect') | ||||
|             if self.last_failure_timestamp > 0: | ||||
|                 print(f'Seconds since last failure: {connect_timestamp - self.last_failure_timestamp}') | ||||
|                 print(f'Prior successive failure count: {self.successive_failure_count}') | ||||
|         return False | ||||
|  | ||||
|     def _connect_delay(self): | ||||
|         mktxp_entry = config_handler._entry() | ||||
|         connect_delay = (1 + self.successive_failure_count / mktxp_entry.delay_inc_div) * mktxp_entry.initial_delay_on_failure | ||||
|         return connect_delay if connect_delay < mktxp_entry.max_delay_on_failure else mktxp_entry.max_delay_on_failure | ||||
|  | ||||
|  | ||||
|     def _set_connect_state(self, success = False, connect_time = datetime.now(), exc = None): | ||||
|         if success: | ||||
|             self.last_failure_timestamp = 0 | ||||
|             self.successive_failure_count = 0 | ||||
|             print(f'{connect_time.strftime("%Y-%m-%d %H:%M:%S")} Connection to router {self.router_name}@{self.router_entry.hostname} has been established') | ||||
|         else: | ||||
|             self.api = None | ||||
|             self.successive_failure_count += 1 | ||||
|             self.last_failure_timestamp = connect_time.timestamp()  | ||||
|             print(f'{connect_time.strftime("%Y-%m-%d %H:%M:%S")} Connection to router {self.router_name}@{self.router_entry.hostname} has failed: {exc}') | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -76,13 +76,20 @@ class RouterMetric: | ||||
|             print(f'Error getting interface traffic info from router{self.router_name}@{self.router_entry.hostname}: {exc}') | ||||
|             return None | ||||
|  | ||||
|     def interface_monitor_records(self, interface_monitor_labels = [], kind = 'ethernet'): | ||||
|     def interface_monitor_records(self, interface_monitor_labels = [], kind = 'ethernet', include_comments = False): | ||||
|         try: | ||||
|             interfaces = self.api_connection.router_api().get_resource(f'/interface/{kind}').get() | ||||
|             interface_names = [interface['name'] for interface in interfaces] | ||||
|             interface_names = [(interface['name'], interface.get('comment')) for interface in interfaces] | ||||
|  | ||||
|             interface_monitor = lambda int_num : self.api_connection.router_api().get_resource(f'/interface/{kind}').call('monitor', {'once':'', 'numbers':f'{int_num}'}) | ||||
|             interface_monitor_records = [interface_monitor(int_num)[0] for int_num in range(len(interface_names))] | ||||
|  | ||||
|             if include_comments: | ||||
|                 # combine interfaces names with comments | ||||
|                 for interface_monitor_record in interface_monitor_records: | ||||
|                     for interface_name in interface_names: | ||||
|                         if interface_name[1] and interface_name[0] == interface_monitor_record['name']: | ||||
|                             interface_monitor_record['name'] = f"{interface_monitor_record['name']} ({interface_name[1]})" | ||||
|             return self._trimmed_records(interface_monitor_records, interface_monitor_labels) | ||||
|         except Exception as exc: | ||||
|             print(f'Error getting {kind} interface monitor info from router{self.router_name}@{self.router_entry.hostname}: {exc}') | ||||
| @@ -109,7 +116,7 @@ class RouterMetric: | ||||
|             route_records = self.api_connection.router_api().get_resource('/ip/route').get(active='yes') | ||||
|             return self._trimmed_records(route_records, route_labels) | ||||
|         except Exception as exc: | ||||
|             print(f'Error getting pool active routes info from router{self.router_name}@{self.router_entry.hostname}: {exc}') | ||||
|             print(f'Error getting routes info from router{self.router_name}@{self.router_entry.hostname}: {exc}') | ||||
|             return None | ||||
|              | ||||
|     def wireless_registration_table_records(self, registration_table_labels = []): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user