Files
netdisco/lib/App/Netdisco/DB/Result/Device.pm
Oliver Gorwits dc1f76c1aa Feature to gather SNMP Walk, use as Pseudo Device, and Browse Objects
* fix anomalous name

* add gather worker

* fix encoding of binary storage

* store results back to job

* now parsing mbis report to translate

* fix the broken report parser

* rename gather to snapshot

* implement walk code copied from SNMP::Info

* can now bulkwalk and parse mibs report and store resolved walk in cache

* add func/glob aliasing broken

* better aliasing

* implement aliasing from globals and funcs

* fix regexp for matching netdisco-mibs report

* fake cache entry for all ND2 methods called, add comments

* also save to logs/snapshots/IP

* add doc for netdisco-do

* add is_pseudo column to device table

* support for loading cache for pseudo devices

* check for hrSystemUptime as well as sysUpTime for snmp connect

* display pseudo devices with yellow pill for name

* color all cells for layers for pseudo

* no need to b64 encode binary data in scalars as we b64 whole thing after

* tweaked uptime check

* store snapshot to database instead of Job

* expose snapshots in device details tab

* small ux improvements on snap download

* fixes for errors in subnet mask searching

* hide snapshot management for pseudo devices

* update to use new netdisco-mibs object cache

* update for new format oids file

* start of work on loading walk into db for browsing

* store values and meta

* add auto increment col and oid index to browser

* start web plugin for browser

* add virtual search for oid children

* have all oid in separte table (60 seconds load on my laptop)

* rename table and add relation

* store oid as int array

* fix sql for children

* make jstree start working

* working very slow tree expand

* fix to work when first displaying tree

* store both oid and oid_parts

* simplify SQL to speed up (more complicated perl)

* fix sql bug, add better index, prettify tree

* render the snmp node detail

* add node template, make scrollable, pretty print data values (insecure)

* store munge hint

* some dubious code to munge the data

* make sure to filter by IP on device_browser

* make safer the rendering of value data (but need to come back to key ordering)

* fix sorting on object values

* limit the opening of child nodes to keep response good and unclutter

* factor out the munge and make safer

* reject unknown mungers

* show the munger and option (not working) to change

* additional js for munge select

* complete custom munge

* change so that saving to database is only at CLI and on request

* hide snmp tab if no browser rows in the db

* add helpful message when no browser rows for the device

* stub handler for search and add recurse control

* working search

* minor ui fixes

* implement typeahead for leaf search

* limit rows in typeahead

* make sure device_browser is visited in delete and renumber

* add requirements for this branch

* update manifest

* make sure node search and typeahead are restricted to current device only
2021-11-06 07:47:29 +00:00

488 lines
11 KiB
Perl

