Transceiver Support (#16165)

* Add transceivers module

Move os specific code to OS

Fix errors and updated connector names

Add RouterOS, a lot less data there.

Add Comware

Add Exa, required a transformer function (mw to dBm)

Add Junos, revision was too short

Just starting on ui

Graphs, and more ui
some polling fixes

collapse header for small screens

refactor a bit

Missed graphs

Transceivers icon inline

Use @once on popup javascript

update db_schema.yaml

Don't show transceivers in basic view
basic view could use a review

Apply fixes from StyleCI

API functions

Comware don't fail if port is missing

Apply fixes from StyleCI

Add alert rules to collection

Device Overview

Attempt to fix bad alert rule, probably needs more

Fix up Comware and remove old sensors

Mark transceiver metrics without thresholds as Unknown

Routeros cleanup

Exa cleanup

Handle missing port

Graph allow filter by channel

More translations

Add transceiver graphs to port graphs

Add Cisco support, use entPhyscial module data if available

Fix OcNos divisors

Labels on transceiver page

Show encoding if available

Hacky OcNos port mapping

Fix up Junos optics and remove old sensors

FS switch support
Metric casts to prevent thrashing
Extra transform_function support

Add link to transceivers page from overview

Change default sort to group by type, then channel
move some code out of overview blade template
Fix bad type ocnos

Apply fixes from StyleCI

Add scales to graphs

Add some test data

Default sort order for metrcis in SQL applied by default

Transceiver metrics threshold manual settings via WebUI and API

Fixes to channels verbiage

Fix severity calculations

Add cable field for SM/MM/Copper

Apply fixes from StyleCI

update test data

Show DDM

Update DB schema file

Extend serial field to 32, even though devices shouldn't be able to have one longer than 16

Missing import

Add status field to database, that way we can support snmp implementations that only return an enum

Add missing files

Fix db_schema

Fix style

Fixes

Style fix

Work around phpstan issue

Update transceivers.blade.php

Missed getStatus() call

Prevent extra dots when channels are not changed

Update module to match upstream

Save ocnos metrics as sensors

Move to regular sensors

add entity physical index

Update UI to sensors WIP

Apply fixes from StyleCI

Forgot one change

Update ui to use sensors

Remove transceiver metrics

Remove metric os discover code
fs-switch pending

Remove transceiver metrics for fs-centec

Exa link up

Revert all test data

Fix up transceiver module interface

Remove unused Convert class

comware cache and transceiver type

Fix some transceiver metrics filtering and formatting issues

Consolidate display formatting

Coalesce commare hh3cTransceiverTable walks to prevent double walk

Use group to identify transceiver sensors

Fixup routeros

Fix up cisco

update db_schema

Small addition to docs

Improve overview layout and add graph popup

Update Junos

update css files

ddm should be nullable

Increase the field length for type and model

Cisco Improve detection when there is an intermediary container

Add transceiver test data

Apply fixes from StyleCI

Fix incorrect test data

Improve display formatting

Fix test data

Apply fixes from StyleCI

Fix up more data

Fix up more data

Fix incorrect return type in routeros

Update ocnos data

* Remove some remaining references to transceiver_metrics table
This commit is contained in:
Tony Murray
2024-09-29 11:05:44 -05:00
committed by GitHub
parent 8d2bf6ceb0
commit e08571c38c
128 changed files with 80016 additions and 2810 deletions

View File

@@ -0,0 +1,39 @@
<?php
/*
* TransceiverDiscovery.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 2024 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Interfaces\Discovery;
use Illuminate\Support\Collection;
interface TransceiverDiscovery
{
/**
* Discover transceivers.
* Distance is in meters.
*
* @return Collection<\App\Models\Transceiver>
*/
public function discoverTransceivers(): Collection;
}

View File

@@ -84,6 +84,8 @@ interface Module
* 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 null if testing is not supported or required.
*
* @param string $type Type is either discovery or poller
*/
public function dump(Device $device, string $type): ?array;
}

View File

