DS refactor, fixes/optimizations

This commit is contained in:
Arseniy Kuznetsov
2021-02-06 15:12:35 +01:00
parent 52bfe9c16d
commit f405d58410
44 changed files with 998 additions and 559 deletions

View File

@@ -11,24 +11,27 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details. ## GNU General Public License for more details.
from http.server import HTTPServer from http.server import HTTPServer
from datetime import datetime from datetime import datetime
from prometheus_client.core import REGISTRY from prometheus_client.core import REGISTRY
from prometheus_client import MetricsHandler, start_http_server from prometheus_client import MetricsHandler, start_http_server
from mktxp.cli.config.config import config_handler from mktxp.cli.config.config import config_handler
from mktxp.collectors_handler import CollectorsHandler from mktxp.collectors_handler import CollectorsHandler
from mktxp.metrics_handler import RouterMetricsHandler from mktxp.router_entries_handler import RouterEntriesHandler
from mktxp.cli.output.capsman_out import CapsmanOutput from mktxp.cli.output.capsman_out import CapsmanOutput
from mktxp.cli.output.wifi_out import WirelessOutput from mktxp.cli.output.wifi_out import WirelessOutput
from mktxp.cli.output.dhcp_out import DHCPOutput
class MKTXPProcessor: class MKTXPProcessor:
''' Base Export Processing ''' Base Export Processing
''' '''
@staticmethod @staticmethod
def start(): def start():
router_metrics_handler = RouterMetricsHandler() router_entries_handler = RouterEntriesHandler()
REGISTRY.register(CollectorsHandler(router_metrics_handler)) REGISTRY.register(CollectorsHandler(router_entries_handler))
MKTXPProcessor.run(port=config_handler._entry().port) MKTXPProcessor.run(port=config_handler._entry().port)
@staticmethod @staticmethod
@@ -45,13 +48,18 @@ class MKTXPCLIProcessor:
''' '''
@staticmethod @staticmethod
def capsman_clients(entry_name): def capsman_clients(entry_name):
router_metric = RouterMetricsHandler.router_metric(entry_name) router_entry = RouterEntriesHandler.router_entry(entry_name)
if router_metric: if router_entry:
CapsmanOutput.clients_summary(router_metric) CapsmanOutput.clients_summary(router_entry)
@staticmethod @staticmethod
def wifi_clients(entry_name): def wifi_clients(entry_name):
router_metric = RouterMetricsHandler.router_metric(entry_name) router_entry = RouterEntriesHandler.router_entry(entry_name)
if router_metric: if router_entry:
WirelessOutput.clients_summary(router_metric) WirelessOutput.clients_summary(router_entry)
@staticmethod
def dhcp_clients(entry_name):
router_entry = RouterEntriesHandler.router_entry(entry_name)
if router_entry:
DHCPOutput.clients_summary(router_entry)

View File

@@ -12,9 +12,10 @@
[MKTXP] [MKTXP]
port = 49090 port = 49090 # default metrics HTTP server port
socket_timeout = 2 bandwidth_test_interval = 420 # Interval between periodic bandwidth tests
initial_delay_on_failure = 120
max_delay_on_failure = 900 socket_timeout = 2 # Socket connection timeout
delay_inc_div = 5 initial_delay_on_failure = 120 # Delay untill next connection attempt to a RouterOS Device
bandwidth_test_interval = 420 max_delay_on_failure = 900 # Max delay untill next connection attempt to a RouterOS Device
delay_inc_div = 5 # Delay increment factor

View File

