From 1752d1efd408af341a93676fe8c1f91e9267994a Mon Sep 17 00:00:00 2001 From: Tony Murray Date: Tue, 16 Nov 2021 16:59:46 -0600 Subject: [PATCH] Poller command rewrite (#13414) * core WIP * try to finish up * trim space too and a couple of cleanups * update test data * put escapes back * another net-snmp difference * correct copy paste error * WIP * Use new code YAY * a tiny bit more * Kind of working * Handle manual modules correctly * convert core to modern module * Only save metrics if modules is not overridden * correct module exists check * database error handling * debug handling * restore bad changes * Introduce Actions RunAlertRulesAction UpdateDeviceGroupsAction * tweaks to output * Fix some issues in outside code * Style fixes * fixes to module status checks * typehints! * Use logger only and DI * OS module not named correctly * Work on quiet output a bit more * generically don't change output when disabling debug if the driver is already stack * Fix missing $device variable for legacy os polling Fix missing dbFacile functions when no legacy modules polled in RunAlertRulesAction * restore legacy os module shim * use the new poller code for tests * PollingDevice event * Fix some issues and enable/disable error reporting around legacy modules * typehints * fully update baseline * Use Process for version commands so we don't leak debug output. * don't detect rrdtool version in ci every time * style fixes * Warning fixes * more fixes * re-update baseline * remove diff noise * fix up alerts --- LibreNMS/Alert/AlertRules.php | 14 +- LibreNMS/Cache/Device.php | 8 + LibreNMS/Data/Store/Rrd.php | 2 +- LibreNMS/Exceptions/PollerException.php | 30 ++ LibreNMS/Interfaces/Module.php | 6 +- LibreNMS/Modules/Isis.php | 6 +- LibreNMS/Modules/LegacyModule.php | 65 +++ LibreNMS/Modules/Mempools.php | 2 +- LibreNMS/Modules/Mpls.php | 6 +- LibreNMS/Modules/Nac.php | 6 +- LibreNMS/Modules/{OS.php => Os.php} | 19 +- LibreNMS/Modules/PrinterSupplies.php | 6 +- LibreNMS/Modules/Slas.php | 6 +- LibreNMS/OS.php | 2 +- LibreNMS/Poller.php | 374 ++++++++++++++++++ LibreNMS/Util/Debug.php | 56 ++- LibreNMS/Util/Dns.php | 14 + LibreNMS/Util/Git.php | 15 +- LibreNMS/Util/ModuleTestHelper.php | 20 +- LibreNMS/Util/StringHelpers.php | 4 +- LibreNMS/Util/Version.php | 40 +- LibreNMS/Validations/Python.php | 2 +- app/Action.php | 41 ++ app/Actions/Alerts/RunAlertRulesAction.php | 55 +++ .../Device/UpdateDeviceGroupsAction.php | 85 ++++ app/Console/Commands/DevicePoll.php | 76 ++++ app/Console/LnmsCommand.php | 14 +- app/Events/DevicePolled.php | 39 ++ app/Events/PollingDevice.php | 39 ++ app/Http/Controllers/AboutController.php | 2 +- app/Listeners/CheckAlerts.php | 37 ++ app/Listeners/UpdateDeviceGroups.php | 43 ++ app/Models/DeviceGroup.php | 48 --- app/Observers/DeviceObserver.php | 2 +- app/Polling/Measure/MeasurementManager.php | 92 +++-- app/Providers/AppServiceProvider.php | 7 + app/Providers/EventServiceProvider.php | 11 +- config/logging.php | 18 +- discovery.php | 1 + includes/common.php | 10 +- includes/discovery/os.inc.php | 2 +- includes/functions.php | 2 +- includes/polling/os.inc.php | 2 +- includes/snmp.inc.php | 3 +- phpstan-baseline.neon | 125 +----- poller.php | 5 +- resources/lang/en/commands.php | 14 + routes/console.php | 3 +- tests/OSModulesTest.php | 12 +- 49 files changed, 1184 insertions(+), 307 deletions(-) create mode 100644 LibreNMS/Exceptions/PollerException.php create mode 100644 LibreNMS/Modules/LegacyModule.php rename LibreNMS/Modules/{OS.php => Os.php} (90%) create mode 100644 LibreNMS/Poller.php create mode 100644 app/Action.php create mode 100644 app/Actions/Alerts/RunAlertRulesAction.php create mode 100644 app/Actions/Device/UpdateDeviceGroupsAction.php create mode 100644 app/Console/Commands/DevicePoll.php create mode 100644 app/Events/DevicePolled.php create mode 100644 app/Events/PollingDevice.php create mode 100644 app/Listeners/CheckAlerts.php create mode 100644 app/Listeners/UpdateDeviceGroups.php diff --git a/LibreNMS/Alert/AlertRules.php b/LibreNMS/Alert/AlertRules.php index 3f36563c06..d60c923880 100644 --- a/LibreNMS/Alert/AlertRules.php +++ b/LibreNMS/Alert/AlertRules.php @@ -33,6 +33,7 @@ namespace LibreNMS\Alert; use Carbon\Carbon; use LibreNMS\Enum\AlertState; +use Log; class AlertRules { @@ -57,7 +58,7 @@ class AlertRules } //Checks each rule. foreach (AlertUtil::getRules($device_id) as $rule) { - c_echo('Rule %p#' . $rule['id'] . ' (' . $rule['name'] . '):%n '); + Log::info('Rule %p#' . $rule['id'] . ' (' . $rule['name'] . '):%n ', ['color' => true]); $extra = json_decode($rule['extra'], true); if (isset($extra['invert'])) { $inv = (bool) $extra['invert']; @@ -90,9 +91,9 @@ class AlertRules $current_state = dbFetchCell('SELECT state FROM alerts WHERE rule_id = ? AND device_id = ? ORDER BY id DESC LIMIT 1', [$rule['id'], $device_id]); if ($doalert) { if ($current_state == AlertState::ACKNOWLEDGED) { - c_echo('Status: %ySKIP'); + Log::info('Status: %ySKIP%n', ['color' => true]); } elseif ($current_state >= AlertState::ACTIVE) { - c_echo('Status: %bNOCHG'); + Log::info('Status: %bNOCHG%n', ['color' => true]); // NOCHG here doesn't mean no change full stop. It means no change to the alert state // So we update the details column with any fresh changes to the alert output we might have. $alert_log = dbFetchRow('SELECT alert_log.id, alert_log.details FROM alert_log,alert_rules WHERE alert_log.rule_id = alert_rules.id && alert_log.device_id = ? && alert_log.rule_id = ? && alert_rules.disabled = 0 @@ -113,12 +114,12 @@ class AlertRules } else { dbUpdate(['state' => AlertState::ACTIVE, 'open' => 1, 'timestamp' => Carbon::now()], 'alerts', 'device_id = ? && rule_id = ?', [$device_id, $rule['id']]); } - c_echo(PHP_EOL . 'Status: %rALERT'); + Log::info(PHP_EOL . 'Status: %rALERT%n', ['color' => true]); } } } else { if (! is_null($current_state) && $current_state == AlertState::RECOVERED) { - c_echo('Status: %bNOCHG'); + Log::info('Status: %bNOCHG%n', ['color' => true]); } else { if (dbInsert(['state' => AlertState::RECOVERED, 'device_id' => $device_id, 'rule_id' => $rule['id']], 'alert_log')) { if (is_null($current_state)) { @@ -127,11 +128,10 @@ class AlertRules dbUpdate(['state' => AlertState::RECOVERED, 'open' => 1, 'note' => '', 'timestamp' => Carbon::now()], 'alerts', 'device_id = ? && rule_id = ?', [$device_id, $rule['id']]); } - c_echo(PHP_EOL . 'Status: %gOK'); + Log::info(PHP_EOL . 'Status: %gOK%n', ['color' => true]); } } } - c_echo('%n' . PHP_EOL); } } } diff --git a/LibreNMS/Cache/Device.php b/LibreNMS/Cache/Device.php index a6429fb997..c1c1273775 100644 --- a/LibreNMS/Cache/Device.php +++ b/LibreNMS/Cache/Device.php @@ -51,6 +51,14 @@ class Device $this->primary = $device_id; } + /** + * Check if a primary device is set + */ + public function hasPrimary(): bool + { + return $this->primary !== null; + } + /** * Get a device by device_id * diff --git a/LibreNMS/Data/Store/Rrd.php b/LibreNMS/Data/Store/Rrd.php index 601661e331..e89bc7da6d 100644 --- a/LibreNMS/Data/Store/Rrd.php +++ b/LibreNMS/Data/Store/Rrd.php @@ -168,7 +168,7 @@ class Rrd extends BaseDatastore $fields = array_filter($fields, function ($key) use ($rrd_def) { $valid = $rrd_def->isValidDataset($key); if (! $valid) { - Log::warning("RRD warning: unused data sent $key"); + Log::debug("RRD warning: unused data sent $key"); } return $valid; diff --git a/LibreNMS/Exceptions/PollerException.php b/LibreNMS/Exceptions/PollerException.php new file mode 100644 index 0000000000..db26e275d1 --- /dev/null +++ b/LibreNMS/Exceptions/PollerException.php @@ -0,0 +1,30 @@ +. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2021 Tony Murray + * @author Tony Murray + */ + +namespace LibreNMS\Exceptions; + +class PollerException extends \Exception +{ +} diff --git a/LibreNMS/Interfaces/Module.php b/LibreNMS/Interfaces/Module.php index e6d90d8f81..8c394bd7a0 100644 --- a/LibreNMS/Interfaces/Module.php +++ b/LibreNMS/Interfaces/Module.php @@ -33,7 +33,7 @@ interface Module * Discover this module. Heavier processes can be run here * Run infrequently (default 4 times a day) * - * @param OS $os + * @param \LibreNMS\OS $os */ public function discover(OS $os); @@ -42,7 +42,7 @@ interface Module * Try to keep this efficient and only run if discovery has indicated there is a reason to run. * Run frequently (default every 5 minutes) * - * @param OS $os + * @param \LibreNMS\OS $os */ public function poll(OS $os); @@ -50,7 +50,7 @@ interface Module * Remove all DB data for this module. * This will be run when the module is disabled. * - * @param OS $os + * @param \LibreNMS\OS $os */ public function cleanup(OS $os); } diff --git a/LibreNMS/Modules/Isis.php b/LibreNMS/Modules/Isis.php index 4a07fafbda..de429d9475 100644 --- a/LibreNMS/Modules/Isis.php +++ b/LibreNMS/Modules/Isis.php @@ -51,7 +51,7 @@ class Isis implements Module * Discover this module. Heavier processes can be run here * Run infrequently (default 4 times a day) * - * @param OS $os + * @param \LibreNMS\OS $os */ public function discover(OS $os) { @@ -68,7 +68,7 @@ class Isis implements Module * Try to keep this efficient and only run if discovery has indicated there is a reason to run. * Run frequently (default every 5 minutes) * - * @param OS $os + * @param \LibreNMS\OS $os */ public function poll(OS $os) { @@ -89,7 +89,7 @@ class Isis implements Module * Remove all DB data for this module. * This will be run when the module is disabled. * - * @param OS $os + * @param Os $os */ public function cleanup(OS $os) { diff --git a/LibreNMS/Modules/LegacyModule.php b/LibreNMS/Modules/LegacyModule.php new file mode 100644 index 0000000000..a565487f2e --- /dev/null +++ b/LibreNMS/Modules/LegacyModule.php @@ -0,0 +1,65 @@ +. + * + * @link https://www.librenms.org + * + * @copyright 2021 Tony Murray + * @author Tony Murray + */ + +namespace LibreNMS\Modules; + +use LibreNMS\Interfaces\Module; +use LibreNMS\OS; +use LibreNMS\Util\Debug; + +class LegacyModule implements Module +{ + /** + * @var string + */ + private $name; + + public function __construct(string $name) + { + $this->name = $name; + } + + public function discover(OS $os): void + { + // TODO: Implement discover() method. + } + + public function poll(OS $os): void + { + $device = &$os->getDeviceArray(); + $device['attribs'] = $os->getDevice()->attribs->toArray(); + Debug::disableErrorReporting(); // ignore errors in legacy code + + include_once base_path('includes/dbFacile.php'); + include base_path("includes/polling/$this->name.inc.php"); + + Debug::enableErrorReporting(); // and back to normal + } + + public function cleanup(OS $os): void + { + // TODO: Implement cleanup() method. + } +} diff --git a/LibreNMS/Modules/Mempools.php b/LibreNMS/Modules/Mempools.php index 1f5282e9d1..2e9d9ea29f 100644 --- a/LibreNMS/Modules/Mempools.php +++ b/LibreNMS/Modules/Mempools.php @@ -108,7 +108,7 @@ class Mempools implements Module } /** - * @param OS $os + * @param \LibreNMS\OS $os * @param \Illuminate\Support\Collection $mempools * @return \Illuminate\Support\Collection */ diff --git a/LibreNMS/Modules/Mpls.php b/LibreNMS/Modules/Mpls.php index 69ee7001d4..6cf9cbf878 100644 --- a/LibreNMS/Modules/Mpls.php +++ b/LibreNMS/Modules/Mpls.php @@ -42,7 +42,7 @@ class Mpls implements Module * Discover this module. Heavier processes can be run here * Run infrequently (default 4 times a day) * - * @param OS $os + * @param \LibreNMS\OS $os */ public function discover(OS $os) { @@ -88,7 +88,7 @@ class Mpls implements Module * Try to keep this efficient and only run if discovery has indicated there is a reason to run. * Run frequently (default every 5 minutes) * - * @param OS $os + * @param \LibreNMS\OS $os */ public function poll(OS $os) { @@ -151,7 +151,7 @@ class Mpls implements Module * Remove all DB data for this module. * This will be run when the module is disabled. * - * @param OS $os + * @param Os $os */ public function cleanup(OS $os) { diff --git a/LibreNMS/Modules/Nac.php b/LibreNMS/Modules/Nac.php index 538840dafe..41501ad37a 100644 --- a/LibreNMS/Modules/Nac.php +++ b/LibreNMS/Modules/Nac.php @@ -37,7 +37,7 @@ class Nac implements Module * Discover this module. Heavier processes can be run here * Run infrequently (default 4 times a day) * - * @param OS $os + * @param Os $os */ public function discover(OS $os) { @@ -49,7 +49,7 @@ class Nac implements Module * Try to keep this efficient and only run if discovery has indicated there is a reason to run. * Run frequently (default every 5 minutes) * - * @param OS $os + * @param \LibreNMS\OS $os */ public function poll(OS $os) { @@ -82,7 +82,7 @@ class Nac implements Module * Remove all DB data for this module. * This will be run when the module is disabled. * - * @param OS $os + * @param \LibreNMS\OS $os */ public function cleanup(OS $os) { diff --git a/LibreNMS/Modules/OS.php b/LibreNMS/Modules/Os.php similarity index 90% rename from LibreNMS/Modules/OS.php rename to LibreNMS/Modules/Os.php index a2eaaf4085..2fdb49b9a1 100644 --- a/LibreNMS/Modules/OS.php +++ b/LibreNMS/Modules/Os.php @@ -30,9 +30,9 @@ use LibreNMS\Interfaces\Module; use LibreNMS\Interfaces\Polling\OSPolling; use LibreNMS\Util\Url; -class OS implements Module +class Os implements Module { - public function discover(\LibreNMS\OS $os) + public function discover(\LibreNMS\OS $os): void { $this->updateLocation($os); $this->sysContact($os); @@ -50,7 +50,7 @@ class OS implements Module $this->handleChanges($os); } - public function poll(\LibreNMS\OS $os) + public function poll(\LibreNMS\OS $os): void { $deviceModel = $os->getDevice(); /** @var \App\Models\Device $deviceModel */ if ($os instanceof OSPolling) { @@ -58,6 +58,11 @@ class OS implements Module } else { // legacy poller files global $graphs, $device; + + if (empty($device)) { + $device = $os->getDeviceArray(); + } + $location = null; if (is_file(base_path('/includes/polling/os/' . $device['os'] . '.inc.php'))) { @@ -85,12 +90,12 @@ class OS implements Module $this->handleChanges($os); } - public function cleanup(\LibreNMS\OS $os) + public function cleanup(\LibreNMS\OS $os): void { // no cleanup needed? } - private function handleChanges(\LibreNMS\OS $os) + private function handleChanges(\LibreNMS\OS $os): void { $device = $os->getDevice(); @@ -104,7 +109,7 @@ class OS implements Module $device->save(); } - private function updateLocation(\LibreNMS\OS $os) + private function updateLocation(\LibreNMS\OS $os): void { $device = $os->getDevice(); $new_location = $device->override_sysLocation ? new Location() : $os->fetchLocation(); // fetch location data from device @@ -112,7 +117,7 @@ class OS implements Module optional($device->location)->save(); } - private function sysContact(\LibreNMS\OS $os) + private function sysContact(\LibreNMS\OS $os): void { $device = $os->getDevice(); $device->sysContact = snmp_get($os->getDeviceArray(), 'sysContact.0', '-Ovq', 'SNMPv2-MIB'); diff --git a/LibreNMS/Modules/PrinterSupplies.php b/LibreNMS/Modules/PrinterSupplies.php index 7bbeebe20a..29ac505127 100644 --- a/LibreNMS/Modules/PrinterSupplies.php +++ b/LibreNMS/Modules/PrinterSupplies.php @@ -39,7 +39,7 @@ class PrinterSupplies implements Module * Discover this module. Heavier processes can be run here * Run infrequently (default 4 times a day) * - * @param OS $os + * @param \LibreNMS\OS $os */ public function discover(OS $os) { @@ -58,7 +58,7 @@ class PrinterSupplies implements Module * Try to keep this efficient and only run if discovery has indicated there is a reason to run. * Run frequently (default every 5 minutes) * - * @param OS $os + * @param \LibreNMS\OS $os */ public function poll(OS $os) { @@ -114,7 +114,7 @@ class PrinterSupplies implements Module * Remove all DB data for this module. * This will be run when the module is disabled. * - * @param OS $os + * @param Os $os */ public function cleanup(OS $os) { diff --git a/LibreNMS/Modules/Slas.php b/LibreNMS/Modules/Slas.php index 4d87040580..05ca4077b7 100644 --- a/LibreNMS/Modules/Slas.php +++ b/LibreNMS/Modules/Slas.php @@ -36,7 +36,7 @@ class Slas implements Module * Discover this module. Heavier processes can be run here * Run infrequently (default 4 times a day) * - * @param OS $os + * @param \LibreNMS\OS $os */ public function discover(OS $os) { @@ -52,7 +52,7 @@ class Slas implements Module * Try to keep this efficient and only run if discovery has indicated there is a reason to run. * Run frequently (default every 5 minutes) * - * @param OS $os + * @param \LibreNMS\OS $os */ public function poll(OS $os) { @@ -73,7 +73,7 @@ class Slas implements Module * Remove all DB data for this module. * This will be run when the module is disabled. * - * @param OS $os + * @param \LibreNMS\OS $os */ public function cleanup(OS $os) { diff --git a/LibreNMS/OS.php b/LibreNMS/OS.php index 38bdf4aee5..37e89b3058 100644 --- a/LibreNMS/OS.php +++ b/LibreNMS/OS.php @@ -127,7 +127,7 @@ class OS implements $this->graphs[$name] = true; } - public function persistGraphs() + public function persistGraphs(): void { $device = $this->getDevice(); $graphs = collect(array_keys($this->graphs)); diff --git a/LibreNMS/Poller.php b/LibreNMS/Poller.php new file mode 100644 index 0000000000..2d9c85e801 --- /dev/null +++ b/LibreNMS/Poller.php @@ -0,0 +1,374 @@ +. + * + * @link https://www.librenms.org + * + * @copyright 2021 Tony Murray + * @author Tony Murray + */ + +namespace LibreNMS; + +use App\Events\DevicePolled; +use App\Events\PollingDevice; +use App\Models\Device; +use App\Polling\Measure\Measurement; +use App\Polling\Measure\MeasurementManager; +use Carbon\Carbon; +use DB; +use Exception; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Support\Str; +use LibreNMS\Exceptions\PollerException; +use LibreNMS\Modules\LegacyModule; +use LibreNMS\Polling\ConnectivityHelper; +use LibreNMS\RRD\RrdDefinition; +use LibreNMS\Util\Debug; +use LibreNMS\Util\Dns; +use LibreNMS\Util\Git; +use LibreNMS\Util\StringHelpers; +use Psr\Log\LoggerInterface; + +class Poller +{ + /** @var string */ + private $device_spec; + /** @var array */ + private $module_override; + + /** + * @var Device + */ + private $device; + /** + * @var array + */ + private $deviceArray; + /** + * @var \LibreNMS\OS|\LibreNMS\OS\Generic + */ + private $os; + /** + * @var LoggerInterface + */ + private $logger; + + public function __construct(string $device_spec, array $module_override, LoggerInterface $logger) + { + $this->device_spec = $device_spec; + $this->module_override = $module_override; + $this->logger = $logger; + $this->parseModules(); + } + + public function poll(): int + { + $polled = 0; + $this->printHeader(); + + if (Debug::isEnabled()) { + \LibreNMS\Util\OS::updateCache(true); // Force update of OS Cache + } + + $this->logger->info("Starting polling run:\n"); + + foreach ($this->buildDeviceQuery()->pluck('device_id') as $device_id) { + $this->initDevice($device_id); + PollingDevice::dispatch($this->device); + $this->os = OS::make($this->deviceArray); + + $helper = new ConnectivityHelper($this->device); + $helper->saveMetrics(); + + $measurement = Measurement::start('poll'); + $measurement->manager()->checkpoint(); // don't count previous stats + + if ($helper->isUp()) { + $this->pollModules(); + } + $measurement->end(); + + if (empty($this->module_override)) { + // record performance + $measurement->manager()->record('device', $measurement); + $this->device->last_polled = Carbon::now(); + $this->device->last_ping_timetaken = $measurement->getDuration(); + app('Datastore')->put($this->deviceArray, 'poller-perf', [ + 'rrd_def' => RrdDefinition::make()->addDataset('poller', 'GAUGE', 0), + 'module' => 'ALL', + ], [ + 'poller' => $measurement->getDuration(), + ]); + $this->os->enableGraph('poller_perf'); + + if ($helper->canPing()) { + $this->os->enableGraph('ping_perf'); + } + + $this->os->persistGraphs(); + $this->logger->info(sprintf("Enabled graphs (%s): %s\n\n", + $this->device->graphs->count(), + $this->device->graphs->pluck('graph')->implode(' ') + )); + } + + $this->device->save(); + $polled++; + + DevicePolled::dispatch($this->device); + + $this->logger->info(sprintf("\n>>> Polled %s (%s) in %0.3f seconds <<<", + $this->device->displayName(), + $this->device->device_id, + $measurement->getDuration())); + + // check if the poll took too long and log an event + if ($measurement->getDuration() > Config::get('rrd.step')) { + \Log::event('Polling took longer than ' . round(Config::get('rrd.step') / 60, 2) . + ' minutes! This will cause gaps in graphs.', $this->device, 'system', 5); + } + } + + return $polled; + } + + private function pollModules(): void + { + $this->filterModules(); + + // update $device array status + $this->deviceArray['status'] = $this->device->status; + $this->deviceArray['status_reason'] = $this->device->status_reason; + + // import legacy garbage + include_once base_path('includes/functions.php'); + include_once base_path('includes/common.php'); + include_once base_path('includes/polling/functions.inc.php'); + include_once base_path('includes/snmp.inc.php'); + include_once base_path('includes/datastore.inc.php'); // remove me + + foreach (Config::get('poller_modules') as $module => $module_status) { + if ($this->isModuleEnabled($module, $module_status)) { + $start_memory = memory_get_usage(); + $module_start = microtime(true); + $this->logger->info("\n#### Load poller module $module ####"); + + try { + $module_class = StringHelpers::toClass($module, '\\LibreNMS\\Modules\\'); + $instance = class_exists($module_class) ? new $module_class : new LegacyModule($module); + $instance->poll($this->os); + } catch (Exception $e) { + // isolate module exceptions so they don't disrupt the polling process + $this->logger->error("Error in $module module. " . $e->getMessage() . PHP_EOL . $e->getTraceAsString() . PHP_EOL); + } + + app(MeasurementManager::class)->printChangedStats(); + $this->saveModulePerformance($module, $module_start, $start_memory); + $this->logger->info("#### Unload poller module $module ####\n"); + } + } + } + + private function saveModulePerformance(string $module, float $start_time, int $start_memory): void + { + $module_time = microtime(true) - $start_time; + $module_mem = (memory_get_usage() - $start_memory); + + $this->logger->info(sprintf(">> Runtime for poller module '%s': %.4f seconds with %s bytes", $module, $module_time, $module_mem)); + + app('Datastore')->put($this->deviceArray, 'poller-perf', [ + 'module' => $module, + 'rrd_def' => RrdDefinition::make()->addDataset('poller', 'GAUGE', 0), + 'rrd_name' => ['poller-perf', $module], + ], [ + 'poller' => $module_time, + ]); + $this->os->enableGraph('poller_modules_perf'); + } + + private function isModuleEnabled(string $module, bool $global_status): bool + { + if (! empty($this->module_override)) { + if (in_array($module, $this->module_override)) { + $this->logger->debug("Module $module manually enabled"); + + return true; + } + + return false; + } + + $os_module_status = Config::get("os.{$this->device->os}.poller_modules.$module"); + $device_attrib = $this->device->getAttrib('poll_' . $module); + $this->logger->debug(sprintf('Modules status: Global %s OS %s Device %s', + $global_status ? '+' : '-', + $os_module_status === null ? ' ' : ($os_module_status ? '+' : '-'), + $device_attrib === null ? ' ' : ($device_attrib ? '+' : '-') + )); + + if ($device_attrib + || ($os_module_status && $device_attrib === null) + || ($global_status && $os_module_status === null && $device_attrib === null)) { + return true; + } + + $reason = $device_attrib !== null ? 'by device' + : ($os_module_status === null || $os_module_status ? 'globally' : 'by OS'); + $this->logger->debug("Module [ $module ] disabled $reason"); + + return false; + } + + private function moduleExists(string $module): bool + { + return class_exists(StringHelpers::toClass($module, '\\LibreNMS\\Modules\\')) + || is_file("includes/polling/$module.inc.php"); + } + + private function buildDeviceQuery(): Builder + { + $query = Device::query(); + + if (empty($this->device_spec)) { + throw new PollerException('Invalid device spec'); + } elseif ($this->device_spec == 'all') { + return $query; + } elseif ($this->device_spec == 'even') { + return $query->where(DB::raw('device_id % 2'), 0); + } elseif ($this->device_spec == 'odd') { + return $query->where(DB::raw('device_id % 2'), 1); + } elseif (is_numeric($this->device_spec)) { + return $query->where('device_id', $this->device_spec); + } elseif (Str::contains($this->device_spec, '*')) { + return $query->where('hostname', 'like', str_replace('*', '%', $this->device_spec)); + } + + return $query->where('hostname', $this->device_spec); + } + + private function initDevice(int $device_id): void + { + \DeviceCache::setPrimary($device_id); + $this->device = \DeviceCache::getPrimary(); + $this->device->ip = $this->device->overwrite_ip ?: Dns::lookupIp($this->device); + + $this->deviceArray = $this->device->toArray(); + if ($os_group = Config::get("os.{$this->device->os}.group")) { + $this->deviceArray['os_group'] = $os_group; + } + + $this->printDeviceInfo($os_group); + $this->initRrdDirectory(); + } + + private function initRrdDirectory(): void + { + $host_rrd = \Rrd::name($this->device->hostname, '', ''); + if (Config::get('rrd.enable', true) && ! is_dir($host_rrd)) { + mkdir($host_rrd); + $this->logger->info("Created directory : $host_rrd"); + } + } + + private function parseModules(): void + { + foreach ($this->module_override as $index => $module) { + // parse submodules (only supported by some modules) + if (Str::contains($module, '/')) { + [$module, $submodule] = explode('/', $module, 2); + $existing_submodules = Config::get("poller_submodules.$module", []); + $existing_submodules[] = $submodule; + Config::set("poller_submodules.$module", $existing_submodules); + } + + if (! $this->moduleExists($module)) { + unset($this->module_override[$index]); + continue; + } + + Config::set("poller_modules.$module", 1); + } + + $this->printModules(); + } + + private function filterModules(): void + { + if ($this->device->snmp_disable) { + // only non-snmp modules + Config::set('poller_modules', array_intersect_key(Config::get('poller_modules'), [ + 'availability' => true, + 'ipmi' => true, + 'unix-agent' => true, + ])); + } else { + // we always want the core module to be included, prepend it + Config::set('poller_modules', ['core' => true] + Config::get('poller_modules')); + } + } + + private function printDeviceInfo(?string $group): void + { + $this->logger->info(sprintf(<<<'EOH' +Hostname: %s %s +ID: %s +OS: %s +IP: %s + +EOH, $this->device->hostname, $group ? " ($group)" : '', $this->device->device_id, $this->device->os, $this->device->ip)); + } + + private function printModules(): void + { + $modules = array_map(function ($module) { + $submodules = Config::get("poller_submodules.$module"); + + return $module . ($submodules ? '(' . implode(',', $submodules) . ')' : ''); + }, array_keys(Config::get('poller_modules', []))); + + $this->logger->debug('Override poller modules: ' . implode(', ', $modules)); + } + + private function printHeader(): void + { + if (Debug::isEnabled() || Debug::isVerbose()) { + $version = \LibreNMS\Util\Version::get(); + $this->logger->info(sprintf(<<<'EOH' +=================================== +Version info: +Commit SHA: %s +Commit Date: %s +DB Schema: %s +PHP: %s +MySQL: %s +RRDTool: %s +SNMP: %s +================================== +EOH, + Git::localCommit(), + Git::localDate(), + vsprintf('%s (%s)', $version->database()), + phpversion(), + \LibreNMS\DB\Eloquent::isConnected() ? \LibreNMS\DB\Eloquent::version() : '?', + $version->rrdtool(), + $version->netSnmp() + )); + } + } +} diff --git a/LibreNMS/Util/Debug.php b/LibreNMS/Util/Debug.php index 6e188f0b12..673b1df942 100644 --- a/LibreNMS/Util/Debug.php +++ b/LibreNMS/Util/Debug.php @@ -32,7 +32,13 @@ use Log; class Debug { + /** + * @var bool + */ private static $debug = false; + /** + * @var bool + */ private static $verbose = false; /** @@ -49,20 +55,12 @@ class Debug restore_error_handler(); // disable Laravel error handler if (self::$debug) { - ini_set('display_errors', '1'); - ini_set('display_startup_errors', '1'); - ini_set('log_errors', '0'); - error_reporting(E_ALL & ~E_NOTICE); - + self::enableErrorReporting(); self::enableCliDebugOutput(); self::enableQueryDebug(); } else { - ini_set('display_errors', '0'); - ini_set('display_startup_errors', '0'); - ini_set('log_errors', '1'); - error_reporting($silence ? 0 : E_ERROR); - - self::disableCliDebugOutput(); + self::disableErrorReporting($silence); + self::disableCliDebugOutput($silence); self::disableQueryDebug(); } @@ -92,7 +90,7 @@ class Debug return self::$verbose; } - public static function disableQueryDebug() + public static function disableQueryDebug(): void { $db = Eloquent::DB(); @@ -102,21 +100,21 @@ class Debug } } - public static function enableCliDebugOutput() + public static function enableCliDebugOutput(): void { if (Laravel::isBooted() && App::runningInConsole()) { - Log::setDefaultDriver('console'); + Log::setDefaultDriver('console_debug'); } } - public static function disableCliDebugOutput() + public static function disableCliDebugOutput(bool $silence): void { - if (Laravel::isBooted()) { - Log::setDefaultDriver('stack'); + if (Laravel::isBooted() && Log::getDefaultDriver() !== 'stack') { + Log::setDefaultDriver(app()->runningInConsole() && ! $silence ? 'console' : 'stack'); } } - public static function enableQueryDebug() + public static function enableQueryDebug(): void { static $sql_debug_enabled; $db = Eloquent::DB(); @@ -141,4 +139,26 @@ class Debug $sql_debug_enabled = true; } } + + /** + * Disable error reporting, do not use with new code + */ + public static function disableErrorReporting(bool $silence = false): void + { + ini_set('display_errors', '0'); + ini_set('display_startup_errors', '0'); + ini_set('log_errors', '1'); + error_reporting($silence ? 0 : E_ERROR); + } + + /** + * Enable error reporting. Please call after disabling for legacy code + */ + public static function enableErrorReporting(): void + { + ini_set('display_errors', '1'); + ini_set('display_startup_errors', '1'); + ini_set('log_errors', '0'); + error_reporting(E_ALL & ~E_NOTICE); + } } diff --git a/LibreNMS/Util/Dns.php b/LibreNMS/Util/Dns.php index ea5859977b..dbc81454b0 100644 --- a/LibreNMS/Util/Dns.php +++ b/LibreNMS/Util/Dns.php @@ -25,6 +25,7 @@ namespace LibreNMS\Util; +use App\Models\Device; use LibreNMS\Interfaces\Geocoder; class Dns implements Geocoder @@ -36,6 +37,19 @@ class Dns implements Geocoder $this->resolver = new \Net_DNS2_Resolver(); } + public static function lookupIp(Device $device): ?string + { + if (IP::isValid($device->hostname)) { + return $device->hostname; + } + + if ($device->transport == 'udp6' || $device->transport == 'tcp6') { + return dns_get_record($device['hostname'], DNS_AAAA)[0]['ipv6'] ?? null; + } + + return dns_get_record($device['hostname'], DNS_A)[0]['ip'] ?? null; + } + /** * @param string $domain Domain which has to be parsed * @param string $record DNS Record which should be searched diff --git a/LibreNMS/Util/Git.php b/LibreNMS/Util/Git.php index 9da07e301c..2eab507ee0 100644 --- a/LibreNMS/Util/Git.php +++ b/LibreNMS/Util/Git.php @@ -25,21 +25,32 @@ namespace LibreNMS\Util; +use Carbon\Carbon; use LibreNMS\Config; class Git { - public static function repoPresent() + public static function repoPresent(): bool { $install_dir = Config::get('install_dir', realpath(__DIR__ . '/../..')); return file_exists("$install_dir/.git"); } - public static function binaryExists() + public static function binaryExists(): bool { exec('git > /dev/null 2>&1', $response, $exit_code); return $exit_code === 1; } + + public static function localCommit(): string + { + return rtrim(exec("git show --pretty='%H' -s HEAD")); + } + + public static function localDate(): Carbon + { + return \Date::createFromTimestamp(exec("git show --pretty='%ct' -s HEAD")); + } } diff --git a/LibreNMS/Util/ModuleTestHelper.php b/LibreNMS/Util/ModuleTestHelper.php index 4d00aab22c..e07d37ccc4 100644 --- a/LibreNMS/Util/ModuleTestHelper.php +++ b/LibreNMS/Util/ModuleTestHelper.php @@ -33,6 +33,7 @@ use LibreNMS\Config; use LibreNMS\Data\Source\SnmpResponse; use LibreNMS\Exceptions\FileNotFoundException; use LibreNMS\Exceptions\InvalidModuleException; +use LibreNMS\Poller; use Symfony\Component\Yaml\Yaml; class ModuleTestHelper @@ -79,8 +80,6 @@ class ModuleTestHelper */ public function __construct($modules, $os, $variant = '') { - global $influxdb; - $this->modules = self::resolveModuleDependencies((array) $modules); $this->os = strtolower($os); $this->variant = strtolower($variant); @@ -196,8 +195,10 @@ class ModuleTestHelper $save_vdebug = Debug::isVerbose(); Debug::set(); Debug::setVerbose(false); + \Log::setDefaultDriver('console'); discover_device($device, $this->parseArgs('discovery')); - poll_device($device, $this->parseArgs('poller')); + $poller = app(Poller::class, ['device_spec' => $device_id, 'module_override' => $this->modules]); + $poller->poll(); Debug::set($save_debug); Debug::setVerbose($save_vdebug); $collection_output = ob_get_contents(); @@ -533,6 +534,7 @@ class ModuleTestHelper { global $device; Config::set('rrd.enable', false); // disable rrd + Config::set('rrdtool_version', '1.7.2'); // don't detect rrdtool version, rrdtool is not install on ci if (! is_file($this->snmprec_file)) { throw new FileNotFoundException("$this->snmprec_file does not exist!"); @@ -592,7 +594,7 @@ class ModuleTestHelper // Dump the discovered data $data = array_merge_recursive($data, $this->dumpDb($device['device_id'], $discovered_modules, 'discovery')); - $device = device_by_id_cache($device_id, true); // refresh the device array + DeviceCache::get($device_id)->refresh(); // refresh the device // Run the poller if ($this->quiet) { @@ -601,7 +603,9 @@ class ModuleTestHelper } ob_start(); - poll_device($device, $this->parseArgs('poller')); + \Log::setDefaultDriver('console'); + $poller = app(Poller::class, ['device_spec' => $device_id, 'module_override' => $this->modules]); + $poller->poll(); $this->poller_output = ob_get_contents(); if ($this->quiet) { @@ -617,12 +621,12 @@ class ModuleTestHelper $polled_modules = array_keys($this->poller_module_output); // Dump polled data - $data = array_merge_recursive($data, $this->dumpDb($device['device_id'], $polled_modules, 'poller')); + $data = array_merge_recursive($data, $this->dumpDb($device_id, $polled_modules, 'poller')); // Remove the test device, we don't need the debug from this if ($device['hostname'] == $snmpsim->getIp()) { Debug::set(false); - delete_device($device['device_id']); + delete_device($device_id); } if (! $no_save) { @@ -721,7 +725,7 @@ class ModuleTestHelper // build joins $join = ''; $select = ["`$table`.*"]; - foreach ($info['joins'] ?: [] as $join_info) { + foreach ($info['joins'] ?? [] as $join_info) { if (isset($join_info['custom'])) { $join .= ' ' . $join_info['custom']; diff --git a/LibreNMS/Util/StringHelpers.php b/LibreNMS/Util/StringHelpers.php index bb77bac020..33adbbf9a9 100644 --- a/LibreNMS/Util/StringHelpers.php +++ b/LibreNMS/Util/StringHelpers.php @@ -1,6 +1,6 @@ */ diff --git a/LibreNMS/Util/Version.php b/LibreNMS/Util/Version.php index a258202a48..70652e86a6 100644 --- a/LibreNMS/Util/Version.php +++ b/LibreNMS/Util/Version.php @@ -25,6 +25,7 @@ namespace LibreNMS\Util; +use LibreNMS\Config; use LibreNMS\DB\Eloquent; use Symfony\Component\Process\Process; @@ -33,6 +34,9 @@ class Version // Update this on release const VERSION = '21.11.0'; + /** + * @var bool + */ protected $is_git_install = false; public function __construct() @@ -40,12 +44,12 @@ class Version $this->is_git_install = Git::repoPresent() && Git::binaryExists(); } - public static function get() + public static function get(): Version { return new static; } - public function local() + public function local(): string { if ($this->is_git_install && $version = $this->fromGit()) { return $version; @@ -54,7 +58,7 @@ class Version return self::VERSION; } - public function database() + public function database(): array { if (Eloquent::isConnected()) { try { @@ -72,34 +76,52 @@ class Version return ['last' => 'Not Connected', 'total' => 0]; } - private function fromGit() + private function fromGit(): string { return rtrim(shell_exec('git describe --tags 2>/dev/null')); } - public function gitChangelog() + public function gitChangelog(): string { return $this->is_git_install ? rtrim(shell_exec('git log -10')) : ''; } - public function gitDate() + public function gitDate(): string { return $this->is_git_install ? rtrim(shell_exec("git show --pretty='%ct' -s HEAD")) : ''; } - public static function python() + public function python(): string { $proc = new Process(['python3', '--version']); $proc->run(); if ($proc->getExitCode() !== 0) { - return null; + return ''; } - return explode(' ', rtrim($proc->getOutput()), 2)[1] ?? null; + return explode(' ', rtrim($proc->getOutput()), 2)[1] ?? ''; + } + + public function rrdtool(): string + { + $process = new Process([Config::get('rrdtool', 'rrdtool'), '--version']); + $process->run(); + preg_match('/^RRDtool ([\w.]+) /', $process->getOutput(), $matches); + + return str_replace('1.7.01.7.0', '1.7.0', $matches[1] ?? ''); + } + + public function netSnmp(): string + { + $process = new Process([Config::get('snmpget', 'snmpget'), '-V']); + $process->run(); + preg_match('/[\w.]+$/', $process->getErrorOutput(), $matches); + + return $matches[0] ?? ''; } } diff --git a/LibreNMS/Validations/Python.php b/LibreNMS/Validations/Python.php index 7497495d05..b4ab2151db 100644 --- a/LibreNMS/Validations/Python.php +++ b/LibreNMS/Validations/Python.php @@ -42,7 +42,7 @@ class Python extends BaseValidation */ public function validate(Validator $validator) { - $version = Version::python(); + $version = Version::get()->python(); if (empty($version)) { $validator->fail('python3 not found', 'Install Python 3 for your system.'); diff --git a/app/Action.php b/app/Action.php new file mode 100644 index 0000000000..85f2543ac1 --- /dev/null +++ b/app/Action.php @@ -0,0 +1,41 @@ +. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2021 Tony Murray + * @author Tony Murray + */ + +namespace App; + +class Action +{ + /** + * Execute an action and return the results + * + * @param string $action + * @param mixed ...$parameters + * @return mixed + */ + public static function execute(string $action, ...$parameters) + { + return app($action, $parameters)->execute(); + } +} diff --git a/app/Actions/Alerts/RunAlertRulesAction.php b/app/Actions/Alerts/RunAlertRulesAction.php new file mode 100644 index 0000000000..86aa4e07f7 --- /dev/null +++ b/app/Actions/Alerts/RunAlertRulesAction.php @@ -0,0 +1,55 @@ +. + * + * @link http://librenms.org + * + * @copyright 2021 Tony Murray + * @author Tony Murray + */ + +namespace App\Actions\Alerts; + +use App\Models\Device; +use LibreNMS\Alert\AlertRules; + +class RunAlertRulesAction +{ + /** + * @var \LibreNMS\Alert\AlertRules + */ + private $rules; + /** + * @var \App\Models\Device + */ + private $device; + + public function __construct(Device $device, AlertRules $rules) + { + $this->rules = $rules; + $this->device = $device; + } + + public function execute(): void + { + // TODO inline logic + include_once base_path('includes/common.php'); + include_once base_path('includes/dbFacile.php'); + $this->rules->runRules($this->device->device_id); + } +} diff --git a/app/Actions/Device/UpdateDeviceGroupsAction.php b/app/Actions/Device/UpdateDeviceGroupsAction.php new file mode 100644 index 0000000000..ef36ce5c75 --- /dev/null +++ b/app/Actions/Device/UpdateDeviceGroupsAction.php @@ -0,0 +1,85 @@ +. + * + * @link http://librenms.org + * + * @copyright 2021 Tony Murray + * @author Tony Murray + */ + +namespace App\Actions\Device; + +use App\Models\Device; +use App\Models\DeviceGroup; +use Log; + +class UpdateDeviceGroupsAction +{ + /** + * @var \App\Models\Device + */ + private $device; + + public function __construct(Device $device) + { + $this->device = $device; + } + + /** + * @return array[] + */ + public function execute(): array + { + if (! $this->device->exists) { + // Device not saved to DB, cowardly refusing + return [ + 'attached' => [], + 'detached' => [], + 'updated' => [], + ]; + } + + $device_group_ids = DeviceGroup::query() + ->with(['devices' => function ($query) { + $query->select('devices.device_id'); + }]) + ->get() + ->filter(function (DeviceGroup $device_group) { + if ($device_group->type == 'dynamic') { + try { + return $device_group->getParser() + ->toQuery() + ->where('devices.device_id', $this->device->device_id) + ->exists(); + } catch (\Illuminate\Database\QueryException $e) { + Log::error("Device Group '$device_group->name' generates invalid query: " . $e->getMessage()); + + return false; + } + } + + // for static, if this device is include, keep it. + return $device_group->devices + ->where('device_id', $this->device->device_id) + ->isNotEmpty(); + })->pluck('id'); + + return $this->device->groups()->sync($device_group_ids); + } +} diff --git a/app/Console/Commands/DevicePoll.php b/app/Console/Commands/DevicePoll.php new file mode 100644 index 0000000000..a1fa05716f --- /dev/null +++ b/app/Console/Commands/DevicePoll.php @@ -0,0 +1,76 @@ +addArgument('device spec', InputArgument::REQUIRED); + $this->addOption('modules', 'm', InputOption::VALUE_REQUIRED); + $this->addOption('no-data', 'x', InputOption::VALUE_NONE); + } + + public function handle(MeasurementManager $measurements): int + { + $this->configureOutputOptions(); + + if ($this->option('no-data')) { + Config::set('rrd.enable', false); + Config::set('influxdb.enable', false); + Config::set('prometheus.enable', false); + Config::set('graphite.enable', false); + } + + try { + $poller = app(Poller::class, ['device_spec' => $this->argument('device spec'), 'module_override' => explode(',', $this->option('modules'))]); + $polled = $poller->poll(); + + if ($polled > 0) { + if (! $this->output->isQuiet()) { + if ($polled > 1) { + $this->output->newLine(); + $this->line(sprintf('Polled %d devices in %0.3fs', $polled, $measurements->getCategory('device')->getSummary('poll')->getDuration())); + } + $this->output->newLine(); + $measurements->printStats(); + } + + return 0; + } + } catch (QueryException $e) { + if ($e->getCode() == 2002) { + $this->error(trans('commands.device:poll.errors.db_connect')); + + return 1; + } elseif ($e->getCode() == 1045) { + // auth failed, don't need to include the query + $this->error(trans('commands.device:poll.errors.db_auth', ['error' => $e->getPrevious()->getMessage()])); + + return 1; + } + + $this->error($e->getMessage()); + + return 1; + } + + return 1; // failed to poll + } +} diff --git a/app/Console/LnmsCommand.php b/app/Console/LnmsCommand.php index ec35866eb0..4e84cb5c20 100644 --- a/app/Console/LnmsCommand.php +++ b/app/Console/LnmsCommand.php @@ -27,6 +27,7 @@ namespace App\Console; use Illuminate\Console\Command; use Illuminate\Validation\ValidationException; +use LibreNMS\Util\Debug; use Symfony\Component\Console\Exception\InvalidArgumentException; use Validator; @@ -45,7 +46,7 @@ abstract class LnmsCommand extends Command $this->setDescription(__('commands.' . $this->getName() . '.description')); } - public function isHidden() + public function isHidden(): bool { $env = $this->getLaravel() ? $this->getLaravel()->environment() : getenv('APP_ENV'); @@ -125,4 +126,15 @@ abstract class LnmsCommand extends Command exit(1); } } + + protected function configureOutputOptions(): void + { + \Log::setDefaultDriver($this->getOutput()->isQuiet() ? 'stack' : 'console'); + if (($verbosity = $this->getOutput()->getVerbosity()) >= 128) { + Debug::set(); + if ($verbosity >= 256) { + Debug::setVerbose(); + } + } + } } diff --git a/app/Events/DevicePolled.php b/app/Events/DevicePolled.php new file mode 100644 index 0000000000..db1072fdc3 --- /dev/null +++ b/app/Events/DevicePolled.php @@ -0,0 +1,39 @@ +device = $device; + } + + /** + * Get the channels the event should broadcast on. + * + * @return \Illuminate\Broadcasting\Channel|array + */ + public function broadcastOn() + { + return new PrivateChannel('channel-name'); + } +} diff --git a/app/Events/PollingDevice.php b/app/Events/PollingDevice.php new file mode 100644 index 0000000000..084642c879 --- /dev/null +++ b/app/Events/PollingDevice.php @@ -0,0 +1,39 @@ +device = $device; + } + + /** + * Get the channels the event should broadcast on. + * + * @return \Illuminate\Broadcasting\Channel|array + */ + public function broadcastOn() + { + return new PrivateChannel('channel-name'); + } +} diff --git a/app/Http/Controllers/AboutController.php b/app/Http/Controllers/AboutController.php index 68b3e48c1b..8535252ea7 100644 --- a/app/Http/Controllers/AboutController.php +++ b/app/Http/Controllers/AboutController.php @@ -76,7 +76,7 @@ class AboutController extends Controller 'version_mysql' => Eloquent::version(), 'version_php' => phpversion(), 'version_laravel' => App::VERSION(), - 'version_python' => Version::python(), + 'version_python' => $version->python(), 'version_webserver' => $request->server('SERVER_SOFTWARE'), 'version_rrdtool' => Rrd::version(), 'version_netsnmp' => str_replace('version: ', '', rtrim(shell_exec(Config::get('snmpget', 'snmpget') . ' -V 2>&1'))), diff --git a/app/Listeners/CheckAlerts.php b/app/Listeners/CheckAlerts.php new file mode 100644 index 0000000000..ede5ee2264 --- /dev/null +++ b/app/Listeners/CheckAlerts.php @@ -0,0 +1,37 @@ +device); + + $end = round(microtime(true) - $start, 4); + Log::info("#### End Alerts ({$end}s) ####\n"); + } +} diff --git a/app/Listeners/UpdateDeviceGroups.php b/app/Listeners/UpdateDeviceGroups.php new file mode 100644 index 0000000000..69cc76f2ca --- /dev/null +++ b/app/Listeners/UpdateDeviceGroups.php @@ -0,0 +1,43 @@ +device); + + $added = implode(',', $group_changes['attached']); + $removed = implode(',', $group_changes['detached']); + $elapsed = round(microtime(true) - $dg_start, 4); + + Log::debug("Groups Added: $added Removed: $removed"); + Log::info("### End Device Groups ({$elapsed}s) ### \n"); + } +} diff --git a/app/Models/DeviceGroup.php b/app/Models/DeviceGroup.php index 7ecaf0eccd..74738aa33e 100644 --- a/app/Models/DeviceGroup.php +++ b/app/Models/DeviceGroup.php @@ -27,7 +27,6 @@ namespace App\Models; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use LibreNMS\Alerting\QueryBuilderFluentParser; -use Log; use Permissions; class DeviceGroup extends BaseModel @@ -70,53 +69,6 @@ class DeviceGroup extends BaseModel } } - /** - * Update the device groups for the given device or device_id - * - * @param Device|int $device - * @return array - */ - public static function updateGroupsFor($device) - { - $device = ($device instanceof Device ? $device : Device::find($device)); - if (! $device instanceof Device) { - // could not load device - return [ - 'attached' => [], - 'detached' => [], - 'updated' => [], - ]; - } - - $device_group_ids = static::query() - ->with(['devices' => function ($query) { - $query->select('devices.device_id'); - }]) - ->get() - ->filter(function ($device_group) use ($device) { - /** @var DeviceGroup $device_group */ - if ($device_group->type == 'dynamic') { - try { - return $device_group->getParser() - ->toQuery() - ->where('devices.device_id', $device->device_id) - ->exists(); - } catch (\Illuminate\Database\QueryException $e) { - Log::error("Device Group '$device_group->name' generates invalid query: " . $e->getMessage()); - - return false; - } - } - - // for static, if this device is include, keep it. - return $device_group->devices - ->where('device_id', $device->device_id) - ->isNotEmpty(); - })->pluck('id'); - - return $device->groups()->sync($device_group_ids); - } - /** * Get a query builder parser instance from this device group * diff --git a/app/Observers/DeviceObserver.php b/app/Observers/DeviceObserver.php index 2d3c2de658..e16e917558 100644 --- a/app/Observers/DeviceObserver.php +++ b/app/Observers/DeviceObserver.php @@ -39,7 +39,7 @@ class DeviceObserver } // key attribute changes - foreach (['os', 'sysName', 'version', 'hardware', 'features', 'serial', 'icon', 'type'] as $attribute) { + foreach (['os', 'sysName', 'version', 'hardware', 'features', 'serial', 'icon', 'type', 'ip'] as $attribute) { if ($device->isDirty($attribute)) { Log::event(self::attributeChangedMessage($attribute, $device->$attribute, $device->getOriginal($attribute)), $device, 'system', 3); } diff --git a/app/Polling/Measure/MeasurementManager.php b/app/Polling/Measure/MeasurementManager.php index 4576c4d7a6..5ed79a7a3c 100644 --- a/app/Polling/Measure/MeasurementManager.php +++ b/app/Polling/Measure/MeasurementManager.php @@ -27,6 +27,8 @@ namespace App\Polling\Measure; use DB; use Illuminate\Database\Events\QueryExecuted; +use Illuminate\Support\Collection; +use Log; class MeasurementManager { @@ -36,20 +38,16 @@ class MeasurementManager const NO_COLOR = "\e[0m"; /** - * @var MeasurementCollection + * @var \Illuminate\Support\Collection */ - private static $snmp; - - /** - * @var MeasurementCollection - */ - private static $db; + private static $categories; public function __construct() { - if (self::$snmp === null) { - self::$snmp = new MeasurementCollection(); - self::$db = new MeasurementCollection(); + if (self::$categories === null) { + self::$categories = new Collection; + self::$categories->put('snmp', new MeasurementCollection()); + self::$categories->put('db', new MeasurementCollection()); } } @@ -64,12 +62,20 @@ class MeasurementManager }); } + /** + * Update statistics for the given category + */ + public function record(string $category, Measurement $measurement): void + { + $this->getCategory($category)->record($measurement); + } + /** * Update statistics for db operations */ public function recordDb(Measurement $measurement): void { - self::$db->record($measurement); + $this->record('db', $measurement); } /** @@ -77,25 +83,24 @@ class MeasurementManager */ public function printChangedStats(): void { - printf( - '>> %sSNMP%s: [%d/%.2fs] %sMySQL%s: [%d/%.2fs]', - self::SNMP_COLOR, - self::NO_COLOR, - self::$snmp->getCountDiff(), - self::$snmp->getDurationDiff(), - self::DB_COLOR, - self::NO_COLOR, - self::$db->getCountDiff(), - self::$db->getDurationDiff() - ); - - app('Datastore')->getStats()->each(function (MeasurementCollection $stats, $datastore) { - printf(' %s%s%s: [%d/%.2fs]', self::DATASTORE_COLOR, $datastore, self::NO_COLOR, $stats->getCountDiff(), $stats->getDurationDiff()); + $dsStats = app('Datastore')->getStats()->map(function (MeasurementCollection $stats, $datastore) { + return sprintf('%s%s%s: [%d/%.2fs]', self::DATASTORE_COLOR, $datastore, self::NO_COLOR, $stats->getCountDiff(), $stats->getDurationDiff()); }); - $this->checkpoint(); + Log::info(sprintf( + '>> %sSNMP%s: [%d/%.2fs] %sMySQL%s: [%d/%.2fs] %s', + self::SNMP_COLOR, + self::NO_COLOR, + $this->getCategory('snmp')->getCountDiff(), + $this->getCategory('snmp')->getDurationDiff(), + self::DB_COLOR, + self::NO_COLOR, + $this->getCategory('db')->getCountDiff(), + $this->getCategory('db')->getDurationDiff(), + $dsStats->implode(' ') + )); - echo PHP_EOL; + $this->checkpoint(); } /** @@ -103,8 +108,7 @@ class MeasurementManager */ public function checkpoint(): void { - self::$snmp->checkpoint(); - self::$db->checkpoint(); + self::$categories->each->checkpoint(); app('Datastore')->getStats()->each->checkpoint(); } @@ -113,7 +117,7 @@ class MeasurementManager */ public function recordSnmp(Measurement $measurement): void { - self::$snmp->record($measurement); + $this->record('snmp', $measurement); } /** @@ -121,22 +125,36 @@ class MeasurementManager */ public function printStats(): void { - $this->printSummary('SNMP', self::$snmp, self::SNMP_COLOR); - $this->printSummary('SQL', self::$db, self::DB_COLOR); + $this->printSummary('SNMP', $this->getCategory('snmp'), self::SNMP_COLOR); + $this->printSummary('SQL', $this->getCategory('db'), self::DB_COLOR); app('Datastore')->getStats()->each(function (MeasurementCollection $stats, string $datastore) { $this->printSummary($datastore, $stats, self::DATASTORE_COLOR); }); } - private function printSummary(string $name, MeasurementCollection $collection, string $color = ''): void + public function getCategory(string $category): MeasurementCollection { - printf('%s%s%s [%d/%.2fs]:', $color, $name, $color ? self::NO_COLOR : '', $collection->getTotalCount(), $collection->getTotalDuration()); + if (! self::$categories->has($category)) { + self::$categories->put($category, new MeasurementCollection()); + } - $collection->each(function (MeasurementSummary $stat) { - printf(' %s[%d/%.2fs]', ucfirst($stat->getType()), $stat->getCount(), $stat->getDuration()); + return self::$categories->get($category); + } + + public function printSummary(string $name, MeasurementCollection $collection, string $color = ''): void + { + $summaries = $collection->map(function (MeasurementSummary $stat) { + return sprintf('%s[%d/%.2fs]', ucfirst($stat->getType()), $stat->getCount(), $stat->getDuration()); }); - echo PHP_EOL; + Log::info(sprintf('%s%s%s [%d/%.2fs]: %s', + $color, + $name, + $color ? self::NO_COLOR : '', + $collection->getTotalCount(), + $collection->getTotalDuration(), + $summaries->implode(' ') + )); } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 69ca759d66..24c96c9319 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -32,6 +32,13 @@ class AppServiceProvider extends ServiceProvider $this->app->singleton('device-cache', function ($app) { return new \LibreNMS\Cache\Device(); }); + + $this->app->bind(\App\Models\Device::class, function () { + /** @var \LibreNMS\Cache\Device $cache */ + $cache = $this->app->make('device-cache'); + + return $cache->hasPrimary() ? $cache->getPrimary() : new \App\Models\Device; + }); } /** diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index a59c528c03..a67ecc36b9 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -2,7 +2,6 @@ namespace App\Providers; -use App\Listeners\MarkNotificationsRead; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; class EventServiceProvider extends ServiceProvider @@ -15,7 +14,15 @@ class EventServiceProvider extends ServiceProvider protected $listen = [ \Illuminate\Auth\Events\Login::class => ['App\Listeners\AuthEventListener@login'], \Illuminate\Auth\Events\Logout::class => ['App\Listeners\AuthEventListener@logout'], - \App\Events\UserCreated::class => [MarkNotificationsRead::class], + \App\Events\UserCreated::class => [ + \App\Listeners\MarkNotificationsRead::class, + ], + \App\Events\PollingDevice::class => [ + ], + \App\Events\DevicePolled::class => [ + \App\Listeners\CheckAlerts::class, + \App\Listeners\UpdateDeviceGroups::class, + ], ]; /** diff --git a/config/logging.php b/config/logging.php index 6682f73c10..246238e080 100644 --- a/config/logging.php +++ b/config/logging.php @@ -55,6 +55,12 @@ return [ 'ignore_exceptions' => false, ], + 'console_debug' => [ + 'driver' => 'stack', + 'channels' => ['single', 'stdout_debug'], + 'ignore_exceptions' => false, + ], + 'single' => [ 'driver' => 'single', 'path' => env('APP_LOG', \LibreNMS\Config::get('log_file', base_path('logs/librenms.log'))), @@ -96,7 +102,7 @@ return [ 'level' => 'debug', ], - 'stdout' => [ + 'stdout_debug' => [ 'driver' => 'monolog', 'handler' => StreamHandler::class, 'formatter' => \LibreNMS\Util\CliColorFormatter::class, @@ -106,6 +112,16 @@ return [ 'level' => 'debug', ], + 'stdout' => [ + 'driver' => 'monolog', + 'handler' => StreamHandler::class, + 'formatter' => \LibreNMS\Util\CliColorFormatter::class, + 'with' => [ + 'stream' => 'php://output', + ], + 'level' => 'info', + ], + 'syslog' => [ 'driver' => 'syslog', 'level' => env('LOG_LEVEL', 'debug'), diff --git a/discovery.php b/discovery.php index a46edf4a80..ceba6e1fe1 100755 --- a/discovery.php +++ b/discovery.php @@ -15,6 +15,7 @@ $init_modules = ['discovery']; require __DIR__ . '/includes/init.php'; $start = microtime(true); +Log::setDefaultDriver('console'); $sqlparams = []; $options = getopt('h:m:i:n:d::v::a::q', ['os:', 'type:']); diff --git a/includes/common.php b/includes/common.php index 51f631b65c..c8cfdd0d8c 100644 --- a/includes/common.php +++ b/includes/common.php @@ -623,14 +623,10 @@ function version_info($remote = false) } $output['db_schema'] = vsprintf('%s (%s)', $version->database()); $output['php_ver'] = phpversion(); - $output['python_ver'] = \LibreNMS\Util\Version::python(); + $output['python_ver'] = $version->python(); $output['mysql_ver'] = \LibreNMS\DB\Eloquent::isConnected() ? \LibreNMS\DB\Eloquent::version() : '?'; - $output['rrdtool_ver'] = str_replace('1.7.01.7.0', '1.7.0', implode(' ', array_slice(explode(' ', shell_exec( - Config::get('rrdtool', 'rrdtool') . ' --version |head -n1' - )), 1, 1))); - $output['netsnmp_ver'] = str_replace('version: ', '', rtrim(shell_exec( - Config::get('snmpget', 'snmpget') . ' -V 2>&1' - ))); + $output['rrdtool_ver'] = $version->rrdtool(); + $output['netsnmp_ver'] = $version->netSnmp(); return $output; }//end version_info() diff --git a/includes/discovery/os.inc.php b/includes/discovery/os.inc.php index 3f1b020a06..233059f7cb 100644 --- a/includes/discovery/os.inc.php +++ b/includes/discovery/os.inc.php @@ -1,3 +1,3 @@ discover($os); +(new \LibreNMS\Modules\Os())->discover($os); diff --git a/includes/functions.php b/includes/functions.php index 9b66e0bed7..55af097c53 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -74,7 +74,7 @@ function parse_modules($type, $options) { $override = false; - if ($options['m']) { + if (! empty($options['m'])) { Config::set("{$type}_modules", []); foreach (explode(',', $options['m']) as $module) { // parse submodules (only supported by some modules) diff --git a/includes/polling/os.inc.php b/includes/polling/os.inc.php index 8aea723610..81a4d869ba 100644 --- a/includes/polling/os.inc.php +++ b/includes/polling/os.inc.php @@ -5,4 +5,4 @@ use LibreNMS\OS; if (! $os instanceof OS) { $os = OS::make($device); } -(new \LibreNMS\Modules\OS())->poll($os); +(new \LibreNMS\Modules\Os())->poll($os); diff --git a/includes/snmp.inc.php b/includes/snmp.inc.php index 11f6c5461c..f49335adee 100644 --- a/includes/snmp.inc.php +++ b/includes/snmp.inc.php @@ -15,7 +15,6 @@ * the source code distribution for details. */ -use App\Models\Device; use App\Polling\Measure\Measurement; use Illuminate\Support\Str; use LibreNMS\Config; @@ -177,7 +176,7 @@ function gen_snmp_cmd($cmd, $device, $oids, $options = null, $mib = null, $mibdi array_push($cmd, '-r', $retries); } - $pollertarget = \LibreNMS\Util\Rewrite::addIpv6Brackets(Device::pollerTarget($device)); + $pollertarget = \LibreNMS\Util\Rewrite::addIpv6Brackets((string) ($device['overwrite_ip'] ?? $device['hostname'])); $cmd[] = $device['transport'] . ':' . $pollertarget . ':' . $device['port']; $cmd = array_merge($cmd, (array) $oids); diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index ec0e883178..fe02aa0ed5 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -3665,60 +3665,30 @@ parameters: count: 1 path: LibreNMS/Modules/Nac.php - - - message: "#^Method LibreNMS\\\\Modules\\\\OS\\:\\:cleanup\\(\\) has no return type specified\\.$#" - count: 1 - path: LibreNMS/Modules/OS.php - - - - message: "#^Method LibreNMS\\\\Modules\\\\OS\\:\\:discover\\(\\) has no return type specified\\.$#" - count: 1 - path: LibreNMS/Modules/OS.php - - - - message: "#^Method LibreNMS\\\\Modules\\\\OS\\:\\:handleChanges\\(\\) has no return type specified\\.$#" - count: 1 - path: LibreNMS/Modules/OS.php - - - - message: "#^Method LibreNMS\\\\Modules\\\\OS\\:\\:poll\\(\\) has no return type specified\\.$#" - count: 1 - path: LibreNMS/Modules/OS.php - - - - message: "#^Method LibreNMS\\\\Modules\\\\OS\\:\\:sysContact\\(\\) has no return type specified\\.$#" - count: 1 - path: LibreNMS/Modules/OS.php - - - - message: "#^Method LibreNMS\\\\Modules\\\\OS\\:\\:updateLocation\\(\\) has no return type specified\\.$#" - count: 1 - path: LibreNMS/Modules/OS.php - - message: "#^Variable \\$features on left side of \\?\\? is never defined\\.$#" count: 1 - path: LibreNMS/Modules/OS.php + path: LibreNMS/Modules/Os.php - message: "#^Variable \\$hardware on left side of \\?\\? is never defined\\.$#" count: 1 - path: LibreNMS/Modules/OS.php + path: LibreNMS/Modules/Os.php - message: "#^Variable \\$location in empty\\(\\) always exists and is always falsy\\.$#" count: 1 - path: LibreNMS/Modules/OS.php + path: LibreNMS/Modules/Os.php - message: "#^Variable \\$serial on left side of \\?\\? is never defined\\.$#" count: 1 - path: LibreNMS/Modules/OS.php + path: LibreNMS/Modules/Os.php - message: "#^Variable \\$version on left side of \\?\\? is never defined\\.$#" count: 1 - path: LibreNMS/Modules/OS.php + path: LibreNMS/Modules/Os.php - message: "#^Method LibreNMS\\\\Modules\\\\PrinterSupplies\\:\\:cleanup\\(\\) has no return type specified\\.$#" @@ -3915,11 +3885,6 @@ parameters: count: 1 path: LibreNMS/OS.php - - - message: "#^Method LibreNMS\\\\OS\\:\\:persistGraphs\\(\\) has no return type specified\\.$#" - count: 1 - path: LibreNMS/OS.php - - message: "#^Method LibreNMS\\\\OS\\:\\:preCache\\(\\) has no return type specified\\.$#" count: 1 @@ -5440,36 +5405,6 @@ parameters: count: 1 path: LibreNMS/Util/Colors.php - - - message: "#^Method LibreNMS\\\\Util\\\\Debug\\:\\:disableCliDebugOutput\\(\\) has no return type specified\\.$#" - count: 1 - path: LibreNMS/Util/Debug.php - - - - message: "#^Method LibreNMS\\\\Util\\\\Debug\\:\\:disableQueryDebug\\(\\) has no return type specified\\.$#" - count: 1 - path: LibreNMS/Util/Debug.php - - - - message: "#^Method LibreNMS\\\\Util\\\\Debug\\:\\:enableCliDebugOutput\\(\\) has no return type specified\\.$#" - count: 1 - path: LibreNMS/Util/Debug.php - - - - message: "#^Method LibreNMS\\\\Util\\\\Debug\\:\\:enableQueryDebug\\(\\) has no return type specified\\.$#" - count: 1 - path: LibreNMS/Util/Debug.php - - - - message: "#^Property LibreNMS\\\\Util\\\\Debug\\:\\:\\$debug has no type specified\\.$#" - count: 1 - path: LibreNMS/Util/Debug.php - - - - message: "#^Property LibreNMS\\\\Util\\\\Debug\\:\\:\\$verbose has no type specified\\.$#" - count: 1 - path: LibreNMS/Util/Debug.php - - message: "#^Property LibreNMS\\\\Util\\\\Dns\\:\\:\\$resolver has no type specified\\.$#" count: 1 @@ -5765,16 +5700,6 @@ parameters: count: 1 path: LibreNMS/Util/FileCategorizer.php - - - message: "#^Method LibreNMS\\\\Util\\\\Git\\:\\:binaryExists\\(\\) has no return type specified\\.$#" - count: 1 - path: LibreNMS/Util/Git.php - - - - message: "#^Method LibreNMS\\\\Util\\\\Git\\:\\:repoPresent\\(\\) has no return type specified\\.$#" - count: 1 - path: LibreNMS/Util/Git.php - - message: "#^Method LibreNMS\\\\Util\\\\GitHub\\:\\:__construct\\(\\) has parameter \\$file with no type specified\\.$#" count: 1 @@ -6845,46 +6770,6 @@ parameters: count: 1 path: LibreNMS/Util/Validate.php - - - message: "#^Method LibreNMS\\\\Util\\\\Version\\:\\:database\\(\\) has no return type specified\\.$#" - count: 1 - path: LibreNMS/Util/Version.php - - - - message: "#^Method LibreNMS\\\\Util\\\\Version\\:\\:fromGit\\(\\) has no return type specified\\.$#" - count: 1 - path: LibreNMS/Util/Version.php - - - - message: "#^Method LibreNMS\\\\Util\\\\Version\\:\\:get\\(\\) has no return type specified\\.$#" - count: 1 - path: LibreNMS/Util/Version.php - - - - message: "#^Method LibreNMS\\\\Util\\\\Version\\:\\:gitChangelog\\(\\) has no return type specified\\.$#" - count: 1 - path: LibreNMS/Util/Version.php - - - - message: "#^Method LibreNMS\\\\Util\\\\Version\\:\\:gitDate\\(\\) has no return type specified\\.$#" - count: 1 - path: LibreNMS/Util/Version.php - - - - message: "#^Method LibreNMS\\\\Util\\\\Version\\:\\:local\\(\\) has no return type specified\\.$#" - count: 1 - path: LibreNMS/Util/Version.php - - - - message: "#^Method LibreNMS\\\\Util\\\\Version\\:\\:python\\(\\) has no return type specified\\.$#" - count: 1 - path: LibreNMS/Util/Version.php - - - - message: "#^Property LibreNMS\\\\Util\\\\Version\\:\\:\\$is_git_install has no type specified\\.$#" - count: 1 - path: LibreNMS/Util/Version.php - - message: "#^Method LibreNMS\\\\ValidationResult\\:\\:consolePrint\\(\\) has no return type specified\\.$#" count: 1 diff --git a/poller.php b/poller.php index b6896e0bbe..5ffbc6fb43 100755 --- a/poller.php +++ b/poller.php @@ -24,6 +24,8 @@ * @author Heath Barnhart */ +use App\Action; +use App\Actions\Device\UpdateDeviceGroupsAction; use LibreNMS\Alert\AlertRules; use LibreNMS\Config; use LibreNMS\Data\Store\Datastore; @@ -33,6 +35,7 @@ $init_modules = ['polling', 'alerts', 'laravel']; require __DIR__ . '/includes/init.php'; $poller_start = microtime(true); +Log::setDefaultDriver('console'); echo Config::get('project_name') . " Poller\n"; $options = getopt('h:m:i:n:r::d::v::a::f::q'); @@ -139,7 +142,7 @@ foreach (dbFetch($query) as $device) { // Update device_groups echo "### Start Device Groups ###\n"; $dg_start = microtime(true); - $group_changes = \App\Models\DeviceGroup::updateGroupsFor($device['device_id']); + $group_changes = Action::execute(UpdateDeviceGroupsAction::class); d_echo('Groups Added: ' . implode(',', $group_changes['attached']) . PHP_EOL); d_echo('Groups Removed: ' . implode(',', $group_changes['detached']) . PHP_EOL); echo '### End Device Groups, runtime: ' . round(microtime(true) - $dg_start, 4) . "s ### \n\n"; diff --git a/resources/lang/en/commands.php b/resources/lang/en/commands.php index be0745e2ca..ec23cadf9a 100644 --- a/resources/lang/en/commands.php +++ b/resources/lang/en/commands.php @@ -69,6 +69,20 @@ return [ 'device spec' => 'Device to ping one of: , , all', ], ], + 'device:poll' => [ + 'description' => 'Poll data from device(s) as defined by discovery', + 'arguments' => [ + 'device spec' => 'Device spec to poll: device_id, hostname, wildcard, odd, even, all', + ], + 'options' => [ + 'modules' => 'Specify single module to be run. Comma separate modules, submodules may be added with /', + 'no-data' => 'Do not update datastores (RRD, InfluxDB, etc)', + ], + 'errors' => [ + 'db_connect' => 'Failed to connect to database. Verify database service is running and connection settings.', + 'db_auth' => 'Failed to connect to database. Verify credentials: :error', + ], + ], 'key:rotate' => [ 'description' => 'Rotate APP_KEY, this decrypts all encrypted data with the given old key and stores it with the new key in APP_KEY.', 'arguments' => [ diff --git a/routes/console.php b/routes/console.php index 11d7e91d2d..5b9f986950 100644 --- a/routes/console.php +++ b/routes/console.php @@ -111,8 +111,7 @@ Artisan::command('device:add if (($verbosity = $this->getOutput()->getVerbosity()) >= 128) { Debug::set(); if ($verbosity >= 256) { - global $verbose; - $verbose = true; + Debug::setVerbose(); } } diff --git a/tests/OSModulesTest.php b/tests/OSModulesTest.php index 501ad9a4c3..b9f4249635 100644 --- a/tests/OSModulesTest.php +++ b/tests/OSModulesTest.php @@ -100,10 +100,8 @@ class OSModulesTest extends DBTestCase $filename = $helper->getJsonFilepath(true); $expected_data = $helper->getTestData(); $results = $helper->generateTestData($this->getSnmpsim(), true); - } catch (FileNotFoundException $e) { - return $this->fail($e->getMessage()); - } catch (InvalidModuleException $e) { - return $this->fail($e->getMessage()); + } catch (FileNotFoundException|InvalidModuleException $e) { + $this->fail($e->getMessage()); } if (is_null($results)) { @@ -168,8 +166,10 @@ class OSModulesTest extends DBTestCase private function stubClasses(): void { $this->app->bind('log', function ($app) { - return \Mockery::mock('\App\Facades\LogManager[event]', [$app]) - ->shouldReceive('event'); + $mock = \Mockery::mock('\App\Facades\LogManager[event]', [$app]); + $mock->shouldReceive('event'); + + return $mock; }); $this->app->bind(Fping::class, function ($app) {