@@ -0,0 +1,96 @@
<?php
/*
* Transceivers.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 2024 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Modules;
use App\Models\Device;
use App\Models\Transceiver;
use App\Observers\ModuleModelObserver;
use LibreNMS\DB\SyncsModels;
use LibreNMS\Interfaces\Data\DataStorageInterface;
use LibreNMS\Interfaces\Discovery\TransceiverDiscovery;
use LibreNMS\Interfaces\Module;
use LibreNMS\OS;
use LibreNMS\Polling\ModuleStatus;
class Transceivers implements Module
{
use SyncsModels;
public function dependencies(): array
{
return ['ports'];
}
public function shouldDiscover(OS $os, ModuleStatus $status): bool
{
return $status->isEnabledAndDeviceUp($os->getDevice()) && $os instanceof TransceiverDiscovery;
}
public function shouldPoll(OS $os, ModuleStatus $status): bool
{
return false;
}
public function discover(OS $os): void
{
if ($os instanceof TransceiverDiscovery) {
$discoveredTransceivers = $os->discoverTransceivers();
// save transceivers
ModuleModelObserver::observe(Transceiver::class);
$this->syncModels($os->getDevice(), 'transceivers', $discoveredTransceivers);
}
}
public function poll(OS $os, DataStorageInterface $datastore): void
{
// no polling
}
public function dataExists(Device $device): bool
{
return $device->transceivers()->exists();
}
public function cleanup(Device $device): int
{
return $device->transceivers()->delete();
}
public function dump(Device $device, string $type): ?array
{
if ($type == 'poller') {
return null;
}
return [
'transceivers' => $device->transceivers()->orderBy('index')
->leftJoin('ports', 'transceivers.port_id', 'ports.port_id')
->select(['transceivers.*', 'ifIndex'])
->get()->map->makeHidden(['id', 'created_at', 'updated_at', 'device_id', 'port_id']),
];
}
}

View File

@@ -27,13 +27,15 @@ namespace LibreNMS\OS;
use App\Models\Device;
use App\Models\Mempool;
use App\Models\Transceiver;
use Illuminate\Support\Collection;
use LibreNMS\Device\Processor;
use LibreNMS\Interfaces\Discovery\MempoolsDiscovery;
use LibreNMS\Interfaces\Discovery\ProcessorDiscovery;
use LibreNMS\Interfaces\Discovery\TransceiverDiscovery;
use LibreNMS\OS;
class Comware extends OS implements MempoolsDiscovery, ProcessorDiscovery
class Comware extends OS implements MempoolsDiscovery, ProcessorDiscovery, TransceiverDiscovery
{
public function discoverOS(Device $device): void
{
@@ -107,4 +109,27 @@ class Comware extends OS implements MempoolsDiscovery, ProcessorDiscovery
return $mempools;
}
public function discoverTransceivers(): Collection
{
$ifIndexToPortId = $this->getDevice()->ports()->pluck('port_id', 'ifIndex');
return \SnmpQuery::cache()->enumStrings()->walk('HH3C-TRANSCEIVER-INFO-MIB::hh3cTransceiverInfoTable')->mapTable(function ($data, $ifIndex) use ($ifIndexToPortId) {
return new Transceiver([
'port_id' => $ifIndexToPortId->get($ifIndex, 0),
'index' => $ifIndex,
'type' => $data['HH3C-TRANSCEIVER-INFO-MIB::hh3cTransceiverType'] ?? null,
'vendor' => $data['HH3C-TRANSCEIVER-INFO-MIB::hh3cTransceiverVendorName'] ?? null,
'oui' => $data['HH3C-TRANSCEIVER-INFO-MIB::hh3cTransceiverVendorOUI'] ?? null,
'revision' => $data['HH3C-TRANSCEIVER-INFO-MIB::hh3cTransceiverRevisionNumber'] ?? null,
'model' => $data['HH3C-TRANSCEIVER-INFO-MIB::hh3cTransceiverPartNumber'] ?? null,
'serial' => $data['HH3C-TRANSCEIVER-INFO-MIB::hh3cTransceiverSerialNumber'] ?? null,
'ddm' => isset($data['HH3C-TRANSCEIVER-INFO-MIB::hh3cTransceiverDiagnostic']) && $data['HH3C-TRANSCEIVER-INFO-MIB::hh3cTransceiverDiagnostic'] == 'true',
'cable' => $data['HH3C-TRANSCEIVER-INFO-MIB::hh3cTransceiverHardwareType'] ?? null,
'distance' => $data['HH3C-TRANSCEIVER-INFO-MIB::hh3cTransceiverTransferDistance'] ?? null,
'wavelength' => isset($data['HH3C-TRANSCEIVER-INFO-MIB::hh3cTransceiverWaveLength']) && $data['HH3C-TRANSCEIVER-INFO-MIB::hh3cTransceiverWaveLength'] != 2147483647 ? $data['HH3C-TRANSCEIVER-INFO-MIB::hh3cTransceiverWaveLength'] : null,
'entity_physical_index' => $ifIndex,
]);
});
}
}

View File

@@ -26,10 +26,13 @@
namespace LibreNMS\OS;
use App\Models\Device;
use App\Models\Transceiver;
use Illuminate\Support\Collection;
use LibreNMS\Interfaces\Discovery\OSDiscovery;
use LibreNMS\Interfaces\Discovery\TransceiverDiscovery;
use LibreNMS\OS;
class Exa extends OS implements OSDiscovery
class Exa extends OS implements OSDiscovery, TransceiverDiscovery
{
public function discoverOS(Device $device): void
{
@@ -47,4 +50,34 @@ class Exa extends OS implements OSDiscovery
return ($card_count[$card] > 1 ? $card_count[$card] . 'x ' : '') . $card;
}, array_keys($card_count)));
}
public function discoverTransceivers(): Collection
{
$ifIndexToPortId = $this->getDevice()->ports()->pluck('port_id', 'ifIndex');
return \SnmpQuery::cache()->walk('E7-Calix-MIB::e7OltPonPortTable')->mapTable(function ($data, $shelf, $card, $port) use ($ifIndexToPortId) {
if ($data['E7-Calix-MIB::e7OltPonPortStatus'] == 0) {
return null;
}
$ifIndex = self::getIfIndex($shelf, $card, $port, 'gpon');
return new Transceiver([
'port_id' => $ifIndexToPortId->get($ifIndex),
'index' => "$shelf.$card.$port",
'entity_physical_index' => $ifIndex,
]);
})->filter();
}
public static function getIfIndex(int $chassis, int $slot, int $id, string $type): int
{
// doesn't work for stacked chassis, I don't have enough info to figure out how it works
$offset = match ($type) {
'gpon' => 20000,
default => 0,
};
return $offset + (10000 * $chassis) + ($slot * 100) + $id;
}
}

55
LibreNMS/OS/FsCentec.php Normal file
View File

@@ -0,0 +1,55 @@
<?php
namespace LibreNMS\OS;
use App\Models\Transceiver;
use Illuminate\Support\Collection;
use LibreNMS\Interfaces\Discovery\TransceiverDiscovery;
use LibreNMS\OS;
use SnmpQuery;
class FsCentec extends OS implements TransceiverDiscovery
{
public function discoverTransceivers(): Collection
{
$ifIndexToPortId = $this->getDevice()->ports()->pluck('port_id', 'ifIndex');
return SnmpQuery::cache()->walk('FS-SWITCH-V2-MIB::transbasicinformationTable')->mapTable(function ($data, $ifIndex) use ($ifIndexToPortId) {
if ($data['FS-SWITCH-V2-MIB::transceiveStatus'] == 'inactive') {
return null;
}
$distance = null;
$cable = null;
if (isset($data['FS-SWITCH-V2-MIB::link9SinglemodeLengthKm']) && $data['FS-SWITCH-V2-MIB::link9SinglemodeLengthKm'] != 0) {
$distance = $data['FS-SWITCH-V2-MIB::link9SinglemodeLengthKm'] * 1000;
$cable = 'SM';
} elseif (isset($data['FS-SWITCH-V2-MIB::link9SinglemodeLengthM']) && $data['FS-SWITCH-V2-MIB::link9SinglemodeLengthM'] != 0) {
$distance = $data['FS-SWITCH-V2-MIB::link9SinglemodeLengthM'];
$cable = 'SM';
} elseif (isset($data['FS-SWITCH-V2-MIB::link50MultimodeLength']) && $data['FS-SWITCH-V2-MIB::link50MultimodeLength'] != 0) {
$distance = $data['FS-SWITCH-V2-MIB::link50MultimodeLength'];
$cable = 'MM';
} elseif (isset($data['FS-SWITCH-V2-MIB::link62MultimodeLength']) && $data['FS-SWITCH-V2-MIB::link62MultimodeLength'] != 0) {
$distance = $data['FS-SWITCH-V2-MIB::link62MultimodeLength'];
$cable = 'MM';
} elseif (isset($data['FS-SWITCH-V2-MIB::linkCopperLength']) && $data['FS-SWITCH-V2-MIB::linkCopperLength'] != 0) {
$distance = $data['FS-SWITCH-V2-MIB::linkCopperLength'];
$cable = 'Copper';
}
return new Transceiver([
'port_id' => $ifIndexToPortId->get($ifIndex),
'index' => $ifIndex,
'vendor' => $data['FS-SWITCH-V2-MIB::transceiveVender'] ?? null,
'type' => $data['FS-SWITCH-V2-MIB::transceiveType'] ?? null,
'model' => $data['FS-SWITCH-V2-MIB::transceivePartNumber'] ?? null,
'serial' => $data['FS-SWITCH-V2-MIB::transceiveSerialNumber'] ?? null,
'cable' => $cable,
'distance' => $distance,
'wavelength' => $data['FS-SWITCH-V2-MIB::transceiveWaveLength'] ?? null,
'entity_physical_index' => $ifIndex,
]);
})->filter();
}
}

View File

@@ -26,10 +26,9 @@
namespace LibreNMS\OS;
use LibreNMS\Device\Processor;
use LibreNMS\Interfaces\Discovery\ProcessorDiscovery;
use LibreNMS\OS;
class FsSwitch extends OS implements ProcessorDiscovery
class FsSwitch extends OS
{
public static function normalizeTransceiverValues($value): float
{

View File

@@ -27,19 +27,22 @@ namespace LibreNMS\OS;
use App\Models\Device;
use App\Models\EntPhysical;
use App\Models\Port;
use App\Models\Sla;
use App\Models\Transceiver;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use LibreNMS\Interfaces\Data\DataStorageInterface;
use LibreNMS\Interfaces\Discovery\SlaDiscovery;
use LibreNMS\Interfaces\Discovery\TransceiverDiscovery;
use LibreNMS\Interfaces\Polling\OSPolling;
use LibreNMS\Interfaces\Polling\SlaPolling;
use LibreNMS\OS\Traits\EntityMib;
use LibreNMS\RRD\RrdDefinition;
use SnmpQuery;
class Junos extends \LibreNMS\OS implements SlaDiscovery, OSPolling, SlaPolling
class Junos extends \LibreNMS\OS implements SlaDiscovery, OSPolling, SlaPolling, TransceiverDiscovery
{
use EntityMib {
EntityMib::discoverEntityPhysical as discoverBaseEntityPhysical;
@@ -291,4 +294,58 @@ class Junos extends \LibreNMS\OS implements SlaDiscovery, OSPolling, SlaPolling
default => null,
};
}
public function discoverTransceivers(): Collection
{
$ifIndexToPortId = Port::query()->where('device_id', $this->getDeviceId())->select(['port_id', 'ifIndex', 'ifName'])->get()->keyBy('ifIndex');
$entPhysical = SnmpQuery::walk('ENTITY-MIB::entityPhysical')->table(1);
$jnxDomCurrentTable = SnmpQuery::cache()->walk('JUNIPER-DOM-MIB::jnxDomCurrentTable')->mapTable(function ($data, $ifIndex) use ($ifIndexToPortId, $entPhysical) {
$ent = $this->findTransceiverEntityByPortName($entPhysical, $ifIndexToPortId->get($ifIndex)?->ifName);
if (empty($ent)) {
return null; // no module
}
return new Transceiver([
'port_id' => $ifIndexToPortId->get($ifIndex)->port_id,
'index' => $ifIndex,
'type' => $ent['ENTITY-MIB::entPhysicalName'] ?? null,
'vendor' => $ent['ENTITY-MIB::entPhysicalMfgName'] ?? null,
'model' => $ent['ENTITY-MIB::entPhysicalModelName'] ?? null,
'revision' => $ent['ENTITY-MIB::entPhysicalHardwareRev'] ?? null,
'serial' => $ent['ENTITY-MIB::entPhysicalSerialNum'] ?? null,
'channels' => $data['JUNIPER-DOM-MIB::jnxDomCurrentModuleLaneCount'] ?? 0,
'entity_physical_index' => $ifIndex,
]);
})->filter();
if ($jnxDomCurrentTable->isNotEmpty()) {
return $jnxDomCurrentTable;
}
// could use improvement by mapping JUNIPER-IFOPTICS-MIB::jnxOpticsConfigTable for a tiny bit more info
return SnmpQuery::cache()->walk('JUNIPER-IFOPTICS-MIB::jnxOpticsPMCurrentTable')
->mapTable(function ($data, $ifIndex) use ($ifIndexToPortId) {
return new Transceiver([
'port_id' => $ifIndexToPortId->get($ifIndex)->port_id,
'index' => $ifIndex,
'entity_physical_index' => $ifIndex,
]);
});
}
private function findTransceiverEntityByPortName(array $entPhysical, string $ifName): array
{
if (preg_match('#-(\d+/\d+/\d+)#', $ifName, $matches)) {
$expected_tail = ' @ ' . $matches[1];
foreach ($entPhysical as $entity) {
if (isset($entity['ENTITY-MIB::entPhysicalDescr']) && str_ends_with($entity['ENTITY-MIB::entPhysicalDescr'], $expected_tail)) {
return $entity;
}
}
}
return [];
}
}

View File

@@ -3,14 +3,17 @@
namespace LibreNMS\OS;
use App\Models\EntPhysical;
use App\Models\Transceiver;
use Illuminate\Support\Collection;
use LibreNMS\Interfaces\Discovery\EntityPhysicalDiscovery;
use LibreNMS\Interfaces\Discovery\TransceiverDiscovery;
use LibreNMS\OS;
use SnmpQuery;
class Ocnos extends OS implements EntityPhysicalDiscovery
class Ocnos extends OS implements EntityPhysicalDiscovery, TransceiverDiscovery
{
private bool $sfpSeen = false;
private ?Collection $ifNamePortIdMap = null;
public function discoverEntityPhysical(): Collection
{
@@ -75,6 +78,12 @@ class Ocnos extends OS implements EntityPhysicalDiscovery
}
$transceivers = SnmpQuery::enumStrings()->walk('IPI-CMM-CHASSIS-MIB::cmmTransEEPROMTable')->table(2);
// load port name to port_id map
if (! empty($transceivers)) {
$ifNameToIndex = array_flip(SnmpQuery::cache()->walk('IF-MIB::ifName')->pluck());
}
foreach ($transceivers as $cmmStackUnitIndex => $chassisTransceivers) {
foreach ($chassisTransceivers as $cmmTransIndex => $transceiver) {
$inventory->push(new EntPhysical([
@@ -89,7 +98,7 @@ class Ocnos extends OS implements EntityPhysicalDiscovery
'entPhysicalParentRelPos' => $cmmTransIndex,
'entPhysicalHardwareRev' => $transceiver['IPI-CMM-CHASSIS-MIB::cmmTransVendorRevision'] ?? null,
'entPhysicalIsFRU' => 'true',
'ifIndex' => $this->guessPortId($cmmTransIndex, $transceiver['IPI-CMM-CHASSIS-MIB::cmmTransType'] ?? 'missing'),
'ifIndex' => $ifNameToIndex[$this->guessIfName($cmmTransIndex, $transceiver['IPI-CMM-CHASSIS-MIB::cmmTransType'] ?? 'missing')] ?? null,
]));
}
}
@@ -152,7 +161,7 @@ class Ocnos extends OS implements EntityPhysicalDiscovery
return $description;
}
private function guessPortId($cmmTransIndex, $cmmTransType): int
public function guessIfName($cmmTransIndex, $cmmTransType): ?string
{
// IP Infusion has no reliable way of mapping a transceiver to a port it varies by hardware
@@ -167,7 +176,7 @@ class Ocnos extends OS implements EntityPhysicalDiscovery
$this->sfpSeen = true;
}
$portName = match ($this->getDevice()->hardware) {
return match ($this->getDevice()->hardware) {
'Ufi Space S9600-32X-R' => $prefix . ($this->sfpSeen ? ($cmmTransType == 'qsfp' ? $cmmTransIndex - 5 : $cmmTransIndex - 2) : $cmmTransIndex - 1),
'Ufi Space S9510-28DC-B' => $prefix . ($cmmTransIndex - 1),
'Ufi Space S9500-30XS-P' => $prefix . ($cmmTransType == 'qsfp' ? $cmmTransIndex - 29 : $cmmTransIndex - 1),
@@ -176,14 +185,79 @@ class Ocnos extends OS implements EntityPhysicalDiscovery
'Edgecore 7712-32X-O-AC-F' => $prefix . $cmmTransIndex . '/1',
default => null, // no port map, so we can't guess
};
}
if ($portName === null) {
return 0; // give up
}
public function discoverTransceivers(): Collection
{
return SnmpQuery::enumStrings()->walk('IPI-CMM-CHASSIS-MIB::cmmTransEEPROMTable')->mapTable(function ($data, $cmmStackUnitIndex, $cmmTransIndex) {
$distance = 0;
if (! empty($data['IPI-CMM-CHASSIS-MIB::cmmTransLengthMtrs']) && $data['IPI-CMM-CHASSIS-MIB::cmmTransLengthMtrs'] !== '-100002') {
$distance = (int) $data['IPI-CMM-CHASSIS-MIB::cmmTransLengthMtrs'];
} elseif (! empty($data['IPI-CMM-CHASSIS-MIB::cmmTransLengthKmtrs']) && $data['IPI-CMM-CHASSIS-MIB::cmmTransLengthKmtrs'] !== '-100002') {
$distance = $data['IPI-CMM-CHASSIS-MIB::cmmTransLengthKmtrs'] * 1000;
} elseif (! empty($data['IPI-CMM-CHASSIS-MIB::cmmTransLengthOM4']) && $data['IPI-CMM-CHASSIS-MIB::cmmTransLengthOM4'] !== '-100002') {
$distance = (int) $data['IPI-CMM-CHASSIS-MIB::cmmTransLengthOM4'];
} elseif (! empty($data['IPI-CMM-CHASSIS-MIB::cmmTransLengthOM3']) && $data['IPI-CMM-CHASSIS-MIB::cmmTransLengthOM3'] !== '-100002') {
$distance = (int) $data['IPI-CMM-CHASSIS-MIB::cmmTransLengthOM3'];
} elseif (! empty($data['IPI-CMM-CHASSIS-MIB::cmmTransLengthOM2']) && $data['IPI-CMM-CHASSIS-MIB::cmmTransLengthOM2'] !== '-100002') {
$distance = (int) $data['IPI-CMM-CHASSIS-MIB::cmmTransLengthOM2'];
} elseif (! empty($data['IPI-CMM-CHASSIS-MIB::cmmTransLengthOM1']) && $data['IPI-CMM-CHASSIS-MIB::cmmTransLengthOM1'] !== '-100002') {
$distance = (int) $data['IPI-CMM-CHASSIS-MIB::cmmTransLengthOM1'];
}
// load port name to port_id map
$ifNameToIndex = array_flip(SnmpQuery::cache()->walk('IF-MIB::ifName')->pluck());
$connector = match ($data['IPI-CMM-CHASSIS-MIB::cmmTransconnectortype'] ?? null) {
'bayonet-or-threaded-neill-concelman' => 'ST',
'copper-pigtail' => 'DAC',
'fiber-jack' => 'FJ',
'fibrechannel-style1-copperconnector', 'fibrechannel-style2-copperconnector', 'fibrechannel-coaxheaders' => 'FC',
'hssdcii' => 'HSSDC',
'lucent-connector' => 'LC',
'mechanical-transfer-registeredjack' => 'MTRJ',
'multifiber-paralleloptic-1x12' => 'MPO-12',
'multifiber-paralleloptic-1x16' => 'MPO-16',
'multiple-optical' => 'MPO',
'mxc2-x16' => 'MXC2-X16',
'no-separable-connector' => 'None',
'optical-pigtail' => 'AOC',
'rj45' => 'RJ45',
'sg' => 'SG',
'subscriber-connector' => 'SC',
default => 'unknown',
};
return $ifNameToIndex[$portName] ?? 0;
$date = $data['IPI-CMM-CHASSIS-MIB::cmmTransDateCode'] ?? '0000-00-00';
if (preg_match('/^(\d{2,4})(\d{2})(\d{2})$/', $date, $date_matches)) {
$year = $date_matches[1];
if (strlen($year) == 2) {
$year = '20' . $year;
}
$date = $year . '-' . $date_matches[2] . '-' . $date_matches[3];
}
$cmmTransType = $data['IPI-CMM-CHASSIS-MIB::cmmTransType'] ?? 'missing';
if ($this->ifNamePortIdMap === null) {
$this->ifNamePortIdMap = $this->getDevice()->ports()->toBase()->pluck('port_id', 'ifName');
}
return new Transceiver([
'port_id' => $this->ifNamePortIdMap[$this->guessIfName($cmmTransIndex, $cmmTransType)] ?? 0,
'index' => "$cmmStackUnitIndex.$cmmTransIndex",
'type' => $cmmTransType,
'vendor' => $data['IPI-CMM-CHASSIS-MIB::cmmTransVendorName'] ?? 'missing',
'oui' => $data['IPI-CMM-CHASSIS-MIB::cmmTransVendorOUI'] ?? 'missing',
'model' => $data['IPI-CMM-CHASSIS-MIB::cmmTransVendorPartNumber'] ?? 'missing',
'revision' => $data['IPI-CMM-CHASSIS-MIB::cmmTransVendorRevision'] ?? 'missing',
'serial' => $data['IPI-CMM-CHASSIS-MIB::cmmTransVendorSerialNumber'] ?? 'missing',
'date' => $date,
'ddm' => isset($data['IPI-CMM-CHASSIS-MIB::cmmTransDDMSupport']) && $data['IPI-CMM-CHASSIS-MIB::cmmTransDDMSupport'] == 'yes',
'encoding' => $data['IPI-CMM-CHASSIS-MIB::cmmTransEncoding'] ?? 'missing',
'distance' => $distance,
'wavelength' => isset($data['IPI-CMM-CHASSIS-MIB::cmmTransWavelength']) && $data['IPI-CMM-CHASSIS-MIB::cmmTransWavelength'] !== '-100002' ? $data['IPI-CMM-CHASSIS-MIB::cmmTransWavelength'] : null,
'connector' => $connector,
'channels' => $data['IPI-CMM-CHASSIS-MIB::cmmTransNoOfChannels'] ?? 0,
'entity_physical_index' => $cmmStackUnitIndex * 10000 + $cmmTransIndex,
]);
});
}
}

View File

@@ -25,6 +25,8 @@
namespace LibreNMS\OS;
use App\Models\Transceiver;
use Illuminate\Support\Collection;
use LibreNMS\Device\WirelessSensor;
use LibreNMS\Interfaces\Data\DataStorageInterface;
use LibreNMS\Interfaces\Discovery\Sensors\WirelessCcqDiscovery;
@@ -38,12 +40,15 @@ use LibreNMS\Interfaces\Discovery\Sensors\WirelessRsrpDiscovery;
use LibreNMS\Interfaces\Discovery\Sensors\WirelessRsrqDiscovery;
use LibreNMS\Interfaces\Discovery\Sensors\WirelessRssiDiscovery;
use LibreNMS\Interfaces\Discovery\Sensors\WirelessSinrDiscovery;
use LibreNMS\Interfaces\Discovery\TransceiverDiscovery;
use LibreNMS\Interfaces\Polling\OSPolling;
use LibreNMS\OS;
use LibreNMS\RRD\RrdDefinition;
use LibreNMS\Util\Number;
class Routeros extends OS implements
OSPolling,
TransceiverDiscovery,
WirelessCcqDiscovery,
WirelessClientsDiscovery,
WirelessFrequencyDiscovery,
@@ -497,4 +502,22 @@ class Routeros extends OS implements
$this->enableGraph('routeros_pppoe_sessions');
}
}
public function discoverTransceivers(): Collection
{
$ifIndexToPortId = $this->getDevice()->ports()->pluck('port_id', 'ifIndex');
return \SnmpQuery::walk('MIKROTIK-MIB::mtxrOpticalTable')->mapTable(function ($data, $ifIndex) use ($ifIndexToPortId) {
$wavelength = isset($data['MIKROTIK-MIB::mtxrOpticalWavelength']) && $data['MIKROTIK-MIB::mtxrOpticalWavelength'] != '.00' ? Number::cast($data['MIKROTIK-MIB::mtxrOpticalWavelength']) : null;
return new Transceiver([
'port_id' => $ifIndexToPortId->get($ifIndex, 0),
'index' => $ifIndex,
'vendor' => $data['MIKROTIK-MIB::mtxrOpticalVendorName'] ?? null,
'serial' => $data['MIKROTIK-MIB::mtxrOpticalVendorSerial'] ?? null,
'wavelength' => $wavelength == 65535 ? null : $wavelength, // NA value = 65535.00
'entity_physical_index' => $ifIndex,
]);
});
}
}

View File

@@ -31,6 +31,7 @@ use App\Models\EntPhysical;
use App\Models\Mempool;
use App\Models\PortsNac;
use App\Models\Sla;
use App\Models\Transceiver;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
@@ -40,6 +41,7 @@ use LibreNMS\Interfaces\Discovery\OSDiscovery;
use LibreNMS\Interfaces\Discovery\ProcessorDiscovery;
use LibreNMS\Interfaces\Discovery\SlaDiscovery;
use LibreNMS\Interfaces\Discovery\StpInstanceDiscovery;
use LibreNMS\Interfaces\Discovery\TransceiverDiscovery;
use LibreNMS\Interfaces\Polling\NacPolling;
use LibreNMS\Interfaces\Polling\SlaPolling;
use LibreNMS\OS;
@@ -54,7 +56,8 @@ class Cisco extends OS implements
ProcessorDiscovery,
MempoolsDiscovery,
NacPolling,
SlaPolling
SlaPolling,
TransceiverDiscovery
{
use YamlOSDiscovery {
YamlOSDiscovery::discoverOS as discoverYamlOS;
@@ -622,4 +625,52 @@ class Cisco extends OS implements
return null;
}
public function discoverTransceivers(): Collection
{
// use data collected by entPhysical module if available
$dbSfpCages = $this->getDevice()->entityPhysical()->where('entPhysicalVendorType', 'cevContainerSFP')->pluck('ifIndex', 'entPhysicalIndex');
if ($dbSfpCages->isNotEmpty()) {
$data = $this->getDevice()->entityPhysical()->whereIn('entPhysicalContainedIn', $dbSfpCages->keys())->get()->map(function ($ent) use ($dbSfpCages) {
if (empty($ent->ifIndex) && $dbSfpCages->has($ent->entPhysicalContainedIn)) {
$ent->ifIndex = $dbSfpCages->get($ent->entPhysicalContainedIn);
}
return $ent;
})->keyBy('entPhysicalIndex');
} else {
// fetch data via snmp
$snmpData = \SnmpQuery::cache()->hideMib()->mibs(['CISCO-ENTITY-VENDORTYPE-OID-MIB'])->walk('ENTITY-MIB::entPhysicalTable')->table(1);
if (empty($snmpData)) {
return new Collection;
}
$snmpData = collect(\SnmpQuery::hideMib()->mibs(['IF-MIB'])->walk('ENTITY-MIB::entAliasMappingIdentifier')->table(1, $snmpData));
$sfpCages = $snmpData->filter(fn ($ent) => isset($ent['entPhysicalVendorType']) && $ent['entPhysicalVendorType'] == 'cevContainerSFP');
$data = $snmpData->filter(fn ($ent) => $sfpCages->has($ent['entPhysicalContainedIn'] ?? null))->map(function ($e) {
if (isset($e['entAliasMappingIdentifier'][0])) {
$e['ifIndex'] = preg_replace('/^.*ifIndex[.[](\d+).*$/', '$1', $e['entAliasMappingIdentifier'][0]);
}
return $e;
});
}
$ifIndexToPortId = $this->getDevice()->ports()->pluck('port_id', 'ifIndex');
return $data->map(function ($ent, $index) use ($ifIndexToPortId) {
$ifIndex = $ent['ifIndex'] ?? null;
return new Transceiver([
'port_id' => $ifIndexToPortId->get($ifIndex, 0),
'index' => $index,
'type' => $ent['entPhysicalDescr'] ?? null,
'vendor' => $ent['entPhysicalMfgName'] ?? null,
'revision' => $ent['entPhysicalHardwareRev'] ?? null,
'model' => $ent['entPhysicalModelName'] ?? null,
'serial' => $ent['entPhysicalSerialNum'] ?? null,
'entity_physical_index' => $ifIndex,
]);
});
}
}

View File

@@ -47,7 +47,8 @@ trait EntityMib
return $data->mapTable(function ($data, $entityPhysicalIndex) use ($entPhysicalToIfIndexMap) {
$entityPhysical = new EntPhysical($data);
$entityPhysical->entPhysicalIndex = $entityPhysicalIndex;
$entityPhysical->ifIndex = $entPhysicalToIfIndexMap[$entityPhysicalIndex] ?? null;
// get ifIndex. also if parent has an ifIndex, set it too
$entityPhysical->ifIndex = $entPhysicalToIfIndexMap[$entityPhysicalIndex] ?? $entPhysicalToIfIndexMap[$entityPhysical->entPhysicalContainedIn] ?? null;
return $entityPhysical;
});

View File

@@ -22,6 +22,7 @@
namespace LibreNMS\Util;
use Illuminate\Support\Str;
use LibreNMS\Exceptions\UserFunctionExistException;
class UserFuncHelper
@@ -42,4 +43,11 @@ class UserFuncHelper
{
return \LibreNMS\Util\Time::dateToDays($this->value_raw);
}
public function fsParseChannelValue(): float
{
$channel = Str::afterLast($this->sensor['sensor_index'], '.');
return Number::cast(explode(',', $this->value_raw)[$channel] ?? '');
}
}