@@ -23,263 +23,263 @@ from mktxp.utils.utils import FSHelper
''' '''
class MKTXPConfigKeys: class MKTXPConfigKeys:
''' MKTXP config file keys ''' MKTXP config file keys
''' '''
# Section Keys # Section Keys
ENABLED_KEY = 'enabled' ENABLED_KEY = 'enabled'
HOST_KEY = 'hostname' HOST_KEY = 'hostname'
PORT_KEY = 'port' PORT_KEY = 'port'
USER_KEY = 'username' USER_KEY = 'username'
PASSWD_KEY = 'password' PASSWD_KEY = 'password'
SSL_KEY = 'use_ssl' SSL_KEY = 'use_ssl'
NO_SSL_CERTIFICATE = 'no_ssl_certificate' NO_SSL_CERTIFICATE = 'no_ssl_certificate'
SSL_CERTIFICATE_VERIFY = 'ssl_certificate_verify' SSL_CERTIFICATE_VERIFY = 'ssl_certificate_verify'
FE_DHCP_KEY = 'dhcp' FE_DHCP_KEY = 'dhcp'
FE_DHCP_LEASE_KEY = 'dhcp_lease' FE_DHCP_LEASE_KEY = 'dhcp_lease'
FE_DHCP_POOL_KEY = 'pool' FE_DHCP_POOL_KEY = 'pool'
FE_INTERFACE_KEY = 'interface' FE_INTERFACE_KEY = 'interface'
FE_FIREWALL_KEY = 'firewall' FE_FIREWALL_KEY = 'firewall'
FE_MONITOR_KEY = 'monitor' FE_MONITOR_KEY = 'monitor'
FE_ROUTE_KEY = 'route' FE_ROUTE_KEY = 'route'
FE_WIRELESS_KEY = 'wireless' FE_WIRELESS_KEY = 'wireless'
FE_WIRELESS_CLIENTS_KEY = 'wireless_clients' FE_WIRELESS_CLIENTS_KEY = 'wireless_clients'
FE_CAPSMAN_KEY = 'capsman' FE_CAPSMAN_KEY = 'capsman'
FE_CAPSMAN_CLIENTS_KEY = 'capsman_clients' FE_CAPSMAN_CLIENTS_KEY = 'capsman_clients'
MKTXP_SOCKET_TIMEOUT = 'socket_timeout' MKTXP_SOCKET_TIMEOUT = 'socket_timeout'
MKTXP_SOCKET_TIMEOUT = 'socket_timeout' MKTXP_SOCKET_TIMEOUT = 'socket_timeout'
MKTXP_INITIAL_DELAY = 'initial_delay_on_failure' MKTXP_INITIAL_DELAY = 'initial_delay_on_failure'
MKTXP_MAX_DELAY = 'max_delay_on_failure' MKTXP_MAX_DELAY = 'max_delay_on_failure'
MKTXP_INC_DIV = 'delay_inc_div' MKTXP_INC_DIV = 'delay_inc_div'
MKTXP_BANDWIDTH_TEST_INTERVAL = 'bandwidth_test_interval' MKTXP_BANDWIDTH_TEST_INTERVAL = 'bandwidth_test_interval'
# UnRegistered entries placeholder # UnRegistered entries placeholder
NO_ENTRIES_REGISTERED = 'NoEntriesRegistered' NO_ENTRIES_REGISTERED = 'NoEntriesRegistered'
MKTXP_USE_COMMENTS_OVER_NAMES = 'use_comments_over_names' MKTXP_USE_COMMENTS_OVER_NAMES = 'use_comments_over_names'
# Base router id labels # Base router id labels
ROUTERBOARD_NAME = 'routerboard_name' ROUTERBOARD_NAME = 'routerboard_name'
ROUTERBOARD_ADDRESS = 'routerboard_address' ROUTERBOARD_ADDRESS = 'routerboard_address'
# Default values # Default values
DEFAULT_API_PORT = 8728 DEFAULT_API_PORT = 8728
DEFAULT_API_SSL_PORT = 8729 DEFAULT_API_SSL_PORT = 8729
DEFAULT_MKTXP_PORT = 49090 DEFAULT_MKTXP_PORT = 49090
DEFAULT_MKTXP_SOCKET_TIMEOUT = 2 DEFAULT_MKTXP_SOCKET_TIMEOUT = 2
DEFAULT_MKTXP_INITIAL_DELAY = 120 DEFAULT_MKTXP_INITIAL_DELAY = 120
DEFAULT_MKTXP_MAX_DELAY = 900 DEFAULT_MKTXP_MAX_DELAY = 900
DEFAULT_MKTXP_INC_DIV = 5 DEFAULT_MKTXP_INC_DIV = 5
DEFAULT_MKTXP_BANDWIDTH_TEST_INTERVAL = 420 DEFAULT_MKTXP_BANDWIDTH_TEST_INTERVAL = 420
BOOLEAN_KEYS = (ENABLED_KEY, SSL_KEY, NO_SSL_CERTIFICATE, SSL_CERTIFICATE_VERIFY, 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_FIREWALL_KEY, FE_DHCP_KEY, FE_DHCP_LEASE_KEY, FE_DHCP_POOL_KEY, FE_INTERFACE_KEY, FE_FIREWALL_KEY,
FE_MONITOR_KEY, FE_ROUTE_KEY, MKTXP_USE_COMMENTS_OVER_NAMES, FE_MONITOR_KEY, FE_ROUTE_KEY, MKTXP_USE_COMMENTS_OVER_NAMES,
FE_WIRELESS_KEY, FE_WIRELESS_CLIENTS_KEY, FE_CAPSMAN_KEY, FE_CAPSMAN_CLIENTS_KEY) FE_WIRELESS_KEY, FE_WIRELESS_CLIENTS_KEY, FE_CAPSMAN_KEY, FE_CAPSMAN_CLIENTS_KEY)
STR_KEYS = (HOST_KEY, USER_KEY, PASSWD_KEY) STR_KEYS = (HOST_KEY, USER_KEY, PASSWD_KEY)
MKTXP_INT_KEYS = (PORT_KEY, MKTXP_SOCKET_TIMEOUT, MKTXP_INITIAL_DELAY, MKTXP_MAX_DELAY, MKTXP_INC_DIV, MKTXP_BANDWIDTH_TEST_INTERVAL) MKTXP_INT_KEYS = (PORT_KEY, MKTXP_SOCKET_TIMEOUT, MKTXP_INITIAL_DELAY, MKTXP_MAX_DELAY, MKTXP_INC_DIV, MKTXP_BANDWIDTH_TEST_INTERVAL)
# MKTXP config entry nane # MKTXP config entry nane
MKTXP_CONFIG_ENTRY_NAME = 'MKTXP' MKTXP_CONFIG_ENTRY_NAME = 'MKTXP'
class ConfigEntry: class ConfigEntry:
MKTXPEntry = namedtuple('MKTXPEntry', [MKTXPConfigKeys.ENABLED_KEY, MKTXPConfigKeys.HOST_KEY, MKTXPConfigKeys.PORT_KEY, MKTXPEntry = namedtuple('MKTXPEntry', [MKTXPConfigKeys.ENABLED_KEY, MKTXPConfigKeys.HOST_KEY, MKTXPConfigKeys.PORT_KEY,
MKTXPConfigKeys.USER_KEY, MKTXPConfigKeys.PASSWD_KEY, MKTXPConfigKeys.USER_KEY, MKTXPConfigKeys.PASSWD_KEY,
MKTXPConfigKeys.SSL_KEY, MKTXPConfigKeys.NO_SSL_CERTIFICATE, MKTXPConfigKeys.SSL_CERTIFICATE_VERIFY, 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_FIREWALL_KEY, MKTXPConfigKeys.FE_DHCP_KEY, MKTXPConfigKeys.FE_DHCP_LEASE_KEY, MKTXPConfigKeys.FE_DHCP_POOL_KEY, MKTXPConfigKeys.FE_INTERFACE_KEY, MKTXPConfigKeys.FE_FIREWALL_KEY,
MKTXPConfigKeys.FE_MONITOR_KEY, MKTXPConfigKeys.FE_ROUTE_KEY, MKTXPConfigKeys.FE_WIRELESS_KEY, MKTXPConfigKeys.FE_WIRELESS_CLIENTS_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, MKTXPConfigKeys.MKTXP_USE_COMMENTS_OVER_NAMES MKTXPConfigKeys.FE_CAPSMAN_KEY, MKTXPConfigKeys.FE_CAPSMAN_CLIENTS_KEY, MKTXPConfigKeys.MKTXP_USE_COMMENTS_OVER_NAMES
]) ])
_MKTXPEntry = namedtuple('_MKTXPEntry', [MKTXPConfigKeys.PORT_KEY, MKTXPConfigKeys.MKTXP_SOCKET_TIMEOUT, _MKTXPEntry = namedtuple('_MKTXPEntry', [MKTXPConfigKeys.PORT_KEY, MKTXPConfigKeys.MKTXP_SOCKET_TIMEOUT,
MKTXPConfigKeys.MKTXP_INITIAL_DELAY, MKTXPConfigKeys.MKTXP_MAX_DELAY, MKTXPConfigKeys.MKTXP_INITIAL_DELAY, MKTXPConfigKeys.MKTXP_MAX_DELAY,
MKTXPConfigKeys.MKTXP_INC_DIV, MKTXPConfigKeys.MKTXP_BANDWIDTH_TEST_INTERVAL]) MKTXPConfigKeys.MKTXP_INC_DIV, MKTXPConfigKeys.MKTXP_BANDWIDTH_TEST_INTERVAL])
class OSConfig(metaclass = ABCMeta): class OSConfig(metaclass = ABCMeta):
''' OS-related config ''' OS-related config
''' '''
@staticmethod @staticmethod
def os_config(quiet = False): def os_config(quiet = False):
''' Factory method ''' Factory method
''' '''
if sys.platform == 'linux': if sys.platform == 'linux':
return LinuxConfig() return LinuxConfig()
elif sys.platform == 'darwin': elif sys.platform == 'darwin':
return OSXConfig() return OSXConfig()
else: else:
if not quiet: if not quiet:
print(f'Non-supported platform: {sys.platform}') print(f'Non-supported platform: {sys.platform}')
return None return None
@property @property
@abstractmethod @abstractmethod
def mktxp_user_dir_path(self): def mktxp_user_dir_path(self):
pass pass
class OSXConfig(OSConfig): class OSXConfig(OSConfig):
''' OSX-related config ''' OSX-related config
''' '''
@property @property
def mktxp_user_dir_path(self): def mktxp_user_dir_path(self):
return FSHelper.full_path('~/mktxp') return FSHelper.full_path('~/mktxp')
class LinuxConfig(OSConfig): class LinuxConfig(OSConfig):
''' Linux-related config ''' Linux-related config
''' '''
@property @property
def mktxp_user_dir_path(self): def mktxp_user_dir_path(self):
return FSHelper.full_path('~/mktxp') return FSHelper.full_path('~/mktxp')
#return FSHelper.full_path('/etc/mktxp') #return FSHelper.full_path('/etc/mktxp')
class MKTXPConfigHandler: class MKTXPConfigHandler:
def __init__(self): def __init__(self):
self.os_config = OSConfig.os_config() self.os_config = OSConfig.os_config()
if not self.os_config: if not self.os_config:
sys.exit(1) sys.exit(1)
# mktxp user config folder # mktxp user config folder
if not os.path.exists(self.os_config.mktxp_user_dir_path): if not os.path.exists(self.os_config.mktxp_user_dir_path):
os.makedirs(self.os_config.mktxp_user_dir_path) os.makedirs(self.os_config.mktxp_user_dir_path)
# if needed, stage the user config data # if needed, stage the user config data
self.usr_conf_data_path = os.path.join(self.os_config.mktxp_user_dir_path, 'mktxp.conf') self.usr_conf_data_path = os.path.join(self.os_config.mktxp_user_dir_path, 'mktxp.conf')
self.mktxp_conf_path = os.path.join(self.os_config.mktxp_user_dir_path, '_mktxp.conf') self.mktxp_conf_path = os.path.join(self.os_config.mktxp_user_dir_path, '_mktxp.conf')
self._create_os_path(self.usr_conf_data_path, 'mktxp/cli/config/mktxp.conf') self._create_os_path(self.usr_conf_data_path, 'mktxp/cli/config/mktxp.conf')
self._create_os_path(self.mktxp_conf_path, 'mktxp/cli/config/_mktxp.conf') self._create_os_path(self.mktxp_conf_path, 'mktxp/cli/config/_mktxp.conf')
self.re_compiled = {} self.re_compiled = {}
self._read_from_disk() self._read_from_disk()
def _read_from_disk(self): def _read_from_disk(self):
''' (Force-)Read conf data from disk ''' (Force-)Read conf data from disk
''' '''
self.config = ConfigObj(self.usr_conf_data_path) self.config = ConfigObj(self.usr_conf_data_path)
self._config = ConfigObj(self.mktxp_conf_path) self._config = ConfigObj(self.mktxp_conf_path)
def _create_os_path(self, os_path, resource_path): def _create_os_path(self, os_path, resource_path):
if not os.path.exists(os_path): if not os.path.exists(os_path):
# stage from the conf templates # stage from the conf templates
lookup_path = resource_filename(Requirement.parse("mktxp"), resource_path) lookup_path = resource_filename(Requirement.parse("mktxp"), resource_path)
shutil.copy(lookup_path, os_path) shutil.copy(lookup_path, os_path)
# MKTXP entries # MKTXP entries
############## ##############
def register_entry(self, entry_name, entry_args, quiet = False): def register_entry(self, entry_name, entry_args, quiet = False):
''' Registers MKTXP conf entry ''' Registers MKTXP conf entry
''' '''
if entry_name in self.registered_entries(): if entry_name in self.registered_entries():
if not quiet: if not quiet:
print('"{0}": entry name already registered'.format(entry_name)) print('"{0}": entry name already registered'.format(entry_name))
return False return False
else: else:
self.config[entry_name] = entry_args self.config[entry_name] = entry_args
self.config.write() self.config.write()
if not quiet: if not quiet:
print('Entry registered: {0}'.format(entry_name)) print('Entry registered: {0}'.format(entry_name))
return True return True
def unregister_entry(self, entry_name, quiet = False): def unregister_entry(self, entry_name, quiet = False):
''' Un-registers MKTXP conf entry ''' Un-registers MKTXP conf entry
''' '''
if self.config[entry_name]: if self.config[entry_name]:
del(self.config[entry_name]) del(self.config[entry_name])
self.config.write() self.config.write()
if not quiet: if not quiet:
print('Unregistered entry: {}'.format(entry_name)) print('Unregistered entry: {}'.format(entry_name))
return True return True
else: else:
if not quiet: if not quiet:
print('Entry is not registered: {}'.format(entry_name)) print('Entry is not registered: {}'.format(entry_name))
return False return False
def registered_entries(self): def registered_entries(self):
''' All MKTXP registered entries ''' All MKTXP registered entries
''' '''
registered_entries = [entry_name for entry_name in self.config.keys()] registered_entries = [entry_name for entry_name in self.config.keys()]
if not registered_entries: if not registered_entries:
registered_entries = [MKTXPConfigKeys.NO_ENTRIES_REGISTERED] registered_entries = [MKTXPConfigKeys.NO_ENTRIES_REGISTERED]
return registered_entries return registered_entries
def entry(self, entry_name): def entry(self, entry_name):
''' Given an entry name, reads and returns the entry info ''' 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) return ConfigEntry.MKTXPEntry(**entry_reader)
def _entry(self): def _entry(self):
''' MKTXP internal config entry ''' MKTXP internal config entry
''' '''
_entry_reader = self._entry_reader() _entry_reader = self._entry_reader()
return ConfigEntry._MKTXPEntry(**_entry_reader) return ConfigEntry._MKTXPEntry(**_entry_reader)
# Helpers # Helpers
def entry_reader(self, entry_name): def entry_reader(self, entry_name):
entry_reader = {} entry_reader = {}
write_needed = False write_needed = False
for key in MKTXPConfigKeys.BOOLEAN_KEYS: for key in MKTXPConfigKeys.BOOLEAN_KEYS:
if self.config[entry_name].get(key): if self.config[entry_name].get(key):
entry_reader[key] = self.config[entry_name].as_bool(key) entry_reader[key] = self.config[entry_name].as_bool(key)
else: else:
entry_reader[key] = False entry_reader[key] = False
write_needed = True # read from disk next time write_needed = True # read from disk next time
for key in MKTXPConfigKeys.STR_KEYS: for key in MKTXPConfigKeys.STR_KEYS:
entry_reader[key] = self.config[entry_name][key] entry_reader[key] = self.config[entry_name][key]
# port # port
if self.config[entry_name].get(MKTXPConfigKeys.PORT_KEY): if self.config[entry_name].get(MKTXPConfigKeys.PORT_KEY):
entry_reader[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: else:
entry_reader[MKTXPConfigKeys.PORT_KEY] = self._default_value_for_key(MKTXPConfigKeys.SSL_KEY, entry_reader[MKTXPConfigKeys.SSL_KEY]) entry_reader[MKTXPConfigKeys.PORT_KEY] = self._default_value_for_key(MKTXPConfigKeys.SSL_KEY, entry_reader[MKTXPConfigKeys.SSL_KEY])
write_needed = True # read from disk next time write_needed = True # read from disk next time
if write_needed: if write_needed:
self.config[entry_name] = entry_reader self.config[entry_name] = entry_reader
self.config.write() self.config.write()
return entry_reader return entry_reader
def _entry_reader(self): def _entry_reader(self):
_entry_reader = {} _entry_reader = {}
entry_name = MKTXPConfigKeys.MKTXP_CONFIG_ENTRY_NAME entry_name = MKTXPConfigKeys.MKTXP_CONFIG_ENTRY_NAME
write_needed = False write_needed = False
for key in MKTXPConfigKeys.MKTXP_INT_KEYS: for key in MKTXPConfigKeys.MKTXP_INT_KEYS:
if self._config[entry_name].get(key): if self._config[entry_name].get(key):
_entry_reader[key] = self._config[entry_name].as_int(key) _entry_reader[key] = self._config[entry_name].as_int(key)
else: else:
_entry_reader[key] = self._default_value_for_key(key) _entry_reader[key] = self._default_value_for_key(key)
write_needed = True # read from disk next time write_needed = True # read from disk next time
if write_needed: if write_needed:
self._config[entry_name] = _entry_reader self._config[entry_name] = _entry_reader
self._config.write() self._config.write()
return _entry_reader return _entry_reader
def _default_value_for_key(self, key, value = None): def _default_value_for_key(self, key, value = None):
return { return {
MKTXPConfigKeys.SSL_KEY: lambda value: MKTXPConfigKeys.DEFAULT_API_SSL_PORT if value else MKTXPConfigKeys.DEFAULT_API_PORT, 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.PORT_KEY: lambda value: MKTXPConfigKeys.DEFAULT_MKTXP_PORT,
MKTXPConfigKeys.MKTXP_SOCKET_TIMEOUT: lambda value: MKTXPConfigKeys.DEFAULT_MKTXP_SOCKET_TIMEOUT, MKTXPConfigKeys.MKTXP_SOCKET_TIMEOUT: lambda value: MKTXPConfigKeys.DEFAULT_MKTXP_SOCKET_TIMEOUT,
MKTXPConfigKeys.MKTXP_INITIAL_DELAY: lambda value: MKTXPConfigKeys.DEFAULT_MKTXP_INITIAL_DELAY, MKTXPConfigKeys.MKTXP_INITIAL_DELAY: lambda value: MKTXPConfigKeys.DEFAULT_MKTXP_INITIAL_DELAY,
MKTXPConfigKeys.MKTXP_MAX_DELAY: lambda value: MKTXPConfigKeys.DEFAULT_MKTXP_MAX_DELAY, MKTXPConfigKeys.MKTXP_MAX_DELAY: lambda value: MKTXPConfigKeys.DEFAULT_MKTXP_MAX_DELAY,
MKTXPConfigKeys.MKTXP_INC_DIV: lambda value: MKTXPConfigKeys.DEFAULT_MKTXP_INC_DIV, MKTXPConfigKeys.MKTXP_INC_DIV: lambda value: MKTXPConfigKeys.DEFAULT_MKTXP_INC_DIV,
MKTXPConfigKeys.MKTXP_BANDWIDTH_TEST_INTERVAL: lambda value: MKTXPConfigKeys.DEFAULT_MKTXP_BANDWIDTH_TEST_INTERVAL MKTXPConfigKeys.MKTXP_BANDWIDTH_TEST_INTERVAL: lambda value: MKTXPConfigKeys.DEFAULT_MKTXP_BANDWIDTH_TEST_INTERVAL
}[key](value) }[key](value)
# Simplest possible Singleton impl # Simplest possible Singleton impl

View File

@@ -12,6 +12,7 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details. ## GNU General Public License for more details.
import sys import sys
import subprocess import subprocess
import mktxp.cli.checks.chk_pv import mktxp.cli.checks.chk_pv
@@ -20,6 +21,7 @@ from mktxp.cli.options import MKTXPOptionsParser, MKTXPCommands
from mktxp.cli.config.config import config_handler, ConfigEntry from mktxp.cli.config.config import config_handler, ConfigEntry
from mktxp.basep import MKTXPProcessor, MKTXPCLIProcessor from mktxp.basep import MKTXPProcessor, MKTXPCLIProcessor
class MKTXPDispatcher: class MKTXPDispatcher:
''' Base MKTXP Commands Dispatcher ''' Base MKTXP Commands Dispatcher
''' '''
@@ -92,7 +94,7 @@ class MKTXPDispatcher:
def print(self, args): def print(self, args):
if not (args['wifi_clients'] or args['capsman_clients']): if not (args['wifi_clients'] or args['capsman_clients']):
print("Select metric option(s) to print out, or run 'mktxp print' -h to find out more") print("Select metric option(s) to print out, or run 'mktxp print -h' to find out more")
if args['wifi_clients']: if args['wifi_clients']:
MKTXPCLIProcessor.wifi_clients(args['entry_name']) MKTXPCLIProcessor.wifi_clients(args['entry_name'])
@@ -100,6 +102,10 @@ class MKTXPDispatcher:
if args['capsman_clients']: if args['capsman_clients']:
MKTXPCLIProcessor.capsman_clients(args['entry_name']) MKTXPCLIProcessor.capsman_clients(args['entry_name'])
if args['dhcp_clients']:
MKTXPCLIProcessor.dhcp_clients(args['entry_name'])
def main(): def main():
MKTXPDispatcher().dispatch() MKTXPDispatcher().dispatch()

View File

@@ -11,6 +11,7 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details. ## GNU General Public License for more details.
import os import os
import pkg_resources import pkg_resources
from argparse import ArgumentParser, HelpFormatter from argparse import ArgumentParser, HelpFormatter
@@ -132,6 +133,9 @@ Selected metrics info can be printed on the command line. For more information,
help = "WiFi clients metrics", help = "WiFi clients metrics",
action = 'store_true') action = 'store_true')
optional_args_group.add_argument('-dc', '--dhcp_clients', dest='dhcp_clients',
help = "DHCP clients metrics",
action = 'store_true')
# Options checking # Options checking
def _check_args(self, args, parser): def _check_args(self, args, parser):

