801 lines
26 KiB
PHP
801 lines
26 KiB
PHP
<?php
|
|
/**
|
|
* ModuleTester.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 2017 Tony Murray
|
|
* @author Tony Murray <murraytony@gmail.com>
|
|
*/
|
|
|
|
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\Config;
|
|
use LibreNMS\Data\Source\SnmpResponse;
|
|
use LibreNMS\Exceptions\FileNotFoundException;
|
|
use LibreNMS\Exceptions\InvalidModuleException;
|
|
use LibreNMS\Poller;
|
|
|
|
class ModuleTestHelper
|
|
{
|
|
private $quiet = false;
|
|
private $modules;
|
|
private $variant;
|
|
private $snmprec_file;
|
|
private $json_file;
|
|
private $snmprec_dir;
|
|
private $json_dir;
|
|
private $file_name;
|
|
private $discovery_module_output = [];
|
|
private $poller_module_output = [];
|
|
private $discovery_output;
|
|
private $poller_output;
|
|
|
|
// Definitions
|
|
// ignore these when dumping all modules
|
|
private $exclude_from_all = ['arp-table', 'fdb-table'];
|
|
|
|
/**
|
|
* ModuleTester constructor.
|
|
*
|
|
* @param array|string $modules
|
|
* @param string $os
|
|
* @param string $variant
|
|
*
|
|
* @throws InvalidModuleException
|
|
*/
|
|
public function __construct($modules, $os, $variant = '')
|
|
{
|
|
$this->modules = self::resolveModuleDependencies((array) $modules);
|
|
$this->variant = strtolower($variant);
|
|
|
|
// preset the file names
|
|
if ($variant) {
|
|
$variant = '_' . $this->variant;
|
|
}
|
|
$install_dir = Config::get('install_dir');
|
|
$this->file_name = $os . $variant;
|
|
$this->snmprec_dir = "$install_dir/tests/snmpsim/";
|
|
$this->snmprec_file = $this->snmprec_dir . $this->file_name . '.snmprec';
|
|
$this->json_dir = "$install_dir/tests/data/";
|
|
$this->json_file = $this->json_dir . $this->file_name . '.json';
|
|
|
|
// never store time series data
|
|
Config::set('rrd.enable', false);
|
|
Config::set('hide_rrd_disabled', true);
|
|
Config::set('influxdb.enable', false);
|
|
Config::set('graphite.enable', false);
|
|
Config::set('prometheus.enable', false);
|
|
}
|
|
|
|
private static function compareOid($a, $b)
|
|
{
|
|
$a_oid = explode('.', $a);
|
|
$b_oid = explode('.', $b);
|
|
|
|
foreach ($a_oid as $index => $a_part) {
|
|
$b_part = $b_oid[$index];
|
|
if ($a_part > $b_part) {
|
|
return 1; // a is higher
|
|
} elseif ($a_part < $b_part) {
|
|
return -1; // b is higher
|
|
}
|
|
}
|
|
|
|
if (count($a_oid) < count($b_oid)) {
|
|
return -1; // same prefix, but b has more so it is higher
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
public function setQuiet($quiet = true)
|
|
{
|
|
$this->quiet = $quiet;
|
|
}
|
|
|
|
public function setSnmprecSavePath($path)
|
|
{
|
|
$this->snmprec_file = $path;
|
|
}
|
|
|
|
public function setJsonSavePath($path)
|
|
{
|
|
$this->json_file = $path;
|
|
}
|
|
|
|
public function captureFromDevice(int $device_id, bool $prefer_new = false, bool $full = false): void
|
|
{
|
|
if ($full) {
|
|
$snmp_oids[][] = [
|
|
'oid' => '.',
|
|
'method' => 'walk',
|
|
'mib' => null,
|
|
'mibdir' => null,
|
|
];
|
|
} else {
|
|
$snmp_oids = $this->collectOids($device_id);
|
|
}
|
|
|
|
DeviceCache::setPrimary($device_id);
|
|
|
|
foreach ($snmp_oids as $context => $context_oids) {
|
|
$snmprec_data = [];
|
|
foreach ($context_oids as $oid_data) {
|
|
$this->qPrint(' ' . $oid_data['oid']);
|
|
|
|
$snmp_options = ['-OUneb', '-Ih', '-m', '+' . $oid_data['mib']];
|
|
if ($oid_data['method'] == 'walk') {
|
|
$data = \SnmpQuery::options($snmp_options)->context($context)->mibDir($oid_data['mibdir'])->walk($oid_data['oid']);
|
|
} elseif ($oid_data['method'] == 'get') {
|
|
$data = \SnmpQuery::options($snmp_options)->context($context)->mibDir($oid_data['mibdir'])->get($oid_data['oid']);
|
|
} elseif ($oid_data['method'] == 'getnext') {
|
|
$data = \SnmpQuery::options($snmp_options)->context($context)->mibDir($oid_data['mibdir'])->next($oid_data['oid']);
|
|
}
|
|
|
|
if (isset($data) && $data->isValid()) {
|
|
$snmprec_data[] = $this->convertSnmpToSnmprec($data);
|
|
}
|
|
}
|
|
|
|
$this->saveSnmprec($snmprec_data, $context, true, $prefer_new);
|
|
}
|
|
}
|
|
|
|
private function collectOids($device_id)
|
|
{
|
|
global $device;
|
|
|
|
$device = device_by_id_cache($device_id);
|
|
DeviceCache::setPrimary($device_id);
|
|
|
|
// Run discovery
|
|
ob_start();
|
|
$save_debug = Debug::isEnabled();
|
|
$save_vdebug = Debug::isVerbose();
|
|
Debug::set();
|
|
Debug::setVerbose();
|
|
discover_device($device, $this->parseArgs('discovery'));
|
|
$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();
|
|
ob_end_clean();
|
|
|
|
d_echo($collection_output);
|
|
d_echo(PHP_EOL);
|
|
|
|
// remove color
|
|
$collection_output = preg_replace('/\033\[[\d;]+m/', '', $collection_output);
|
|
|
|
// extract snmp queries
|
|
$snmp_query_regex = '/SNMP\[.*snmp(?:bulk)?([a-z]+)\' .+(udp|tcp|tcp6|udp6):(?:\[[0-9a-f:]+\]|[^:]+):[0-9]+\' \'(.+)\']/';
|
|
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'],
|
|
],
|
|
];
|
|
foreach ($snmp_matches[0] as $index => $line) {
|
|
preg_match("/'-m' '\+?([a-zA-Z0-9:\-]+)'/", $line, $mib_matches);
|
|
$mib = $mib_matches[1];
|
|
preg_match("/'-M' '\+?([a-zA-Z0-9:\-\/]+)'/", $line, $mibdir_matches);
|
|
$mibdir = $mibdir_matches[1];
|
|
$method = $snmp_matches[1][$index];
|
|
$oids = explode("' '", trim($snmp_matches[3][$index]));
|
|
preg_match("/('-c' '.*@([^']+)'|'-n' '([^']+)')/", $line, $context_matches);
|
|
$context = $context_matches[2] ?? $context_matches[3] ?? null;
|
|
|
|
foreach ($oids as $oid) {
|
|
$snmp_oids[$context]["{$oid}_$method"] = [
|
|
'oid' => $oid,
|
|
'mib' => $mib,
|
|
'mibdir' => $mibdir,
|
|
'method' => $method,
|
|
];
|
|
}
|
|
}
|
|
|
|
d_echo('OIDs to capture ');
|
|
d_echo($snmp_oids);
|
|
|
|
return $snmp_oids;
|
|
}
|
|
|
|
/**
|
|
* Generate a list of os containing test data for $modules (an empty array means all)
|
|
*
|
|
* Returns an array indexed by the basename ($os or $os_$variant)
|
|
* Each entry contains [$os, $variant, $valid_modules]
|
|
* $valid_modules is an array of selected modules this os has test data for
|
|
*
|
|
* @param array $modules
|
|
* @return array
|
|
*
|
|
* @throws InvalidModuleException
|
|
*/
|
|
public static function findOsWithData($modules = [], string $os_filter = null)
|
|
{
|
|
$os_list = [];
|
|
|
|
foreach (glob(Config::get('install_dir') . '/tests/data/*.json') as $file) {
|
|
$base_name = basename($file, '.json');
|
|
[$os, $variant] = self::extractVariant($file);
|
|
|
|
if ($os_filter != '' && $os_filter != $os) {
|
|
continue;
|
|
}
|
|
|
|
// calculate valid modules
|
|
$decoded = json_decode(file_get_contents($file), true);
|
|
|
|
if (json_last_error()) {
|
|
echo "Invalid json data: $base_name\n";
|
|
exit(1);
|
|
}
|
|
|
|
$data_modules = array_keys($decoded);
|
|
|
|
if (empty($modules)) {
|
|
$valid_modules = $data_modules;
|
|
} else {
|
|
$valid_modules = array_intersect($modules, $data_modules);
|
|
}
|
|
|
|
if (empty($valid_modules)) {
|
|
continue; // no test data for selected modules
|
|
}
|
|
|
|
try {
|
|
$os_list[$base_name] = [
|
|
$os,
|
|
$variant,
|
|
self::resolveModuleDependencies($valid_modules),
|
|
];
|
|
} catch (InvalidModuleException $e) {
|
|
throw new InvalidModuleException('Invalid module ' . $e->getMessage() . " in $os $variant");
|
|
}
|
|
}
|
|
|
|
return $os_list;
|
|
}
|
|
|
|
/**
|
|
* Given a json filename or basename, extract os and variant
|
|
*
|
|
* @param string $os_file Either a filename or the basename
|
|
* @return array [$os, $variant]
|
|
*/
|
|
public static function extractVariant($os_file)
|
|
{
|
|
$full_name = basename($os_file, '.json');
|
|
|
|
if (! Str::contains($full_name, '_')) {
|
|
return [$full_name, ''];
|
|
} elseif (is_file(Config::get('install_dir') . "/includes/definitions/$full_name.yaml")) {
|
|
return [$full_name, ''];
|
|
} else {
|
|
[$rvar, $ros] = explode('_', strrev($full_name), 2);
|
|
|
|
return [strrev($ros), strrev($rvar)];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate a module list. Try to take dependencies into account.
|
|
* Probably needs to be more robust
|
|
*
|
|
* @param array $modules
|
|
* @return array
|
|
*
|
|
* @throws InvalidModuleException
|
|
*/
|
|
private static function resolveModuleDependencies(array $modules): array
|
|
{
|
|
// generate a full list of modules
|
|
$full_list = [];
|
|
foreach ($modules as $module) {
|
|
// only allow valid modules
|
|
if (! (Config::has("poller_modules.$module") || Config::has("discovery_modules.$module"))) {
|
|
throw new InvalidModuleException("Invalid module name: $module");
|
|
}
|
|
|
|
$full_list = array_merge($full_list, Module::fromName($module)->dependencies());
|
|
$full_list[] = $module;
|
|
}
|
|
|
|
return array_unique($full_list);
|
|
}
|
|
|
|
private function parseArgs($type)
|
|
{
|
|
if (empty($this->modules)) {
|
|
return false;
|
|
}
|
|
|
|
return parse_modules($type, ['m' => implode(',', $this->modules)]);
|
|
}
|
|
|
|
private function qPrint($var)
|
|
{
|
|
if ($this->quiet) {
|
|
return;
|
|
}
|
|
|
|
if (is_array($var)) {
|
|
print_r($var);
|
|
} else {
|
|
echo $var;
|
|
}
|
|
}
|
|
|
|
private function convertSnmpToSnmprec(SnmpResponse $snmp_data): array
|
|
{
|
|
$result = [];
|
|
foreach (explode(PHP_EOL, $snmp_data->raw) as $line) {
|
|
if (empty($line)) {
|
|
continue;
|
|
}
|
|
|
|
if (preg_match('/^\.[.\d]+ =/', $line)) {
|
|
[$oid, $raw_data] = explode(' =', $line, 2);
|
|
$oid = ltrim($oid, '.');
|
|
$raw_data = trim($raw_data);
|
|
|
|
if (empty($raw_data) || $raw_data == '""') {
|
|
$result[] = "$oid|4|"; // empty data, we don't know type, put string
|
|
} else {
|
|
[$raw_type, $data] = explode(':', $raw_data, 2);
|
|
if (Str::startsWith($raw_type, 'Wrong Type (should be ')) {
|
|
// device returned the wrong type, save the wrong type to emulate the device behavior
|
|
[$raw_type, $data] = explode(':', ltrim($data), 2);
|
|
}
|
|
|
|
$type = $this->getSnmprecType($raw_type);
|
|
|
|
$data = ltrim($data, ' ');
|
|
if (Str::startsWith($data, '"') && Str::endsWith($data, '"')) {
|
|
// raw string surrounded by quotes, strip extra escapes
|
|
$data = stripslashes(substr($data, 1, -1));
|
|
}
|
|
|
|
if ($type == '6') {
|
|
// remove leading . from oid data
|
|
$data = ltrim($data, '.');
|
|
} elseif ($type == '4x') {
|
|
// remove spaces from hex-strings
|
|
$data = str_replace(' ', '', $data);
|
|
} elseif ($type == '67') {
|
|
// extract timeticks value (-Ot removes type info)
|
|
preg_match('/\((\d+)\)/', $data, $match);
|
|
$data = $match[1];
|
|
}
|
|
|
|
$result[] = "$oid|$type|$data";
|
|
}
|
|
} else {
|
|
// multi-line data, append to last
|
|
$last = end($result);
|
|
|
|
[$oid, $type, $data] = explode('|', $last, 3);
|
|
if ($type == '4x') {
|
|
$result[key($result)] .= bin2hex(PHP_EOL . $line);
|
|
} else {
|
|
$result[key($result)] = "$oid|4x|" . bin2hex($data . PHP_EOL . $line);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
private function getSnmprecType($text)
|
|
{
|
|
$snmpTypes = [
|
|
'STRING' => '4',
|
|
'OID' => '6',
|
|
'Hex-STRING' => '4x',
|
|
'Timeticks' => '67',
|
|
'INTEGER' => '2',
|
|
'OCTET STRING' => '4',
|
|
'BITS' => '4', // not sure if this is right
|
|
'Integer32' => '2',
|
|
'NULL' => '5',
|
|
'OBJECT IDENTIFIER' => '6',
|
|
'IpAddress' => '64',
|
|
'Counter32' => '65',
|
|
'Gauge32' => '66',
|
|
'Opaque' => '68',
|
|
'Counter64' => '70',
|
|
'Network Address' => '4',
|
|
];
|
|
|
|
return $snmpTypes[$text];
|
|
}
|
|
|
|
private function saveSnmprec(array $data, ?string $context = null, bool $write = true, bool $prefer_new = false): string
|
|
{
|
|
$filename = $this->snmprec_file;
|
|
|
|
if ($context) {
|
|
$filename = str_replace('.snmprec', '', $filename) . "@$context.snmprec";
|
|
}
|
|
|
|
if (is_file($filename)) {
|
|
$existing_data = $this->indexSnmprec(explode(PHP_EOL, file_get_contents($filename)));
|
|
} else {
|
|
$existing_data = [];
|
|
}
|
|
|
|
$new_data = [];
|
|
foreach ($data as $part) {
|
|
$new_data = array_merge($new_data, $this->indexSnmprec($part));
|
|
}
|
|
|
|
$this->cleanSnmprecData($new_data);
|
|
|
|
// merge new and existing data
|
|
if ($prefer_new) {
|
|
$results = array_merge($existing_data, $new_data);
|
|
} else {
|
|
$results = array_merge($new_data, $existing_data);
|
|
}
|
|
|
|
// put data in the proper order for snmpsim
|
|
uksort($results, [$this, 'compareOid']);
|
|
|
|
$output = implode(PHP_EOL, $results) . PHP_EOL;
|
|
|
|
if ($write) {
|
|
if (empty($results)) {
|
|
$this->qPrint("No data for $filename\n");
|
|
} else {
|
|
$this->qPrint("\nSaved snmprec data $filename\n");
|
|
file_put_contents($filename, $output);
|
|
}
|
|
}
|
|
|
|
return $output;
|
|
}
|
|
|
|
private function indexSnmprec(array $snmprec_data)
|
|
{
|
|
$result = [];
|
|
|
|
foreach ($snmprec_data as $line) {
|
|
if (! empty($line)) {
|
|
[$oid] = explode('|', $line, 2);
|
|
$result[$oid] = $line;
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
private function cleanSnmprecData(&$data)
|
|
{
|
|
$private_oid = [
|
|
'1.3.6.1.2.1.1.6.0',
|
|
'1.3.6.1.2.1.1.4.0',
|
|
'1.3.6.1.2.1.1.5.0',
|
|
];
|
|
|
|
foreach ($private_oid as $oid) {
|
|
if (isset($data[$oid])) {
|
|
$parts = explode('|', $data[$oid], 3);
|
|
$parts[2] = $parts[1] === '4' ? '<private>' : '3C707269766174653E';
|
|
$data[$oid] = implode('|', $parts);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Run discovery and polling against snmpsim data and create a database dump
|
|
* Save the dumped data to tests/data/<os>.json
|
|
*
|
|
* @param Snmpsim $snmpsim
|
|
* @param bool $no_save
|
|
* @return array|null
|
|
*
|
|
* @throws FileNotFoundException
|
|
*/
|
|
public function generateTestData(Snmpsim $snmpsim, $no_save = false)
|
|
{
|
|
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
|
|
|
|
// don't allow external DNS queries that could fail
|
|
app()->bind(\LibreNMS\Util\AutonomousSystem::class, function ($app, $parameters) {
|
|
$asn = $parameters['asn'];
|
|
$mock = \Mockery::mock(\LibreNMS\Util\AutonomousSystem::class);
|
|
$mock->shouldReceive('name')->withAnyArgs()->zeroOrMoreTimes()->andReturnUsing(function () use ($asn) {
|
|
return "AS$asn-MOCK-TEXT";
|
|
});
|
|
|
|
return $mock;
|
|
});
|
|
|
|
if (! is_file($this->snmprec_file)) {
|
|
throw new FileNotFoundException("$this->snmprec_file does not exist!");
|
|
}
|
|
|
|
// Remove existing device in case it didn't get removed previously
|
|
if (($existing_device = device_by_name($snmpsim->getIp())) && isset($existing_device['device_id'])) {
|
|
delete_device($existing_device['device_id']);
|
|
}
|
|
|
|
// Add the test device
|
|
try {
|
|
$new_device = new Device([
|
|
'hostname' => $snmpsim->getIp(),
|
|
'version' => 'v2c',
|
|
'community' => $this->file_name,
|
|
'port' => $snmpsim->getPort(),
|
|
'disabled' => 1, // disable to block normal pollers
|
|
]);
|
|
(new ValidateDeviceAndCreate($new_device, true))->execute();
|
|
$device_id = $new_device->device_id;
|
|
|
|
$this->qPrint("Added device: $device_id\n");
|
|
} catch (\Exception $e) {
|
|
echo $this->file_name . ': ' . $e->getMessage() . PHP_EOL;
|
|
|
|
return null;
|
|
}
|
|
|
|
// Populate the device variable
|
|
$device = device_by_id_cache($device_id, true);
|
|
DeviceCache::setPrimary($device_id);
|
|
|
|
$data = []; // array to hold dumped data
|
|
|
|
// Run discovery
|
|
$save_debug = Debug::isEnabled();
|
|
$save_vedbug = Debug::isVerbose();
|
|
if ($this->quiet) {
|
|
Debug::setOnly();
|
|
Debug::setVerbose();
|
|
}
|
|
ob_start();
|
|
|
|
discover_device($device, $this->parseArgs('discovery'));
|
|
|
|
$this->discovery_output = ob_get_contents();
|
|
if ($this->quiet) {
|
|
Debug::setOnly($save_debug);
|
|
Debug::setVerbose($save_vedbug);
|
|
} else {
|
|
ob_flush();
|
|
}
|
|
ob_end_clean();
|
|
|
|
$this->qPrint(PHP_EOL);
|
|
|
|
// Parse discovered modules
|
|
$this->discovery_module_output = $this->extractModuleOutput($this->discovery_output, 'disco');
|
|
$discovered_modules = array_keys($this->discovery_module_output);
|
|
|
|
// Dump the discovered data
|
|
$data = array_merge_recursive($data, $this->dumpDb($device['device_id'], $discovered_modules, 'discovery'));
|
|
DeviceCache::get($device_id)->refresh(); // refresh the device
|
|
|
|
// Run the poller
|
|
if ($this->quiet) {
|
|
Debug::setOnly();
|
|
Debug::setVerbose();
|
|
}
|
|
ob_start();
|
|
|
|
\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) {
|
|
Debug::setOnly($save_debug);
|
|
Debug::setVerbose($save_vedbug);
|
|
} else {
|
|
ob_flush();
|
|
}
|
|
ob_end_clean();
|
|
|
|
// Parse polled modules
|
|
$this->poller_module_output = $this->extractModuleOutput($this->poller_output, 'poller');
|
|
$polled_modules = array_keys($this->poller_module_output);
|
|
|
|
// Dump polled data
|
|
$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_id);
|
|
}
|
|
|
|
if (! $no_save) {
|
|
d_echo($data);
|
|
|
|
// Save the data to the default test data location (or elsewhere if specified)
|
|
$existing_data = json_decode(file_get_contents($this->json_file), true);
|
|
|
|
// insert new data, don't store duplicate data
|
|
foreach ($data as $module => $module_data) {
|
|
// skip saving modules with no data
|
|
if (empty($module_data['discovery']) && empty($module_data['poller'])) {
|
|
continue;
|
|
}
|
|
if ($module_data['discovery'] == $module_data['poller']) {
|
|
$existing_data[$module] = [
|
|
'discovery' => $module_data['discovery'],
|
|
'poller' => 'matches discovery',
|
|
];
|
|
} else {
|
|
$existing_data[$module] = $module_data;
|
|
}
|
|
}
|
|
|
|
file_put_contents($this->json_file, json_encode($existing_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . PHP_EOL);
|
|
$this->qPrint("Saved to $this->json_file\nReady for testing!\n");
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* @param string $output poller or discovery output
|
|
* @param string $type poller|disco identified by "#### Load disco module" string
|
|
* @return array
|
|
*/
|
|
private function extractModuleOutput($output, $type)
|
|
{
|
|
$module_output = [];
|
|
$module_start = "#### Load $type module ";
|
|
$module_end = "#### Unload $type module %s ####";
|
|
$parts = explode($module_start, $output);
|
|
array_shift($parts); // throw away first part of output
|
|
foreach ($parts as $part) {
|
|
// find the module name
|
|
$module = strtok($part, ' ');
|
|
|
|
// insert the name into the end string
|
|
$end = sprintf($module_end, $module);
|
|
|
|
// find the end
|
|
$end_pos = strrpos($part, $end) ?: -1;
|
|
|
|
// save output, re-add bits we used for parsing
|
|
$module_output[$module] = $module_start . substr($part, 0, $end_pos) . $end;
|
|
}
|
|
|
|
return $module_output;
|
|
}
|
|
|
|
/**
|
|
* Dump the current database data for the module to an array
|
|
* Mostly used for testing
|
|
*
|
|
* @param int $device_id The test device id
|
|
* @param array $modules to capture data for (should be a list of modules that were actually run)
|
|
* @param string $type a key to store the data under the module key (usually discovery or poller)
|
|
* @return array The dumped data keyed by module -> table
|
|
*/
|
|
public function dumpDb($device_id, $modules, $type)
|
|
{
|
|
$data = [];
|
|
|
|
// 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 (and modules that support dumping)
|
|
foreach ($modules as $module) {
|
|
$module_data = Module::fromName($module)->dump(DeviceCache::get($device_id));
|
|
if ($module_data !== false) {
|
|
$data[$module][$type] = $this->dumpToArray($module_data);
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* @param array|\Illuminate\Support\Collection|\stdClass $data
|
|
* @return array
|
|
*/
|
|
private function dumpToArray($data): array
|
|
{
|
|
$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;
|
|
}
|
|
|
|
/**
|
|
* Get the output from the last discovery that was run
|
|
* If module was specified, only return that module's output
|
|
*
|
|
* @param null $module
|
|
* @return mixed
|
|
*/
|
|
public function getDiscoveryOutput($module = null)
|
|
{
|
|
if ($module) {
|
|
if (isset($this->discovery_module_output[$module])) {
|
|
return $this->discovery_module_output[$module];
|
|
} else {
|
|
return "Module $module not run. Modules: " . implode(',', array_keys($this->poller_module_output));
|
|
}
|
|
}
|
|
|
|
return $this->discovery_output;
|
|
}
|
|
|
|
/**
|
|
* Get output from the last poller that was run
|
|
* If module was specified, only return that module's output
|
|
*
|
|
* @param null $module
|
|
* @return mixed
|
|
*/
|
|
public function getPollerOutput($module = null)
|
|
{
|
|
if ($module) {
|
|
if (isset($this->poller_module_output[$module])) {
|
|
return $this->poller_module_output[$module];
|
|
} else {
|
|
return "Module $module not run. Modules: " . implode(',', array_keys($this->poller_module_output));
|
|
}
|
|
}
|
|
|
|
return $this->poller_output;
|
|
}
|
|
|
|
public function getTestData()
|
|
{
|
|
return json_decode(file_get_contents($this->json_file), true);
|
|
}
|
|
|
|
public function getJsonFilepath($short = false)
|
|
{
|
|
if ($short) {
|
|
return ltrim(str_replace(Config::get('install_dir'), '', $this->json_file), '/');
|
|
}
|
|
|
|
return $this->json_file;
|
|
}
|
|
}
|