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 18, 2024
1 parent de8d388 commit a0c15a1
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 0 deletions.
17 changes: 17 additions & 0 deletions data/templates/node_exporter/node_exporter.service.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{% set vrf_command = 'ip vrf exec ' ~ vrf ~ ' runuser -u node_exporter -- ' if vrf is vyos_defined else '' %}
[Unit]
Description=Node Exporter
Documentation=https://github.com/prometheus/node_exporter
After=network.target

[Service]
{% if vrf is not vyos_defined %}
User=node_exporter
{% endif %}
ExecStart={{ vrf_command }}/usr/sbin/node_exporter \
{% for address in listen_address %}
--web.listen-address={{ address }}:{{ port }}
{% endfor %}

[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>Prometheus exporter for hardware and operating system metrics</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>
64 changes: 64 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,64 @@
#!/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()
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)
104 changes: 104 additions & 0 deletions src/conf_mode/service_monitoring_node-exporter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/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 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}')

# 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 a0c15a1

Please sign in to comment.