View File

@@ -21,16 +21,17 @@ from mktxp.cli.config.config import config_handler
class BaseOutputProcessor: class BaseOutputProcessor:
OutputCapsmanEntry = namedtuple('OutputCapsmanEntry', ['dhcp_name', 'dhcp_address', 'mac_address', 'rx_signal', 'interface', 'ssid', 'tx_rate', 'rx_rate', 'uptime']) OutputCapsmanEntry = namedtuple('OutputCapsmanEntry', ['dhcp_name', 'dhcp_address', 'mac_address', 'rx_signal', 'interface', 'ssid', 'tx_rate', 'rx_rate', 'uptime'])
OutputWiFiEntry = namedtuple('OutputWiFiEntry', ['dhcp_name', 'dhcp_address', 'mac_address', 'signal_strength', 'signal_to_noise', 'interface', 'tx_rate', 'rx_rate', 'uptime']) OutputWiFiEntry = namedtuple('OutputWiFiEntry', ['dhcp_name', 'dhcp_address', 'mac_address', 'signal_strength', 'signal_to_noise', 'interface', 'tx_rate', 'rx_rate', 'uptime'])
OutputDHCPEntry = namedtuple('OutputDHCPEntry', ['host_name', 'comment', 'address', 'active_address', 'mac_address', 'server', 'expires_after'])
@staticmethod @staticmethod
def augment_record(router_metric, registration_record, dhcp_lease_records): def augment_record(router_entry, registration_record, dhcp_lease_records):
try: 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'])) 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']))
dhcp_name = dhcp_lease_record.get('host_name') dhcp_name = dhcp_lease_record.get('host_name')
dhcp_comment = dhcp_lease_record.get('comment') dhcp_comment = dhcp_lease_record.get('comment')
if dhcp_name and dhcp_comment: if dhcp_name and dhcp_comment:
dhcp_name = f'{dhcp_name[0:20]} ({dhcp_comment[0:20]})' if not router_metric.router_entry.use_comments_over_names else dhcp_comment dhcp_name = f'{dhcp_name[0:20]} ({dhcp_comment[0:20]})' if not router_entry.config_entry.use_comments_over_names else dhcp_comment
elif dhcp_comment: elif dhcp_comment:
dhcp_name = dhcp_comment dhcp_name = dhcp_comment
else: else:
@@ -49,9 +50,12 @@ class BaseOutputProcessor:
registration_record['rx_bytes'] = registration_record['bytes'].split(',')[1] registration_record['rx_bytes'] = registration_record['bytes'].split(',')[1]
del registration_record['bytes'] del registration_record['bytes']
registration_record['tx_rate'] = BaseOutputProcessor.parse_rates(registration_record['tx_rate']) if registration_record.get('tx_rate'):
registration_record['rx_rate'] = BaseOutputProcessor.parse_rates(registration_record['rx_rate']) registration_record['tx_rate'] = BaseOutputProcessor.parse_rates(registration_record['tx_rate'])
registration_record['uptime'] = naturaldelta(BaseOutputProcessor.parse_timedelta_seconds(registration_record['uptime']), months=True, minimum_unit='seconds', when=None) if registration_record.get('rx_rate'):
registration_record['rx_rate'] = BaseOutputProcessor.parse_rates(registration_record['rx_rate'])
if registration_record.get('uptime'):
registration_record['uptime'] = naturaldelta(BaseOutputProcessor.parse_timedelta_seconds(registration_record['uptime']), months=True, minimum_unit='seconds', when=None)
if registration_record.get('signal_strength'): if registration_record.get('signal_strength'):
registration_record['signal_strength'] = BaseOutputProcessor.parse_signal_strength(registration_record['signal_strength']) registration_record['signal_strength'] = BaseOutputProcessor.parse_signal_strength(registration_record['signal_strength'])

View File

@@ -11,27 +11,31 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details. ## GNU General Public License for more details.
from tabulate import tabulate from tabulate import tabulate
from mktxp.cli.output.base_out import BaseOutputProcessor from mktxp.cli.output.base_out import BaseOutputProcessor
from mktxp.datasources.dhcp_ds import DHCPMetricsDataSource
from mktxp.datasources.capsman_ds import CapsmanRegistrationsMetricsDataSource
class CapsmanOutput: class CapsmanOutput:
''' CAPsMAN CLI Output ''' CAPsMAN CLI Output
''' '''
@staticmethod @staticmethod
def clients_summary(router_metric): def clients_summary(router_entry):
registration_labels = ['interface', 'ssid', 'mac_address', 'rx_signal', 'uptime', 'tx_rate', 'rx_rate'] registration_labels = ['interface', 'ssid', 'mac_address', 'rx_signal', 'uptime', 'tx_rate', 'rx_rate']
registration_records = router_metric.capsman_registration_table_records(registration_labels, False) registration_records = CapsmanRegistrationsMetricsDataSource.metric_records(router_entry, metric_labels = registration_labels, add_router_id = False)
if not registration_records: if not registration_records:
print('No CAPsMAN registration records') print('No CAPsMAN registration records')
return return
# translate / trim / augment registration records # translate / trim / augment registration records
dhcp_lease_labels = ['host_name', 'comment', 'address', 'mac_address'] dhcp_lease_labels = ['host_name', 'comment', 'address', 'mac_address']
dhcp_lease_records = router_metric.dhcp_lease_records(dhcp_lease_labels, False) dhcp_lease_records = DHCPMetricsDataSource.metric_records(router_entry, metric_labels = dhcp_lease_labels, add_router_id = False)
dhcp_rt_by_interface = {} dhcp_rt_by_interface = {}
for registration_record in sorted(registration_records, key = lambda rt_record: rt_record['rx_signal'], reverse=True): for registration_record in sorted(registration_records, key = lambda rt_record: rt_record['rx_signal'], reverse=True):
BaseOutputProcessor.augment_record(router_metric, registration_record, dhcp_lease_records) BaseOutputProcessor.augment_record(router_entry, registration_record, dhcp_lease_records)
interface = registration_record['interface'] interface = registration_record['interface']
if interface in dhcp_rt_by_interface.keys(): if interface in dhcp_rt_by_interface.keys():

View File

@@ -0,0 +1,48 @@
# coding=utf8
## Copyright (c) 2020 Arseniy Kuznetsov
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
from tabulate import tabulate
from mktxp.cli.output.base_out import BaseOutputProcessor
from mktxp.datasources.dhcp_ds import DHCPMetricsDataSource
class DHCPOutput:
''' DHCP Clients CLI Output
'''
@staticmethod
def clients_summary(router_entry):
dhcp_lease_labels = ['host_name', 'comment', 'active_address', 'address', 'mac_address', 'server', 'expires_after']
dhcp_lease_records = DHCPMetricsDataSource.metric_records(router_entry, metric_labels = dhcp_lease_labels, add_router_id = False)
if not dhcp_lease_records:
print('No DHCP registration records')
return
dhcp_by_server = {}
for dhcp_lease_record in sorted(dhcp_lease_records, key = lambda dhcp_record: dhcp_record['active_address'], reverse=True):
server = dhcp_lease_record['server']
if server in dhcp_by_server.keys():
dhcp_by_server[server].append(dhcp_lease_record)
else:
dhcp_by_server[server] = [dhcp_lease_record]
num_records = 0
output_table = []
for key in dhcp_by_server.keys():
for record in dhcp_by_server[key]:
output_table.append(BaseOutputProcessor.OutputDHCPEntry(**record))
num_records += 1
output_table.append({})
print()
print(tabulate(output_table, headers = "keys", tablefmt="github"))
print(tabulate([{0:'DHCP Clients:', 1:num_records}], tablefmt="text"))

View File

@@ -11,27 +11,31 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details. ## GNU General Public License for more details.
from tabulate import tabulate from tabulate import tabulate
from mktxp.cli.output.base_out import BaseOutputProcessor from mktxp.cli.output.base_out import BaseOutputProcessor
from mktxp.datasources.dhcp_ds import DHCPMetricsDataSource
from mktxp.datasources.wireless_ds import WirelessMetricsDataSource
class WirelessOutput: class WirelessOutput:
''' Wireless Clients CLI Output ''' Wireless Clients CLI Output
''' '''
@staticmethod @staticmethod
def clients_summary(router_metric): def clients_summary(router_entry):
registration_labels = ['interface', 'mac_address', 'signal_strength', 'uptime', 'tx_rate', 'rx_rate', 'signal_to_noise'] registration_labels = ['interface', 'mac_address', 'signal_strength', 'uptime', 'tx_rate', 'rx_rate', 'signal_to_noise']
registration_records = router_metric.wireless_registration_table_records(registration_labels, False) registration_records = WirelessMetricsDataSource.metric_records(router_entry, metric_labels = registration_labels, add_router_id = False)
if not registration_records: if not registration_records:
print('No wireless registration records') print('No wireless registration records')
return return
# translate / trim / augment registration records # translate / trim / augment registration records
dhcp_lease_labels = ['host_name', 'comment', 'address', 'mac_address'] dhcp_lease_labels = ['host_name', 'comment', 'address', 'mac_address']
dhcp_lease_records = router_metric.dhcp_lease_records(dhcp_lease_labels, False) dhcp_lease_records = DHCPMetricsDataSource.metric_records(router_entry, metric_labels = dhcp_lease_labels, add_router_id = False)
dhcp_rt_by_interface = {} dhcp_rt_by_interface = {}
for registration_record in sorted(registration_records, key = lambda rt_record: rt_record['signal_strength'], reverse=True): for registration_record in sorted(registration_records, key = lambda rt_record: rt_record['signal_strength'], reverse=True):
BaseOutputProcessor.augment_record(router_metric, registration_record, dhcp_lease_records) BaseOutputProcessor.augment_record(router_entry, registration_record, dhcp_lease_records)
interface = registration_record['interface'] interface = registration_record['interface']
if interface in dhcp_rt_by_interface.keys(): if interface in dhcp_rt_by_interface.keys():

View File

