Rewrite smokeping script to be an lnms command (#11585)

* Rewrite smokeping script to be an lnms command

* Add a default number of probes for smokeping

* Formatting fixes

* Refactor to simplify a couple of methods

* Fix a name collision when using more than 60 threads

* Simplify

* First pass at documentation

A few climate fixes too.

I'm a little dubious about the + LibreNMS fix - it could be included
in /etc/smokeping/config if it is really needed, but it sounds like
(looking at git blame) it's needed for if the script generates a broken
configuration file.

I'm thinking we should just not generate broken config.

* Improve testability and add some tests

* Load laravel to make translations available

* Second pass at documentation

* Fix brace

* Extend the device factory to include the type

Also adds a device group fake I created before I realised I didn't need it

* Mimic gen_smokeping.php a little more closely

* Update tests to properly verify old and new behaviour against each other

* Replace gen_smokeping with a wrapper

* Don't double whitespace

Render does this automatically.
Explicitly order by hostname too.

* Make faker less likely to generate duplicates

I tried adding a unique constraint here, but it didn't have the
intended effect.

Extending the hostname like this seems to work fine - I tried
generating 10 million hosts and got no duplicates, compared to
duplicates appearing with as few as 100 hosts without this
 change.

A true fix would be to add an 'fqdn' fake upstream.

* Make the tests more robust

* Assorted bug fixes

* Style corrections

* Handle 'generic' devices

* Fix an issue that came up during rebase

Co-authored-by: Tony Murray <murraytony@gmail.com>
This commit is contained in:
Adam Bishop 2020-09-18 06:04:54 +01:00 committed by GitHub
parent 5e63c34d19
commit bbdb1a2a5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1070 additions and 236 deletions

View File

@ -0,0 +1,388 @@
<?php
/**
* SmokepingGenerateCommand.php
*
* CLI command to generate a smokeping configuration.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2020 Adam Bishop
* @author Adam Bishop <adam@omega.org.uk>
*/
namespace App\Console\Commands;
use App\Console\LnmsCommand;
use App\Models\Device;
use LibreNMS\Config;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
class SmokepingGenerateCommand extends LnmsCommand
{
protected $name = 'smokeping:generate';
protected $dnsLookup = true;
private $ip4count = 0;
private $ip6count = 0;
private $warnings = [];
const IP4PROBE = 'lnmsFPing-';
const IP6PROBE = 'lnmsFPing6-';
// These entries are solely used to appease the smokeping config parser and serve no function
const DEFAULTIP4PROBE = 'FPing';
const DEFAULTIP6PROBE = 'FPing6';
const DEFAULTPROBE = self::DEFAULTIP4PROBE;
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
$this->setDescription(__('commands.smokeping:generate.description'));
$this->addOption('probes', null, InputOption::VALUE_NONE);
$this->addOption('targets', null, InputOption::VALUE_NONE);
$this->addOption('no-header', null, InputOption::VALUE_NONE);
$this->addOption('single-process', null, InputOption::VALUE_NONE);
$this->addOption('no-dns', null, InputOption::VALUE_NONE);
$this->addOption('compat', null, InputOption::VALUE_NONE);
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
if (!$this->validateOptions()) {
return 1;
}
$devices = Device::isNotDisabled()->orderBy('type')->orderBy('hostname')->get();
if (sizeof($devices) < 1) {
$this->error(__('commands.smokeping:generate.no-devices'));
return 3;
}
if ($this->option('probes')) {
return $this->buildProbesConfiguration();
} elseif ($this->option('targets')) {
return $this->buildTargetsConfiguration($devices);
}
return 2;
}
/**
* Disable DNS lookups by the configuration builder
*
* @return bool
*/
public function disableDNSLookup()
{
return $this->dnsLookup = false;
}
/**
* Build and output the probe configuration
*
* @return int
*/
public function buildProbesConfiguration()
{
$probes = $this->assembleProbes(Config::get('smokeping.probes'));
$header = $this->buildHeader($this->option('no-header'), $this->option('compat'));
return $this->render($header, $probes);
}
/**
* Build and output the target configuration
*
* @return int
*/
public function buildTargetsConfiguration($devices)
{
// Take the devices array and build it into a hierarchical list
foreach ($devices as $device) {
$smokelist[$device->type][$device->hostname] = ['transport' => $device->transport];
}
$targets = $this->buildTargets($smokelist, Config::get('smokeping.probes'), $this->option('single-process'));
$header = $this->buildHeader($this->option('no-header'), $this->option('compat'));
return $this->render($header, $targets);
}
/**
* Set a warning to be emitted
*
* @return void
*/
public function setWarning($warning)
{
$this->warnings[] = sprintf('# %s', $warning);
}
/**
* Bring together the probe lists
*
* @param int $probeCount Number of processes to create
*
* @return array
*/
public function assembleProbes($probeCount)
{
if ($probeCount < 1) {
return [];
}
return array_merge(
$this->buildProbes('FPing', self::DEFAULTIP4PROBE, self::IP4PROBE, Config::get('fping'), $probeCount),
$this->buildProbes('FPing6', self::DEFAULTIP6PROBE, self::IP6PROBE, Config::get('fping6'), $probeCount)
);
}
/**
* Determine if a list of probes is needed, and write one if so
*
* @param string $module The smokeping module to use for this probe (FPing or FPing6, typically)
* @param string $defaultProbe A default probe, needed by the smokeping configuration parser
* @param string $probe The first part of the probe name, e.g. 'lnmsFPing' or 'lnmsFPing6'
* @param string $binary Path to the relevant probe binary (i.e. the output of `which fping` or `which fping6`)
* @param int $probeCount Number of processes to create
*
* @return array
*/
public function buildProbes($module, $defaultProbe, $probe, $binary, $probeCount)
{
$lines = [];
$lines[] = sprintf('+ %s', $module);
$lines[] = sprintf(' binary = %s', $binary);
$lines[] = ' blazemode = true';
$lines[] = sprintf('++ %s', $defaultProbe);
for ($i = 0; $i < $probeCount; $i++) {
$lines[] = sprintf('++ %s%s', $probe, $i);
}
$lines[] = '';
return $lines;
}
/**
* Generate a header to append to the smokeping configuration file
*
* @return array
*/
public function buildHeader($noHeader, $compat)
{
$lines = [];
if ($compat) {
$lines[] = '';
$lines[] = 'menu = Top';
$lines[] = 'title = Network Latency Grapher';
$lines[] = '';
}
if (!$noHeader) {
$lines[] = sprintf('# %s', __('commands.smokeping:generate.header-first'));
$lines[] = sprintf('# %s', __('commands.smokeping:generate.header-second'));
$lines[] = sprintf('# %s', __('commands.smokeping:generate.header-third'));
return array_merge($lines, $this->warnings, ['']);
}
return $lines;
}
/**
* Determine if a list of targets is needed, and write one if so
*
* @param array $devices A list of devices to create a a config block for
*
* @return array
*/
public function buildTargets($smokelist, $probeCount, $singleProcess)
{
$lines = [];
foreach ($smokelist as $type => $devices) {
if (empty($type)) {
$type = 'Ungrouped';
}
$lines[] = sprintf('+ %s', $this->buildMenuEntry($type));
$lines[] = sprintf(' menu = %s', $type);
$lines[] = sprintf(' title = %s', $type);
$lines[] = '';
$lines = array_merge($lines, $this->buildDevices($devices, $probeCount, $singleProcess));
}
return $lines;
}
/**
* Check arguments passed are sensible
*
* @return bool
*/
private function validateOptions()
{
if (!Config::has('smokeping.probes') ||
!Config::has('fping') ||
!Config::has('fping6')
) {
$this->error(__('commands.smokeping:generate.config-insufficient'));
return false;
}
if (!($this->option('probes') xor $this->option('targets'))) {
$this->error(__('commands.smokeping:generate.args-nonsense'));
return false;
}
if (Config::get('smokeping.probes') < 1) {
$this->error(__('commands.smokeping:generate.no-probes'));
return false;
}
if ($this->option('compat') && !$this->option('targets')) {
$this->error(__('commands.smokeping:generate.args-nonsense'));
return false;
}
if ($this->option('no-dns')) {
$this->disableDNSLookup();
}
return true;
}
/**
* Take config lines and output them to stdout
*
* @param array ...$blocks Blocks of smokeping configuration arranged in arrays of strings
*
* @return int
*/
private function render(...$blocks)
{
foreach (array_merge(...$blocks) as $line) {
$this->line($line);
}
return 0;
}
/**
* Build the configuration for a set of devices inside a type block
*
* @param array $devices A list of devices to create a a config block for
*
* @return array
*/
private function buildDevices($devices, $probeCount, $singleProcess)
{
$lines = [];
foreach ($devices as $hostname => $config) {
if (!$this->dnsLookup || $this->deviceIsResolvable($hostname)) {
$lines[] = sprintf('++ %s', $this->buildMenuEntry($hostname));
$lines[] = sprintf(' menu = %s', $hostname);
$lines[] = sprintf(' title = %s', $hostname);
if (!$singleProcess) {
$lines[] = sprintf(' probe = %s', $this->balanceProbes($config['transport'], $probeCount));
}
$lines[] = sprintf(' host = %s', $hostname);
$lines[] = '';
}
}
return $lines;
}
/**
* Smokeping refuses to load if it has an unresolvable host, so check for this
*
* @param string $hostname Hostname to be checked
*
* @return bool
*/
private function deviceIsResolvable($hostname)
{
// First we check for IP literals, then for a dns entry, finally for a hosts entry due to a PHP/libc limitation
// We look for the hosts entry last (and separately) as this only works for v4 - v6 host entries won't be found
if (filter_var($hostname, FILTER_VALIDATE_IP) || checkdnsrr($hostname, 'ANY') || is_array(gethostbynamel($hostname))) {
return true;
}
$this->setWarning(sprintf('"%s" %s', $hostname, __('commands.smokeping:generate.dns-fail')));
return false;
}
/**
* Rewrite menu entries to a format that smokeping finds acceptable
*
* @param string $entry The LibreNMS device hostname to rewrite
*
* @return string
*/
private function buildMenuEntry($entry)
{
return str_replace(['.', ' '], '_', $entry);
}
/**
* Select a probe to use deterministically.
*
* @param string $transport The transport (udp or udp6) as per the device database entry
*
* @return string
*/
private function balanceProbes($transport, $probeCount)
{
if ($transport === 'udp') {
if ($probeCount === $this->ip4count) {
$this->ip4count = 0;
}
return sprintf('%s%s', self::IP4PROBE, $this->ip4count++);
}
if ($probeCount === $this->ip6count) {
$this->ip6count = 0;
}
return sprintf('%s%s', self::IP6PROBE, $this->ip6count++);
}
}

