New device:add code (#13842)

* New device:add code
pre-requisite for updating other code paths
includes option to set display name
separate validation code from device creation

* remove duplicate community and v3 creds

* style fixes

* some lint fixes

* fix phpstan

* Exception cleanup
improved messages and translations

* port association mode to enum
well, pseudo enum

* defaults and cleanups

* fixed/improved validation messages

* fix tests

* fix stupid ide refactor mistake

* lint fixes
This commit is contained in:
Tony Murray
2022-03-12 16:14:32 -06:00
committed by GitHub
parent 6d3bf03074
commit 1bfd411995
27 changed files with 803 additions and 389 deletions

View File

@@ -0,0 +1,75 @@
<?php
/**
* PortAssociationMode.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\Enum;
class PortAssociationMode
{
const ifIndex = 1;
const ifName = 2;
const ifDescr = 3;
const ifAlias = 4;
/**
* Get mode names keyed by id
*
* @return string[]
*/
public static function getModes(): array
{
return [
self::ifIndex => 'ifIndex',
self::ifName => 'ifName',
self::ifDescr => 'ifDescr',
self::ifAlias => 'ifAlias',
];
}
/**
* Translate a named port association mode to an integer for storage
*
* @param string $name
* @return int|null
*/
public static function getId(string $name): ?int
{
$names = array_flip(self::getModes());
return $names[$name] ?? null;
}
/**
* Get name of given port association mode id
*
* @param int $id
* @return string|null
*/
public static function getName(int $id): ?string
{
$modes = self::getModes();
return $modes[$id] ?? null;
}
}

View File