@@ -12,16 +12,19 @@
## GNU General Public License for more details. ## GNU General Public License for more details.
import socket
import speedtest import speedtest
from datetime import datetime from datetime import datetime
from multiprocessing import Pool from multiprocessing import Pool
from mktxp.cli.config.config import config_handler from mktxp.cli.config.config import config_handler
from mktxp.collectors.base_collector import BaseCollector from mktxp.collectors.base_collector import BaseCollector
result_list = [{'download': 0, 'upload': 0, 'ping': 0}] result_list = [{'download': 0, 'upload': 0, 'ping': 0}]
def get_result(bandwidth_dict): def get_result(bandwidth_dict):
result_list[0] = bandwidth_dict result_list[0] = bandwidth_dict
class BandwidthCollector(BaseCollector): class BandwidthCollector(BaseCollector):
''' MKTXP collector ''' MKTXP collector
''' '''
@@ -53,8 +56,21 @@ class BandwidthCollector(BaseCollector):
@staticmethod @staticmethod
def bandwidth_worker(): def bandwidth_worker():
bandwidth_test = speedtest.Speedtest() if BandwidthCollector.inet_connected():
bandwidth_test.get_best_server() bandwidth_test = speedtest.Speedtest()
bandwidth_test.download() bandwidth_test.get_best_server()
bandwidth_test.upload() bandwidth_test.download()
return bandwidth_test.results.dict() bandwidth_test.upload()
return bandwidth_test.results.dict()
else:
return {'download': 0, 'upload': 0, 'ping': 0}
@staticmethod
def inet_connected(host="8.8.8.8", port=53, timeout=3):
try:
socket.setdefaulttimeout(timeout)
socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, port))
return True
except socket.error as exc:
return False

View File

@@ -15,6 +15,7 @@
from prometheus_client.core import GaugeMetricFamily, CounterMetricFamily, InfoMetricFamily from prometheus_client.core import GaugeMetricFamily, CounterMetricFamily, InfoMetricFamily
from mktxp.cli.config.config import MKTXPConfigKeys from mktxp.cli.config.config import MKTXPConfigKeys
class BaseCollector: class BaseCollector:
''' Base Collector methods ''' Base Collector methods
For use by custom collectors For use by custom collectors

View File

@@ -11,43 +11,47 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details. ## GNU General Public License for more details.
from mktxp.cli.output.base_out import BaseOutputProcessor from mktxp.cli.output.base_out import BaseOutputProcessor
from mktxp.cli.config.config import MKTXPConfigKeys from mktxp.cli.config.config import MKTXPConfigKeys
from mktxp.collectors.base_collector import BaseCollector from mktxp.collectors.base_collector import BaseCollector
from mktxp.datasources.dhcp_ds import DHCPMetricsDataSource
from mktxp.datasources.capsman_ds import CapsmanCapsMetricsDataSource, CapsmanRegistrationsMetricsDataSource
class CapsmanCollector(BaseCollector): class CapsmanCollector(BaseCollector):
''' CAPsMAN Metrics collector ''' CAPsMAN Metrics collector
''' '''
@staticmethod @staticmethod
def collect(router_metric): def collect(router_entry):
remote_caps_labels = ['identity', 'version', 'base_mac', 'board', 'base_mac'] remote_caps_labels = ['identity', 'version', 'base_mac', 'board', 'base_mac']
remote_caps_records = router_metric.capsman_remote_caps_records(remote_caps_labels) remote_caps_records = CapsmanCapsMetricsDataSource.metric_records(router_entry, metric_labels = remote_caps_labels)
if remote_caps_records: if remote_caps_records:
remote_caps_metrics = BaseCollector.info_collector('capsman_remote_caps', 'CAPsMAN remote caps', remote_caps_records, remote_caps_labels) remote_caps_metrics = BaseCollector.info_collector('capsman_remote_caps', 'CAPsMAN remote caps', remote_caps_records, remote_caps_labels)
yield remote_caps_metrics yield remote_caps_metrics
registration_labels = ['interface', 'ssid', 'mac_address', 'tx_rate', 'rx_rate', 'rx_signal', 'uptime', 'bytes'] registration_labels = ['interface', 'ssid', 'mac_address', 'tx_rate', 'rx_rate', 'rx_signal', 'uptime', 'bytes']
registration_records = router_metric.capsman_registration_table_records(registration_labels) registration_records = CapsmanRegistrationsMetricsDataSource.metric_records(router_entry, metric_labels = registration_labels)
if registration_records: if registration_records:
# calculate number of registrations per interface # calculate number of registrations per interface
registration_per_interface = {} registration_per_interface = {}
for registration_record in registration_records: for registration_record in registration_records:
registration_per_interface[registration_record['interface']] = registration_per_interface.get(registration_record['interface'], 0) + 1 registration_per_interface[registration_record['interface']] = registration_per_interface.get(registration_record['interface'], 0) + 1
# compile registrations-per-interface records # compile registrations-per-interface records
registration_per_interface_records = [{ MKTXPConfigKeys.ROUTERBOARD_NAME: router_metric.router_id[MKTXPConfigKeys.ROUTERBOARD_NAME], registration_per_interface_records = [{ MKTXPConfigKeys.ROUTERBOARD_NAME: router_entry.router_id[MKTXPConfigKeys.ROUTERBOARD_NAME],
MKTXPConfigKeys.ROUTERBOARD_ADDRESS: router_metric.router_id[MKTXPConfigKeys.ROUTERBOARD_ADDRESS], MKTXPConfigKeys.ROUTERBOARD_ADDRESS: router_entry.router_id[MKTXPConfigKeys.ROUTERBOARD_ADDRESS],
'interface': key, 'count': value} for key, value in registration_per_interface.items()] 'interface': key, 'count': value} for key, value in registration_per_interface.items()]
# yield registrations-per-interface metrics # yield registrations-per-interface metrics
registration_per_interface_metrics = BaseCollector.gauge_collector('capsman_registrations_count', 'Number of active registration per CAPsMAN interface', registration_per_interface_records, 'count', ['interface']) registration_per_interface_metrics = BaseCollector.gauge_collector('capsman_registrations_count', 'Number of active registration per CAPsMAN interface', registration_per_interface_records, 'count', ['interface'])
yield registration_per_interface_metrics yield registration_per_interface_metrics
# the client info metrics # the client info metrics
if router_metric.router_entry.capsman_clients: if router_entry.config_entry.capsman_clients:
# translate / trim / augment registration records # translate / trim / augment registration records
dhcp_lease_labels = ['mac_address', 'address', 'host_name', 'comment'] dhcp_lease_labels = ['mac_address', 'address', 'host_name', 'comment']
dhcp_lease_records = router_metric.dhcp_lease_records(dhcp_lease_labels) dhcp_lease_records = DHCPMetricsDataSource.metric_records(router_entry, metric_labels = dhcp_lease_labels)
for registration_record in registration_records: for registration_record in registration_records:
BaseOutputProcessor.augment_record(router_metric, registration_record, dhcp_lease_records) BaseOutputProcessor.augment_record(router_entry, registration_record, dhcp_lease_records)
tx_byte_metrics = BaseCollector.counter_collector('capsman_clients_tx_bytes', 'Number of sent packet bytes', registration_records, 'tx_bytes', ['dhcp_name']) tx_byte_metrics = BaseCollector.counter_collector('capsman_clients_tx_bytes', 'Number of sent packet bytes', registration_records, 'tx_bytes', ['dhcp_name'])
yield tx_byte_metrics yield tx_byte_metrics

View File

@@ -11,17 +11,19 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details. ## GNU General Public License for more details.
from mktxp.cli.config.config import MKTXPConfigKeys from mktxp.cli.config.config import MKTXPConfigKeys
from mktxp.collectors.base_collector import BaseCollector from mktxp.collectors.base_collector import BaseCollector
from mktxp.datasources.dhcp_ds import DHCPMetricsDataSource
class DHCPCollector(BaseCollector): class DHCPCollector(BaseCollector):
''' DHCP Metrics collector ''' DHCP Metrics collector
''' '''
@staticmethod @staticmethod
def collect(router_metric): def collect(router_entry):
dhcp_lease_labels = ['active_address', 'mac_address', 'host_name', 'comment', 'server', 'expires_after'] dhcp_lease_labels = ['active_address', 'address', 'mac_address', 'host_name', 'comment', 'server', 'expires_after']
dhcp_lease_records = router_metric.dhcp_lease_records(dhcp_lease_labels) dhcp_lease_records = DHCPMetricsDataSource.metric_records(router_entry, metric_labels = dhcp_lease_labels)
if dhcp_lease_records: if dhcp_lease_records:
# calculate number of leases per DHCP server # calculate number of leases per DHCP server
dhcp_lease_servers = {} dhcp_lease_servers = {}
@@ -29,8 +31,8 @@ class DHCPCollector(BaseCollector):
dhcp_lease_servers[dhcp_lease_record['server']] = dhcp_lease_servers.get(dhcp_lease_record['server'], 0) + 1 dhcp_lease_servers[dhcp_lease_record['server']] = dhcp_lease_servers.get(dhcp_lease_record['server'], 0) + 1
# compile leases-per-server records # compile leases-per-server records
dhcp_lease_servers_records = [{ MKTXPConfigKeys.ROUTERBOARD_NAME: router_metric.router_id[MKTXPConfigKeys.ROUTERBOARD_NAME], dhcp_lease_servers_records = [{ MKTXPConfigKeys.ROUTERBOARD_NAME: router_entry.router_id[MKTXPConfigKeys.ROUTERBOARD_NAME],
MKTXPConfigKeys.ROUTERBOARD_ADDRESS: router_metric.router_id[MKTXPConfigKeys.ROUTERBOARD_ADDRESS], MKTXPConfigKeys.ROUTERBOARD_ADDRESS: router_entry.router_id[MKTXPConfigKeys.ROUTERBOARD_ADDRESS],
'server': key, 'count': value} for key, value in dhcp_lease_servers.items()] 'server': key, 'count': value} for key, value in dhcp_lease_servers.items()]
# yield lease-per-server metrics # yield lease-per-server metrics
@@ -38,7 +40,7 @@ class DHCPCollector(BaseCollector):
yield dhcp_lease_server_metrics yield dhcp_lease_server_metrics
# active lease metrics # active lease metrics
if router_metric.router_entry.dhcp_lease: if router_entry.config_entry.dhcp_lease:
dhcp_lease_metrics = BaseCollector.info_collector('dhcp_lease', 'DHCP Active Leases', dhcp_lease_records, dhcp_lease_labels) dhcp_lease_metrics = BaseCollector.info_collector('dhcp_lease', 'DHCP Active Leases', dhcp_lease_records, dhcp_lease_labels)
yield dhcp_lease_metrics yield dhcp_lease_metrics

View File

@@ -11,34 +11,37 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details. ## GNU General Public License for more details.
from mktxp.collectors.base_collector import BaseCollector
from mktxp.cli.config.config import MKTXPConfigKeys from mktxp.cli.config.config import MKTXPConfigKeys
from mktxp.collectors.base_collector import BaseCollector
from mktxp.datasources.firewall_ds import FirewallMetricsDataSource
class FirewallCollector(BaseCollector): class FirewallCollector(BaseCollector):
''' Firewall rules traffic metrics collector ''' Firewall rules traffic metrics collector
''' '''
@staticmethod @staticmethod
def collect(router_metric): def collect(router_entry):
# initialize all pool counts, including those currently not used # initialize all pool counts, including those currently not used
firewall_labels = ['chain', 'action', 'bytes', 'comment'] firewall_labels = ['chain', 'action', 'bytes', 'comment']
firewall_filter_records = router_metric.firewall_records(firewall_labels) firewall_filter_records = FirewallMetricsDataSource.metric_records(router_entry, metric_labels = firewall_labels)
if firewall_filter_records: if firewall_filter_records:
metris_records = [FirewallCollector.metric_record(router_metric, record) for record in firewall_filter_records] metris_records = [FirewallCollector.metric_record(router_entry, record) for record in firewall_filter_records]
firewall_filter_metrics = BaseCollector.counter_collector('firewall_filter', 'Total amount of bytes matched by firewall rules', metris_records, 'bytes', ['name']) firewall_filter_metrics = BaseCollector.counter_collector('firewall_filter', 'Total amount of bytes matched by firewall rules', metris_records, 'bytes', ['name'])
yield firewall_filter_metrics yield firewall_filter_metrics
firewall_raw_records = router_metric.firewall_records(firewall_labels, raw = True) firewall_raw_records = FirewallMetricsDataSource.metric_records(router_entry, metric_labels = firewall_labels, raw = True)
if firewall_raw_records: if firewall_raw_records:
metris_records = [FirewallCollector.metric_record(router_metric, record) for record in firewall_raw_records] metris_records = [FirewallCollector.metric_record(router_entry, record) for record in firewall_raw_records]
firewall_raw_metrics = BaseCollector.counter_collector('firewall_raw', 'Total amount of bytes matched by raw firewall rules', metris_records, 'bytes', ['name']) firewall_raw_metrics = BaseCollector.counter_collector('firewall_raw', 'Total amount of bytes matched by raw firewall rules', metris_records, 'bytes', ['name'])
yield firewall_raw_metrics yield firewall_raw_metrics
# Helpers # Helpers
@staticmethod @staticmethod
def metric_record(router_metric, firewall_record): def metric_record(router_entry, firewall_record):
name = f"| {firewall_record['chain']} | {firewall_record['action']} | {firewall_record['comment']}" name = f"| {firewall_record['chain']} | {firewall_record['action']} | {firewall_record['comment']}"
bytes = firewall_record['bytes'] bytes = firewall_record['bytes']
return {MKTXPConfigKeys.ROUTERBOARD_NAME: router_metric.router_id[MKTXPConfigKeys.ROUTERBOARD_NAME], return {MKTXPConfigKeys.ROUTERBOARD_NAME: router_entry.router_id[MKTXPConfigKeys.ROUTERBOARD_NAME],
MKTXPConfigKeys.ROUTERBOARD_ADDRESS: router_metric.router_id[MKTXPConfigKeys.ROUTERBOARD_ADDRESS], MKTXPConfigKeys.ROUTERBOARD_ADDRESS: router_entry.router_id[MKTXPConfigKeys.ROUTERBOARD_ADDRESS],
'name': name, 'bytes': bytes} 'name': name, 'bytes': bytes}

