Merge branch 'master' into og-autoload
This commit is contained in:
@@ -1,3 +1,91 @@
|
|||||||
|
2.028012 - 2014-07-22
|
||||||
|
|
||||||
|
[BUG FIXES]
|
||||||
|
|
||||||
|
* UTF8 decode the remote type string in neighbor protocol
|
||||||
|
|
||||||
|
2.028011 - 2014-07-22
|
||||||
|
|
||||||
|
[BUG FIXES]
|
||||||
|
|
||||||
|
* Load Graph modules only if available
|
||||||
|
|
||||||
|
2.028010 - 2014-07-22
|
||||||
|
|
||||||
|
[NEW FEATURES]
|
||||||
|
|
||||||
|
* Node Monitor admin panel and netdisco-do action (beta)
|
||||||
|
|
||||||
|
[BUG FIXES]
|
||||||
|
|
||||||
|
* Hash deref in Nbtstat.pm
|
||||||
|
|
||||||
|
2.028008 - 2014-07-22
|
||||||
|
|
||||||
|
[BUG FIXES]
|
||||||
|
|
||||||
|
* Encode NetBIOS name, domain, user as UTF-8
|
||||||
|
|
||||||
|
2.028007 - 2014-07-21
|
||||||
|
|
||||||
|
[ENHANCEMENTS]
|
||||||
|
|
||||||
|
* Add 'graph' option to netdisco-do
|
||||||
|
|
||||||
|
[BUG FIXES]
|
||||||
|
|
||||||
|
* DNS Mismatch report incorrectly trimmed domain_suffix
|
||||||
|
* Fake one device aliases entry for devices not providing ip_index
|
||||||
|
* DataTables render function should check for type
|
||||||
|
|
||||||
|
2.028006 - 2014-07-21
|
||||||
|
|
||||||
|
[ENHANCEMENTS]
|
||||||
|
|
||||||
|
* Asynchronous NBTstat
|
||||||
|
|
||||||
|
[BUG FIXES]
|
||||||
|
|
||||||
|
* Device DNS Mismatch report: use name in the name column, and fix link
|
||||||
|
|
||||||
|
2.028005 - 2014-07-17
|
||||||
|
|
||||||
|
[BUG FIXES]
|
||||||
|
|
||||||
|
* Custom path handling for DataTables ajax calls
|
||||||
|
* [#116] Log true client IP when running through a proxy
|
||||||
|
* Use /etc/hosts for all forward/reverse DNS
|
||||||
|
|
||||||
|
2.028004 - 2014-07-16
|
||||||
|
|
||||||
|
[BUG FIXES]
|
||||||
|
|
||||||
|
* Close socket upon completion of async DNS resolution
|
||||||
|
|
||||||
|
2.028003 - 2014-07-15
|
||||||
|
|
||||||
|
[NEW FEATURES]
|
||||||
|
|
||||||
|
* GraphViz graph export (beta)
|
||||||
|
|
||||||
|
[BUG FIXES]
|
||||||
|
|
||||||
|
* No default for interactives made daemon die (H. Weber)
|
||||||
|
|
||||||
|
2.028001 - 2014-07-13
|
||||||
|
|
||||||
|
[NEW FEATURES]
|
||||||
|
|
||||||
|
* portctl_nameonly to limit port control to name only (F. Schiavarelli)
|
||||||
|
* add by_hostname config option to netdisco-rancid-export
|
||||||
|
|
||||||
|
[BUG FIXES]
|
||||||
|
|
||||||
|
* [ND1#117] unknown devices missing from inventory
|
||||||
|
* [#118] Fix table rendering on port search tab when VLAN is null
|
||||||
|
* Fix truncated port description change (F. Schiavarelli)
|
||||||
|
* Fix device port ordering in DataTables when Port Control is enabled
|
||||||
|
|
||||||
2.028000 - 2014-07-01
|
2.028000 - 2014-07-01
|
||||||
|
|
||||||
[NEW FEATURES]
|
[NEW FEATURES]
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ inc/Module/Install/Share.pm
|
|||||||
inc/Module/Install/Win32.pm
|
inc/Module/Install/Win32.pm
|
||||||
inc/Module/Install/WriteAll.pm
|
inc/Module/Install/WriteAll.pm
|
||||||
lib/App/Netdisco.pm
|
lib/App/Netdisco.pm
|
||||||
|
lib/App/Netdisco/AnyEvent/Nbtstat.pm
|
||||||
lib/App/Netdisco/Configuration.pm
|
lib/App/Netdisco/Configuration.pm
|
||||||
lib/App/Netdisco/Core/Arpnip.pm
|
lib/App/Netdisco/Core/Arpnip.pm
|
||||||
lib/App/Netdisco/Core/Discover.pm
|
lib/App/Netdisco/Core/Discover.pm
|
||||||
@@ -80,6 +81,7 @@ lib/App/Netdisco/DB/Result/Virtual/DeviceLinks.pm
|
|||||||
lib/App/Netdisco/DB/Result/Virtual/DevicePoeStatus.pm
|
lib/App/Netdisco/DB/Result/Virtual/DevicePoeStatus.pm
|
||||||
lib/App/Netdisco/DB/Result/Virtual/DuplexMismatch.pm
|
lib/App/Netdisco/DB/Result/Virtual/DuplexMismatch.pm
|
||||||
lib/App/Netdisco/DB/Result/Virtual/GenericReport.pm
|
lib/App/Netdisco/DB/Result/Virtual/GenericReport.pm
|
||||||
|
lib/App/Netdisco/DB/Result/Virtual/NodeMonitor.pm
|
||||||
lib/App/Netdisco/DB/Result/Virtual/NodeWithAge.pm
|
lib/App/Netdisco/DB/Result/Virtual/NodeWithAge.pm
|
||||||
lib/App/Netdisco/DB/Result/Virtual/OrphanedDevices.pm
|
lib/App/Netdisco/DB/Result/Virtual/OrphanedDevices.pm
|
||||||
lib/App/Netdisco/DB/Result/Virtual/PhonesDiscovered.pm
|
lib/App/Netdisco/DB/Result/Virtual/PhonesDiscovered.pm
|
||||||
@@ -151,7 +153,9 @@ lib/App/Netdisco/Manual/WritingPlugins.pod
|
|||||||
lib/App/Netdisco/Util/Device.pm
|
lib/App/Netdisco/Util/Device.pm
|
||||||
lib/App/Netdisco/Util/DNS.pm
|
lib/App/Netdisco/Util/DNS.pm
|
||||||
lib/App/Netdisco/Util/ExpandParams.pm
|
lib/App/Netdisco/Util/ExpandParams.pm
|
||||||
|
lib/App/Netdisco/Util/Graph.pm
|
||||||
lib/App/Netdisco/Util/Node.pm
|
lib/App/Netdisco/Util/Node.pm
|
||||||
|
lib/App/Netdisco/Util/NodeMonitor.pm
|
||||||
lib/App/Netdisco/Util/Noop.pm
|
lib/App/Netdisco/Util/Noop.pm
|
||||||
lib/App/Netdisco/Util/Permission.pm
|
lib/App/Netdisco/Util/Permission.pm
|
||||||
lib/App/Netdisco/Util/Port.pm
|
lib/App/Netdisco/Util/Port.pm
|
||||||
@@ -167,6 +171,7 @@ lib/App/Netdisco/Web/GenericReport.pm
|
|||||||
lib/App/Netdisco/Web/Password.pm
|
lib/App/Netdisco/Web/Password.pm
|
||||||
lib/App/Netdisco/Web/Plugin.pm
|
lib/App/Netdisco/Web/Plugin.pm
|
||||||
lib/App/Netdisco/Web/Plugin/AdminTask/JobQueue.pm
|
lib/App/Netdisco/Web/Plugin/AdminTask/JobQueue.pm
|
||||||
|
lib/App/Netdisco/Web/Plugin/AdminTask/NodeMonitor.pm
|
||||||
lib/App/Netdisco/Web/Plugin/AdminTask/OrphanedDevices.pm
|
lib/App/Netdisco/Web/Plugin/AdminTask/OrphanedDevices.pm
|
||||||
lib/App/Netdisco/Web/Plugin/AdminTask/PollerPerformance.pm
|
lib/App/Netdisco/Web/Plugin/AdminTask/PollerPerformance.pm
|
||||||
lib/App/Netdisco/Web/Plugin/AdminTask/PseudoDevice.pm
|
lib/App/Netdisco/Web/Plugin/AdminTask/PseudoDevice.pm
|
||||||
@@ -292,6 +297,7 @@ share/public/javascripts/toastr.js
|
|||||||
share/public/javascripts/underscore.min.js
|
share/public/javascripts/underscore.min.js
|
||||||
share/views/admintask.tt
|
share/views/admintask.tt
|
||||||
share/views/ajax/admintask/jobqueue.tt
|
share/views/ajax/admintask/jobqueue.tt
|
||||||
|
share/views/ajax/admintask/nodemonitor.tt
|
||||||
share/views/ajax/admintask/orphaned.tt
|
share/views/ajax/admintask/orphaned.tt
|
||||||
share/views/ajax/admintask/orphaned_csv.tt
|
share/views/ajax/admintask/orphaned_csv.tt
|
||||||
share/views/ajax/admintask/performance.tt
|
share/views/ajax/admintask/performance.tt
|
||||||
|
|||||||
@@ -51,12 +51,12 @@ requires:
|
|||||||
Net::Domain: 1.23
|
Net::Domain: 1.23
|
||||||
Net::LDAP: 0
|
Net::LDAP: 0
|
||||||
Net::MAC: 2.103622
|
Net::MAC: 2.103622
|
||||||
Net::NBName: 0.26
|
|
||||||
NetAddr::IP: 4.068
|
NetAddr::IP: 4.068
|
||||||
Opcode: 1.07
|
Opcode: 1.07
|
||||||
Path::Class: 0.32
|
Path::Class: 0.32
|
||||||
Plack: 1.0023
|
Plack: 1.0023
|
||||||
Plack::Middleware::Expires: 0.03
|
Plack::Middleware::Expires: 0.03
|
||||||
|
Plack::Middleware::ReverseProxy: 0.15
|
||||||
Role::Tiny: 1.002005
|
Role::Tiny: 1.002005
|
||||||
SNMP::Info: 3.18
|
SNMP::Info: 3.18
|
||||||
SQL::Translator: 0.11016
|
SQL::Translator: 0.11016
|
||||||
@@ -81,4 +81,4 @@ resources:
|
|||||||
homepage: http://netdisco.org/
|
homepage: http://netdisco.org/
|
||||||
license: http://opensource.org/licenses/bsd-license.php
|
license: http://opensource.org/licenses/bsd-license.php
|
||||||
repository: git://git.code.sf.net/p/netdisco/netdisco-ng
|
repository: git://git.code.sf.net/p/netdisco/netdisco-ng
|
||||||
version: 2.028000
|
version: 2.028012
|
||||||
|
|||||||
@@ -36,12 +36,12 @@ requires 'Net::Domain' => 1.23;
|
|||||||
requires 'Net::DNS' => 0.72;
|
requires 'Net::DNS' => 0.72;
|
||||||
requires 'Net::LDAP' => 0;
|
requires 'Net::LDAP' => 0;
|
||||||
requires 'Net::MAC' => 2.103622;
|
requires 'Net::MAC' => 2.103622;
|
||||||
requires 'Net::NBName' => 0.26;
|
|
||||||
requires 'NetAddr::IP' => 4.068;
|
requires 'NetAddr::IP' => 4.068;
|
||||||
requires 'Opcode' => 1.07;
|
requires 'Opcode' => 1.07;
|
||||||
requires 'Path::Class' => 0.32;
|
requires 'Path::Class' => 0.32;
|
||||||
requires 'Plack' => 1.0023;
|
requires 'Plack' => 1.0023;
|
||||||
requires 'Plack::Middleware::Expires' => 0.03;
|
requires 'Plack::Middleware::Expires' => 0.03;
|
||||||
|
requires 'Plack::Middleware::ReverseProxy' => 0.15;
|
||||||
requires 'Role::Tiny' => 1.002005;
|
requires 'Role::Tiny' => 1.002005;
|
||||||
requires 'Socket6' => 0.23;
|
requires 'Socket6' => 0.23;
|
||||||
requires 'Starman' => 0.4008;
|
requires 'Starman' => 0.4008;
|
||||||
|
|||||||
@@ -81,6 +81,19 @@ if (!length $action) {
|
|||||||
with 'App::Netdisco::Daemon::Worker::Poller::Expiry';
|
with 'App::Netdisco::Daemon::Worker::Poller::Expiry';
|
||||||
with 'App::Netdisco::Daemon::Worker::Interactive::DeviceActions';
|
with 'App::Netdisco::Daemon::Worker::Interactive::DeviceActions';
|
||||||
with 'App::Netdisco::Daemon::Worker::Interactive::PortActions';
|
with 'App::Netdisco::Daemon::Worker::Interactive::PortActions';
|
||||||
|
|
||||||
|
use Module::Load ();
|
||||||
|
eval { Module::Load::load 'App::Netdisco::Util::Graph' };
|
||||||
|
sub graph {
|
||||||
|
App::Netdisco::Util::Graph::graph();
|
||||||
|
return ('done', 'Generated graph data.');
|
||||||
|
}
|
||||||
|
|
||||||
|
use App::Netdisco::Util::NodeMonitor ();
|
||||||
|
sub monitor {
|
||||||
|
App::Netdisco::Util::NodeMonitor::monitor();
|
||||||
|
return ('done', 'Generated monitor data.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
my $worker = MyWorker->new();
|
my $worker = MyWorker->new();
|
||||||
|
|
||||||
@@ -152,6 +165,10 @@ Run an arpnip on the device (specified with C<-d>).
|
|||||||
|
|
||||||
Run an nbtstat on the node (specified with C<-d>).
|
Run an nbtstat on the node (specified with C<-d>).
|
||||||
|
|
||||||
|
=head2 graph
|
||||||
|
|
||||||
|
Generate GrapgViz graphs for the largest cluster of devices.
|
||||||
|
|
||||||
=head2 set_location
|
=head2 set_location
|
||||||
|
|
||||||
Set the SNMP location field on the device (specified with C<-d>). Pass the
|
Set the SNMP location field on the device (specified with C<-d>). Pass the
|
||||||
|
|||||||
@@ -43,15 +43,22 @@ use Dancer::Plugin::DBIC 'schema';
|
|||||||
use App::Netdisco::Util::Permission ':all';
|
use App::Netdisco::Util::Permission ':all';
|
||||||
|
|
||||||
my $settings = setting( 'rancid' );
|
my $settings = setting( 'rancid' );
|
||||||
|
my $domain_suffix = setting( 'domain_suffix' ) || '';
|
||||||
my $delimiter = $settings->{ 'delimiter' } || ':';
|
my $delimiter = $settings->{ 'delimiter' } || ':';
|
||||||
my $down_age = $settings->{ 'down_age' } || '1 day';
|
my $down_age = $settings->{ 'down_age' } || '1 day';
|
||||||
my $rancidhome = $settings->{ 'rancid_home' } || '/var/lib/rancid';
|
my $rancidhome = $settings->{ 'rancid_home' } || '/var/lib/rancid';
|
||||||
my $config_vendormap = $settings->{ 'vendormap' } || {};
|
my $config_vendormap = $settings->{ 'vendormap' } || {};
|
||||||
|
|
||||||
my $by_ip = {};
|
my $by_ip = {};
|
||||||
foreach my $g (@{$settings->{ 'by_ip' }}) {
|
foreach my $g (@{$settings->{ 'by_ip' }}) {
|
||||||
$by_ip->{$g} = 1;
|
$by_ip->{$g} = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
my $by_hostname = {};
|
||||||
|
foreach my $g (@{$settings->{ 'by_hostname' }}) {
|
||||||
|
$by_hostname->{$g} = 1;
|
||||||
|
}
|
||||||
|
|
||||||
my @devices = schema('netdisco')->resultset('Device')->search({},
|
my @devices = schema('netdisco')->resultset('Device')->search({},
|
||||||
{
|
{
|
||||||
'+columns' => {
|
'+columns' => {
|
||||||
@@ -92,13 +99,16 @@ foreach my $group (keys %$list) {
|
|||||||
$vendor = $VENDORMAP{$vendormodel} || $VENDORMAP{$vendor};
|
$vendor = $VENDORMAP{$vendormodel} || $VENDORMAP{$vendor};
|
||||||
}
|
}
|
||||||
if ( $config_vendormap->{$vendor} or $config_vendormap->{$vendormodel} ) {
|
if ( $config_vendormap->{$vendor} or $config_vendormap->{$vendormodel} ) {
|
||||||
$vendor = $config_vendormap{$vendormodel} || $config_vendormap{$vendor};
|
$vendor = $config_vendormap->{$vendormodel} || $config_vendormap->{$vendor};
|
||||||
}
|
}
|
||||||
if ($by_ip->{$group}) {
|
if ($by_ip->{$group}) {
|
||||||
$name = $dev->ip;
|
$name = $dev->ip;
|
||||||
} else {
|
} else {
|
||||||
$name = ($dev->dns || $dev->name);
|
$name = ($dev->dns || $dev->name);
|
||||||
}
|
}
|
||||||
|
if ($by_hostname->{$group}) {
|
||||||
|
$name =~ s/$domain_suffix$//;
|
||||||
|
}
|
||||||
printf ROUTER "%s$delimiter%s$delimiter%s\n", $name, $vendor,
|
printf ROUTER "%s$delimiter%s$delimiter%s\n", $name, $vendor,
|
||||||
$dev->get_column( 'old' ) ? "down" : "up";
|
$dev->get_column( 'old' ) ? "down" : "up";
|
||||||
}
|
}
|
||||||
@@ -119,6 +129,7 @@ This script requires some configuration to be added to your Netdisco
|
|||||||
down_age: '1 day'
|
down_age: '1 day'
|
||||||
delimiter: ':'
|
delimiter: ':'
|
||||||
by_ip: [ other ]
|
by_ip: [ other ]
|
||||||
|
by_hostname: [ other2 ]
|
||||||
groups:
|
groups:
|
||||||
switch: [ 'name:.*[Ss][Ww].*' ]
|
switch: [ 'name:.*[Ss][Ww].*' ]
|
||||||
rtr: [ 'name:[rR]tr.*' ]
|
rtr: [ 'name:[rR]tr.*' ]
|
||||||
@@ -172,6 +183,12 @@ Netdisco's "C<*_only>" settings, and accepts IP, prefix, device property.
|
|||||||
List of RANCID Groups which will have Device IPs written to the RANCID
|
List of RANCID Groups which will have Device IPs written to the RANCID
|
||||||
configuration file, instead of DNS or SNMP host names.
|
configuration file, instead of DNS or SNMP host names.
|
||||||
|
|
||||||
|
=head2 C<by_hostname>
|
||||||
|
|
||||||
|
List of RANCID Groups which will have Device Hostname written to the RANCID
|
||||||
|
configuration file, instead of FQDN. This is done simply by stripping the
|
||||||
|
C<domain_suffix> configuration item from the FQDN.
|
||||||
|
|
||||||
=head1 SEE ALSO
|
=head1 SEE ALSO
|
||||||
|
|
||||||
=over 4
|
=over 4
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ my $home = ($ENV{NETDISCO_HOME} || $ENV{HOME});
|
|||||||
set(session_dir => dir($home, 'netdisco-web-sessions')->stringify);
|
set(session_dir => dir($home, 'netdisco-web-sessions')->stringify);
|
||||||
|
|
||||||
set plack_middlewares => [
|
set plack_middlewares => [
|
||||||
|
['Plack::Middleware::ReverseProxy'],
|
||||||
[ Expires => (
|
[ Expires => (
|
||||||
content_type => [qr{^application/javascript}, qr{^text/css}, qr{image}, qr{font}],
|
content_type => [qr{^application/javascript}, qr{^text/css}, qr{image}, qr{font}],
|
||||||
expires => 'access plus 1 day',
|
expires => 'access plus 1 day',
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use strict;
|
|||||||
use warnings;
|
use warnings;
|
||||||
use 5.010_000;
|
use 5.010_000;
|
||||||
|
|
||||||
our $VERSION = '2.028000';
|
our $VERSION = '2.028012';
|
||||||
use App::Netdisco::Configuration;
|
use App::Netdisco::Configuration;
|
||||||
|
|
||||||
use Module::Find ();
|
use Module::Find ();
|
||||||
|
|||||||
277
Netdisco/lib/App/Netdisco/AnyEvent/Nbtstat.pm
Normal file
277
Netdisco/lib/App/Netdisco/AnyEvent/Nbtstat.pm
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
package App::Netdisco::AnyEvent::Nbtstat;
|
||||||
|
|
||||||
|
use Socket qw(AF_INET SOCK_DGRAM inet_aton sockaddr_in);
|
||||||
|
use List::Util ();
|
||||||
|
use Carp ();
|
||||||
|
|
||||||
|
use AnyEvent (); BEGIN { AnyEvent::common_sense }
|
||||||
|
use AnyEvent::Util ();
|
||||||
|
|
||||||
|
sub new {
|
||||||
|
my ( $class, %args ) = @_;
|
||||||
|
|
||||||
|
my $interval = $args{interval};
|
||||||
|
# This default should generate ~ 50 requests per second
|
||||||
|
$interval = 0.2 unless defined $interval;
|
||||||
|
|
||||||
|
my $timeout = $args{timeout};
|
||||||
|
|
||||||
|
# Timeout should be 250ms according to RFC1002, but we're going to double
|
||||||
|
$timeout = 0.5 unless defined $timeout;
|
||||||
|
|
||||||
|
my $self = bless { interval => $interval, timeout => $timeout, %args },
|
||||||
|
$class;
|
||||||
|
|
||||||
|
Scalar::Util::weaken( my $wself = $self );
|
||||||
|
|
||||||
|
socket my $fh4, AF_INET, Socket::SOCK_DGRAM(), 0
|
||||||
|
or Carp::croak "Unable to create socket : $!";
|
||||||
|
|
||||||
|
AnyEvent::Util::fh_nonblocking $fh4, 1;
|
||||||
|
$self->{fh4} = $fh4;
|
||||||
|
$self->{rw4} = AE::io $fh4, 0, sub {
|
||||||
|
if ( my $peer = recv $fh4, my $resp, 2048, 0 ) {
|
||||||
|
$wself->_on_read( $resp, $peer );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
# Nbtstat tasks
|
||||||
|
$self->{_tasks} = {};
|
||||||
|
|
||||||
|
return $self;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub interval { @_ > 1 ? $_[0]->{interval} = $_[1] : $_[0]->{interval} }
|
||||||
|
|
||||||
|
sub timeout { @_ > 1 ? $_[0]->{timeout} = $_[1] : $_[0]->{timeout} }
|
||||||
|
|
||||||
|
sub nbtstat {
|
||||||
|
my ( $self, $host, $cb ) = @_;
|
||||||
|
|
||||||
|
my $ip = inet_aton($host);
|
||||||
|
my $port = 137;
|
||||||
|
|
||||||
|
my $request = {
|
||||||
|
host => $host,
|
||||||
|
results => {},
|
||||||
|
cb => $cb,
|
||||||
|
destination => scalar sockaddr_in( $port, $ip ),
|
||||||
|
};
|
||||||
|
|
||||||
|
$self->{_tasks}{ $request->{destination} } = $request;
|
||||||
|
|
||||||
|
my $delay = $self->interval * scalar keys %{ $self->{_tasks} || {} };
|
||||||
|
|
||||||
|
# There's probably a better way to throttle the sends
|
||||||
|
# but this will work for now since we currently don't support retries
|
||||||
|
my $w; $w = AE::timer $delay, 0, sub {
|
||||||
|
undef $w;
|
||||||
|
$self->_send_request($request);
|
||||||
|
};
|
||||||
|
|
||||||
|
return $self;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _on_read {
|
||||||
|
my ( $self, $resp, $peer ) = @_;
|
||||||
|
|
||||||
|
($resp) = $resp =~ /^(.*)$/s
|
||||||
|
if AnyEvent::TAINT && $self->{untaint};
|
||||||
|
|
||||||
|
# Find our task
|
||||||
|
my $request = $self->{_tasks}{$peer};
|
||||||
|
|
||||||
|
return unless $request;
|
||||||
|
|
||||||
|
$self->_store_result( $request, 'OK', $resp );
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _store_result {
|
||||||
|
my ( $self, $request, $status, $resp ) = @_;
|
||||||
|
|
||||||
|
my $results = $request->{results};
|
||||||
|
|
||||||
|
my @rr = ();
|
||||||
|
my $mac_address = "";
|
||||||
|
|
||||||
|
if ( $status eq 'OK' && length($resp) > 56 ) {
|
||||||
|
my $num_names = unpack( "C", substr( $resp, 56 ) );
|
||||||
|
my $name_data = substr( $resp, 57 );
|
||||||
|
|
||||||
|
for ( my $i = 0; $i < $num_names; $i++ ) {
|
||||||
|
my $rr_data = substr( $name_data, 18 * $i, 18 );
|
||||||
|
push @rr, _decode_rr($rr_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$mac_address = join "-",
|
||||||
|
map { sprintf "%02X", $_ }
|
||||||
|
unpack( "C*", substr( $name_data, 18 * $num_names, 6 ) );
|
||||||
|
$results = {
|
||||||
|
'status' => 'OK',
|
||||||
|
'names' => \@rr,
|
||||||
|
'mac_address' => $mac_address
|
||||||
|
};
|
||||||
|
}
|
||||||
|
elsif ( $status eq 'OK' ) {
|
||||||
|
$results = { 'status' => 'SHORT' };
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$results = { 'status' => $status };
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clear request specific data
|
||||||
|
delete $request->{timer};
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
delete $self->{_tasks}{ $request->{destination} };
|
||||||
|
|
||||||
|
# Done
|
||||||
|
$request->{cb}->($results);
|
||||||
|
|
||||||
|
undef $request;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _send_request {
|
||||||
|
my ( $self, $request ) = @_;
|
||||||
|
|
||||||
|
my $msg = "";
|
||||||
|
# We use process id as identifier field, since don't have a need to
|
||||||
|
# unique responses beyond host / port queried
|
||||||
|
$msg .= pack( "n*", $$, 0, 1, 0, 0, 0 );
|
||||||
|
$msg .= _encode_name( "*", "\x00", 0 );
|
||||||
|
$msg .= pack( "n*", 0x21, 0x0001 );
|
||||||
|
|
||||||
|
$request->{start} = time;
|
||||||
|
|
||||||
|
$request->{timer} = AE::timer $self->timeout, 0, sub {
|
||||||
|
$self->_store_result( $request, 'TIMEOUT' );
|
||||||
|
};
|
||||||
|
|
||||||
|
my $fh = $self->{fh4};
|
||||||
|
|
||||||
|
send $fh, $msg, 0, $request->{destination}
|
||||||
|
or $self->_store_result( $request, 'ERROR' );
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _encode_name {
|
||||||
|
my $name = uc(shift);
|
||||||
|
my $pad = shift || "\x20";
|
||||||
|
my $suffix = shift || 0x00;
|
||||||
|
|
||||||
|
$name .= $pad x ( 16 - length($name) );
|
||||||
|
substr( $name, 15, 1, chr( $suffix & 0xFF ) );
|
||||||
|
|
||||||
|
my $encoded_name = "";
|
||||||
|
for my $c ( unpack( "C16", $name ) ) {
|
||||||
|
$encoded_name .= chr( ord('A') + ( ( $c & 0xF0 ) >> 4 ) );
|
||||||
|
$encoded_name .= chr( ord('A') + ( $c & 0xF ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
# Note that the _encode_name function doesn't add any scope,
|
||||||
|
# nor does it calculate the length (32), it just prefixes it
|
||||||
|
return "\x20" . $encoded_name . "\x00";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _decode_rr {
|
||||||
|
my $rr_data = shift;
|
||||||
|
|
||||||
|
my @nodetypes = qw/B-node P-node M-node H-node/;
|
||||||
|
my ( $name, $suffix, $flags ) = unpack( "a15Cn", $rr_data );
|
||||||
|
$name =~ tr/\x00-\x19/\./; # replace ctrl chars with "."
|
||||||
|
$name =~ s/\s+//g;
|
||||||
|
|
||||||
|
my $rr = {};
|
||||||
|
$rr->{'name'} = $name;
|
||||||
|
$rr->{'suffix'} = $suffix;
|
||||||
|
$rr->{'G'} = ( $flags & 2**15 ) ? "GROUP" : "UNIQUE";
|
||||||
|
$rr->{'ONT'} = $nodetypes[ ( $flags >> 13 ) & 3 ];
|
||||||
|
$rr->{'DRG'} = ( $flags & 2**12 ) ? "Deregistering" : "Registered";
|
||||||
|
$rr->{'CNF'} = ( $flags & 2**11 ) ? "Conflict" : "";
|
||||||
|
$rr->{'ACT'} = ( $flags & 2**10 ) ? "Active" : "Inactive";
|
||||||
|
$rr->{'PRM'} = ( $flags & 2**9 ) ? "Permanent" : "";
|
||||||
|
|
||||||
|
return $rr;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
__END__
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
App::Netdisco::AnyEvent::Nbtstat - Request NetBIOS node status with AnyEvent
|
||||||
|
|
||||||
|
=head1 SYNOPSIS
|
||||||
|
|
||||||
|
use App::Netdisco::AnyEvent::Nbtstat;;
|
||||||
|
|
||||||
|
my $request = App::Netdisco::AnyEvent::Nbtstat->new();
|
||||||
|
|
||||||
|
my $cv = AE::cv;
|
||||||
|
|
||||||
|
$request->nbtstat(
|
||||||
|
'127.0.0.1',
|
||||||
|
sub {
|
||||||
|
my $result = shift;
|
||||||
|
print "MAC: ", $result->{'mac_address'} || '', " ";
|
||||||
|
print "Status: ", $result->{'status'}, "\n";
|
||||||
|
printf '%3s %-18s %4s %-18s', '', 'Name', '', 'Type'
|
||||||
|
if ( $result->{'status'} eq 'OK' );
|
||||||
|
print "\n";
|
||||||
|
for my $rr ( @{ $result->{'names'} } ) {
|
||||||
|
printf '%3s %-18s <%02s> %-18s', '', $rr->{'name'},
|
||||||
|
$rr->{'suffix'},
|
||||||
|
$rr->{'G'};
|
||||||
|
print "\n";
|
||||||
|
}
|
||||||
|
$cv->send;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$cv->recv;
|
||||||
|
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
|
L<App::Netdisco::AnyEvent::Nbtstat> is an asynchronous AnyEvent NetBIOS node
|
||||||
|
status requester.
|
||||||
|
|
||||||
|
=head1 ATTRIBUTES
|
||||||
|
|
||||||
|
L<App::Netdisco::AnyEvent::Nbtstat> implements the following attributes.
|
||||||
|
|
||||||
|
=head2 C<interval>
|
||||||
|
|
||||||
|
my $interval = $request->interval;
|
||||||
|
$request->interval(1);
|
||||||
|
|
||||||
|
Interval between requests, defaults to 0.02 seconds.
|
||||||
|
|
||||||
|
=head2 C<timeout>
|
||||||
|
|
||||||
|
my $timeout = $request->timeout;
|
||||||
|
$request->timeout(2);
|
||||||
|
|
||||||
|
Maximum request response time, defaults to 0.5 seconds.
|
||||||
|
|
||||||
|
=head1 METHODS
|
||||||
|
|
||||||
|
L<App::Netdisco::AnyEvent::Nbtstat> implements the following methods.
|
||||||
|
|
||||||
|
=head2 C<nbtstat>
|
||||||
|
|
||||||
|
$request->nbtstat($ip, sub {
|
||||||
|
my $result = shift;
|
||||||
|
});
|
||||||
|
|
||||||
|
Perform a NetBIOS node status request of $ip.
|
||||||
|
|
||||||
|
=head1 SEE ALSO
|
||||||
|
|
||||||
|
L<AnyEvent>
|
||||||
|
|
||||||
|
=cut
|
||||||
@@ -41,8 +41,9 @@ setting('plugins')->{DBIC}->{daemon} = {
|
|||||||
schema_class => 'App::Netdisco::Daemon::DB',
|
schema_class => 'App::Netdisco::Daemon::DB',
|
||||||
};
|
};
|
||||||
|
|
||||||
# default queue model is Pg
|
# defaults for workers
|
||||||
setting('workers')->{queue} ||= 'PostgreSQL';
|
setting('workers')->{queue} ||= 'PostgreSQL';
|
||||||
|
setting('workers')->{interactives} ||= 1;
|
||||||
|
|
||||||
# force skipped DNS resolution, if unset
|
# force skipped DNS resolution, if unset
|
||||||
setting('dns')->{hosts_file} ||= '/etc/hosts';
|
setting('dns')->{hosts_file} ||= '/etc/hosts';
|
||||||
|
|||||||
@@ -146,6 +146,10 @@ sub store_device {
|
|||||||
scalar @aliases, $ENV{'PERL_ANYEVENT_MAX_OUTSTANDING_DNS'};
|
scalar @aliases, $ENV{'PERL_ANYEVENT_MAX_OUTSTANDING_DNS'};
|
||||||
my $resolved_aliases = hostnames_resolve_async(\@aliases);
|
my $resolved_aliases = hostnames_resolve_async(\@aliases);
|
||||||
|
|
||||||
|
# fake one aliases entry for devices not providing ip_index
|
||||||
|
push @$resolved_aliases, { alias => $device->ip, dns => $hostname }
|
||||||
|
if 0 == scalar @aliases;
|
||||||
|
|
||||||
# VTP Management Domain -- assume only one.
|
# VTP Management Domain -- assume only one.
|
||||||
my $vtpdomains = $snmp->vtp_d_name;
|
my $vtpdomains = $snmp->vtp_d_name;
|
||||||
my $vtpdomain;
|
my $vtpdomain;
|
||||||
@@ -682,7 +686,7 @@ sub store_neighbors {
|
|||||||
my $remote_ip = $c_ip->{$entry};
|
my $remote_ip = $c_ip->{$entry};
|
||||||
my $remote_ipad = NetAddr::IP::Lite->new($remote_ip);
|
my $remote_ipad = NetAddr::IP::Lite->new($remote_ip);
|
||||||
my $remote_port = undef;
|
my $remote_port = undef;
|
||||||
my $remote_type = $c_platform->{$entry} || '';
|
my $remote_type = Encode::decode('UTF-8', $c_platform->{$entry} || '');
|
||||||
my $remote_id = Encode::decode('UTF-8', $c_id->{$entry});
|
my $remote_id = Encode::decode('UTF-8', $c_id->{$entry});
|
||||||
my $remote_cap = $c_cap->{$entry} || [];
|
my $remote_cap = $c_cap->{$entry} || [];
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ use Dancer::Plugin::DBIC 'schema';
|
|||||||
|
|
||||||
use App::Netdisco::Util::Node 'check_mac';
|
use App::Netdisco::Util::Node 'check_mac';
|
||||||
use NetAddr::IP::Lite ':lower';
|
use NetAddr::IP::Lite ':lower';
|
||||||
use Net::NBName;
|
use App::Netdisco::AnyEvent::Nbtstat;
|
||||||
|
use Encode;
|
||||||
|
|
||||||
use base 'Exporter';
|
use base 'Exporter';
|
||||||
our @EXPORT = ();
|
our @EXPORT = ();
|
||||||
our @EXPORT_OK = qw/ do_nbtstat store_nbt /;
|
our @EXPORT_OK = qw/ nbtstat_resolve_async store_nbt /;
|
||||||
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||||
|
|
||||||
=head1 NAME
|
=head1 NAME
|
||||||
@@ -25,42 +26,64 @@ subroutines.
|
|||||||
|
|
||||||
=head1 EXPORT_OK
|
=head1 EXPORT_OK
|
||||||
|
|
||||||
=head2 do_nbtstat( $node )
|
=head2 nbtstat_resolve_async( $ips )
|
||||||
|
|
||||||
Connects to node and gets NetBIOS information. Then adds entries to
|
This method uses an asynchronous AnyEvent NetBIOS node status requester
|
||||||
node_nbt table.
|
C<App::Netdisco::AnyEvent::Nbtstat>.
|
||||||
|
|
||||||
Returns whether a node is answering netbios calls or not.
|
Given a reference to an array of hashes will connects to the C<IPv4> of a
|
||||||
|
node and gets NetBIOS node status information.
|
||||||
|
|
||||||
|
Returns the supplied reference to an array of hashes with MAC address,
|
||||||
|
NetBIOS name, NetBIOS domain/workgroup, NetBIOS user, and NetBIOS server
|
||||||
|
service status for addresses which responded.
|
||||||
|
|
||||||
=cut
|
=cut
|
||||||
|
|
||||||
sub do_nbtstat {
|
sub nbtstat_resolve_async {
|
||||||
my ($host, $now) = @_;
|
my $ips = shift;
|
||||||
my $ip = NetAddr::IP::Lite->new($host) or return;
|
|
||||||
|
|
||||||
unless ( $ip->version() == 4 ) {
|
my $timeout = setting('nbtstat_timeout') || 1;
|
||||||
debug ' nbtstat only supports IPv4, invalid ip %s', $ip->addr;
|
my $interval = setting('nbtstat_interval') || 0.02;
|
||||||
return;
|
|
||||||
|
my $stater = App::Netdisco::AnyEvent::Nbtstat->new(
|
||||||
|
timeout => $timeout,
|
||||||
|
interval => $interval
|
||||||
|
);
|
||||||
|
|
||||||
|
# Set up the condvar
|
||||||
|
my $cv = AE::cv;
|
||||||
|
$cv->begin( sub { shift->send } );
|
||||||
|
|
||||||
|
foreach my $hash_ref (@$ips) {
|
||||||
|
my $ip = $hash_ref->{'ip'};
|
||||||
|
$cv->begin;
|
||||||
|
$stater->nbtstat(
|
||||||
|
$ip,
|
||||||
|
sub {
|
||||||
|
my $res = shift;
|
||||||
|
_filter_nbname( $ip, $hash_ref, $res );
|
||||||
|
$cv->end;
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
my $nb = Net::NBName->new;
|
# Decrement the cv counter to cancel out the send declaration
|
||||||
my $ns = $nb->node_status( $ip->addr );
|
$cv->end;
|
||||||
|
|
||||||
# Check for NetBIOS Info
|
# Wait for the resolver to perform all resolutions
|
||||||
return unless $ns;
|
$cv->recv;
|
||||||
|
|
||||||
my $nbname = _filter_nbname( $ip->addr, $ns );
|
# Close sockets
|
||||||
|
undef $stater;
|
||||||
|
|
||||||
if ($nbname) {
|
return $ips;
|
||||||
store_nbt($nbname, $now);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# filter nbt names / information
|
# filter nbt names / information
|
||||||
sub _filter_nbname {
|
sub _filter_nbname {
|
||||||
my $ip = shift;
|
my $ip = shift;
|
||||||
|
my $hash_ref = shift;
|
||||||
my $node_status = shift;
|
my $node_status = shift;
|
||||||
|
|
||||||
my $server = 0;
|
my $server = 0;
|
||||||
@@ -68,10 +91,10 @@ sub _filter_nbname {
|
|||||||
my $domain = '';
|
my $domain = '';
|
||||||
my $nbuser = '';
|
my $nbuser = '';
|
||||||
|
|
||||||
for my $rr ( $node_status->names ) {
|
for my $rr ( @{$node_status->{'names'}} ) {
|
||||||
my $suffix = defined $rr->suffix ? $rr->suffix : -1;
|
my $suffix = defined $rr->{'suffix'} ? $rr->{'suffix'} : -1;
|
||||||
my $G = defined $rr->G ? $rr->G : '';
|
my $G = defined $rr->{'G'} ? $rr->{'G'} : '';
|
||||||
my $name = defined $rr->name ? $rr->name : '';
|
my $name = defined $rr->{'name'} ? $rr->{'name'} : '';
|
||||||
|
|
||||||
if ( $suffix == 0 and $G eq "GROUP" ) {
|
if ( $suffix == 0 and $G eq "GROUP" ) {
|
||||||
$domain = $name;
|
$domain = $name;
|
||||||
@@ -88,11 +111,11 @@ sub _filter_nbname {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unless ($nbname) {
|
unless ($nbname) {
|
||||||
debug ' nbtstat no computer name found for %s', $ip;
|
debug sprintf ' nbtstat no computer name found for %s', $ip;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $mac = $node_status->mac_address || '';
|
my $mac = $node_status->{'mac_address'} || '';
|
||||||
|
|
||||||
unless ( check_mac( $ip, $mac ) ) {
|
unless ( check_mac( $ip, $mac ) ) {
|
||||||
|
|
||||||
@@ -101,23 +124,23 @@ sub _filter_nbname {
|
|||||||
->single( { ip => $ip, -bool => 'active' } );
|
->single( { ip => $ip, -bool => 'active' } );
|
||||||
|
|
||||||
if ( !defined $node_ip ) {
|
if ( !defined $node_ip ) {
|
||||||
debug ' no MAC for %s returned by nbtstat or in DB', $ip;
|
debug sprintf ' no MAC for %s returned by nbtstat or in DB', $ip;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$mac = $node_ip->mac;
|
$mac = $node_ip->mac;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
$hash_ref->{'ip'} = $ip;
|
||||||
ip => $ip,
|
$hash_ref->{'mac'} = $mac;
|
||||||
mac => $mac,
|
$hash_ref->{'nbname'} = Encode::decode('UTF-8', $nbname);
|
||||||
nbname => $nbname,
|
$hash_ref->{'domain'} = Encode::decode('UTF-8', $domain);
|
||||||
domain => $domain,
|
$hash_ref->{'server'} = $server;
|
||||||
server => $server,
|
$hash_ref->{'nbuser'} = Encode::decode('UTF-8', $nbuser);
|
||||||
nbuser => $nbuser
|
|
||||||
};
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
=head2 store_nbt($nb_hash_ref, $now?)
|
=item store_nbt($nb_hash_ref, $now?)
|
||||||
|
|
||||||
Stores entries in C<node_nbt> table from the provided hash reference; MAC
|
Stores entries in C<node_nbt> table from the provided hash reference; MAC
|
||||||
C<mac>, IP C<ip>, Unique NetBIOS Node Name C<nbname>, NetBIOS Domain or
|
C<mac>, IP C<ip>, Unique NetBIOS Node Name C<nbname>, NetBIOS Domain or
|
||||||
|
|||||||
@@ -17,9 +17,8 @@ SELECT *
|
|||||||
FROM device
|
FROM device
|
||||||
WHERE dns IS NULL
|
WHERE dns IS NULL
|
||||||
OR name IS NULL
|
OR name IS NULL
|
||||||
OR lower(trim(TRAILING ?
|
OR regexp_replace(lower(dns), ? || '$', '')
|
||||||
FROM dns)::text) != lower(trim(TRAILING ?
|
!= regexp_replace(lower(name), ? || '$', '')
|
||||||
FROM name)::text)
|
|
||||||
ENDSQL
|
ENDSQL
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|||||||
49
Netdisco/lib/App/Netdisco/DB/Result/Virtual/NodeMonitor.pm
Normal file
49
Netdisco/lib/App/Netdisco/DB/Result/Virtual/NodeMonitor.pm
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package App::Netdisco::DB::Result::Virtual::NodeMonitor;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use utf8;
|
||||||
|
use base 'DBIx::Class::Core';
|
||||||
|
|
||||||
|
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||||
|
|
||||||
|
__PACKAGE__->table('node_monitor_virtual');
|
||||||
|
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||||
|
__PACKAGE__->result_source_instance->view_definition(<<'ENDSQL');
|
||||||
|
SELECT nm.why, nm.cc, trim(trailing '.' from trim(trailing '0123456789' from date::text)) as date,
|
||||||
|
n.mac, n.switch, n.port,
|
||||||
|
d.name, d.location,
|
||||||
|
dp.name AS portname
|
||||||
|
FROM node_monitor nm, node n, device d, device_port dp
|
||||||
|
WHERE nm.mac = n.mac
|
||||||
|
AND nm.active
|
||||||
|
AND nm.cc IS NOT NULL
|
||||||
|
AND d.ip = n.switch
|
||||||
|
AND dp.ip = n.switch
|
||||||
|
AND dp.port = n.port
|
||||||
|
AND d.last_macsuck = n.time_last
|
||||||
|
ENDSQL
|
||||||
|
|
||||||
|
__PACKAGE__->add_columns(
|
||||||
|
"why",
|
||||||
|
{ data_type => "text", is_nullable => 1 },
|
||||||
|
"cc",
|
||||||
|
{ data_type => "text", is_nullable => 0 },
|
||||||
|
"date",
|
||||||
|
{ data_type => "timestamp", is_nullable => 0 },
|
||||||
|
"mac",
|
||||||
|
{ data_type => "macaddr", is_nullable => 0 },
|
||||||
|
"switch",
|
||||||
|
{ data_type => "inet", is_nullable => 0 },
|
||||||
|
"port",
|
||||||
|
{ data_type => "text", is_nullable => 0 },
|
||||||
|
"name",
|
||||||
|
{ data_type => "text", is_nullable => 0 },
|
||||||
|
"location",
|
||||||
|
{ data_type => "text", is_nullable => 1 },
|
||||||
|
"portname",
|
||||||
|
{ data_type => "text", is_nullable => 0 },
|
||||||
|
);
|
||||||
|
|
||||||
|
1;
|
||||||
@@ -23,6 +23,10 @@ sub set_portcontrol {
|
|||||||
return job_error("Cannot alter port: $reconfig_check")
|
return job_error("Cannot alter port: $reconfig_check")
|
||||||
if $reconfig_check;
|
if $reconfig_check;
|
||||||
|
|
||||||
|
# need to remove "-other" which appears for power/portcontrol
|
||||||
|
(my $sa = $job->subaction) =~ s/-\w+//;
|
||||||
|
$job->subaction($sa);
|
||||||
|
|
||||||
return _set_port_generic($job, 'up_admin');
|
return _set_port_generic($job, 'up_admin');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +54,7 @@ sub _set_port_generic {
|
|||||||
|
|
||||||
my $ip = $job->device;
|
my $ip = $job->device;
|
||||||
my $pn = $job->port;
|
my $pn = $job->port;
|
||||||
(my $data = $job->subaction) =~ s/-\w+//;
|
my $data = $job->subaction;
|
||||||
|
|
||||||
my $port = get_port($ip, $pn)
|
my $port = get_port($ip, $pn)
|
||||||
or return job_error("Unknown port name [$pn] on device [$ip]");
|
or return job_error("Unknown port name [$pn] on device [$ip]");
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package App::Netdisco::Daemon::Worker::Poller::Nbtstat;
|
|||||||
use Dancer qw/:moose :syntax :script/;
|
use Dancer qw/:moose :syntax :script/;
|
||||||
use Dancer::Plugin::DBIC 'schema';
|
use Dancer::Plugin::DBIC 'schema';
|
||||||
|
|
||||||
use App::Netdisco::Core::Nbtstat 'do_nbtstat';
|
use App::Netdisco::Core::Nbtstat qw/nbtstat_resolve_async store_nbt/;
|
||||||
use App::Netdisco::Util::Node 'is_nbtstatable';
|
use App::Netdisco::Util::Node 'is_nbtstatable';
|
||||||
use App::Netdisco::Util::Device qw/get_device is_discoverable/;
|
use App::Netdisco::Util::Device qw/get_device is_discoverable/;
|
||||||
use App::Netdisco::Daemon::Util ':all';
|
use App::Netdisco::Daemon::Util ':all';
|
||||||
@@ -33,7 +33,7 @@ sub nbtstat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# get list of nodes on device
|
# get list of nodes on device
|
||||||
my $interval = (setting('nbt_max_age') || 7) . ' day';
|
my $interval = (setting('nbtstat_max_age') || 7) . ' day';
|
||||||
my $rs = schema('netdisco')->resultset('NodeIp')->search({
|
my $rs = schema('netdisco')->resultset('NodeIp')->search({
|
||||||
-bool => 'me.active',
|
-bool => 'me.active',
|
||||||
-bool => 'nodes.active',
|
-bool => 'nodes.active',
|
||||||
@@ -46,10 +46,25 @@ sub nbtstat {
|
|||||||
})->ip_version(4);
|
})->ip_version(4);
|
||||||
|
|
||||||
my @nodes = $rs->get_column('ip')->all;
|
my @nodes = $rs->get_column('ip')->all;
|
||||||
|
|
||||||
|
# Unless we have IP's don't bother
|
||||||
|
if (scalar @nodes) {
|
||||||
|
# filter exclusions from config
|
||||||
|
@nodes = grep { is_nbtstatable( $_ ) } @nodes;
|
||||||
|
|
||||||
|
# setup the hash nbtstat_resolve_async expects
|
||||||
|
my @ips = map {+{'ip' => $_}} @nodes;
|
||||||
my $now = 'to_timestamp('. (join '.', gettimeofday) .')';
|
my $now = 'to_timestamp('. (join '.', gettimeofday) .')';
|
||||||
|
|
||||||
$self->_single_node_body('nbtstat', $_, $now)
|
my $resolved_nodes = nbtstat_resolve_async(\@ips);
|
||||||
for @nodes;
|
|
||||||
|
# update node_nbt with status entries
|
||||||
|
foreach my $result (@$resolved_nodes) {
|
||||||
|
if (defined $result->{'nbname'}) {
|
||||||
|
store_nbt($result, $now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return job_done("Ended nbtstat for ". $host->addr);
|
return job_done("Ended nbtstat for ". $host->addr);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -681,6 +681,20 @@ Value: Number. Default: 7.
|
|||||||
The maximum age of a node in days for it to be checked for NetBIOS
|
The maximum age of a node in days for it to be checked for NetBIOS
|
||||||
information.
|
information.
|
||||||
|
|
||||||
|
=head3 C<nbtstat_interval>
|
||||||
|
|
||||||
|
Value: Number. Default: 0.02.
|
||||||
|
|
||||||
|
Interval between nbtstat requests in each poller. Defaults to 0.02 seconds,
|
||||||
|
equating to 50 requests per second per poller.
|
||||||
|
|
||||||
|
=head3 C<nbtstat_timeout>
|
||||||
|
|
||||||
|
Value: Number. Default: 1.
|
||||||
|
|
||||||
|
Seconds nbtstat will wait for a response before time out. Accepts fractional
|
||||||
|
seconds as well as integers.
|
||||||
|
|
||||||
=head3 C<expire_devices>
|
=head3 C<expire_devices>
|
||||||
|
|
||||||
Value: Number of Days.
|
Value: Number of Days.
|
||||||
@@ -782,6 +796,14 @@ field to use as the management IP address for a device.
|
|||||||
Value: Boolean. Default: C<true>.
|
Value: Boolean. Default: C<true>.
|
||||||
|
|
||||||
Set to false to prevent users from changing the default VLAN on an interface.
|
Set to false to prevent users from changing the default VLAN on an interface.
|
||||||
|
This setting has no effect when C<portctl_nameonly> below is set to true.
|
||||||
|
|
||||||
|
=head3 C<portctl_nameonly>
|
||||||
|
|
||||||
|
Value: Boolean. Default: C<false>.
|
||||||
|
|
||||||
|
Set to true to limit port control action to only changing the interface name
|
||||||
|
(description).
|
||||||
|
|
||||||
=head3 C<portctl_nophones>
|
=head3 C<portctl_nophones>
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,13 @@ use Net::DNS;
|
|||||||
use AnyEvent::DNS;
|
use AnyEvent::DNS;
|
||||||
use NetAddr::IP::Lite ':lower';
|
use NetAddr::IP::Lite ':lower';
|
||||||
|
|
||||||
|
use base 'Exporter';
|
||||||
|
our @EXPORT = ();
|
||||||
|
our @EXPORT_OK = qw/
|
||||||
|
hostname_from_ip hostnames_resolve_async ipv4_from_hostname
|
||||||
|
/;
|
||||||
|
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||||
|
|
||||||
# AE::DNS::EtcHosts only works for A/AAAA/SRV, but we want PTR.
|
# AE::DNS::EtcHosts only works for A/AAAA/SRV, but we want PTR.
|
||||||
# this loads+parses /etc/hosts file using AE. dirty hack.
|
# this loads+parses /etc/hosts file using AE. dirty hack.
|
||||||
use AnyEvent::Socket 'format_address';
|
use AnyEvent::Socket 'format_address';
|
||||||
@@ -15,12 +22,10 @@ use AnyEvent::DNS::EtcHosts;
|
|||||||
AnyEvent::DNS::EtcHosts::_load_hosts_unless(sub{},AE::cv);
|
AnyEvent::DNS::EtcHosts::_load_hosts_unless(sub{},AE::cv);
|
||||||
no AnyEvent::DNS::EtcHosts; # unimport
|
no AnyEvent::DNS::EtcHosts; # unimport
|
||||||
|
|
||||||
use base 'Exporter';
|
our %HOSTS = ();
|
||||||
our @EXPORT = ();
|
$HOSTS{$_} = [ map { [ $_ ? (format_address $_->[0]) : '' ] }
|
||||||
our @EXPORT_OK = qw/
|
@{$AnyEvent::DNS::EtcHosts::HOSTS{$_}} ]
|
||||||
hostname_from_ip hostnames_resolve_async ipv4_from_hostname
|
for keys %AnyEvent::DNS::EtcHosts::HOSTS;
|
||||||
/;
|
|
||||||
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
|
||||||
|
|
||||||
=head1 NAME
|
=head1 NAME
|
||||||
|
|
||||||
@@ -47,6 +52,13 @@ sub hostname_from_ip {
|
|||||||
my $ip = shift;
|
my $ip = shift;
|
||||||
return unless $ip;
|
return unless $ip;
|
||||||
|
|
||||||
|
# check /etc/hosts file and short-circuit if found
|
||||||
|
foreach my $name (reverse sort keys %HOSTS) {
|
||||||
|
if ($HOSTS{$name}->[0]->[0] eq $ip) {
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
my $res = Net::DNS::Resolver->new;
|
my $res = Net::DNS::Resolver->new;
|
||||||
my $query = $res->search($ip);
|
my $query = $res->search($ip);
|
||||||
|
|
||||||
@@ -72,6 +84,12 @@ sub ipv4_from_hostname {
|
|||||||
my $name = shift;
|
my $name = shift;
|
||||||
return unless $name;
|
return unless $name;
|
||||||
|
|
||||||
|
# check /etc/hosts file and short-circuit if found
|
||||||
|
if (exists $HOSTS{$name} and $HOSTS{$name}->[0]->[0]) {
|
||||||
|
my $ip = NetAddr::IP::Lite->new($HOSTS{$name}->[0]->[0]);
|
||||||
|
return $ip->addr if $ip and $ip->bits == 32;
|
||||||
|
}
|
||||||
|
|
||||||
my $res = Net::DNS::Resolver->new;
|
my $res = Net::DNS::Resolver->new;
|
||||||
my $query = $res->search($name);
|
my $query = $res->search($name);
|
||||||
|
|
||||||
@@ -92,9 +110,7 @@ resolver C<AnyEvent::DNS>.
|
|||||||
|
|
||||||
Given a reference to an array of hashes will resolve the C<IPv4> or C<IPv6>
|
Given a reference to an array of hashes will resolve the C<IPv4> or C<IPv6>
|
||||||
address in the C<ip> or C<alias> key of each hash into its hostname which
|
address in the C<ip> or C<alias> key of each hash into its hostname which
|
||||||
will be inserted in the C<dns> key of the hash. The resolver does also
|
will be inserted in the C<dns> key of the hash.
|
||||||
forward-lookups to verify that the resolved hostnames point to the
|
|
||||||
address.
|
|
||||||
|
|
||||||
Returns the supplied reference to an array of hashes with dns values for
|
Returns the supplied reference to an array of hashes with dns values for
|
||||||
addresses which resolved.
|
addresses which resolved.
|
||||||
@@ -103,12 +119,6 @@ addresses which resolved.
|
|||||||
|
|
||||||
sub hostnames_resolve_async {
|
sub hostnames_resolve_async {
|
||||||
my $ips = shift;
|
my $ips = shift;
|
||||||
my $resolver = AnyEvent::DNS->new();
|
|
||||||
|
|
||||||
my %HOSTS = ();
|
|
||||||
$HOSTS{$_} = [ map { [ $_ ? (format_address $_->[0]) : '' ] }
|
|
||||||
@{$AnyEvent::DNS::EtcHosts::HOSTS{$_}} ]
|
|
||||||
for keys %AnyEvent::DNS::EtcHosts::HOSTS;
|
|
||||||
|
|
||||||
# Set up the condvar
|
# Set up the condvar
|
||||||
my $done = AE::cv;
|
my $done = AE::cv;
|
||||||
@@ -137,6 +147,9 @@ sub hostnames_resolve_async {
|
|||||||
# Wait for the resolver to perform all resolutions
|
# Wait for the resolver to perform all resolutions
|
||||||
$done->recv;
|
$done->recv;
|
||||||
|
|
||||||
|
# Remove reference to resolver so that we close sockets
|
||||||
|
undef $AnyEvent::DNS::RESOLVER if $AnyEvent::DNS::RESOLVER;
|
||||||
|
|
||||||
return $ips;
|
return $ips;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
479
Netdisco/lib/App/Netdisco/Util/Graph.pm
Normal file
479
Netdisco/lib/App/Netdisco/Util/Graph.pm
Normal file
@@ -0,0 +1,479 @@
|
|||||||
|
package App::Netdisco::Util::Graph;
|
||||||
|
|
||||||
|
use App::Netdisco;
|
||||||
|
|
||||||
|
use Dancer qw/:syntax :script/;
|
||||||
|
use Dancer::Plugin::DBIC 'schema';
|
||||||
|
|
||||||
|
use App::Netdisco::Util::DNS qw/hostname_from_ip ipv4_from_hostname/;
|
||||||
|
use Graph::Undirected ();
|
||||||
|
use GraphViz ();
|
||||||
|
|
||||||
|
use base 'Exporter';
|
||||||
|
our @EXPORT = ('graph');
|
||||||
|
our @EXPORT_OK = qw/
|
||||||
|
graph_each
|
||||||
|
graph_addnode
|
||||||
|
make_graph
|
||||||
|
/;
|
||||||
|
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||||
|
|
||||||
|
# nothing to see here, please move along...
|
||||||
|
our ($ip, $label, $isdev, $devloc, %GRAPH, %GRAPH_SPEED);
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
App::Netdisco::Util::Graph
|
||||||
|
|
||||||
|
=head1 SYNOPSIS
|
||||||
|
|
||||||
|
$ brew install graphviz <-- install graphviz on your system
|
||||||
|
|
||||||
|
$ ~/bin/localenv bash
|
||||||
|
$ cpanm --notest Graph GraphViz
|
||||||
|
$ mkdir ~/graph
|
||||||
|
|
||||||
|
use App::Netdisco::Util::Graph;
|
||||||
|
graph;
|
||||||
|
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
|
Generate GraphViz output from Netdisco data. Requires that the L<Graph> and
|
||||||
|
L<GraphViz> distributions be installed.
|
||||||
|
|
||||||
|
Requires the same config as for Netdisco 1, but within a C<graph> key. See
|
||||||
|
C<share/config.yml> in the source distribution for an example.
|
||||||
|
|
||||||
|
The C<graph> subroutine is exported by default. The C<:all> tag will export
|
||||||
|
all subroutines.
|
||||||
|
|
||||||
|
=head1 EXPORT
|
||||||
|
|
||||||
|
=over 4
|
||||||
|
|
||||||
|
=item graph()
|
||||||
|
|
||||||
|
Creates netmap of network.
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub graph {
|
||||||
|
my %CONFIG = %{ setting('graph') };
|
||||||
|
|
||||||
|
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
|
||||||
|
my $month = sprintf("%d%02d",$year+1900,$mon+1);
|
||||||
|
|
||||||
|
info "graph() - Creating Graphs";
|
||||||
|
my $G = make_graph();
|
||||||
|
|
||||||
|
unless (defined $G){
|
||||||
|
print "graph() - make_graph() failed. Try running with debug (-D).";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my @S = $G->connected_components;
|
||||||
|
|
||||||
|
# Count number of nodes in each subgraph
|
||||||
|
my %S_count;
|
||||||
|
for (my $i=0;$i< scalar @S;$i++){
|
||||||
|
$S_count{$i} = scalar @{$S[$i]};
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach my $subgraph (sort { $S_count{$b} <=> $S_count{$a} } keys %S_count){
|
||||||
|
my $SUBG = $G->copy;
|
||||||
|
print "\$S[$subgraph] has $S_count{$subgraph} nodes.\n";
|
||||||
|
|
||||||
|
# Remove other subgraphs from this one
|
||||||
|
my %S_notme = %S_count;
|
||||||
|
delete $S_notme{$subgraph};
|
||||||
|
foreach my $other (keys %S_notme){
|
||||||
|
print "Removing Non-connected nodes: ",join(',',@{$S[$other]}),"\n";
|
||||||
|
$SUBG->delete_vertices(@{$S[$other]})
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create the subgraph
|
||||||
|
my $timeout = defined $CONFIG{graph_timeout} ? $CONFIG{graph_timeout} : 60;
|
||||||
|
|
||||||
|
eval {
|
||||||
|
alarm($timeout*60);
|
||||||
|
graph_each($SUBG,'');
|
||||||
|
alarm(0);
|
||||||
|
};
|
||||||
|
if ($@) {
|
||||||
|
if ($@ =~ /timeout/){
|
||||||
|
print "! Creating Graph timed out!\n";
|
||||||
|
} else {
|
||||||
|
print "\n$@\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Facility to create subgraph for each non-connected network segment.
|
||||||
|
# Right now, let's just make the biggest one only.
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
=head1 EXPORT_OK
|
||||||
|
|
||||||
|
=over 4
|
||||||
|
|
||||||
|
=item graph_each($graph_obj, $name)
|
||||||
|
|
||||||
|
Generates subgraph. Does actual GraphViz calls.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub graph_each {
|
||||||
|
my ($G, $name) = @_;
|
||||||
|
my %CONFIG = %{ setting('graph') };
|
||||||
|
info "Creating new Graph";
|
||||||
|
|
||||||
|
my $graph_defs = {
|
||||||
|
'bgcolor' => $CONFIG{graph_bg} || 'black',
|
||||||
|
'color' => $CONFIG{graph_color} || 'white',
|
||||||
|
'overlap' => $CONFIG{graph_overlap} || 'scale',
|
||||||
|
'fontpath'=> _homepath('graph_fontpath',''),
|
||||||
|
'ranksep' => $CONFIG{graph_ranksep} || 0.3,
|
||||||
|
'nodesep' => $CONFIG{graph_nodesep} || 2,
|
||||||
|
'ratio' => $CONFIG{graph_ratio} || 'compress',
|
||||||
|
'splines' => ($CONFIG{graph_splines} ? 'true' : 'false'),
|
||||||
|
'fontcolor' => $CONFIG{node_fontcolor} || 'white',
|
||||||
|
'fontname' => $CONFIG{node_font} || 'lucon',
|
||||||
|
'fontsize' => $CONFIG{node_fontsize} || 12,
|
||||||
|
};
|
||||||
|
my $edge_defs = {
|
||||||
|
'color' => $CONFIG{edge_color} || 'wheat',
|
||||||
|
};
|
||||||
|
my $node_defs = {
|
||||||
|
'shape' => $CONFIG{node_shape} || 'box',
|
||||||
|
'fillcolor' => $CONFIG{node_fillcolor} || 'dimgrey',
|
||||||
|
'fontcolor' => $CONFIG{node_fontcolor} || 'white',
|
||||||
|
'style' => $CONFIG{node_style} || 'filled',
|
||||||
|
'fontname' => $CONFIG{node_font} || 'lucon',
|
||||||
|
'fontsize' => $CONFIG{node_fontsize} || 12,
|
||||||
|
'fixedsize' => ($CONFIG{node_fixedsize} ? 'true' : 'false'),
|
||||||
|
};
|
||||||
|
$node_defs->{height} = $CONFIG{node_height} if defined $CONFIG{node_height};
|
||||||
|
$node_defs->{width} = $CONFIG{node_width} if defined $CONFIG{node_width};
|
||||||
|
|
||||||
|
my $epsilon = undef;
|
||||||
|
if (defined $CONFIG{graph_epsilon}){
|
||||||
|
$epsilon = "0." . '0' x $CONFIG{graph_epsilon} . '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
my %gv = (
|
||||||
|
directed => 0,
|
||||||
|
layout => $CONFIG{graph_layout} || 'twopi',
|
||||||
|
graph => $graph_defs,
|
||||||
|
node => $node_defs,
|
||||||
|
edge => $edge_defs,
|
||||||
|
width => $CONFIG{graph_x} || 30,
|
||||||
|
height => $CONFIG{graph_y} || 30,
|
||||||
|
epsilon => $epsilon,
|
||||||
|
);
|
||||||
|
|
||||||
|
my $gv = GraphViz->new(%gv);
|
||||||
|
|
||||||
|
my %node_map = ();
|
||||||
|
my @nodes = $G->vertices;
|
||||||
|
|
||||||
|
foreach my $dev (@nodes){
|
||||||
|
my $node_name = graph_addnode($gv,$dev);
|
||||||
|
$node_map{$dev} = $node_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $root_ip = defined $CONFIG{root_device}
|
||||||
|
? (ipv4_from_hostname($CONFIG{root_device}) || $CONFIG{root_device})
|
||||||
|
: undef;
|
||||||
|
|
||||||
|
if (defined $root_ip and defined $node_map{$root_ip}){
|
||||||
|
my $gv_root_name = $gv->_quote_name($root_ip);
|
||||||
|
if (defined $gv_root_name){
|
||||||
|
$gv->{GRAPH_ATTRS}->{root}=$gv_root_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
my @edges = $G->edges;
|
||||||
|
|
||||||
|
while (my $e = shift @edges){
|
||||||
|
my $link = $e->[0];
|
||||||
|
my $dest = $e->[1];
|
||||||
|
my $speed = $GRAPH_SPEED{$link}->{$dest}->{speed};
|
||||||
|
|
||||||
|
if (!defined($speed)) {
|
||||||
|
info " ! No link speed for $link -> $dest";
|
||||||
|
$speed = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
my %edge = ();
|
||||||
|
my $val = ''; my $suffix = '';
|
||||||
|
|
||||||
|
if ($speed =~ /^([\d.]+)\s+([a-z])bps$/i) {
|
||||||
|
$val = $1; $suffix = $2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ($suffix eq 'k') or ($speed =~ m/(t1|ds3)/i) ){
|
||||||
|
$edge{color} = 'green';
|
||||||
|
$edge{style} = 'dotted';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($suffix eq 'M'){
|
||||||
|
if ($val < 10.0){
|
||||||
|
$edge{color} = 'green';
|
||||||
|
#$edge{style} = 'dotted';
|
||||||
|
$edge{style} = 'dashed';
|
||||||
|
} elsif ($val < 100.0){
|
||||||
|
$edge{color} = '#8b7e66';
|
||||||
|
#$edge{style} = 'normal';
|
||||||
|
$edge{style} = 'solid';
|
||||||
|
} else {
|
||||||
|
$edge{color} = '#ffe7ba';
|
||||||
|
$edge{style} = 'solid';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($suffix eq 'G'){
|
||||||
|
#$edge{style} = 'bold';
|
||||||
|
$edge{color} = 'cyan1';
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add extra styles to edges (mainly for modifying width)
|
||||||
|
if(defined $CONFIG{edge_style}) {
|
||||||
|
$edge{style} .= "," . $CONFIG{edge_style};
|
||||||
|
}
|
||||||
|
|
||||||
|
$gv->add_edge($link => $dest, %edge );
|
||||||
|
}
|
||||||
|
|
||||||
|
info "Ignore all warnings about node size";
|
||||||
|
|
||||||
|
if (defined $CONFIG{graph_raw} and $CONFIG{graph_raw}){
|
||||||
|
my $graph_raw = _homepath('graph_raw');
|
||||||
|
info " Creating raw graph: $graph_raw";
|
||||||
|
$gv->as_canon($graph_raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defined $CONFIG{graph} and $CONFIG{graph}){
|
||||||
|
my $graph_gif = _homepath('graph');
|
||||||
|
info " Creating graph: $graph_gif";
|
||||||
|
$gv->as_gif($graph_gif);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defined $CONFIG{graph_png} and $CONFIG{graph_png}){
|
||||||
|
my $graph_png = _homepath('graph_png');
|
||||||
|
info " Creating png graph: $graph_png";
|
||||||
|
$gv->as_png($graph_png);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defined $CONFIG{graph_map} and $CONFIG{graph_map}){
|
||||||
|
my $graph_map = _homepath('graph_map');
|
||||||
|
info " Creating CMAP : $graph_map";
|
||||||
|
$gv->as_cmap($graph_map);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defined $CONFIG{graph_svg} and $CONFIG{graph_svg}){
|
||||||
|
my $graph_svg = _homepath('graph_svg');
|
||||||
|
info " Creating SVG : $graph_svg";
|
||||||
|
$gv->as_svg($graph_svg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
=item graph_addnode($graphviz_obj, $node_ip)
|
||||||
|
|
||||||
|
Checks for mapping settings in config file and adds node to the GraphViz
|
||||||
|
object.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub graph_addnode {
|
||||||
|
my $gv = shift;
|
||||||
|
my %CONFIG = %{ setting('graph') };
|
||||||
|
my %node = ();
|
||||||
|
|
||||||
|
$ip = shift;
|
||||||
|
$label = $GRAPH{$ip}->{dns};
|
||||||
|
$isdev = $GRAPH{$ip}->{isdev};
|
||||||
|
$devloc = $GRAPH{$ip}->{location};
|
||||||
|
|
||||||
|
$label = "($ip)" unless defined $label;
|
||||||
|
my $domain_suffix = setting('domain_suffix') || '';
|
||||||
|
$label =~ s/$domain_suffix$//;
|
||||||
|
$node{label} = $label;
|
||||||
|
|
||||||
|
# Dereferencing the scalar by name below
|
||||||
|
# requires that the variable be non-lexical (not my)
|
||||||
|
# we'll create some local non-lexical versions
|
||||||
|
# that will expire at the end of this block
|
||||||
|
# Node Mappings
|
||||||
|
foreach my $map (@{ $CONFIG{'node_map'} || [] }){
|
||||||
|
my ($var, $regex, $attr, $val) = split(':', $map);
|
||||||
|
|
||||||
|
{ no strict 'refs';
|
||||||
|
$var = ${"$var"};
|
||||||
|
}
|
||||||
|
next unless defined $var;
|
||||||
|
|
||||||
|
if ($var =~ /$regex/) {
|
||||||
|
debug " graph_addnode - Giving node $ip $attr = $val";
|
||||||
|
$node{$attr} = $val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# URL for image maps FIXME for non-root hosting
|
||||||
|
if ($isdev) {
|
||||||
|
$node{URL} = "/device?&q=$ip";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$node{URL} = "/search?tab=node&q=$ip";
|
||||||
|
# Overrides any colors given to nodes above. Bug 1094208
|
||||||
|
$node{fillcolor} = $CONFIG{'node_problem'} || 'red';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($CONFIG{'graph_clusters'} && $devloc) {
|
||||||
|
# This odd construct works around a bug in GraphViz.pm's
|
||||||
|
# quoting of cluster names. If it has a name with spaces,
|
||||||
|
# it'll just quote it, resulting in creating a subgraph name
|
||||||
|
# of cluster_"location with spaces". This is an illegal name
|
||||||
|
# according to the dot grammar, so if the name matches the
|
||||||
|
# problematic regexp we make GraphViz.pm generate an internal
|
||||||
|
# name by using a leading space in the name.
|
||||||
|
#
|
||||||
|
# This is bug ID 16912 at rt.cpan.org -
|
||||||
|
# http://rt.cpan.org/NoAuth/Bug.html?id=16912
|
||||||
|
#
|
||||||
|
# Another bug, ID 11514, prevents us from using a combination
|
||||||
|
# of name and label attributes to hide the extra space from
|
||||||
|
# the user. However, since it's just a space, hopefully it
|
||||||
|
# won't be too noticable.
|
||||||
|
my($loc) = $devloc;
|
||||||
|
$loc = " " . $loc if ($loc =~ /^[a-zA-Z](\w| )*$/);
|
||||||
|
$node{cluster} = { name => $loc };
|
||||||
|
}
|
||||||
|
|
||||||
|
my $rv = $gv->add_node($ip, %node);
|
||||||
|
return $rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
=item make_graph()
|
||||||
|
|
||||||
|
Returns C<Graph::Undirected> object that represents the discovered network.
|
||||||
|
|
||||||
|
Graph is made by loading all the C<device_port> entries that have a neighbor,
|
||||||
|
using them as edges. Then each device seen in those entries is added as a
|
||||||
|
vertex.
|
||||||
|
|
||||||
|
Nodes without topology information are not included.
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub make_graph {
|
||||||
|
my $G = Graph::Undirected->new();
|
||||||
|
|
||||||
|
my $devices = schema('netdisco')->resultset('Device')
|
||||||
|
->search({}, { columns => [qw/ip dns location /] });
|
||||||
|
my $links = schema('netdisco')->resultset('DevicePort')
|
||||||
|
->search({remote_ip => { -not => undef }},
|
||||||
|
{ columns => [qw/ip remote_ip speed remote_type/]});
|
||||||
|
my %aliases = map {$_->alias => $_->ip}
|
||||||
|
schema('netdisco')->resultset('DeviceIp')
|
||||||
|
->search({}, { columns => [qw/ip alias/] })->all;
|
||||||
|
|
||||||
|
my %devs = ( map {($_->ip => $_->dns)} $devices->all );
|
||||||
|
my %locs = ( map {($_->ip => $_->location)} $devices->all );
|
||||||
|
|
||||||
|
# Check for no topology info
|
||||||
|
unless ($links->count > 0) {
|
||||||
|
debug "make_graph() - No topology information. skipping.";
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
my %link_seen = ();
|
||||||
|
my %linkmap = ();
|
||||||
|
|
||||||
|
while (my $link = $links->next) {
|
||||||
|
my $source = $link->ip;
|
||||||
|
my $dest = $link->remote_ip;
|
||||||
|
my $speed = $link->speed;
|
||||||
|
my $type = $link->remote_type;
|
||||||
|
|
||||||
|
# Check for Aliases
|
||||||
|
if (defined $aliases{$dest}) {
|
||||||
|
# Set to root device
|
||||||
|
$dest = $aliases{$dest};
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove loopback - After alias check (bbaetz)
|
||||||
|
if ($source eq $dest) {
|
||||||
|
debug " make_graph() - Loopback on $source";
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Skip IP Phones
|
||||||
|
if (defined $type and $type =~ /ip.phone/i) {
|
||||||
|
debug " make_graph() - Skipping IP Phone. $source -> $dest ($type)";
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
next if exists $link_seen{$source}->{$dest};
|
||||||
|
|
||||||
|
push(@{ $linkmap{$source} }, $dest);
|
||||||
|
|
||||||
|
# take care of reverse too
|
||||||
|
$link_seen{$source}->{$dest}++;
|
||||||
|
$link_seen{$dest}->{$source}++;
|
||||||
|
|
||||||
|
$GRAPH_SPEED{$source}->{$dest}->{speed}=$speed;
|
||||||
|
$GRAPH_SPEED{$dest}->{$source}->{speed}=$speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach my $link (keys %linkmap) {
|
||||||
|
foreach my $dest (@{ $linkmap{$link} }) {
|
||||||
|
|
||||||
|
foreach my $side ($link, $dest) {
|
||||||
|
unless (defined $GRAPH{$side}) {
|
||||||
|
my $is_dev = exists $devs{$side};
|
||||||
|
my $dns = $is_dev ?
|
||||||
|
$devs{$side} :
|
||||||
|
hostname_from_ip($side);
|
||||||
|
|
||||||
|
# Default to IP if no dns
|
||||||
|
$dns = defined $dns ? $dns : "($side)";
|
||||||
|
|
||||||
|
$G->add_vertex($side);
|
||||||
|
debug " make_graph() - add_vertex('$side')";
|
||||||
|
|
||||||
|
$GRAPH{$side}->{dns} = $dns;
|
||||||
|
$GRAPH{$side}->{isdev} = $is_dev;
|
||||||
|
$GRAPH{$side}->{seen}++;
|
||||||
|
$GRAPH{$side}->{location} = $locs{$side};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$G->add_edge($link,$dest);
|
||||||
|
debug " make_graph - add_edge('$link','$dest')";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $G;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _homepath {
|
||||||
|
my ($path, $default) = @_;
|
||||||
|
|
||||||
|
my $home = $ENV{NETDISCO_HOME};
|
||||||
|
my $item = setting('graph')->{$path} || $default;
|
||||||
|
return undef unless defined($item);
|
||||||
|
|
||||||
|
if ($item =~ m,^/,) {
|
||||||
|
return $item;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$home =~ s,/*$,,;
|
||||||
|
return $home . "/" . $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
@@ -37,7 +37,7 @@ database storage.
|
|||||||
|
|
||||||
Returns false, and might log a debug level message, if the checks fail.
|
Returns false, and might log a debug level message, if the checks fail.
|
||||||
|
|
||||||
Returns a true value if these checks pass:
|
Returns a true value (the MAC address in IEEE format) if these checks pass:
|
||||||
|
|
||||||
=over 4
|
=over 4
|
||||||
|
|
||||||
@@ -67,12 +67,13 @@ MAC address does not belong to an interface on any known Device
|
|||||||
sub check_mac {
|
sub check_mac {
|
||||||
my ($device, $node, $port_macs) = @_;
|
my ($device, $node, $port_macs) = @_;
|
||||||
my $mac = Net::MAC->new(mac => $node, 'die' => 0, verbose => 0);
|
my $mac = Net::MAC->new(mac => $node, 'die' => 0, verbose => 0);
|
||||||
|
my $devip = (ref $device ? $device->ip : '');
|
||||||
$port_macs ||= {};
|
$port_macs ||= {};
|
||||||
|
|
||||||
# incomplete MAC addresses (BayRS frame relay DLCI, etc)
|
# incomplete MAC addresses (BayRS frame relay DLCI, etc)
|
||||||
if ($mac->get_error) {
|
if ($mac->get_error) {
|
||||||
debug sprintf ' [%s] check_mac - mac [%s] malformed - skipping',
|
debug sprintf ' [%s] check_mac - mac [%s] malformed - skipping',
|
||||||
$device->ip, $node;
|
$devip, $node;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -92,32 +93,32 @@ sub check_mac {
|
|||||||
# multicast
|
# multicast
|
||||||
if ($node =~ m/^[0-9a-f](?:1|3|5|7|9|b|d|f):/) {
|
if ($node =~ m/^[0-9a-f](?:1|3|5|7|9|b|d|f):/) {
|
||||||
debug sprintf ' [%s] check_mac - multicast mac [%s] - skipping',
|
debug sprintf ' [%s] check_mac - multicast mac [%s] - skipping',
|
||||||
$device->ip, $node;
|
$devip, $node;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
# VRRP
|
# VRRP
|
||||||
if (index($node, '00:00:5e:00:01:') == 0) {
|
if (index($node, '00:00:5e:00:01:') == 0) {
|
||||||
debug sprintf ' [%s] check_mac - VRRP mac [%s] - skipping',
|
debug sprintf ' [%s] check_mac - VRRP mac [%s] - skipping',
|
||||||
$device->ip, $node;
|
$devip, $node;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
# HSRP
|
# HSRP
|
||||||
if (index($node, '00:00:0c:07:ac:') == 0) {
|
if (index($node, '00:00:0c:07:ac:') == 0) {
|
||||||
debug sprintf ' [%s] check_mac - HSRP mac [%s] - skipping',
|
debug sprintf ' [%s] check_mac - HSRP mac [%s] - skipping',
|
||||||
$device->ip, $node;
|
$devip, $node;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
# device's own MACs
|
# device's own MACs
|
||||||
if (exists $port_macs->{$node}) {
|
if ($port_macs and exists $port_macs->{$node}) {
|
||||||
debug sprintf ' [%s] check_mac - mac [%s] is device port - skipping',
|
debug sprintf ' [%s] check_mac - mac [%s] is device port - skipping',
|
||||||
$device->ip, $node;
|
$devip, $node;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return $node;
|
||||||
}
|
}
|
||||||
|
|
||||||
=head2 check_node_no( $ip, $setting_name )
|
=head2 check_node_no( $ip, $setting_name )
|
||||||
@@ -221,11 +222,9 @@ Returns false if the host is not permitted to nbtstat the target node.
|
|||||||
sub is_nbtstatable {
|
sub is_nbtstatable {
|
||||||
my $ip = shift;
|
my $ip = shift;
|
||||||
|
|
||||||
return _bail_msg("is_nbtstatable: node matched nbtstat_no")
|
return if check_node_no($ip, 'nbtstat_no');
|
||||||
if check_node_no($ip, 'nbtstat_no');
|
|
||||||
|
|
||||||
return _bail_msg("is_nbtstatable: node failed to match nbtstat_only")
|
return unless check_node_only($ip, 'nbtstat_only');
|
||||||
unless check_node_only($ip, 'nbtstat_only');
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
52
Netdisco/lib/App/Netdisco/Util/NodeMonitor.pm
Normal file
52
Netdisco/lib/App/Netdisco/Util/NodeMonitor.pm
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package App::Netdisco::Util::NodeMonitor;
|
||||||
|
|
||||||
|
use App::Netdisco;
|
||||||
|
|
||||||
|
use Dancer qw/:syntax :script/;
|
||||||
|
use Dancer::Plugin::DBIC 'schema';
|
||||||
|
|
||||||
|
use App::Netdisco::Util::DNS qw/hostname_from_ip ipv4_from_hostname/;
|
||||||
|
|
||||||
|
use base 'Exporter';
|
||||||
|
our @EXPORT_OK = qw/
|
||||||
|
monitor
|
||||||
|
/;
|
||||||
|
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||||
|
|
||||||
|
sub _email {
|
||||||
|
my ($to, $subject, $body) = @_;
|
||||||
|
my $domain = setting('domain_suffix') || 'localhost';
|
||||||
|
$domain =~ s/^\.//;
|
||||||
|
|
||||||
|
my $SENDMAIL = '/usr/sbin/sendmail';
|
||||||
|
open (SENDMAIL, "| $SENDMAIL -t") or die "Can't open sendmail at $SENDMAIL.\n";
|
||||||
|
print SENDMAIL "To: $to\n";
|
||||||
|
print SENDMAIL "From: Netdisco <netdisco\@$domain>\n";
|
||||||
|
print SENDMAIL "Subject: $subject\n\n";
|
||||||
|
print SENDMAIL $body;
|
||||||
|
close (SENDMAIL) or die "Can't send letter. $!\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub monitor {
|
||||||
|
my $monitor = schema('netdisco')->resultset('Virtual::NodeMonitor');
|
||||||
|
|
||||||
|
while (my $entry = $monitor->next) {
|
||||||
|
my $body = <<"end_body";
|
||||||
|
........ n e t d i s c o .........
|
||||||
|
Node : @{[$entry->mac]} (@{[$entry->why]})
|
||||||
|
When : @{[$entry->date]}
|
||||||
|
Switch : @{[$entry->name]} (@{[$entry->switch]})
|
||||||
|
Port : @{[$entry->port]} (@{[$entry->portname]})
|
||||||
|
Location: @{[$entry->location]}
|
||||||
|
|
||||||
|
end_body
|
||||||
|
|
||||||
|
_email(
|
||||||
|
$entry->cc,
|
||||||
|
"Saw mac @{[$entry->mac]} (@{[$entry->why]}) on @{[$entry->name]} @{[$entry->port]}",
|
||||||
|
$body
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
@@ -68,6 +68,10 @@ sub vlan_reconfig_check {
|
|||||||
|
|
||||||
=item *
|
=item *
|
||||||
|
|
||||||
|
Permission check that C<portctl_nameonly> is false in Netdisco config.
|
||||||
|
|
||||||
|
=item *
|
||||||
|
|
||||||
Permission check that C<portctl_uplinks> is true in Netdisco config, if
|
Permission check that C<portctl_uplinks> is true in Netdisco config, if
|
||||||
C<$port> is an uplink.
|
C<$port> is an uplink.
|
||||||
|
|
||||||
@@ -95,6 +99,10 @@ sub port_reconfig_check {
|
|||||||
my $has_phone = port_has_phone($port);
|
my $has_phone = port_has_phone($port);
|
||||||
my $is_vlan = is_vlan_interface($port);
|
my $is_vlan = is_vlan_interface($port);
|
||||||
|
|
||||||
|
# only permitted to change interface name
|
||||||
|
return "forbidden: not permitted to change port configuration"
|
||||||
|
if setting('portctl_nameonly');
|
||||||
|
|
||||||
# uplink check
|
# uplink check
|
||||||
return "forbidden: port [$name] on [$ip] is an uplink"
|
return "forbidden: port [$name] on [$ip] is an uplink"
|
||||||
if $port->remote_type and not $has_phone and not setting('portctl_uplinks');
|
if $port->remote_type and not $has_phone and not setting('portctl_uplinks');
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package App::Netdisco::Web::Plugin::AdminTask::NodeMonitor;
|
||||||
|
|
||||||
|
use Dancer ':syntax';
|
||||||
|
use Dancer::Plugin::Ajax;
|
||||||
|
use Dancer::Plugin::DBIC;
|
||||||
|
use Dancer::Plugin::Auth::Extensible;
|
||||||
|
|
||||||
|
use App::Netdisco::Web::Plugin;
|
||||||
|
use App::Netdisco::Util::Node 'check_mac';
|
||||||
|
|
||||||
|
register_admin_task({
|
||||||
|
tag => 'nodemonitor',
|
||||||
|
label => 'Node Monitor',
|
||||||
|
});
|
||||||
|
|
||||||
|
sub _sanity_ok {
|
||||||
|
return 0 unless param('mac')
|
||||||
|
and check_mac(undef, param('mac'));
|
||||||
|
|
||||||
|
params->{mac} = check_mac(undef, param('mac'));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ajax '/ajax/control/admin/nodemonitor/add' => require_role admin => sub {
|
||||||
|
send_error('Bad Request', 400) unless _sanity_ok();
|
||||||
|
|
||||||
|
schema('netdisco')->txn_do(sub {
|
||||||
|
my $monitor = schema('netdisco')->resultset('NodeMonitor')
|
||||||
|
->create({
|
||||||
|
mac => param('mac'),
|
||||||
|
active => (param('active') ? \'true' : \'false'),
|
||||||
|
why => param('why'),
|
||||||
|
cc => param('cc'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ajax '/ajax/control/admin/nodemonitor/del' => require_role admin => sub {
|
||||||
|
send_error('Bad Request', 400) unless _sanity_ok();
|
||||||
|
|
||||||
|
schema('netdisco')->txn_do(sub {
|
||||||
|
schema('netdisco')->resultset('NodeMonitor')
|
||||||
|
->find({mac => param('mac')})->delete;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ajax '/ajax/control/admin/nodemonitor/update' => require_role admin => sub {
|
||||||
|
send_error('Bad Request', 400) unless _sanity_ok();
|
||||||
|
|
||||||
|
schema('netdisco')->txn_do(sub {
|
||||||
|
my $monitor = schema('netdisco')->resultset('NodeMonitor')
|
||||||
|
->find({mac => param('mac')});
|
||||||
|
return unless $monitor;
|
||||||
|
|
||||||
|
$monitor->update({
|
||||||
|
mac => param('mac'),
|
||||||
|
active => (param('active') ? \'true' : \'false'),
|
||||||
|
why => param('why'),
|
||||||
|
cc => param('cc'),
|
||||||
|
date => \'now()',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ajax '/ajax/content/admin/nodemonitor' => require_role admin => sub {
|
||||||
|
my $set = schema('netdisco')->resultset('NodeMonitor')
|
||||||
|
->search(undef, { order_by => [qw/active date mac/] });
|
||||||
|
|
||||||
|
content_type('text/html');
|
||||||
|
template 'ajax/admintask/nodemonitor.tt', {
|
||||||
|
results => $set,
|
||||||
|
}, { layout => undef };
|
||||||
|
};
|
||||||
|
|
||||||
|
true;
|
||||||
@@ -119,6 +119,8 @@ arpnip_min_age: 0
|
|||||||
nbtstat_no: []
|
nbtstat_no: []
|
||||||
nbtstat_only: []
|
nbtstat_only: []
|
||||||
nbtstat_max_age: 7
|
nbtstat_max_age: 7
|
||||||
|
nbtstat_interval: 0.02
|
||||||
|
nbtstat_timeout: 1
|
||||||
expire_devices: 0
|
expire_devices: 0
|
||||||
expire_nodes: 0
|
expire_nodes: 0
|
||||||
expire_nodes_archive: 0
|
expire_nodes_archive: 0
|
||||||
@@ -144,6 +146,7 @@ ignore_interfaces:
|
|||||||
ignore_private_nets: false
|
ignore_private_nets: false
|
||||||
reverse_sysname: false
|
reverse_sysname: false
|
||||||
vlanctl: true
|
vlanctl: true
|
||||||
|
portctl_nameonly: false
|
||||||
portctl_nophones: false
|
portctl_nophones: false
|
||||||
portctl_vlans: false
|
portctl_vlans: false
|
||||||
portctl_uplinks: false
|
portctl_uplinks: false
|
||||||
@@ -165,7 +168,7 @@ port_control_reasons:
|
|||||||
# --------------
|
# --------------
|
||||||
|
|
||||||
workers:
|
workers:
|
||||||
interactives: 2
|
interactives: 1
|
||||||
pollers: 10
|
pollers: 10
|
||||||
sleep_time: 2
|
sleep_time: 2
|
||||||
queue: PostgreSQL
|
queue: PostgreSQL
|
||||||
@@ -210,6 +213,50 @@ job_type_keys:
|
|||||||
Poller: pollers
|
Poller: pollers
|
||||||
Interactive: interactives
|
Interactive: interactives
|
||||||
|
|
||||||
|
# ---------------
|
||||||
|
# GraphViz Export
|
||||||
|
# ---------------
|
||||||
|
|
||||||
|
graph:
|
||||||
|
# ---- Graph Settings ----
|
||||||
|
edge_color : wheat
|
||||||
|
|
||||||
|
graph : 'graph/netmap.gif'
|
||||||
|
graph_png : 'graph/netmap.png'
|
||||||
|
graph_bg : black
|
||||||
|
graph_clusters : false # try fdp layout
|
||||||
|
graph_color : white
|
||||||
|
graph_default : png
|
||||||
|
#graph_dir : net_dir.gif
|
||||||
|
graph_epsilon : 6
|
||||||
|
graph_layout : twopi # try neato or fdp too
|
||||||
|
graph_map : 'graph/netmap.map'
|
||||||
|
graph_overlap : scale
|
||||||
|
graph_nodesep : 2
|
||||||
|
graph_ranksep : .3
|
||||||
|
graph_raw : 'graph/graph_raw.dot'
|
||||||
|
graph_splines : false
|
||||||
|
graph_svg : 'graph/netmap.svg'
|
||||||
|
graph_timeout : 90
|
||||||
|
graph_x : 30
|
||||||
|
graph_y : 30
|
||||||
|
|
||||||
|
node_fillcolor : dimgrey
|
||||||
|
node_font : lucon
|
||||||
|
node_fontsize : 46.0
|
||||||
|
node_fontcolor : white
|
||||||
|
node_problem : red
|
||||||
|
node_shape : box
|
||||||
|
node_style : filled
|
||||||
|
#edge_style : setlinewidth(10)
|
||||||
|
|
||||||
|
# ---- Node Maps ----
|
||||||
|
# variable:matching pattern:node attribute:attribute value:key:key name
|
||||||
|
#node_map:
|
||||||
|
# - 'label:cat(?!-g):fillcolor:blue:cat:Blue Box - Catalyst Device'
|
||||||
|
# - 'label:-g:fillcolor:darkgreen:dev-g:Green Box - Gateway / Router'
|
||||||
|
# - 'ip:^192.168\.:color:yellow:dev:Yellow Border - ResNet'
|
||||||
|
|
||||||
# ---------------
|
# ---------------
|
||||||
# DANCER INTERNAL
|
# DANCER INTERNAL
|
||||||
# ---------------
|
# ---------------
|
||||||
|
|||||||
92
Netdisco/share/views/ajax/admintask/nodemonitor.tt
Normal file
92
Netdisco/share/views/ajax/admintask/nodemonitor.tt
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
<table id="data-table" class="table table-striped table-bordered" width="100%" cellspacing="0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="nd_center-cell">Date Added</th>
|
||||||
|
<th class="nd_center-cell">MAC Address</th>
|
||||||
|
<th class="nd_center-cell">Enabled</th>
|
||||||
|
<th class="nd_center-cell">Reason</th>
|
||||||
|
<th class="nd_center-cell">Email</th>
|
||||||
|
<th class="nd_center-cell">Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="nd_center-cell"></td>
|
||||||
|
<td class="nd_center-cell"><input data-form="add" name="mac" type="text"></td>
|
||||||
|
<td class="nd_center-cell"><input data-form="add" name="active" type="checkbox" checked></td>
|
||||||
|
<td class="nd_center-cell"><input data-form="add" name="why" type="text"></td>
|
||||||
|
<td class="nd_center-cell"><input data-form="add" name="cc" type="email"></td>
|
||||||
|
<td class="nd_center-cell">
|
||||||
|
<button class="btn btn-small nd_adminbutton" name="add" type="submit"><i class="icon-plus-sign"></i> Add</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
[% SET count = 0 %]
|
||||||
|
[% WHILE (row = results.next) %]
|
||||||
|
[% SET count = count + 1 %]
|
||||||
|
<tr>
|
||||||
|
<td class="nd_center-cell">[% row.date | html_entity %]</td>
|
||||||
|
<td class="nd_center-cell">
|
||||||
|
<input data-form="update" name="mac" type="text" value="[% row.mac | html_entity %]">
|
||||||
|
</td>
|
||||||
|
<td class="nd_center-cell">
|
||||||
|
<input data-form="update" name="active" type="checkbox" [% 'checked="checked"' IF row.active %]>
|
||||||
|
</td>
|
||||||
|
<td class="nd_center-cell">
|
||||||
|
<input data-form="update" name="why" type="text" value="[% row.why | html_entity %]">
|
||||||
|
</td>
|
||||||
|
<td class="nd_center-cell">
|
||||||
|
<input data-form="update" name="cc" type="email" value="[% row.cc | html_entity %]">
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="nd_center-cell">
|
||||||
|
<button class="btn nd_adminbutton" name="update" type="submit"><i class="icon-save text-warning"></i></button>
|
||||||
|
|
||||||
|
<button class="btn" data-toggle="modal"
|
||||||
|
data-target="#nd_devdel-[% count %]" type="button"><i class="icon-trash text-error"></i></button>
|
||||||
|
|
||||||
|
<div id="nd_devdel-[% count %]" class="nd_modal nd_deep-horizon modal hide fade" tabindex="-1"
|
||||||
|
role="dialog" aria-labelledby="nd_devdel-label-[% count %]" aria-hidden="true">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
|
||||||
|
<h3 id="nd_devdel-label-[% count %]">Are you sure?</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<blockquote>
|
||||||
|
<p class="text-info">Monitor for "[% row.mac | html_entity %]" will be removed.</p>
|
||||||
|
</blockquote>
|
||||||
|
<input data-form="del" name="mac" type="hidden" value="[% row.mac | html_entity %]">
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-success" data-dismiss="modal" aria-hidden="true">Cancel</button>
|
||||||
|
<button class="btn btn-danger nd_adminbutton" name="del" data-dismiss="modal">Confirm</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
[% END %]
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#data-table').dataTable({
|
||||||
|
"stateSave": true,
|
||||||
|
"pageLength": [% settings.table_pagesize %],
|
||||||
|
"language": {
|
||||||
|
"search": 'Filter records: '
|
||||||
|
},
|
||||||
|
"columnDefs": [
|
||||||
|
{
|
||||||
|
"targets": [ 0, 2, 5 ],
|
||||||
|
"searchable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"targets": [ 0, 2, 5 ],
|
||||||
|
"orderable": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
</script>
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ $(document).ready(function() {
|
|||||||
},
|
},
|
||||||
"serverSide": true,
|
"serverSide": true,
|
||||||
"order": [[ 0, "desc" ]],
|
"order": [[ 0, "desc" ]],
|
||||||
"ajax": "/ajax/control/admin/userlog/data",
|
"ajax": "[% uri_for('/ajax/control/admin/userlog/data') %]",
|
||||||
"columns": [{
|
"columns": [{
|
||||||
"data": 'creation',
|
"data": 'creation',
|
||||||
"className": "nd_center-cell",
|
"className": "nd_center-cell",
|
||||||
|
|||||||
@@ -60,12 +60,14 @@
|
|||||||
[% IF user_can_port_control AND params.c_admin %]
|
[% IF user_can_port_control AND params.c_admin %]
|
||||||
[% IF row.up_admin == 'up' %]
|
[% IF row.up_admin == 'up' %]
|
||||||
<td nowrap class="nd_editable-cell" data-action="down"
|
<td nowrap class="nd_editable-cell" data-action="down"
|
||||||
|
data-order="[% row.port | html_entity %]" data-filter="[% row.port | html_entity %]"
|
||||||
data-field="c_port" data-for-device="[% device.ip | html_entity %]" data-for-port="[% row.port | html_entity %]">
|
data-field="c_port" data-for-device="[% device.ip | html_entity %]" data-for-port="[% row.port | html_entity %]">
|
||||||
<i class="icon-hand-down nd_hand-icon"
|
<i class="icon-hand-down nd_hand-icon"
|
||||||
rel="tooltip" data-placement="top" data-offset="3"
|
rel="tooltip" data-placement="top" data-offset="3"
|
||||||
data-animation="" data-title="Click to Disable"></i>
|
data-animation="" data-title="Click to Disable"></i>
|
||||||
[% ELSE %]
|
[% ELSE %]
|
||||||
<td nowrap class="nd_editable-cell" data-action="up"
|
<td nowrap class="nd_editable-cell" data-action="up"
|
||||||
|
data-order="[% row.port | html_entity %]" data-filter="[% row.port | html_entity %]"
|
||||||
data-field="c_port" data-for-device="[% device.ip | html_entity %]" data-for-port="[% row.port | html_entity %]">
|
data-field="c_port" data-for-device="[% device.ip | html_entity %]" data-for-port="[% row.port | html_entity %]">
|
||||||
<i class="icon-hand-up nd_hand-icon"
|
<i class="icon-hand-up nd_hand-icon"
|
||||||
rel="tooltip" data-placement="top" data-offset="3"
|
rel="tooltip" data-placement="top" data-offset="3"
|
||||||
@@ -78,7 +80,7 @@
|
|||||||
data-animation="" data-title="View Port Log"></i>
|
data-animation="" data-title="View Port Log"></i>
|
||||||
</a>
|
</a>
|
||||||
[% ELSE %]
|
[% ELSE %]
|
||||||
<td nowrap>
|
<td nowrap data-order="[% row.port | html_entity %]" data-filter="[% row.port | html_entity %]">
|
||||||
[% END %]
|
[% END %]
|
||||||
<a class="nd_this-port-only nd_port-only-first" href="[% uri_for('/device',
|
<a class="nd_this-port-only nd_port-only-first" href="[% uri_for('/device',
|
||||||
self_options) %]&q=[% params.q | uri %]&f=[% row.port | uri %]&prefer=port">
|
self_options) %]&q=[% params.q | uri %]&f=[% row.port | uri %]&prefer=port">
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ $(document).ready(function() {
|
|||||||
"search": 'Filter records: '
|
"search": 'Filter records: '
|
||||||
},
|
},
|
||||||
"serverSide": true,
|
"serverSide": true,
|
||||||
"ajax": '/ajax/content/report/apradiochannelpower/data',
|
"ajax": "[% uri_for('/ajax/content/report/apradiochannelpower/data') %]",
|
||||||
"order": [[ 0, 'asc' ]],
|
"order": [[ 0, 'asc' ]],
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ $(document).ready(function() {
|
|||||||
{
|
{
|
||||||
"data": 'ip',
|
"data": 'ip',
|
||||||
"render": function(data, type, row, meta) {
|
"render": function(data, type, row, meta) {
|
||||||
return '<a search_device %]&q=' + encodeURIComponent(data) + '">' + he.encode(row.ip || row.name) + '</a>';
|
return '<a href="[% search_device %]&q=' + encodeURIComponent(data) + '">' + he.encode(row.name || row.ip) + '</a>';
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"data": 'dns',
|
"data": 'dns',
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ $(document).ready(function() {
|
|||||||
"search": 'Filter records: '
|
"search": 'Filter records: '
|
||||||
},
|
},
|
||||||
"serverSide": true,
|
"serverSide": true,
|
||||||
"ajax": '/ajax/content/report/devicepoestatus/data',
|
"ajax": "[% uri_for('/ajax/content/report/devicepoestatus/data') %]",
|
||||||
"order": [[ 0, 'asc' ]],
|
"order": [[ 0, 'asc' ]],
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,12 +25,16 @@ $(document).ready(function() {
|
|||||||
"data": 'ip',
|
"data": 'ip',
|
||||||
"render": function(data, type, row, meta) {
|
"render": function(data, type, row, meta) {
|
||||||
var cell_str = he.encode(data);
|
var cell_str = he.encode(data);
|
||||||
|
if (type == 'display') {
|
||||||
if (row.time_last && row.node) {
|
if (row.time_last && row.node) {
|
||||||
cell_str = '<a href="[% search_node %]&q=' + encodeURIComponent(data) + (row.active ? '' : '&archived=on') + '">' + he.encode(data) + (row.active ? '' : ' <i class="icon-book text-warning"></i> ') + '</a>';
|
cell_str = '<a href="[% search_node %]&q=' + encodeURIComponent(data)
|
||||||
|
+ (row.active ? '' : '&archived=on') + '">' + he.encode(data)
|
||||||
|
+ (row.active ? '' : ' <i class="icon-book text-warning"></i> ') + '</a>';
|
||||||
}
|
}
|
||||||
else if (row.time_last) {
|
else if (row.time_last) {
|
||||||
cell_str = '<a href="[% search_device %]&q=' + encodeURIComponent(data) + '">' + he.encode(data) + '</a>';
|
cell_str = '<a href="[% search_device %]&q=' + encodeURIComponent(data) + '">' + he.encode(data) + '</a>';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return cell_str;
|
return cell_str;
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ $(document).ready(function() {
|
|||||||
"serverSide": true,
|
"serverSide": true,
|
||||||
"searching": false,
|
"searching": false,
|
||||||
"order": [[ 0, "desc" ]],
|
"order": [[ 0, "desc" ]],
|
||||||
"ajax": '/ajax/content/report/moduleinventory/data?[% url(params('query').hash) %]',
|
"ajax": "[% uri_for('/ajax/content/report/moduleinventory/data') %]?[% url(params('query').hash) %]",
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"data": 'ip',
|
"data": 'ip',
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ $(document).ready(function() {
|
|||||||
[% IF opt %]
|
[% IF opt %]
|
||||||
"serverSide": true,
|
"serverSide": true,
|
||||||
"order": [[ 0, "desc" ]],
|
"order": [[ 0, "desc" ]],
|
||||||
"ajax": '/ajax/content/report/netbios/data?[% url(params('query').hash) %]',
|
"ajax": "[% uri_for('/ajax/content/report/netbios/data') %]?[% url(params('query').hash) %]",
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"data": 'domain',
|
"data": 'domain',
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ $(document).ready(function() {
|
|||||||
[% IF opt %]
|
[% IF opt %]
|
||||||
"serverSide": true,
|
"serverSide": true,
|
||||||
"order": [[ 0, "desc" ]],
|
"order": [[ 0, "desc" ]],
|
||||||
"ajax": '/ajax/content/report/nodevendor/data?[% url(params('query').hash) %]',
|
"ajax": "[% uri_for('/ajax/content/report/nodevendor/data') %]?[% url(params('query').hash) %]",
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"data": 'mac',
|
"data": 'mac',
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ $(document).ready(function() {
|
|||||||
}, {
|
}, {
|
||||||
"data": 'port_vlans.vlan',
|
"data": 'port_vlans.vlan',
|
||||||
"render": function(data, type, row, meta) {
|
"render": function(data, type, row, meta) {
|
||||||
return data;
|
return data || '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -13,17 +13,21 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
[% FOREACH platform IN models.all %]
|
[% FOREACH platform IN models.all %]
|
||||||
[% NEXT UNLESS platform.vendor AND platform.model %]
|
[% NEXT UNLESS platform.vendor OR platform.model %]
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
|
[% IF platform.vendor %]
|
||||||
<a class="nd_linkcell"
|
<a class="nd_linkcell"
|
||||||
href="[% search_device %]&q=[% platform.vendor | uri %]&vendor=[% platform.vendor | uri %]">
|
href="[% search_device %]&q=[% platform.vendor | uri %]&vendor=[% platform.vendor | uri %]">
|
||||||
[% platform.vendor | html_entity %]</a>
|
[% platform.vendor | html_entity %]</a>
|
||||||
|
[% ELSE %]unknown[% END %]
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
|
[% IF platform.model %]
|
||||||
<a class="nd_linkcell"
|
<a class="nd_linkcell"
|
||||||
href="[% search_device %]&q=[% platform.model | uri %]&model=[% platform.model | uri %]">
|
href="[% search_device %]&q=[% platform.model | uri %]&model=[% platform.model | uri %]">
|
||||||
[% platform.model | html_entity %]</a>
|
[% platform.model | html_entity %]</a>
|
||||||
|
[% ELSE %]unknown[% END %]
|
||||||
</th>
|
</th>
|
||||||
<th>[% platform.get_column('count') | html_entity %]</th>
|
<th>[% platform.get_column('count') | html_entity %]</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -43,13 +47,15 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
[% FOREACH release IN releases.all %]
|
[% FOREACH release IN releases.all %]
|
||||||
[% NEXT UNLESS (release.os AND release.os_ver) %]
|
[% NEXT UNLESS (release.os OR release.os_ver) %]
|
||||||
<tr>
|
<tr>
|
||||||
<th>[% release.os | html_entity %]</th>
|
<th>[% release.os || 'unknown' | html_entity %]</th>
|
||||||
<th>
|
<th>
|
||||||
|
[% IF release.os_ver %]
|
||||||
<a class="nd_linkcell"
|
<a class="nd_linkcell"
|
||||||
href="[% search_device %]&q=[% release.os_ver | uri %]&os_ver=[% release.os_ver | uri %]">
|
href="[% search_device %]&q=[% release.os_ver | uri %]&os_ver=[% release.os_ver | uri %]">
|
||||||
[% release.os_ver | html_entity %]</a>
|
[% release.os_ver | html_entity %]</a>
|
||||||
|
[% ELSE %]unknown[% END %]
|
||||||
</th>
|
</th>
|
||||||
<th>[% release.get_column('count') | html_entity %]</th>
|
<th>[% release.get_column('count') | html_entity %]</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
2.004000 - 2014-07-13
|
||||||
|
|
||||||
|
* Added by_hostname config option support
|
||||||
|
|
||||||
2.003002 - 2014-05-03
|
2.003002 - 2014-05-03
|
||||||
|
|
||||||
* POD fixups
|
* POD fixups
|
||||||
|
|||||||
@@ -24,4 +24,4 @@ resources:
|
|||||||
homepage: http://netdisco.org/
|
homepage: http://netdisco.org/
|
||||||
license: http://opensource.org/licenses/bsd-license.php
|
license: http://opensource.org/licenses/bsd-license.php
|
||||||
repository: git://git.code.sf.net/p/netdisco/netdisco-ng
|
repository: git://git.code.sf.net/p/netdisco/netdisco-ng
|
||||||
version: 2.003002
|
version: 2.004000
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package App::NetdiscoX::Web::Plugin::RANCID;
|
package App::NetdiscoX::Web::Plugin::RANCID;
|
||||||
|
|
||||||
our $VERSION = '2.003002';
|
our $VERSION = '2.004000';
|
||||||
|
|
||||||
use Dancer ':syntax';
|
use Dancer ':syntax';
|
||||||
|
|
||||||
@@ -25,6 +25,7 @@ hook 'before_template' => sub {
|
|||||||
my $config = config;
|
my $config = config;
|
||||||
my $tokens = shift;
|
my $tokens = shift;
|
||||||
my $device = $tokens->{d};
|
my $device = $tokens->{d};
|
||||||
|
my $domain_suffix = setting('domain_suffix') || '';
|
||||||
|
|
||||||
# defaults
|
# defaults
|
||||||
$tokens->{rancidgroup} = '';
|
$tokens->{rancidgroup} = '';
|
||||||
@@ -35,12 +36,15 @@ hook 'before_template' => sub {
|
|||||||
|
|
||||||
$rancid->{groups} ||= {};
|
$rancid->{groups} ||= {};
|
||||||
$rancid->{by_ip} ||= [];
|
$rancid->{by_ip} ||= [];
|
||||||
|
$rancid->{by_hostname} ||= [];
|
||||||
|
|
||||||
foreach my $g (keys %{ $rancid->{groups} }) {
|
foreach my $g (keys %{ $rancid->{groups} }) {
|
||||||
if (check_acl( get_device($device->{ip}), $rancid->{groups}->{$g} )) {
|
if (check_acl( get_device($device->{ip}), $rancid->{groups}->{$g} )) {
|
||||||
$tokens->{rancidgroup} = $g;
|
$tokens->{rancidgroup} = $g;
|
||||||
$tokens->{ranciddevice} = $device->{ip}
|
$tokens->{ranciddevice} = $device->{ip}
|
||||||
if 0 < scalar grep {$_ eq $g} @{ $rancid->{by_ip} };
|
if 0 < scalar grep {$_ eq $g} @{ $rancid->{by_ip} };
|
||||||
|
$tokens->{ranciddevice} =~ s/$domain_suffix$//
|
||||||
|
if 0 < scalar grep {$_ eq $g} @{ $rancid->{by_hostname} };
|
||||||
last;
|
last;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,6 +94,7 @@ L<netdisco-rancid-export>, an example of which is below:
|
|||||||
|
|
||||||
rancid:
|
rancid:
|
||||||
by_ip: [ other ]
|
by_ip: [ other ]
|
||||||
|
by_hostname: [ other2 ]
|
||||||
groups:
|
groups:
|
||||||
switch: [ 'name:.*[Ss][Ww].*' ]
|
switch: [ 'name:.*[Ss][Ww].*' ]
|
||||||
rtr: [ 'name:[rR]tr.*' ]
|
rtr: [ 'name:[rR]tr.*' ]
|
||||||
@@ -102,7 +107,8 @@ regular expression as in the above example.
|
|||||||
|
|
||||||
The device DNS name is used, or if missing the device SNMP sysName. Adding the
|
The device DNS name is used, or if missing the device SNMP sysName. Adding the
|
||||||
group to the list in C<by_ip> will make the link include the device IP
|
group to the list in C<by_ip> will make the link include the device IP
|
||||||
instead of the name.
|
instead of the name. Adding the group to the list in C<by_hostname> will
|
||||||
|
use the device FQDN minus the C<domain_suffix> config item (i.e. the hostname).
|
||||||
|
|
||||||
=head2 open_in_same_window
|
=head2 open_in_same_window
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user