View File

@ -49,13 +49,41 @@ $factory->define(\App\Models\Bill::class, function (Faker\Generator $faker) {
$factory->define(\App\Models\Device::class, function (Faker\Generator $faker) {
return [
'hostname' => $faker->domainWord . '.' . $faker->domainName,
'hostname' => $faker->domainWord . '-' . $faker->domainWord . '-' . $faker->domainWord . '.' . $faker->domainName,
'ip' => $faker->randomElement([$faker->ipv4, $faker->ipv6]),
'type' => $faker->randomElement([
'appliance',
'camera',
'collaboration',
'encoder',
'environment',
'firewall',
'loadbalancer',
'management',
'network',
'power',
'printer',
'proxy',
'sensor',
'server',
'storage',
'timing',
'wireless',
'workstation'
]),
'status' => $status = random_int(0, 1),
'status_reason' => $status == 0 ? $faker->randomElement(['snmp', 'icmp']) : '', // allow invalid states?
];
});
$factory->define(\App\Models\DeviceGroup::class, function (Faker\Generator $faker) {
return [
'name' => $faker->domainWord,
'desc' => $faker->text(255),
'type' =>'static',
];
});
$factory->define(\App\Models\Port::class, function (Faker\Generator $faker) {
return [
'ifIndex' => $faker->unique()->numberBetween(),

View File

@ -3,35 +3,154 @@ path: blob/master/doc/
# Smokeping integration
[SmokePing](https://oss.oetiker.ch/smokeping/) is a tool which lets us
keep track of network latency, and visualise this through RRD graphs.
[SmokePing](https://oss.oetiker.ch/smokeping/) is a tool which lets us keep
track of network latency, and visualise this through RRD graphs.
LibreNMS has support for both new and pre-existing SmokePing installations.
For new installations, we can use the included
`scripts/gen_smokeping.php` script to generate a Smokeping config file.
For new installations, we can use the `lnms` cli to generate a Smokeping
configuration file.
## New Smokeping installation
## Pre-Existing Smokeping Installation
### Install and integrate Smokeping - Debian/Ubuntu
If you have an existing smokeping server, follow the instructions, you only need
to look at [Configure LibreNMS - All Operating Systems](#configure-librenms-all-operating-systems).
This guide assumes you have already [installed
librenms](http://docs.librenms.org/Installation/Installing-LibreNMS/),
and is working with either **Apache** or
**nginx**.
## New Installation
Note: You may need to install `fcgiwrap` as well (at least with `nginx`).
All installation steps assume a clean configuration - if you have an existing
smokeping setup, you'll need to adapt these steps somewhat.
### Install Smokeping
### Install and integrate Smokeping Backend - RHEL, CentOS and alike
Smokeping is available via EPEL, which if you're running LibreNMS, you probably
already have. If you want to do something like run Smokeping on a seperate host
and ship data via RRCached though, here's the install command:
```bash
sudo apt update && sudo apt install smokeping
sudo yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
sudo yum install smokeping
```
## Configure SmokePing
Once installed, you should need a cron script installed to make sure that the
configuration file is updated. You can find an example in `misc/librenms-smokeping-rhel.example`.
Put this into /etc/cron.d/hourly, and mark it executable:
Smokeping has several configuration files. By default, these are
located in `/etc/smokeping/config.d/`
```
sudo cp /opt/librenms/misc/smokeping-rhel.example /etc/cron.hourly/librenms-smokeping
sudo chmod +x /etc/cron.hourly/librenms-smokeping
```
Finally, update the default configuration. Strip *everything* from the
`*** Probes ***` and `*** Targets ***` stanza's, and replace with:
```
*** Probes ***
@include /etc/smokeping/librenms-probes.conf
```
```
*** Targets ***
probe = FPing
menu = Top
title = Network Latency Grapher
remark = Welcome to the SmokePing website of <b>Insert Company Name Here</b>. \
Here you will learn all about the latency of our network.
@include /etc/smokeping/librenms-targets.conf
```
Note there may be other stanza's (possibly `*** Slaves ***`) between the
`*** Probes ***` and `*** Targets ***` stanza's - leave these intact.
Leave everything else untouched. If you need to add other confiruation, make
sure it comes *after* the LibreNMS configuration, and keep in mind that
Smokeping does not allow duplicate modules, and cares about the configuration
file sequence.
Once you're happy, manually kick off the cron once, then enable and start
smokeping:
```bash
sudo /etc/cron.hourly/librenms-smokeping
sudo systemctl enable --now smokeping
```
### Install and integrate Smokeping Backend - Ubuntu, Debian and alike
Smokeping is available via the default repositories.
```bash
sudo apt-get install smokeping
```
Once installed, you should need a cron script installed to make sure that the
configuration file is updated. You can find an example in `misc/librenms-smokeping-debian.example`.
Put this into /etc/cron.d/hourly, and mark it executable:
```
sudo cp /opt/librenms/misc/smokeping-debian.example /etc/cron.hourly/librenms-smokeping
sudo chmod +x /etc/cron.hourly/librenms-smokeping
```
Finally, update the default configuration. Strip *everything* from
`/etc/smokeping/config.d/Probes` and replace with:
```
*** Probes ***
@include /etc/smokeping/config.d/librenms-probes.conf
```
Strip *everything* from `/etc/smokeping/config.d/Targets` and replace with:
```
*** Targets ***
probe = FPing
menu = Top
title = Network Latency Grapher
remark = Welcome to the SmokePing website of <b>Insert Company Name Here</b>. \
Here you will learn all about the latency of our network.
@include /etc/smokeping/config.d/librenms-targets.conf
```
Leave everything else untouched. If you need to add other confiruation, make
sure it comes *after* the LibreNMS configuration, and keep in mind that
Smokeping does not allow duplicate modules, and cares about the configuration
file sequence.
## Configure LibreNMS - All Operating Systems
Edit `/opt/librenms/config.php` and add the following:
```php
$config['smokeping']['dir'] = '/var/lib/smokeping';
$config['smokeping']['pings'] = 20;
$config['smokeping']['probes'] = 2;
$config['smokeping']['integration'] = true;
$config['smokeping']['url'] = 'smokeping/'; // If you have a specific URL or path for smokeping
```
`dir` should match the location that smokeping writes RRD's to
`pings` should match the default smokeping value, default 20
`probes` should be the number of processes to spread pings over, default 2
These settings can also be set in the Web UI.
## Configure Smokeping's Web UI - Optional
This section covers the required configuration for your web server of
choice. This covers the required configuration for either Apache or Nginx.
LibreNMS does not need the Web UI - you can find the graphs in LibreNMS on the
*latency* tab.
### Apache Configuration - Ubuntu, Debian and alike
Edit the `General` configuration file's **Owner** and **contact**, and
**cgiurl hostname** details:
@ -43,80 +162,7 @@ contact = admin@ACME.xxx
cgiurl = http://yourlibrenms/cgi-bin/smokeping.cgi
```
### Configure Smokeping to use LibreNMS list of nodes
Add the following line to `/etc/smokeping/config` config file:
```bash
@include /etc/smokeping/config.d/librenms.conf
```
We will generate the conf file in the next step.
### Generate LibreNMS list of Smokeping Nodes
LibreNMS comes equipped with a script which exports our list of nodes
from LibreNMS into a configuration file in the format required by
Smokeping.
To generate the config file once:
```bash
(echo "+ LibreNMS"; php -f /opt/librenms/scripts/gen_smokeping.php) | sudo tee /etc/smokeping/config.d/librenms.conf
```
**However**, it is more desirable to set up a cron job which
regenerates our list of nodes and adds these into Smokeping. You can
add the following to the end of your librenms cron job, e.g. `nano /etc/cron.d/librenms`
**Ubuntu 16.04** Sample cron (will run daily at 00:05) :
```bash
05 00 * * * root (echo "+ LibreNMS"; php -f /opt/librenms/scripts/gen_smokeping.php) > /etc/smokeping/config.d/librenms.conf && systemctl reload smokeping.service >> /dev/null 2>&1
```
**Ubuntu 14.04** Sample cron (will run daily at 00:05):
```bash
05 00 * * * root (echo "+ LibreNMS"; php -f /opt/librenms/scripts/gen_smokeping.php) > /opt/smokeping/etc/librenms.conf && /opt/smokeping/bin/smokeping --reload >> /dev/null 2>&1
```
**Why echo "+ LibreNMS" ?**
This is in the cron job because the `gen_smokeping.php` script contains
```
menu = Top
title = Network Latency Grapher
```
Which can cause Smokeping to not start. `echo "+ LibreNMS"` prepends
this in our smokeping config file. We could remove the above from the
gen_smokeping script, however this may cause issues with LibreNMS
failing to update with `daily.sh` due config files being modified.
## Configure LibreNMS
Edit `/opt/librenms/config.php` and add the following:
**Note:** Make sure you point dir to the correct Smokeping data directory:
```php
$config['smokeping']['dir'] = '/var/lib/smokeping'; // Ubuntu 16.04 and newer Location
#$config['smokeping']['dir'] = '/opt/smokeping/data';
$config['smokeping']['pings'] = 20; // should be equal to "pings" in your smokeping config
$config['smokeping']['integration'] = true;
$config['smokeping']['url'] = 'smokeping/'; // If you have a specific URL or path for smokeping
```
## Configure web server
This section covers the required configuration for your web server of
choice. This covers the required configuration for either Apache or Nginx.
### Apache Configuration
Smokeping should automatically install an Apache config file in
Smokeping should automatically install an Apache configuration file in
`/etc/apache2/conf-available/`. Verify this using :
```bash
@ -124,7 +170,8 @@ librenms@librenms:~/scripts$ ls /etc/apache2/conf-available/ | grep smokeping
smokeping.conf
```
If you don't see `smokeping.conf` listed, you'll need to create a symlink for it:
If you don't see `smokeping.conf` listed, you'll need to create a symlink for
it:
```bash
ln -s /etc/smokeping/apache2.conf /etc/apache2/conf-available/smokeping.conf
@ -134,7 +181,7 @@ After creating the symlink, restart Apache with `sudo systemctl apache2 restart`
You should be able to load the Smokeping web interface at `http://yourhost/cgi-bin/smokeping.cgi`
### Nginx Configuration
### Nginx Configuration - Ubuntu, Debian and alike
This section assumes you have configured LibreNMS with Nginx as
specified in [Configure Nginx](https://docs.librenms.org/Installation/Installation-Ubuntu-1804-Nginx/).
@ -177,8 +224,8 @@ The following will configure Nginx to respond to `http://yourlibrenms/smokeping`
}
```
After saving the config file, verify your Nginx config file syntax is
OK with `sudo nginx -t`, then restart Nginx with `sudo systemctl restart nginx`
After saving the configuration file, verify your Nginx configuration file syntax
is OK with `sudo nginx -t`, then restart Nginx with `sudo systemctl restart nginx`
You should be able to load the Smokeping web interface at `http://yourhost/smokeping`
@ -216,128 +263,68 @@ Then you just need to add to your config `auth_basic` parameters
}
```
### Start SmokePing
## Common Problems
Use the below commands to start and verify smokeping is running.
### RRDs::update ERROR: opening ... Permission denied
There is a problem writing to the RRD directory. This is somewhat out of scope
of LibreNMS, but make sure that file permissions and SELinux labels allow the
smokeping user to write to the directory.
**Ubuntu 14.04:** `sudo service smokeping start`
If you're using RRDCacheD, make sure that the permissions are correct there too,
and that if you're using -B that the smokeping RRD's are inside the base
directory; update the smokeping rrd directory if required.
Verify: `sudo service smokeping status`
It's not recommended to run RRDCachedD without the -B switch.
**Ubuntu 16.04 and newer:** `sudo systemctl start smokeping`
Verify: `sudo systemctl status smokeping`
## Verify in LibreNMS
Within LibreNMS, you should now see the Smokeping graphs under Latency tab.
--------------
# Pre-Existing Smokeping Installation
The following section covers the requirements for an existing
SmokePing installation. The primary difference is this section does
not cover using the LibreNMS Smokeping config script, and assumes an
existing Smokeping server is set up and working correctly.
In terms of configuration, simply add the location of where smokeping
data such as RRD files are stored. If this is on a separate server,
ensure there is a mount point reachable, along with the server's hostname.
**Note:** The location should be the RRD root folder, NOT the
sub-directory such as network.
```php
$config['smokeping']['dir'] = '/var/lib/smokeping'; // Ubuntu 16.04 and newer Location
#$config['smokeping']['dir'] = '/opt/smokeping/data';
$config['smokeping']['pings'] = 20; // should be equal to "pings" in your smokeping config
$config['smokeping']['integration'] = true;
```
You should now see a new tab in your device page called ping.
# Issues
## `ERROR: /etc/smokeping/config.d/pathnames, line 1: File '/usr/sbin/sendmail' does not exist`
If you got this error at the end of the installation, simply edit
smokeping's config file like so:
```diff
nano /etc/smokeping/config.d/pathnames
-sendmail = /usr/sbin/sendmail
+#sendmail = /usr/sbin/sendmail
```
## Smokeping and RRDCached
If you are using the standard smokeping data dir
(`/etc/smokeping/data`) then you may need to alter the rrdcached
config slightly.
In the standard configuration the -B argument may have been used to
restrict rrdcached to read only from a single base dir.
If this is true, when you try an open one of the smokeping graphs from
within LibreNMS you will see something like this error at the end of
the rrdcached command:
```bash
ERROR: rrdcached: /var/lib/smokeping/<device name>.rrd: Permission denied
```
You will need to either change the dir in which smokeping saves its
rrd files to be the same as the main librenms dir or you can remove
the -B argument from the rrdcached config to allow it to read from
more than one dir.
### To remove the -B switch:
```bash
sudo nano /etc/default/rrdcached
```
then find:
```bash
BASE_OPTIONS=
```
If -B is in the list of arguments delete it.
### To store smokeping rrd in librenms rrd folder
#### Share RRDCached with LibreNMS
Move the RRD's and give smokeping access rights to the LibreNMS RRD directory:
```bash
sudo systemctl stop smokeping
sudo mv /var/lib/smokeping/ /opt/librenms/rrd/
sudo nano /etc/smokeping/config.d/pathnames
```
Then update the config file:
```bash
datadir = /opt/librenms/rrd/smokeping
dyndir = /opt/librenms/rrd/smokeping/__cgi
```
And give to smokeping rights to access files
```bash
sudo mv /var/lib/smokeping /opt/librenms/rrd/
sudo usermod -a -G librenms smokeping
```
Restart smokeping service
Update data directory in */etc/smokeping*:
```
datadir = /opt/librenms/rrd/smokeping
dyndir = /opt/librenms/rrd/smokeping/__cgi
```
Finally restart the smokeping service:
```bash
sudo systemctl start smokeping
```
Finally update smokeping rrd path in librenms
Remember to update *config.php* with the new locations.
```bash
nano /opt/librenms/config.php
```
### Probe FPing missing missing from the probes section
```php
$config['smokeping']['dir'] = '/opt/librenms/rrd/smokeping';
#$config['smokeping']['dir'] = '/var/lib/smokeping';
```
Take a look at the instructions again - something isn't correct in your
configuration.
### Section or variable already exists
Most likely, content wasn't fully removed from the `*** Probes ***`
`*** Targets***` stanza's as instructed.
If you're trying to integrate LibreNMS, smokeping *and* another source of
configuration, you're probably trying to redefine a module (e.g. '+ FPing' more
than once) or stanza. Otherwise, look again at the instructions.
### Mandatory variable 'probe' not defined
The target block must have a default probe. If you follow the instructions you
will have one. If you're trying to integrate LibreNMS, smokeping *and* another
source of configuration, you need to make sure there are no duplicate or missing
definitions.
### File '/usr/sbin/sendmail' does not exist`
If you got this error at the end of the installation, simply edit
commend out the sendmail entry in the configuration:
```diff
-sendmail = /usr/sbin/sendmail
+#sendmail = /usr/sbin/sendmail
```

View File

@ -4900,6 +4900,10 @@
"type": "integer",
"units": "pings"
},
"smokeping.probes": {
"default": 2,
"type": "integer"
},
"snmp.community": {
"group": "poller",
"section": "snmp",

View File

@ -0,0 +1,6 @@
#! /usr/bin/env bash
sudo -u librenms /opt/librenms/lnms smokeping:generate --targets > /etc/smokeping/config.d/librenms-targets.conf
sudo -u librenms /opt/librenms/lnms smokeping:generate --probes > /etc/smokeping/config.d/librenms-probes.conf
systemctl reload smokeping > /dev/null 2<&1

View File

@ -0,0 +1,6 @@
#! /usr/bin/env bash
sudo -u librenms /opt/librenms/lnms smokeping:generate --targets > /etc/smokeping/librenms-targets.conf
sudo -u librenms /opt/librenms/lnms smokeping:generate --probes > /etc/smokeping/librenms-probes.conf
systemctl reload smokeping > /dev/null 2<&1

View File

@ -41,7 +41,29 @@ return [
'os' => 'Specific OS to run tests on. Implies unit, --db, --snmpsim',
'quiet' => 'Hide output unless there is an error',
'snmpsim' => 'Use snmpsim for unit tests',
]
],
],
'smokeping:generate' => [
'args-nonsense' => 'Use one of --probes and --targets',
'config-insufficient' => 'In order to generate a smokeping configuration, you must have set "smokeping.probes", "fping", and "fping6" set in your configuration',
'dns-fail' => 'was not resolvable and was omitted from the configuration',
'description' => 'Generate a configuration suitable for use with smokeping',
'header-first' => 'This file was automatically generated by "lnms smokeping:generate',
'header-second' => 'Local changes may be overwritten without notice or backups being taken',
'header-third' => 'For more information see https://docs.librenms.org/Extensions/Smokeping/"',
'no-devices' => 'No eligible devices found - devices must not be disabled.',
'no-probes' => 'At least one probe is required.',
'options' => [
'probes' => 'Generate probe list - used for splitting the smokeping configuration into multiple files. Conflicts with "--targets"',
'targets' => 'Generate the target list - used for splitting the smokeping configuration into multiple files. Conflicts with "--probes"',
'no-header' => 'Don\'t add the boilerplate comment to the start of the generated file',
'no-dns' => 'Skip DNS lookups',
'single-process' => 'Only use a single process for smokeping',
'compat' => '[deprecated] Mimic the behaviour of gen_smokeping.php',
],
],
'translation:generate' => [
'description' => 'Generate updated json language files for use in the web frontend',
],
'user:add' => [
'description' => 'Add a local user, you can only log in with this user if auth is set to mysql',
@ -58,8 +80,5 @@ return [
'password-request' => "Please enter the user's password",
'success' => 'Successfully added user: :username',
'wrong-auth' => 'Warning! You will not be able to log in with this user because you are not using MySQL auth',
],
'translation:generate' => [
'description' => 'Generate updated json language files for use in the web frontend',
]
];

View File

@ -1,34 +1,37 @@
#!/usr/bin/env php
<?php
/*
* LibreNMS
*
* Copyright (c) 2015 Søren Friis Rosiak <sorenrosiak@gmail.com>
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version. Please see LICENSE.txt at the top level of
* the source code distribution for details.
*/
/**
* gen_smokeping.php
*
* Legacy wrapper for generating smokeping configurations
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2020 Adam Bishop
* @author Adam Bishop <adam@omega.org.uk>
*/
$init_modules = array();
require realpath(__DIR__ . '/..') . '/includes/init.php';
?>
if (php_sapi_name() === 'cli') {
$init_modules = [];
require realpath(__DIR__ . '/..') . '/includes/init.php';
menu = Top
title = Network Latency Grapher
<?php
foreach (dbFetchRows("SELECT `type` FROM `devices` WHERE `disabled` = 0 AND `type` != '' GROUP BY `type`") as $groups) {
//Dot and space need to be replaced, since smokeping doesn't accept it at this level
echo '+ ' . str_replace(['.', ' '], '_', $groups['type']) . PHP_EOL;
echo 'menu = ' . $groups['type'] . PHP_EOL;
echo 'title = ' . $groups['type'] . PHP_EOL;
foreach (dbFetchRows("SELECT `hostname` FROM `devices` WHERE `type` = ? AND `disabled` = 0", array($groups['type'])) as $devices) {
echo '++ ' . str_replace(['.', ' '], '_', $devices['hostname']) . PHP_EOL;
echo 'menu = ' . $devices['hostname'] . PHP_EOL;
echo 'title = ' . $devices['hostname'] . PHP_EOL;
echo 'host = ' . $devices['hostname'] . PHP_EOL . PHP_EOL;
}
$return = \Artisan::call('smokeping:generate --targets --no-header --no-dns --single-process --compat');
echo \Artisan::Output();
exit($return);
}
exit();

393
tests/SmokepingCliTest.php Normal file
View File

@ -0,0 +1,393 @@
<?php
/**
* SmokepingCliTest.php
*
* Checks that smokeping configuration output is consistent
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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/>.
*
* @package LibreNMS
* @link https://librenms.org
* @copyright 2020 Adam Bishop
* @author Adam Bishop <adam@omega.org.uk>
*/
namespace LibreNMS\Tests;
use app\Console\Commands\SmokepingGenerateCommand;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Str;
use Illuminate\Support\arr;
use Illuminate\Translation\Translator;
use LibreNMS\Config;
use App\Models\Device;
class SmokepingCliTest extends DBTestCase
{
use DatabaseTransactions;
protected $groups = [
'Le23HKVMvN' => [
'Cl09bZU4sn' => [
'transport' => 'udp'
],
'c559TvthzY' => [
'transport' => 'udp6'
],
'sNtzSdxdw8' => [
'transport' => 'udp6'
],
'10.0.0.3' => [
'transport' => 'udp'
],
'2600::' => [
'transport' => 'udp'
]
],
'Psv9oZcxdC' => [
'oHiPfLzrmU' => [
'transport' => 'udp'
],
'kEn7hZ7N37' => [
'transport' => 'udp6'
],
'PcbZ5FKtS3' => [
'transport' => 'udp6'
],
'192.168.1.1' => [
'transport' => 'udp'
],
'fe80::' => [
'transport' => 'udp'
]
],
'4diY0pWFik' => [
'example.org' => [
'transport' => 'udp'
],
'host_with_under_score.example.org' => [
'transport' => 'udp6'
],
]
];
private $instance = null;
public function setUp(): void
{
// We need an app instance available for these tests to load the translation machinary
$this->app = $this->createApplication();
$this->instance = new SmokePingGenerateCommand();
$this->instance->disableDNSLookup();
parent::setUp();
}
public function testNonsense()
{
$this->assertNotEquals(0, \Artisan::call('smokeping:generate --probes --targets --no-header'));
$this->assertNotEquals(0, \Artisan::call('smokeping:generate --probes --targets --single-process'));
$this->assertNotEquals(0, \Artisan::call('smokeping:generate --probes --targets'));
$this->assertNotEquals(0, \Artisan::call('smokeping:generate --no-header'));
$this->assertNotEquals(0, \Artisan::call('smokeping:generate --single-process'));
$this->assertNotEquals(0, \Artisan::call('smokeping:generate'));
$this->expectException('RuntimeException');
\Artisan::call('smokeping:generate --foobar');
}
public function testBuildHeader()
{
$warnings = ['rpPvjwdI0M0hlg6ZgZA', '2aUjOMql6ZWN7H0DthWDOyCvkXs0kVShhnASnc', 'HYMWbDplSW9PLNK9o9tySeJF4Ac61uTRHUUxxBXHiCl'];
$this->instance->setWarning($warnings[0]);
$this->instance->setWarning($warnings[1]);
$this->instance->setWarning($warnings[2]);
$header = $this->instance->buildHeader(false, false);
$this->assertEmpty(array_pop($header));
foreach ($header as $line) {
$this->assertTrue(Str::startsWith($line, '# '), $line);
$this->assertTrue(Str::contains($line, array_merge($warnings, [__('commands.smokeping:generate.header-first'), __('commands.smokeping:generate.header-second'), __('commands.smokeping:generate.header-third')])), $line);
}
$this->assertEquals($this->instance->buildHeader(true, false), []);
}
public function testAssembleProbes()
{
$tests = [0, -1];
foreach ($tests as $test) {
$this->assertEmpty($this->instance->assembleProbes($test));
}
}
public function testBuildProbe()
{
$saved = ['+ Pl0JnP2vfE',
' binary = /usr/bin/G28F3fFeew',
' blazemode = true',
'++ Xq93BufZAU',
'++ etzY41dSRj0',
'++ etzY41dSRj1',
'++ etzY41dSRj2',
''
];
$output = $this->instance->buildProbes('Pl0JnP2vfE', 'Xq93BufZAU', 'etzY41dSRj', '/usr/bin/G28F3fFeew', 3);
$this->assertEquals(implode(PHP_EOL, $saved), implode(PHP_EOL, $output));
}
public function testBuildTargets()
{
$saved = [
'+ Le23HKVMvN',
' menu = Le23HKVMvN',
' title = Le23HKVMvN',
'',
'++ Cl09bZU4sn',
' menu = Cl09bZU4sn',
' title = Cl09bZU4sn',
' probe = lnmsFPing-0',
' host = Cl09bZU4sn',
'',
'++ c559TvthzY',
' menu = c559TvthzY',
' title = c559TvthzY',
' probe = lnmsFPing6-0',
' host = c559TvthzY',
'',
'++ sNtzSdxdw8',
' menu = sNtzSdxdw8',
' title = sNtzSdxdw8',
' probe = lnmsFPing6-1',
' host = sNtzSdxdw8',
'',
'++ 10_0_0_3',
' menu = 10.0.0.3',
' title = 10.0.0.3',
' probe = lnmsFPing-1',
' host = 10.0.0.3',
'',
'++ 2600::',
' menu = 2600::',
' title = 2600::',
' probe = lnmsFPing-2',
' host = 2600::',
'',
'+ Psv9oZcxdC',
' menu = Psv9oZcxdC',
' title = Psv9oZcxdC',
'',
'++ oHiPfLzrmU',
' menu = oHiPfLzrmU',
' title = oHiPfLzrmU',
' probe = lnmsFPing-3',
' host = oHiPfLzrmU',
'',
'++ kEn7hZ7N37',
' menu = kEn7hZ7N37',
' title = kEn7hZ7N37',
' probe = lnmsFPing6-2',
' host = kEn7hZ7N37',
'',
'++ PcbZ5FKtS3',
' menu = PcbZ5FKtS3',
' title = PcbZ5FKtS3',
' probe = lnmsFPing6-3',
' host = PcbZ5FKtS3',
'',
'++ 192_168_1_1',
' menu = 192.168.1.1',
' title = 192.168.1.1',
' probe = lnmsFPing-0',
' host = 192.168.1.1',
'',
'++ fe80::',
' menu = fe80::',
' title = fe80::',
' probe = lnmsFPing-1',
' host = fe80::',
'',
'+ 4diY0pWFik',
' menu = 4diY0pWFik',
' title = 4diY0pWFik',
'',
'++ example_org',
' menu = example.org',
' title = example.org',
' probe = lnmsFPing-2',
' host = example.org',
'',
'++ host_with_under_score_example_org',
' menu = host_with_under_score.example.org',
' title = host_with_under_score.example.org',
' probe = lnmsFPing6-0',
' host = host_with_under_score.example.org',
''
];
$output = $this->instance->buildTargets($this->groups, 4, false);
$this->assertEquals(implode(PHP_EOL, $saved), implode(PHP_EOL, $output));
}
public function testSingleProccess()
{
$saved = [
'+ Le23HKVMvN',
' menu = Le23HKVMvN',
' title = Le23HKVMvN',
'',
'++ Cl09bZU4sn',
' menu = Cl09bZU4sn',
' title = Cl09bZU4sn',
' host = Cl09bZU4sn',
'',
'++ c559TvthzY',
' menu = c559TvthzY',
' title = c559TvthzY',
' host = c559TvthzY',
'',
'++ sNtzSdxdw8',
' menu = sNtzSdxdw8',
' title = sNtzSdxdw8',
' host = sNtzSdxdw8',
'',
'++ 10_0_0_3',
' menu = 10.0.0.3',
' title = 10.0.0.3',
' host = 10.0.0.3',
'',
'++ 2600::',
' menu = 2600::',
' title = 2600::',
' host = 2600::',
'',
'+ Psv9oZcxdC',
' menu = Psv9oZcxdC',
' title = Psv9oZcxdC',
'',
'++ oHiPfLzrmU',
' menu = oHiPfLzrmU',
' title = oHiPfLzrmU',
' host = oHiPfLzrmU',
'',
'++ kEn7hZ7N37',
' menu = kEn7hZ7N37',
' title = kEn7hZ7N37',
' host = kEn7hZ7N37',
'',
'++ PcbZ5FKtS3',
' menu = PcbZ5FKtS3',
' title = PcbZ5FKtS3',
' host = PcbZ5FKtS3',
'',
'++ 192_168_1_1',
' menu = 192.168.1.1',
' title = 192.168.1.1',
' host = 192.168.1.1',
'',
'++ fe80::',
' menu = fe80::',
' title = fe80::',
' host = fe80::',
'',
'+ 4diY0pWFik',
' menu = 4diY0pWFik',
' title = 4diY0pWFik',
'',
'++ example_org',
' menu = example.org',
' title = example.org',
' host = example.org',
'',
'++ host_with_under_score_example_org',
' menu = host_with_under_score.example.org',
' title = host_with_under_score.example.org',
' host = host_with_under_score.example.org',
''
];
$output = $this->instance->buildTargets($this->groups, 4, true);
$this->assertEquals(implode(PHP_EOL, $saved), implode(PHP_EOL, $output));
}
public function testCompareLegacy()
{
$data = [];
// Generate a ridiculous number of random devices for testing
foreach (range(1, 1000) as $i) {
$device = factory(Device::class)->create();
$data[$device->type][] = $device->hostname;
}
// Sort the data so the output matches the one from the database
$data = Arr::sortRecursive($data);
// Disable DNS lookups
\Artisan::call('smokeping:generate --targets --no-header --no-dns --single-process --compat');
$new = \Artisan::Output();
$old = $this->legacyAlgo($data);
$this->assertEquals($this->canonicalise($new), $this->canonicalise($old));
}
public function legacyAlgo($data)
{
// This is the code taken from the old gen_smokeping script, with echos and sql queries replaced
$lines = [];
$lines[] = '' . PHP_EOL;
$lines[] = 'menu = Top' . PHP_EOL;
$lines[] = 'title = Network Latency Grapher' . PHP_EOL;
$lines[] = '' . PHP_EOL;
foreach ($data as $groupName => $devices) {
//Dot and space need to be replaced, since smokeping doesn't accept it at this level
$lines[] = '+ ' . str_replace(['.', ' '], '_', $groupName) . PHP_EOL;
$lines[] = 'menu = ' . $groupName . PHP_EOL;
$lines[] = 'title = ' . $groupName . PHP_EOL;
foreach ($devices as $device) {
$lines[] = '++ ' . str_replace(['.', ' '], '_', $device) . PHP_EOL;
$lines[] = 'menu = ' . $device . PHP_EOL;
$lines[] = 'title = ' . $device . PHP_EOL;
$lines[] = 'host = ' . $device . PHP_EOL . PHP_EOL;
}
}
// Return a string as we need to evaluate the entire thing as a block
return implode('', $lines);
}
public function canonicalise($input)
{
$input = explode(PHP_EOL, $input);
$output = [];
foreach ($input as $line) {
if (trim($line) !== '') {
$output[] = trim($line);
}
}
return implode(PHP_EOL, $output);
}
}