View File

@@ -11,16 +11,18 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details. ## GNU General Public License for more details.
from mktxp.collectors.base_collector import BaseCollector from mktxp.collectors.base_collector import BaseCollector
from mktxp.datasources.health_ds import HealthMetricsDataSource
class HealthCollector(BaseCollector): class HealthCollector(BaseCollector):
''' System Health Metrics collector ''' System Health Metrics collector
''' '''
@staticmethod @staticmethod
def collect(router_metric): def collect(router_entry):
health_labels = ['voltage', 'temperature'] health_labels = ['voltage', 'temperature']
health_records = router_metric.health_records(health_labels) health_records = HealthMetricsDataSource.metric_records(router_entry, metric_labels = health_labels)
if health_records: if health_records:
voltage_metrics = BaseCollector.gauge_collector('system_routerboard_voltage', 'Supplied routerboard voltage', health_records, 'voltage') voltage_metrics = BaseCollector.gauge_collector('system_routerboard_voltage', 'Supplied routerboard voltage', health_records, 'voltage')
yield voltage_metrics yield voltage_metrics

View File

@@ -11,15 +11,18 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details. ## GNU General Public License for more details.
from mktxp.collectors.base_collector import BaseCollector from mktxp.collectors.base_collector import BaseCollector
from mktxp.datasources.identity_ds import IdentityMetricsDataSource
class IdentityCollector(BaseCollector): class IdentityCollector(BaseCollector):
''' System Identity Metrics collector ''' System Identity Metrics collector
''' '''
@staticmethod @staticmethod
def collect(router_metric): def collect(router_entry):
identity_labels = ['name'] identity_labels = ['name']
identity_records = router_metric.identity_records(identity_labels) identity_records = IdentityMetricsDataSource.metric_records(router_entry, metric_labels = identity_labels)
if identity_records: if identity_records:
identity_metrics = BaseCollector.info_collector('system_identity', 'System identity', identity_records, identity_labels) identity_metrics = BaseCollector.info_collector('system_identity', 'System identity', identity_records, identity_labels)
yield identity_metrics yield identity_metrics

View File

@@ -11,20 +11,23 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details. ## GNU General Public License for more details.
from mktxp.collectors.base_collector import BaseCollector from mktxp.collectors.base_collector import BaseCollector
from mktxp.datasources.interface_ds import InterfaceTrafficMetricsDataSource
class InterfaceCollector(BaseCollector): class InterfaceCollector(BaseCollector):
''' Router Interface Metrics collector ''' Router Interface Metrics collector
''' '''
@staticmethod @staticmethod
def collect(router_metric): def collect(router_entry):
interface_traffic_labels = ['name', 'comment', 'rx_byte', 'tx_byte', 'rx_packet', 'tx_packet', 'rx_error', 'tx_error', 'rx_drop', 'tx_drop'] 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) interface_traffic_records = InterfaceTrafficMetricsDataSource.metric_records(router_entry, metric_labels = interface_traffic_labels)
if interface_traffic_records: if interface_traffic_records:
for interface_traffic_record in interface_traffic_records: for interface_traffic_record in interface_traffic_records:
if interface_traffic_record.get('comment'): if interface_traffic_record.get('comment'):
interface_traffic_record['name'] = interface_traffic_record['comment'] if router_metric.router_entry.use_comments_over_names \ interface_traffic_record['name'] = interface_traffic_record['comment'] if router_entry.config_entry.use_comments_over_names \
else f"{interface_traffic_record['name']} ({interface_traffic_record['comment']})" else 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']) rx_byte_metric = BaseCollector.counter_collector('interface_rx_byte', 'Number of received bytes', interface_traffic_records, 'rx_byte', ['name'])

View File

@@ -11,14 +11,17 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details. ## GNU General Public License for more details.
from mktxp.collectors.base_collector import BaseCollector from mktxp.collectors.base_collector import BaseCollector
from mktxp.datasources.mktxp_ds import MKTXPMetricsDataSource
class MKTXPCollector(BaseCollector): class MKTXPCollector(BaseCollector):
''' System Identity Metrics collector ''' System Identity Metrics collector
''' '''
@staticmethod @staticmethod
def collect(router_metric): def collect(router_entry):
mktxp_records = router_metric.mktxp_records() mktxp_records = MKTXPMetricsDataSource.metric_records(router_entry)
if mktxp_records: if mktxp_records:
mktxp_duration_metric = BaseCollector.counter_collector('collection_time', 'Total time spent collecting metrics in milliseconds', mktxp_records, 'duration', ['name']) mktxp_duration_metric = BaseCollector.counter_collector('collection_time', 'Total time spent collecting metrics in milliseconds', mktxp_records, 'duration', ['name'])
yield mktxp_duration_metric yield mktxp_duration_metric

View File

@@ -11,16 +11,19 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details. ## GNU General Public License for more details.
from mktxp.collectors.base_collector import BaseCollector from mktxp.collectors.base_collector import BaseCollector
from mktxp.cli.output.base_out import BaseOutputProcessor from mktxp.cli.output.base_out import BaseOutputProcessor
from mktxp.datasources.interface_ds import InterfaceMonitorMetricsDataSource
class MonitorCollector(BaseCollector): class MonitorCollector(BaseCollector):
''' Ethernet Interface Monitor Metrics collector ''' Ethernet Interface Monitor Metrics collector
''' '''
@staticmethod @staticmethod
def collect(router_metric): def collect(router_entry):
monitor_labels = ('status', 'rate', 'full_duplex', 'name') monitor_labels = ('status', 'rate', 'full_duplex', 'name')
monitor_records = router_metric.interface_monitor_records(monitor_labels, include_comments = True) monitor_records = InterfaceMonitorMetricsDataSource.metric_records(router_entry, metric_labels = monitor_labels, include_comments = True)
if monitor_records: if monitor_records:
# translate records to appropriate values # translate records to appropriate values
for monitor_record in monitor_records: for monitor_record in monitor_records:

View File

@@ -11,28 +11,31 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details. ## GNU General Public License for more details.
from mktxp.cli.config.config import MKTXPConfigKeys from mktxp.cli.config.config import MKTXPConfigKeys
from mktxp.collectors.base_collector import BaseCollector from mktxp.collectors.base_collector import BaseCollector
from mktxp.datasources.pool_ds import PoolMetricsDataSource, PoolUsedMetricsDataSource
class PoolCollector(BaseCollector): class PoolCollector(BaseCollector):
''' IP Pool Metrics collector ''' IP Pool Metrics collector
''' '''
@staticmethod @staticmethod
def collect(router_metric): def collect(router_entry):
# initialize all pool counts, including those currently not used # initialize all pool counts, including those currently not used
pool_records = router_metric.pool_records(['name']) pool_records = PoolMetricsDataSource.metric_records(router_entry, metric_labels = ['name'])
if pool_records: if pool_records:
pool_used_labels = ['pool'] pool_used_labels = ['pool']
pool_used_counts = {pool_record['name']: 0 for pool_record in pool_records} pool_used_counts = {pool_record['name']: 0 for pool_record in pool_records}
# for pools in usage, calculate the current numbers # for pools in usage, calculate the current numbers
pool_used_records = router_metric.pool_used_records(pool_used_labels) pool_used_records = PoolUsedMetricsDataSource.metric_records(router_entry, metric_labels = pool_used_labels)
for pool_used_record in pool_used_records: for pool_used_record in pool_used_records:
pool_used_counts[pool_used_record['pool']] = pool_used_counts.get(pool_used_record['pool'], 0) + 1 pool_used_counts[pool_used_record['pool']] = pool_used_counts.get(pool_used_record['pool'], 0) + 1
# compile used-per-pool records # compile used-per-pool records
used_per_pool_records = [{ MKTXPConfigKeys.ROUTERBOARD_NAME: router_metric.router_id[MKTXPConfigKeys.ROUTERBOARD_NAME], used_per_pool_records = [{ MKTXPConfigKeys.ROUTERBOARD_NAME: router_entry.router_id[MKTXPConfigKeys.ROUTERBOARD_NAME],
MKTXPConfigKeys.ROUTERBOARD_ADDRESS: router_metric.router_id[MKTXPConfigKeys.ROUTERBOARD_ADDRESS], MKTXPConfigKeys.ROUTERBOARD_ADDRESS: router_entry.router_id[MKTXPConfigKeys.ROUTERBOARD_ADDRESS],
'pool': key, 'count': value} for key, value in pool_used_counts.items()] 'pool': key, 'count': value} for key, value in pool_used_counts.items()]
# yield used-per-pool metrics # yield used-per-pool metrics

View File

@@ -11,20 +11,23 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details. ## GNU General Public License for more details.
from mktxp.collectors.base_collector import BaseCollector from mktxp.collectors.base_collector import BaseCollector
from mktxp.cli.output.base_out import BaseOutputProcessor from mktxp.cli.output.base_out import BaseOutputProcessor
from mktxp.datasources.system_resource_ds import SystemResourceMetricsDataSource
class SystemResourceCollector(BaseCollector): class SystemResourceCollector(BaseCollector):
''' System Resource Metrics collector ''' System Resource Metrics collector
''' '''
@staticmethod @staticmethod
def collect(router_metric): def collect(router_entry):
resource_labels = ['uptime', 'version', 'free_memory', 'total_memory', resource_labels = ['uptime', 'version', 'free_memory', 'total_memory',
'cpu', 'cpu_count', 'cpu_frequency', 'cpu_load', 'cpu', 'cpu_count', 'cpu_frequency', 'cpu_load',
'free_hdd_space', 'total_hdd_space', 'free_hdd_space', 'total_hdd_space',
'architecture_name', 'board_name'] 'architecture_name', 'board_name']
resource_records = router_metric.system_resource_records(resource_labels) resource_records = SystemResourceMetricsDataSource.metric_records(router_entry, metric_labels = resource_labels)
if resource_records: if resource_records:
# translate records to appropriate values # translate records to appropriate values
translated_fields = ['uptime'] translated_fields = ['uptime']

View File

