diff options
Diffstat (limited to 'src')
-rwxr-xr-x | src/conf_mode/service_monitoring_frr-exporter.py | 101 | ||||
-rwxr-xr-x | src/conf_mode/service_monitoring_node-exporter.py | 101 | ||||
-rwxr-xr-x | src/conf_mode/service_monitoring_prometheus.py | 145 | ||||
-rw-r--r-- | src/migration-scripts/monitoring/1-to-2 | 50 | ||||
-rwxr-xr-x | src/op_mode/dhcp.py | 58 |
5 files changed, 234 insertions, 221 deletions
diff --git a/src/conf_mode/service_monitoring_frr-exporter.py b/src/conf_mode/service_monitoring_frr-exporter.py deleted file mode 100755 index 01527d579..000000000 --- a/src/conf_mode/service_monitoring_frr-exporter.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2024 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# 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. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -import os - -from sys import exit - -from vyos.config import Config -from vyos.configdict import is_node_changed -from vyos.configverify import verify_vrf -from vyos.template import render -from vyos.utils.process import call -from vyos import ConfigError -from vyos import airbag - - -airbag.enable() - -service_file = '/etc/systemd/system/frr_exporter.service' -systemd_service = 'frr_exporter.service' - - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - base = ['service', 'monitoring', 'frr-exporter'] - if not conf.exists(base): - return None - - config_data = conf.get_config_dict( - base, key_mangling=('-', '_'), get_first_key=True - ) - config_data = conf.merge_defaults(config_data, recursive=True) - - tmp = is_node_changed(conf, base + ['vrf']) - if tmp: - config_data.update({'restart_required': {}}) - - return config_data - - -def verify(config_data): - # bail out early - looks like removal from running config - if not config_data: - return None - - verify_vrf(config_data) - return None - - -def generate(config_data): - if not config_data: - # Delete systemd files - if os.path.isfile(service_file): - os.unlink(service_file) - return None - - # Render frr_exporter service_file - render(service_file, 'frr_exporter/frr_exporter.service.j2', config_data) - return None - - -def apply(config_data): - # Reload systemd manager configuration - call('systemctl daemon-reload') - if not config_data: - call(f'systemctl stop {systemd_service}') - return - - # we need to restart the service if e.g. the VRF name changed - systemd_action = 'reload-or-restart' - if 'restart_required' in config_data: - systemd_action = 'restart' - - call(f'systemctl {systemd_action} {systemd_service}') - - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/service_monitoring_node-exporter.py b/src/conf_mode/service_monitoring_node-exporter.py deleted file mode 100755 index db34bb5d0..000000000 --- a/src/conf_mode/service_monitoring_node-exporter.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2024 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# 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. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -import os - -from sys import exit - -from vyos.config import Config -from vyos.configdict import is_node_changed -from vyos.configverify import verify_vrf -from vyos.template import render -from vyos.utils.process import call -from vyos import ConfigError -from vyos import airbag - - -airbag.enable() - -service_file = '/etc/systemd/system/node_exporter.service' -systemd_service = 'node_exporter.service' - - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - base = ['service', 'monitoring', 'node-exporter'] - if not conf.exists(base): - return None - - config_data = conf.get_config_dict( - base, key_mangling=('-', '_'), get_first_key=True - ) - config_data = conf.merge_defaults(config_data, recursive=True) - - tmp = is_node_changed(conf, base + ['vrf']) - if tmp: - config_data.update({'restart_required': {}}) - - return config_data - - -def verify(config_data): - # bail out early - looks like removal from running config - if not config_data: - return None - - verify_vrf(config_data) - return None - - -def generate(config_data): - if not config_data: - # Delete systemd files - if os.path.isfile(service_file): - os.unlink(service_file) - return None - - # Render node_exporter service_file - render(service_file, 'node_exporter/node_exporter.service.j2', config_data) - return None - - -def apply(config_data): - # Reload systemd manager configuration - call('systemctl daemon-reload') - if not config_data: - call(f'systemctl stop {systemd_service}') - return - - # we need to restart the service if e.g. the VRF name changed - systemd_action = 'reload-or-restart' - if 'restart_required' in config_data: - systemd_action = 'restart' - - call(f'systemctl {systemd_action} {systemd_service}') - - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/service_monitoring_prometheus.py b/src/conf_mode/service_monitoring_prometheus.py new file mode 100755 index 000000000..e0a9fc4ef --- /dev/null +++ b/src/conf_mode/service_monitoring_prometheus.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# 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. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos.configdict import is_node_changed +from vyos.configverify import verify_vrf +from vyos.template import render +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag + + +airbag.enable() + +node_exporter_service_file = '/etc/systemd/system/node_exporter.service' +node_exporter_systemd_service = 'node_exporter.service' + +frr_exporter_service_file = '/etc/systemd/system/frr_exporter.service' +frr_exporter_systemd_service = 'frr_exporter.service' + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['service', 'monitoring', 'prometheus'] + if not conf.exists(base): + return None + + monitoring = conf.get_config_dict( + base, key_mangling=('-', '_'), get_first_key=True, with_recursive_defaults=True + ) + + tmp = is_node_changed(conf, base + ['node-exporter', 'vrf']) + if tmp: + monitoring.update({'node_exporter_restart_required': {}}) + + tmp = is_node_changed(conf, base + ['frr-exporter', 'vrf']) + if tmp: + monitoring.update({'frr_exporter_restart_required': {}}) + + return monitoring + + +def verify(monitoring): + if not monitoring: + return None + + if 'node_exporter' in monitoring: + verify_vrf(monitoring['node_exporter']) + + if 'frr_exporter' in monitoring: + verify_vrf(monitoring['frr_exporter']) + + return None + + +def generate(monitoring): + if not monitoring or 'node_exporter' not in monitoring: + # Delete systemd files + if os.path.isfile(node_exporter_service_file): + os.unlink(node_exporter_service_file) + + if not monitoring or 'frr_exporter' not in monitoring: + # Delete systemd files + if os.path.isfile(frr_exporter_service_file): + os.unlink(frr_exporter_service_file) + + if not monitoring: + return None + + if 'node_exporter' in monitoring: + # Render node_exporter node_exporter_service_file + render( + node_exporter_service_file, + 'prometheus/node_exporter.service.j2', + monitoring['node_exporter'], + ) + + if 'frr_exporter' in monitoring: + # Render frr_exporter service_file + render( + frr_exporter_service_file, + 'prometheus/frr_exporter.service.j2', + monitoring['frr_exporter'], + ) + + return None + + +def apply(monitoring): + # Reload systemd manager configuration + call('systemctl daemon-reload') + if not monitoring or 'node_exporter' not in monitoring: + call(f'systemctl stop {node_exporter_systemd_service}') + if not monitoring or 'frr_exporter' not in monitoring: + call(f'systemctl stop {frr_exporter_systemd_service}') + + if not monitoring: + return + + if 'node_exporter' in monitoring: + # we need to restart the service if e.g. the VRF name changed + systemd_action = 'reload-or-restart' + if 'node_exporter_restart_required' in monitoring: + systemd_action = 'restart' + + call(f'systemctl {systemd_action} {node_exporter_systemd_service}') + + if 'frr_exporter' in monitoring: + # we need to restart the service if e.g. the VRF name changed + systemd_action = 'reload-or-restart' + if 'frr_exporter_restart_required' in monitoring: + systemd_action = 'restart' + + call(f'systemctl {systemd_action} {frr_exporter_systemd_service}') + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/migration-scripts/monitoring/1-to-2 b/src/migration-scripts/monitoring/1-to-2 new file mode 100644 index 000000000..8bdaebae9 --- /dev/null +++ b/src/migration-scripts/monitoring/1-to-2 @@ -0,0 +1,50 @@ +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T6953: merge node and frr exporter under prometheus section + +from vyos.configtree import ConfigTree + +old_base = ['service', 'monitoring'] +new_base = ['service', 'monitoring', 'prometheus'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(old_base): + # Nothing to do + return + + if config.exists(old_base + ['node-exporter']): + if config.exists(old_base + ['node-exporter', 'listen-address']): + tmp = config.return_value(old_base + ['node-exporter', 'listen-address']) + config.set(new_base + ['node-exporter', 'listen-address'], value=tmp) + if config.exists(old_base + ['node-exporter', 'port']): + tmp = config.return_value(old_base + ['node-exporter', 'port']) + config.set(new_base + ['node-exporter', 'port'], value=tmp) + if config.exists(old_base + ['node-exporter', 'vrf']): + tmp = config.return_value(old_base + ['node-exporter', 'vrf']) + config.set(new_base + ['node-exporter', 'vrf'], value=tmp) + config.delete(old_base + ['node-exporter']) + + if config.exists(old_base + ['frr-exporter']): + if config.exists(old_base + ['frr-exporter', 'listen-address']): + tmp = config.return_value(old_base + ['frr-exporter', 'listen-address']) + config.set(new_base + ['frr-exporter', 'listen-address'], value=tmp) + if config.exists(old_base + ['frr-exporter', 'port']): + tmp = config.return_value(old_base + ['frr-exporter', 'port']) + config.set(new_base + ['frr-exporter', 'port'], value=tmp) + if config.exists(old_base + ['frr-exporter', 'vrf']): + tmp = config.return_value(old_base + ['frr-exporter', 'vrf']) + config.set(new_base + ['frr-exporter', 'vrf'], value=tmp) + config.delete(old_base + ['frr-exporter']) diff --git a/src/op_mode/dhcp.py b/src/op_mode/dhcp.py index e5455c8af..1429fd7b1 100755 --- a/src/op_mode/dhcp.py +++ b/src/op_mode/dhcp.py @@ -19,6 +19,7 @@ import sys import typing from datetime import datetime +from datetime import timezone from glob import glob from ipaddress import ip_address from tabulate import tabulate @@ -62,7 +63,7 @@ def _format_hex_string(in_str): return out_str -def _find_list_of_dict_index(lst, key='ip', value='') -> int: +def _find_list_of_dict_index(lst, key='ip', value=''): """ Find the index entry of list of dict matching the dict value Exampe: @@ -82,7 +83,7 @@ def _get_raw_server_leases(family='inet', pool=None, sorted=None, state=[], orig inet_suffix = '6' if family == 'inet6' else '4' try: leases = kea_get_leases(inet_suffix) - except: + except Exception: raise vyos.opmode.DataUnavailable('Cannot fetch DHCP server lease information') if pool is None: @@ -92,7 +93,7 @@ def _get_raw_server_leases(family='inet', pool=None, sorted=None, state=[], orig try: active_config = kea_get_active_config(inet_suffix) - except: + except Exception: raise vyos.opmode.DataUnavailable('Cannot fetch DHCP server configuration') data = [] @@ -110,11 +111,14 @@ def _get_raw_server_leases(family='inet', pool=None, sorted=None, state=[], orig data_lease['pool'] = kea_get_pool_from_subnet_id(active_config, inet_suffix, lease['subnet-id']) if active_config else '-' data_lease['end'] = lease['expire_timestamp'].timestamp() if lease['expire_timestamp'] else None data_lease['origin'] = 'local' # TODO: Determine remote in HA + data_lease['hostname'] = lease.get('hostname', '-') + # remove trailing dot to ensure consistency for `vyos-hostsd-client` + if data_lease['hostname'][-1] == '.': + data_lease['hostname'] = data_lease['hostname'][:-1] if family == 'inet': data_lease['mac'] = lease['hw-address'] data_lease['start'] = lease['start_timestamp'].timestamp() - data_lease['hostname'] = lease['hostname'] if family == 'inet6': data_lease['last_communication'] = lease['start_timestamp'].timestamp() @@ -128,12 +132,12 @@ def _get_raw_server_leases(family='inet', pool=None, sorted=None, state=[], orig data_lease['remaining'] = '-' if lease['valid-lft'] > 0: - data_lease['remaining'] = lease['expire_timestamp'] - datetime.utcnow() + data_lease['remaining'] = lease['expire_timestamp'] - datetime.now(timezone.utc) if data_lease['remaining'].days >= 0: # substraction gives us a timedelta object which can't be formatted with strftime # so we use str(), split gets rid of the microseconds - data_lease['remaining'] = str(data_lease["remaining"]).split('.')[0] + data_lease['remaining'] = str(data_lease['remaining']).split('.')[0] # Do not add old leases if data_lease['remaining'] != '' and data_lease['pool'] in pool and data_lease['state'] != 'free': @@ -148,7 +152,8 @@ def _get_raw_server_leases(family='inet', pool=None, sorted=None, state=[], orig checked.append(addr) else: idx = _find_list_of_dict_index(data, key='ip', value=addr) - data.pop(idx) + if idx is not None: + data.pop(idx) if sorted: if sorted == 'ip': @@ -279,22 +284,36 @@ def _get_raw_server_static_mappings(family='inet', pool=None, sorted=None): if sorted: if sorted == 'ip': - data.sort(key = lambda x:ip_address(x['ip-address'])) + if family == 'inet6': + mappings.sort(key = lambda x:ip_address(x['ipv6-address'])) + else: + mappings.sort(key = lambda x:ip_address(x['ip-address'])) else: - data.sort(key = lambda x:x[sorted]) + mappings.sort(key = lambda x:x[sorted]) return mappings def _get_formatted_server_static_mappings(raw_data, family='inet'): data_entries = [] - for entry in raw_data: - pool = entry.get('pool') - subnet = entry.get('subnet') - name = entry.get('name') - ip_addr = entry.get('ip-address', 'N/A') - mac_addr = entry.get('mac', 'N/A') - duid = entry.get('duid', 'N/A') - description = entry.get('description', 'N/A') - data_entries.append([pool, subnet, name, ip_addr, mac_addr, duid, description]) + if family == 'inet': + for entry in raw_data: + pool = entry.get('pool') + subnet = entry.get('subnet') + name = entry.get('name') + ip_addr = entry.get('ip-address', 'N/A') + mac_addr = entry.get('mac', 'N/A') + duid = entry.get('duid', 'N/A') + description = entry.get('description', 'N/A') + data_entries.append([pool, subnet, name, ip_addr, mac_addr, duid, description]) + elif family == 'inet6': + for entry in raw_data: + pool = entry.get('pool') + subnet = entry.get('subnet') + name = entry.get('name') + ip_addr = entry.get('ipv6-address', 'N/A') + mac_addr = entry.get('mac', 'N/A') + duid = entry.get('duid', 'N/A') + description = entry.get('description', 'N/A') + data_entries.append([pool, subnet, name, ip_addr, mac_addr, duid, description]) headers = ['Pool', 'Subnet', 'Name', 'IP Address', 'MAC Address', 'DUID', 'Description'] output = tabulate(data_entries, headers, numalign='left') @@ -445,7 +464,8 @@ def _get_raw_client_leases(family='inet', interface=None): if 'interface' in tmp: vrf = get_interface_vrf(tmp['interface']) - if vrf: tmp.update({'vrf' : vrf}) + if vrf: + tmp.update({'vrf' : vrf}) lease_data.append(tmp) |