Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

T973: add basic node_exporter implementation #4048

Open
wants to merge 1 commit into
base: current
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
rebortg marked this conversation as resolved.
Show resolved Hide resolved
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)
Loading