@@ -11,21 +11,24 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details. ## GNU General Public License for more details.
from mktxp.cli.config.config import MKTXPConfigKeys from mktxp.cli.config.config import MKTXPConfigKeys
from mktxp.collectors.base_collector import BaseCollector from mktxp.collectors.base_collector import BaseCollector
from mktxp.datasources.route_ds import RouteMetricsDataSource
class RouteCollector(BaseCollector): class RouteCollector(BaseCollector):
''' IP Route Metrics collector ''' IP Route Metrics collector
''' '''
@staticmethod @staticmethod
def collect(router_metric): def collect(router_entry):
route_labels = ['connect', 'dynamic', 'static', 'bgp', 'ospf'] route_labels = ['connect', 'dynamic', 'static', 'bgp', 'ospf']
route_records = router_metric.route_records(route_labels) route_records = RouteMetricsDataSource.metric_records(router_entry, metric_labels = route_labels)
if route_records: if route_records:
# compile total routes records # compile total routes records
total_routes = len(route_records) total_routes = len(route_records)
total_routes_records = [{ MKTXPConfigKeys.ROUTERBOARD_NAME: router_metric.router_id[MKTXPConfigKeys.ROUTERBOARD_NAME], total_routes_records = [{ MKTXPConfigKeys.ROUTERBOARD_NAME: router_entry.router_id[MKTXPConfigKeys.ROUTERBOARD_NAME],
MKTXPConfigKeys.ROUTERBOARD_ADDRESS: router_metric.router_id[MKTXPConfigKeys.ROUTERBOARD_ADDRESS], MKTXPConfigKeys.ROUTERBOARD_ADDRESS: router_entry.router_id[MKTXPConfigKeys.ROUTERBOARD_ADDRESS],
'count': total_routes 'count': total_routes
}] }]
total_routes_metrics = BaseCollector.gauge_collector('routes_total_routes', 'Overall number of routes in RIB', total_routes_records, 'count') total_routes_metrics = BaseCollector.gauge_collector('routes_total_routes', 'Overall number of routes in RIB', total_routes_records, 'count')
@@ -40,8 +43,8 @@ class RouteCollector(BaseCollector):
routes_per_protocol[route_label] += 1 routes_per_protocol[route_label] += 1
# compile route-per-protocol records # compile route-per-protocol records
route_per_protocol_records = [{ MKTXPConfigKeys.ROUTERBOARD_NAME: router_metric.router_id[MKTXPConfigKeys.ROUTERBOARD_NAME], route_per_protocol_records = [{ MKTXPConfigKeys.ROUTERBOARD_NAME: router_entry.router_id[MKTXPConfigKeys.ROUTERBOARD_NAME],
MKTXPConfigKeys.ROUTERBOARD_ADDRESS: router_metric.router_id[MKTXPConfigKeys.ROUTERBOARD_ADDRESS], MKTXPConfigKeys.ROUTERBOARD_ADDRESS: router_entry.router_id[MKTXPConfigKeys.ROUTERBOARD_ADDRESS],
'protocol': key, 'count': value} for key, value in routes_per_protocol.items()] 'protocol': key, 'count': value} for key, value in routes_per_protocol.items()]
# yield route-per-protocol metrics # yield route-per-protocol metrics

View File

@@ -11,16 +11,21 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details. ## GNU General Public License for more details.
from mktxp.cli.output.base_out import BaseOutputProcessor from mktxp.cli.output.base_out import BaseOutputProcessor
from mktxp.collectors.base_collector import BaseCollector from mktxp.collectors.base_collector import BaseCollector
from mktxp.datasources.dhcp_ds import DHCPMetricsDataSource
from mktxp.datasources.wireless_ds import WirelessMetricsDataSource
from mktxp.datasources.interface_ds import InterfaceMonitorMetricsDataSource
class WLANCollector(BaseCollector): class WLANCollector(BaseCollector):
''' Wireless Metrics collector ''' Wireless Metrics collector
''' '''
@staticmethod @staticmethod
def collect(router_metric): def collect(router_entry):
monitor_labels = ['channel', 'noise_floor', 'overall_tx_ccq', 'registered_clients'] monitor_labels = ['channel', 'noise_floor', 'overall_tx_ccq', 'registered_clients']
monitor_records = router_metric.interface_monitor_records(monitor_labels, 'wireless') monitor_records = InterfaceMonitorMetricsDataSource.metric_records(router_entry, metric_labels = monitor_labels, kind = 'wireless')
if monitor_records: if monitor_records:
# sanitize records for relevant labels # sanitize records for relevant labels
noise_floor_records = [monitor_record for monitor_record in monitor_records if monitor_record.get('noise_floor')] noise_floor_records = [monitor_record for monitor_record in monitor_records if monitor_record.get('noise_floor')]
@@ -40,15 +45,15 @@ class WLANCollector(BaseCollector):
yield registered_clients_metrics yield registered_clients_metrics
# the client info metrics # the client info metrics
if router_metric.router_entry.wireless_clients: if router_entry.config_entry.wireless_clients:
registration_labels = ['interface', 'ssid', 'mac_address', 'tx_rate', 'rx_rate', 'uptime', 'bytes', 'signal_to_noise', 'tx_ccq', 'signal_strength'] 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) registration_records = WirelessMetricsDataSource.metric_records(router_entry, metric_labels = registration_labels)
if registration_records: if registration_records:
dhcp_lease_labels = ['mac_address', 'address', 'host_name', 'comment'] dhcp_lease_labels = ['mac_address', 'address', 'host_name', 'comment']
dhcp_lease_records = router_metric.dhcp_lease_records(dhcp_lease_labels) dhcp_lease_records = DHCPMetricsDataSource.metric_records(router_entry, metric_labels = dhcp_lease_labels)
for registration_record in registration_records: for registration_record in registration_records:
BaseOutputProcessor.augment_record(router_metric, registration_record, dhcp_lease_records) BaseOutputProcessor.augment_record(router_entry, registration_record, dhcp_lease_records)
tx_byte_metrics = BaseCollector.counter_collector('wlan_clients_tx_bytes', 'Number of sent packet bytes', registration_records, 'tx_bytes', ['dhcp_name']) tx_byte_metrics = BaseCollector.counter_collector('wlan_clients_tx_bytes', 'Number of sent packet bytes', registration_records, 'tx_bytes', ['dhcp_name'])
yield tx_byte_metrics yield tx_byte_metrics

View File

@@ -11,6 +11,7 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details. ## GNU General Public License for more details.
from timeit import default_timer from timeit import default_timer
from mktxp.collectors.dhcp_collector import DHCPCollector from mktxp.collectors.dhcp_collector import DHCPCollector
from mktxp.collectors.interface_collector import InterfaceCollector from mktxp.collectors.interface_collector import InterfaceCollector
@@ -30,73 +31,73 @@ from mktxp.collectors.mktxp_collector import MKTXPCollector
class CollectorsHandler: class CollectorsHandler:
''' MKTXP Collectors Handler ''' MKTXP Collectors Handler
''' '''
def __init__(self, metrics_handler): def __init__(self, entries_handler):
self.metrics_handler = metrics_handler self.entries_handler = entries_handler
self.bandwidthCollector = BandwidthCollector() self.bandwidthCollector = BandwidthCollector()
def collect(self): def collect(self):
# process mktxp internal metrics # process mktxp internal metrics
yield from self.bandwidthCollector.collect() yield from self.bandwidthCollector.collect()
for router_metric in self.metrics_handler.router_metrics: for router_entry in self.entries_handler.router_entries:
if not router_metric.api_connection.is_connected(): if not router_entry.api_connection.is_connected():
# let's pick up on things in the next run # let's pick up on things in the next run
router_metric.api_connection.connect() router_entry.api_connection.connect()
continue continue
start = default_timer() start = default_timer()
yield from IdentityCollector.collect(router_metric) yield from IdentityCollector.collect(router_entry)
router_metric.time_spent['IdentityCollector'] += default_timer() - start router_entry.time_spent['IdentityCollector'] += default_timer() - start
start = default_timer() start = default_timer()
yield from SystemResourceCollector.collect(router_metric) yield from SystemResourceCollector.collect(router_entry)
router_metric.time_spent['SystemResourceCollector'] += default_timer() - start router_entry.time_spent['SystemResourceCollector'] += default_timer() - start
start = default_timer() start = default_timer()
yield from HealthCollector.collect(router_metric) yield from HealthCollector.collect(router_entry)
router_metric.time_spent['HealthCollector'] += default_timer() - start router_entry.time_spent['HealthCollector'] += default_timer() - start
if router_metric.router_entry.dhcp: if router_entry.config_entry.dhcp:
start = default_timer() start = default_timer()
yield from DHCPCollector.collect(router_metric) yield from DHCPCollector.collect(router_entry)
router_metric.time_spent['DHCPCollector'] += default_timer() - start router_entry.time_spent['DHCPCollector'] += default_timer() - start
if router_metric.router_entry.pool: if router_entry.config_entry.pool:
start = default_timer() start = default_timer()
yield from PoolCollector.collect(router_metric) yield from PoolCollector.collect(router_entry)
router_metric.time_spent['PoolCollector'] += default_timer() - start router_entry.time_spent['PoolCollector'] += default_timer() - start
if router_metric.router_entry.interface: if router_entry.config_entry.interface:
start = default_timer() start = default_timer()
yield from InterfaceCollector.collect(router_metric) yield from InterfaceCollector.collect(router_entry)
router_metric.time_spent['InterfaceCollector'] += default_timer() - start router_entry.time_spent['InterfaceCollector'] += default_timer() - start
if router_metric.router_entry.firewall: if router_entry.config_entry.firewall:
start = default_timer() start = default_timer()
yield from FirewallCollector.collect(router_metric) yield from FirewallCollector.collect(router_entry)
router_metric.time_spent['FirewallCollector'] += default_timer() - start router_entry.time_spent['FirewallCollector'] += default_timer() - start
if router_metric.router_entry.monitor: if router_entry.config_entry.monitor:
start = default_timer() start = default_timer()
yield from MonitorCollector.collect(router_metric) yield from MonitorCollector.collect(router_entry)
router_metric.time_spent['MonitorCollector'] += default_timer() - start router_entry.time_spent['MonitorCollector'] += default_timer() - start
if router_metric.router_entry.route: if router_entry.config_entry.route:
start = default_timer() start = default_timer()
yield from RouteCollector.collect(router_metric) yield from RouteCollector.collect(router_entry)
router_metric.time_spent['RouteCollector'] += default_timer() - start router_entry.time_spent['RouteCollector'] += default_timer() - start
if router_metric.router_entry.wireless: if router_entry.config_entry.wireless:
start = default_timer() start = default_timer()
yield from WLANCollector.collect(router_metric) yield from WLANCollector.collect(router_entry)
router_metric.time_spent['WLANCollector'] += default_timer() - start router_entry.time_spent['WLANCollector'] += default_timer() - start
if router_metric.router_entry.capsman: if router_entry.config_entry.capsman:
start = default_timer() start = default_timer()
yield from CapsmanCollector.collect(router_metric) yield from CapsmanCollector.collect(router_entry)
router_metric.time_spent['CapsmanCollector'] += default_timer() - start router_entry.time_spent['CapsmanCollector'] += default_timer() - start
yield from MKTXPCollector.collect(router_metric) yield from MKTXPCollector.collect(router_entry)

View File

View File

@@ -0,0 +1,39 @@
# coding=utf8
## Copyright (c) 2020 Arseniy Kuznetsov
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
class BaseDSProcessor:
''' Base Metrics DataSource processing
'''
@staticmethod
def trimmed_records(router_entry, *, router_records = [], metric_labels = [], add_router_id = True, translation_table = {}):
if len(metric_labels) == 0 and len(router_records) > 0:
metric_labels = router_records[0].keys()
metric_labels = set(metric_labels)
labeled_records = []
dash2_ = lambda x : x.replace('-', '_')
for router_record in router_records:
translated_record = {dash2_(key): value for (key, value) in router_record.items() if dash2_(key) in metric_labels}
if add_router_id:
for key, value in router_entry.router_id.items():
translated_record[key] = value
# translate fields if needed
for key, func in translation_table.items():
translated_record[key] = func(translated_record.get(key))
labeled_records.append(translated_record)
return labeled_records

View File

@@ -0,0 +1,41 @@
# coding=utf8
## Copyright (c) 2020 Arseniy Kuznetsov
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
from mktxp.datasources.base_ds import BaseDSProcessor
class CapsmanCapsMetricsDataSource:
''' Caps Metrics data provider
'''
@staticmethod
def metric_records(router_entry, *, metric_labels = []):
try:
remote_caps_records = router_entry.api_connection.router_api().get_resource('/caps-man/remote-cap').get()
return BaseDSProcessor.trimmed_records(router_entry, router_records = remote_caps_records, metric_labels = metric_labels)
except Exception as exc:
print(f'Error getting caps-man remote caps info from router{router_entry.router_name}@{router_entry.config_entry.hostname}: {exc}')
return None
class CapsmanRegistrationsMetricsDataSource:
''' Capsman Registrations Metrics data provider
'''
@staticmethod
def metric_records(router_entry, *, metric_labels = [], add_router_id = True):
try:
registration_table_records = router_entry.api_connection.router_api().get_resource('/caps-man/registration-table').get()
return BaseDSProcessor.trimmed_records(router_entry, router_records = registration_table_records, metric_labels = metric_labels, add_router_id = add_router_id)
except Exception as exc:
print(f'Error getting caps-man registration table info from router{router_entry.router_name}@{router_entry.config_entry.hostname}: {exc}')
return None

View File

