Skip to content

Commit

Permalink
T973: add basic frr_exporter implementation (#4150)
Browse files Browse the repository at this point in the history
  • Loading branch information
rebortg authored Oct 17, 2024
1 parent e5d2ac5 commit 3f933f1
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 0 deletions.
20 changes: 20 additions & 0 deletions data/templates/frr_exporter/frr_exporter.service.j2
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
25 changes: 25 additions & 0 deletions interface-definitions/service_monitoring_frr_exporter.xml.in
Original file line number Diff line number Diff line change
@@ -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>
64 changes: 64 additions & 0 deletions smoketest/scripts/cli/test_service_monitoring_frr-exporter.py
Original file line number Diff line number Diff line change
@@ -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)
101 changes: 101 additions & 0 deletions src/conf_mode/service_monitoring_frr-exporter.py
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit 3f933f1

Please sign in to comment.