Skip to content

Commit

Permalink
T973: add basic node_exporter implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
rebortg committed Sep 11, 2024
1 parent de8d388 commit f23fc7b
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 0 deletions.
11 changes: 11 additions & 0 deletions data/templates/node_exporter/node_exporter.service.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[Unit]
Description=Node Exporter
Documentation=https://github.com/prometheus/node_exporter
After=network.target

[Service]
User=node_exporter
ExecStart={{ vrf_arg }}/usr/sbin/node_exporter {{ deamon_args | join(' ') }}

[Install]
WantedBy=multi-user.target
5 changes: 5 additions & 0 deletions debian/vyos-1x.postinst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ if ! grep -q '^openvpn' /etc/passwd; then
adduser --quiet --firstuid 100 --system --group --shell /usr/sbin/nologin openvpn
fi

# node_exporter should get its own user
if ! grep -q '^node_exporter' /etc/passwd; then
adduser --quiet --firstuid 100 --system --group --shell /bin/false node_exporter
fi

# We need to have a group for RADIUS service users to use it inside PAM rules
if ! grep -q '^radius' /etc/group; then
addgroup --firstgid 1000 --quiet radius
Expand Down
28 changes: 28 additions & 0 deletions interface-definitions/service_monitoring_node_exporter.xml.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?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>node-exporter settings</help>
<priority>1280</priority>
</properties>
<children>
#include <include/listen-address.xml.i>
<leafNode name="listen-address">
<defaultValue>0.0.0.0</defaultValue>
</leafNode>
#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>
62 changes: 62 additions & 0 deletions smoketest/scripts/cli/test_service_monitoring_node-exporter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
#
# Copyright (C) 2020-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()

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)
116 changes: 116 additions & 0 deletions src/conf_mode/service_monitoring_node-exporter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/usr/bin/env python3
#
# Copyright (C) 2021-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 and service_file
render_data = {
'deamon_args': list(),
'vrf_arg': '',
}
for address in config_data['listen_address']:
render_data['deamon_args'].append(
f"--web.listen-address={address}:{config_data['port']}"
)

if 'vrf' in config_data:
render_data['vrf_arg'] = f"ip vrf exec {config_data['vrf']} "

render(service_file, 'node_exporter/node_exporter.service.j2', render_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}')

# Telegraf include custom rsyslog config changes
call('systemctl reload-or-restart rsyslog')


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 f23fc7b

Please sign in to comment.