@@ -0,0 +1,43 @@
# coding=utf8
## Copyright (c) 2020 Arseniy Kuznetsov
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
from mktxp.datasources.base_ds import BaseDSProcessor
class DHCPMetricsDataSource:
''' DHCP Metrics data provider
'''
@staticmethod
def metric_records(router_entry, *, metric_labels = [], add_router_id = True):
try:
#dhcp_lease_records = router_entry.api_connection.router_api().get_resource('/ip/dhcp-server/lease').get(status='bound')
dhcp_lease_records = router_entry.api_connection.router_api().get_resource('/ip/dhcp-server/lease').call('print', {'active':''})
# translation rules
translation_table = {}
if 'comment' in metric_labels:
translation_table['comment'] = lambda c: c if c else ''
if 'host_name' in metric_labels:
translation_table['host_name'] = lambda c: c if c else ''
if 'expires_after' in metric_labels:
translation_table['expires_after'] = lambda c: c if c else ''
if 'active_address' in metric_labels:
translation_table['active_address'] = lambda c: c if c else ''
return BaseDSProcessor.trimmed_records(router_entry, router_records = dhcp_lease_records, metric_labels = metric_labels, add_router_id = add_router_id, translation_table = translation_table)
except Exception as exc:
print(f'Error getting dhcp info from router{router_entry.router_name}@{router_entry.config_entry.hostname}: {exc}')
return None

View File

@@ -0,0 +1,39 @@
# coding=utf8
## Copyright (c) 2020 Arseniy Kuznetsov
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
from mktxp.datasources.base_ds import BaseDSProcessor
class FirewallMetricsDataSource:
''' Firewall Metrics data provider
'''
@staticmethod
def metric_records(router_entry, *, metric_labels = [], raw = False, matching_only = True):
try:
filter_path = '/ip/firewall/filter' if not raw else '/ip/firewall/raw'
firewall_records = router_entry.api_connection.router_api().get_resource(filter_path).call('print', {'stats':'', 'all':''})
if matching_only:
firewall_records = [record for record in firewall_records if int(record.get('bytes', '0')) > 0]
# translation rules
translation_table = {}
if 'comment' in metric_labels:
translation_table['comment'] = lambda c: c if c else ''
return BaseDSProcessor.trimmed_records(router_entry, router_records = firewall_records, metric_labels = metric_labels, translation_table = translation_table)
except Exception as exc:
print(f'Error getting firewall filters info from router{router_entry.router_name}@{router_entry.config_entry.hostname}: {exc}')
return None

View File

@@ -0,0 +1,28 @@
# coding=utf8
## Copyright (c) 2020 Arseniy Kuznetsov
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
from mktxp.datasources.base_ds import BaseDSProcessor
class HealthMetricsDataSource:
''' Health Metrics data provider
'''
@staticmethod
def metric_records(router_entry, *, metric_labels = []):
try:
health_records = router_entry.api_connection.router_api().get_resource('/system/health').get()
return BaseDSProcessor.trimmed_records(router_entry, router_records = health_records, metric_labels = metric_labels)
except Exception as exc:
print(f'Error getting system health info from router{router_entry.router_name}@{router_entry.config_entry.hostname}: {exc}')
return None

View File

@@ -0,0 +1,28 @@
# coding=utf8
## Copyright (c) 2020 Arseniy Kuznetsov
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
from mktxp.datasources.base_ds import BaseDSProcessor
class IdentityMetricsDataSource:
''' Identity Metrics data provider
'''
@staticmethod
def metric_records(router_entry, *, metric_labels = []):
try:
identity_records = router_entry.api_connection.router_api().get_resource('/system/identity').get()
return BaseDSProcessor.trimmed_records(router_entry, router_records = identity_records, metric_labels = metric_labels)
except Exception as exc:
print(f'Error getting system identity info from router{router_entry.router_name}@{router_entry.config_entry.hostname}: {exc}')
return None

View File

@@ -0,0 +1,59 @@
# coding=utf8
## Copyright (c) 2020 Arseniy Kuznetsov
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
from mktxp.datasources.base_ds import BaseDSProcessor
class InterfaceTrafficMetricsDataSource:
''' Interface Traffic Metrics data provider
'''
@staticmethod
def metric_records(router_entry, *, metric_labels = []):
try:
traffic_records = router_entry.api_connection.router_api().get_resource('/interface').get(running='yes', disabled='no')
return BaseDSProcessor.trimmed_records(router_entry, router_records = traffic_records, metric_labels = metric_labels)
except Exception as exc:
print(f'Error getting interface traffic info from router{router_entry.router_name}@{router_entry.config_entry.hostname}: {exc}')
return None
class InterfaceMonitorMetricsDataSource:
''' Interface Monitor Metrics data provider
'''
@staticmethod
def metric_records(router_entry, *, metric_labels = [], kind = 'ethernet', include_comments = False, running_only = True):
try:
interfaces = router_entry.api_connection.router_api().get_resource(f'/interface/{kind}').get()
interface_names = [(interface['name'], interface.get('comment'), interface.get('running')) for interface in interfaces]
interface_monitor_records = []
for int_num, interface_name in enumerate(interface_names):
interface_monitor_record = {}
if not running_only or interface_name[2] == 'true':
interface_monitor_record = router_entry.api_connection.router_api().get_resource(f'/interface/{kind}').call('monitor', {'once':'', 'numbers':f'{int_num}'})[0]
else:
# unless explicitly requested, no need to do a monitor call for not running interfaces
interface_monitor_record = {'name': interface_name[0], 'status': 'no-link'}
if include_comments and interface_name[1]:
# combines names with comments
interface_monitor_record['name'] = interface_name[1] if router_entry.config_entry.use_comments_over_names else \
f"{interface_name[0]} ({interface_name[1]})"
interface_monitor_records.append(interface_monitor_record)
return BaseDSProcessor.trimmed_records(router_entry, router_records = interface_monitor_records, metric_labels = metric_labels)
except Exception as exc:
print(f'Error getting {kind} interface monitor info from router{router_entry.router_name}@{router_entry.config_entry.hostname}: {exc}')
return None

View File

@@ -0,0 +1,29 @@
# coding=utf8
## Copyright (c) 2020 Arseniy Kuznetsov
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
from mktxp.datasources.base_ds import BaseDSProcessor
class MKTXPMetricsDataSource:
''' MKTXP Metrics data provider
'''
@staticmethod
def metric_records(router_entry):
mktxp_records = []
for key in router_entry.time_spent.keys():
mktxp_records.append({'name': key, 'duration': router_entry.time_spent[key]})
# translation rules
translation_table = {'duration': lambda d: d*1000}
return BaseDSProcessor.trimmed_records(router_entry, router_records = mktxp_records, translation_table = translation_table)

View File

@@ -0,0 +1,41 @@
# coding=utf8
## Copyright (c) 2020 Arseniy Kuznetsov
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
from mktxp.datasources.base_ds import BaseDSProcessor
class PoolMetricsDataSource:
''' Pool Metrics data provider
'''
@staticmethod
def metric_records(router_entry, *, metric_labels = []):
try:
pool_records = router_entry.api_connection.router_api().get_resource('/ip/pool').get()
return BaseDSProcessor.trimmed_records(router_entry, router_records = pool_records, metric_labels = metric_labels)
except Exception as exc:
print(f'Error getting pool info from router{router_entry.router_name}@{router_entry.config_entry.hostname}: {exc}')
return None
class PoolUsedMetricsDataSource:
''' Pool/Used Metrics data provider
'''
@staticmethod
def metric_records(router_entry, *, metric_labels = []):
try:
pool_used_records = router_entry.api_connection.router_api().get_resource('/ip/pool/used').get()
return BaseDSProcessor.trimmed_records(router_entry, router_records = pool_used_records, metric_labels = metric_labels)
except Exception as exc:
print(f'Error getting pool used info from router{router_entry.router_name}@{router_entry.config_entry.hostname}: {exc}')
return None

View File

@@ -0,0 +1,28 @@
# coding=utf8
## Copyright (c) 2020 Arseniy Kuznetsov
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
from mktxp.datasources.base_ds import BaseDSProcessor
class RouteMetricsDataSource:
''' Routes Metrics data provider
'''
@staticmethod
def metric_records(router_entry, *, metric_labels = []):
try:
route_records = router_entry.api_connection.router_api().get_resource('/ip/route').get(active='yes')
return BaseDSProcessor.trimmed_records(router_entry, router_records = route_records, metric_labels = metric_labels)
except Exception as exc:
print(f'Error getting routes info from router{router_entry.router_name}@{router_entry.config_entry.hostname}: {exc}')
return None

View File

@@ -0,0 +1,29 @@
# coding=utf8
## Copyright (c) 2020 Arseniy Kuznetsov
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
from mktxp.datasources.base_ds import BaseDSProcessor
class RouterboardMetricsDataSource:
''' Routerboard Metrics data provider
'''
@staticmethod
def metric_records(router_entry, *, metric_labels = []):
try:
routerboard_records = router_entry.api_connection.router_api().get_resource('/system/routerboard').get()
return BaseDSProcessor.trimmed_records(router_entry, router_records = routerboard_records, metric_labels = metric_labels)
except Exception as exc:
print(f'Error getting system routerboard info from router{router_entry.router_name}@{router_entry.config_entry.hostname}: {exc}')
return None

View File

@@ -0,0 +1,28 @@
# coding=utf8
## Copyright (c) 2020 Arseniy Kuznetsov
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
from mktxp.datasources.base_ds import BaseDSProcessor
class SystemResourceMetricsDataSource:
''' System Resource Metrics data provider
'''
@staticmethod
def metric_records(router_entry, *, metric_labels = []):
try:
system_resource_records = router_entry.api_connection.router_api().get_resource('/system/resource').get()
return BaseDSProcessor.trimmed_records(router_entry, router_records = system_resource_records, metric_labels = metric_labels)
except Exception as exc:
print(f'Error getting system resource info from router{router_entry.router_name}@{router_entry.config_entry.hostname}: {exc}')
return None

View File

@@ -0,0 +1,30 @@
# coding=utf8
## Copyright (c) 2020 Arseniy Kuznetsov
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
from mktxp.datasources.base_ds import BaseDSProcessor
class WirelessMetricsDataSource:
''' Wireless Metrics data provider
'''
@staticmethod
def metric_records(router_entry, *, metric_labels = [], add_router_id = True):
try:
registration_table_records = router_entry.api_connection.router_api().get_resource('/interface/wireless/registration-table').get()
return BaseDSProcessor.trimmed_records(router_entry, router_records = registration_table_records, metric_labels = metric_labels, add_router_id = add_router_id)
except Exception as exc:
print(f'Error getting wireless registration table info from router{router_entry.router_name}@{router_entry.config_entry.hostname}: {exc}')
return None

View File

@@ -11,12 +11,14 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details. ## GNU General Public License for more details.
import ssl import ssl
import socket import socket
from datetime import datetime from datetime import datetime
from routeros_api import RouterOsApiPool from routeros_api import RouterOsApiPool
from mktxp.cli.config.config import config_handler from mktxp.cli.config.config import config_handler
class RouterAPIConnectionError(Exception): class RouterAPIConnectionError(Exception):
pass pass
@@ -24,24 +26,24 @@ class RouterAPIConnectionError(Exception):
class RouterAPIConnection: class RouterAPIConnection:
''' Base wrapper interface for the routeros_api library ''' Base wrapper interface for the routeros_api library
''' '''
def __init__(self, router_name, router_entry): def __init__(self, router_name, config_entry):
self.router_name = router_name self.router_name = router_name
self.router_entry = router_entry self.config_entry = config_entry
self.last_failure_timestamp = self.successive_failure_count = 0 self.last_failure_timestamp = self.successive_failure_count = 0
ctx = None ctx = None
if self.router_entry.use_ssl and self.router_entry.no_ssl_certificate: if self.config_entry.use_ssl and self.config_entry.no_ssl_certificate:
ctx = ssl.create_default_context() ctx = ssl.create_default_context()
ctx.set_ciphers('ADH:@SECLEVEL=0') ctx.set_ciphers('ADH:@SECLEVEL=0')
self.connection = RouterOsApiPool( self.connection = RouterOsApiPool(
host = self.router_entry.hostname, host = self.config_entry.hostname,
username = self.router_entry.username, username = self.config_entry.username,
password = self.router_entry.password, password = self.config_entry.password,
port = self.router_entry.port, port = self.config_entry.port,
plaintext_login = True, plaintext_login = True,
use_ssl = self.router_entry.use_ssl, use_ssl = self.config_entry.use_ssl,
ssl_verify = self.router_entry.ssl_certificate_verify, ssl_verify = self.config_entry.ssl_certificate_verify,
ssl_context = ctx) ssl_context = ctx)
self.connection.socket_timeout = config_handler._entry().socket_timeout self.connection.socket_timeout = config_handler._entry().socket_timeout
@@ -62,7 +64,7 @@ class RouterAPIConnection:
if self.is_connected() or self._in_connect_timeout(connect_time.timestamp()): if self.is_connected() or self._in_connect_timeout(connect_time.timestamp()):
return return
try: try:
print(f'Connecting to router {self.router_name}@{self.router_entry.hostname}') print(f'Connecting to router {self.router_name}@{self.config_entry.hostname}')
self.api = self.connection.get_api() self.api = self.connection.get_api()
self._set_connect_state(success = True, connect_time = connect_time) self._set_connect_state(success = True, connect_time = connect_time)
except (socket.error, socket.timeout, Exception) as exc: except (socket.error, socket.timeout, Exception) as exc:
@@ -78,11 +80,11 @@ class RouterAPIConnection:
connect_delay = self._connect_delay() connect_delay = self._connect_delay()
if (connect_timestamp - self.last_failure_timestamp) < connect_delay: if (connect_timestamp - self.last_failure_timestamp) < connect_delay:
if not quiet: 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'{self.router_name}@{self.config_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}') print(f'Successive failure count: {self.successive_failure_count}')
return True return True
if not quiet: if not quiet:
print(f'{self.router_name}@{self.router_entry.hostname}: OK to connect') print(f'{self.router_name}@{self.config_entry.hostname}: OK to connect')
if self.last_failure_timestamp > 0: if self.last_failure_timestamp > 0:
print(f'Seconds since last failure: {connect_timestamp - self.last_failure_timestamp}') print(f'Seconds since last failure: {connect_timestamp - self.last_failure_timestamp}')
print(f'Prior successive failure count: {self.successive_failure_count}') print(f'Prior successive failure count: {self.successive_failure_count}')
@@ -98,12 +100,12 @@ class RouterAPIConnection:
if success: if success:
self.last_failure_timestamp = 0 self.last_failure_timestamp = 0
self.successive_failure_count = 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') print(f'{connect_time.strftime("%Y-%m-%d %H:%M:%S")} Connection to router {self.router_name}@{self.config_entry.hostname} has been established')
else: else:
self.api = None self.api = None
self.successive_failure_count += 1 self.successive_failure_count += 1
self.last_failure_timestamp = connect_time.timestamp() 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}') print(f'{connect_time.strftime("%Y-%m-%d %H:%M:%S")} Connection to router {self.router_name}@{self.config_entry.hostname} has failed: {exc}')

