Developer device simulation (#12577)

* Developer device simulation
lnms dev:simulate
handy helper to start snmpsim with test data and optionally add and remove a device to LibreNMS

* doc update
This commit is contained in:
Tony Murray 2021-03-03 21:42:49 -06:00 committed by GitHub
parent 0a8f24c5b1
commit e4e2113585
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 157 additions and 9 deletions

View File

@ -173,7 +173,7 @@ class Snmpsim
unset($this->proc);
}
private function findSnmpsimd()
public function findSnmpsimd()
{
$cmd = Config::locateBinary('snmpsimd');
if (! is_executable($cmd)) {

View File

@ -0,0 +1,131 @@
<?php
namespace App\Console\Commands;
use App\Console\LnmsCommand;
use App\Models\Device;
use Illuminate\Support\Str;
use LibreNMS\Util\Snmpsim;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Process\Process;
class DevSimulate extends LnmsCommand
{
protected $name = 'dev:simulate';
protected $developer = true;
/**
* @var Snmpsim
*/
protected $snmpsim = null;
/**
* The console command description.
*
* @var string
*/
protected $description = 'Simulate devices using test data';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
$this->addArgument('file', InputArgument::OPTIONAL);
$this->addOption('multiple', 'm', InputOption::VALUE_NONE);
$this->addOption('remove', 'r', InputOption::VALUE_NONE);
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$this->snmpsim = new Snmpsim();
$snmprec_dir = $this->snmpsim->getDir();
$listen = $this->snmpsim->getIp() . ':' . $this->snmpsim->getPort();
$snmpsim = new Process([
$this->snmpsim->findSnmpsimd(),
"--data-dir=$snmprec_dir",
"--agent-udpv4-endpoint=$listen",
]);
$snmpsim->setTimeout(null);
$snmpsim->run(function ($type, $buffer) use ($listen) {
if (Process::ERR === $type) {
if (Str::contains($buffer, $listen)) {
$this->line(trim($buffer));
$this->started();
$this->line(trans('commands.dev:simulate.exit'));
}
}
});
return 0;
}
private function started()
{
if ($file = $this->argument('file')) {
$this->addDevice($file);
}
}
private function addDevice($community)
{
$hostname = $this->option('new') ? $community : 'snmpsim';
$device = Device::firstOrNew(['hostname' => $hostname]);
$action = $device->exists ? 'updated' : 'added';
$device->overwrite_ip = $this->snmpsim->getIp();
$device->port = $this->snmpsim->getPort();
$device->snmpver = 'v2c';
$device->transport = 'udp';
$device->community = $community;
$device->last_discovered = null;
$device->status_reason = '';
$device->save();
$this->info(trans("commands.dev:simulate.$action", ['hostname' => $device->hostname, 'id' => $device->device_id]));
// set up removal shutdown function if requested
if ($this->option('remove')) {
$this->queueRemoval($device->device_id);
}
}
private function queueRemoval($device_id)
{
if (function_exists('pcntl_signal')) {
pcntl_signal(SIGINT, function () {
exit(); // exit normally on SIGINT
});
}
register_shutdown_function(function () use ($device_id) {
Device::findOrNew($device_id)->delete();
$this->info(trans('commands.dev:simulate.removed', ['id' => $device_id]));
exit();
});
}
public function completeArgument($name, $value)
{
if ($name == 'file') {
return collect(glob(base_path('tests/snmpsim/*.snmprec')))->map(function ($file) {
return basename($file, '.snmprec');
})->filter(function ($snmprec) use ($value) {
return ! $value || Str::startsWith($snmprec, $value);
})->all();
}
return false;
}
}

View File

@ -16,7 +16,7 @@ make sure it is modified in a consistent manner.
> testing. For OS discovery, we can mock snmpsim, but for other tests
> you will need it installed and functioning. We run snmpsim during
> our integration tests, but not by default when running
> `./lnms dev:check`. You can install snmpsim with the
> `lnms dev:check`. You can install snmpsim with the
> command `pip3 install snmpsim`.
## Capturing test data
@ -58,23 +58,26 @@ After you have the data you need in the snmprec file, you can just use save-test
directory. This will read composer.json and install any dependencies required.
After you have saved your test data, you should run
`./lnms dev:check` verify they pass.
`lnms dev:check` verify they pass.
To run the full suite of tests enable database and snmpsim reliant
tests: `./lnms dev:check unit --db --snmpsim`
tests: `lnms dev:check unit --db --snmpsim`
### Specific OS
`./lnms dev:check unit -o osname`
`lnms dev:check unit -o osname`
### Specific Module
`./lnms dev:check unit -m modulename`
`lnms dev:check unit -m modulename`
## Using snmpsim for testing
You can run snmpsim to access test data by running
`./scripts/collect-snmp-data.php --snmpsim`
```bash
lnms dev:simulate
```
You may then run snmp queries against it using the os (and variant) as
the community and 127.1.6.1:1161 as the host.
@ -143,7 +146,7 @@ must use a variant to store your test data (-v).
1. If there is additional snmp data required, run `./scripts/collect-snmp-data.php -h 42`
1. Run `./scripts/save-test-data.php -o example-os` to update the dumped database data.
1. Review data. If you modified the snmprec or code (don't modify json manually) run `./scripts/save-test-data.php -o example-os -m os`
1. Run `./lnms dev:check unit --db --snmpsim`
1. Run `lnms dev:check unit --db --snmpsim`
1. If the tests succeed submit a pull request
### Additional module support or test data
@ -153,5 +156,5 @@ must use a variant to store your test data (-v).
more data to the snmprec file
1. Review data. If you modified the snmprec (don't modify json
manually) run `./scripts/save-test-data.php -o example-os -m <module>`
1. Run `./lnms dev:check unit --db --snmpsim`
1. Run `lnms dev:check unit --db --snmpsim`
1. If the tests succeed submit a pull request

View File

@ -43,6 +43,20 @@ return [
'snmpsim' => 'Use snmpsim for unit tests',
],
],
'dev:simulate' => [
'description' => 'Simulate devices using test data',
'arguments' => [
'file' => 'The file name (only base name) of the snmprec file to update or add to LibreNMS. If file not specified, no device will be added or updated.',
],
'options' => [
'multiple' => 'Use community name for hostname instead of snmpsim',
'remove' => 'Remove the device after stopping',
],
'added' => 'Device :hostname (:id) added',
'exit' => 'Ctrl-C to stop',
'removed' => 'Device :id removed',
'updated' => 'Device :hostname (:id) updated',
],
'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',