Improved Modern Modules (#14315)

* Improved Modern Modules
Modules now report module dependencies and can dump data for testing
This should serve to the process of building a module more obvious.
cleanup now only requires a device, not an os wrapped around a device
Helper to create a modern module (including the legacy adapter) from a name.

* return false correctly for dump and handle it.

* make sure test data is in the right format.

* wrong isis table name

* sort

* Fix style and lint issues
This commit is contained in:
Tony Murray 2022-09-07 16:53:16 -05:00 committed by GitHub
parent 6342d69b18
commit b44f1546a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 506 additions and 427 deletions

View File

@ -25,17 +25,23 @@
namespace LibreNMS\Interfaces;
use App\Models\Device;
use LibreNMS\OS;
interface Module
{
/**
* An array of all modules this module depends on
*/
public function dependencies(): array;
/**
* Discover this module. Heavier processes can be run here
* Run infrequently (default 4 times a day)
*
* @param \LibreNMS\OS $os
*/
public function discover(OS $os);
public function discover(OS $os): void;
/**
* Poll data for this module and update the DB / RRD.
@ -44,13 +50,24 @@ interface Module
*
* @param \LibreNMS\OS $os
*/
public function poll(OS $os);
public function poll(OS $os): void;
/**
* Remove all DB data for this module.
* This will be run when the module is disabled.
*
* @param \LibreNMS\OS $os
* @param \App\Models\Device $device
*/
public function cleanup(OS $os);
public function cleanup(Device $device): void;
/**
* Dump current module data for the given device for tests.
* Make sure to hide transient fields, such as id and date.
* You should always order the data by a non-transient column.
* Some id fields may need to be joined to tie back to non-transient data.
* Module may return false if testing is not supported or required.
*
* @return array|false
*/
public function dump(Device $device);
}

View File

@ -39,7 +39,15 @@ use SnmpQuery;
class Core implements Module
{
public function discover(OS $os)
/**
* @inheritDoc
*/
public function dependencies(): array
{
return [];
}
public function discover(OS $os): void
{
$snmpdata = SnmpQuery::numeric()->get(['SNMPv2-MIB::sysObjectID.0', 'SNMPv2-MIB::sysDescr.0', 'SNMPv2-MIB::sysName.0'])
->values();
@ -78,7 +86,7 @@ class Core implements Module
echo 'OS: ' . Config::getOsSetting($device->os, 'text') . " ($device->os)\n\n";
}
public function poll(OS $os)
public function poll(OS $os): void
{
$snmpdata = SnmpQuery::numeric()
->get(['SNMPv2-MIB::sysDescr.0', 'SNMPv2-MIB::sysObjectID.0', 'SNMPv2-MIB::sysUpTime.0', 'SNMPv2-MIB::sysName.0'])
@ -95,11 +103,19 @@ class Core implements Module
$device->save();
}
public function cleanup(OS $os)
public function cleanup(Device $device): void
{
// nothing to cleanup
}
/**
* @inheritDoc
*/
public function dump(Device $device)
{
return false; // all data here is stored in the devices table and covered by the os module
}
/**
* Detect the os of the given device.
*
@ -265,7 +281,7 @@ class Core implements Module
}
}
protected static function discoveryIsSlow($def): bool
protected static function discoveryIsSlow(array $def): bool
{
foreach ($def['discovery'] as $item) {
if (array_key_exists('snmpget', $item) || array_key_exists('snmpwalk', $item)) {

View File

@ -25,6 +25,7 @@
namespace LibreNMS\Modules;
use App\Models\Device;
use App\Models\IsisAdjacency;
use App\Observers\ModuleModelObserver;
use Illuminate\Support\Arr;
@ -47,13 +48,21 @@ class Isis implements Module
'unknown' => 'unknown',
];
/**
* @inheritDoc
*/
public function dependencies(): array
{
return ['ports'];
}
/**
* Discover this module. Heavier processes can be run here
* Run infrequently (default 4 times a day)
*
* @param \LibreNMS\OS $os
*/
public function discover(OS $os)
public function discover(OS $os): void
{
$adjacencies = $os instanceof IsIsDiscovery
? $os->discoverIsIs()
@ -70,7 +79,7 @@ class Isis implements Module
*
* @param \LibreNMS\OS $os
*/
public function poll(OS $os)
public function poll(OS $os): void
{
$adjacencies = $os->getDevice()->isisAdjacencies;
@ -88,15 +97,13 @@ class Isis implements Module
/**
* Remove all DB data for this module.
* This will be run when the module is disabled.
*
* @param Os $os
*/
public function cleanup(OS $os)
public function cleanup(Device $device): void
{
$os->getDevice()->isisAdjacencies()->delete();
$device->isisAdjacencies()->delete();
// clean up legacy components from old code
$os->getDevice()->components()->where('type', 'ISIS')->delete();
$device->components()->where('type', 'ISIS')->delete();
}
public function discoverIsIsMib(OS $os): Collection
@ -175,4 +182,15 @@ class Isis implements Module
{
return (int) max($data['isisISAdjLastUpTime'] ?? 1, 1) / 100;
}
/**
* @inheritDoc
*/
public function dump(Device $device)
{
return [
'isis_adjacencies' => $device->isisAdjacencies()->orderBy('index')
->get()->map->makeHidden(['id', 'device_id', 'port_id']),
];
}
}

View File

@ -25,12 +25,34 @@
namespace LibreNMS\Modules;
use App\Models\Device;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use LibreNMS\Component;
use LibreNMS\Interfaces\Module;
use LibreNMS\OS;
use LibreNMS\Util\Debug;
use Symfony\Component\Yaml\Yaml;
class LegacyModule implements Module
{
/** @var array */
private $module_deps = [
'arp-table' => ['ports'],
'cisco-mac-accounting' => ['ports'],
'fdb-table' => ['ports', 'vlans'],
'vlans' => ['ports'],
'vrf' => ['ports'],
];
/**
* @inheritDoc
*/
public function dependencies(): array
{
return $this->module_deps[$this->name] ?? [];
}
/**
* @var string
*/
@ -64,8 +86,102 @@ class LegacyModule implements Module
Debug::enableErrorReporting(); // and back to normal
}
public function cleanup(OS $os): void
public function cleanup(Device $device): void
{
// TODO: Implement cleanup() method.
}
/**
* @inheritDoc
*/
public function dump(Device $device)
{
$data = [];
$dump_rules = $this->moduleDumpDefinition();
if (empty($dump_rules)) {
return false; // not supported for this legacy module
}
foreach ($dump_rules as $table => $info) {
if ($table == 'component') {
$components = $this->collectComponents($device->device_id);
if (! empty($components)) {
$data[$table] = $components;
}
continue;
}
// check for custom where
$where = $info['custom_where'] ?? "WHERE `$table`.`device_id`=?";
$params = [$device->device_id];
// build joins
$join = '';
$select = ["`$table`.*"];
foreach ($info['joins'] ?? [] as $join_info) {
if (isset($join_info['custom'])) {
$join .= ' ' . $join_info['custom'];
$default_select = [];
} else {
[$left, $lkey] = explode('.', $join_info['left']);
[$right, $rkey] = explode('.', $join_info['right']);
$join .= " LEFT JOIN `$right` ON (`$left`.`$lkey` = `$right`.`$rkey`)";
$default_select = ["`$right`.*"];
}
// build selects
$select = array_merge($select, isset($join_info['select']) ? (array) $join_info['select'] : $default_select);
}
$order_by = isset($info['order_by']) ? " ORDER BY {$info['order_by']}" : '';
$fields = implode(', ', $select);
$rows = DB::select("SELECT $fields FROM `$table` $join $where $order_by", $params);
// don't include empty tables
if (empty($rows)) {
continue;
}
// remove unwanted fields
if (isset($info['included_fields'])) {
$keys = array_flip($info['included_fields']);
$rows = array_map(function ($row) use ($keys) {
return array_intersect_key((array) $row, $keys);
}, $rows);
} elseif (isset($info['excluded_fields'])) {
$keys = array_flip($info['excluded_fields']);
$rows = array_map(function ($row) use ($keys) {
return array_diff_key((array) $row, $keys);
}, $rows);
}
$data[$table] = $rows;
}
return $data;
}
private function moduleDumpDefinition(): array
{
static $def;
if ($def === null) {
// only load the yaml once, then keep it in memory
$def = Yaml::parse(file_get_contents(base_path('/tests/module_tables.yaml')));
}
return $def[$this->name] ?? [];
}
private function collectComponents(int $device_id): array
{
$components = (new Component())->getComponents($device_id)[$device_id] ?? [];
$components = Arr::sort($components, function ($item) {
return $item['type'] . $item['label'];
});
return array_values($components);
}
}

View File

@ -25,6 +25,7 @@
namespace LibreNMS\Modules;
use App\Models\Device;
use App\Models\Mempool;
use App\Observers\MempoolObserver;
use Illuminate\Support\Collection;
@ -41,7 +42,15 @@ class Mempools implements Module
{
use SyncsModels;
public function discover(OS $os)
/**
* @inheritDoc
*/
public function dependencies(): array
{
return [];
}
public function discover(OS $os): void
{
if ($os instanceof MempoolsDiscovery) {
$mempools = $os->discoverMempools()->filter(function (Mempool $mempool) {
@ -64,7 +73,7 @@ class Mempools implements Module
}
}
public function poll(OS $os)
public function poll(OS $os): void
{
$mempools = $os->getDevice()->mempools;
@ -131,16 +140,27 @@ class Mempools implements Module
return $mempools;
}
public function cleanup(OS $os)
public function cleanup(Device $device): void
{
$os->getDevice()->mempools()->delete();
$device->mempools()->delete();
}
/**
* @inheritDoc
*/
public function dump(Device $device)
{
return [
'mempools' => $device->mempools()->orderBy('mempool_type')->orderBy('mempool_id')
->get()->map->makeHidden(['device_id', 'mempool_id']),
];
}
/**
* Calculate available memory. This is free + buffers + cached.
*
* @param \Illuminate\Support\Collection $mempools
* @return \Illuminate\Support\Collection|void
* @return \Illuminate\Support\Collection
*/
private function calculateAvailable(Collection $mempools)
{
@ -186,7 +206,7 @@ class Mempools implements Module
return $mempools;
}
private function printMempool(Mempool $mempool)
private function printMempool(Mempool $mempool): void
{
echo "$mempool->mempool_type [$mempool->mempool_class]: $mempool->mempool_descr: $mempool->mempool_perc%";
if ($mempool->mempool_total != 100) {

View File

@ -27,6 +27,7 @@
namespace LibreNMS\Modules;
use App\Models\Device;
use App\Observers\ModuleModelObserver;
use LibreNMS\DB\SyncsModels;
use LibreNMS\Interfaces\Discovery\MplsDiscovery;
@ -38,13 +39,21 @@ class Mpls implements Module
{
use SyncsModels;
/**
* @inheritDoc
*/
public function dependencies(): array
{
return ['ports', 'vrf'];
}
/**
* Discover this module. Heavier processes can be run here
* Run infrequently (default 4 times a day)
*
* @param \LibreNMS\OS $os
*/
public function discover(OS $os)
public function discover(OS $os): void
{
if ($os instanceof MplsDiscovery) {
echo "\nMPLS LSPs: ";
@ -90,7 +99,7 @@ class Mpls implements Module
*
* @param \LibreNMS\OS $os
*/
public function poll(OS $os)
public function poll(OS $os): void
{
if ($os instanceof MplsPolling) {
$device = $os->getDevice();
@ -150,18 +159,47 @@ class Mpls implements Module
/**
* Remove all DB data for this module.
* This will be run when the module is disabled.
*
* @param Os $os
*/
public function cleanup(OS $os)
public function cleanup(Device $device): void
{
$os->getDevice()->mplsLsps()->delete();
$os->getDevice()->mplsLspPaths()->delete();
$os->getDevice()->mplsSdps()->delete();
$os->getDevice()->mplsServices()->delete();
$os->getDevice()->mplsSaps()->delete();
$os->getDevice()->mplsSdpBinds()->delete();
$os->getDevice()->mplsTunnelArHops()->delete();
$os->getDevice()->mplsTunnelCHops()->delete();
$device->mplsLsps()->delete();
$device->mplsLspPaths()->delete();
$device->mplsSdps()->delete();
$device->mplsServices()->delete();
$device->mplsSaps()->delete();
$device->mplsSdpBinds()->delete();
$device->mplsTunnelArHops()->delete();
$device->mplsTunnelCHops()->delete();
}
/**
* @inheritDoc
*/
public function dump(Device $device)
{
return [
'mpls_lsps' => $device->mplsLsps()->orderBy('vrf_oid')->orderBy('lsp_oid')
->get()->map->makeHidden(['lsp_id', 'device_id']),
'mpls_lsp_paths' => $device->mplsLspPaths()
->leftJoin('mpls_lsps', 'mpls_lsp_paths.lsp_id', 'mpls_lsps.lsp_id')
->select(['mpls_lsp_paths.*', 'mpls_lsps.vrf_oid', 'mpls_lsps.lsp_oid'])
->orderBy('vrf_oid')->orderBy('lsp_oid')->orderBy('path_oid')
->get()->map->makeHidden(['lsp_path_id', 'device_id', 'lsp_id']),
'mpls_sdps' => $device->mplsSdps()->orderBy('sdp_oid')
->get()->map->makeHidden(['sdp_id', 'device_id']),
'mpls_sdp_binds' => $device->mplsSdpBinds()
->leftJoin('mpls_sdps', 'mpls_sdp_binds.sdp_id', 'mpls_sdps.sdp_id')
->leftJoin('mpls_services', 'mpls_sdp_binds.svc_id', 'mpls_services.svc_id')
->orderBy('mpls_sdps.sdp_oid')->orderBy('mpls_services.svc_oid')
->select(['mpls_sdp_binds.*', 'mpls_sdps.sdp_oid', 'mpls_services.svc_oid'])
->get()->map->makeHidden(['bind_id', 'sdp_id', 'svc_id', 'device_id']),
'mpls_services' => $device->mplsServices()->orderBy('svc_oid')
->get()->map->makeHidden(['svc_id', 'device_id']),
'mpls_saps' => $device->mplsSaps()
->leftJoin('mpls_services', 'mpls_saps.svc_id', 'mpls_services.svc_id')
->orderBy('mpls_services.svc_oid')->orderBy('mpls_saps.sapPortId')->orderBy('mpls_saps.sapEncapValue')
->select(['mpls_saps.*', 'mpls_services.svc_oid'])
->get()->map->makeHidden(['sap_id', 'svc_id', 'device_id']),
];
}
}

View File

@ -25,6 +25,7 @@
namespace LibreNMS\Modules;
use App\Models\Device;
use App\Models\PortsNac;
use App\Observers\ModuleModelObserver;
use LibreNMS\Interfaces\Module;
@ -33,13 +34,21 @@ use LibreNMS\OS;
class Nac implements Module
{
/**
* @inheritDoc
*/
public function dependencies(): array
{
return ['ports'];
}
/**
* Discover this module. Heavier processes can be run here
* Run infrequently (default 4 times a day)
*
* @param Os $os
*/
public function discover(OS $os)
public function discover(OS $os): void
{
// not implemented
}
@ -51,7 +60,7 @@ class Nac implements Module
*
* @param \LibreNMS\OS $os
*/
public function poll(OS $os)
public function poll(OS $os): void
{
if ($os instanceof NacPolling) {
// discovery output (but don't install it twice (testing can can do this)
@ -81,11 +90,22 @@ class Nac implements Module
/**
* Remove all DB data for this module.
* This will be run when the module is disabled.
*
* @param \LibreNMS\OS $os
*/
public function cleanup(OS $os)
public function cleanup(Device $device): void
{
$os->getDevice()->portsNac()->delete();
$device->portsNac()->delete();
}
/**
* @inheritDoc
*/
public function dump(Device $device)
{
return [
'ports_nac' => $device->portsNac()->orderBy('ports.ifIndex')->orderBy('mac_address')
->leftJoin('ports', 'ports_nac.port_id', 'ports.port_id')
->select(['ports_nac.*', 'ifIndex'])
->get()->map->makeHidden(['ports_nac_id', 'device_id', 'port_id']),
];
}
}

View File

@ -25,6 +25,8 @@
namespace LibreNMS\Modules;
use App\Models\Device;
use LibreNMS\Interfaces\Module;
use LibreNMS\Interfaces\Polling\Netstats\IcmpNetstatsPolling;
use LibreNMS\Interfaces\Polling\Netstats\IpForwardNetstatsPolling;
use LibreNMS\Interfaces\Polling\Netstats\IpNetstatsPolling;
@ -34,8 +36,16 @@ use LibreNMS\Interfaces\Polling\Netstats\UdpNetstatsPolling;
use LibreNMS\OS;
use LibreNMS\RRD\RrdDefinition;
class Netstats implements \LibreNMS\Interfaces\Module
class Netstats implements Module
{
/**
* @inheritDoc
*/
public function dependencies(): array
{
return [];
}
/**
* @var string[][]
*/
@ -208,11 +218,19 @@ class Netstats implements \LibreNMS\Interfaces\Module
/**
* @inheritDoc
*/
public function cleanup(OS $os): void
public function cleanup(Device $device): void
{
// no cleanup
}
/**
* @inheritDoc
*/
public function dump(Device $device)
{
return false; // no database data to dump (may add rrd later)
}
private function statName(string $oid): string
{
$start = strpos($oid, '::') + 2;

View File

@ -25,6 +25,7 @@
namespace LibreNMS\Modules;
use App\Models\Device;
use App\Models\Location;
use LibreNMS\Interfaces\Module;
use LibreNMS\Interfaces\Polling\OSPolling;
@ -32,6 +33,14 @@ use LibreNMS\Util\Url;
class Os implements Module
{
/**
* @inheritDoc
*/
public function dependencies(): array
{
return [];
}
public function discover(\LibreNMS\OS $os): void
{
$this->updateLocation($os);
@ -90,9 +99,26 @@ class Os implements Module
$this->handleChanges($os);
}
public function cleanup(\LibreNMS\OS $os): void
/**
* @inheritDoc
*/
public function cleanup(Device $device): void
{
// no cleanup needed?
// no cleanup needed
}
/**
* @inheritDoc
*/
public function dump(Device $device)
{
// get data fresh from the database
return [
'devices' => Device::where('device_id', $device->device_id)
->leftJoin('locations', 'location_id', 'id')
->select(['sysName', 'sysObjectID', 'sysDescr', 'sysContact', 'version', 'hardware', 'features', 'location', 'os', 'type', 'serial', 'icon'])
->get(),
];
}
private function handleChanges(\LibreNMS\OS $os): void

View File

@ -25,6 +25,7 @@
namespace LibreNMS\Modules;
use App\Models\Device;
use App\Models\Ipv4Address;
use App\Models\OspfArea;
use App\Models\OspfInstance;
@ -38,6 +39,14 @@ use SnmpQuery;
class Ospf implements Module
{
/**
* @inheritDoc
*/
public function dependencies(): array
{
return ['ports'];
}
/**
* @inheritDoc
*/
@ -214,11 +223,27 @@ class Ospf implements Module
/**
* @inheritDoc
*/
public function cleanup(OS $os): void
public function cleanup(Device $device): void
{
$os->getDevice()->ospfPorts()->delete();
$os->getDevice()->ospfNbrs()->delete();
$os->getDevice()->ospfAreas()->delete();
$os->getDevice()->ospfInstances()->delete();
$device->ospfPorts()->delete();
$device->ospfNbrs()->delete();
$device->ospfAreas()->delete();
$device->ospfInstances()->delete();
}
/**
* @inheritDoc
*/
public function dump(Device $device)
{
return [
'ospf_ports' => $device->ospfPorts()
->leftJoin('ports', 'ospf_ports.port_id', 'ports.port_id')
->select(['ospf_ports.*', 'ifIndex'])
->get()->map->makeHidden(['id', 'device_id', 'port_id']),
'ospf_instances' => $device->ospfInstances->map->makeHidden(['id', 'device_id']),
'ospf_areas' => $device->ospfAreas->map->makeHidden(['id', 'device_id']),
'ospf_nbrs' => $device->ospfNbrs->map->makeHidden(['id', 'device_id']),
];
}
}

View File

@ -20,6 +20,7 @@
namespace LibreNMS\Modules;
use App\Models\Device;
use App\Models\PrinterSupply;
use App\Observers\ModuleModelObserver;
use Illuminate\Support\Collection;
@ -36,13 +37,21 @@ class PrinterSupplies implements Module
{
use SyncsModels;
/**
* @inheritDoc
*/
public function dependencies(): array
{
return [];
}
/**
* Discover this module. Heavier processes can be run here
* Run infrequently (default 4 times a day)
*
* @param \LibreNMS\OS $os
*/
public function discover(OS $os)
public function discover(OS $os): void
{
$device = $os->getDeviceArray();
@ -61,7 +70,7 @@ class PrinterSupplies implements Module
*
* @param \LibreNMS\OS $os
*/
public function poll(OS $os)
public function poll(OS $os): void
{
$device = $os->getDeviceArray();
$toner_data = $os->getDevice()->printerSupplies;
@ -114,12 +123,21 @@ class PrinterSupplies implements Module
/**
* Remove all DB data for this module.
* This will be run when the module is disabled.
*
* @param Os $os
*/
public function cleanup(OS $os)
public function cleanup(Device $device): void
{
$os->getDevice()->printerSupplies()->delete();
$device->printerSupplies()->delete();
}
/**
* @inheritDoc
*/
public function dump(Device $device)
{
return [
'printer_supplies' => $device->printerSupplies()->orderBy('supply_oid')->orderBy('supply_index')
->get()->map->makeHidden(['device_id', 'supply_id']),
];
}
private function discoveryLevels($device): Collection

View File

@ -20,6 +20,7 @@
namespace LibreNMS\Modules;
use App\Models\Device;
use App\Models\Sla;
use App\Observers\ModuleModelObserver;
use LibreNMS\DB\SyncsModels;
@ -32,13 +33,21 @@ class Slas implements Module
{
use SyncsModels;
/**
* @inheritDoc
*/
public function dependencies(): array
{
return [];
}
/**
* Discover this module. Heavier processes can be run here
* Run infrequently (default 4 times a day)
*
* @param \LibreNMS\OS $os
*/
public function discover(OS $os)
public function discover(OS $os): void
{
if ($os instanceof SlaDiscovery) {
$slas = $os->discoverSlas();
@ -54,7 +63,7 @@ class Slas implements Module
*
* @param \LibreNMS\OS $os
*/
public function poll(OS $os)
public function poll(OS $os): void
{
if ($os instanceof SlaPolling) {
// Gather our SLA's from the DB.
@ -72,11 +81,20 @@ class Slas implements Module
/**
* Remove all DB data for this module.
* This will be run when the module is disabled.
*
* @param \LibreNMS\OS $os
*/
public function cleanup(OS $os)
public function cleanup(Device $device): void
{
$os->getDevice()->slas()->delete();
$device->slas()->delete();
}
/**
* @inheritDoc
*/
public function dump(Device $device)
{
return [
'slas' => $device->slas()->orderBy('sla_nr')
->get()->map->makeHidden(['device_id', 'sla_id']),
];
}
}

View File

@ -25,6 +25,7 @@
namespace LibreNMS\Modules;
use App\Models\Device;
use App\Models\PortStp;
use App\Observers\ModuleModelObserver;
use LibreNMS\DB\SyncsModels;
@ -39,6 +40,14 @@ class Stp implements Module
{
use SyncsModels;
/**
* @inheritDoc
*/
public function dependencies(): array
{
return ['ports', 'vlans'];
}
public function discover(OS $os): void
{
$device = $os->getDevice();
@ -79,10 +88,25 @@ class Stp implements Module
}
}
public function cleanup(OS $os): void
public function cleanup(Device $device): void
{
$os->getDevice()->stpInstances()->delete();
$os->getDevice()->stpPorts()->delete();
$device->stpInstances()->delete();
$device->stpPorts()->delete();
}
/**
* @inheritDoc
*/
public function dump(Device $device)
{
return [
'stp' => $device->stpInstances()->orderBy('bridgeAddress')
->get()->map->makeHidden(['stp_id', 'device_id']),
'ports_stp' => $device->portsStp()->orderBy('port_index')
->leftJoin('ports', 'ports_stp.port_id', 'ports.port_id')
->select(['ports_stp.*', 'ifIndex'])
->get()->map->makeHidden(['port_stp_id', 'device_id', 'port_id']),
];
}
/**

View File

@ -36,12 +36,12 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Str;
use LibreNMS\Enum\Alert;
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\Module;
use LibreNMS\Util\StringHelpers;
use Psr\Log\LoggerInterface;
use Throwable;
@ -183,8 +183,7 @@ class Poller
$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 = Module::fromName($module);
$instance->poll($this->os);
} catch (Throwable $e) {
// isolate module exceptions so they don't disrupt the polling process

38
LibreNMS/Util/Module.php Normal file
View File

@ -0,0 +1,38 @@
<?php
/**
* Module.php
*
* -Description-
*
* 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 <https://www.gnu.org/licenses/>.
*
* @link https://www.librenms.org
*
* @copyright 2022 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Util;
use LibreNMS\Modules\LegacyModule;
class Module
{
public static function fromName(string $name): \LibreNMS\Interfaces\Module
{
$module_class = StringHelpers::toClass($name, '\\LibreNMS\\Modules\\');
return class_exists($module_class) ? new $module_class : new LegacyModule($name);
}
}

View File

@ -28,24 +28,20 @@ namespace LibreNMS\Util;
use App\Actions\Device\ValidateDeviceAndCreate;
use App\Models\Device;
use DeviceCache;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use LibreNMS\Component;
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
{
private static $module_tables;
private $quiet = false;
private $modules;
private $variant;
private $os;
private $snmprec_file;
private $json_file;
private $snmprec_dir;
@ -59,18 +55,6 @@ class ModuleTestHelper
// Definitions
// ignore these when dumping all modules
private $exclude_from_all = ['arp-table', 'fdb-table'];
private static $module_deps = [
'arp-table' => ['ports', 'arp-table'],
'cisco-mac-accounting' => ['ports', 'cisco-mac-accounting'],
'fdb-table' => ['ports', 'vlans', 'fdb-table'],
'isis' => ['ports', 'isis'],
'mpls' => ['ports', 'vrf', 'mpls'],
'nac' => ['ports', 'nac'],
'ospf' => ['ports', 'ospf'],
'stp' => ['ports', 'vlans', 'stp'],
'vlans' => ['ports', 'vlans'],
'vrf' => ['ports', 'vrf'],
];
/**
* ModuleTester constructor.
@ -84,7 +68,6 @@ class ModuleTestHelper
public function __construct($modules, $os, $variant = '')
{
$this->modules = self::resolveModuleDependencies((array) $modules);
$this->os = strtolower($os);
$this->variant = strtolower($variant);
// preset the file names
@ -104,11 +87,6 @@ class ModuleTestHelper
Config::set('influxdb.enable', false);
Config::set('graphite.enable', false);
Config::set('prometheus.enable', false);
if (is_null(self::$module_tables)) {
// only load the yaml once, then keep it in memory
self::$module_tables = Yaml::parse(file_get_contents($install_dir . '/tests/module_tables.yaml'));
}
}
private static function compareOid($a, $b)
@ -217,10 +195,12 @@ class ModuleTestHelper
preg_match_all($snmp_query_regex, $collection_output, $snmp_matches);
// extract mibs and group with oids
$snmp_oids = [null => [
'sysDescr.0_get' => ['oid' => 'sysDescr.0', 'mib' => 'SNMPv2-MIB', 'method' => 'get'],
'sysObjectID.0_get' => ['oid' => 'sysObjectID.0', 'mib' => 'SNMPv2-MIB', 'method' => 'get'],
]];
$snmp_oids = [
null => [
'sysDescr.0_get' => ['oid' => 'sysDescr.0', 'mib' => 'SNMPv2-MIB', 'method' => 'get'],
'sysObjectID.0_get' => ['oid' => 'sysObjectID.0', 'mib' => 'SNMPv2-MIB', 'method' => 'get'],
],
];
foreach ($snmp_matches[0] as $index => $line) {
preg_match("/'-m' '\+?([a-zA-Z0-9:\-]+)'/", $line, $mib_matches);
$mib = $mib_matches[1];
@ -335,7 +315,7 @@ class ModuleTestHelper
*
* @throws InvalidModuleException
*/
private static function resolveModuleDependencies($modules)
private static function resolveModuleDependencies(array $modules): array
{
// generate a full list of modules
$full_list = [];
@ -345,11 +325,8 @@ class ModuleTestHelper
throw new InvalidModuleException("Invalid module name: $module");
}
if (isset(self::$module_deps[$module])) {
$full_list = array_merge($full_list, self::$module_deps[$module]);
} else {
$full_list[] = $module;
}
$full_list = array_merge($full_list, Module::fromName($module)->dependencies());
$full_list[] = $module;
}
return array_unique($full_list);
@ -720,76 +697,17 @@ class ModuleTestHelper
public function dumpDb($device_id, $modules, $type)
{
$data = [];
$module_dump_info = $this->getTableData();
// don't dump some modules by default unless they are manually listed
if (empty($this->modules)) {
$modules = array_diff($modules, $this->exclude_from_all);
}
// only dump data for the given modules
// only dump data for the given modules (and modules that support dumping)
foreach ($modules as $module) {
foreach ($module_dump_info[$module] ?? [] as $table => $info) {
if ($table == 'component') {
$components = $this->collectComponents($device_id);
if (! empty($components)) {
$data[$module][$type][$table] = $components;
}
continue;
}
// check for custom where
$where = isset($info['custom_where']) ? $info['custom_where'] : "WHERE `$table`.`device_id`=?";
$params = [$device_id];
// build joins
$join = '';
$select = ["`$table`.*"];
foreach ($info['joins'] ?? [] as $join_info) {
if (isset($join_info['custom'])) {
$join .= ' ' . $join_info['custom'];
$default_select = [];
} else {
[$left, $lkey] = explode('.', $join_info['left']);
[$right, $rkey] = explode('.', $join_info['right']);
$join .= " LEFT JOIN `$right` ON (`$left`.`$lkey` = `$right`.`$rkey`)";
$default_select = ["`$right`.*"];
}
// build selects
$select = array_merge($select, isset($join_info['select']) ? (array) $join_info['select'] : $default_select);
}
if (isset($info['order_by'])) {
$order_by = " ORDER BY {$info['order_by']}";
} else {
$order_by = '';
}
$fields = implode(', ', $select);
$rows = dbFetchRows("SELECT $fields FROM `$table` $join $where $order_by", $params);
// don't include empty tables
if (empty($rows)) {
continue;
}
// remove unwanted fields
if (isset($info['included_fields'])) {
$keys = array_flip($info['included_fields']);
$rows = array_map(function ($row) use ($keys) {
return array_intersect_key($row, $keys);
}, $rows);
} elseif (isset($info['excluded_fields'])) {
$keys = array_flip($info['excluded_fields']);
$rows = array_map(function ($row) use ($keys) {
return array_diff_key($row, $keys);
}, $rows);
}
$data[$module][$type][$table] = $rows;
$module_data = Module::fromName($module)->dump(DeviceCache::get($device_id));
if ($module_data !== false) {
$data[$module][$type] = $this->dumpToArray($module_data);
}
}
@ -797,14 +715,22 @@ class ModuleTestHelper
}
/**
* Get list of tables used by a module
* Includes a list of fields that will not be considered for testing
*
* @param array|\Illuminate\Support\Collection|\stdClass $data
* @return array
*/
public function getTableData()
private function dumpToArray($data): array
{
return array_intersect_key(self::$module_tables, array_flip($this->getModules()));
$output = [];
foreach ($data as $table => $table_data) {
foreach ($table_data as $item) {
$output[$table][] = is_a($item, Model::class)
? Arr::except($item->getAttributes(), $item->getHidden()) // don't apply accessors
: (array) $item;
}
}
return $output;
}
/**
@ -847,27 +773,6 @@ class ModuleTestHelper
return $this->poller_output;
}
/**
* Get a list of all modules that support capturing data
*
* @return array
*/
public function getSupportedModules()
{
return array_keys(self::$module_tables);
}
/**
* Get a list of modules to capture data for
* If modules is empty, returns all supported modules
*
* @return array
*/
private function getModules()
{
return empty($this->modules) ? $this->getSupportedModules() : $this->modules;
}
public function getTestData()
{
return json_decode(file_get_contents($this->json_file), true);
@ -881,14 +786,4 @@ class ModuleTestHelper
return $this->json_file;
}
private function collectComponents(int $device_id): array
{
$components = (new Component())->getComponents($device_id)[$device_id] ?? [];
$components = Arr::sort($components, function ($item) {
return $item['type'] . $item['label'];
});
return array_values($components);
}
}

View File

@ -3250,21 +3250,6 @@ parameters:
count: 1
path: LibreNMS/Interfaces/Discovery/SlaDiscovery.php
-
message: "#^Method LibreNMS\\\\Interfaces\\\\Module\\:\\:cleanup\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Interfaces/Module.php
-
message: "#^Method LibreNMS\\\\Interfaces\\\\Module\\:\\:discover\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Interfaces/Module.php
-
message: "#^Method LibreNMS\\\\Interfaces\\\\Module\\:\\:poll\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Interfaces/Module.php
-
message: "#^Method LibreNMS\\\\Interfaces\\\\Polling\\\\MplsPolling\\:\\:pollMplsTunnelArHops\\(\\) has parameter \\$paths with no type specified\\.$#"
count: 1
@ -3335,46 +3320,11 @@ parameters:
count: 1
path: LibreNMS/Model.php
-
message: "#^Method LibreNMS\\\\Modules\\\\Core\\:\\:cleanup\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Modules/Core.php
-
message: "#^Method LibreNMS\\\\Modules\\\\Core\\:\\:discover\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Modules/Core.php
-
message: "#^Method LibreNMS\\\\Modules\\\\Core\\:\\:discoveryIsSlow\\(\\) has parameter \\$def with no type specified\\.$#"
count: 1
path: LibreNMS/Modules/Core.php
-
message: "#^Method LibreNMS\\\\Modules\\\\Core\\:\\:poll\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Modules/Core.php
-
message: "#^Method LibreNMS\\\\Modules\\\\Isis\\:\\:cleanup\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Modules/Isis.php
-
message: "#^Method LibreNMS\\\\Modules\\\\Isis\\:\\:discover\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Modules/Isis.php
-
message: "#^Method LibreNMS\\\\Modules\\\\Isis\\:\\:parseAdjacencyTime\\(\\) has parameter \\$data with no type specified\\.$#"
count: 1
path: LibreNMS/Modules/Isis.php
-
message: "#^Method LibreNMS\\\\Modules\\\\Isis\\:\\:poll\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Modules/Isis.php
-
message: "#^Property LibreNMS\\\\Modules\\\\Isis\\:\\:\\$isis_codes has no type specified\\.$#"
count: 1
@ -3385,46 +3335,6 @@ parameters:
count: 1
path: LibreNMS/Modules/Isis.php
-
message: "#^Method LibreNMS\\\\Modules\\\\Mempools\\:\\:calculateAvailable\\(\\) never returns void so it can be removed from the return type\\.$#"
count: 1
path: LibreNMS/Modules/Mempools.php
-
message: "#^Method LibreNMS\\\\Modules\\\\Mempools\\:\\:cleanup\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Modules/Mempools.php
-
message: "#^Method LibreNMS\\\\Modules\\\\Mempools\\:\\:discover\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Modules/Mempools.php
-
message: "#^Method LibreNMS\\\\Modules\\\\Mempools\\:\\:poll\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Modules/Mempools.php
-
message: "#^Method LibreNMS\\\\Modules\\\\Mempools\\:\\:printMempool\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Modules/Mempools.php
-
message: "#^Method LibreNMS\\\\Modules\\\\Mpls\\:\\:cleanup\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Modules/Mpls.php
-
message: "#^Method LibreNMS\\\\Modules\\\\Mpls\\:\\:discover\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Modules/Mpls.php
-
message: "#^Method LibreNMS\\\\Modules\\\\Mpls\\:\\:poll\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Modules/Mpls.php
-
message: "#^Variable \\$lsps might not be defined\\.$#"
count: 1
@ -3445,21 +3355,6 @@ parameters:
count: 2
path: LibreNMS/Modules/Mpls.php
-
message: "#^Method LibreNMS\\\\Modules\\\\Nac\\:\\:cleanup\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Modules/Nac.php
-
message: "#^Method LibreNMS\\\\Modules\\\\Nac\\:\\:discover\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Modules/Nac.php
-
message: "#^Method LibreNMS\\\\Modules\\\\Nac\\:\\:poll\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Modules/Nac.php
-
message: "#^Variable \\$features on left side of \\?\\? is never defined\\.$#"
count: 1
@ -3490,16 +3385,6 @@ parameters:
count: 1
path: LibreNMS/Modules/Ospf.php
-
message: "#^Method LibreNMS\\\\Modules\\\\PrinterSupplies\\:\\:cleanup\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Modules/PrinterSupplies.php
-
message: "#^Method LibreNMS\\\\Modules\\\\PrinterSupplies\\:\\:discover\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Modules/PrinterSupplies.php
-
message: "#^Method LibreNMS\\\\Modules\\\\PrinterSupplies\\:\\:discoveryLevels\\(\\) has parameter \\$device with no type specified\\.$#"
count: 1
@ -3510,26 +3395,6 @@ parameters:
count: 1
path: LibreNMS/Modules/PrinterSupplies.php
-
message: "#^Method LibreNMS\\\\Modules\\\\PrinterSupplies\\:\\:poll\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Modules/PrinterSupplies.php
-
message: "#^Method LibreNMS\\\\Modules\\\\Slas\\:\\:cleanup\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Modules/Slas.php
-
message: "#^Method LibreNMS\\\\Modules\\\\Slas\\:\\:discover\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Modules/Slas.php
-
message: "#^Method LibreNMS\\\\Modules\\\\Slas\\:\\:poll\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Modules/Slas.php
-
message: "#^Method LibreNMS\\\\OS\\:\\:discoverYamlMempools\\(\\) has no return type specified\\.$#"
count: 1
@ -5820,31 +5685,11 @@ parameters:
count: 1
path: LibreNMS/Util/ModuleTestHelper.php
-
message: "#^Property LibreNMS\\\\Util\\\\ModuleTestHelper\\:\\:\\$module_deps has no type specified\\.$#"
count: 1
path: LibreNMS/Util/ModuleTestHelper.php
-
message: "#^Property LibreNMS\\\\Util\\\\ModuleTestHelper\\:\\:\\$module_tables has no type specified\\.$#"
count: 1
path: LibreNMS/Util/ModuleTestHelper.php
-
message: "#^Property LibreNMS\\\\Util\\\\ModuleTestHelper\\:\\:\\$modules has no type specified\\.$#"
count: 1
path: LibreNMS/Util/ModuleTestHelper.php
-
message: "#^Property LibreNMS\\\\Util\\\\ModuleTestHelper\\:\\:\\$os has no type specified\\.$#"
count: 1
path: LibreNMS/Util/ModuleTestHelper.php
-
message: "#^Property LibreNMS\\\\Util\\\\ModuleTestHelper\\:\\:\\$os is never read, only written\\.$#"
count: 1
path: LibreNMS/Util/ModuleTestHelper.php
-
message: "#^Property LibreNMS\\\\Util\\\\ModuleTestHelper\\:\\:\\$poller_module_output has no type specified\\.$#"
count: 1

View File

@ -28,50 +28,6 @@ fdb-table:
order_by: ports.ifIndex, vlans.vlan_vlan, ports_fdb.mac_address
loadbalancers:
component: true
mempools:
mempools:
excluded_fields: [device_id, mempool_id]
order_by: mempool_type, mempool_id
mpls:
mpls_lsps:
excluded_fields: [lsp_id, device_id]
order_by: vrf_oid, lsp_oid
mpls_lsp_paths:
excluded_fields: [lsp_path_id, device_id, lsp_id]
joins:
- { left: mpls_lsp_paths.lsp_id, right: mpls_lsps.lsp_id, select: [vrf_oid, lsp_oid] }
order_by: vrf_oid, lsp_oid, path_oid
mpls_sdps:
excluded_fields: [sdp_id, device_id]
order_by: sdp_oid
mpls_sdp_binds:
excluded_fields: [bind_id, sdp_id, svc_id, device_id]
joins:
- { left: mpls_sdp_binds.sdp_id, right: mpls_sdps.sdp_id, select: [mpls_sdps.sdp_oid] }
- { left: mpls_sdp_binds.svc_id, right: mpls_services.svc_id, select: [mpls_services.svc_oid] }
order_by: mpls_sdps.sdp_oid, mpls_services.svc_oid
mpls_services:
excluded_fields: [svc_id, device_id]
order_by: svc_oid
mpls_saps:
excluded_fields: [sap_id, svc_id, device_id]
joins:
- { left: mpls_saps.svc_id, right: mpls_services.svc_id, select: [mpls_services.svc_oid] }
order_by: mpls_services.svc_oid, mpls_saps.sapPortId, mpls_saps.sapEncapValue
ospf:
ospf_ports:
excluded_fields: [id, device_id, port_id]
joins:
- { left: ospf_ports.port_id, right: ports.port_id, select: [ ifIndex ] }
ospf_instances:
excluded_fields: [id, device_id]
ospf_areas:
excluded_fields: [id, device_id]
ospf_nbrs:
excluded_fields: [id, device_id]
isis:
isis_adjacencies:
excluded_fields: [id, device_id, port_id]
ports:
ports:
excluded_fields: [device_id, port_id, poll_time, poll_period, ifVrf]
@ -92,17 +48,6 @@ route:
route:
excluded_fields: [port_id, device_id, route_id, created_at, updated_at]
order_by: inetCidrRouteDest
nac:
ports_nac:
excluded_fields: [ports_nac_id, device_id, port_id]
joins:
- { left: ports_nac.port_id, right: ports.port_id, select: [ifIndex] }
order_by: ports.ifIndex, mac_address
os:
devices:
included_fields: [sysName, sysObjectID, sysDescr, sysContact, version, hardware, features, location, os, type, serial, icon]
joins:
- { left: devices.location_id, right: locations.id, select: [location] }
processors:
processors:
excluded_fields: [device_id, processor_id]
@ -120,27 +65,10 @@ sensors:
- { custom: 'INNER JOIN ( SELECT i.state_index_id FROM `sensors_to_state_indexes` i LEFT JOIN `sensors` s ON (i.`sensor_id` = s.`sensor_id`) WHERE `device_id`=? GROUP BY i.state_index_id) d ON d.state_index_id = state_indexes.state_index_id' }
order_by: state_indexes.state_name, state_translations.state_value
custom_where: ''
slas:
slas:
excluded_fields: [device_id, sla_id]
order_by: sla_nr
storage:
storage:
excluded_fields: [device_id, storage_id]
order_by: storage_index, storage_type
stp:
stp:
excluded_fields: [stp_id, device_id]
order_by: bridgeAddress
ports_stp:
excluded_fields: [port_stp_id, device_id, port_id]
order_by: port_index
joins:
- { left: ports_stp.port_id, right: ports.port_id, select: [ ifIndex ] }
printer-supplies:
printer_supplies:
excluded_fields: [device_id, supply_id]
order_by: supply_oid, supply_index
vlans:
vlans:
excluded_fields: [vlan_id, device_id]