summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/prometheus/frr_exporter.service.j2 (renamed from data/templates/frr_exporter/frr_exporter.service.j2)0
-rw-r--r--data/templates/prometheus/node_exporter.service.j2 (renamed from data/templates/node_exporter/node_exporter.service.j2)0
-rw-r--r--debian/control8
-rw-r--r--interface-definitions/include/version/monitoring-version.xml.i2
-rw-r--r--interface-definitions/service_monitoring_frr_exporter.xml.in25
-rw-r--r--interface-definitions/service_monitoring_node_exporter.xml.in25
-rw-r--r--interface-definitions/service_monitoring_prometheus.xml.in45
-rw-r--r--op-mode-definitions/monitor-log.xml.in4
-rw-r--r--python/vyos/ifconfig/interface.py27
-rw-r--r--python/vyos/ifconfig/pppoe.py8
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_babel.py117
-rwxr-xr-xsmoketest/scripts/cli/test_service_dns_dynamic.py1
-rwxr-xr-xsmoketest/scripts/cli/test_service_monitoring_node-exporter.py64
-rwxr-xr-xsmoketest/scripts/cli/test_service_monitoring_prometheus.py (renamed from smoketest/scripts/cli/test_service_monitoring_frr-exporter.py)38
-rwxr-xr-xsrc/conf_mode/service_monitoring_frr-exporter.py101
-rwxr-xr-xsrc/conf_mode/service_monitoring_node-exporter.py101
-rwxr-xr-xsrc/conf_mode/service_monitoring_prometheus.py145
-rw-r--r--src/migration-scripts/monitoring/1-to-250
-rwxr-xr-xsrc/op_mode/dhcp.py58
19 files changed, 393 insertions, 426 deletions
diff --git a/data/templates/frr_exporter/frr_exporter.service.j2 b/data/templates/prometheus/frr_exporter.service.j2
index c3892e42b..c3892e42b 100644
--- a/data/templates/frr_exporter/frr_exporter.service.j2
+++ b/data/templates/prometheus/frr_exporter.service.j2
diff --git a/data/templates/node_exporter/node_exporter.service.j2 b/data/templates/prometheus/node_exporter.service.j2
index 62e7e6774..62e7e6774 100644
--- a/data/templates/node_exporter/node_exporter.service.j2
+++ b/data/templates/prometheus/node_exporter.service.j2
diff --git a/debian/control b/debian/control
index 76ca83dcd..08b86356a 100644
--- a/debian/control
+++ b/debian/control
@@ -235,12 +235,12 @@ Depends:
squidclient,
squidguard,
# End "service webproxy"
-# For "service monitoring node-exporter"
+# For "service monitoring prometheus node-exporter"
node-exporter,
-# End "service monitoring node-exporter"
-# For "service monitoring frr-exporter"
+# End "service monitoring prometheus node-exporter"
+# For "service monitoring prometheus frr-exporter"
frr-exporter,
-# End "service monitoring frr-exporter"
+# End "service monitoring prometheus frr-exporter"
# For "service monitoring telegraf"
telegraf (>= 1.20),
# End "service monitoring telegraf"
diff --git a/interface-definitions/include/version/monitoring-version.xml.i b/interface-definitions/include/version/monitoring-version.xml.i
index 6a275a5d8..2e2e0116e 100644
--- a/interface-definitions/include/version/monitoring-version.xml.i
+++ b/interface-definitions/include/version/monitoring-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/monitoring-version.xml.i -->
-<syntaxVersion component='monitoring' version='1'></syntaxVersion>
+<syntaxVersion component='monitoring' version='2'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/service_monitoring_frr_exporter.xml.in b/interface-definitions/service_monitoring_frr_exporter.xml.in
deleted file mode 100644
index 96aee3ab4..000000000
--- a/interface-definitions/service_monitoring_frr_exporter.xml.in
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0"?>
-<interfaceDefinition>
- <node name="service">
- <children>
- <node name="monitoring">
- <children>
- <node name="frr-exporter" owner="${vyos_conf_scripts_dir}/service_monitoring_frr-exporter.py">
- <properties>
- <help>Prometheus exporter for FRR metrics</help>
- <priority>1280</priority>
- </properties>
- <children>
- #include <include/listen-address.xml.i>
- #include <include/port-number.xml.i>
- <leafNode name="port">
- <defaultValue>9342</defaultValue>
- </leafNode>
- #include <include/interface/vrf.xml.i>
- </children>
- </node>
- </children>
- </node>
- </children>
- </node>
-</interfaceDefinition>
diff --git a/interface-definitions/service_monitoring_node_exporter.xml.in b/interface-definitions/service_monitoring_node_exporter.xml.in
deleted file mode 100644
index a11d2304f..000000000
--- a/interface-definitions/service_monitoring_node_exporter.xml.in
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0"?>
-<interfaceDefinition>
- <node name="service">
- <children>
- <node name="monitoring">
- <children>
- <node name="node-exporter" owner="${vyos_conf_scripts_dir}/service_monitoring_node-exporter.py">
- <properties>
- <help>Prometheus exporter for hardware and operating system metrics</help>
- <priority>1280</priority>
- </properties>
- <children>
- #include <include/listen-address.xml.i>
- #include <include/port-number.xml.i>
- <leafNode name="port">
- <defaultValue>9100</defaultValue>
- </leafNode>
- #include <include/interface/vrf.xml.i>
- </children>
- </node>
- </children>
- </node>
- </children>
- </node>
-</interfaceDefinition>
diff --git a/interface-definitions/service_monitoring_prometheus.xml.in b/interface-definitions/service_monitoring_prometheus.xml.in
new file mode 100644
index 000000000..24f31e15c
--- /dev/null
+++ b/interface-definitions/service_monitoring_prometheus.xml.in
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="service">
+ <children>
+ <node name="monitoring">
+ <children>
+ <node name="prometheus" owner="${vyos_conf_scripts_dir}/service_monitoring_prometheus.py">
+ <properties>
+ <help>Prometheus metric exporter</help>
+ <priority>1280</priority>
+ </properties>
+ <children>
+ <node name="node-exporter">
+ <properties>
+ <help>Prometheus exporter for hardware and operating system metrics</help>
+ </properties>
+ <children>
+ #include <include/listen-address.xml.i>
+ #include <include/port-number.xml.i>
+ <leafNode name="port">
+ <defaultValue>9100</defaultValue>
+ </leafNode>
+ #include <include/interface/vrf.xml.i>
+ </children>
+ </node>
+ <node name="frr-exporter">
+ <properties>
+ <help>Prometheus exporter for FRR metrics</help>
+ </properties>
+ <children>
+ #include <include/listen-address.xml.i>
+ #include <include/port-number.xml.i>
+ <leafNode name="port">
+ <defaultValue>9342</defaultValue>
+ </leafNode>
+ #include <include/interface/vrf.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in
index f6b70be32..c9dc49b3a 100644
--- a/op-mode-definitions/monitor-log.xml.in
+++ b/op-mode-definitions/monitor-log.xml.in
@@ -9,13 +9,13 @@
<properties>
<help>Monitor last lines of messages file</help>
</properties>
- <command>SYSTEMD_LOG_COLOR=false journalctl --no-hostname --follow --boot</command>
+ <command>SYSTEMD_COLORS=false journalctl --no-hostname --follow --boot</command>
<children>
<node name="color">
<properties>
<help>Output log in a colored fashion</help>
</properties>
- <command>SYSTEMD_LOG_COLOR=false grc journalctl --no-hostname --follow --boot</command>
+ <command>SYSTEMD_COLORS=false grc journalctl --no-hostname --follow --boot</command>
</node>
<node name="ids">
<properties>
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index eac9f61f5..cad1685a9 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -29,6 +29,7 @@ from netifaces import AF_INET6
from netaddr import EUI
from netaddr import mac_unix_expanded
+from vyos.base import ConfigError
from vyos.configdict import list_diff
from vyos.configdict import dict_merge
from vyos.configdict import get_vlan_ids
@@ -43,6 +44,7 @@ from vyos.template import render
from vyos.utils.network import mac2eui64
from vyos.utils.dict import dict_search
from vyos.utils.network import get_interface_config
+from vyos.utils.network import get_interface_address
from vyos.utils.network import get_interface_namespace
from vyos.utils.network import get_vrf_tableid
from vyos.utils.network import is_netns_interface
@@ -62,7 +64,6 @@ from vyos.ifconfig.control import Control
from vyos.ifconfig.vrrp import VRRP
from vyos.ifconfig.operational import Operational
from vyos.ifconfig import Section
-from vyos import ConfigError
link_local_prefix = 'fe80::/64'
@@ -1375,12 +1376,11 @@ class Interface(Control):
if enable not in [True, False]:
raise ValueError()
- ifname = self.ifname
config_base = directories['isc_dhclient_dir'] + '/dhclient'
- dhclient_config_file = f'{config_base}_{ifname}.conf'
- dhclient_lease_file = f'{config_base}_{ifname}.leases'
- systemd_override_file = f'/run/systemd/system/dhclient@{ifname}.service.d/10-override.conf'
- systemd_service = f'dhclient@{ifname}.service'
+ dhclient_config_file = f'{config_base}_{self.ifname}.conf'
+ dhclient_lease_file = f'{config_base}_{self.ifname}.leases'
+ systemd_override_file = f'/run/systemd/system/dhclient@{self.ifname}.service.d/10-override.conf'
+ systemd_service = f'dhclient@{self.ifname}.service'
# Rendered client configuration files require the apsolute config path
self.config['isc_dhclient_dir'] = directories['isc_dhclient_dir']
@@ -1414,6 +1414,21 @@ class Interface(Control):
else:
if is_systemd_service_active(systemd_service):
self._cmd(f'systemctl stop {systemd_service}')
+
+ # Smoketests occationally fail if the lease is not removed from the Kernel fast enough:
+ # AssertionError: 2 unexpectedly found in {17: [{'addr': '52:54:00:00:00:00',
+ # 'broadcast': 'ff:ff:ff:ff:ff:ff'}], 2: [{'addr': '192.0.2.103', 'netmask': '255.255.255.0',
+ #
+ # We will force removal of any dynamic IPv4 address from the interface
+ tmp = get_interface_address(self.ifname)
+ if tmp and 'addr_info' in tmp:
+ for address_dict in tmp['addr_info']:
+ # Only remove dynamic assigned addresses
+ if 'dynamic' not in address_dict:
+ continue
+ address = address_dict['local']
+ self.del_addr(address)
+
# cleanup old config files
for file in [dhclient_config_file, systemd_override_file, dhclient_lease_file]:
if os.path.isfile(file):
diff --git a/python/vyos/ifconfig/pppoe.py b/python/vyos/ifconfig/pppoe.py
index febf1452d..f80a68d4f 100644
--- a/python/vyos/ifconfig/pppoe.py
+++ b/python/vyos/ifconfig/pppoe.py
@@ -115,14 +115,6 @@ class PPPoEIf(Interface):
# before this is done by the base class.
self._config = config
- # remove old routes from an e.g. old VRF assignment
- if 'shutdown_required':
- vrf = None
- tmp = get_interface_config(self.ifname)
- if 'master' in tmp:
- vrf = tmp['master']
- self._remove_routes(vrf)
-
# DHCPv6 PD handling is a bit different on PPPoE interfaces, as we do
# not require an 'address dhcpv6' CLI option as with other interfaces
if 'dhcpv6_options' in config and 'pd' in config['dhcpv6_options']:
diff --git a/smoketest/scripts/cli/test_protocols_babel.py b/smoketest/scripts/cli/test_protocols_babel.py
index 107e262cc..12f6ecf38 100755
--- a/smoketest/scripts/cli/test_protocols_babel.py
+++ b/smoketest/scripts/cli/test_protocols_babel.py
@@ -50,54 +50,30 @@ class TestProtocolsBABEL(VyOSUnitTestSHIM.TestCase):
# check process health and continuity
self.assertEqual(self.daemon_pid, process_named_running(babel_daemon))
- def test_babel_interfaces(self):
- def_update_interval = default_value(base_path + ['interface', 'eth0', 'update-interval'])
- channel = '20'
- hello_interval = '1000'
- max_rtt_penalty = '100'
- rtt_decay = '23'
- rtt_max = '119'
- rtt_min = '11'
- rxcost = '40000'
- type = 'wired'
+ def test_01_basic(self):
+ diversity_factor = '64'
+ resend_delay = '100'
+ smoothing_half_life = '400'
- for interface in self._interfaces:
- self.cli_set(base_path + ['interface', interface])
- self.cli_set(base_path + ['interface', interface, 'channel', channel])
- self.cli_set(base_path + ['interface', interface, 'enable-timestamps'])
- self.cli_set(base_path + ['interface', interface, 'hello-interval', hello_interval])
- self.cli_set(base_path + ['interface', interface, 'max-rtt-penalty', max_rtt_penalty])
- self.cli_set(base_path + ['interface', interface, 'rtt-decay', rtt_decay])
- self.cli_set(base_path + ['interface', interface, 'rtt-max', rtt_max])
- self.cli_set(base_path + ['interface', interface, 'rtt-min', rtt_min])
- self.cli_set(base_path + ['interface', interface, 'enable-timestamps'])
- self.cli_set(base_path + ['interface', interface, 'rxcost', rxcost])
- self.cli_set(base_path + ['interface', interface, 'split-horizon', 'disable'])
- self.cli_set(base_path + ['interface', interface, 'type', type])
+ self.cli_set(base_path + ['parameters', 'diversity'])
+ self.cli_set(base_path + ['parameters', 'diversity-factor', diversity_factor])
+ self.cli_set(base_path + ['parameters', 'resend-delay', resend_delay])
+ self.cli_set(base_path + ['parameters', 'smoothing-half-life', smoothing_half_life])
self.cli_commit()
frrconfig = self.getFRRconfig('router babel', endsection='^exit', daemon=babel_daemon)
- for interface in self._interfaces:
- self.assertIn(f' network {interface}', frrconfig)
-
- iface_config = self.getFRRconfig(f'interface {interface}', endsection='^exit', daemon=babel_daemon)
- self.assertIn(f' babel channel {channel}', iface_config)
- self.assertIn(f' babel enable-timestamps', iface_config)
- self.assertIn(f' babel update-interval {def_update_interval}', iface_config)
- self.assertIn(f' babel hello-interval {hello_interval}', iface_config)
- self.assertIn(f' babel rtt-decay {rtt_decay}', iface_config)
- self.assertIn(f' babel rtt-max {rtt_max}', iface_config)
- self.assertIn(f' babel rtt-min {rtt_min}', iface_config)
- self.assertIn(f' babel rxcost {rxcost}', iface_config)
- self.assertIn(f' babel max-rtt-penalty {max_rtt_penalty}', iface_config)
- self.assertIn(f' no babel split-horizon', iface_config)
- self.assertIn(f' babel {type}', iface_config)
+ self.assertIn(f' babel diversity', frrconfig)
+ self.assertIn(f' babel diversity-factor {diversity_factor}', frrconfig)
+ self.assertIn(f' babel resend-delay {resend_delay}', frrconfig)
+ self.assertIn(f' babel smoothing-half-life {smoothing_half_life}', frrconfig)
- def test_babel_redistribute(self):
+ def test_02_redistribute(self):
ipv4_protos = ['bgp', 'connected', 'isis', 'kernel', 'ospf', 'rip', 'static']
ipv6_protos = ['bgp', 'connected', 'isis', 'kernel', 'ospfv3', 'ripng', 'static']
+ self.cli_set(base_path + ['interface', self._interfaces[0], 'enable-timestamps'])
+
for protocol in ipv4_protos:
self.cli_set(base_path + ['redistribute', 'ipv4', protocol])
for protocol in ipv6_protos:
@@ -113,25 +89,7 @@ class TestProtocolsBABEL(VyOSUnitTestSHIM.TestCase):
protocol = 'ospf6'
self.assertIn(f' redistribute ipv6 {protocol}', frrconfig)
- def test_babel_basic(self):
- diversity_factor = '64'
- resend_delay = '100'
- smoothing_half_life = '400'
-
- self.cli_set(base_path + ['parameters', 'diversity'])
- self.cli_set(base_path + ['parameters', 'diversity-factor', diversity_factor])
- self.cli_set(base_path + ['parameters', 'resend-delay', resend_delay])
- self.cli_set(base_path + ['parameters', 'smoothing-half-life', smoothing_half_life])
-
- self.cli_commit()
-
- frrconfig = self.getFRRconfig('router babel', endsection='^exit', daemon=babel_daemon)
- self.assertIn(f' babel diversity', frrconfig)
- self.assertIn(f' babel diversity-factor {diversity_factor}', frrconfig)
- self.assertIn(f' babel resend-delay {resend_delay}', frrconfig)
- self.assertIn(f' babel smoothing-half-life {smoothing_half_life}', frrconfig)
-
- def test_babel_distribute_list(self):
+ def test_03_distribute_list(self):
access_list_in4 = '40'
access_list_out4 = '50'
access_list_in4_iface = '44'
@@ -214,5 +172,48 @@ class TestProtocolsBABEL(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' ipv6 distribute-list prefix {prefix_list_in6}-{interface} in {interface}', frrconfig)
self.assertIn(f' ipv6 distribute-list prefix {prefix_list_out6}-{interface} out {interface}', frrconfig)
+ def test_04_interfaces(self):
+ def_update_interval = default_value(base_path + ['interface', 'eth0', 'update-interval'])
+ channel = '20'
+ hello_interval = '1000'
+ max_rtt_penalty = '100'
+ rtt_decay = '23'
+ rtt_max = '119'
+ rtt_min = '11'
+ rxcost = '40000'
+ type = 'wired'
+
+ for interface in self._interfaces:
+ self.cli_set(base_path + ['interface', interface])
+ self.cli_set(base_path + ['interface', interface, 'channel', channel])
+ self.cli_set(base_path + ['interface', interface, 'enable-timestamps'])
+ self.cli_set(base_path + ['interface', interface, 'hello-interval', hello_interval])
+ self.cli_set(base_path + ['interface', interface, 'max-rtt-penalty', max_rtt_penalty])
+ self.cli_set(base_path + ['interface', interface, 'rtt-decay', rtt_decay])
+ self.cli_set(base_path + ['interface', interface, 'rtt-max', rtt_max])
+ self.cli_set(base_path + ['interface', interface, 'rtt-min', rtt_min])
+ self.cli_set(base_path + ['interface', interface, 'rxcost', rxcost])
+ self.cli_set(base_path + ['interface', interface, 'split-horizon', 'disable'])
+ self.cli_set(base_path + ['interface', interface, 'type', type])
+
+ self.cli_commit()
+
+ frrconfig = self.getFRRconfig('router babel', endsection='^exit', daemon=babel_daemon)
+ for interface in self._interfaces:
+ self.assertIn(f' network {interface}', frrconfig)
+
+ iface_config = self.getFRRconfig(f'interface {interface}', endsection='^exit', daemon=babel_daemon)
+ self.assertIn(f' babel channel {channel}', iface_config)
+ self.assertIn(f' babel enable-timestamps', iface_config)
+ self.assertIn(f' babel update-interval {def_update_interval}', iface_config)
+ self.assertIn(f' babel hello-interval {hello_interval}', iface_config)
+ self.assertIn(f' babel rtt-decay {rtt_decay}', iface_config)
+ self.assertIn(f' babel rtt-max {rtt_max}', iface_config)
+ self.assertIn(f' babel rtt-min {rtt_min}', iface_config)
+ self.assertIn(f' babel rxcost {rxcost}', iface_config)
+ self.assertIn(f' babel max-rtt-penalty {max_rtt_penalty}', iface_config)
+ self.assertIn(f' no babel split-horizon', iface_config)
+ self.assertIn(f' babel {type}', iface_config)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_service_dns_dynamic.py b/smoketest/scripts/cli/test_service_dns_dynamic.py
index 3e14976fc..3ce459e44 100755
--- a/smoketest/scripts/cli/test_service_dns_dynamic.py
+++ b/smoketest/scripts/cli/test_service_dns_dynamic.py
@@ -14,7 +14,6 @@
# 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
import unittest
import tempfile
diff --git a/smoketest/scripts/cli/test_service_monitoring_node-exporter.py b/smoketest/scripts/cli/test_service_monitoring_node-exporter.py
deleted file mode 100755
index e18a3f7a2..000000000
--- a/smoketest/scripts/cli/test_service_monitoring_node-exporter.py
+++ /dev/null
@@ -1,64 +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 unittest
-
-from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.utils.process import process_named_running
-from vyos.utils.file import read_file
-
-PROCESS_NAME = 'node_exporter'
-base_path = ['service', 'monitoring', 'node-exporter']
-service_file = '/etc/systemd/system/node_exporter.service'
-listen_if = 'dum3421'
-listen_ip = '192.0.2.1'
-
-
-class TestMonitoringNodeExporter(VyOSUnitTestSHIM.TestCase):
- @classmethod
- def setUpClass(cls):
- # call base-classes classmethod
- super(TestMonitoringNodeExporter, cls).setUpClass()
- # create a test interfaces
- cls.cli_set(
- cls, ['interfaces', 'dummy', listen_if, 'address', listen_ip + '/32']
- )
-
- @classmethod
- def tearDownClass(cls):
- cls.cli_delete(cls, ['interfaces', 'dummy', listen_if])
- super(TestMonitoringNodeExporter, cls).tearDownClass()
-
- def tearDown(self):
- self.cli_delete(base_path)
- self.cli_commit()
- self.assertFalse(process_named_running(PROCESS_NAME))
-
- def test_01_basic_config(self):
- self.cli_set(base_path + ['listen-address', listen_ip])
-
- # commit changes
- self.cli_commit()
-
- file_content = read_file(service_file)
- self.assertIn(f'{listen_ip}:9100', file_content)
-
- # Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
-
-
-if __name__ == '__main__':
- unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_service_monitoring_frr-exporter.py b/smoketest/scripts/cli/test_service_monitoring_prometheus.py
index 230171c11..dae103e4b 100755
--- a/smoketest/scripts/cli/test_service_monitoring_frr-exporter.py
+++ b/smoketest/scripts/cli/test_service_monitoring_prometheus.py
@@ -20,18 +20,21 @@ from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.utils.process import process_named_running
from vyos.utils.file import read_file
-PROCESS_NAME = 'frr_exporter'
-base_path = ['service', 'monitoring', 'frr-exporter']
-service_file = '/etc/systemd/system/frr_exporter.service'
+NODE_EXPORTER_PROCESS_NAME = 'node_exporter'
+FRR_EXPORTER_PROCESS_NAME = 'frr_exporter'
+
+base_path = ['service', 'monitoring', 'prometheus']
listen_if = 'dum3421'
listen_ip = '192.0.2.1'
+node_exporter_service_file = '/etc/systemd/system/node_exporter.service'
+frr_exporter_service_file = '/etc/systemd/system/frr_exporter.service'
-class TestMonitoringFrrExporter(VyOSUnitTestSHIM.TestCase):
+class TestMonitoringPrometheus(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
# call base-classes classmethod
- super(TestMonitoringFrrExporter, cls).setUpClass()
+ super(TestMonitoringPrometheus, cls).setUpClass()
# create a test interfaces
cls.cli_set(
cls, ['interfaces', 'dummy', listen_if, 'address', listen_ip + '/32']
@@ -40,24 +43,37 @@ class TestMonitoringFrrExporter(VyOSUnitTestSHIM.TestCase):
@classmethod
def tearDownClass(cls):
cls.cli_delete(cls, ['interfaces', 'dummy', listen_if])
- super(TestMonitoringFrrExporter, cls).tearDownClass()
+ super(TestMonitoringPrometheus, cls).tearDownClass()
def tearDown(self):
self.cli_delete(base_path)
self.cli_commit()
- self.assertFalse(process_named_running(PROCESS_NAME))
+ self.assertFalse(process_named_running(NODE_EXPORTER_PROCESS_NAME))
+ self.assertFalse(process_named_running(FRR_EXPORTER_PROCESS_NAME))
+
+ def test_01_node_exporter(self):
+ self.cli_set(base_path + ['node-exporter', 'listen-address', listen_ip])
+
+ # commit changes
+ self.cli_commit()
+
+ file_content = read_file(node_exporter_service_file)
+ self.assertIn(f'{listen_ip}:9100', file_content)
+
+ # Check for running process
+ self.assertTrue(process_named_running(NODE_EXPORTER_PROCESS_NAME))
- def test_01_basic_config(self):
- self.cli_set(base_path + ['listen-address', listen_ip])
+ def test_02_frr_exporter(self):
+ self.cli_set(base_path + ['frr-exporter', 'listen-address', listen_ip])
# commit changes
self.cli_commit()
- file_content = read_file(service_file)
+ file_content = read_file(frr_exporter_service_file)
self.assertIn(f'{listen_ip}:9342', file_content)
# Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
+ self.assertTrue(process_named_running(FRR_EXPORTER_PROCESS_NAME))
if __name__ == '__main__':
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)