diff --git a/LibreNMS/Util/Snmpsim.php b/LibreNMS/Util/Snmpsim.php index 700f7b91cc..61ccc70b46 100644 --- a/LibreNMS/Util/Snmpsim.php +++ b/LibreNMS/Util/Snmpsim.php @@ -173,7 +173,7 @@ class Snmpsim unset($this->proc); } - private function findSnmpsimd() + public function findSnmpsimd() { $cmd = Config::locateBinary('snmpsimd'); if (! is_executable($cmd)) { diff --git a/app/Console/Commands/DevSimulate.php b/app/Console/Commands/DevSimulate.php new file mode 100644 index 0000000000..b073c70372 --- /dev/null +++ b/app/Console/Commands/DevSimulate.php @@ -0,0 +1,131 @@ +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; + } +} diff --git a/doc/Developing/os/Test-Units.md b/doc/Developing/os/Test-Units.md index 1f6eb5b66d..3b293a3218 100644 --- a/doc/Developing/os/Test-Units.md +++ b/doc/Developing/os/Test-Units.md @@ -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 ` -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 diff --git a/resources/lang/en/commands.php b/resources/lang/en/commands.php index e1600c68cb..e271907d33 100644 --- a/resources/lang/en/commands.php +++ b/resources/lang/en/commands.php @@ -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',