@@ -27,4 +27,31 @@ namespace LibreNMS\Exceptions;
class HostIpExistsException extends HostExistsException
{
/**
* @var string
*/
public $hostname;
/**
* @var string
*/
public $existing_hostname;
/**
* @var string
*/
public $ip;
public function __construct(string $hostname, string $existing_hostname, string $ip)
{
$this->hostname = $hostname;
$this->existing_hostname = $existing_hostname;
$this->ip = $ip;
$message = trans('exceptions.host_exists.ip_exists', [
'hostname' => $hostname,
'existing' => $existing_hostname,
'ip' => $ip,
]);
parent::__construct($message);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace LibreNMS\Exceptions;
class HostSysnameExistsException extends HostExistsException
{
/**
* @var string
*/
public $hostname;
/**
* @var string
*/
public $sysname;
public function __construct(string $hostname, string $sysname)
{
$this->hostname = $hostname;
$this->sysname = $sysname;
$message = trans('exceptions.host_exists.sysname_exists', [
'hostname' => $hostname,
'sysname' => $sysname,
]);
parent::__construct($message);
}
}

View File

@@ -42,11 +42,21 @@ class HostUnreachableException extends \Exception
/**
* Add additional reasons
*
* @param string $message
* @param string $snmpVersion
* @param string $credentials
*/
public function addReason($message)
public function addReason(string $snmpVersion, string $credentials)
{
$this->reasons[] = $message;
$vars = [
'snmpver' => $snmpVersion,
'credentials' => $credentials,
];
if ($snmpVersion == 'v3') {
$this->reasons[] = trans('exceptions.host_unreachable.no_reply_credentials', $vars);
} else {
$this->reasons[] = trans('exceptions.host_unreachable.no_reply_community', $vars);
}
}
/**

View File

@@ -25,6 +25,29 @@
namespace LibreNMS\Exceptions;
use LibreNMS\Util\IP;
class HostUnreachablePingException extends HostUnreachableException
{
/**
* @var string
*/
public $hostname;
/**
* @var string
*/
public $ip;
public function __construct(string $hostname)
{
$this->hostname = $hostname;
$this->ip = gethostbyname($hostname);
$message = trans('exceptions.host_unreachable.unpingable', [
'hostname' => $hostname,
'ip' => IP::isValid($this->ip) ? $this->ip : trans('exceptions.host_unreachable.unresolvable'),
]);
parent::__construct($message);
}
}

View File

@@ -27,4 +27,17 @@ namespace LibreNMS\Exceptions;
class HostUnreachableSnmpException extends HostUnreachableException
{
/**
* @var string
*/
public $hostname;
public function __construct(string $hostname)
{
$this->hostname = $hostname;
$message = trans('exceptions.host_unreachable.unsnmpable', [
'hostname' => $hostname,
]);
parent::__construct($message);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace LibreNMS\Exceptions;
class HostnameExistsException extends HostExistsException
{
/**
* @var string
*/
public $hostname;
/**
* @var string
*/
public $existing;
public function __construct(string $hostname)
{
$this->hostname = $hostname;
$message = trans('exceptions.host_exists.hostname_exists', [
'hostname' => $hostname,
]);
parent::__construct($message);
}
}

View File

@@ -27,4 +27,16 @@ namespace LibreNMS\Exceptions;
class SnmpVersionUnsupportedException extends \Exception
{
/**
* @var string
*/
public $snmpVersion;
public function __construct(string $snmpVersion)
{
$this->snmpVersion = $snmpVersion;
$message = trans('exceptions.snmp_version_unsupported.message', ['snmpver' => $snmpVersion]);
parent::__construct($message);
}
}

View File

@@ -10,6 +10,7 @@
*/
use LibreNMS\Config;
use LibreNMS\Enum\PortAssociationMode;
use LibreNMS\Exceptions\HostUnreachableException;
$init_modules = [];
@@ -39,7 +40,7 @@ if (isset($options['f']) && $options['f'] == 0) {
}
$port_assoc_mode = Config::get('default_port_association_mode');
$valid_assoc_modes = get_port_assoc_modes();
$valid_assoc_modes = PortAssociationMode::getModes();
if (isset($options['p'])) {
$port_assoc_mode = $options['p'];
if (! in_array($port_assoc_mode, $valid_assoc_modes)) {

View File

@@ -0,0 +1,235 @@
<?php
/*
* ValidateDeviceAndCreate.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 <http://www.gnu.org/licenses/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2022 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace App\Actions\Device;
use App\Models\Device;
use Illuminate\Support\Arr;
use LibreNMS\Config;
use LibreNMS\Exceptions\HostIpExistsException;
use LibreNMS\Exceptions\HostnameExistsException;
use LibreNMS\Exceptions\HostSysnameExistsException;
use LibreNMS\Exceptions\HostUnreachablePingException;
use LibreNMS\Exceptions\HostUnreachableSnmpException;
use LibreNMS\Exceptions\SnmpVersionUnsupportedException;
use LibreNMS\Modules\Core;
use SnmpQuery;
class ValidateDeviceAndCreate
{
/**
* @var \App\Models\Device
*/
private $device;
/**
* @var bool
*/
private $force;
/**
* @var bool
*/
private $ping_fallback;
/**
* @var \LibreNMS\Polling\ConnectivityHelper
*/
private $connectivity;
public function __construct(Device $device, bool $force = false, bool $ping_fallback = false)
{
$this->device = $device;
$this->force = $force;
$this->ping_fallback = $ping_fallback;
$this->connectivity = new \LibreNMS\Polling\ConnectivityHelper($this->device);
}
/**
* @return bool
*
* @throws \LibreNMS\Exceptions\HostExistsException
* @throws \LibreNMS\Exceptions\HostUnreachablePingException
* @throws \LibreNMS\Exceptions\HostUnreachableException
* @throws \LibreNMS\Exceptions\SnmpVersionUnsupportedException
*/
public function execute(): bool
{
if ($this->device->exists) {
return false;
}
$this->exceptIfHostnameExists();
// defaults
$this->device->os = $this->device->os ?: 'generic';
$this->device->status_reason = '';
$this->device->sysName = $this->device->sysName ?: $this->device->hostname;
if (! $this->force) {
$this->exceptIfIpExists();
if (! $this->connectivity->isPingable()->success()) {
throw new HostUnreachablePingException($this->device->hostname);
}
$this->detectCredentials();
$this->cleanCredentials();
$this->device->sysName = SnmpQuery::device($this->device)->get('SNMPv2-MIB::sysName.0')->value();
$this->exceptIfSysNameExists();
$this->device->os = Core::detectOS($this->device);
}
return $this->device->save();
}
/**
* @throws \LibreNMS\Exceptions\HostUnreachableException
* @throws \LibreNMS\Exceptions\SnmpVersionUnsupportedException
*/
private function detectCredentials(): void
{
if ($this->device->snmp_disable) {
return;
}
$host_unreachable_exception = new HostUnreachableSnmpException($this->device->hostname);
// which snmp version should we try (and in what order)
$snmp_versions = $this->device->snmpver ? [$this->device->snmpver] : Config::get('snmp.version');
$communities = \LibreNMS\Config::get('snmp.community');
if ($this->device->community) {
array_unshift($communities, $this->device->community);
}
$communities = array_unique($communities);
$v3_credentials = \LibreNMS\Config::get('snmp.v3');
array_unshift($v3_credentials, $this->device->only(['authlevel', 'authname', 'authpass', 'authalgo', 'cryptopass', 'cryptoalgo']));
$v3_credentials = array_unique($v3_credentials, SORT_REGULAR);
foreach ($snmp_versions as $snmp_version) {
$this->device->snmpver = $snmp_version;
if ($snmp_version === 'v3') {
// Try each set of parameters from config
foreach ($v3_credentials as $v3) {
$this->device->fill(Arr::only($v3, ['authlevel', 'authname', 'authpass', 'authalgo', 'cryptopass', 'cryptoalgo']));
if ($this->connectivity->isSNMPable()) {
return;
} else {
$host_unreachable_exception->addReason($snmp_version, $this->device->authname . '/' . $this->device->authlevel);
}
}
} elseif ($snmp_version === 'v2c' || $snmp_version === 'v1') {
// try each community from config
foreach ($communities as $community) {
$this->device->community = $community;
if ($this->connectivity->isSNMPable()) {
return;
} else {
$host_unreachable_exception->addReason($snmp_version, $this->device->community);
}
}
} else {
throw new SnmpVersionUnsupportedException($snmp_version);
}
}
if ($this->ping_fallback) {
$this->device->snmp_disable = 1;
$this->device->os = 'ping';
return;
}
throw $host_unreachable_exception;
}
private function cleanCredentials(): void
{
if ($this->device->snmpver == 'v3') {
$this->device->community = null;
} else {
$this->device->authlevel = null;
$this->device->authname = null;
$this->device->authalgo = null;
$this->device->cryptopass = null;
$this->device->cryptoalgo = null;
}
}
/**
* @throws \LibreNMS\Exceptions\HostExistsException
*/
private function exceptIfHostnameExists(): void
{
if (Device::where('hostname', $this->device->hostname)->exists()) {
throw new HostnameExistsException($this->device->hostname);
}
}
/**
* @throws \LibreNMS\Exceptions\HostExistsException
*/
private function exceptIfIpExists(): void
{
if ($this->device->overwrite_ip) {
$ip = $this->device->overwrite_ip;
} elseif (Config::get('addhost_alwayscheckip')) {
$ip = gethostbyname($this->device->hostname);
} else {
$ip = $this->device->hostname;
}
$existing = Device::findByIp($ip);
if ($existing) {
throw new HostIpExistsException($this->device->hostname, $existing->hostname, $ip);
}
}
/**
* Check if a device with match hostname or sysname exists in the database.
* Throw and error if they do.
*
* @return void
*
* @throws \LibreNMS\Exceptions\HostExistsException
*/
private function exceptIfSysNameExists(): void
{
if (Config::get('allow_duplicate_sysName')) {
return;
}
if (Device::where('sysName', $this->device->sysName)
->when(Config::get('mydomain'), function ($query, $domain) {
$query->orWhere('sysName', rtrim($this->device->sysName, '.') . '.' . $domain);
})->exists()) {
throw new HostSysnameExistsException($this->device->hostname, $this->device->sysName);
}
}
}

View File

@@ -0,0 +1,147 @@
<?php
namespace App\Console\Commands;
use App\Actions\Device\ValidateDeviceAndCreate;
use App\Console\LnmsCommand;
use App\Models\Device;
use App\Models\PollerGroup;
use Exception;
use Illuminate\Validation\Rule;
use LibreNMS\Config;
use LibreNMS\Enum\PortAssociationMode;
use LibreNMS\Exceptions\HostExistsException;
use LibreNMS\Exceptions\HostnameExistsException;
use LibreNMS\Exceptions\HostUnreachableException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
class DeviceAdd extends LnmsCommand
{
/**
* The name of the console command.
*
* @var string
*/
protected $name = 'device:add';
/**
* Valid values for options
*
* @var string[][]|null
*/
protected $optionValues;
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
$this->optionValues = [
'transport' => ['udp', 'udp6', 'tcp', 'tcp6'],
'port-association-mode' => PortAssociationMode::getModes(),
'auth-protocol' => \LibreNMS\SNMPCapabilities::supportedAuthAlgorithms(),
'privacy-protocol' => \LibreNMS\SNMPCapabilities::supportedCryptoAlgorithms(),
];
$this->addArgument('device spec', InputArgument::REQUIRED);
$this->addOption('v1', null, InputOption::VALUE_NONE);
$this->addOption('v2c', null, InputOption::VALUE_NONE);
$this->addOption('v3', null, InputOption::VALUE_NONE);
$this->addOption('display-name', 'd', InputOption::VALUE_REQUIRED);
$this->addOption('force', 'f', InputOption::VALUE_NONE);
$this->addOption('poller-group', 'g', InputOption::VALUE_REQUIRED, null, Config::get('default_poller_group'));
$this->addOption('ping-fallback', 'b', InputOption::VALUE_NONE);
$this->addOption('port-association-mode', 'p', InputOption::VALUE_REQUIRED, null, Config::get('default_port_association_mode'));
$this->addOption('community', 'c', InputOption::VALUE_REQUIRED);
$this->addOption('transport', 't', InputOption::VALUE_REQUIRED, null, 'udp');
$this->addOption('port', 'r', InputOption::VALUE_REQUIRED, null, 161);
$this->addOption('security-name', 'u', InputOption::VALUE_REQUIRED, null, 'root');
$this->addOption('auth-password', 'A', InputOption::VALUE_REQUIRED);
$this->addOption('auth-protocol', 'a', InputOption::VALUE_REQUIRED, null, 'MD5');
$this->addOption('privacy-password', 'X', InputOption::VALUE_REQUIRED);
$this->addOption('privacy-protocol', 'x', InputOption::VALUE_REQUIRED, null, 'AES');
$this->addOption('ping-only', 'P', InputOption::VALUE_NONE);
$this->addOption('os', 'o', InputOption::VALUE_REQUIRED, null, 'ping');
$this->addOption('hardware', 'w', InputOption::VALUE_REQUIRED);
$this->addOption('sysName', 's', InputOption::VALUE_REQUIRED);
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$this->configureOutputOptions();
$this->validate([
'port' => 'numeric|between:1,65535',
'poller-group' => ['numeric', Rule::in(PollerGroup::pluck('id')->prepend(0))],
]);
$auth = $this->option('auth-password');
$priv = $this->option('privacy-password');
$device = new Device([
'hostname' => $this->argument('device spec'),
'display' => $this->option('display-name'),
'snmpver' => $this->option('v3') ? 'v3' : ($this->option('v2c') ? 'v2c' : ($this->option('v1') ? 'v1' : '')),
'port' => $this->option('port'),
'transport' => $this->option('transport'),
'poller_group' => $this->option('poller-group'),
'port_association_mode' => PortAssociationMode::getId($this->option('port-association-mode')),
'community' => $this->option('community'),
'authlevel' => ($auth ? 'auth' : 'noAuth') . (($priv && $auth) ? 'Priv' : 'NoPriv'),
'authname' => $this->option('security-name'),
'authpass' => $this->option('auth-password'),
'authalgo' => $this->option('auth-protocol'),
'cryptopass' => $this->option('privacy-password'),
'cryptoalgo' => $this->option('privacy-protocol'),
]);
if ($this->option('ping-only')) {
$device->snmp_disable = 1;
$device->os = $this->option('os');
$device->hardware = $this->option('hardware');
$device->sysName = $this->option('sysName');
}
try {
$result = (new ValidateDeviceAndCreate($device, $this->option('force'), $this->option('ping-fallback')))->execute();
if (! $result) {
$this->error(trans('commands.device:add.messages.save_failed', ['hostname' => $device->hostname]));
return 4;
}
$this->info(trans('commands.device:add.messages.added', ['hostname' => $device->hostname, 'device_id' => $device->device_id]));
return 0;
} catch (HostUnreachableException $e) {
// host unreachable errors
$this->error($e->getMessage() . PHP_EOL . implode(PHP_EOL, $e->getReasons()));
$this->line(trans('commands.device:add.messages.try_force'));
return 1;
} catch (HostExistsException $e) {
// host exists errors
$this->error($e->getMessage());
if (! $e instanceof HostnameExistsException) {
$this->line(trans('commands.device:add.messages.try_force'));
}
return 2;
} catch (Exception $e) {
// other errors?
$this->error(get_class($e) . ': ' . $e->getMessage());
return 3;
}
}
}

View File

@@ -26,6 +26,7 @@
namespace App\Console;
use Illuminate\Console\Command;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use LibreNMS\Util\Debug;
use Symfony\Component\Console\Exception\InvalidArgumentException;
@@ -35,6 +36,9 @@ abstract class LnmsCommand extends Command
{
protected $developer = false;
/** @var string[][]|null */
protected $optionValues;
/**
* Create a new command instance.
*
@@ -97,6 +101,10 @@ abstract class LnmsCommand extends Command
$description = __('commands.' . $this->getName() . '.options.' . $name);
}
if (isset($this->optionValues[$name])) {
$description .= ' [' . implode(', ', $this->optionValues[$name]) . ']';
}
parent::addOption($name, $shortcut, $mode, $description, $default);
return $this;
@@ -108,6 +116,19 @@ abstract class LnmsCommand extends Command
*/
protected function validate(array $rules, array $messages = []): array
{
// auto create option value rules if they don't exist
if (isset($this->optionValues)) {
foreach ($this->optionValues as $option => $values) {
if (empty($rules[$option])) {
$rules[$option] = Rule::in($values);
$messages[$option . '.in'] = trans('commands.lnms.validation-errors.optionValue', [
'option' => $option,
'values' => implode(', ', $values),
]);
}
}
}
$error_messages = trans('commands.' . $this->getName() . '.validation-errors');
$validator = Validator::make(
$this->arguments() + $this->options(),

View File

@@ -79,7 +79,7 @@ class Device extends BaseModel
// ---- Helper Functions ----
public static function findByHostname($hostname)
public static function findByHostname(string $hostname): ?Device
{
return static::where('hostname', $hostname)->first();
}
@@ -110,7 +110,7 @@ class Device extends BaseModel
return $overwrite_ip ?: $hostname;
}
public static function findByIp($ip)
public static function findByIp(?string $ip): ?Device
{
if (! IP::isValid($ip)) {
return null;
@@ -561,8 +561,8 @@ class Device extends BaseModel
return $query->whereIn(
$query->qualifyColumn('device_id'), function ($query) use ($deviceGroup) {
$query->select('device_id')
->from('device_group_device')
->where('device_group_id', $deviceGroup);
->from('device_group_device')
->where('device_group_id', $deviceGroup);
}
);
}
@@ -572,8 +572,8 @@ class Device extends BaseModel
return $query->whereNotIn(
$query->qualifyColumn('device_id'), function ($query) use ($deviceGroup) {
$query->select('device_id')
->from('device_group_device')
->where('device_group_id', $deviceGroup);
->from('device_group_device')
->where('device_group_id', $deviceGroup);
}
);
}
@@ -583,8 +583,8 @@ class Device extends BaseModel
return $query->whereIn(
$query->qualifyColumn('device_id'), function ($query) use ($serviceTemplate) {
$query->select('device_id')
->from('service_templates_device')
->where('service_template_id', $serviceTemplate);
->from('service_templates_device')
->where('service_template_id', $serviceTemplate);
}
);
}
@@ -594,8 +594,8 @@ class Device extends BaseModel
return $query->whereNotIn(
$query->qualifyColumn('device_id'), function ($query) use ($serviceTemplate) {
$query->select('device_id')
->from('service_templates_device')
->where('service_template_id', $serviceTemplate);
->from('service_templates_device')
->where('service_template_id', $serviceTemplate);
}
);
}

View File

@@ -651,47 +651,6 @@ function format_hostname($device): string
]);
}
/**
* Return valid port association modes
*
* @return array
*/
function get_port_assoc_modes()
{
return [
1 => 'ifIndex',
2 => 'ifName',
3 => 'ifDescr',
4 => 'ifAlias',
];
}
/**
* Get DB id of given port association mode name
*
* @param string $port_assoc_mode
* @return int
*/
function get_port_assoc_mode_id($port_assoc_mode)
{
$modes = array_flip(get_port_assoc_modes());
return isset($modes[$port_assoc_mode]) ? $modes[$port_assoc_mode] : false;
}
/**
* Get name of given port association_mode ID
*
* @param int $port_assoc_mode_id Port association mode ID
* @return bool
*/
function get_port_assoc_mode_name($port_assoc_mode_id)
{
$modes = get_port_assoc_modes();
return isset($modes[$port_assoc_mode_id]) ? $modes[$port_assoc_mode_id] : false;
}
/**
* Query all ports of the given device (by ID) and build port array and
* port association maps for ifIndex, ifName, ifDescr. Query port stats

View File

@@ -3,6 +3,7 @@
// Build SNMP Cache Array
use App\Models\PortGroup;
use LibreNMS\Config;
use LibreNMS\Enum\PortAssociationMode;
use LibreNMS\Util\StringHelpers;
$descrSnmpFlags = '-OQUs';
@@ -42,7 +43,7 @@ d_echo($port_stats);
// compatibility reasons.
$port_association_mode = Config::get('default_port_association_mode');
if ($device['port_association_mode']) {
$port_association_mode = get_port_assoc_mode_name($device['port_association_mode']);
$port_association_mode = PortAssociationMode::getName($device['port_association_mode']);
}
// Build array of ports in the database and an ifIndex/ifName -> port_id map

View File

@@ -12,15 +12,17 @@ use App\Models\Device;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use LibreNMS\Config;
use LibreNMS\Enum\PortAssociationMode;
use LibreNMS\Exceptions\HostExistsException;
use LibreNMS\Exceptions\HostIpExistsException;
use LibreNMS\Exceptions\HostnameExistsException;
use LibreNMS\Exceptions\HostSysnameExistsException;
use LibreNMS\Exceptions\HostUnreachableException;
use LibreNMS\Exceptions\HostUnreachablePingException;
use LibreNMS\Exceptions\HostUnreachableSnmpException;
use LibreNMS\Exceptions\InvalidPortAssocModeException;
use LibreNMS\Exceptions\SnmpVersionUnsupportedException;
use LibreNMS\Modules\Core;
use LibreNMS\Util\IPv4;
use LibreNMS\Util\IPv6;
use LibreNMS\Util\Proxy;
function array_sort_by_column($array, $on, $order = SORT_ASC)
@@ -112,24 +114,6 @@ function logfile($string)
fclose($fd);
}
/**
* Check an array of regexes against a subject if any match, return true
*
* @param string $subject the string to match against
* @param array|string $regexes an array of regexes or single regex to check
* @return bool if any of the regexes matched, return true
*/
function preg_match_any($subject, $regexes)
{
foreach ((array) $regexes as $regex) {
if (preg_match($regex, $subject)) {
return true;
}
}
return false;
}
/**
* Perform comparison of two items based on give comparison method
* Valid comparisons: =, !=, ==, !==, >=, <=, >, <, contains, starts, ends, regex
@@ -201,36 +185,6 @@ function percent_colour($perc)
return sprintf('#%02x%02x%02x', $r, $b, $b);
}
/**
* @param $device
* @return string the logo image path for this device. Images are often wide, not square.
*/
function getLogo($device)
{
$img = getImageName($device, true, 'images/logos/');
if (! Str::startsWith($img, 'generic')) {
return 'images/logos/' . $img;
}
return getIcon($device);
}
/**
* @param array $device
* @param string $class to apply to the image tag
* @return string an image tag with the logo for this device. Images are often wide, not square.
*/
function getLogoTag($device, $class = null)
{
$tag = '<img src="' . url(getLogo($device)) . '" title="' . getImageTitle($device) . '"';
if (isset($class)) {
$tag .= " class=\"$class\" ";
}
$tag .= ' />';
return $tag;
}
/**
* @param $device
* @return string the path to the icon image for this device. Close to square.
@@ -330,12 +284,12 @@ function addHost($host, $snmp_version = '', $port = 161, $transport = 'udp', $po
{
// Test Database Exists
if (host_exists($host)) {
throw new HostExistsException("Already have host $host");
throw new HostnameExistsException($host);
}
// Valid port assoc mode
if (! in_array($port_assoc_mode, get_port_assoc_modes())) {
throw new InvalidPortAssocModeException("Invalid port association_mode '$port_assoc_mode'. Valid modes are: " . join(', ', get_port_assoc_modes()));
if (! in_array($port_assoc_mode, PortAssociationMode::getModes())) {
throw new InvalidPortAssocModeException("Invalid port association_mode '$port_assoc_mode'. Valid modes are: " . join(', ', PortAssociationMode::getModes()));
}
// check if we have the host by IP
@@ -348,19 +302,14 @@ function addHost($host, $snmp_version = '', $port = 161, $transport = 'udp', $po
} else {
$ip = $host;
}
if ($force_add !== true && $device = device_has_ip($ip)) {
$message = "Cannot add $host, already have device with this IP $ip";
if ($ip != $device->hostname) {
$message .= " ($device->hostname)";
}
$message .= '. You may force add to ignore this.';
throw new HostIpExistsException($message);
if ($force_add !== true && $existing = device_has_ip($ip)) {
throw new HostIpExistsException($host, $existing->hostname, $ip);
}
// Test reachability
if (! $force_add) {
if (! ((new \LibreNMS\Polling\ConnectivityHelper(new Device(['hostname' => $ip])))->isPingable()->success())) {
throw new HostUnreachablePingException("Could not ping $host");
throw new HostUnreachablePingException($host);
}
}
@@ -374,7 +323,7 @@ function addHost($host, $snmp_version = '', $port = 161, $transport = 'udp', $po
if (isset($additional['snmp_disable']) && $additional['snmp_disable'] == 1) {
return createHost($host, '', $snmp_version, $port, $transport, [], $poller_group, 1, true, $overwrite_ip, $additional);
}
$host_unreachable_exception = new HostUnreachableException("Could not connect to $host, please check the snmp details and snmp reachability");
$host_unreachable_exception = new HostUnreachableSnmpException($host);
// try different snmp variables to add the device
foreach ($snmpvers as $snmpver) {
if ($snmpver === 'v3') {
@@ -384,7 +333,7 @@ function addHost($host, $snmp_version = '', $port = 161, $transport = 'udp', $po
if ($force_add === true || isSNMPable($device)) {
return createHost($host, null, $snmpver, $port, $transport, $v3, $poller_group, $port_assoc_mode, $force_add, $overwrite_ip);
} else {
$host_unreachable_exception->addReason("SNMP $snmpver: No reply with credentials " . $v3['authname'] . '/' . $v3['authlevel']);
$host_unreachable_exception->addReason($snmpver, $v3['authname'] . '/' . $v3['authlevel']);
}
}
} elseif ($snmpver === 'v2c' || $snmpver === 'v1') {
@@ -395,11 +344,11 @@ function addHost($host, $snmp_version = '', $port = 161, $transport = 'udp', $po
if ($force_add === true || isSNMPable($device)) {
return createHost($host, $community, $snmpver, $port, $transport, [], $poller_group, $port_assoc_mode, $force_add, $overwrite_ip);
} else {
$host_unreachable_exception->addReason("SNMP $snmpver: No reply with community $community");
$host_unreachable_exception->addReason($snmpver, $community);
}
}
} else {
throw new SnmpVersionUnsupportedException("Unsupported SNMP Version \"$snmpver\", must be v1, v2c, or v3");
throw new SnmpVersionUnsupportedException($snmpver);
}
}
if (isset($additional['ping_fallback']) && $additional['ping_fallback'] == 1) {
@@ -422,7 +371,7 @@ function deviceArray($host, $community, $snmpver, $port = 161, $transport = 'udp
/* Get port_assoc_mode id if neccessary
* We can work with names of IDs here */
if (! is_int($port_assoc_mode)) {
$port_assoc_mode = get_port_assoc_mode_id($port_assoc_mode);
$port_assoc_mode = PortAssociationMode::getId($port_assoc_mode);
}
$device['port_association_mode'] = $port_assoc_mode;
@@ -518,7 +467,7 @@ function createHost(
/* Get port_assoc_mode id if necessary
* We can work with names of IDs here */
if (! is_int($port_assoc_mode)) {
$port_assoc_mode = get_port_assoc_mode_id($port_assoc_mode);
$port_assoc_mode = PortAssociationMode::getId($port_assoc_mode);
}
$device = new Device(array_merge([
@@ -543,7 +492,7 @@ function createHost(
$device->sysName = SnmpQuery::device($device)->get('SNMPv2-MIB::sysName.0')->value();
if (host_exists($host, $device->sysName)) {
throw new HostExistsException("Already have host $host ({$device->sysName}) due to duplicate sysName");
throw new HostSysnameExistsException($host, $device->sysName);
}
}
if ($device->save()) {
@@ -920,23 +869,7 @@ function fix_integer_value($value)
*/
function device_has_ip($ip)
{
if (IPv6::isValid($ip)) {
$ip_address = \App\Models\Ipv6Address::query()
->where('ipv6_address', IPv6::parse($ip, true)->uncompressed())
->with('port.device')
->first();
} elseif (IPv4::isValid($ip)) {
$ip_address = \App\Models\Ipv4Address::query()
->where('ipv4_address', $ip)
->with('port.device')
->first();
}
if (isset($ip_address) && $ip_address->port) {
return $ip_address->port->device;
}
return false; // not an ipv4 or ipv6 address...
return Device::findByIp($ip);
}
/**
@@ -1312,17 +1245,6 @@ function q_bridge_bits2indices($hex_data)
return $indices;
}
function update_device_logo(&$device)
{
$icon = getImageName($device, false);
if ($icon != $device['icon']) {
log_event('Device Icon changed ' . $device['icon'] . " => $icon", $device, 'system', 3);
$device['icon'] = $icon;
dbUpdate(['icon' => $icon], 'devices', 'device_id=?', [$device['device_id']]);
echo "Changed Icon! : $icon\n";
}
}
/**
* Function to generate Mac OUI Cache
*/

View File

@@ -95,3 +95,23 @@ if (! function_exists('trans_fb')) {
return ($key === ($translation = trans($key, $replace, $locale))) ? $fallback : $translation;
}
}
if (! function_exists('preg_match_any')) {
/**
* Check an array of regexes against a subject if any match, return true
*
* @param string $subject the string to match against
* @param array|string $regexes an array of regexes or single regex to check
* @return bool if any of the regexes matched, return true
*/
function preg_match_any($subject, $regexes)
{
foreach ((array) $regexes as $regex) {
if (preg_match($regex, $subject)) {
return true;
}
}
return false;
}
}

View File

@@ -1,6 +1,7 @@
<?php
use LibreNMS\Config;
use LibreNMS\Enum\PortAssociationMode;
use LibreNMS\Exceptions\HostUnreachableException;
use LibreNMS\Util\IP;
@@ -181,7 +182,7 @@ foreach (Config::get('snmp.transports') as $transport) {
<select name="port_assoc_mode" id="port_assoc_mode" class="form-control input-sm">
<?php
foreach (get_port_assoc_modes() as $mode) {
foreach (PortAssociationMode::getModes() as $mode) {
$selected = '';
if ($mode == Config::get('default_port_association_mode')) {
$selected = 'selected';

View File

@@ -1,6 +1,7 @@
<?php
use LibreNMS\Config;
use LibreNMS\Enum\PortAssociationMode;
if ($_POST['editing']) {
if (Auth::user()->hasGlobalAdmin()) {
@@ -286,7 +287,7 @@ echo " </select>
<select name='port_assoc_mode' id='port_assoc_mode' class='form-control input-sm'>
";
foreach (get_port_assoc_modes() as $pam_id => $pam) {
foreach (PortAssociationMode::getModes() as $pam_id => $pam) {
echo " <option value='$pam_id'";
if ($pam_id == $device['port_association_mode']) {

View File

@@ -3,6 +3,7 @@
// Build SNMP Cache Array
use Illuminate\Support\Str;
use LibreNMS\Config;
use LibreNMS\Enum\PortAssociationMode;
use LibreNMS\RRD\RrdDefinition;
use LibreNMS\Util\Debug;
use LibreNMS\Util\Number;
@@ -460,7 +461,7 @@ d_echo($port_stats);
// compatibility reasons.
$port_association_mode = Config::get('default_port_association_mode');
if ($device['port_association_mode']) {
$port_association_mode = get_port_assoc_mode_name($device['port_association_mode']);
$port_association_mode = PortAssociationMode::getName($device['port_association_mode']);
}
$ports_found = [];

View File

@@ -7190,6 +7190,11 @@ parameters:
count: 1
path: LibreNMS/Validator.php
-
message: "#^Property App\\\\Models\\\\Device\\:\\:\\$authlevel \\('authNoPriv'\\|'authPriv'\\|'noAuthNoPriv'\\) does not accept null\\.$#"
count: 1
path: app/Actions/Device/ValidateDeviceAndCreate.php
-
message: "#^Property App\\\\ApiClients\\\\BaseApi\\:\\:\\$base_uri has no type specified\\.$#"
count: 1
@@ -9020,26 +9025,6 @@ parameters:
count: 1
path: app/Models/Config.php
-
message: "#^Method App\\\\Models\\\\Device\\:\\:findByHostname\\(\\) has no return type specified\\.$#"
count: 1
path: app/Models/Device.php
-
message: "#^Method App\\\\Models\\\\Device\\:\\:findByHostname\\(\\) has parameter \\$hostname with no type specified\\.$#"
count: 1
path: app/Models/Device.php
-
message: "#^Method App\\\\Models\\\\Device\\:\\:findByIp\\(\\) has no return type specified\\.$#"
count: 1
path: app/Models/Device.php
-
message: "#^Method App\\\\Models\\\\Device\\:\\:findByIp\\(\\) has parameter \\$ip with no type specified\\.$#"
count: 1
path: app/Models/Device.php
-
message: "#^Method App\\\\Models\\\\Device\\:\\:forgetAttrib\\(\\) has no return type specified\\.$#"
count: 1
@@ -10210,71 +10195,6 @@ parameters:
count: 1
path: database/migrations/2021_02_09_122930_migrate_to_utf8mb4.php
-
message: "#^Method LibreNMS\\\\Tests\\\\AddHostCliTest\\:\\:testCLIping\\(\\) has no return type specified\\.$#"
count: 1
path: tests/AddHostCliTest.php
-
message: "#^Method LibreNMS\\\\Tests\\\\AddHostCliTest\\:\\:testCLIsnmpV1\\(\\) has no return type specified\\.$#"
count: 1
path: tests/AddHostCliTest.php
-
message: "#^Method LibreNMS\\\\Tests\\\\AddHostCliTest\\:\\:testCLIsnmpV2\\(\\) has no return type specified\\.$#"
count: 1
path: tests/AddHostCliTest.php
-
message: "#^Method LibreNMS\\\\Tests\\\\AddHostCliTest\\:\\:testCLIsnmpV3UserAndPW\\(\\) has no return type specified\\.$#"
count: 1
path: tests/AddHostCliTest.php
-
message: "#^Method LibreNMS\\\\Tests\\\\AddHostCliTest\\:\\:testPortAssociationMode\\(\\) has no return type specified\\.$#"
count: 1
path: tests/AddHostCliTest.php
-
message: "#^Method LibreNMS\\\\Tests\\\\AddHostCliTest\\:\\:testSnmpTransport\\(\\) has no return type specified\\.$#"
count: 1
path: tests/AddHostCliTest.php
-
message: "#^Method LibreNMS\\\\Tests\\\\AddHostCliTest\\:\\:testSnmpV3AuthProtocol\\(\\) has no return type specified\\.$#"
count: 1
path: tests/AddHostCliTest.php
-
message: "#^Method LibreNMS\\\\Tests\\\\AddHostCliTest\\:\\:testSnmpV3PrivacyProtocol\\(\\) has no return type specified\\.$#"
count: 1
path: tests/AddHostCliTest.php
-
message: "#^Property LibreNMS\\\\Tests\\\\AddHostCliTest\\:\\:\\$hostName has no type specified\\.$#"
count: 1
path: tests/AddHostCliTest.php
-
message: "#^Method LibreNMS\\\\Tests\\\\AddHostTest\\:\\:testAddping\\(\\) has no return type specified\\.$#"
count: 1
path: tests/AddHostTest.php
-
message: "#^Method LibreNMS\\\\Tests\\\\AddHostTest\\:\\:testAddsnmpV1\\(\\) has no return type specified\\.$#"
count: 1
path: tests/AddHostTest.php
-
message: "#^Method LibreNMS\\\\Tests\\\\AddHostTest\\:\\:testAddsnmpV2\\(\\) has no return type specified\\.$#"
count: 1
path: tests/AddHostTest.php
-
message: "#^Method LibreNMS\\\\Tests\\\\AddHostTest\\:\\:testAddsnmpV3\\(\\) has no return type specified\\.$#"
count: 1
path: tests/AddHostTest.php
-
message: "#^Parameter \\#4 \\$transport of function addHost expects string, int given\\.$#"
count: 1

View File

@@ -64,6 +64,43 @@ return [
'removed' => 'Device :id removed',
'updated' => 'Device :hostname (:id) updated',
],
'device:add' => [
'description' => 'Add a new device',
'arguments' => [
'device spec' => 'Hostname or IP to add',
],
'options' => [
'v1' => 'Use SNMP v1',
'v2c' => 'Use SNMP v2c',
'v3' => 'Use SNMP v3',
'display-name' => 'A string to display as the name of this device, defaults to hostname. May be a simple template using replacements: {{ $hostname }}, {{ $sysName }}, {{ $sysName_fallback }}, {{ $ip }}',
'force' => 'Just add the device, do not make any safety checks',
'group' => 'Poller group (for distributed polling)',
'ping-fallback' => 'Add the device as ping only if it does not respond to SNMP',
'port-association-mode' => 'Sets how ports are mapped. ifName is suggested for Linux/Unix',
'community' => 'SNMP v1 or v2 community',
'transport' => 'Transport to connect to the device',
'port' => 'SNMP transport port',
'security-name' => 'SNMPv3 security username',
'auth-password' => 'SNMPv3 authentication password',
'auth-protocol' => 'SNMPv3 authentication protocol',
'privacy-protocol' => 'SNMPv3 privacy protocol',
'privacy-password' => 'SNMPv3 privacy password',
'ping-only' => 'Add a ping only device',
'os' => 'Ping only: specify OS',
'hardware' => 'Ping only: specify hardware',
'sysName' => 'Ping only: specify sysName',
],
'validation-errors' => [
'port.between' => 'Port should be 1-65535',
'poller-group.in' => 'The given poller-group does not exist',
],
'messages' => [
'save_failed' => 'Failed to save device :hostname',
'try_force' => 'You my try with the --force option to skip safety checks',
'added' => 'Added device :hostname (:device_id)',
],
],
'device:ping' => [
'description' => 'Ping device and record data for response',
'arguments' => [
@@ -104,6 +141,11 @@ return [
'required' => 'Either old key or --generate-new-key is required.',
],
],
'lnms' => [
'validation-errors' => [
'optionValue' => 'Selected :option is invalid. Should be one of: :values',
],
],
'smokeping:generate' => [
'args-nonsense' => 'Use one of --probes and --targets',
'config-insufficient' => 'In order to generate a smokeping configuration, you must have set "smokeping.probes", "fping", and "fping6" set in your configuration',

View File

@@ -16,6 +16,18 @@ return [
'title' => 'Error: Could not write to file',
'message' => 'Failed to write to file (:file). Please check permissions and SELinux/AppArmor if applicable.',
],
'host_exists' => [
'hostname_exists' => 'Device :hostname already exists',
'ip_exists' => 'Cannot add :hostname, already have device :existing with this IP :ip',
'sysname_exists' => 'Already have device :hostname due to duplicate sysName: :sysname',
],
'host_unreachable' => [
'unpingable' => 'Could not ping :hostname (:ip)',
'unsnmpable' => 'Could not connect to :hostname, please check the snmp details and snmp reachability',
'unresolvable' => 'Hostname did not resolve to IP',
'no_reply_community' => 'SNMP :version: No reply with community :credentials',
'no_reply_credentials' => 'SNMP :version: No reply with credentials :credentials',
],
'ldap_missing' => [
'title' => 'PHP LDAP support missing',
'message' => 'PHP does not support LDAP, please install or enable the PHP LDAP extension',
@@ -28,4 +40,7 @@ return [
'title' => 'Error caused by PHP version mismatch',
'message' => 'The version of PHP your web server is running (:web_version) does not match the CLI version (:cli_version)',
],
'snmp_version_unsupported' => [
'message' => 'Unsupported SNMP Version ":snmpver", must be v1, v2c, or v3',
],
];

View File

@@ -1,9 +1,6 @@
<?php
use App\Models\Device;
use Illuminate\Support\Facades\Artisan;
use LibreNMS\Exceptions\HostUnreachableException;
use LibreNMS\Util\Debug;
use Symfony\Component\Process\Process;
/*
@@ -29,117 +26,6 @@ Artisan::command('device:rename
]))->setTimeout(null)->setIdleTimeout(null)->setTty(true)->run();
})->purpose(__('Rename a device, this can be used to change the hostname or IP of a device'));
Artisan::command('device:add
{device spec : Hostname or IP to add}
{--v1 : ' . __('Use SNMP v1') . '}
{--v2c : ' . __('Use SNMP v2c') . '}
{--v3 : ' . __('Use SNMP v3') . '}
{--f|force : ' . __('Just add the device, do not make any safety checks') . '}
{--g|group= : ' . __('Poller group (for distributed polling)') . '}
{--b|ping-fallback : ' . __('Add the device as ping only if it does not respond to SNMP') . '}
{--p|port-association-mode=ifIndex : ' . __('Sets how ports are mapped :modes, ifName is suggested for Linux/Unix', ['modes' => '[ifIndex, ifName, ifDescr, ifAlias]']) . '}
{--c|community= : ' . __('SNMP v1 or v2 community') . '}
{--t|transport=udp : ' . __('Transport to connect to the device') . ' [udp, udp6, tcp, tcp6]}
{--r|port=161 : ' . __('SNMP transport port') . '}
{--u|security-name=root : ' . __('SNMPv3 security username') . '}
{--A|auth-password= : ' . __('SNMPv3 authentication password') . '}
{--a|auth-protocol=md5 : ' . __('SNMPv3 authentication protocol') . ' [' . implode(', ', \LibreNMS\SNMPCapabilities::supportedAuthAlgorithms()) . ']}
{--x|privacy-protocol=aes : ' . __('SNMPv3 privacy protocol') . ' [' . implode(', ', \LibreNMS\SNMPCapabilities::supportedCryptoAlgorithms()) . ']}
{--X|privacy-password= : ' . __('SNMPv3 privacy password') . '}
{--P|ping-only : ' . __('Add a ping only device') . '}
{--o|os=ping : ' . __('Ping only: specify OS') . '}
{--w|hardware= : ' . __('Ping only: specify hardware') . '}
{--s|sysName= : ' . __('Ping only: specify sysName') . '}
', function () {
/** @var \Illuminate\Console\Command $this */
// Value Checks
if (! in_array($this->option('port-association-mode'), ['ifIndex', 'ifName', 'ifDescr', 'ifAlias'])) {
$this->error(__('Invalid port association mode'));
}
if (! in_array($this->option('transport'), ['udp', 'udp6', 'tcp', 'tcp6'])) {
$this->error(__('Invalid SNMP transport'));
}
if (! in_array($this->option('auth-protocol'), \LibreNMS\SNMPCapabilities::supportedAuthAlgorithms())) {
$this->error(__('Invalid authentication protocol'));
}
if (! in_array($this->option('privacy-protocol'), \LibreNMS\SNMPCapabilities::supportedCryptoAlgorithms())) {
$this->error(__('Invalid privacy protocol'));
}
$port = (int) $this->option('port');
if ($port < 1 || $port > 65535) {
$this->error(__('Port should be 1-65535'));
}
// build additional
$additional = [
'os' => $this->option('os'),
'hardware' => $this->option('hardware'),
'sysName' => $this->option('sysName'),
];
if ($this->option('ping-only')) {
$additional['snmp_disable'] = 1;
} elseif ($this->option('ping-fallback')) {
$additional['ping_fallback'] = 1;
}
if ($this->option('community')) {
$community_config = \LibreNMS\Config::get('snmp.community');
array_unshift($community_config, $this->option('community'));
\LibreNMS\Config::set('snmp.community', $community_config);
}
$auth = $this->option('auth-password');
$priv = $this->option('privacy-password');
$v3_config = \LibreNMS\Config::get('snmp.v3');
array_unshift($v3_config, [
'authlevel' => ($auth ? 'auth' : 'noAuth') . (($priv && $auth) ? 'Priv' : 'NoPriv'),
'authname' => $this->option('security-name'),
'authpass' => $this->option('auth-password'),
'authalgo' => $this->option('auth-protocol'),
'cryptopass' => $this->option('privacy-password'),
'cryptoalgo' => $this->option('privacy-protocol'),
]);
\LibreNMS\Config::set('snmp.v3', $v3_config);
try {
$init_modules = [];
include base_path('includes/init.php');
if (($verbosity = $this->getOutput()->getVerbosity()) >= 128) {
Debug::set();
if ($verbosity >= 256) {
Debug::setVerbose();
}
}
$device_id = addHost(
$this->argument('device spec'),
$this->option('v3') ? 'v3' : ($this->option('v2c') ? 'v2c' : ($this->option('v1') ? 'v1' : '')),
$port,
$this->option('transport'),
$this->option('group'),
$this->option('force'),
$this->option('port-association-mode'),
$additional
);
$hostname = Device::where('device_id', $device_id)->value('hostname');
$this->info("Added device $hostname ($device_id)");
return 0;
} catch (HostUnreachableException $e) {
$this->error($e->getMessage() . PHP_EOL . implode(PHP_EOL, $e->getReasons()));
return 1;
} catch (Exception $e) {
$this->error($e->getMessage());
return 3;
}
})->purpose('Add a new device');
Artisan::command('device:remove
{device spec : ' . __('Hostname, IP, or device id to remove') . '}
', function () {

View File

@@ -31,12 +31,16 @@ use Illuminate\Foundation\Testing\DatabaseTransactions;
class AddHostCliTest extends DBTestCase
{
use DatabaseTransactions;
/** @var string */
private $hostName = 'testHost';
public function testCLIsnmpV1()
public function testCLIsnmpV1(): void
{
$result = \Artisan::call('device:add ' . $this->hostName . ' -force -ccommunity --v1');
$this->assertEquals(0, $result, 'command returned non zero value');
$this->artisan('device:add', ['device spec' => $this->hostName, '--force' => true, '-c' => 'community', '--v1' => true])
->assertExitCode(0)
->execute();
$device = Device::findByHostname($this->hostName);
$this->assertNotNull($device);
@@ -45,10 +49,12 @@ class AddHostCliTest extends DBTestCase
$this->assertEquals('v1', $device->snmpver, 'Wrong snmp version');
}
public function testCLIsnmpV2()
public function testCLIsnmpV2(): void
{
$result = \Artisan::call('device:add ' . $this->hostName . ' -force -ccommunity --v2c');
$this->assertEquals(0, $result, 'command returned non zero value');
$this->artisan('device:add', ['device spec' => $this->hostName, '--force' => true, '-c' => 'community', '--v2c' => true])
->assertExitCode(0)
->execute();
$device = Device::findByHostname($this->hostName);
$this->assertNotNull($device);
@@ -57,10 +63,12 @@ class AddHostCliTest extends DBTestCase
$this->assertEquals('v2c', $device->snmpver, 'Wrong snmp version');
}
public function testCLIsnmpV3UserAndPW()
public function testCLIsnmpV3UserAndPW(): void
{
$result = \Artisan::call('device:add ' . $this->hostName . ' -force -uSecName -AAuthPW -XPrivPW --v3');
$this->assertEquals(0, $result, 'command returned non zero value');
$this->artisan('device:add', ['device spec' => $this->hostName, '--force' => true, '-u' => 'SecName', '-A' => 'AuthPW', '-X' => 'PrivPW', '--v3' => true])
->assertExitCode(0)
->execute();
$device = Device::findByHostname($this->hostName);
$this->assertNotNull($device);
@@ -72,26 +80,30 @@ class AddHostCliTest extends DBTestCase
$this->assertEquals('v3', $device->snmpver, 'Wrong snmp version');
}
public function testPortAssociationMode()
public function testPortAssociationMode(): void
{
$modes = ['ifIndex', 'ifName', 'ifDescr', 'ifAlias'];
foreach ($modes as $index => $mode) {
$host = 'hostName' . $mode;
$result = \Artisan::call('device:add ' . $host . ' -force -p ' . $mode . ' --v1');
$this->assertEquals(0, $result, 'command returned non zero value');
$this->artisan('device:add', ['device spec' => $host, '--force' => true, '-p' => $mode, '--v1' => true])
->assertExitCode(0)
->execute();
$device = Device::findByHostname($host);
$this->assertNotNull($device);
$this->assertEquals($index + 1, $device->port_association_mode, 'Wrong port association mode ' . $mode);
}
}
public function testSnmpTransport()
public function testSnmpTransport(): void
{
$modes = ['udp', 'udp6', 'tcp', 'tcp6'];
foreach ($modes as $mode) {
$host = 'hostName' . $mode;
$result = \Artisan::call('device:add ' . $host . ' -force -t ' . $mode . ' --v1');
$this->assertEquals(0, $result, 'command returned non zero value');
$this->artisan('device:add', ['device spec' => $host, '--force' => true, '-t' => $mode, '--v1' => true])
->assertExitCode(0)
->execute();
$device = Device::findByHostname($host);
$this->assertNotNull($device);
@@ -99,13 +111,15 @@ class AddHostCliTest extends DBTestCase
}
}
public function testSnmpV3AuthProtocol()
public function testSnmpV3AuthProtocol(): void
{
$modes = ['MD5', 'SHA', 'SHA-224', 'SHA-256', 'SHA-384', 'SHA-512'];
$modes = \LibreNMS\SNMPCapabilities::supportedAuthAlgorithms();
foreach ($modes as $mode) {
$host = 'hostName' . $mode;
$result = \Artisan::call('device:add ' . $host . ' -force -a ' . $mode . ' --v3');
$this->assertEquals(0, $result, 'command returned non zero value');
$this->artisan('device:add', ['device spec' => $host, '--force' => true, '-a' => $mode, '--v3' => true])
->assertExitCode(0)
->execute();
$device = Device::findByHostname($host);
$this->assertNotNull($device);
@@ -113,13 +127,15 @@ class AddHostCliTest extends DBTestCase
}
}
public function testSnmpV3PrivacyProtocol()
public function testSnmpV3PrivacyProtocol(): void
{
$modes = ['DES', 'AES', 'AES-192', 'AES-256', 'AES-256-C'];
$modes = \LibreNMS\SNMPCapabilities::supportedCryptoAlgorithms();
foreach ($modes as $mode) {
$host = 'hostName' . $mode;
$result = \Artisan::call('device:add ' . $host . ' -force -x ' . $mode . ' --v3');
$this->assertEquals(0, $result, 'command returned non zero value');
$this->artisan('device:add', ['device spec' => $host, '--force' => true, '-x' => $mode, '--v3' => true])
->assertExitCode(0)
->execute();
$device = Device::findByHostname($host);
$this->assertNotNull($device);
@@ -127,10 +143,11 @@ class AddHostCliTest extends DBTestCase
}
}
public function testCLIping()
public function testCLIping(): void
{
$result = \Artisan::call('device:add ' . $this->hostName . ' -force -P -onameOfOS -whardware -sSystem --v1');
$this->assertEquals(0, $result, 'command returned non zero value');
$this->artisan('device:add', ['device spec' => $this->hostName, '--force' => true, '-P' => true, '-o' => 'nameOfOS', '-w' => 'hardware', '-s' => 'System', '--v1' => true])
->assertExitCode(0)
->execute();
$device = Device::findByHostname($this->hostName);
$this->assertNotNull($device);
@@ -140,4 +157,14 @@ class AddHostCliTest extends DBTestCase
$this->assertEquals('nameOfOS', $device->os, 'Wrong os name');
$this->assertEquals('system', $device->sysName, 'Wrong system name');
}
public function testExistingDevice(): void
{
$this->artisan('device:add', ['device spec' => 'existing', '--force' => true])
->assertExitCode(0)
->execute();
$this->artisan('device:add', ['device spec' => 'existing'])
->assertExitCode(2)
->execute();
}
}

View File

@@ -34,7 +34,7 @@ class AddHostTest extends DBTestCase
use DatabaseTransactions;
private $host = 'testHost';
public function testAddsnmpV1()
public function testAddsnmpV1(): void
{
addHost($this->host, 'v1', 111, 'tcp', 0, true, 'ifIndex');
$device = Device::findByHostname($this->host);
@@ -47,7 +47,7 @@ class AddHostTest extends DBTestCase
$this->assertEquals('tcp', $device->transport, 'Wrong snmp transport (udp/tcp)');
}
public function testAddsnmpV2()
public function testAddsnmpV2(): void
{
addHost($this->host, 'v2c', 111, 'tcp', 0, true, 'ifName');
$device = Device::findByHostname($this->host);
@@ -59,7 +59,7 @@ class AddHostTest extends DBTestCase
$this->assertEquals('v2c', $device->snmpver, 'Wrong snmp version');
}
public function testAddsnmpV3()
public function testAddsnmpV3(): void
{
addHost($this->host, 'v3', 111, 'tcp', 0, true, 'ifIndex');
$device = Device::findByHostname($this->host);
@@ -73,7 +73,7 @@ class AddHostTest extends DBTestCase
$this->assertEquals(Config::get('snmp.v3')[0]['authpass'], $device->authpass, 'Wrong snmp v3 password');
}
public function testAddping()
public function testAddping(): void
{
$additional = [
'snmp_disable' => 1,

View File

@@ -27,6 +27,7 @@ namespace LibreNMS\Tests;
use Illuminate\Support\Str;
use LibreNMS\Config;
use LibreNMS\Enum\PortAssociationMode;
use LibreNMS\Util\Clean;
use LibreNMS\Util\Validate;
@@ -232,10 +233,10 @@ class CommonFunctionsTest extends TestCase
4 => 'ifAlias',
];
$this->assertEquals($modes, get_port_assoc_modes());
$this->assertEquals('ifIndex', get_port_assoc_mode_name(1));
$this->assertEquals(1, get_port_assoc_mode_id('ifIndex'));
$this->assertFalse(get_port_assoc_mode_name(666));
$this->assertFalse(get_port_assoc_mode_id('lucifer'));
$this->assertEquals($modes, PortAssociationMode::getModes());
$this->assertEquals('ifIndex', PortAssociationMode::getName(1));
$this->assertEquals(1, PortAssociationMode::getId('ifIndex'));
$this->assertNull(PortAssociationMode::getName(666));
$this->assertNull(PortAssociationMode::getId('lucifer'));
}
}