View File

@@ -13,28 +13,28 @@
from mktxp.cli.config.config import config_handler from mktxp.cli.config.config import config_handler
from mktxp.router_metric import RouterMetric from mktxp.router_entry import RouterEntry
class RouterMetricsHandler: class RouterEntriesHandler:
''' Handles RouterOS entries defined in MKTXP config ''' Handles RouterOS entries defined in MKTXP config
''' '''
def __init__(self): def __init__(self):
self.router_metrics = [] self.router_entries = []
for router_name in config_handler.registered_entries(): for router_name in config_handler.registered_entries():
entry = config_handler.entry(router_name) entry = config_handler.entry(router_name)
if entry.enabled: if entry.enabled:
self.router_metrics.append(RouterMetric(router_name)) self.router_entries.append(RouterEntry(router_name))
@staticmethod @staticmethod
def router_metric(entry_name, enabled_only = False): def router_entry(entry_name, enabled_only = False):
router_metric = None router_entry = None
for router_name in config_handler.registered_entries(): for router_name in config_handler.registered_entries():
if router_name == entry_name: if router_name == entry_name:
if enabled_only: if enabled_only:
entry = config_handler.entry(router_name) entry = config_handler.entry(router_name)
if not entry.enabled: if not entry.enabled:
break break
router_metric = RouterMetric(router_name) router_entry = RouterEntry(router_name)
break break
return router_metric return router_entry

41
mktxp/router_entry.py Normal file
View File

@@ -0,0 +1,41 @@
# coding=utf8
## Copyright (c) 2020 Arseniy Kuznetsov
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
from mktxp.cli.config.config import config_handler, MKTXPConfigKeys
from mktxp.router_connection import RouterAPIConnection
class RouterEntry:
''' RouterOS Entry
'''
def __init__(self, router_name):
self.router_name = router_name
self.config_entry = config_handler.entry(router_name)
self.api_connection = RouterAPIConnection(router_name, self.config_entry)
self.router_id = {
MKTXPConfigKeys.ROUTERBOARD_NAME: self.router_name,
MKTXPConfigKeys.ROUTERBOARD_ADDRESS: self.config_entry.hostname
}
self.time_spent = { 'IdentityCollector': 0,
'SystemResourceCollector': 0,
'HealthCollector': 0,
'DHCPCollector': 0,
'PoolCollector': 0,
'InterfaceCollector': 0,
'FirewallCollector': 0,
'MonitorCollector': 0,
'RouteCollector': 0,
'WLANCollector': 0,
'CapsmanCollector': 0
}

View File

@@ -1,201 +0,0 @@
# coding=utf8
## Copyright (c) 2020 Arseniy Kuznetsov
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
from mktxp.cli.config.config import config_handler, MKTXPConfigKeys
from mktxp.router_connection import RouterAPIConnection
class RouterMetric:
''' RouterOS Metrics data provider
'''
def __init__(self, router_name):
self.router_name = router_name
self.router_entry = config_handler.entry(router_name)
self.api_connection = RouterAPIConnection(router_name, self.router_entry)
self.router_id = {
MKTXPConfigKeys.ROUTERBOARD_NAME: self.router_name,
MKTXPConfigKeys.ROUTERBOARD_ADDRESS: self.router_entry.hostname
}
self.time_spent = { 'IdentityCollector': 0,
'SystemResourceCollector': 0,
'HealthCollector': 0,
'DHCPCollector': 0,
'PoolCollector': 0,
'InterfaceCollector': 0,
'FirewallCollector': 0,
'MonitorCollector': 0,
'RouteCollector': 0,
'WLANCollector': 0,
'CapsmanCollector': 0
}
def identity_records(self, identity_labels = []):
try:
identity_records = self.api_connection.router_api().get_resource('/system/identity').get()
return self._trimmed_records(identity_records, identity_labels)
except Exception as exc:
print(f'Error getting system identity info from router{self.router_name}@{self.router_entry.hostname}: {exc}')
return None
def routerboard_records(self, routerboard_labels = []):
try:
routerboard_records = self.api_connection.router_api().get_resource('/system/routerboard').get()
return self._trimmed_records(routerboard_records, routerboard_labels)
except Exception as exc:
print(f'Error getting system routerboard info from router{self.router_name}@{self.router_entry.hostname}: {exc}')
return None
def health_records(self, health_labels = []):
try:
health_records = self.api_connection.router_api().get_resource('/system/health').get()
return self._trimmed_records(health_records, health_labels)
except Exception as exc:
print(f'Error getting system health info from router{self.router_name}@{self.router_entry.hostname}: {exc}')
return None
def system_resource_records(self, resource_labels = []):
try:
system_resource_records = self.api_connection.router_api().get_resource('/system/resource').get()
return self._trimmed_records(system_resource_records, resource_labels)
except Exception as exc:
print(f'Error getting system resource info from router{self.router_name}@{self.router_entry.hostname}: {exc}')
return None
def dhcp_lease_records(self, dhcp_lease_labels = [], add_router_id = True):
try:
#dhcp_lease_records = self.api_connection.router_api().get_resource('/ip/dhcp-server/lease').get(status='bound')
dhcp_lease_records = self.api_connection.router_api().get_resource('/ip/dhcp-server/lease').call('print', {'active':''})
return self._trimmed_records(dhcp_lease_records, dhcp_lease_labels, add_router_id)
except Exception as exc:
print(f'Error getting dhcp info from router{self.router_name}@{self.router_entry.hostname}: {exc}')
return None
def interface_traffic_records(self, interface_traffic_labels = []):
try:
traffic_records = self.api_connection.router_api().get_resource('/interface').get(running='yes', disabled='no')
return self._trimmed_records(traffic_records, interface_traffic_labels)
except Exception as exc:
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', include_comments = False):
try:
interfaces = self.api_connection.router_api().get_resource(f'/interface/{kind}').get()
interface_names = [(interface['name'], interface.get('comment'), interface.get('running')) 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'] = interface_name[1] if self.router_entry.use_comments_over_names else \
f"{interface_name[0]} ({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}')
return None
def pool_records(self, pool_labels = []):
try:
pool_records = self.api_connection.router_api().get_resource('/ip/pool').get()
return self._trimmed_records(pool_records, pool_labels)
except Exception as exc:
print(f'Error getting pool info from router{self.router_name}@{self.router_entry.hostname}: {exc}')
return None
def pool_used_records(self, pool_used_labels = []):
try:
pool_used_records = self.api_connection.router_api().get_resource('/ip/pool/used').get()
return self._trimmed_records(pool_used_records, pool_used_labels)
except Exception as exc:
print(f'Error getting pool used info from router{self.router_name}@{self.router_entry.hostname}: {exc}')
return None
def route_records(self, route_labels = []):
try:
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 routes info from router{self.router_name}@{self.router_entry.hostname}: {exc}')
return None
def wireless_registration_table_records(self, registration_table_labels = [], add_router_id = True):
try:
registration_table_records = self.api_connection.router_api().get_resource('/interface/wireless/registration-table').get()
return self._trimmed_records(registration_table_records, registration_table_labels, add_router_id)
except Exception as exc:
print(f'Error getting wireless registration table info from router{self.router_name}@{self.router_entry.hostname}: {exc}')
return None
def capsman_remote_caps_records(self, remote_caps_labels = []):
try:
remote_caps_records = self.api_connection.router_api().get_resource('/caps-man/remote-cap').get()
return self._trimmed_records(remote_caps_records, remote_caps_labels)
except Exception as exc:
print(f'Error getting caps-man remote caps info from router{self.router_name}@{self.router_entry.hostname}: {exc}')
return None
def capsman_registration_table_records(self, registration_table_labels = [], add_router_id = True):
try:
registration_table_records = self.api_connection.router_api().get_resource('/caps-man/registration-table').get()
return self._trimmed_records(registration_table_records, registration_table_labels, add_router_id)
except Exception as exc:
print(f'Error getting caps-man registration table info from router{self.router_name}@{self.router_entry.hostname}: {exc}')
return None
def firewall_records(self, firewall_labels = [], raw = False, matching_only = True):
try:
filter_path = '/ip/firewall/filter' if not raw else '/ip/firewall/raw'
firewall_records = self.api_connection.router_api().get_resource(filter_path).call('print', {'stats':'', 'all':''})
if matching_only:
firewall_records = [record for record in firewall_records if int(record.get('bytes', '0')) > 0]
# translation rules
translation_table = {}
# if 'id' in firewall_labels:
# translation_table['id'] = lambda id: str(int(id[1:], 16) - 1)
if 'comment' in firewall_labels:
translation_table['comment'] = lambda c: c if c else ''
return self._trimmed_records(firewall_records, firewall_labels, translation_table = translation_table)
except Exception as exc:
print(f'Error getting firewall filters info from router{self.router_name}@{self.router_entry.hostname}: {exc}')
return None
def mktxp_records(self):
mktxp_records = []
for key in self.time_spent.keys():
mktxp_records.append({'name': key, 'duration': self.time_spent[key]})
# translation rules
translation_table = {'duration': lambda d: d*1000}
return self._trimmed_records(mktxp_records, translation_table = translation_table)
# Helpers
def _trimmed_records(self, router_records, metric_labels = [], add_router_id = True, translation_table = {}):
if len(metric_labels) == 0 and len(router_records) > 0:
metric_labels = router_records[0].keys()
metric_labels = set(metric_labels)
labeled_records = []
dash2_ = lambda x : x.replace('-', '_')
for router_record in router_records:
translated_record = {dash2_(key): value for (key, value) in router_record.items() if dash2_(key) in metric_labels}
if add_router_id:
for key, value in self.router_id.items():
translated_record[key] = value
# translate fields if needed
for key, func in translation_table.items():
translated_record[key] = func(translated_record.get(key))
labeled_records.append(translated_record)
return labeled_records

View File

@@ -11,6 +11,7 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details. ## GNU General Public License for more details.
import os, sys, shlex, tempfile, shutil, re import os, sys, shlex, tempfile, shutil, re
import subprocess, hashlib import subprocess, hashlib
from timeit import default_timer from timeit import default_timer

View File

@@ -20,7 +20,7 @@ with open(path.join(pkg_dir, 'README.md'), encoding='utf-8') as f:
setup( setup(
name='mktxp', name='mktxp',
version='0.23', version='0.24',
url='https://github.com/akpw/mktxp', url='https://github.com/akpw/mktxp',