use utf8;
package App::Netdisco::DB::Result::Device;
use strict;
use warnings;
use NetAddr::IP::Lite ':lower';
use App::Netdisco::Util::DNS 'hostname_from_ip';
use overload '""' => sub { shift->ip }, fallback => 1;
use base 'App::Netdisco::DB::Result';
__PACKAGE__->table("device");
__PACKAGE__->add_columns(
"ip",
{ data_type => "inet", is_nullable => 0 },
"creation",
{
data_type => "timestamp",
default_value => \"current_timestamp",
is_nullable => 1,
original => { default_value => \"now()" },
},
"dns",
{ data_type => "text", is_nullable => 1 },
"description",
{ data_type => "text", is_nullable => 1 },
"uptime",
{ data_type => "bigint", is_nullable => 1 },
"contact",
{ data_type => "text", is_nullable => 1 },
"name",
{ data_type => "text", is_nullable => 1 },
"location",
{ data_type => "text", is_nullable => 1 },
"layers",
{ data_type => "varchar", is_nullable => 1, size => 8 },
"num_ports",
{ data_type => "integer", is_serializable => 0, is_nullable => 1 },
"mac",
{ data_type => "macaddr", is_nullable => 1 },
"serial",
{ data_type => "text", is_nullable => 1 },
"chassis_id",
{ data_type => "text", is_nullable => 1 },
"model",
{ data_type => "text", is_nullable => 1 },
"ps1_type",
{ data_type => "text", is_nullable => 1 },
"ps2_type",
{ data_type => "text", is_nullable => 1 },
"ps1_status",
{ data_type => "text", is_nullable => 1 },
"ps2_status",
{ data_type => "text", is_nullable => 1 },
"fan",
{ data_type => "text", is_nullable => 1 },
"slots",
{ data_type => "integer", is_nullable => 1 },
"vendor",
{ data_type => "text", is_nullable => 1 },
"os",
{ data_type => "text", is_nullable => 1 },
"os_ver",
{ data_type => "text", is_nullable => 1 },
"log",
{ data_type => "text", is_nullable => 1 },
"snmp_ver",
{ data_type => "integer", is_nullable => 1 },
"snmp_comm",
{ data_type => "text", is_nullable => 1 },
"snmp_class",
{ data_type => "text", is_nullable => 1 },
"snmp_engineid",
{ data_type => "text", is_nullable => 1 },
"vtp_domain",
{ data_type => "text", is_nullable => 1 },
"last_discover",
{ data_type => "timestamp", is_nullable => 1 },
"last_macsuck",
{ data_type => "timestamp", is_nullable => 1 },
"last_arpnip",
{ data_type => "timestamp", is_nullable => 1 },
"is_pseudo",
{ data_type => "boolean", is_nullable => 0, default_value => \"false" },
);
__PACKAGE__->set_primary_key("ip");
=head1 RELATIONSHIPS
=head2 device_ips
Returns rows from the C<device_ip> table which relate to this Device. That is,
all the interface IP aliases configured on the Device.
=cut
__PACKAGE__->has_many( device_ips => 'App::Netdisco::DB::Result::DeviceIp', 'ip' );
=head2 device_ips_by_address_or_name
Returns rows from the C<device_ip> table which relate to this Device. That is,
all the interface IP aliases configured on the Device. However you probably
want to use the C<device_ips_with_address_or_name> ResultSet method instead,
so you can pass the MAC address part.
=cut
__PACKAGE__->has_many( device_ips_by_address_or_name => 'App::Netdisco::DB::Result::DeviceIp',
sub {
my $args = shift;
return {
"$args->{foreign_alias}.ip" => { -ident => "$args->{self_alias}.ip" },
-or => [
"$args->{foreign_alias}.dns" => { 'ilike', \'?' },
"$args->{foreign_alias}.alias" => { '<<=', \'?' },
"$args->{foreign_alias}.alias::text" => { 'ilike', \'?' },
],
};
},
{ cascade_copy => 0, cascade_update => 0, cascade_delete => 0 }
);
=head2 vlans
Returns the C<device_vlan> entries for this Device. That is, the list of VLANs
configured on or known by this Device.
=cut
__PACKAGE__->has_many( vlans => 'App::Netdisco::DB::Result::DeviceVlan', 'ip' );
=head2 ports
Returns the set of ports on this Device.
=cut
__PACKAGE__->has_many( ports => 'App::Netdisco::DB::Result::DevicePort', 'ip' );
=head2 ports_by_mac
Returns the set of ports on this Device, filtered by MAC. However you probably
want to use the C<ports_with_mac> ResultSet method instead, so you can pass the
MAC address part.
=cut
__PACKAGE__->has_many( ports_by_mac => 'App::Netdisco::DB::Result::DevicePort',
sub {
my $args = shift;
return {
"$args->{foreign_alias}.ip" => { -ident => "$args->{self_alias}.ip" },
"$args->{foreign_alias}.mac::text" => { 'ilike', \'?' },
};
},
{ cascade_copy => 0, cascade_update => 0, cascade_delete => 0 }
);
=head2 modules
Returns the set chassis modules on this Device.
=cut
__PACKAGE__->has_many( modules => 'App::Netdisco::DB::Result::DeviceModule', 'ip' );
=head2 power_modules
Returns the set of power modules on this Device.
=cut
__PACKAGE__->has_many( power_modules => 'App::Netdisco::DB::Result::DevicePower', 'ip' );
=head2 oids
Returns the oids walked on this Device.
=cut
__PACKAGE__->has_many( oids => 'App::Netdisco::DB::Result::DeviceBrowser', 'ip' );
=head2 port_vlans
Returns the set of VLANs known to be configured on Ports on this Device,
either tagged or untagged.
The JOIN is of type "RIGHT" meaning that the results are constrained to VLANs
only on Ports on this Device.
=cut
__PACKAGE__->has_many(
port_vlans => 'App::Netdisco::DB::Result::DevicePortVlan',
'ip', { join_type => 'RIGHT' }
);
=head2 port_vlans_filter
A JOIN condition which can be used to filter a set of Devices to those known
carrying a given VLAN on its ports. Uses an INNER JOIN to achieve this.
=cut
__PACKAGE__->has_many(
port_vlans_filter => 'App::Netdisco::DB::Result::DevicePortVlan',
'ip', { join_type => 'INNER' }
);
# helper which assumes we've just RIGHT JOINed to Vlans table
sub vlan { return (shift)->vlans->first }
=head2 wireless_ports
Returns the set of wireless IDs known to be configured on Ports on this
Device.
=cut
__PACKAGE__->has_many(
wireless_ports => 'App::Netdisco::DB::Result::DevicePortWireless',
'ip', { join_type => 'RIGHT' }
);
=head2 ssids
Returns the set of SSIDs known to be configured on Ports on this Device.
=cut
__PACKAGE__->has_many(
ssids => 'App::Netdisco::DB::Result::DevicePortSsid',
'ip', { join_type => 'RIGHT' }
);
=head2 properties_ports
Returns the set of ports known to have recorded properties
=cut
__PACKAGE__->has_many(
properties_ports => 'App::Netdisco::DB::Result::DevicePortProperties',
'ip', { join_type => 'RIGHT' }
);
=head2 powered_ports
Returns the set of ports known to have PoE capability
=cut
__PACKAGE__->has_many(
powered_ports => 'App::Netdisco::DB::Result::DevicePortPower',
'ip', { join_type => 'RIGHT' }
);
=head2 community
Returns the row from the community string table, if one exists.
=cut
__PACKAGE__->might_have(
community => 'App::Netdisco::DB::Result::Community', 'ip');
=head2 snapshot
Returns the row from the snapshot table, if one exists.
=cut
__PACKAGE__->might_have(
snapshot => 'App::Netdisco::DB::Result::DeviceSnapshot', 'ip');
=head2 throughput
Returns a sum of speeds on all ports on the device.
=cut
__PACKAGE__->has_one(
throughput => 'App::Netdisco::DB::Result::Virtual::DevicePortSpeed', 'ip');
=head1 ADDITIONAL METHODS
=head2 has_layer( $number )
Returns true if the device provided sysServices and supports the given layer.
=cut
sub has_layer {
my ($device, $layer) = @_;
return unless $layer and $layer =~ m/^[1-7]$/;
return ($device->layers and (substr($device->layers, (8-$layer), 1) == 1));
}
=head2 renumber( $new_ip )
Will update this device and all related database records to use the new IP
C<$new_ip>. Returns C<undef> if $new_ip seems invalid, otherwise returns the
Device row object.
=cut
sub renumber {
my ($device, $ip) = @_;
my $schema = $device->result_source->schema;
my $new_addr = NetAddr::IP::Lite->new($ip)
or return;
my $old_ip = $device->ip;
my $new_ip = $new_addr->addr;
return
if $new_ip eq '0.0.0.0'
or $new_ip eq '127.0.0.1';
# Community is not included as SNMP::test_connection will take care of it
foreach my $set (qw/
DeviceBrowser
DeviceIp
DeviceModule
DevicePower
DeviceSnapshot
DeviceVlan
DevicePort
DevicePortLog
DevicePortPower
DevicePortProperties
DevicePortSsid
DevicePortVlan
DevicePortWireless
/) {
$schema->resultset($set)
->search({ip => $old_ip})
->update({ip => $new_ip});
}
$schema->resultset('DeviceSkip')
->search({device => $new_ip})->delete;
$schema->resultset('DeviceSkip')
->search({device => $old_ip})
->update({device => $new_ip});
$schema->resultset('DevicePort')
->search({remote_ip => $old_ip})
->update({remote_ip => $new_ip});
$schema->resultset('Node')
->search({switch => $old_ip})
->update({switch => $new_ip});
$schema->resultset('Topology')
->search({dev1 => $old_ip})
->update({dev1 => $new_ip});
$schema->resultset('Topology')
->search({dev2 => $old_ip})
->update({dev2 => $new_ip});
$schema->resultset('Admin')->search({
device => $old_ip,
status => { '-not_like' => 'queued-%' },
})->delete;
$device->update({
ip => $new_ip,
dns => hostname_from_ip($new_ip),
});
return $device;
}
=head1 ADDITIONAL COLUMNS
=head2 oui
Returns the first half of the device MAC address.
=cut
sub oui { return substr( ((shift)->mac || ''), 0, 8 ) }
=head2 port_count
Returns the number of ports on this device. Enable this
column by applying the C<with_port_count()> modifier to C<search()>.
=cut
sub port_count { return (shift)->get_column('port_count') }
=head2 uptime_age
Formatted version of the C<uptime> field.
The format is in "X days/months/years" style, similar to:
1 year 4 months 05:46:00
=cut
sub uptime_age { return (shift)->get_column('uptime_age') }
=head2 first_seen_stamp
Formatted version of the C<creation> field, accurate to the minute.
The format is somewhat like ISO 8601 or RFC3339 but without the middle C<T>
between the date stamp and time stamp. That is:
2012-02-06 12:49
=cut
sub first_seen_stamp { return (shift)->get_column('first_seen_stamp') }
=head2 last_discover_stamp
Formatted version of the C<last_discover> field, accurate to the minute.
The format is somewhat like ISO 8601 or RFC3339 but without the middle C<T>
between the date stamp and time stamp. That is:
2012-02-06 12:49
=cut
sub last_discover_stamp { return (shift)->get_column('last_discover_stamp') }
=head2 last_macsuck_stamp
Formatted version of the C<last_macsuck> field, accurate to the minute.
The format is somewhat like ISO 8601 or RFC3339 but without the middle C<T>
between the date stamp and time stamp. That is:
2012-02-06 12:49
=cut
sub last_macsuck_stamp { return (shift)->get_column('last_macsuck_stamp') }
=head2 last_arpnip_stamp
Formatted version of the C<last_arpnip> field, accurate to the minute.
The format is somewhat like ISO 8601 or RFC3339 but without the middle C<T>
between the date stamp and time stamp. That is:
2012-02-06 12:49
=cut
sub last_arpnip_stamp { return (shift)->get_column('last_arpnip_stamp') }
=head2 since_last_discover
Number of seconds which have elapsed since the value of C<last_discover>.
=cut
sub since_last_discover { return (shift)->get_column('since_last_discover') }
=head2 since_last_macsuck
Number of seconds which have elapsed since the value of C<last_macsuck>.
=cut
sub since_last_macsuck { return (shift)->get_column('since_last_macsuck') }
=head2 since_last_arpnip
Number of seconds which have elapsed since the value of C<last_arpnip>.
=cut
sub since_last_arpnip { return (shift)->get_column('since_last_arpnip') }
1;