Merge pull request #82 from M0r13n/main

Adds new metric: system_update_available
This commit is contained in:
Arseniy Kuznetsov
2023-06-30 10:06:48 +01:00
committed by GitHub
4 changed files with 97 additions and 5 deletions

View File

@@ -88,6 +88,8 @@ class MKTXPConfigKeys:
FE_QUEUE_KEY = 'queue'
FE_REMOTE_DHCP_ENTRY = 'remote_dhcp_entry'
FE_CHECK_FOR_UPDATES = 'check_for_updates'
MKTXP_SOCKET_TIMEOUT = 'socket_timeout'
MKTXP_INITIAL_DELAY = 'initial_delay_on_failure'
MKTXP_MAX_DELAY = 'max_delay_on_failure'
@@ -127,7 +129,7 @@ class MKTXPConfigKeys:
DEFAULT_MKTXP_TOTAL_MAX_SCRAPE_DURATION = 30
BOOLEAN_KEYS_NO = {ENABLED_KEY, SSL_KEY, NO_SSL_CERTIFICATE,
BOOLEAN_KEYS_NO = {ENABLED_KEY, SSL_KEY, NO_SSL_CERTIFICATE, FE_CHECK_FOR_UPDATES,
SSL_CERTIFICATE_VERIFY, FE_IPV6_FIREWALL_KEY, FE_IPV6_NEIGHBOR_KEY, FE_CONNECTION_STATS_KEY}
# Feature keys enabled by default
@@ -157,7 +159,7 @@ class ConfigEntry:
MKTXPConfigKeys.FE_FIREWALL_KEY, MKTXPConfigKeys.FE_MONITOR_KEY, MKTXPConfigKeys.FE_ROUTE_KEY, MKTXPConfigKeys.FE_WIRELESS_KEY, MKTXPConfigKeys.FE_WIRELESS_CLIENTS_KEY,
MKTXPConfigKeys.FE_IP_CONNECTIONS_KEY, MKTXPConfigKeys.FE_CONNECTION_STATS_KEY, MKTXPConfigKeys.FE_CAPSMAN_KEY, MKTXPConfigKeys.FE_CAPSMAN_CLIENTS_KEY, MKTXPConfigKeys.FE_POE_KEY, MKTXPConfigKeys.FE_NETWATCH_KEY,
MKTXPConfigKeys.MKTXP_USE_COMMENTS_OVER_NAMES, MKTXPConfigKeys.FE_PUBLIC_IP_KEY, MKTXPConfigKeys.FE_IPV6_FIREWALL_KEY, MKTXPConfigKeys.FE_IPV6_NEIGHBOR_KEY,
MKTXPConfigKeys.FE_USER_KEY, MKTXPConfigKeys.FE_QUEUE_KEY, MKTXPConfigKeys.FE_REMOTE_DHCP_ENTRY
MKTXPConfigKeys.FE_USER_KEY, MKTXPConfigKeys.FE_QUEUE_KEY, MKTXPConfigKeys.FE_REMOTE_DHCP_ENTRY, MKTXPConfigKeys.FE_CHECK_FOR_UPDATES
])
MKTXPSystemEntry = namedtuple('MKTXPSystemEntry', [MKTXPConfigKeys.PORT_KEY, MKTXPConfigKeys.MKTXP_SOCKET_TIMEOUT,
MKTXPConfigKeys.MKTXP_INITIAL_DELAY, MKTXPConfigKeys.MKTXP_MAX_DELAY,

View File

@@ -53,4 +53,6 @@
remote_dhcp_entry = None # An MKTXP entry for remote DHCP info resolution (capsman/wireless)
use_comments_over_names = True # when available, forces using comments over the interfaces names
use_comments_over_names = True # when available, forces using comments over the interfaces names
check_for_updates = False # check for available ROS updates

View File

@@ -15,6 +15,7 @@
from mktxp.collector.base_collector import BaseCollector
from mktxp.flow.processor.output import BaseOutputProcessor
from mktxp.datasource.system_resource_ds import SystemResourceMetricsDataSource
from mktxp.utils.utils import check_for_updates
class SystemResourceCollector(BaseCollector):
@@ -61,6 +62,16 @@ class SystemResourceCollector(BaseCollector):
cpu_frequency_metrics = BaseCollector.gauge_collector('system_cpu_frequency', 'Current CPU frequency', resource_records, 'cpu_frequency', ['version', 'board_name', 'cpu', 'architecture_name'])
yield cpu_frequency_metrics
# Check for updates
if router_entry.config_entry.check_for_updates:
for record in resource_records:
cur_version, newest_version = check_for_updates(record['version'])
record['newest_version'] = str(newest_version)
record['update_available'] = cur_version < newest_version
update_available_metrics = BaseCollector.gauge_collector('system_update_available', 'Is there a newer version available', resource_records, 'update_available', ['newest_version',])
yield update_available_metrics
# Helpers
@staticmethod

View File

@@ -11,14 +11,18 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
from functools import lru_cache
import os, sys, shlex, tempfile, shutil, re
import subprocess, hashlib
import subprocess, hashlib, urllib
import time
from timeit import default_timer
from collections.abc import Iterable
import xml.etree.ElementTree as ET
from contextlib import contextmanager
from multiprocessing import Process, Event
from datetime import timedelta
from pkg_resources import packaging
''' Utilities / Helpers
@@ -270,3 +274,76 @@ class Benchmark:
self.time = default_timer() - self.start
# mapping of channels to RSS feeds
CHANNEL_RSS_FEED_MAPPING = {
'development': 'https://mikrotik.com/development.rss',
'long-term': 'https://mikrotik.com/bugfix.rss',
'stable': 'https://mikrotik.com/current.rss',
'testing': 'https://mikrotik.com/candidate.rss',
}
def get_ttl_hash(seconds=3600):
"""Return the same value withing `seconds` time period"""
return round(time.time() / seconds)
@lru_cache(maxsize=5)
def get_available_updates(channel, ttl_hash=get_ttl_hash()):
"""Check the RSS feed for available updates for a given update channel.
This method fetches the RSS feed and returns all version from the parsed XML.
Version numbers are parsed into version.Version instances (part of setuptools)."""
del ttl_hash
rss_feed = CHANNEL_RSS_FEED_MAPPING[channel]
print(f'Fetching available ROS releases from {rss_feed}')
versions = []
with urllib.request.urlopen(rss_feed) as response:
result = response.read()
root = ET.fromstring(result)
channel = root[0]
for child in channel:
# iterate over all updates
if child.tag == 'item':
title, _, _, _, _, _ = child
# extract and parse the version number from title
version_text = re.findall(r'[\d+\.]+', title.text)[0]
version_number = packaging.version.parse(version_text)
versions.append(version_number)
return versions
def parse_ros_version(string):
"""Parse the version returned from the /system/resource command.
Returns a tuple: (<version>, <channel>).
>>> parse_ros_version('1.2.3 (stable)')
1.2.3, stable
"""
version, channel = re.findall(r'([\d\.]+).*?([\w]+)', string)[0]
return packaging.version.parse(version), channel
def check_for_updates(cur_version):
"""Try to check if there is a newer version available.
If anything goes wrong, it returns the same version.
Returns a tuple: (<current version>, <newest version>)"""
error = False
try:
cur_version, channel = parse_ros_version(cur_version)
available_versions = get_available_updates(channel)
newest_version = sorted(available_versions)[-1]
except KeyError:
print(f'unknown update channel {channel}')
error = True
except urllib.error.HTTPError as err:
print(f'update feed returned: {str(err)}')
error = True
except Exception as err:
print(f'could not check for updates, because: {str(err)}')
error = True
if error:
return cur_version, cur_version
return cur_version, newest_version