summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/frr_exporter/frr_exporter.service.j220
-rw-r--r--debian/control3
-rw-r--r--interface-definitions/service_monitoring_frr_exporter.xml.in25
-rwxr-xr-xsmoketest/scripts/cli/test_service_monitoring_frr-exporter.py64
-rwxr-xr-xsrc/conf_mode/service_monitoring_frr-exporter.py101
5 files changed, 213 insertions, 0 deletions
diff --git a/data/templates/frr_exporter/frr_exporter.service.j2 b/data/templates/frr_exporter/frr_exporter.service.j2
new file mode 100644
index 000000000..c3892e42b
--- /dev/null
+++ b/data/templates/frr_exporter/frr_exporter.service.j2
@@ -0,0 +1,20 @@
+{% set vrf_command = 'ip vrf exec ' ~ vrf ~ ' runuser -u frr -- ' if vrf is vyos_defined else '' %}
+[Unit]
+Description=FRR Exporter
+Documentation=https://github.com/tynany/frr_exporter
+After=network.target
+
+[Service]
+{% if vrf is not vyos_defined %}
+User=frr
+{% endif %}
+ExecStart={{ vrf_command }}/usr/sbin/frr_exporter \
+{% if listen_address is vyos_defined %}
+{% for address in listen_address %}
+ --web.listen-address={{ address }}:{{ port }}
+{% endfor %}
+{% else %}
+ --web.listen-address=:{{ port }}
+{% endif %}
+[Install]
+WantedBy=multi-user.target
diff --git a/debian/control b/debian/control
index 20cfcdc43..a19461412 100644
--- a/debian/control
+++ b/debian/control
@@ -238,6 +238,9 @@ Depends:
# For "service monitoring node-exporter"
node-exporter,
# End "service monitoring node-exporter"
+# For "service monitoring frr-exporter"
+ frr-exporter,
+# End "service monitoring frr-exporter"
# For "service monitoring telegraf"
telegraf (>= 1.20),
# End "service monitoring telegraf"
diff --git a/interface-definitions/service_monitoring_frr_exporter.xml.in b/interface-definitions/service_monitoring_frr_exporter.xml.in
new file mode 100644
index 000000000..96aee3ab4
--- /dev/null
+++ b/interface-definitions/service_monitoring_frr_exporter.xml.in
@@ -0,0 +1,25 @@
+<?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/smoketest/scripts/cli/test_service_monitoring_frr-exporter.py b/smoketest/scripts/cli/test_service_monitoring_frr-exporter.py
new file mode 100755
index 000000000..230171c11
--- /dev/null
+++ b/smoketest/scripts/cli/test_service_monitoring_frr-exporter.py
@@ -0,0 +1,64 @@
+#!/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 = 'frr_exporter'
+base_path = ['service', 'monitoring', 'frr-exporter']
+service_file = '/etc/systemd/system/frr_exporter.service'
+listen_if = 'dum3421'
+listen_ip = '192.0.2.1'
+
+
+class TestMonitoringFrrExporter(VyOSUnitTestSHIM.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ # call base-classes classmethod
+ super(TestMonitoringFrrExporter, 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(TestMonitoringFrrExporter, 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}:9342', file_content)
+
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/src/conf_mode/service_monitoring_frr-exporter.py b/src/conf_mode/service_monitoring_frr-exporter.py
new file mode 100755
index 000000000..01527d579
--- /dev/null
+++ b/src/conf_mode/service_monitoring_frr-exporter.py
@@ -0,0 +1,101 @@
+#!/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)