relocate repo files so ND2 is the only code
This commit is contained in:
325
lib/App/Netdisco.pm
Normal file
325
lib/App/Netdisco.pm
Normal file
@@ -0,0 +1,325 @@
|
||||
package App::Netdisco;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.010_000;
|
||||
|
||||
our $VERSION = '2.034003';
|
||||
use App::Netdisco::Configuration;
|
||||
|
||||
use Module::Find ();
|
||||
Module::Find::usesub 'App::NetdiscoE::Init';
|
||||
|
||||
=head1 NAME
|
||||
|
||||
App::Netdisco - An open source web-based network management tool.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Netdisco is a web-based network management tool designed for network
|
||||
administrators. Data is collected into a PostgreSQL database using SNMP.
|
||||
|
||||
Some of the things you can do with Netdisco:
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
B<Locate> a machine on the network by MAC or IP and show the switch port it
|
||||
lives at
|
||||
|
||||
=item *
|
||||
|
||||
B<Turn off> a switch port, or change the VLAN or PoE status of a port
|
||||
|
||||
=item *
|
||||
|
||||
B<Inventory> your network hardware by model, vendor, software and operating
|
||||
system
|
||||
|
||||
=item *
|
||||
|
||||
B<Pretty pictures> of your network
|
||||
|
||||
=back
|
||||
|
||||
L<App::Netdisco> provides a web frontend with built-in web server, and a
|
||||
backend daemon to handle interactive requests such as changing port or device
|
||||
properties.
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
See the demo at: L<http://netdisco2-demo.herokuapp.com/>
|
||||
|
||||
=back
|
||||
|
||||
If you have any trouble getting installed or running, check out the
|
||||
L<Deployment|App::Netdisco::Manual::Deployment> and
|
||||
L<Troubleshooting|App::Netdisco::Manual::Troubleshooting> notes, or speak to
|
||||
someone in the C<#netdisco@freenode> IRC channel, or on the L<community email
|
||||
list|https://lists.sourceforge.net/lists/listinfo/netdisco-users>. Before
|
||||
installing or upgrading please always review the latest L<Release
|
||||
Notes|App::Netdisco::Manual::ReleaseNotes>.
|
||||
|
||||
=head1 Dependencies
|
||||
|
||||
Netdisco has several Perl library dependencies which will be automatically
|
||||
installed. However it's I<strongly> recommended that you first install
|
||||
L<DBD::Pg>, L<SNMP>, and a compiler using your operating system packages.
|
||||
|
||||
On Ubuntu/Debian:
|
||||
|
||||
root:~# apt-get install libdbd-pg-perl libsnmp-perl build-essential
|
||||
|
||||
On Fedora/Red-Hat:
|
||||
|
||||
root:~# yum install perl-core perl-DBD-Pg net-snmp-perl net-snmp-devel make automake gcc
|
||||
|
||||
On BSD systems please see L<our BSD tips|App::Netdisco::Manual::BSDInstall>.
|
||||
|
||||
With those installed, please check that your system's clock is correct.
|
||||
|
||||
Create a user on your system called C<netdisco> if one does not already exist.
|
||||
We'll install Netdisco and its dependencies into this user's home area, which
|
||||
will take about 250MB including MIB files.
|
||||
|
||||
root:~# useradd -m -p x -s /bin/bash netdisco
|
||||
|
||||
Netdisco uses the PostgreSQL database server. Install PostgreSQL (at least
|
||||
version 8.4) and then change to the PostgreSQL superuser (usually
|
||||
C<postgres>). Create a new database and PostgreSQL user for the Netdisco
|
||||
application:
|
||||
|
||||
root:~# su - postgres
|
||||
|
||||
postgres:~$ createuser -DRSP netdisco
|
||||
Enter password for new role:
|
||||
Enter it again:
|
||||
|
||||
postgres:~$ createdb -O netdisco netdisco
|
||||
|
||||
The default PostgreSQL configuration isn't well tuned for modern server
|
||||
hardware. We strongly recommend that you use the C<pgtune> Python program to
|
||||
auto-tune your C<postgresql.conf> file:
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
L<https://github.com/elitwin/pgtune>
|
||||
|
||||
=back
|
||||
|
||||
=head1 Installation
|
||||
|
||||
The following is a general guide which works well in most circumstances. It
|
||||
assumes you have a user C<netdisco> on your system, that you want to perform
|
||||
an on-line installation, and have the application run self-contained from
|
||||
within that user's home. There are alternatives: see the
|
||||
L<Deployment|App::Netdisco::Manual::Deployment> documentation for further
|
||||
details.
|
||||
|
||||
To avoid muddying your system, use the following script to download and
|
||||
install Netdisco and its dependencies into the C<netdisco> user's home area
|
||||
(C<~netdisco/perl5>):
|
||||
|
||||
su - netdisco
|
||||
curl -L http://cpanmin.us/ | perl - --notest --local-lib ~/perl5 App::Netdisco
|
||||
|
||||
Link some of the newly installed apps into a handy location:
|
||||
|
||||
mkdir ~/bin
|
||||
ln -s ~/perl5/bin/{localenv,netdisco-*} ~/bin/
|
||||
|
||||
Test the installation by running the following command, which should only
|
||||
produce a status message (it's just a test - you'll start the daemon properly,
|
||||
later on):
|
||||
|
||||
~/bin/netdisco-daemon status
|
||||
|
||||
=head1 Configuration
|
||||
|
||||
Make a directory for your local configuration and copy the configuration
|
||||
template from this distribution:
|
||||
|
||||
mkdir ~/environments
|
||||
cp ~/perl5/lib/perl5/auto/share/dist/App-Netdisco/environments/deployment.yml ~/environments
|
||||
chmod 600 ~/environments/deployment.yml
|
||||
|
||||
Edit the file ("C<~/environments/deployment.yml>") and change the database
|
||||
connection parameters to match those for your local system (that is, the
|
||||
C<name>, C<user> and C<pass>).
|
||||
|
||||
In the same file uncomment and edit the C<domain_suffix> setting to be
|
||||
appropriate for your local site.
|
||||
|
||||
Change the C<community> string setting if your site has different values, and
|
||||
uncomment the C<schedule> setting to enable SNMP data gathering from
|
||||
devices (this replaces cron jobs in Netdisco 1).
|
||||
|
||||
Have a quick read of the other settings to make sure you're happy, then move
|
||||
on. See L<Configuration|App::Netdisco::Manual::Configuration> for further
|
||||
details.
|
||||
|
||||
=head1 Bootstrap
|
||||
|
||||
The database either needs configuring if new, or updating from the current
|
||||
release of Netdisco (1.x). You also need vendor MAC address prefixes (OUI
|
||||
data) and some MIBs if you want to run the daemon. The following script will
|
||||
take care of all this for you:
|
||||
|
||||
~/bin/netdisco-deploy
|
||||
|
||||
If this is a new installation of Netdisco 2, answer yes to all questions. If
|
||||
you wish to deploy without Internet access, see the
|
||||
L<Deployment|App::Netdisco::Manual::Deployment> documentation.
|
||||
|
||||
=head1 Startup
|
||||
|
||||
Run the following command to start the web-app server as a backgrounded daemon
|
||||
(listening on port 5000):
|
||||
|
||||
~/bin/netdisco-web start
|
||||
|
||||
Run the following command to start the job control daemon (port control, etc):
|
||||
|
||||
~/bin/netdisco-daemon start
|
||||
|
||||
You should take care not to run this Netdisco daemon and the Netdisco 1.x
|
||||
daemon at the same time. Similarly, if you use the device discovery with
|
||||
Netdisco 2, disable your system's cron jobs for the Netdisco 1.x poller.
|
||||
|
||||
For further documentation on deployment, see
|
||||
L<Deployment|App::Netdisco::Manual::Deployment>. If you think Netdisco isn't
|
||||
behaving correctly, see also the
|
||||
L<Troubleshooting|App::Netdisco::Manual::Troubleshooting> page.
|
||||
|
||||
=head1 Upgrading from 2.x
|
||||
|
||||
If you're running a version of Netdisco prior to 2.x then you should follow
|
||||
the full installation instructions, above. This process is for upgrading
|
||||
version 2.x only.
|
||||
|
||||
Before upgrading please review the latest L<Release
|
||||
Notes|App::Netdisco::Manual::ReleaseNotes>. Then, the process is as follows:
|
||||
|
||||
# upgrade Netdisco
|
||||
~/bin/localenv cpanm --notest App::Netdisco
|
||||
|
||||
# apply database schema updates
|
||||
~/bin/netdisco-deploy
|
||||
|
||||
# restart web service
|
||||
~/bin/netdisco-web restart
|
||||
|
||||
# restart job daemon (if you use it)
|
||||
~/bin/netdisco-daemon restart
|
||||
|
||||
=head1 Tips and Tricks
|
||||
|
||||
=head2 Searching
|
||||
|
||||
The main black navigation bar has a search box which is smart enough to work
|
||||
out what you're looking for in most cases. For example device names, node IP
|
||||
or MAC addreses, VLAN numbers, and so on.
|
||||
|
||||
=head2 Command-Line Device and Port Actions
|
||||
|
||||
Most significant Device jobs and Port actions, as well as several
|
||||
troubleshooting and housekeeping duties, can be performed at the command-ling
|
||||
with the L<netdisco-do> program. For example:
|
||||
|
||||
~/bin/netdisco-do -D discover -d 192.0.2.1
|
||||
|
||||
See the L<netdisco-do documentation|netdisco-do> for further details.
|
||||
|
||||
=head2 Import Topology
|
||||
|
||||
Netdisco 1.x had support for a topology information file to fill in device
|
||||
port relations which could not be discovered. This is now stored in the
|
||||
database (and edited in the web interface). To import a legacy topology file,
|
||||
run:
|
||||
|
||||
~/bin/localenv nd-import-topology /path/to/netdisco-topology.txt
|
||||
|
||||
=head2 Database API
|
||||
|
||||
Bundled with this distribution is a L<DBIx::Class> layer for the Netdisco
|
||||
database. This abstracts away all the SQL into an elegant, re-usable OO
|
||||
interface. See the L<Developer|App::Netdisco::Manual::Developing>
|
||||
documentation for further information.
|
||||
|
||||
=head2 Plugins
|
||||
|
||||
Netdisco includes a Plugin subsystem for customizing the web user interface.
|
||||
See L<App::Netdisco::Web::Plugin> for further information.
|
||||
|
||||
=head2 Developing
|
||||
|
||||
Lots of information about the architecture of this application is contained
|
||||
within the L<Developer|App::Netdisco::Manual::Developing> documentation.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Oliver Gorwits <oliver@cpan.org>
|
||||
|
||||
=head1 CONTRIBUTORS
|
||||
|
||||
Netdisco was created at the University of California, Santa Cruz (UCSC),
|
||||
Networking and Technology Services (NTS) department. UCSC continues to support
|
||||
the development of Netdisco by providing development servers and beer.
|
||||
|
||||
Original development by Max Baker, with significant contributions from Mark
|
||||
Boolootian and Jim Warner (through whose ideas Netdisco was born and shaped),
|
||||
Bill Fenner, Jeroen van Ingen, Eric Miller, Carlos Vicente, and Brian de Wolf.
|
||||
|
||||
Other contributions (large and small) by Mike Hunter (UCB), Brian Wilson
|
||||
(NCSU), Bradley Baetz (bbaetz), David Temkin (sig.com), Edson Manners (FSU),
|
||||
Dmitry Sergienko (Trifle Co, .ua), Remo Rickli (PSI, Switzerland),
|
||||
Jean-Philippe Luiggi (sagem.com), A.L.M Buxey (Loughborough University, UK),
|
||||
Kevin Cheek (UMICH), John Bigrow (bnl.gov), George Pavel (llnl.gov), Charles
|
||||
Goldsmith (wokka.org), Douglas M. McKeown (saintmarys.edu), Revital Shvarzman
|
||||
(York U, Ontario), Walter Gould (Auburn U), Lindsay Druet and Colin Palmer (U
|
||||
of Waikato, Hamilton NZ), Dusty Hall (Auburn U), Jon Monroe (center pointe),
|
||||
Alexander Barthel, Bill Anderson, Alexander Hartmaier (t-systems.at), Justin
|
||||
Hunter (Arizona State U), Jethro Binks (U of Strathclyde, Glasgow), Jordi
|
||||
Guijarro (UAB.es), Sam Stickland (spacething.org), Stefan Radman (CTBTO.org),
|
||||
Clint Wise, Max Kosmach, and Bernhard Augenstein.
|
||||
|
||||
We probably forgot some names - sorry about that :-(.
|
||||
|
||||
Deep gratitude also goes
|
||||
to the authors and communities of all the other software that Netdisco is
|
||||
built upon.
|
||||
|
||||
=head1 COPYRIGHT AND LICENSE
|
||||
|
||||
This software is copyright (c) 2012-2015 by The Netdisco Developer Team.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Netdisco Project nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE NETDISCO DEVELOPER TEAM BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
=cut
|
||||
|
||||
1;
|
||||
280
lib/App/Netdisco/AnyEvent/Nbtstat.pm
Normal file
280
lib/App/Netdisco/AnyEvent/Nbtstat.pm
Normal file
@@ -0,0 +1,280 @@
|
||||
package App::Netdisco::AnyEvent::Nbtstat;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
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
|
||||
96
lib/App/Netdisco/Configuration.pm
Normal file
96
lib/App/Netdisco/Configuration.pm
Normal file
@@ -0,0 +1,96 @@
|
||||
package App::Netdisco::Configuration;
|
||||
|
||||
use App::Netdisco::Environment;
|
||||
use Dancer ':script';
|
||||
|
||||
BEGIN {
|
||||
# stuff useful locations into @INC
|
||||
unshift @INC, @{ (setting('include_paths') || []) };
|
||||
}
|
||||
|
||||
# set up database schema config from simple config vars
|
||||
if (ref {} eq ref setting('database')) {
|
||||
my $name = ($ENV{NETDISCO_DBNAME} || setting('database')->{name} || 'netdisco');
|
||||
my $host = setting('database')->{host};
|
||||
my $user = setting('database')->{user};
|
||||
my $pass = setting('database')->{pass};
|
||||
|
||||
my $dsn = "dbi:Pg:dbname=${name}";
|
||||
$dsn .= ";host=${host}" if $host;
|
||||
|
||||
# set up the netdisco schema now we have access to the config
|
||||
# but only if it doesn't exist from an earlier config style
|
||||
setting('plugins')->{DBIC}->{netdisco} ||= {
|
||||
dsn => $dsn,
|
||||
user => $user,
|
||||
password => $pass,
|
||||
options => {
|
||||
AutoCommit => 1,
|
||||
RaiseError => 1,
|
||||
auto_savepoint => 1,
|
||||
pg_enable_utf8 => 1,
|
||||
},
|
||||
schema_class => 'App::Netdisco::DB',
|
||||
};
|
||||
|
||||
foreach my $c (@{setting('external_databases')}) {
|
||||
my $schema = delete $c->{tag} or next;
|
||||
next if $schema eq 'netdisco';
|
||||
setting('plugins')->{DBIC}->{$schema} = $c;
|
||||
setting('plugins')->{DBIC}->{$schema}->{schema_class}
|
||||
||= 'App::Netdisco::GenericDB';
|
||||
}
|
||||
}
|
||||
|
||||
# defaults for workers
|
||||
setting('workers')->{queue} ||= 'PostgreSQL';
|
||||
if (exists setting('workers')->{interactives}
|
||||
or exists setting('workers')->{pollers}) {
|
||||
|
||||
setting('workers')->{tasks} =
|
||||
(setting('workers')->{pollers} || 0)
|
||||
+ (setting('workers')->{interactives} || 0);
|
||||
|
||||
delete setting('workers')->{pollers};
|
||||
delete setting('workers')->{interactives};
|
||||
}
|
||||
|
||||
# force skipped DNS resolution, if unset
|
||||
setting('dns')->{hosts_file} ||= '/etc/hosts';
|
||||
setting('dns')->{no} ||= ['fe80::/64','169.254.0.0/16'];
|
||||
|
||||
# legacy config item names
|
||||
|
||||
config->{'devport_vlan_limit'} =
|
||||
config->{'deviceport_vlan_membership_threshold'}
|
||||
if setting('deviceport_vlan_membership_threshold')
|
||||
and not setting('devport_vlan_limit');
|
||||
delete config->{'deviceport_vlan_membership_threshold'};
|
||||
|
||||
config->{'schedule'} = config->{'housekeeping'}
|
||||
if setting('housekeeping') and not setting('schedule');
|
||||
delete config->{'housekeeping'};
|
||||
|
||||
# schedule expire used to be called expiry
|
||||
setting('schedule')->{expire} ||= setting('schedule')->{expiry}
|
||||
if setting('schedule') and exists setting('schedule')->{expiry};
|
||||
delete config->{'schedule'}->{'expiry'} if setting('schedule');
|
||||
|
||||
# upgrade reports config from hash to list
|
||||
if (setting('reports') and ref {} eq ref setting('reports')) {
|
||||
config->{'reports'} = [ map {{
|
||||
tag => $_,
|
||||
%{ setting('reports')->{$_} }
|
||||
}} keys %{ setting('reports') } ];
|
||||
}
|
||||
|
||||
# set max outstanding requests for AnyEvent::DNS
|
||||
$ENV{'PERL_ANYEVENT_MAX_OUTSTANDING_DNS'}
|
||||
= setting('dns')->{max_outstanding} || 50;
|
||||
$ENV{'PERL_ANYEVENT_HOSTS'}
|
||||
= setting('dns')->{hosts_file} || '/etc/hosts';
|
||||
|
||||
# always set this
|
||||
$ENV{DBIC_TRACE_PROFILE} = 'console';
|
||||
|
||||
true;
|
||||
192
lib/App/Netdisco/Core/Arpnip.pm
Normal file
192
lib/App/Netdisco/Core/Arpnip.pm
Normal file
@@ -0,0 +1,192 @@
|
||||
package App::Netdisco::Core::Arpnip;
|
||||
|
||||
use Dancer qw/:syntax :script/;
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
|
||||
use App::Netdisco::Util::Node 'check_mac';
|
||||
use App::Netdisco::Util::DNS ':all';
|
||||
use NetAddr::IP::Lite ':lower';
|
||||
use Time::HiRes 'gettimeofday';
|
||||
use NetAddr::MAC ();
|
||||
|
||||
use base 'Exporter';
|
||||
our @EXPORT = ();
|
||||
our @EXPORT_OK = qw/ do_arpnip store_arp /;
|
||||
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||
|
||||
=head1 NAME
|
||||
|
||||
App::Netdisco::Core::Arpnip
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Helper subroutines to support parts of the Netdisco application.
|
||||
|
||||
There are no default exports, however the C<:all> tag will export all
|
||||
subroutines.
|
||||
|
||||
=head1 EXPORT_OK
|
||||
|
||||
=head2 do_arpnip( $device, $snmp )
|
||||
|
||||
Given a Device database object, and a working SNMP connection, connect to a
|
||||
device and discover its ARP cache for IPv4 and Neighbor cache for IPv6.
|
||||
|
||||
Will also discover subnets in use on the device and update the Subnets table.
|
||||
|
||||
=cut
|
||||
|
||||
sub do_arpnip {
|
||||
my ($device, $snmp) = @_;
|
||||
|
||||
unless ($device->in_storage) {
|
||||
debug sprintf ' [%s] arpnip - skipping device not yet discovered', $device->ip;
|
||||
return;
|
||||
}
|
||||
|
||||
# get v4 arp table
|
||||
my $v4 = _get_arps($device, $snmp->at_paddr, $snmp->at_netaddr);
|
||||
# get v6 neighbor cache
|
||||
my $v6 = _get_arps($device, $snmp->ipv6_n2p_mac, $snmp->ipv6_n2p_addr);
|
||||
|
||||
# get directly connected networks
|
||||
my @subnets = _gather_subnets($device, $snmp);
|
||||
# TODO: IPv6 subnets
|
||||
|
||||
# would be possible just to use now() on updated records, but by using this
|
||||
# same value for them all, we _can_ if we want add a job at the end to
|
||||
# select and do something with the updated set (no reason to yet, though)
|
||||
my $now = 'to_timestamp('. (join '.', gettimeofday) .')';
|
||||
|
||||
# update node_ip with ARP and Neighbor Cache entries
|
||||
store_arp(\%$_, $now) for @$v4;
|
||||
debug sprintf ' [%s] arpnip - processed %s ARP Cache entries',
|
||||
$device->ip, scalar @$v4;
|
||||
|
||||
store_arp(\%$_, $now) for @$v6;
|
||||
debug sprintf ' [%s] arpnip - processed %s IPv6 Neighbor Cache entries',
|
||||
$device->ip, scalar @$v6;
|
||||
|
||||
_store_subnet($_, $now) for @subnets;
|
||||
debug sprintf ' [%s] arpnip - processed %s Subnet entries',
|
||||
$device->ip, scalar @subnets;
|
||||
|
||||
$device->update({last_arpnip => \$now});
|
||||
}
|
||||
|
||||
# get an arp table (v4 or v6)
|
||||
sub _get_arps {
|
||||
my ($device, $paddr, $netaddr) = @_;
|
||||
my @arps = ();
|
||||
|
||||
while (my ($arp, $node) = each %$paddr) {
|
||||
my $ip = $netaddr->{$arp};
|
||||
next unless defined $ip;
|
||||
next unless check_mac($device, $node);
|
||||
push @arps, {
|
||||
node => $node,
|
||||
ip => $ip,
|
||||
dns => undef,
|
||||
};
|
||||
}
|
||||
|
||||
debug sprintf ' resolving %d ARP entries with max %d outstanding requests',
|
||||
scalar @arps, $ENV{'PERL_ANYEVENT_MAX_OUTSTANDING_DNS'};
|
||||
my $resolved_ips = hostnames_resolve_async(\@arps);
|
||||
|
||||
return $resolved_ips;
|
||||
}
|
||||
|
||||
=head2 store_arp( \%host, $now? )
|
||||
|
||||
Stores a new entry to the C<node_ip> table with the given MAC, IP (v4 or v6)
|
||||
and DNS host name. Host details are provided in a Hash ref:
|
||||
|
||||
{
|
||||
ip => '192.0.2.1',
|
||||
node => '00:11:22:33:44:55',
|
||||
dns => 'myhost.example.com',
|
||||
}
|
||||
|
||||
The C<dns> entry is optional. The update will mark old entries for this IP as
|
||||
no longer C<active>.
|
||||
|
||||
Optionally a literal string can be passed in the second argument for the
|
||||
C<time_last> timestamp, otherwise the current timestamp (C<now()>) is used.
|
||||
|
||||
=cut
|
||||
|
||||
sub store_arp {
|
||||
my ($hash_ref, $now) = @_;
|
||||
$now ||= 'now()';
|
||||
my $ip = $hash_ref->{'ip'};
|
||||
my $mac = NetAddr::MAC->new($hash_ref->{'node'});
|
||||
my $name = $hash_ref->{'dns'};
|
||||
|
||||
return if !defined $mac or $mac->errstr;
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $current = schema('netdisco')->resultset('NodeIp')
|
||||
->search(
|
||||
{ ip => $ip, -bool => 'active'},
|
||||
{ columns => [qw/mac ip/] })->update({active => \'false'});
|
||||
|
||||
schema('netdisco')->resultset('NodeIp')
|
||||
->update_or_create(
|
||||
{
|
||||
mac => $mac->as_ieee,
|
||||
ip => $ip,
|
||||
dns => $name,
|
||||
active => \'true',
|
||||
time_last => \$now,
|
||||
},
|
||||
{
|
||||
key => 'primary',
|
||||
for => 'update',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
# gathers device subnets
|
||||
sub _gather_subnets {
|
||||
my ($device, $snmp) = @_;
|
||||
my @subnets = ();
|
||||
|
||||
my $ip_netmask = $snmp->ip_netmask;
|
||||
my $localnet = NetAddr::IP::Lite->new('127.0.0.0/8');
|
||||
|
||||
foreach my $entry (keys %$ip_netmask) {
|
||||
my $ip = NetAddr::IP::Lite->new($entry);
|
||||
my $addr = $ip->addr;
|
||||
|
||||
next if $addr eq '0.0.0.0';
|
||||
next if $ip->within($localnet);
|
||||
next if setting('ignore_private_nets') and $ip->is_rfc1918;
|
||||
|
||||
my $netmask = $ip_netmask->{$addr};
|
||||
next if $netmask eq '255.255.255.255' or $netmask eq '0.0.0.0';
|
||||
|
||||
my $cidr = NetAddr::IP::Lite->new($addr, $netmask)->network->cidr;
|
||||
|
||||
debug sprintf ' [%s] arpnip - found subnet %s', $device->ip, $cidr;
|
||||
push @subnets, $cidr;
|
||||
}
|
||||
|
||||
return @subnets;
|
||||
}
|
||||
|
||||
# update subnets with new networks
|
||||
sub _store_subnet {
|
||||
my ($subnet, $now) = @_;
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
schema('netdisco')->resultset('Subnet')->update_or_create(
|
||||
{
|
||||
net => $subnet,
|
||||
last_discover => \$now,
|
||||
},
|
||||
{ for => 'update' });
|
||||
});
|
||||
}
|
||||
|
||||
1;
|
||||
985
lib/App/Netdisco/Core/Discover.pm
Normal file
985
lib/App/Netdisco/Core/Discover.pm
Normal file
@@ -0,0 +1,985 @@
|
||||
package App::Netdisco::Core::Discover;
|
||||
|
||||
use Dancer qw/:syntax :script/;
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
|
||||
use App::Netdisco::Util::Device
|
||||
qw/get_device match_devicetype is_discoverable/;
|
||||
use App::Netdisco::Util::DNS ':all';
|
||||
use App::Netdisco::JobQueue qw/jq_queued jq_insert/;
|
||||
use NetAddr::IP::Lite ':lower';
|
||||
use List::MoreUtils ();
|
||||
use Scalar::Util 'blessed';
|
||||
use Encode;
|
||||
use Try::Tiny;
|
||||
use NetAddr::MAC;
|
||||
|
||||
use base 'Exporter';
|
||||
our @EXPORT = ();
|
||||
our @EXPORT_OK = qw/
|
||||
set_canonical_ip
|
||||
store_device store_interfaces store_wireless
|
||||
store_vlans store_power store_modules
|
||||
store_neighbors discover_new_neighbors
|
||||
/;
|
||||
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||
|
||||
=head1 NAME
|
||||
|
||||
App::Netdisco::Core::Discover
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A set of helper subroutines to support parts of the Netdisco application.
|
||||
|
||||
There are no default exports, however the C<:all> tag will export all
|
||||
subroutines.
|
||||
|
||||
=head1 EXPORT_OK
|
||||
|
||||
=head2 set_canonical_ip( $device, $snmp )
|
||||
|
||||
Returns: C<$device>
|
||||
|
||||
Given a Device database object, and a working SNMP connection, check whether
|
||||
the database object's IP is the best choice for that device. If not, update
|
||||
the IP and hostname in the device object for the canonical IP.
|
||||
|
||||
=cut
|
||||
|
||||
sub set_canonical_ip {
|
||||
my ($device, $snmp) = @_;
|
||||
|
||||
my $old_ip = $device->ip;
|
||||
my $new_ip = $old_ip;
|
||||
my $revname = ipv4_from_hostname($snmp->name);
|
||||
|
||||
if (setting('reverse_sysname') and $revname) {
|
||||
$new_ip = $revname;
|
||||
}
|
||||
|
||||
if (setting('device_identity')) {
|
||||
}
|
||||
|
||||
return if $new_ip eq $old_ip;
|
||||
|
||||
if (not $snmp->snmp_connect_ip( $new_ip )) {
|
||||
# should be warning or error?
|
||||
debug sprintf ' [%s] device - cannot change IP to %s - SNMP connect failed',
|
||||
$old_ip, $device->ip;
|
||||
return;
|
||||
}
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
$device->renumber($new_ip)
|
||||
or die "cannot renumber to: $new_ip"; # rollback
|
||||
|
||||
debug sprintf ' [%s] device - changed IP to %s (%s)',
|
||||
$old_ip, $device->ip, ($device->dns || '');
|
||||
});
|
||||
}
|
||||
|
||||
=head2 store_device( $device, $snmp )
|
||||
|
||||
Given a Device database object, and a working SNMP connection, discover and
|
||||
store basic device information.
|
||||
|
||||
The Device database object can be a fresh L<DBIx::Class::Row> object which is
|
||||
not yet stored to the database.
|
||||
|
||||
=cut
|
||||
|
||||
sub store_device {
|
||||
my ($device, $snmp) = @_;
|
||||
|
||||
my $ip_index = $snmp->ip_index;
|
||||
my $interfaces = $snmp->interfaces;
|
||||
my $ip_netmask = $snmp->ip_netmask;
|
||||
|
||||
my $localnet = NetAddr::IP::Lite->new('127.0.0.0/8');
|
||||
|
||||
# build device aliases suitable for DBIC
|
||||
my @aliases;
|
||||
foreach my $entry (keys %$ip_index) {
|
||||
my $ip = NetAddr::IP::Lite->new($entry)
|
||||
or next;
|
||||
my $addr = $ip->addr;
|
||||
|
||||
next if $addr eq '0.0.0.0';
|
||||
next if $ip->within($localnet);
|
||||
next if setting('ignore_private_nets') and $ip->is_rfc1918;
|
||||
|
||||
my $iid = $ip_index->{$addr};
|
||||
my $port = $interfaces->{$iid};
|
||||
my $subnet = $ip_netmask->{$addr}
|
||||
? NetAddr::IP::Lite->new($addr, $ip_netmask->{$addr})->network->cidr
|
||||
: undef;
|
||||
|
||||
debug sprintf ' [%s] device - aliased as %s', $device->ip, $addr;
|
||||
push @aliases, {
|
||||
alias => $addr,
|
||||
port => $port,
|
||||
subnet => $subnet,
|
||||
dns => undef,
|
||||
};
|
||||
}
|
||||
|
||||
debug sprintf ' resolving %d aliases with max %d outstanding requests',
|
||||
scalar @aliases, $ENV{'PERL_ANYEVENT_MAX_OUTSTANDING_DNS'};
|
||||
my $resolved_aliases = hostnames_resolve_async(\@aliases);
|
||||
|
||||
# fake one aliases entry for devices not providing ip_index
|
||||
push @$resolved_aliases, { alias => $device->ip, dns => $device->dns }
|
||||
if 0 == scalar @aliases;
|
||||
|
||||
# VTP Management Domain -- assume only one.
|
||||
my $vtpdomains = $snmp->vtp_d_name;
|
||||
my $vtpdomain;
|
||||
if (defined $vtpdomains and scalar values %$vtpdomains) {
|
||||
$device->set_column( vtp_domain => (values %$vtpdomains)[-1] );
|
||||
}
|
||||
|
||||
my $hostname = hostname_from_ip($device->ip);
|
||||
$device->set_column( dns => $hostname ) if $hostname;
|
||||
|
||||
my @properties = qw/
|
||||
snmp_ver
|
||||
description uptime contact name location
|
||||
layers ports mac
|
||||
ps1_type ps2_type ps1_status ps2_status
|
||||
fan slots
|
||||
vendor os os_ver
|
||||
/;
|
||||
|
||||
foreach my $property (@properties) {
|
||||
$device->set_column( $property => $snmp->$property );
|
||||
}
|
||||
|
||||
$device->set_column( model => Encode::decode('UTF-8', $snmp->model) );
|
||||
$device->set_column( serial => Encode::decode('UTF-8', $snmp->serial) );
|
||||
|
||||
$device->set_column( snmp_class => $snmp->class );
|
||||
$device->set_column( last_discover => \'now()' );
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->device_ips->delete;
|
||||
debug sprintf ' [%s] device - removed %d aliases',
|
||||
$device->ip, $gone;
|
||||
$device->update_or_insert(undef, {for => 'update'});
|
||||
$device->device_ips->populate($resolved_aliases);
|
||||
debug sprintf ' [%s] device - added %d new aliases',
|
||||
$device->ip, scalar @aliases;
|
||||
});
|
||||
}
|
||||
|
||||
=head2 store_interfaces( $device, $snmp )
|
||||
|
||||
Given a Device database object, and a working SNMP connection, discover and
|
||||
store the device's interface/port information.
|
||||
|
||||
The Device database object can be a fresh L<DBIx::Class::Row> object which is
|
||||
not yet stored to the database.
|
||||
|
||||
=cut
|
||||
|
||||
sub store_interfaces {
|
||||
my ($device, $snmp) = @_;
|
||||
|
||||
my $interfaces = $snmp->interfaces;
|
||||
my $i_type = $snmp->i_type;
|
||||
my $i_ignore = $snmp->i_ignore;
|
||||
my $i_descr = $snmp->i_description;
|
||||
my $i_mtu = $snmp->i_mtu;
|
||||
my $i_speed = $snmp->i_speed;
|
||||
my $i_mac = $snmp->i_mac;
|
||||
my $i_up = $snmp->i_up;
|
||||
my $i_up_admin = $snmp->i_up_admin;
|
||||
my $i_name = $snmp->i_name;
|
||||
my $i_duplex = $snmp->i_duplex;
|
||||
my $i_duplex_admin = $snmp->i_duplex_admin;
|
||||
my $i_stp_state = $snmp->i_stp_state;
|
||||
my $i_vlan = $snmp->i_vlan;
|
||||
my $i_lastchange = $snmp->i_lastchange;
|
||||
my $agg_ports = $snmp->agg_ports;
|
||||
|
||||
# clear the cached uptime and get a new one
|
||||
my $dev_uptime = $snmp->load_uptime;
|
||||
if (!defined $dev_uptime) {
|
||||
error sprintf ' [%s] interfaces - Error! Failed to get uptime from device!',
|
||||
$device->ip;
|
||||
return;
|
||||
}
|
||||
|
||||
# used to track how many times the device uptime wrapped
|
||||
my $dev_uptime_wrapped = 0;
|
||||
|
||||
# use SNMP-FRAMEWORK-MIB::snmpEngineTime if available to
|
||||
# fix device uptime if wrapped
|
||||
if (defined $snmp->snmpEngineTime) {
|
||||
$dev_uptime_wrapped = int( $snmp->snmpEngineTime * 100 / 2**32 );
|
||||
if ($dev_uptime_wrapped > 0) {
|
||||
info sprintf ' [%s] interface - device uptime wrapped %d times - correcting',
|
||||
$device->ip, $dev_uptime_wrapped;
|
||||
$device->uptime( $dev_uptime + $dev_uptime_wrapped * 2**32 );
|
||||
}
|
||||
}
|
||||
|
||||
# build device interfaces suitable for DBIC
|
||||
my %interfaces;
|
||||
foreach my $entry (keys %$interfaces) {
|
||||
my $port = $interfaces->{$entry};
|
||||
|
||||
if (not $port) {
|
||||
debug sprintf ' [%s] interfaces - ignoring %s (no port mapping)',
|
||||
$device->ip, $entry;
|
||||
next;
|
||||
}
|
||||
|
||||
if (scalar grep {$port =~ m/^$_$/} @{setting('ignore_interfaces') || []}) {
|
||||
debug sprintf
|
||||
' [%s] interfaces - ignoring %s (%s) (config:ignore_interfaces)',
|
||||
$device->ip, $entry, $port;
|
||||
next;
|
||||
}
|
||||
|
||||
if (exists $i_ignore->{$entry}) {
|
||||
debug sprintf ' [%s] interfaces - ignoring %s (%s) (%s)',
|
||||
$device->ip, $entry, $port, $i_type->{$entry};
|
||||
next;
|
||||
}
|
||||
|
||||
my $lc = $i_lastchange->{$entry} || 0;
|
||||
if (not $dev_uptime_wrapped and $lc > $dev_uptime) {
|
||||
info sprintf ' [%s] interfaces - device uptime wrapped (%s) - correcting',
|
||||
$device->ip, $port;
|
||||
$device->uptime( $dev_uptime + 2**32 );
|
||||
$dev_uptime_wrapped = 1;
|
||||
}
|
||||
|
||||
if ($device->is_column_changed('uptime') and $lc) {
|
||||
if ($lc < $dev_uptime) {
|
||||
# ambiguous: lastchange could be sysUptime before or after wrap
|
||||
if ($dev_uptime > 30000 and $lc < 30000) {
|
||||
# uptime wrap more than 5min ago but lastchange within 5min
|
||||
# assume lastchange was directly after boot -> no action
|
||||
}
|
||||
else {
|
||||
# uptime wrap less than 5min ago or lastchange > 5min ago
|
||||
# to be on safe side, assume lastchange after counter wrap
|
||||
debug sprintf
|
||||
' [%s] interfaces - correcting LastChange for %s, assuming sysUptime wrap',
|
||||
$device->ip, $port;
|
||||
$lc += $dev_uptime_wrapped * 2**32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$interfaces{$port} = {
|
||||
port => $port,
|
||||
descr => $i_descr->{$entry},
|
||||
up => $i_up->{$entry},
|
||||
up_admin => $i_up_admin->{$entry},
|
||||
mac => $i_mac->{$entry},
|
||||
speed => $i_speed->{$entry},
|
||||
mtu => $i_mtu->{$entry},
|
||||
name => Encode::decode('UTF-8', $i_name->{$entry}),
|
||||
duplex => $i_duplex->{$entry},
|
||||
duplex_admin => $i_duplex_admin->{$entry},
|
||||
stp => $i_stp_state->{$entry},
|
||||
type => $i_type->{$entry},
|
||||
vlan => $i_vlan->{$entry},
|
||||
pvid => $i_vlan->{$entry},
|
||||
is_master => 'false',
|
||||
slave_of => undef,
|
||||
lastchange => $lc,
|
||||
};
|
||||
}
|
||||
|
||||
# must do this after building %interfaces so that we can set is_master
|
||||
foreach my $sidx (keys %$agg_ports) {
|
||||
my $slave = $interfaces->{$sidx} or next;
|
||||
my $master = $interfaces->{ $agg_ports->{$sidx} } or next;
|
||||
next unless exists $interfaces{$slave} and exists $interfaces{$master};
|
||||
|
||||
$interfaces{$slave}->{slave_of} = $master;
|
||||
$interfaces{$master}->{is_master} = 'true';
|
||||
}
|
||||
|
||||
schema('netdisco')->resultset('DevicePort')->txn_do_locked(sub {
|
||||
my $gone = $device->ports->delete({keep_nodes => 1});
|
||||
debug sprintf ' [%s] interfaces - removed %d interfaces',
|
||||
$device->ip, $gone;
|
||||
$device->update_or_insert(undef, {for => 'update'});
|
||||
$device->ports->populate([values %interfaces]);
|
||||
debug sprintf ' [%s] interfaces - added %d new interfaces',
|
||||
$device->ip, scalar values %interfaces;
|
||||
});
|
||||
}
|
||||
|
||||
=head2 store_wireless( $device, $snmp )
|
||||
|
||||
Given a Device database object, and a working SNMP connection, discover and
|
||||
store the device's wireless interface information.
|
||||
|
||||
The Device database object can be a fresh L<DBIx::Class::Row> object which is
|
||||
not yet stored to the database.
|
||||
|
||||
=cut
|
||||
|
||||
sub store_wireless {
|
||||
my ($device, $snmp) = @_;
|
||||
|
||||
my $ssidlist = $snmp->i_ssidlist;
|
||||
return unless scalar keys %$ssidlist;
|
||||
|
||||
my $interfaces = $snmp->interfaces;
|
||||
my $ssidbcast = $snmp->i_ssidbcast;
|
||||
my $ssidmac = $snmp->i_ssidmac;
|
||||
my $channel = $snmp->i_80211channel;
|
||||
my $power = $snmp->dot11_cur_tx_pwr_mw;
|
||||
|
||||
# build device ssid list suitable for DBIC
|
||||
my @ssids;
|
||||
foreach my $entry (keys %$ssidlist) {
|
||||
(my $iid = $entry) =~ s/\.\d+$//;
|
||||
my $port = $interfaces->{$iid};
|
||||
|
||||
if (not $port) {
|
||||
debug sprintf ' [%s] wireless - ignoring %s (no port mapping)',
|
||||
$device->ip, $iid;
|
||||
next;
|
||||
}
|
||||
|
||||
push @ssids, {
|
||||
port => $port,
|
||||
ssid => $ssidlist->{$entry},
|
||||
broadcast => $ssidbcast->{$entry},
|
||||
bssid => $ssidmac->{$entry},
|
||||
};
|
||||
}
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->ssids->delete;
|
||||
debug sprintf ' [%s] wireless - removed %d SSIDs',
|
||||
$device->ip, $gone;
|
||||
$device->ssids->populate(\@ssids);
|
||||
debug sprintf ' [%s] wireless - added %d new SSIDs',
|
||||
$device->ip, scalar @ssids;
|
||||
});
|
||||
|
||||
# build device channel list suitable for DBIC
|
||||
my @channels;
|
||||
foreach my $entry (keys %$channel) {
|
||||
my $port = $interfaces->{$entry};
|
||||
|
||||
if (not $port) {
|
||||
debug sprintf ' [%s] wireless - ignoring %s (no port mapping)',
|
||||
$device->ip, $entry;
|
||||
next;
|
||||
}
|
||||
|
||||
push @channels, {
|
||||
port => $port,
|
||||
channel => $channel->{$entry},
|
||||
power => $power->{$entry},
|
||||
};
|
||||
}
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->wireless_ports->delete;
|
||||
debug sprintf ' [%s] wireless - removed %d wireless channels',
|
||||
$device->ip, $gone;
|
||||
$device->wireless_ports->populate(\@channels);
|
||||
debug sprintf ' [%s] wireless - added %d new wireless channels',
|
||||
$device->ip, scalar @channels;
|
||||
});
|
||||
}
|
||||
|
||||
=head2 store_vlans( $device, $snmp )
|
||||
|
||||
Given a Device database object, and a working SNMP connection, discover and
|
||||
store the device's vlan information.
|
||||
|
||||
The Device database object can be a fresh L<DBIx::Class::Row> object which is
|
||||
not yet stored to the database.
|
||||
|
||||
=cut
|
||||
|
||||
sub store_vlans {
|
||||
my ($device, $snmp) = @_;
|
||||
|
||||
my $v_name = $snmp->v_name;
|
||||
my $v_index = $snmp->v_index;
|
||||
|
||||
# build device vlans suitable for DBIC
|
||||
my %v_seen = ();
|
||||
my @devicevlans;
|
||||
foreach my $entry (keys %$v_name) {
|
||||
my $vlan = $v_index->{$entry};
|
||||
next unless defined $vlan and $vlan;
|
||||
++$v_seen{$vlan};
|
||||
|
||||
push @devicevlans, {
|
||||
vlan => $vlan,
|
||||
description => $v_name->{$entry},
|
||||
last_discover => \'now()',
|
||||
};
|
||||
}
|
||||
|
||||
my $i_vlan = $snmp->i_vlan;
|
||||
my $i_vlan_membership = $snmp->i_vlan_membership;
|
||||
my $i_vlan_type = $snmp->i_vlan_type;
|
||||
my $interfaces = $snmp->interfaces;
|
||||
|
||||
# build device port vlans suitable for DBIC
|
||||
my @portvlans = ();
|
||||
foreach my $entry (keys %$i_vlan_membership) {
|
||||
my %port_vseen = ();
|
||||
my $port = $interfaces->{$entry};
|
||||
next unless defined $port;
|
||||
|
||||
my $type = $i_vlan_type->{$entry};
|
||||
|
||||
foreach my $vlan (@{ $i_vlan_membership->{$entry} }) {
|
||||
next unless defined $vlan and $vlan;
|
||||
next if ++$port_vseen{$vlan} > 1;
|
||||
|
||||
my $native = ((defined $i_vlan->{$entry}) and ($vlan eq $i_vlan->{$entry})) ? "t" : "f";
|
||||
push @portvlans, {
|
||||
port => $port,
|
||||
vlan => $vlan,
|
||||
native => $native,
|
||||
vlantype => $type,
|
||||
last_discover => \'now()',
|
||||
};
|
||||
|
||||
next if $v_seen{$vlan};
|
||||
|
||||
# also add an unnamed vlan to the device
|
||||
push @devicevlans, {
|
||||
vlan => $vlan,
|
||||
description => (sprintf "VLAN %d", $vlan),
|
||||
last_discover => \'now()',
|
||||
};
|
||||
++$v_seen{$vlan};
|
||||
}
|
||||
}
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->vlans->delete;
|
||||
debug sprintf ' [%s] vlans - removed %d device VLANs',
|
||||
$device->ip, $gone;
|
||||
$device->vlans->populate(\@devicevlans);
|
||||
debug sprintf ' [%s] vlans - added %d new device VLANs',
|
||||
$device->ip, scalar @devicevlans;
|
||||
});
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->port_vlans->delete;
|
||||
debug sprintf ' [%s] vlans - removed %d port VLANs',
|
||||
$device->ip, $gone;
|
||||
$device->port_vlans->populate(\@portvlans);
|
||||
debug sprintf ' [%s] vlans - added %d new port VLANs',
|
||||
$device->ip, scalar @portvlans;
|
||||
});
|
||||
}
|
||||
|
||||
=head2 store_power( $device, $snmp )
|
||||
|
||||
Given a Device database object, and a working SNMP connection, discover and
|
||||
store the device's PoE information.
|
||||
|
||||
The Device database object can be a fresh L<DBIx::Class::Row> object which is
|
||||
not yet stored to the database.
|
||||
|
||||
=cut
|
||||
|
||||
sub store_power {
|
||||
my ($device, $snmp) = @_;
|
||||
|
||||
my $p_watts = $snmp->peth_power_watts;
|
||||
my $p_status = $snmp->peth_power_status;
|
||||
|
||||
if (!defined $p_watts) {
|
||||
debug sprintf ' [%s] power - 0 power modules', $device->ip;
|
||||
return;
|
||||
}
|
||||
|
||||
# build device module power info suitable for DBIC
|
||||
my @devicepower;
|
||||
foreach my $entry (keys %$p_watts) {
|
||||
push @devicepower, {
|
||||
module => $entry,
|
||||
power => $p_watts->{$entry},
|
||||
status => $p_status->{$entry},
|
||||
};
|
||||
}
|
||||
|
||||
my $interfaces = $snmp->interfaces;
|
||||
my $p_ifindex = $snmp->peth_port_ifindex;
|
||||
my $p_admin = $snmp->peth_port_admin;
|
||||
my $p_pstatus = $snmp->peth_port_status;
|
||||
my $p_class = $snmp->peth_port_class;
|
||||
my $p_power = $snmp->peth_port_power;
|
||||
|
||||
# build device port power info suitable for DBIC
|
||||
my @portpower;
|
||||
foreach my $entry (keys %$p_ifindex) {
|
||||
my $port = $interfaces->{ $p_ifindex->{$entry} };
|
||||
next unless $port;
|
||||
|
||||
my ($module) = split m/\./, $entry;
|
||||
|
||||
push @portpower, {
|
||||
port => $port,
|
||||
module => $module,
|
||||
admin => $p_admin->{$entry},
|
||||
status => $p_pstatus->{$entry},
|
||||
class => $p_class->{$entry},
|
||||
power => $p_power->{$entry},
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->power_modules->delete;
|
||||
debug sprintf ' [%s] power - removed %d power modules',
|
||||
$device->ip, $gone;
|
||||
$device->power_modules->populate(\@devicepower);
|
||||
debug sprintf ' [%s] power - added %d new power modules',
|
||||
$device->ip, scalar @devicepower;
|
||||
});
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->powered_ports->delete;
|
||||
debug sprintf ' [%s] power - removed %d PoE capable ports',
|
||||
$device->ip, $gone;
|
||||
$device->powered_ports->populate(\@portpower);
|
||||
debug sprintf ' [%s] power - added %d new PoE capable ports',
|
||||
$device->ip, scalar @portpower;
|
||||
});
|
||||
}
|
||||
|
||||
=head2 store_modules( $device, $snmp )
|
||||
|
||||
Given a Device database object, and a working SNMP connection, discover and
|
||||
store the device's module information.
|
||||
|
||||
The Device database object can be a fresh L<DBIx::Class::Row> object which is
|
||||
not yet stored to the database.
|
||||
|
||||
=cut
|
||||
|
||||
sub store_modules {
|
||||
my ($device, $snmp) = @_;
|
||||
|
||||
my $e_index = $snmp->e_index;
|
||||
|
||||
if (!defined $e_index) {
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->modules->delete;
|
||||
debug sprintf ' [%s] modules - removed %d chassis modules',
|
||||
$device->ip, $gone;
|
||||
|
||||
$device->modules->update_or_create({
|
||||
ip => $device->ip,
|
||||
index => 1,
|
||||
parent => 0,
|
||||
name => 'chassis',
|
||||
class => 'chassis',
|
||||
pos => -1,
|
||||
# too verbose and link doesn't work anyway
|
||||
# description => $device->description,
|
||||
sw_ver => $device->os_ver,
|
||||
serial => $device->serial,
|
||||
model => $device->model,
|
||||
fru => \'false',
|
||||
last_discover => \'now()',
|
||||
});
|
||||
});
|
||||
|
||||
debug
|
||||
sprintf ' [%s] modules - 0 chassis components (added one pseudo for chassis)',
|
||||
$device->ip;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
my $e_descr = $snmp->e_descr;
|
||||
my $e_type = $snmp->e_type;
|
||||
my $e_parent = $snmp->e_parent;
|
||||
my $e_name = $snmp->e_name;
|
||||
my $e_class = $snmp->e_class;
|
||||
my $e_pos = $snmp->e_pos;
|
||||
my $e_hwver = $snmp->e_hwver;
|
||||
my $e_fwver = $snmp->e_fwver;
|
||||
my $e_swver = $snmp->e_swver;
|
||||
my $e_model = $snmp->e_model;
|
||||
my $e_serial = $snmp->e_serial;
|
||||
my $e_fru = $snmp->e_fru;
|
||||
|
||||
# build device modules list for DBIC
|
||||
my @modules;
|
||||
foreach my $entry (keys %$e_index) {
|
||||
push @modules, {
|
||||
index => $e_index->{$entry},
|
||||
type => $e_type->{$entry},
|
||||
parent => $e_parent->{$entry},
|
||||
name => Encode::decode('UTF-8', $e_name->{$entry}),
|
||||
class => $e_class->{$entry},
|
||||
pos => $e_pos->{$entry},
|
||||
hw_ver => Encode::decode('UTF-8', $e_hwver->{$entry}),
|
||||
fw_ver => Encode::decode('UTF-8', $e_fwver->{$entry}),
|
||||
sw_ver => Encode::decode('UTF-8', $e_swver->{$entry}),
|
||||
model => Encode::decode('UTF-8', $e_model->{$entry}),
|
||||
serial => Encode::decode('UTF-8', $e_serial->{$entry}),
|
||||
fru => $e_fru->{$entry},
|
||||
description => Encode::decode('UTF-8', $e_descr->{$entry}),
|
||||
last_discover => \'now()',
|
||||
};
|
||||
}
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->modules->delete;
|
||||
debug sprintf ' [%s] modules - removed %d chassis modules',
|
||||
$device->ip, $gone;
|
||||
$device->modules->populate(\@modules);
|
||||
debug sprintf ' [%s] modules - added %d new chassis modules',
|
||||
$device->ip, scalar @modules;
|
||||
});
|
||||
}
|
||||
|
||||
=head2 store_neighbors( $device, $snmp )
|
||||
|
||||
returns: C<@to_discover>
|
||||
|
||||
Given a Device database object, and a working SNMP connection, discover and
|
||||
store the device's port neighbors information.
|
||||
|
||||
Entries in the Topology database table will override any discovered device
|
||||
port relationships.
|
||||
|
||||
The Device database object can be a fresh L<DBIx::Class::Row> object which is
|
||||
not yet stored to the database.
|
||||
|
||||
A list of discovererd neighbors will be returned as [C<$ip>, C<$type>] tuples.
|
||||
|
||||
=cut
|
||||
|
||||
sub store_neighbors {
|
||||
my ($device, $snmp) = @_;
|
||||
my @to_discover = ();
|
||||
|
||||
# first allow any manually configured topology to be set
|
||||
_set_manual_topology($device, $snmp);
|
||||
|
||||
my $c_ip = $snmp->c_ip;
|
||||
unless ($snmp->hasCDP or scalar keys %$c_ip) {
|
||||
debug sprintf ' [%s] neigh - CDP/LLDP not enabled!', $device->ip;
|
||||
return @to_discover;
|
||||
}
|
||||
|
||||
my $interfaces = $snmp->interfaces;
|
||||
my $c_if = $snmp->c_if;
|
||||
my $c_port = $snmp->c_port;
|
||||
my $c_id = $snmp->c_id;
|
||||
my $c_platform = $snmp->c_platform;
|
||||
my $c_cap = $snmp->c_cap;
|
||||
|
||||
foreach my $entry (sort (List::MoreUtils::uniq( (keys %$c_ip), (keys %$c_cap) ))) {
|
||||
if (!defined $c_if->{$entry} or !defined $interfaces->{ $c_if->{$entry} }) {
|
||||
debug sprintf ' [%s] neigh - port for IID:%s not resolved, skipping',
|
||||
$device->ip, $entry;
|
||||
next;
|
||||
}
|
||||
|
||||
my $port = $interfaces->{ $c_if->{$entry} };
|
||||
my $portrow = schema('netdisco')->resultset('DevicePort')
|
||||
->single({ip => $device->ip, port => $port});
|
||||
|
||||
if (!defined $portrow) {
|
||||
info sprintf ' [%s] neigh - local port %s not in database!',
|
||||
$device->ip, $port;
|
||||
next;
|
||||
}
|
||||
|
||||
my $remote_ip = $c_ip->{$entry};
|
||||
my $remote_ipad = NetAddr::IP::Lite->new($remote_ip);
|
||||
my $remote_port = undef;
|
||||
my $remote_type = Encode::decode('UTF-8', $c_platform->{$entry} || '');
|
||||
my $remote_id = Encode::decode('UTF-8', $c_id->{$entry});
|
||||
my $remote_cap = $c_cap->{$entry} || [];
|
||||
|
||||
# IP Phone and WAP detection type fixup
|
||||
if (scalar @$remote_cap or $remote_type) {
|
||||
my $phone_flag = grep {match_devicetype($_, 'phone_capabilities')}
|
||||
@$remote_cap;
|
||||
my $ap_flag = grep {match_devicetype($_, 'wap_capabilities')}
|
||||
@$remote_cap;
|
||||
|
||||
if ($phone_flag or match_devicetype($remote_type, 'phone_platforms')) {
|
||||
$remote_type = 'IP Phone: '. $remote_type
|
||||
if $remote_type !~ /ip.phone/i;
|
||||
}
|
||||
elsif ($ap_flag or match_devicetype($remote_type, 'wap_platforms')) {
|
||||
$remote_type = 'AP: '. $remote_type;
|
||||
}
|
||||
|
||||
$portrow->update({remote_type => $remote_type});
|
||||
}
|
||||
|
||||
if ($portrow->manual_topo) {
|
||||
info sprintf ' [%s] neigh - %s has manually defined topology',
|
||||
$device->ip, $port;
|
||||
next;
|
||||
}
|
||||
|
||||
next unless $remote_ip;
|
||||
|
||||
# a bunch of heuristics to search known devices if we don't have a
|
||||
# useable remote IP...
|
||||
|
||||
if ($remote_ip eq '0.0.0.0' or
|
||||
$remote_ipad->within(NetAddr::IP::Lite->new('127.0.0.0/8'))) {
|
||||
|
||||
if ($remote_id) {
|
||||
my $devices = schema('netdisco')->resultset('Device');
|
||||
my $neigh = $devices->single({name => $remote_id});
|
||||
info sprintf
|
||||
' [%s] neigh - bad address %s on port %s, searching for %s instead',
|
||||
$device->ip, $remote_ip, $port, $remote_id;
|
||||
|
||||
if (!defined $neigh) {
|
||||
my $mac = NetAddr::MAC->new(mac => $remote_id);
|
||||
if ($mac and not $mac->errstr) {
|
||||
$neigh = $devices->single({mac => $mac->as_ieee});
|
||||
}
|
||||
}
|
||||
|
||||
# some HP switches send 127.0.0.1 as remote_ip if no ip address
|
||||
# on default vlan for HP switches remote_ip looks like
|
||||
# "myswitchname(012345-012345)"
|
||||
if (!defined $neigh) {
|
||||
(my $tmpid = $remote_id) =~ s/.*\(([0-9a-f]{6})-([0-9a-f]{6})\).*/$1$2/;
|
||||
my $mac = NetAddr::MAC->new(mac => $tmpid);
|
||||
if ($mac and not $mac->errstr) {
|
||||
info sprintf
|
||||
'[%s] neigh - found neighbor %s by MAC %s',
|
||||
$device->ip, $remote_id, $mac->as_ieee;
|
||||
$neigh = $devices->single({mac => $mac->as_ieee});
|
||||
}
|
||||
}
|
||||
|
||||
if (!defined $neigh) {
|
||||
(my $shortid = $remote_id) =~ s/\..*//;
|
||||
$neigh = $devices->single({name => { -ilike => "${shortid}%" }});
|
||||
}
|
||||
|
||||
if ($neigh) {
|
||||
$remote_ip = $neigh->ip;
|
||||
info sprintf ' [%s] neigh - found %s with IP %s',
|
||||
$device->ip, $remote_id, $remote_ip;
|
||||
}
|
||||
else {
|
||||
info sprintf ' [%s] neigh - could not find %s, skipping',
|
||||
$device->ip, $remote_id;
|
||||
next;
|
||||
}
|
||||
}
|
||||
else {
|
||||
info sprintf ' [%s] neigh - skipping unuseable address %s on port %s',
|
||||
$device->ip, $remote_ip, $port;
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
# hack for devices seeing multiple neighbors on the port
|
||||
if (ref [] eq ref $remote_ip) {
|
||||
debug sprintf
|
||||
' [%s] neigh - port %s has multiple neighbors, setting remote as self',
|
||||
$device->ip, $port;
|
||||
|
||||
if (wantarray) {
|
||||
foreach my $n (@$remote_ip) {
|
||||
debug sprintf
|
||||
' [%s] neigh - adding neighbor %s, type [%s], on %s to discovery queue',
|
||||
$device->ip, $n, ($remote_type || ''), $port;
|
||||
push @to_discover, [$n, $remote_type];
|
||||
}
|
||||
}
|
||||
|
||||
# set self as remote IP to suppress any further work
|
||||
$remote_ip = $device->ip;
|
||||
$remote_port = $port;
|
||||
}
|
||||
else {
|
||||
# what we came here to do.... discover the neighbor
|
||||
if (wantarray) {
|
||||
debug sprintf
|
||||
' [%s] neigh - adding neighbor %s, type [%s], on %s to discovery queue',
|
||||
$device->ip, $remote_ip, ($remote_type || ''), $port;
|
||||
push @to_discover, [$remote_ip, $remote_type];
|
||||
}
|
||||
|
||||
# further device type discovery using MAC OUI
|
||||
# only works once device is fully discovered (so we have a MAC addr)
|
||||
my $neigh = get_device($remote_ip);
|
||||
if (blessed $neigh and $neigh->in_storage and $neigh->mac) {
|
||||
if (match_devicetype($neigh->mac, 'phone_ouis')) {
|
||||
$remote_type = 'IP Phone: '. $remote_type
|
||||
if $remote_type !~ /ip.phone/i;
|
||||
}
|
||||
elsif (match_devicetype($neigh->mac, 'wap_ouis')) {
|
||||
$remote_type = 'AP: '. $remote_type
|
||||
if $remote_type !~ /^AP: /;
|
||||
}
|
||||
}
|
||||
|
||||
$remote_port = $c_port->{$entry};
|
||||
if (defined $remote_port) {
|
||||
# clean weird characters
|
||||
$remote_port =~ s/[^\d\/\.,()\w:-]+//gi;
|
||||
}
|
||||
else {
|
||||
info sprintf ' [%s] neigh - no remote port found for port %s at %s',
|
||||
$device->ip, $port, $remote_ip;
|
||||
}
|
||||
}
|
||||
|
||||
$portrow->update({
|
||||
remote_ip => $remote_ip,
|
||||
remote_port => $remote_port,
|
||||
remote_type => $remote_type,
|
||||
remote_id => $remote_id,
|
||||
is_uplink => \"true",
|
||||
manual_topo => \"false",
|
||||
});
|
||||
|
||||
# update master of our aggregate to be a neighbor of
|
||||
# the master on our peer device (a lot of iffs to get there...).
|
||||
# & cannot use ->neighbor prefetch because this is the port insert!
|
||||
if (defined $portrow->slave_of) {
|
||||
|
||||
my $peer_device = get_device($remote_ip);
|
||||
my $master = schema('netdisco')->resultset('DevicePort')->single({
|
||||
ip => $device->ip,
|
||||
port => $portrow->slave_of
|
||||
});
|
||||
|
||||
if ($peer_device and $peer_device->in_storage and $master
|
||||
and not ($portrow->is_master or defined $master->slave_of)) {
|
||||
|
||||
my $peer_port = schema('netdisco')->resultset('DevicePort')->single({
|
||||
ip => $peer_device->ip,
|
||||
port => $portrow->remote_port,
|
||||
});
|
||||
|
||||
$master->update({
|
||||
remote_ip => ($peer_device->ip || $remote_ip),
|
||||
remote_port => ($peer_port ? $peer_port->slave_of : undef ),
|
||||
is_uplink => \"true",
|
||||
is_master => \"true",
|
||||
manual_topo => \"false",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return @to_discover;
|
||||
}
|
||||
|
||||
# take data from the topology table and update remote_ip and remote_port
|
||||
# in the devices table. only use root_ips and skip any bad topo entries.
|
||||
sub _set_manual_topology {
|
||||
my ($device, $snmp) = @_;
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
# clear manual topology flags
|
||||
schema('netdisco')->resultset('DevicePort')
|
||||
->search({ip => $device->ip})->update({manual_topo => \'false'});
|
||||
|
||||
my $topo_links = schema('netdisco')->resultset('Topology')
|
||||
->search({-or => [dev1 => $device->ip, dev2 => $device->ip]});
|
||||
debug sprintf ' [%s] neigh - setting manual topology links', $device->ip;
|
||||
|
||||
while (my $link = $topo_links->next) {
|
||||
# could fail for broken topo, but we ignore to try the rest
|
||||
try {
|
||||
schema('netdisco')->txn_do(sub {
|
||||
# only work on root_ips
|
||||
my $left = get_device($link->dev1);
|
||||
my $right = get_device($link->dev2);
|
||||
|
||||
# skip bad entries
|
||||
return unless ($left->in_storage and $right->in_storage);
|
||||
|
||||
$left->ports
|
||||
->single({port => $link->port1})
|
||||
->update({
|
||||
remote_ip => $right->ip,
|
||||
remote_port => $link->port2,
|
||||
remote_type => undef,
|
||||
remote_id => undef,
|
||||
is_uplink => \"true",
|
||||
manual_topo => \"true",
|
||||
});
|
||||
|
||||
$right->ports
|
||||
->single({port => $link->port2})
|
||||
->update({
|
||||
remote_ip => $left->ip,
|
||||
remote_port => $link->port1,
|
||||
remote_type => undef,
|
||||
remote_id => undef,
|
||||
is_uplink => \"true",
|
||||
manual_topo => \"true",
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
=head2 discover_new_neighbors( $device, $snmp )
|
||||
|
||||
Given a Device database object, and a working SNMP connection, discover and
|
||||
store the device's port neighbors information.
|
||||
|
||||
Entries in the Topology database table will override any discovered device
|
||||
port relationships.
|
||||
|
||||
The Device database object can be a fresh L<DBIx::Class::Row> object which is
|
||||
not yet stored to the database.
|
||||
|
||||
Any discovered neighbor unknown to Netdisco will have a C<discover> job
|
||||
immediately queued (subject to the filtering by the C<discover_*> settings).
|
||||
|
||||
=cut
|
||||
|
||||
sub discover_new_neighbors {
|
||||
my @to_discover = store_neighbors(@_);
|
||||
|
||||
# only enqueue if device is not already discovered,
|
||||
# discover_* config permits the discovery
|
||||
foreach my $neighbor (@to_discover) {
|
||||
my ($ip, $remote_type) = @$neighbor;
|
||||
|
||||
my $device = get_device($ip);
|
||||
next if $device->in_storage;
|
||||
|
||||
if (not is_discoverable($device, $remote_type)) {
|
||||
debug sprintf
|
||||
' queue - %s, type [%s] excluded by discover_* config',
|
||||
$ip, ($remote_type || '');
|
||||
next;
|
||||
}
|
||||
|
||||
jq_insert({
|
||||
device => $ip,
|
||||
action => 'discover',
|
||||
subaction => 'with-nodes',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
543
lib/App/Netdisco/Core/Macsuck.pm
Normal file
543
lib/App/Netdisco/Core/Macsuck.pm
Normal file
@@ -0,0 +1,543 @@
|
||||
package App::Netdisco::Core::Macsuck;
|
||||
|
||||
use Dancer qw/:syntax :script/;
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
|
||||
use App::Netdisco::Util::PortMAC 'get_port_macs';
|
||||
use App::Netdisco::Util::Device qw/check_device_no match_devicetype/;
|
||||
use App::Netdisco::Util::Node 'check_mac';
|
||||
use App::Netdisco::Util::SNMP 'snmp_comm_reindex';
|
||||
use Time::HiRes 'gettimeofday';
|
||||
|
||||
use base 'Exporter';
|
||||
our @EXPORT = ();
|
||||
our @EXPORT_OK = qw/
|
||||
do_macsuck
|
||||
store_node
|
||||
store_wireless_client_info
|
||||
/;
|
||||
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||
|
||||
=head1 NAME
|
||||
|
||||
App::Netdisco::Core::Macsuck
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Helper subroutines to support parts of the Netdisco application.
|
||||
|
||||
There are no default exports, however the C<:all> tag will export all
|
||||
subroutines.
|
||||
|
||||
=head1 EXPORT_OK
|
||||
|
||||
=head2 do_macsuck( $device, $snmp )
|
||||
|
||||
Given a Device database object, and a working SNMP connection, connect to a
|
||||
device and discover the MAC addresses listed against each physical port
|
||||
without a neighbor.
|
||||
|
||||
If the device has VLANs, C<do_macsuck> will walk each VLAN to get the MAC
|
||||
addresses from there.
|
||||
|
||||
It will also gather wireless client information if C<store_wireless_clients>
|
||||
configuration setting is enabled.
|
||||
|
||||
=cut
|
||||
|
||||
sub do_macsuck {
|
||||
my ($device, $snmp) = @_;
|
||||
|
||||
unless ($device->in_storage) {
|
||||
debug sprintf
|
||||
' [%s] macsuck - skipping device not yet discovered',
|
||||
$device->ip;
|
||||
return;
|
||||
}
|
||||
|
||||
my $ip = $device->ip;
|
||||
|
||||
# would be possible just to use now() on updated records, but by using this
|
||||
# same value for them all, we can if we want add a job at the end to
|
||||
# select and do something with the updated set (see set archive, below)
|
||||
my $now = 'to_timestamp('. (join '.', gettimeofday) .')';
|
||||
my $total_nodes = 0;
|
||||
|
||||
# do this before we start messing with the snmp community string
|
||||
store_wireless_client_info($device, $snmp, $now);
|
||||
|
||||
# cache the device ports to save hitting the database for many single rows
|
||||
my $device_ports = {map {($_->port => $_)}
|
||||
$device->ports(undef, {prefetch => {neighbor_alias => 'device'}})->all};
|
||||
my $port_macs = get_port_macs();
|
||||
my $interfaces = $snmp->interfaces;
|
||||
|
||||
# get forwarding table data via basic snmp connection
|
||||
my $fwtable = _walk_fwtable($device, $snmp, $interfaces, $port_macs, $device_ports);
|
||||
|
||||
# ...then per-vlan if supported
|
||||
my @vlan_list = _get_vlan_list($device, $snmp);
|
||||
foreach my $vlan (@vlan_list) {
|
||||
snmp_comm_reindex($snmp, $device, $vlan);
|
||||
my $pv_fwtable = _walk_fwtable($device, $snmp, $interfaces, $port_macs, $device_ports, $vlan);
|
||||
$fwtable = {%$fwtable, %$pv_fwtable};
|
||||
}
|
||||
|
||||
# now it's time to call store_node for every node discovered
|
||||
# on every port on every vlan on this device.
|
||||
|
||||
# reverse sort allows vlan 0 entries to be included only as fallback
|
||||
foreach my $vlan (reverse sort keys %$fwtable) {
|
||||
foreach my $port (keys %{ $fwtable->{$vlan} }) {
|
||||
debug sprintf ' [%s] macsuck - port %s vlan %s : %s nodes',
|
||||
$ip, $port, $vlan, scalar keys %{ $fwtable->{$vlan}->{$port} };
|
||||
|
||||
# make sure this port is UP in netdisco (unless it's a lag master,
|
||||
# because we can still see nodes without a functioning aggregate)
|
||||
$device_ports->{$port}->update({up_admin => 'up', up => 'up'})
|
||||
if not $device_ports->{$port}->is_master;
|
||||
|
||||
foreach my $mac (keys %{ $fwtable->{$vlan}->{$port} }) {
|
||||
|
||||
# remove vlan 0 entry for this MAC addr
|
||||
delete $fwtable->{0}->{$_}->{$mac}
|
||||
for keys %{ $fwtable->{0} };
|
||||
|
||||
++$total_nodes;
|
||||
store_node($ip, $vlan, $port, $mac, $now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug sprintf ' [%s] macsuck - %s updated forwarding table entries',
|
||||
$ip, $total_nodes;
|
||||
|
||||
# a use for $now ... need to archive dissapeared nodes
|
||||
my $archived = 0;
|
||||
|
||||
if (setting('node_freshness')) {
|
||||
$archived = schema('netdisco')->resultset('Node')->search({
|
||||
switch => $ip,
|
||||
time_last => \[ "< ($now - ?::interval)",
|
||||
setting('node_freshness') .' minutes' ],
|
||||
})->update({ active => \'false' });
|
||||
}
|
||||
|
||||
debug sprintf ' [%s] macsuck - removed %d fwd table entries to archive',
|
||||
$ip, $archived;
|
||||
|
||||
$device->update({last_macsuck => \$now});
|
||||
}
|
||||
|
||||
=head2 store_node( $ip, $vlan, $port, $mac, $now? )
|
||||
|
||||
Writes a fresh entry to the Netdisco C<node> database table. Will mark old
|
||||
entries for this data as no longer C<active>.
|
||||
|
||||
All four fields in the tuple are required. If you don't know the VLAN ID,
|
||||
Netdisco supports using ID "0".
|
||||
|
||||
Optionally, a fifth argument can be the literal string passed to the time_last
|
||||
field of the database record. If not provided, it defauls to C<now()>.
|
||||
|
||||
=cut
|
||||
|
||||
sub store_node {
|
||||
my ($ip, $vlan, $port, $mac, $now) = @_;
|
||||
$now ||= 'now()';
|
||||
$vlan ||= 0;
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $nodes = schema('netdisco')->resultset('Node');
|
||||
|
||||
my $old = $nodes->search(
|
||||
{ mac => $mac,
|
||||
# where vlan is unknown, need to archive on all other vlans
|
||||
($vlan ? (vlan => $vlan) : ()),
|
||||
-bool => 'active',
|
||||
-not => {
|
||||
switch => $ip,
|
||||
port => $port,
|
||||
},
|
||||
})->update( { active => \'false' } );
|
||||
|
||||
# new data
|
||||
$nodes->update_or_create(
|
||||
{
|
||||
switch => $ip,
|
||||
port => $port,
|
||||
vlan => $vlan,
|
||||
mac => $mac,
|
||||
active => \'true',
|
||||
oui => substr($mac,0,8),
|
||||
time_last => \$now,
|
||||
(($old != 0) ? (time_recent => \$now) : ()),
|
||||
},
|
||||
{
|
||||
key => 'primary',
|
||||
for => 'update',
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
# return a list of vlan numbers which are OK to macsuck on this device
|
||||
sub _get_vlan_list {
|
||||
my ($device, $snmp) = @_;
|
||||
|
||||
return () unless $snmp->cisco_comm_indexing;
|
||||
|
||||
my (%vlans, %vlan_names);
|
||||
my $i_vlan = $snmp->i_vlan || {};
|
||||
my $trunks = $snmp->i_vlan_membership || {};
|
||||
my $i_type = $snmp->i_type || {};
|
||||
|
||||
# get list of vlans in use
|
||||
while (my ($idx, $vlan) = each %$i_vlan) {
|
||||
# hack: if vlan id comes as 1.142 instead of 142
|
||||
$vlan =~ s/^\d+\.//;
|
||||
|
||||
# VLANs are ports interfaces capture VLAN, but don't count as in use
|
||||
# Port channels are also 'propVirtual', but capture while checking
|
||||
# trunk VLANs below
|
||||
if (exists $i_type->{$idx} and $i_type->{$idx} eq 'propVirtual') {
|
||||
$vlans{$vlan} ||= 0;
|
||||
}
|
||||
else {
|
||||
++$vlans{$vlan};
|
||||
}
|
||||
foreach my $t_vlan (@{$trunks->{$idx}}) {
|
||||
++$vlans{$t_vlan};
|
||||
}
|
||||
}
|
||||
|
||||
unless (scalar keys %vlans) {
|
||||
debug sprintf ' [%s] macsuck - no VLANs found.', $device->ip;
|
||||
return ();
|
||||
}
|
||||
|
||||
my $v_name = $snmp->v_name || {};
|
||||
|
||||
# get vlan names (required for config which filters by name)
|
||||
while (my ($idx, $name) = each %$v_name) {
|
||||
# hack: if vlan id comes as 1.142 instead of 142
|
||||
(my $vlan = $idx) =~ s/^\d+\.//;
|
||||
|
||||
# just in case i_vlan is different to v_name set
|
||||
# capture the VLAN, but it's not in use on a port
|
||||
$vlans{$vlan} ||= 0;
|
||||
|
||||
$vlan_names{$vlan} = $name;
|
||||
}
|
||||
|
||||
debug sprintf ' [%s] macsuck - VLANs: %s', $device->ip,
|
||||
(join ',', sort keys %vlans);
|
||||
|
||||
my @ok_vlans = ();
|
||||
foreach my $vlan (sort keys %vlans) {
|
||||
my $name = $vlan_names{$vlan} || '(unnamed)';
|
||||
|
||||
if (ref [] eq ref setting('macsuck_no_vlan')) {
|
||||
my $ignore = setting('macsuck_no_vlan');
|
||||
|
||||
if ((scalar grep {$_ eq $vlan} @$ignore) or
|
||||
(scalar grep {$_ eq $name} @$ignore)) {
|
||||
|
||||
debug sprintf
|
||||
' [%s] macsuck VLAN %s - skipped by macsuck_no_vlan config',
|
||||
$device->ip, $vlan;
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
if (ref [] eq ref setting('macsuck_no_devicevlan')) {
|
||||
my $ignore = setting('macsuck_no_devicevlan');
|
||||
my $ip = $device->ip;
|
||||
|
||||
if ((scalar grep {$_ eq "$ip:$vlan"} @$ignore) or
|
||||
(scalar grep {$_ eq "$ip:$name"} @$ignore)) {
|
||||
|
||||
debug sprintf
|
||||
' [%s] macsuck VLAN %s - skipped by macsuck_no_devicevlan config',
|
||||
$device->ip, $vlan;
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
if (setting('macsuck_no_unnamed') and $name eq '(unnamed)') {
|
||||
debug sprintf
|
||||
' [%s] macsuck VLAN %s - skipped by macsuck_no_unnamed config',
|
||||
$device->ip, $vlan;
|
||||
next;
|
||||
}
|
||||
|
||||
if ($vlan == 0 or $vlan > 4094) {
|
||||
debug sprintf ' [%s] macsuck - invalid VLAN number %s',
|
||||
$device->ip, $vlan;
|
||||
next;
|
||||
}
|
||||
|
||||
# check in use by a port on this device
|
||||
if (!$vlans{$vlan} && !setting('macsuck_all_vlans')) {
|
||||
|
||||
debug sprintf
|
||||
' [%s] macsuck VLAN %s/%s - not in use by any port - skipping.',
|
||||
$device->ip, $vlan, $name;
|
||||
next;
|
||||
}
|
||||
|
||||
push @ok_vlans, $vlan;
|
||||
}
|
||||
|
||||
return @ok_vlans;
|
||||
}
|
||||
|
||||
# walks the forwarding table (BRIDGE-MIB) for the device and returns a
|
||||
# table of node entries.
|
||||
sub _walk_fwtable {
|
||||
my ($device, $snmp, $interfaces, $port_macs, $device_ports, $comm_vlan) = @_;
|
||||
my $skiplist = {}; # ports through which we can see another device
|
||||
my $cache = {};
|
||||
|
||||
my $fw_mac = $snmp->fw_mac;
|
||||
my $fw_port = $snmp->fw_port;
|
||||
my $fw_vlan = $snmp->qb_fw_vlan;
|
||||
my $bp_index = $snmp->bp_index;
|
||||
|
||||
# to map forwarding table port to device port we have
|
||||
# fw_port -> bp_index -> interfaces
|
||||
|
||||
while (my ($idx, $mac) = each %$fw_mac) {
|
||||
my $bp_id = $fw_port->{$idx};
|
||||
next unless check_mac($device, $mac);
|
||||
|
||||
unless (defined $bp_id) {
|
||||
debug sprintf
|
||||
' [%s] macsuck %s - %s has no fw_port mapping - skipping.',
|
||||
$device->ip, $mac, $idx;
|
||||
next;
|
||||
}
|
||||
|
||||
my $iid = $bp_index->{$bp_id};
|
||||
|
||||
unless (defined $iid) {
|
||||
debug sprintf
|
||||
' [%s] macsuck %s - port %s has no bp_index mapping - skipping.',
|
||||
$device->ip, $mac, $bp_id;
|
||||
next;
|
||||
}
|
||||
|
||||
my $port = $interfaces->{$iid};
|
||||
|
||||
unless (defined $port) {
|
||||
debug sprintf
|
||||
' [%s] macsuck %s - iid %s has no port mapping - skipping.',
|
||||
$device->ip, $mac, $iid;
|
||||
next;
|
||||
}
|
||||
|
||||
if (exists $skiplist->{$port}) {
|
||||
debug sprintf
|
||||
' [%s] macsuck %s - seen another device thru port %s - skipping.',
|
||||
$device->ip, $mac, $port;
|
||||
next;
|
||||
}
|
||||
|
||||
# this uses the cached $ports resultset to limit hits on the db
|
||||
my $device_port = $device_ports->{$port};
|
||||
|
||||
unless (defined $device_port) {
|
||||
debug sprintf
|
||||
' [%s] macsuck %s - port %s is not in database - skipping.',
|
||||
$device->ip, $mac, $port;
|
||||
next;
|
||||
}
|
||||
|
||||
my $vlan = $fw_vlan->{$idx} || $comm_vlan || '0';
|
||||
|
||||
# check to see if the port is connected to another device
|
||||
# and if we have that device in the database.
|
||||
|
||||
# we have several ways to detect "uplink" port status:
|
||||
# * a neighbor was discovered using CDP/LLDP
|
||||
# * a mac addr is seen which belongs to any device port/interface
|
||||
# * (TODO) admin sets is_uplink_admin on the device_port
|
||||
|
||||
# allow to gather MACs on upstream port for some kinds of device that
|
||||
# do not expose MAC address tables via SNMP. relies on prefetched
|
||||
# neighbors otherwise it would kill the DB with device lookups.
|
||||
my $neigh_cannot_macsuck = eval { # can fail
|
||||
check_device_no($device_port->neighbor, 'macsuck_unsupported') ||
|
||||
match_devicetype($device_port->remote_type, 'macsuck_unsupported_type') };
|
||||
|
||||
if ($device_port->is_uplink) {
|
||||
if ($neigh_cannot_macsuck) {
|
||||
debug sprintf
|
||||
' [%s] macsuck %s - port %s neighbor %s without macsuck support',
|
||||
$device->ip, $mac, $port,
|
||||
(eval { $device_port->neighbor->ip }
|
||||
|| ($device_port->remote_ip
|
||||
|| $device_port->remote_id || '?'));
|
||||
# continue!!
|
||||
}
|
||||
elsif (my $neighbor = $device_port->neighbor) {
|
||||
debug sprintf
|
||||
' [%s] macsuck %s - port %s has neighbor %s - skipping.',
|
||||
$device->ip, $mac, $port, $neighbor->ip;
|
||||
next;
|
||||
}
|
||||
elsif (my $remote = $device_port->remote_ip) {
|
||||
debug sprintf
|
||||
' [%s] macsuck %s - port %s has undiscovered neighbor %s',
|
||||
$device->ip, $mac, $port, $remote;
|
||||
# continue!!
|
||||
}
|
||||
elsif (not setting('macsuck_bleed')) {
|
||||
debug sprintf
|
||||
' [%s] macsuck %s - port %s is detected uplink - skipping.',
|
||||
$device->ip, $mac, $port;
|
||||
|
||||
$skiplist->{$port} = [ $vlan, $mac ] # remember for later
|
||||
if exists $port_macs->{$mac};
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
if (exists $port_macs->{$mac}) {
|
||||
my $switch_ip = $port_macs->{$mac};
|
||||
if ($device->ip eq $switch_ip) {
|
||||
debug sprintf
|
||||
' [%s] macsuck %s - port %s connects to self - skipping.',
|
||||
$device->ip, $mac, $port;
|
||||
next;
|
||||
}
|
||||
|
||||
debug sprintf ' [%s] macsuck %s - port %s is probably an uplink',
|
||||
$device->ip, $mac, $port;
|
||||
$device_port->update({is_uplink => \'true'});
|
||||
|
||||
# neighbor exists and Netdisco can speak to it, so we don't want
|
||||
# its MAC address. however don't add to skiplist as that would
|
||||
# clear all other MACs on the port.
|
||||
next if $neigh_cannot_macsuck;
|
||||
|
||||
# when there's no CDP/LLDP, we only want to gather macs at the
|
||||
# topology edge, hence skip ports with known device macs.
|
||||
if (not setting('macsuck_bleed')) {
|
||||
debug sprintf ' [%s] macsuck %s - adding port %s to skiplist',
|
||||
$device->ip, $mac, $port;
|
||||
|
||||
$skiplist->{$port} = [ $vlan, $mac ]; # remember for later
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
# possibly move node to lag master
|
||||
if (defined $device_port->slave_of
|
||||
and exists $device_ports->{$device_port->slave_of}) {
|
||||
$port = $device_port->slave_of;
|
||||
$device_ports->{$port}->update({is_uplink => \'true'});
|
||||
}
|
||||
|
||||
++$cache->{$vlan}->{$port}->{$mac};
|
||||
}
|
||||
|
||||
# restore MACs of neighbor devices.
|
||||
# this is when we have a "possible uplink" detected but we still want to
|
||||
# record the single MAC of the neighbor device so it works in Node search.
|
||||
foreach my $port (keys %$skiplist) {
|
||||
my ($vlan, $mac) = @{ $skiplist->{$port} };
|
||||
delete $cache->{$_}->{$port} for keys %$cache; # nuke nodes on all VLANs
|
||||
++$cache->{$vlan}->{$port}->{$mac};
|
||||
}
|
||||
|
||||
return $cache;
|
||||
}
|
||||
|
||||
=head2 store_wireless_client_info( $device, $snmp, $now? )
|
||||
|
||||
Given a Device database object, and a working SNMP connection, connect to a
|
||||
device and discover 802.11 related information for all connected wireless
|
||||
clients.
|
||||
|
||||
If the device doesn't support the 802.11 MIBs, then this will silently return.
|
||||
|
||||
If the device does support the 802.11 MIBs but Netdisco's configuration
|
||||
does not permit polling (C<store_wireless_clients> must be true) then a debug
|
||||
message is logged and the subroutine returns.
|
||||
|
||||
Otherwise, client information is gathered and stored to the database.
|
||||
|
||||
Optionally, a third argument can be the literal string passed to the time_last
|
||||
field of the database record. If not provided, it defauls to C<now()>.
|
||||
|
||||
=cut
|
||||
|
||||
sub store_wireless_client_info {
|
||||
my ($device, $snmp, $now) = @_;
|
||||
$now ||= 'now()';
|
||||
|
||||
my $cd11_txrate = $snmp->cd11_txrate;
|
||||
return unless $cd11_txrate and scalar keys %$cd11_txrate;
|
||||
|
||||
if (setting('store_wireless_clients')) {
|
||||
debug sprintf ' [%s] macsuck - gathering wireless client info',
|
||||
$device->ip;
|
||||
}
|
||||
else {
|
||||
debug sprintf ' [%s] macsuck - dot11 info available but skipped due to config',
|
||||
$device->ip;
|
||||
return;
|
||||
}
|
||||
|
||||
my $cd11_rateset = $snmp->cd11_rateset();
|
||||
my $cd11_uptime = $snmp->cd11_uptime();
|
||||
my $cd11_sigstrength = $snmp->cd11_sigstrength();
|
||||
my $cd11_sigqual = $snmp->cd11_sigqual();
|
||||
my $cd11_mac = $snmp->cd11_mac();
|
||||
my $cd11_port = $snmp->cd11_port();
|
||||
my $cd11_rxpkt = $snmp->cd11_rxpkt();
|
||||
my $cd11_txpkt = $snmp->cd11_txpkt();
|
||||
my $cd11_rxbyte = $snmp->cd11_rxbyte();
|
||||
my $cd11_txbyte = $snmp->cd11_txbyte();
|
||||
my $cd11_ssid = $snmp->cd11_ssid();
|
||||
|
||||
while (my ($idx, $txrates) = each %$cd11_txrate) {
|
||||
my $rates = $cd11_rateset->{$idx};
|
||||
my $mac = $cd11_mac->{$idx};
|
||||
next unless defined $mac; # avoid null entries
|
||||
# there can be more rows in txrate than other tables
|
||||
|
||||
my $txrate = defined $txrates->[$#$txrates]
|
||||
? int($txrates->[$#$txrates])
|
||||
: undef;
|
||||
|
||||
my $maxrate = defined $rates->[$#$rates]
|
||||
? int($rates->[$#$rates])
|
||||
: undef;
|
||||
|
||||
my $ssid = $cd11_ssid->{$idx} || 'unknown';
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
schema('netdisco')->resultset('NodeWireless')
|
||||
->search({ 'me.mac' => $mac, 'me.ssid' => $ssid })
|
||||
->update_or_create({
|
||||
txrate => $txrate,
|
||||
maxrate => $maxrate,
|
||||
uptime => $cd11_uptime->{$idx},
|
||||
rxpkt => $cd11_rxpkt->{$idx},
|
||||
txpkt => $cd11_txpkt->{$idx},
|
||||
rxbyte => $cd11_rxbyte->{$idx},
|
||||
txbyte => $cd11_txbyte->{$idx},
|
||||
sigqual => $cd11_sigqual->{$idx},
|
||||
sigstrength => $cd11_sigstrength->{$idx},
|
||||
time_last => \$now,
|
||||
}, {
|
||||
order_by => [qw/mac ssid/],
|
||||
for => 'update',
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
179
lib/App/Netdisco/Core/Nbtstat.pm
Normal file
179
lib/App/Netdisco/Core/Nbtstat.pm
Normal file
@@ -0,0 +1,179 @@
|
||||
package App::Netdisco::Core::Nbtstat;
|
||||
|
||||
use Dancer qw/:syntax :script/;
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
|
||||
use App::Netdisco::Util::Node 'check_mac';
|
||||
use NetAddr::IP::Lite ':lower';
|
||||
use App::Netdisco::AnyEvent::Nbtstat;
|
||||
use Encode;
|
||||
|
||||
use base 'Exporter';
|
||||
our @EXPORT = ();
|
||||
our @EXPORT_OK = qw/ nbtstat_resolve_async store_nbt /;
|
||||
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||
|
||||
=head1 NAME
|
||||
|
||||
App::Netdisco::Core::Nbtstat
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Helper subroutines to support parts of the Netdisco application.
|
||||
|
||||
There are no default exports, however the C<:all> tag will export all
|
||||
subroutines.
|
||||
|
||||
=head1 EXPORT_OK
|
||||
|
||||
=head2 nbtstat_resolve_async( $ips )
|
||||
|
||||
This method uses an asynchronous AnyEvent NetBIOS node status requester
|
||||
C<App::Netdisco::AnyEvent::Nbtstat>.
|
||||
|
||||
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
|
||||
|
||||
sub nbtstat_resolve_async {
|
||||
my $ips = shift;
|
||||
|
||||
my $timeout = setting('nbtstat_timeout') || 1;
|
||||
my $interval = setting('nbtstat_interval') || 0.02;
|
||||
|
||||
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;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
# Decrement the cv counter to cancel out the send declaration
|
||||
$cv->end;
|
||||
|
||||
# Wait for the resolver to perform all resolutions
|
||||
$cv->recv;
|
||||
|
||||
# Close sockets
|
||||
undef $stater;
|
||||
|
||||
return $ips;
|
||||
}
|
||||
|
||||
# filter nbt names / information
|
||||
sub _filter_nbname {
|
||||
my $ip = shift;
|
||||
my $hash_ref = shift;
|
||||
my $node_status = shift;
|
||||
|
||||
my $server = 0;
|
||||
my $nbname = '';
|
||||
my $domain = '';
|
||||
my $nbuser = '';
|
||||
|
||||
for my $rr ( @{$node_status->{'names'}} ) {
|
||||
my $suffix = defined $rr->{'suffix'} ? $rr->{'suffix'} : -1;
|
||||
my $G = defined $rr->{'G'} ? $rr->{'G'} : '';
|
||||
my $name = defined $rr->{'name'} ? $rr->{'name'} : '';
|
||||
|
||||
if ( $suffix == 0 and $G eq "GROUP" ) {
|
||||
$domain = $name;
|
||||
}
|
||||
if ( $suffix == 3 and $G eq "UNIQUE" ) {
|
||||
$nbuser = $name;
|
||||
}
|
||||
if ( $suffix == 0 and $G eq "UNIQUE" ) {
|
||||
$nbname = $name unless $name =~ /^IS~/;
|
||||
}
|
||||
if ( $suffix == 32 and $G eq "UNIQUE" ) {
|
||||
$server = 1;
|
||||
}
|
||||
}
|
||||
|
||||
unless ($nbname) {
|
||||
debug sprintf ' nbtstat no computer name found for %s', $ip;
|
||||
return;
|
||||
}
|
||||
|
||||
my $mac = $node_status->{'mac_address'} || '';
|
||||
|
||||
unless ( check_mac( $ip, $mac ) ) {
|
||||
|
||||
# Just assume it's the last MAC we saw this IP at.
|
||||
my $node_ip = schema('netdisco')->resultset('NodeIp')
|
||||
->single( { ip => $ip, -bool => 'active' } );
|
||||
|
||||
if ( !defined $node_ip ) {
|
||||
debug sprintf ' no MAC for %s returned by nbtstat or in DB', $ip;
|
||||
return;
|
||||
}
|
||||
$mac = $node_ip->mac;
|
||||
}
|
||||
|
||||
$hash_ref->{'ip'} = $ip;
|
||||
$hash_ref->{'mac'} = $mac;
|
||||
$hash_ref->{'nbname'} = Encode::decode('UTF-8', $nbname);
|
||||
$hash_ref->{'domain'} = Encode::decode('UTF-8', $domain);
|
||||
$hash_ref->{'server'} = $server;
|
||||
$hash_ref->{'nbuser'} = Encode::decode('UTF-8', $nbuser);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
=head2 store_nbt($nb_hash_ref, $now?)
|
||||
|
||||
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
|
||||
Workgroup C<domain>, whether the Server Service is running C<server>,
|
||||
and the current NetBIOS user C<nbuser>.
|
||||
|
||||
Adds new entry or time stamps matching one.
|
||||
|
||||
Optionally a literal string can be passed in the second argument for the
|
||||
C<time_last> timestamp, otherwise the current timestamp (C<now()>) is used.
|
||||
|
||||
=cut
|
||||
|
||||
sub store_nbt {
|
||||
my ( $hash_ref, $now ) = @_;
|
||||
$now ||= 'now()';
|
||||
|
||||
schema('netdisco')->resultset('NodeNbt')->update_or_create(
|
||||
{ mac => $hash_ref->{'mac'},
|
||||
ip => $hash_ref->{'ip'},
|
||||
nbname => $hash_ref->{'nbname'},
|
||||
domain => $hash_ref->{'domain'},
|
||||
server => $hash_ref->{'server'},
|
||||
nbuser => $hash_ref->{'nbuser'},
|
||||
active => \'true',
|
||||
time_last => \$now,
|
||||
},
|
||||
{ key => 'primary',
|
||||
for => 'update',
|
||||
}
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
1;
|
||||
31
lib/App/Netdisco/DB.pm
Normal file
31
lib/App/Netdisco/DB.pm
Normal file
@@ -0,0 +1,31 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Schema';
|
||||
|
||||
__PACKAGE__->load_namespaces(
|
||||
default_resultset_class => 'ResultSet',
|
||||
);
|
||||
|
||||
our # try to hide from kwalitee
|
||||
$VERSION # try harder
|
||||
= 41; # schema version used for upgrades, keep as integer
|
||||
|
||||
use Path::Class;
|
||||
use File::Basename;
|
||||
|
||||
my (undef, $libpath, undef) = fileparse( $INC{ 'App/Netdisco/DB.pm' } );
|
||||
our $schema_versions_dir = Path::Class::Dir->new($libpath)
|
||||
->subdir("DB", "schema_versions")->stringify;
|
||||
|
||||
__PACKAGE__->load_components(qw/
|
||||
Schema::Versioned
|
||||
+App::Netdisco::DB::ExplicitLocking
|
||||
/);
|
||||
|
||||
__PACKAGE__->upgrade_directory($schema_versions_dir);
|
||||
|
||||
1;
|
||||
165
lib/App/Netdisco/DB/ExplicitLocking.pm
Normal file
165
lib/App/Netdisco/DB/ExplicitLocking.pm
Normal file
@@ -0,0 +1,165 @@
|
||||
package App::Netdisco::DB::ExplicitLocking;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
our %lock_modes;
|
||||
|
||||
BEGIN {
|
||||
%lock_modes = (
|
||||
ACCESS_SHARE => 'ACCESS SHARE',
|
||||
ROW_SHARE => 'ROW SHARE',
|
||||
ROW_EXCLUSIVE => 'ROW EXCLUSIVE',
|
||||
SHARE_UPDATE_EXCLUSIVE => 'SHARE UPDATE EXCLUSIVE',
|
||||
SHARE => 'SHARE',
|
||||
SHARE_ROW_EXCLUSIVE => 'SHARE ROW EXCLUSIVE',
|
||||
EXCLUSIVE => 'EXCLUSIVE',
|
||||
ACCESS_EXCLUSIVE => 'ACCESS EXCLUSIVE',
|
||||
);
|
||||
}
|
||||
|
||||
use constant \%lock_modes;
|
||||
|
||||
use base 'Exporter';
|
||||
our @EXPORT = ();
|
||||
our @EXPORT_OK = (keys %lock_modes);
|
||||
our %EXPORT_TAGS = (modes => \@EXPORT_OK);
|
||||
|
||||
sub txn_do_locked {
|
||||
my ($self, $table, $mode, $sub) = @_;
|
||||
my $sql_fmt = q{LOCK TABLE %s IN %%s MODE};
|
||||
my $schema = $self;
|
||||
|
||||
if ($self->can('result_source')) {
|
||||
# ResultSet component
|
||||
$sub = $mode;
|
||||
$mode = $table;
|
||||
$table = $self->result_source->from;
|
||||
$schema = $self->result_source->schema;
|
||||
}
|
||||
|
||||
$schema->throw_exception('missing Table name to txn_do_locked()')
|
||||
unless $table;
|
||||
|
||||
$table = [$table] if ref '' eq ref $table;
|
||||
my $table_fmt = join ', ', ('%s' x scalar @$table);
|
||||
my $sql = sprintf $sql_fmt, $table_fmt;
|
||||
|
||||
if (ref '' eq ref $mode and $mode) {
|
||||
scalar grep {$_ eq $mode} values %lock_modes
|
||||
or $schema->throw_exception('bad LOCK_MODE to txn_do_locked()');
|
||||
}
|
||||
else {
|
||||
$sub = $mode;
|
||||
$mode = 'ACCESS EXCLUSIVE';
|
||||
}
|
||||
|
||||
$schema->txn_do(sub {
|
||||
my @params = map {$schema->storage->dbh->quote_identifier($_)} @$table;
|
||||
$schema->storage->dbh->do(sprintf $sql, @params, $mode);
|
||||
$sub->();
|
||||
});
|
||||
}
|
||||
|
||||
=head1 NAME
|
||||
|
||||
App::Netdisco::DB::ExplicitLocking - Support for PostgreSQL Lock Modes
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
In your L<DBIx::Class> schema:
|
||||
|
||||
package My::Schema;
|
||||
__PACKAGE__->load_components('+App::Netdisco::DB::ExplicitLocking');
|
||||
|
||||
Then, in your application code:
|
||||
|
||||
use App::Netdisco::DB::ExplicitLocking ':modes';
|
||||
$schema->txn_do_locked($table, MODE_NAME, sub { ... });
|
||||
|
||||
This also works for the ResultSet:
|
||||
|
||||
package My::Schema::ResultSet::TableName;
|
||||
__PACKAGE__->load_components('+App::Netdisco::DB::ExplicitLocking');
|
||||
|
||||
Then, in your application code:
|
||||
|
||||
use App::Netdisco::DB::ExplicitLocking ':modes';
|
||||
$schema->resultset('TableName')->txn_do_locked(MODE_NAME, sub { ... });
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This L<DBIx::Class> component provides an easy way to execute PostgreSQL table
|
||||
locks before a transaction block.
|
||||
|
||||
You can load the component in either the Schema class or ResultSet class (or
|
||||
both) and then use an interface very similar to C<DBIx::Class>'s C<txn_do()>.
|
||||
|
||||
The package also exports constants for each of the table lock modes supported
|
||||
by PostgreSQL, which must be used if specifying the mode (default mode is
|
||||
C<ACCESS EXCLUSIVE>).
|
||||
|
||||
=head1 EXPORTS
|
||||
|
||||
With the C<:modes> tag (as in SYNOPSIS above) the following constants are
|
||||
exported and must be used if specifying the lock mode:
|
||||
|
||||
=over 4
|
||||
|
||||
=item * C<ACCESS_SHARE>
|
||||
|
||||
=item * C<ROW_SHARE>
|
||||
|
||||
=item * C<ROW_EXCLUSIVE>
|
||||
|
||||
=item * C<SHARE_UPDATE_EXCLUSIVE>
|
||||
|
||||
=item * C<SHARE>
|
||||
|
||||
=item * C<SHARE_ROW_EXCLUSIVE>
|
||||
|
||||
=item * C<EXCLUSIVE>
|
||||
|
||||
=item * C<ACCESS_EXCLUSIVE>
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 C<< $schema->txn_do_locked($table|\@tables, MODE_NAME?, $subref) >>
|
||||
|
||||
This is the method signature used when the component is loaded into your
|
||||
Schema class. The reason you might want to use this over the ResultSet version
|
||||
(below) is to specify multiple tables to be locked before the transaction.
|
||||
|
||||
The first argument is one or more tables, and is required. Note that these are
|
||||
the real table names in PostgreSQL, and not C<DBIx::Class> ResultSet aliases
|
||||
or anything like that.
|
||||
|
||||
The mode name is optional, and defaults to C<ACCESS EXCLUSIVE>. You must use
|
||||
one of the exported constants in this parameter.
|
||||
|
||||
Finally pass a subroutine reference, just as you would to the normal
|
||||
C<DBIx::Class> C<txn_do()> method. Note that additional arguments are not
|
||||
supported.
|
||||
|
||||
=head2 C<< $resultset->txn_do_locked(MODE_NAME?, $subref) >>
|
||||
|
||||
This is the method signature used when the component is loaded into your
|
||||
ResultSet class. If you don't yet have a ResultSet class (which is the default
|
||||
- normally only Result classes are created) then you can create a stub which
|
||||
simply loads this component (and inherits from C<DBIx::Class::ResultSet>).
|
||||
|
||||
This is the simplest way to use this module if you only want to lock one table
|
||||
before your transaction block.
|
||||
|
||||
The first argument is the optional mode name, which defaults to C<ACCESS
|
||||
EXCLUSIVE>. You must use one of the exported constants in this parameter.
|
||||
|
||||
The second argument is a subroutine reference, just as you would pass to the
|
||||
normal C<DBIx::Class> C<txn_do()> method. Note that additional arguments are
|
||||
not supported.
|
||||
|
||||
=cut
|
||||
|
||||
1;
|
||||
117
lib/App/Netdisco/DB/Result/Admin.pm
Normal file
117
lib/App/Netdisco/DB/Result/Admin.pm
Normal file
@@ -0,0 +1,117 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::Admin;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("admin");
|
||||
__PACKAGE__->add_columns(
|
||||
"job",
|
||||
{
|
||||
data_type => "integer",
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0,
|
||||
sequence => "admin_job_seq",
|
||||
},
|
||||
"entered",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
"started",
|
||||
{ data_type => "timestamp", is_nullable => 1 },
|
||||
"finished",
|
||||
{ data_type => "timestamp", is_nullable => 1 },
|
||||
"device",
|
||||
{ data_type => "inet", is_nullable => 1 },
|
||||
"port",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"action",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"subaction",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"status",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"username",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"userip",
|
||||
{ data_type => "inet", is_nullable => 1 },
|
||||
"log",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"debug",
|
||||
{ data_type => "boolean", is_nullable => 1 },
|
||||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:gW4JW4pMgrufFIxFeYPYpw
|
||||
|
||||
__PACKAGE__->set_primary_key("job");
|
||||
|
||||
# You can replace this text with custom code or comments, and it will be preserved on regeneration
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 summary
|
||||
|
||||
An attempt to make a meaningful statement about the job.
|
||||
|
||||
=cut
|
||||
|
||||
sub summary {
|
||||
my $job = shift;
|
||||
return join ' ',
|
||||
$job->action,
|
||||
($job->device || ''),
|
||||
($job->port || '');
|
||||
# ($job->subaction ? (q{'}. $job->subaction .q{'}) : '');
|
||||
}
|
||||
|
||||
=head1 ADDITIONAL COLUMNS
|
||||
|
||||
=head2 entererd_stamp
|
||||
|
||||
Formatted version of the C<entered> 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 entered_stamp { return (shift)->get_column('entered_stamp') }
|
||||
|
||||
=head2 started_stamp
|
||||
|
||||
Formatted version of the C<started> 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 started_stamp { return (shift)->get_column('started_stamp') }
|
||||
|
||||
=head2 finished_stamp
|
||||
|
||||
Formatted version of the C<finished> 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 finished_stamp { return (shift)->get_column('finished_stamp') }
|
||||
|
||||
1;
|
||||
21
lib/App/Netdisco/DB/Result/Community.pm
Normal file
21
lib/App/Netdisco/DB/Result/Community.pm
Normal file
@@ -0,0 +1,21 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::Community;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("community");
|
||||
__PACKAGE__->add_columns(
|
||||
"ip",
|
||||
{ data_type => "inet", is_nullable => 0 },
|
||||
"snmp_comm_rw",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"snmp_auth_tag_read",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"snmp_auth_tag_write",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
);
|
||||
__PACKAGE__->set_primary_key("ip");
|
||||
|
||||
1;
|
||||
357
lib/App/Netdisco/DB/Result/Device.pm
Normal file
357
lib/App/Netdisco/DB/Result/Device.pm
Normal file
@@ -0,0 +1,357 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::Device;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use NetAddr::IP::Lite ':lower';
|
||||
use App::Netdisco::Util::DNS 'hostname_from_ip';
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__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 },
|
||||
"ports",
|
||||
{ data_type => "integer", is_nullable => 1 },
|
||||
"mac",
|
||||
{ data_type => "macaddr", is_nullable => 1 },
|
||||
"serial",
|
||||
{ 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 },
|
||||
"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 },
|
||||
);
|
||||
__PACKAGE__->set_primary_key("ip");
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:671/XuuvsO2aMB1+IRWFjg
|
||||
|
||||
=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 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 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 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' }
|
||||
);
|
||||
|
||||
# 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 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');
|
||||
|
||||
=head1 ADDITIONAL METHODS
|
||||
|
||||
=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';
|
||||
|
||||
foreach my $set (qw/
|
||||
DeviceIp
|
||||
DeviceModule
|
||||
DevicePower
|
||||
DeviceVlan
|
||||
DevicePort
|
||||
DevicePortLog
|
||||
DevicePortPower
|
||||
DevicePortSsid
|
||||
DevicePortVlan
|
||||
DevicePortWireless
|
||||
Community
|
||||
/) {
|
||||
$schema->resultset($set)
|
||||
->search({ip => $old_ip})
|
||||
->update({ip => $new_ip});
|
||||
}
|
||||
|
||||
$schema->resultset('DevicePort')
|
||||
->search({remote_ip => $old_ip})
|
||||
->update({remote_ip => $new_ip});
|
||||
|
||||
$schema->resultset('Admin')
|
||||
->search({device => $old_ip})
|
||||
->update({device => $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});
|
||||
|
||||
$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 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;
|
||||
59
lib/App/Netdisco/DB/Result/DeviceIp.pm
Normal file
59
lib/App/Netdisco/DB/Result/DeviceIp.pm
Normal file
@@ -0,0 +1,59 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::DeviceIp;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("device_ip");
|
||||
__PACKAGE__->add_columns(
|
||||
"ip",
|
||||
{ data_type => "inet", is_nullable => 0 },
|
||||
"alias",
|
||||
{ data_type => "inet", is_nullable => 0 },
|
||||
"subnet",
|
||||
{ data_type => "cidr", is_nullable => 1 },
|
||||
"port",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"dns",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"creation",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
);
|
||||
__PACKAGE__->set_primary_key("ip", "alias");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:/ugGtBSGyrJ7s6yqJ9bclQ
|
||||
|
||||
=head1 RELATIONSHIPS
|
||||
|
||||
=head2 device
|
||||
|
||||
Returns the entry from the C<device> table to which this IP alias relates.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( device => 'App::Netdisco::DB::Result::Device', 'ip' );
|
||||
|
||||
=head2 device_port
|
||||
|
||||
Returns the Port on which this IP address is configured (typically a loopback,
|
||||
routed port or virtual interface).
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->add_unique_constraint(['alias']);
|
||||
|
||||
__PACKAGE__->belongs_to( device_port => 'App::Netdisco::DB::Result::DevicePort',
|
||||
{ 'foreign.port' => 'self.port', 'foreign.ip' => 'self.ip' } );
|
||||
|
||||
1;
|
||||
68
lib/App/Netdisco/DB/Result/DeviceModule.pm
Normal file
68
lib/App/Netdisco/DB/Result/DeviceModule.pm
Normal file
@@ -0,0 +1,68 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::DeviceModule;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("device_module");
|
||||
__PACKAGE__->add_columns(
|
||||
"ip",
|
||||
{ data_type => "inet", is_nullable => 0 },
|
||||
"index",
|
||||
{ data_type => "integer", is_nullable => 0 },
|
||||
"description",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"type",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"parent",
|
||||
{ data_type => "integer", is_nullable => 1 },
|
||||
"name",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"class",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"pos",
|
||||
{ data_type => "integer", is_nullable => 1 },
|
||||
"hw_ver",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"fw_ver",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"sw_ver",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"serial",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"model",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"fru",
|
||||
{ data_type => "boolean", is_nullable => 1 },
|
||||
"creation",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
"last_discover",
|
||||
{ data_type => "timestamp", is_nullable => 1 },
|
||||
);
|
||||
__PACKAGE__->set_primary_key("ip", "index");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:nuwxZBoiip9trdJFmgk3Fw
|
||||
|
||||
=head1 RELATIONSHIPS
|
||||
|
||||
=head2 device
|
||||
|
||||
Returns the entry from the C<device> table on which this VLAN entry was discovered.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( device => 'App::Netdisco::DB::Result::Device', 'ip' );
|
||||
|
||||
# You can replace this text with custom code or comments, and it will be preserved on regeneration
|
||||
1;
|
||||
378
lib/App/Netdisco/DB/Result/DevicePort.pm
Normal file
378
lib/App/Netdisco/DB/Result/DevicePort.pm
Normal file
@@ -0,0 +1,378 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::DevicePort;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use NetAddr::MAC;
|
||||
|
||||
use MIME::Base64 'encode_base64url';
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("device_port");
|
||||
__PACKAGE__->add_columns(
|
||||
"ip",
|
||||
{ data_type => "inet", is_nullable => 0 },
|
||||
"port",
|
||||
{ data_type => "text", is_nullable => 0 },
|
||||
"creation",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
"descr",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"up",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"up_admin",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"type",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"duplex",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"duplex_admin",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"speed",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"name",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"mac",
|
||||
{ data_type => "macaddr", is_nullable => 1 },
|
||||
"mtu",
|
||||
{ data_type => "integer", is_nullable => 1 },
|
||||
"stp",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"remote_ip",
|
||||
{ data_type => "inet", is_nullable => 1 },
|
||||
"remote_port",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"remote_type",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"remote_id",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"is_master",
|
||||
{ data_type => "bool", is_nullable => 0, default_value => \"false" },
|
||||
"slave_of",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"manual_topo",
|
||||
{ data_type => "bool", is_nullable => 0, default_value => \"false" },
|
||||
"is_uplink",
|
||||
{ data_type => "bool", is_nullable => 1 },
|
||||
"vlan",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"pvid",
|
||||
{ data_type => "integer", is_nullable => 1 },
|
||||
"lastchange",
|
||||
{ data_type => "bigint", is_nullable => 1 },
|
||||
);
|
||||
__PACKAGE__->set_primary_key("port", "ip");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:lcbweb0loNwHoWUuxTN/hA
|
||||
|
||||
=head1 RELATIONSHIPS
|
||||
|
||||
=head2 device
|
||||
|
||||
Returns the Device table entry to which the given Port is related.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( device => 'App::Netdisco::DB::Result::Device', 'ip' );
|
||||
|
||||
=head2 port_vlans
|
||||
|
||||
Returns the set of C<device_port_vlan> entries associated with this Port.
|
||||
These will be both tagged and untagged. Use this relation in search conditions.
|
||||
|
||||
See also C<all_port_vlans>.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->has_many( port_vlans => 'App::Netdisco::DB::Result::DevicePortVlan',
|
||||
{ 'foreign.ip' => 'self.ip', 'foreign.port' => 'self.port' } );
|
||||
|
||||
=head2 all_port_vlans
|
||||
|
||||
Returns the set of C<device_port_vlan> entries associated with this Port.
|
||||
These will be both tagged and untagged. Use this relation when prefetching related
|
||||
C<device_port_vlan> rows.
|
||||
|
||||
See also C<port_vlans>.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->has_many( all_port_vlans => 'App::Netdisco::DB::Result::DevicePortVlan',
|
||||
{ 'foreign.ip' => 'self.ip', 'foreign.port' => 'self.port' } );
|
||||
|
||||
=head2 nodes / active_nodes / nodes_with_age / active_nodes_with_age
|
||||
|
||||
Returns the set of Nodes whose MAC addresses are associated with this Device
|
||||
Port.
|
||||
|
||||
The C<active> variants return only the subset of nodes currently in the switch
|
||||
MAC address table, that is the active ones.
|
||||
|
||||
The C<with_age> variants add an additional column C<time_last_age>, a
|
||||
preformatted value for the Node's C<time_last> field, which reads as "X
|
||||
days/weeks/months/years".
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->has_many( nodes => 'App::Netdisco::DB::Result::Node',
|
||||
{
|
||||
'foreign.switch' => 'self.ip',
|
||||
'foreign.port' => 'self.port',
|
||||
},
|
||||
{ join_type => 'LEFT' },
|
||||
);
|
||||
|
||||
__PACKAGE__->has_many( nodes_with_age => 'App::Netdisco::DB::Result::Virtual::NodeWithAge',
|
||||
{
|
||||
'foreign.switch' => 'self.ip',
|
||||
'foreign.port' => 'self.port',
|
||||
},
|
||||
{ join_type => 'LEFT',
|
||||
cascade_copy => 0, cascade_update => 0, cascade_delete => 0 },
|
||||
);
|
||||
|
||||
__PACKAGE__->has_many( active_nodes => 'App::Netdisco::DB::Result::Virtual::ActiveNode',
|
||||
{
|
||||
'foreign.switch' => 'self.ip',
|
||||
'foreign.port' => 'self.port',
|
||||
},
|
||||
{ join_type => 'LEFT',
|
||||
cascade_copy => 0, cascade_update => 0, cascade_delete => 0 },
|
||||
);
|
||||
|
||||
__PACKAGE__->has_many( active_nodes_with_age => 'App::Netdisco::DB::Result::Virtual::ActiveNodeWithAge',
|
||||
{
|
||||
'foreign.switch' => 'self.ip',
|
||||
'foreign.port' => 'self.port',
|
||||
},
|
||||
{ join_type => 'LEFT',
|
||||
cascade_copy => 0, cascade_update => 0, cascade_delete => 0 },
|
||||
);
|
||||
|
||||
=head2 logs
|
||||
|
||||
Returns the set of C<device_port_log> entries associated with this Port.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->has_many( logs => 'App::Netdisco::DB::Result::DevicePortLog',
|
||||
{ 'foreign.ip' => 'self.ip', 'foreign.port' => 'self.port' },
|
||||
);
|
||||
|
||||
=head2 power
|
||||
|
||||
Returns a row from the C<device_port_power> table if one refers to this
|
||||
device port.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->might_have( power => 'App::Netdisco::DB::Result::DevicePortPower', {
|
||||
'foreign.ip' => 'self.ip', 'foreign.port' => 'self.port',
|
||||
});
|
||||
|
||||
=head2 ssid
|
||||
|
||||
Returns a row from the C<device_port_ssid> table if one refers to this
|
||||
device port.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->might_have(
|
||||
ssid => 'App::Netdisco::DB::Result::DevicePortSsid',
|
||||
{ 'foreign.ip' => 'self.ip',
|
||||
'foreign.port' => 'self.port',
|
||||
}
|
||||
);
|
||||
|
||||
=head2 wireless
|
||||
|
||||
Returns a row from the C<device_port_wireless> table if one refers to this
|
||||
device port.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->might_have(
|
||||
wireless => 'App::Netdisco::DB::Result::DevicePortWireless',
|
||||
{ 'foreign.ip' => 'self.ip',
|
||||
'foreign.port' => 'self.port',
|
||||
}
|
||||
);
|
||||
|
||||
=head2 agg_master
|
||||
|
||||
Returns another row from the C<device_port> table if this port is slave
|
||||
to another in a link aggregate.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
agg_master => 'App::Netdisco::DB::Result::DevicePort', {
|
||||
'foreign.ip' => 'self.ip',
|
||||
'foreign.port' => 'self.slave_of',
|
||||
}, {
|
||||
join_type => 'LEFT',
|
||||
}
|
||||
);
|
||||
|
||||
=head2 neighbor_alias
|
||||
|
||||
When a device port has an attached neighbor device, this relationship will
|
||||
return the IP address of the neighbor. See the C<neighbor> helper method if
|
||||
what you really want is to retrieve the Device entry for that neighbor.
|
||||
|
||||
The JOIN is of type "LEFT" in case the neighbor device is known but has not
|
||||
been fully discovered by Netdisco and so does not exist itself in the
|
||||
database.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( neighbor_alias => 'App::Netdisco::DB::Result::DeviceIp',
|
||||
sub {
|
||||
my $args = shift;
|
||||
return {
|
||||
"$args->{foreign_alias}.ip" => { '=' =>
|
||||
$args->{self_resultsource}->schema->resultset('DeviceIp')
|
||||
->search({alias => { -ident => "$args->{self_alias}.remote_ip"}},
|
||||
{rows => 1, columns => 'ip', alias => 'devipsub'})->as_query }
|
||||
};
|
||||
},
|
||||
{ join_type => 'LEFT' },
|
||||
);
|
||||
|
||||
=head2 vlans
|
||||
|
||||
As compared to C<port_vlans>, this relationship returns a set of VLAN
|
||||
row objects for the VLANs on the given port, which might be more useful if you
|
||||
want to find out details such as the VLAN name.
|
||||
|
||||
See also C<vlan_count>.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->many_to_many( vlans => 'all_port_vlans', 'vlan' );
|
||||
|
||||
|
||||
=head2 oui
|
||||
|
||||
Returns the C<oui> table entry matching this Port. You can then join on this
|
||||
relation and retrieve the Company name from the related table.
|
||||
|
||||
The JOIN is of type LEFT, in case the OUI table has not been populated.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( oui => 'App::Netdisco::DB::Result::Oui',
|
||||
sub {
|
||||
my $args = shift;
|
||||
return {
|
||||
"$args->{foreign_alias}.oui" =>
|
||||
{ '=' => \"substring(cast($args->{self_alias}.mac as varchar) for 8)" }
|
||||
};
|
||||
},
|
||||
{ join_type => 'LEFT' }
|
||||
);
|
||||
|
||||
=head1 ADDITIONAL METHODS
|
||||
|
||||
=head2 neighbor
|
||||
|
||||
Returns the Device entry for the neighbour Device on the given port.
|
||||
|
||||
Might return an undefined value if there is no neighbor on the port, or if the
|
||||
neighbor has not been fully discovered by Netdisco and so does not exist in
|
||||
the database.
|
||||
|
||||
=cut
|
||||
|
||||
sub neighbor {
|
||||
my $row = shift;
|
||||
return eval { $row->neighbor_alias->device || undef };
|
||||
}
|
||||
|
||||
=head1 ADDITIONAL COLUMNS
|
||||
|
||||
=head2 native
|
||||
|
||||
An alias for the C<vlan> column, which stores the PVID (that is, the VLAN
|
||||
ID assigned to untagged frames received on the port).
|
||||
|
||||
=cut
|
||||
|
||||
sub native { return (shift)->vlan }
|
||||
|
||||
=head2 vlan_count
|
||||
|
||||
Returns the number of VLANs active on this device port. Enable this column by
|
||||
applying the C<with_vlan_count()> modifier to C<search()>.
|
||||
|
||||
=cut
|
||||
|
||||
sub vlan_count { return (shift)->get_column('vlan_count') }
|
||||
|
||||
=head2 lastchange_stamp
|
||||
|
||||
Formatted version of the C<lastchange> field, accurate to the minute. Enable
|
||||
this column by applying the C<with_times()> modifier to C<search()>.
|
||||
|
||||
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 lastchange_stamp { return (shift)->get_column('lastchange_stamp') }
|
||||
|
||||
=head2 is_free
|
||||
|
||||
This method can be used to evaluate whether a device port could be considered
|
||||
unused, based on the last time it changed from the "up" state to a "down"
|
||||
state.
|
||||
|
||||
See the C<with_is_free> and C<only_free_ports> modifiers to C<search()>.
|
||||
|
||||
=cut
|
||||
|
||||
sub is_free { return (shift)->get_column('is_free') }
|
||||
|
||||
=head2 base64url_port
|
||||
|
||||
Returns a Base64 encoded version of the C<port> column value suitable for use
|
||||
in a URL.
|
||||
|
||||
=cut
|
||||
|
||||
sub base64url_port { return encode_base64url((shift)->port) }
|
||||
|
||||
=head2 net_mac
|
||||
|
||||
Returns the C<mac> column instantiated into a L<NetAddr::MAC> object.
|
||||
|
||||
=cut
|
||||
|
||||
sub net_mac { return NetAddr::MAC->new(mac => (shift)->mac) }
|
||||
|
||||
=head2 last_comment
|
||||
|
||||
Returns the most recent comment from the logs for this device port.
|
||||
|
||||
=cut
|
||||
|
||||
sub last_comment {
|
||||
my $row = (shift)->logs->search(undef,
|
||||
{ order_by => { -desc => 'creation' }, rows => 1 })->first;
|
||||
return ($row ? $row->log : '');
|
||||
}
|
||||
|
||||
1;
|
||||
60
lib/App/Netdisco/DB/Result/DevicePortLog.pm
Normal file
60
lib/App/Netdisco/DB/Result/DevicePortLog.pm
Normal file
@@ -0,0 +1,60 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::DevicePortLog;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("device_port_log");
|
||||
__PACKAGE__->add_columns(
|
||||
"id",
|
||||
{
|
||||
data_type => "integer",
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0,
|
||||
sequence => "device_port_log_id_seq",
|
||||
},
|
||||
"ip",
|
||||
{ data_type => "inet", is_nullable => 1 },
|
||||
"port",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"reason",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"log",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"username",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"userip",
|
||||
{ data_type => "inet", is_nullable => 1 },
|
||||
"action",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"creation",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key("id");
|
||||
|
||||
=head1 ADDITIONAL COLUMNS
|
||||
|
||||
=head2 creation_stamp
|
||||
|
||||
Formatted version of the C<creation> field, accurate to the second.
|
||||
|
||||
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:23
|
||||
|
||||
=cut
|
||||
|
||||
sub creation_stamp { return (shift)->get_column('creation_stamp') }
|
||||
|
||||
1;
|
||||
57
lib/App/Netdisco/DB/Result/DevicePortPower.pm
Normal file
57
lib/App/Netdisco/DB/Result/DevicePortPower.pm
Normal file
@@ -0,0 +1,57 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::DevicePortPower;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("device_port_power");
|
||||
__PACKAGE__->add_columns(
|
||||
"ip",
|
||||
{ data_type => "inet", is_nullable => 0 },
|
||||
"port",
|
||||
{ data_type => "text", is_nullable => 0 },
|
||||
"module",
|
||||
{ data_type => "integer", is_nullable => 1 },
|
||||
"admin",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"status",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"class",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"power",
|
||||
{ data_type => "integer", is_nullable => 1 },
|
||||
);
|
||||
__PACKAGE__->set_primary_key("port", "ip");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:sHcdItRUFUOAtIZQjdWbcg
|
||||
|
||||
=head1 RELATIONSHIPS
|
||||
|
||||
=head2 port
|
||||
|
||||
Returns the entry from the C<port> table for which this Power entry applies.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( port => 'App::Netdisco::DB::Result::DevicePort', {
|
||||
'foreign.ip' => 'self.ip', 'foreign.port' => 'self.port',
|
||||
});
|
||||
|
||||
=head2 device_module
|
||||
|
||||
Returns the entry from the C<device_power> table for which this Power entry
|
||||
applies.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( device_module => 'App::Netdisco::DB::Result::DevicePower', {
|
||||
'foreign.ip' => 'self.ip', 'foreign.module' => 'self.module',
|
||||
});
|
||||
|
||||
1;
|
||||
67
lib/App/Netdisco/DB/Result/DevicePortSsid.pm
Normal file
67
lib/App/Netdisco/DB/Result/DevicePortSsid.pm
Normal file
@@ -0,0 +1,67 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::DevicePortSsid;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("device_port_ssid");
|
||||
__PACKAGE__->add_columns(
|
||||
"ip",
|
||||
{ data_type => "inet", is_nullable => 0 },
|
||||
"port",
|
||||
{ data_type => "text", is_nullable => 0 },
|
||||
"ssid",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"broadcast",
|
||||
{ data_type => "boolean", is_nullable => 1 },
|
||||
"bssid",
|
||||
{ data_type => "macaddr", is_nullable => 1 },
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key("port", "ip");
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:zvgylKzUQtizJZCe1rEdUg
|
||||
|
||||
=head1 RELATIONSHIPS
|
||||
|
||||
=head2 device
|
||||
|
||||
Returns the entry from the C<device> table which hosts this SSID.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( device => 'App::Netdisco::DB::Result::Device', 'ip' );
|
||||
|
||||
=head2 port
|
||||
|
||||
Returns the entry from the C<port> table which corresponds to this SSID.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( port => 'App::Netdisco::DB::Result::DevicePort', {
|
||||
'foreign.ip' => 'self.ip', 'foreign.port' => 'self.port',
|
||||
});
|
||||
|
||||
=head2 nodes
|
||||
|
||||
Returns the set of Nodes whose MAC addresses are associated with this Device
|
||||
Port SSID.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->has_many( nodes => 'App::Netdisco::DB::Result::Node',
|
||||
{
|
||||
'foreign.switch' => 'self.ip',
|
||||
'foreign.port' => 'self.port',
|
||||
},
|
||||
{ join_type => 'LEFT',
|
||||
cascade_copy => 0, cascade_update => 0, cascade_delete => 0 },
|
||||
);
|
||||
|
||||
# You can replace this text with custom code or comments, and it will be preserved on regeneration
|
||||
1;
|
||||
76
lib/App/Netdisco/DB/Result/DevicePortVlan.pm
Normal file
76
lib/App/Netdisco/DB/Result/DevicePortVlan.pm
Normal file
@@ -0,0 +1,76 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::DevicePortVlan;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("device_port_vlan");
|
||||
__PACKAGE__->add_columns(
|
||||
"ip",
|
||||
{ data_type => "inet", is_nullable => 0 },
|
||||
"port",
|
||||
{ data_type => "text", is_nullable => 0 },
|
||||
"vlan",
|
||||
{ data_type => "integer", is_nullable => 0 },
|
||||
"native",
|
||||
{ data_type => "boolean", default_value => \"false", is_nullable => 0 },
|
||||
"creation",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
"last_discover",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
"vlantype",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
);
|
||||
__PACKAGE__->set_primary_key("ip", "port", "vlan", "native");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:/3KLjJ3D18pGaPEaw9EU5w
|
||||
|
||||
=head1 RELATIONSHIPS
|
||||
|
||||
=head2 device
|
||||
|
||||
Returns the entry from the C<device> table which hosts the Port on which this
|
||||
VLAN is configured.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( device => 'App::Netdisco::DB::Result::Device', 'ip' );
|
||||
|
||||
=head2 port
|
||||
|
||||
Returns the entry from the C<port> table on which this VLAN is configured.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( port => 'App::Netdisco::DB::Result::DevicePort', {
|
||||
'foreign.ip' => 'self.ip', 'foreign.port' => 'self.port',
|
||||
});
|
||||
|
||||
=head2 vlan
|
||||
|
||||
Returns the entry from the C<device_vlan> table describing this VLAN in
|
||||
detail, typically in order that the C<name> can be retrieved.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( vlan => 'App::Netdisco::DB::Result::DeviceVlan', {
|
||||
'foreign.ip' => 'self.ip', 'foreign.vlan' => 'self.vlan',
|
||||
});
|
||||
|
||||
1;
|
||||
66
lib/App/Netdisco/DB/Result/DevicePortWireless.pm
Normal file
66
lib/App/Netdisco/DB/Result/DevicePortWireless.pm
Normal file
@@ -0,0 +1,66 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::DevicePortWireless;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("device_port_wireless");
|
||||
__PACKAGE__->add_columns(
|
||||
"ip",
|
||||
{ data_type => "inet", is_nullable => 0 },
|
||||
"port",
|
||||
{ data_type => "text", is_nullable => 0 },
|
||||
"channel",
|
||||
{ data_type => "integer", is_nullable => 1 },
|
||||
"power",
|
||||
{ data_type => "integer", is_nullable => 1 },
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key("port", "ip");
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:T5GmnCj/9BB7meiGZ3xN7g
|
||||
|
||||
=head1 RELATIONSHIPS
|
||||
|
||||
=head2 device
|
||||
|
||||
Returns the entry from the C<device> table which hosts this wireless port.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( device => 'App::Netdisco::DB::Result::Device', 'ip' );
|
||||
|
||||
=head2 port
|
||||
|
||||
Returns the entry from the C<port> table which corresponds to this wireless
|
||||
interface.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( port => 'App::Netdisco::DB::Result::DevicePort', {
|
||||
'foreign.ip' => 'self.ip', 'foreign.port' => 'self.port',
|
||||
});
|
||||
|
||||
=head2 nodes
|
||||
|
||||
Returns the set of Nodes whose MAC addresses are associated with this Device
|
||||
Port Wireless.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->has_many( nodes => 'App::Netdisco::DB::Result::Node',
|
||||
{
|
||||
'foreign.switch' => 'self.ip',
|
||||
'foreign.port' => 'self.port',
|
||||
},
|
||||
{ join_type => 'LEFT',
|
||||
cascade_copy => 0, cascade_update => 0, cascade_delete => 0 },
|
||||
);
|
||||
|
||||
# You can replace this text with custom code or comments, and it will be preserved on regeneration
|
||||
1;
|
||||
49
lib/App/Netdisco/DB/Result/DevicePower.pm
Normal file
49
lib/App/Netdisco/DB/Result/DevicePower.pm
Normal file
@@ -0,0 +1,49 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::DevicePower;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("device_power");
|
||||
__PACKAGE__->add_columns(
|
||||
"ip",
|
||||
{ data_type => "inet", is_nullable => 0 },
|
||||
"module",
|
||||
{ data_type => "integer", is_nullable => 0 },
|
||||
"power",
|
||||
{ data_type => "integer", is_nullable => 1 },
|
||||
"status",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
);
|
||||
__PACKAGE__->set_primary_key("ip", "module");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:awZRI/IH2VewzGlxISsr7w
|
||||
|
||||
=head1 RELATIONSHIPS
|
||||
|
||||
=head2 device
|
||||
|
||||
Returns the entry from the C<device> table on which this power module was discovered.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( device => 'App::Netdisco::DB::Result::Device', 'ip' );
|
||||
|
||||
=head2 ports
|
||||
|
||||
Returns the set of PoE ports associated with a power module.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->has_many( ports => 'App::Netdisco::DB::Result::DevicePortPower', {
|
||||
'foreign.ip' => 'self.ip', 'foreign.module' => 'self.module',
|
||||
} );
|
||||
|
||||
# You can replace this text with custom code or comments, and it will be preserved on regeneration
|
||||
1;
|
||||
42
lib/App/Netdisco/DB/Result/DeviceRoute.pm
Normal file
42
lib/App/Netdisco/DB/Result/DeviceRoute.pm
Normal file
@@ -0,0 +1,42 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::DeviceRoute;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("device_route");
|
||||
__PACKAGE__->add_columns(
|
||||
"ip",
|
||||
{ data_type => "inet", is_nullable => 0 },
|
||||
"network",
|
||||
{ data_type => "cidr", is_nullable => 0 },
|
||||
"creation",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
"dest",
|
||||
{ data_type => "inet", is_nullable => 0 },
|
||||
"last_discover",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
);
|
||||
__PACKAGE__->set_primary_key("ip", "network", "dest");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:3jcvPP60E5BvwnUbXql7mQ
|
||||
|
||||
|
||||
# You can replace this text with custom code or comments, and it will be preserved on regeneration
|
||||
1;
|
||||
113
lib/App/Netdisco/DB/Result/DeviceVlan.pm
Normal file
113
lib/App/Netdisco/DB/Result/DeviceVlan.pm
Normal file
@@ -0,0 +1,113 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::DeviceVlan;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("device_vlan");
|
||||
__PACKAGE__->add_columns(
|
||||
"ip",
|
||||
{ data_type => "inet", is_nullable => 0 },
|
||||
"vlan",
|
||||
{ data_type => "integer", is_nullable => 0 },
|
||||
"description",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"creation",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
"last_discover",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
);
|
||||
__PACKAGE__->set_primary_key("ip", "vlan");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:hBJRcdzOic4d3u4pD1m8iA
|
||||
|
||||
=head1 RELATIONSHIPS
|
||||
|
||||
=head2 device
|
||||
|
||||
Returns the entry from the C<device> table on which this VLAN entry was discovered.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( device => 'App::Netdisco::DB::Result::Device', 'ip' );
|
||||
|
||||
=head2 port_vlans_tagged
|
||||
|
||||
Link relationship for C<tagged_ports>, see below.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->has_many( port_vlans_tagged => 'App::Netdisco::DB::Result::DevicePortVlan',
|
||||
sub {
|
||||
my $args = shift;
|
||||
return {
|
||||
"$args->{foreign_alias}.ip" => { -ident => "$args->{self_alias}.ip" },
|
||||
"$args->{foreign_alias}.vlan" => { -ident => "$args->{self_alias}.vlan" },
|
||||
-not_bool => "$args->{foreign_alias}.native",
|
||||
};
|
||||
},
|
||||
{ cascade_copy => 0, cascade_update => 0, cascade_delete => 0 }
|
||||
);
|
||||
|
||||
=head2 port_vlans_untagged
|
||||
|
||||
Link relationship to support C<untagged_ports>, see below.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->has_many( port_vlans_untagged => 'App::Netdisco::DB::Result::DevicePortVlan',
|
||||
sub {
|
||||
my $args = shift;
|
||||
return {
|
||||
"$args->{foreign_alias}.ip" => { -ident => "$args->{self_alias}.ip" },
|
||||
"$args->{foreign_alias}.vlan" => { -ident => "$args->{self_alias}.vlan" },
|
||||
-bool => "$args->{foreign_alias}.native",
|
||||
};
|
||||
},
|
||||
{ cascade_copy => 0, cascade_update => 0, cascade_delete => 0 }
|
||||
);
|
||||
|
||||
=head2 ports
|
||||
|
||||
Link relationship to support C<ports>.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->has_many( ports => 'App::Netdisco::DB::Result::DevicePortVlan',
|
||||
{ 'foreign.ip' => 'self.ip', 'foreign.vlan' => 'self.vlan' },
|
||||
{ cascade_copy => 0, cascade_update => 0, cascade_delete => 0 }
|
||||
);
|
||||
|
||||
=head2 tagged_ports
|
||||
|
||||
Returns the set of Device Ports on which this VLAN is configured to be tagged.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->many_to_many( tagged_ports => 'port_vlans_tagged', 'port' );
|
||||
|
||||
=head2 untagged_ports
|
||||
|
||||
Returns the set of Device Ports on which this VLAN is an untagged VLAN.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->many_to_many( untagged_ports => 'port_vlans_untagged', 'port' );
|
||||
|
||||
1;
|
||||
41
lib/App/Netdisco/DB/Result/Log.pm
Normal file
41
lib/App/Netdisco/DB/Result/Log.pm
Normal file
@@ -0,0 +1,41 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::Log;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("log");
|
||||
__PACKAGE__->add_columns(
|
||||
"id",
|
||||
{
|
||||
data_type => "integer",
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0,
|
||||
sequence => "log_id_seq",
|
||||
},
|
||||
"creation",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
"class",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"entry",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"logfile",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:eonwOHvvzWm88Ug+IGKuzg
|
||||
|
||||
|
||||
# You can replace this text with custom code or comments, and it will be preserved on regeneration
|
||||
1;
|
||||
202
lib/App/Netdisco/DB/Result/Node.pm
Normal file
202
lib/App/Netdisco/DB/Result/Node.pm
Normal file
@@ -0,0 +1,202 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::Node;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use NetAddr::MAC;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("node");
|
||||
__PACKAGE__->add_columns(
|
||||
"mac",
|
||||
{ data_type => "macaddr", is_nullable => 0 },
|
||||
"switch",
|
||||
{ data_type => "inet", is_nullable => 0 },
|
||||
"port",
|
||||
{ data_type => "text", is_nullable => 0 },
|
||||
"active",
|
||||
{ data_type => "boolean", is_nullable => 1 },
|
||||
"oui",
|
||||
{ data_type => "varchar", is_nullable => 1, size => 8 },
|
||||
"time_first",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
"time_recent",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
"time_last",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
"vlan",
|
||||
{ data_type => "text", is_nullable => 0, default_value => '0' },
|
||||
);
|
||||
__PACKAGE__->set_primary_key("mac", "switch", "port", "vlan");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:sGGyKEfUkoIFVtmj1wnH7A
|
||||
|
||||
=head1 RELATIONSHIPS
|
||||
|
||||
=head2 device
|
||||
|
||||
Returns the single C<device> to which this Node entry was associated at the
|
||||
time of discovery.
|
||||
|
||||
The JOIN is of type LEFT, in case the C<device> is no longer present in the
|
||||
database but the relation is being used in C<search()>.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( device => 'App::Netdisco::DB::Result::Device',
|
||||
{ 'foreign.ip' => 'self.switch' }, { join_type => 'LEFT' } );
|
||||
|
||||
=head2 device_port
|
||||
|
||||
Returns the single C<device_port> to which this Node entry was associated at
|
||||
the time of discovery.
|
||||
|
||||
The JOIN is of type LEFT, in case the C<device> is no longer present in the
|
||||
database but the relation is being used in C<search()>.
|
||||
|
||||
=cut
|
||||
|
||||
# device port may have been deleted (reconfigured modules?) but node remains
|
||||
__PACKAGE__->belongs_to( device_port => 'App::Netdisco::DB::Result::DevicePort',
|
||||
{ 'foreign.ip' => 'self.switch', 'foreign.port' => 'self.port' },
|
||||
{ join_type => 'LEFT' }
|
||||
);
|
||||
|
||||
=head2 wireless_port
|
||||
|
||||
Returns the single C<wireless_port> to which this Node entry was associated at
|
||||
the time of discovery.
|
||||
|
||||
The JOIN is of type LEFT, in case the C<device> is no longer present in the
|
||||
database but the relation is being used in C<search()>.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
wireless_port => 'App::Netdisco::DB::Result::DevicePortWireless',
|
||||
{ 'foreign.ip' => 'self.switch', 'foreign.port' => 'self.port' },
|
||||
{ join_type => 'LEFT' }
|
||||
);
|
||||
|
||||
=head2 ips
|
||||
|
||||
Returns the set of C<node_ip> entries associated with this Node. That is, the
|
||||
IP addresses which this MAC address was hosting at the time of discovery.
|
||||
|
||||
Note that the Active status of the returned IP entries will all be the same as
|
||||
the current Node's.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->has_many( ips => 'App::Netdisco::DB::Result::NodeIp',
|
||||
{ 'foreign.mac' => 'self.mac', 'foreign.active' => 'self.active' } );
|
||||
|
||||
=head2 ip4s
|
||||
|
||||
Same as C<ips> but for IPv4 only.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->has_many( ip4s => 'App::Netdisco::DB::Result::Virtual::NodeIp4',
|
||||
{ 'foreign.mac' => 'self.mac', 'foreign.active' => 'self.active' } );
|
||||
|
||||
=head2 ip6s
|
||||
|
||||
Same as C<ips> but for IPv6 only.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->has_many( ip6s => 'App::Netdisco::DB::Result::Virtual::NodeIp6',
|
||||
{ 'foreign.mac' => 'self.mac', 'foreign.active' => 'self.active' } );
|
||||
|
||||
=head2 netbios
|
||||
|
||||
Returns the C<node_nbt> entry associated with this Node if one exists. That
|
||||
is, the NetBIOS information of this MAC address at the time of discovery.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->might_have( netbios => 'App::Netdisco::DB::Result::NodeNbt',
|
||||
{ 'foreign.mac' => 'self.mac' } );
|
||||
|
||||
=head2 wireless
|
||||
|
||||
Returns the set of C<node_wireless> entries associated with this Node. That
|
||||
is, the SSIDs and wireless statistics associated with this MAC address
|
||||
at the time of discovery.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->has_many( wireless => 'App::Netdisco::DB::Result::NodeWireless',
|
||||
{ 'foreign.mac' => 'self.mac' } );
|
||||
|
||||
=head2 oui
|
||||
|
||||
Returns the C<oui> table entry matching this Node. You can then join on this
|
||||
relation and retrieve the Company name from the related table.
|
||||
|
||||
The JOIN is of type LEFT, in case the OUI table has not been populated.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( oui => 'App::Netdisco::DB::Result::Oui', 'oui',
|
||||
{ join_type => 'LEFT' } );
|
||||
|
||||
=head1 ADDITIONAL COLUMNS
|
||||
|
||||
=head2 time_first_stamp
|
||||
|
||||
Formatted version of the C<time_first> 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 time_first_stamp { return (shift)->get_column('time_first_stamp') }
|
||||
|
||||
=head2 time_last_stamp
|
||||
|
||||
Formatted version of the C<time_last> 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 time_last_stamp { return (shift)->get_column('time_last_stamp') }
|
||||
|
||||
=head2 net_mac
|
||||
|
||||
Returns the C<mac> column instantiated into a L<NetAddr::MAC> object.
|
||||
|
||||
=cut
|
||||
|
||||
sub net_mac { return NetAddr::MAC->new(mac => (shift)->mac) }
|
||||
|
||||
1;
|
||||
230
lib/App/Netdisco/DB/Result/NodeIp.pm
Normal file
230
lib/App/Netdisco/DB/Result/NodeIp.pm
Normal file
@@ -0,0 +1,230 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::NodeIp;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use NetAddr::MAC;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("node_ip");
|
||||
__PACKAGE__->add_columns(
|
||||
"mac",
|
||||
{ data_type => "macaddr", is_nullable => 0 },
|
||||
"ip",
|
||||
{ data_type => "inet", is_nullable => 0 },
|
||||
"dns",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"active",
|
||||
{ data_type => "boolean", is_nullable => 1 },
|
||||
"time_first",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
"time_last",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
);
|
||||
__PACKAGE__->set_primary_key("mac", "ip");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:9+CuvuVWH88WxAf6IBij8g
|
||||
|
||||
=head1 RELATIONSHIPS
|
||||
|
||||
=head2 oui
|
||||
|
||||
Returns the C<oui> table entry matching this Node. You can then join on this
|
||||
relation and retrieve the Company name from the related table.
|
||||
|
||||
The JOIN is of type LEFT, in case the OUI table has not been populated.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( oui => 'App::Netdisco::DB::Result::Oui',
|
||||
sub {
|
||||
my $args = shift;
|
||||
return {
|
||||
"$args->{foreign_alias}.oui" =>
|
||||
{ '=' => \"substring(cast($args->{self_alias}.mac as varchar) for 8)" }
|
||||
};
|
||||
},
|
||||
{ join_type => 'LEFT' }
|
||||
);
|
||||
|
||||
=head2 node_ips
|
||||
|
||||
Returns the set of all C<node_ip> entries which are associated together with
|
||||
this IP. That is, all the IP addresses hosted on the same interface (MAC
|
||||
address) as the current Node IP entry.
|
||||
|
||||
Note that the set will include the original Node IP object itself. If you wish
|
||||
to find the I<other> IPs excluding this one, see the C<ip_aliases> helper
|
||||
routine, below.
|
||||
|
||||
Remember you can pass a filter to this method to find only active or inactive
|
||||
nodes, but do take into account that both the C<node> and C<node_ip> tables
|
||||
include independent C<active> fields.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->has_many( node_ips => 'App::Netdisco::DB::Result::NodeIp',
|
||||
{ 'foreign.mac' => 'self.mac' } );
|
||||
|
||||
=head2 nodes
|
||||
|
||||
Returns the set of C<node> entries associated with this IP. That is, all the
|
||||
MAC addresses recorded which have ever hosted this IP Address.
|
||||
|
||||
Remember you can pass a filter to this method to find only active or inactive
|
||||
nodes, but do take into account that both the C<node> and C<node_ip> tables
|
||||
include independent C<active> fields.
|
||||
|
||||
See also the C<node_sightings> helper routine, below.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->has_many( nodes => 'App::Netdisco::DB::Result::Node',
|
||||
{ 'foreign.mac' => 'self.mac' } );
|
||||
|
||||
=head2 netbios
|
||||
|
||||
Returns the set of C<node_nbt> entries associated with the MAC of this IP.
|
||||
That is, all the NetBIOS entries recorded which shared the same MAC with this
|
||||
IP Address.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->has_many( netbios => 'App::Netdisco::DB::Result::NodeNbt',
|
||||
{ 'foreign.mac' => 'self.mac' } );
|
||||
|
||||
my $search_attr = {
|
||||
order_by => {'-desc' => 'time_last'},
|
||||
'+columns' => {
|
||||
time_first_stamp => \"to_char(time_first, 'YYYY-MM-DD HH24:MI')",
|
||||
time_last_stamp => \"to_char(time_last, 'YYYY-MM-DD HH24:MI')",
|
||||
},
|
||||
};
|
||||
|
||||
=head2 ip_aliases( \%cond, \%attrs? )
|
||||
|
||||
Returns the set of other C<node_ip> entries hosted on the same interface (MAC
|
||||
address) as the current Node IP, excluding the current IP itself.
|
||||
|
||||
Remember you can pass a filter to this method to find only active or inactive
|
||||
nodes, but do take into account that both the C<node> and C<node_ip> tables
|
||||
include independent C<active> fields.
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
Results are ordered by time last seen.
|
||||
|
||||
=item *
|
||||
|
||||
Additional columns C<time_first_stamp> and C<time_last_stamp> provide
|
||||
preformatted timestamps of the C<time_first> and C<time_last> fields.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub ip_aliases {
|
||||
my ($row, $cond, $attrs) = @_;
|
||||
|
||||
my $rs = $row->node_ips({ip => { '!=' => $row->ip }});
|
||||
|
||||
return $rs
|
||||
->search_rs({}, $search_attr)
|
||||
->search($cond, $attrs);
|
||||
}
|
||||
|
||||
=head2 node_sightings( \%cond, \%attrs? )
|
||||
|
||||
Returns the set of C<node> entries associated with this IP. That is, all the
|
||||
MAC addresses recorded which have ever hosted this IP Address.
|
||||
|
||||
Remember you can pass a filter to this method to find only active or inactive
|
||||
nodes, but do take into account that both the C<node> and C<node_ip> tables
|
||||
include independent C<active> fields.
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
Results are ordered by time last seen.
|
||||
|
||||
=item *
|
||||
|
||||
Additional columns C<time_first_stamp> and C<time_last_stamp> provide
|
||||
preformatted timestamps of the C<time_first> and C<time_last> fields.
|
||||
|
||||
=item *
|
||||
|
||||
A JOIN is performed on the Device table and the Device DNS column prefetched.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub node_sightings {
|
||||
my ($row, $cond, $attrs) = @_;
|
||||
|
||||
return $row
|
||||
->nodes({}, {
|
||||
'+columns' => [qw/ device.dns /],
|
||||
join => 'device',
|
||||
})
|
||||
->search_rs({}, $search_attr)
|
||||
->search($cond, $attrs);
|
||||
}
|
||||
|
||||
=head1 ADDITIONAL COLUMNS
|
||||
|
||||
=head2 time_first_stamp
|
||||
|
||||
Formatted version of the C<time_first> 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 time_first_stamp { return (shift)->get_column('time_first_stamp') }
|
||||
|
||||
=head2 time_last_stamp
|
||||
|
||||
Formatted version of the C<time_last> 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 time_last_stamp { return (shift)->get_column('time_last_stamp') }
|
||||
|
||||
=head2 net_mac
|
||||
|
||||
Returns the C<mac> column instantiated into a L<NetAddr::MAC> object.
|
||||
|
||||
=cut
|
||||
|
||||
sub net_mac { return NetAddr::MAC->new(mac => (shift)->mac) }
|
||||
|
||||
1;
|
||||
37
lib/App/Netdisco/DB/Result/NodeMonitor.pm
Normal file
37
lib/App/Netdisco/DB/Result/NodeMonitor.pm
Normal file
@@ -0,0 +1,37 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::NodeMonitor;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("node_monitor");
|
||||
__PACKAGE__->add_columns(
|
||||
"mac",
|
||||
{ data_type => "macaddr", is_nullable => 0 },
|
||||
"active",
|
||||
{ data_type => "boolean", is_nullable => 1 },
|
||||
"why",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"cc",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"date",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
);
|
||||
__PACKAGE__->set_primary_key("mac");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:0prRdz2XYlFuE+nahsI2Yg
|
||||
|
||||
|
||||
# You can replace this text with custom code or comments, and it will be preserved on regeneration
|
||||
1;
|
||||
187
lib/App/Netdisco/DB/Result/NodeNbt.pm
Normal file
187
lib/App/Netdisco/DB/Result/NodeNbt.pm
Normal file
@@ -0,0 +1,187 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::NodeNbt;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use NetAddr::MAC;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("node_nbt");
|
||||
__PACKAGE__->add_columns(
|
||||
"mac",
|
||||
{ data_type => "macaddr", is_nullable => 0 },
|
||||
"ip",
|
||||
{ data_type => "inet", is_nullable => 1 },
|
||||
"nbname",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"domain",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"server",
|
||||
{ data_type => "boolean", is_nullable => 1 },
|
||||
"nbuser",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"active",
|
||||
{ data_type => "boolean", is_nullable => 1 },
|
||||
"time_first",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
"time_last",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
);
|
||||
__PACKAGE__->set_primary_key("mac");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:XFpxaGAWE13iizQIuVOP3g
|
||||
|
||||
=head1 RELATIONSHIPS
|
||||
|
||||
=head2 oui
|
||||
|
||||
Returns the C<oui> table entry matching this Node. You can then join on this
|
||||
relation and retrieve the Company name from the related table.
|
||||
|
||||
The JOIN is of type LEFT, in case the OUI table has not been populated.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( oui => 'App::Netdisco::DB::Result::Oui',
|
||||
sub {
|
||||
my $args = shift;
|
||||
return {
|
||||
"$args->{foreign_alias}.oui" =>
|
||||
{ '=' => \"substring(cast($args->{self_alias}.mac as varchar) for 8)" }
|
||||
};
|
||||
},
|
||||
{ join_type => 'LEFT' }
|
||||
);
|
||||
|
||||
=head2 nodes
|
||||
|
||||
Returns the set of C<node> entries associated with this IP. That is, all the
|
||||
MAC addresses recorded which have ever hosted this IP Address.
|
||||
|
||||
Remember you can pass a filter to this method to find only active or inactive
|
||||
nodes, but do take into account that both the C<node> and C<node_nbt> tables
|
||||
include independent C<active> fields.
|
||||
|
||||
See also the C<node_sightings> helper routine, below.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->has_many( nodes => 'App::Netdisco::DB::Result::Node',
|
||||
{ 'foreign.mac' => 'self.mac' } );
|
||||
|
||||
|
||||
=head2 nodeips
|
||||
|
||||
Returns the set of C<node_ip> entries associated with this NetBIOS entry.
|
||||
That is, the IP addresses which the same MAC address at the time of discovery.
|
||||
|
||||
Note that the Active status of the returned IP entries will all be the same
|
||||
as the current NetBIOS entry.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->has_many( nodeips => 'App::Netdisco::DB::Result::NodeIp',
|
||||
{ 'foreign.mac' => 'self.mac', 'foreign.active' => 'self.active' } );
|
||||
|
||||
|
||||
my $search_attr = {
|
||||
order_by => {'-desc' => 'time_last'},
|
||||
'+columns' => {
|
||||
time_first_stamp => \"to_char(time_first, 'YYYY-MM-DD HH24:MI')",
|
||||
time_last_stamp => \"to_char(time_last, 'YYYY-MM-DD HH24:MI')",
|
||||
},
|
||||
};
|
||||
|
||||
=head2 node_sightings( \%cond, \%attrs? )
|
||||
|
||||
Returns the set of C<node> entries associated with this IP. That is, all the
|
||||
MAC addresses recorded which have ever hosted this IP Address.
|
||||
|
||||
Remember you can pass a filter to this method to find only active or inactive
|
||||
nodes, but do take into account that both the C<node> and C<node_ip> tables
|
||||
include independent C<active> fields.
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
Results are ordered by time last seen.
|
||||
|
||||
=item *
|
||||
|
||||
Additional columns C<time_first_stamp> and C<time_last_stamp> provide
|
||||
preformatted timestamps of the C<time_first> and C<time_last> fields.
|
||||
|
||||
=item *
|
||||
|
||||
A JOIN is performed on the Device table and the Device DNS column prefetched.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub node_sightings {
|
||||
my ($row, $cond, $attrs) = @_;
|
||||
|
||||
return $row
|
||||
->nodes({}, {
|
||||
'+columns' => [qw/ device.dns /],
|
||||
join => 'device',
|
||||
})
|
||||
->search_rs({}, $search_attr)
|
||||
->search($cond, $attrs);
|
||||
}
|
||||
|
||||
=head1 ADDITIONAL COLUMNS
|
||||
|
||||
=head2 time_first_stamp
|
||||
|
||||
Formatted version of the C<time_first> 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 time_first_stamp { return (shift)->get_column('time_first_stamp') }
|
||||
|
||||
=head2 time_last_stamp
|
||||
|
||||
Formatted version of the C<time_last> 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 time_last_stamp { return (shift)->get_column('time_last_stamp') }
|
||||
|
||||
=head2 net_mac
|
||||
|
||||
Returns the C<mac> column instantiated into a L<NetAddr::MAC> object.
|
||||
|
||||
=cut
|
||||
|
||||
sub net_mac { return NetAddr::MAC->new(mac => (shift)->mac) }
|
||||
|
||||
1;
|
||||
96
lib/App/Netdisco/DB/Result/NodeWireless.pm
Normal file
96
lib/App/Netdisco/DB/Result/NodeWireless.pm
Normal file
@@ -0,0 +1,96 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::NodeWireless;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use NetAddr::MAC;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("node_wireless");
|
||||
__PACKAGE__->add_columns(
|
||||
"mac",
|
||||
{ data_type => "macaddr", is_nullable => 0 },
|
||||
"uptime",
|
||||
{ data_type => "integer", is_nullable => 1 },
|
||||
"maxrate",
|
||||
{ data_type => "integer", is_nullable => 1 },
|
||||
"txrate",
|
||||
{ data_type => "integer", is_nullable => 1 },
|
||||
"sigstrength",
|
||||
{ data_type => "integer", is_nullable => 1 },
|
||||
"sigqual",
|
||||
{ data_type => "integer", is_nullable => 1 },
|
||||
"rxpkt",
|
||||
{ data_type => "integer", is_nullable => 1 },
|
||||
"txpkt",
|
||||
{ data_type => "integer", is_nullable => 1 },
|
||||
"rxbyte",
|
||||
{ data_type => "bigint", is_nullable => 1 },
|
||||
"txbyte",
|
||||
{ data_type => "bigint", is_nullable => 1 },
|
||||
"time_last",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
"ssid",
|
||||
{ data_type => "text", is_nullable => 0, default_value => '' },
|
||||
);
|
||||
__PACKAGE__->set_primary_key("mac", "ssid");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:3xsSiWzL85ih3vhdews8Hg
|
||||
|
||||
=head1 RELATIONSHIPS
|
||||
|
||||
=head2 oui
|
||||
|
||||
Returns the C<oui> table entry matching this Node. You can then join on this
|
||||
relation and retrieve the Company name from the related table.
|
||||
|
||||
The JOIN is of type LEFT, in case the OUI table has not been populated.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( oui => 'App::Netdisco::DB::Result::Oui',
|
||||
sub {
|
||||
my $args = shift;
|
||||
return {
|
||||
"$args->{foreign_alias}.oui" =>
|
||||
{ '=' => \"substring(cast($args->{self_alias}.mac as varchar) for 8)" }
|
||||
};
|
||||
},
|
||||
{ join_type => 'LEFT' }
|
||||
);
|
||||
|
||||
=head2 node
|
||||
|
||||
Returns the C<node> table entry matching this wireless entry.
|
||||
|
||||
The JOIN is of type LEFT, in case the C<node> is no longer present in the
|
||||
database but the relation is being used in C<search()>.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( node => 'App::Netdisco::DB::Result::Node',
|
||||
{ 'foreign.mac' => 'self.mac' },
|
||||
{ join_type => 'LEFT' } );
|
||||
|
||||
=head1 ADDITIONAL COLUMNS
|
||||
|
||||
=head2 net_mac
|
||||
|
||||
Returns the C<mac> column instantiated into a L<NetAddr::MAC> object.
|
||||
|
||||
=cut
|
||||
|
||||
sub net_mac { return NetAddr::MAC->new(mac => (shift)->mac) }
|
||||
|
||||
1;
|
||||
28
lib/App/Netdisco/DB/Result/Oui.pm
Normal file
28
lib/App/Netdisco/DB/Result/Oui.pm
Normal file
@@ -0,0 +1,28 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::Oui;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("oui");
|
||||
__PACKAGE__->add_columns(
|
||||
"oui",
|
||||
{ data_type => "varchar", is_nullable => 0, size => 8 },
|
||||
"company",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"abbrev",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
);
|
||||
__PACKAGE__->set_primary_key("oui");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:s51mj6SvstPd4GdNEy9SoA
|
||||
|
||||
|
||||
# You can replace this text with custom code or comments, and it will be preserved on regeneration
|
||||
1;
|
||||
38
lib/App/Netdisco/DB/Result/Process.pm
Normal file
38
lib/App/Netdisco/DB/Result/Process.pm
Normal file
@@ -0,0 +1,38 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::Process;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("process");
|
||||
__PACKAGE__->add_columns(
|
||||
"controller",
|
||||
{ data_type => "integer", is_nullable => 0 },
|
||||
"device",
|
||||
{ data_type => "inet", is_nullable => 0 },
|
||||
"action",
|
||||
{ data_type => "text", is_nullable => 0 },
|
||||
"status",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"count",
|
||||
{ data_type => "integer", is_nullable => 1 },
|
||||
"creation",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:28hTnOo4oNwJabiWWHBgCw
|
||||
|
||||
|
||||
# You can replace this text with custom code or comments, and it will be preserved on regeneration
|
||||
1;
|
||||
33
lib/App/Netdisco/DB/Result/Session.pm
Normal file
33
lib/App/Netdisco/DB/Result/Session.pm
Normal file
@@ -0,0 +1,33 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::Session;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("sessions");
|
||||
__PACKAGE__->add_columns(
|
||||
"id",
|
||||
{ data_type => "char", is_nullable => 0, size => 32 },
|
||||
"creation",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
"a_session",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
);
|
||||
__PACKAGE__->set_primary_key("id");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:khNPh72VjQh8QHayuW/p1w
|
||||
|
||||
|
||||
# You can replace this text with custom code or comments, and it will be preserved on regeneration
|
||||
1;
|
||||
38
lib/App/Netdisco/DB/Result/Subnet.pm
Normal file
38
lib/App/Netdisco/DB/Result/Subnet.pm
Normal file
@@ -0,0 +1,38 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::Subnet;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("subnets");
|
||||
__PACKAGE__->add_columns(
|
||||
"net",
|
||||
{ data_type => "cidr", is_nullable => 0 },
|
||||
"creation",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
"last_discover",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
);
|
||||
__PACKAGE__->set_primary_key("net");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:1EHOfYx8PYOHoTkViZR6OA
|
||||
|
||||
|
||||
# You can replace this text with custom code or comments, and it will be preserved on regeneration
|
||||
1;
|
||||
35
lib/App/Netdisco/DB/Result/Topology.pm
Normal file
35
lib/App/Netdisco/DB/Result/Topology.pm
Normal file
@@ -0,0 +1,35 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::Topology;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table("topology");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"dev1",
|
||||
{ data_type => "inet", is_nullable => 0 },
|
||||
"port1",
|
||||
{ data_type => "text", is_nullable => 0 },
|
||||
"dev2",
|
||||
{ data_type => "inet", is_nullable => 0 },
|
||||
"port2",
|
||||
{ data_type => "text", is_nullable => 0 },
|
||||
);
|
||||
|
||||
__PACKAGE__->add_unique_constraint(['dev1','port1']);
|
||||
__PACKAGE__->add_unique_constraint(['dev2','port2']);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
device1 => 'App::Netdisco::DB::Result::Device',
|
||||
{'foreign.ip' => 'self.dev1'}
|
||||
);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
device2 => 'App::Netdisco::DB::Result::Device',
|
||||
{'foreign.ip' => 'self.dev2'}
|
||||
);
|
||||
|
||||
1;
|
||||
45
lib/App/Netdisco/DB/Result/User.pm
Normal file
45
lib/App/Netdisco/DB/Result/User.pm
Normal file
@@ -0,0 +1,45 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::User;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("users");
|
||||
__PACKAGE__->add_columns(
|
||||
"username",
|
||||
{ data_type => "varchar", is_nullable => 0, size => 50 },
|
||||
"password",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"creation",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
"last_on",
|
||||
{ data_type => "timestamp", is_nullable => 1 },
|
||||
"port_control",
|
||||
{ data_type => "boolean", default_value => \"false", is_nullable => 1 },
|
||||
"ldap",
|
||||
{ data_type => "boolean", default_value => \"false", is_nullable => 1 },
|
||||
"admin",
|
||||
{ data_type => "boolean", default_value => \"false", is_nullable => 1 },
|
||||
"fullname",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"note",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
);
|
||||
__PACKAGE__->set_primary_key("username");
|
||||
|
||||
__PACKAGE__->has_many( roles => 'App::Netdisco::DB::Result::Virtual::UserRole',
|
||||
'username', { cascade_copy => 0, cascade_update => 0, cascade_delete => 0 } );
|
||||
|
||||
sub created { return (shift)->get_column('created') }
|
||||
sub last_seen { return (shift)->get_column('last_seen') }
|
||||
|
||||
1;
|
||||
43
lib/App/Netdisco/DB/Result/UserLog.pm
Normal file
43
lib/App/Netdisco/DB/Result/UserLog.pm
Normal file
@@ -0,0 +1,43 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::UserLog;
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader
|
||||
# DO NOT MODIFY THE FIRST PART OF THIS FILE
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("user_log");
|
||||
__PACKAGE__->add_columns(
|
||||
"entry",
|
||||
{
|
||||
data_type => "integer",
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0,
|
||||
sequence => "user_log_entry_seq",
|
||||
},
|
||||
"username",
|
||||
{ data_type => "varchar", is_nullable => 1, size => 50 },
|
||||
"userip",
|
||||
{ data_type => "inet", is_nullable => 1 },
|
||||
"event",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"details",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"creation",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:BFrhjYJOhcLIHeWviu9rjw
|
||||
|
||||
|
||||
# You can replace this text with custom code or comments, and it will be preserved on regeneration
|
||||
1;
|
||||
19
lib/App/Netdisco/DB/Result/Virtual/ActiveNode.pm
Normal file
19
lib/App/Netdisco/DB/Result/Virtual/ActiveNode.pm
Normal file
@@ -0,0 +1,19 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::Virtual::ActiveNode;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'App::Netdisco::DB::Result::Node';
|
||||
|
||||
__PACKAGE__->load_components('Helper::Row::SubClass');
|
||||
__PACKAGE__->subclass;
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
__PACKAGE__->table("active_node");
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(q{
|
||||
SELECT * FROM node WHERE active
|
||||
});
|
||||
|
||||
1;
|
||||
27
lib/App/Netdisco/DB/Result/Virtual/ActiveNodeWithAge.pm
Normal file
27
lib/App/Netdisco/DB/Result/Virtual/ActiveNodeWithAge.pm
Normal file
@@ -0,0 +1,27 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::Virtual::ActiveNodeWithAge;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'App::Netdisco::DB::Result::Virtual::ActiveNode';
|
||||
|
||||
__PACKAGE__->load_components('Helper::Row::SubClass');
|
||||
__PACKAGE__->subclass;
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
__PACKAGE__->table("active_node_with_age");
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(q{
|
||||
SELECT *,
|
||||
replace( date_trunc( 'minute', age( now(), time_last + interval '30 second' ) ) ::text, 'mon', 'month')
|
||||
AS time_last_age
|
||||
FROM node WHERE active
|
||||
});
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"time_last_age",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
);
|
||||
|
||||
1;
|
||||
71
lib/App/Netdisco/DB/Result/Virtual/ApRadioChannelPower.pm
Normal file
71
lib/App/Netdisco/DB/Result/Virtual/ApRadioChannelPower.pm
Normal file
@@ -0,0 +1,71 @@
|
||||
package App::Netdisco::DB::Result::Virtual::ApRadioChannelPower;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
|
||||
__PACKAGE__->table('ap_radio_channel_power');
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(<<ENDSQL
|
||||
SELECT w.channel,
|
||||
w.power,
|
||||
w.ip,
|
||||
w.port,
|
||||
dp.name AS port_name,
|
||||
dp.descr,
|
||||
d.name AS device_name,
|
||||
d.dns,
|
||||
d.model,
|
||||
d.location,
|
||||
CASE
|
||||
WHEN w.power > 0 THEN round((10.0 * log(w.power) / log(10))::numeric, 1)
|
||||
ELSE NULL
|
||||
END AS power2
|
||||
FROM device_port_wireless AS w
|
||||
JOIN device_port AS dp ON dp.port = w.port
|
||||
AND dp.ip = w.ip
|
||||
JOIN device AS d ON d.ip = w.ip
|
||||
WHERE w.channel != '0'
|
||||
ENDSQL
|
||||
);
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
'channel' => {
|
||||
data_type => 'integer',
|
||||
},
|
||||
'power' => {
|
||||
data_type => 'integer',
|
||||
},
|
||||
'ip' => {
|
||||
data_type => 'inet',
|
||||
},
|
||||
'port' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'port_name' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'descr' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'device_name' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'dns' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'model' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'location' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'power2' => {
|
||||
data_type => 'numeric',
|
||||
},
|
||||
);
|
||||
|
||||
1;
|
||||
50
lib/App/Netdisco/DB/Result/Virtual/CidrIps.pm
Normal file
50
lib/App/Netdisco/DB/Result/Virtual/CidrIps.pm
Normal file
@@ -0,0 +1,50 @@
|
||||
package App::Netdisco::DB::Result::Virtual::CidrIps;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
|
||||
__PACKAGE__->table('cidr_ips');
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(<<'ENDSQL');
|
||||
SELECT host(network (prefix) + sub.int)::inet AS ip,
|
||||
NULL AS mac,
|
||||
NULL::text AS dns,
|
||||
NULL::timestamp AS time_first,
|
||||
NULL::timestamp AS time_last,
|
||||
false::boolean AS active
|
||||
FROM (
|
||||
SELECT prefix,
|
||||
generate_series(1, (broadcast(prefix) - network(prefix) - 1)) AS int
|
||||
FROM (
|
||||
SELECT ?::inet AS prefix
|
||||
) AS addr
|
||||
) AS sub
|
||||
ENDSQL
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"ip",
|
||||
{ data_type => "inet", is_nullable => 0 },
|
||||
"mac",
|
||||
{ data_type => "macaddr", is_nullable => 1 },
|
||||
"dns",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"active",
|
||||
{ data_type => "boolean", is_nullable => 1 },
|
||||
"time_first",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
is_nullable => 1,
|
||||
},
|
||||
"time_last",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
is_nullable => 1,
|
||||
},
|
||||
);
|
||||
|
||||
1;
|
||||
24
lib/App/Netdisco/DB/Result/Virtual/DeviceDnsMismatch.pm
Normal file
24
lib/App/Netdisco/DB/Result/Virtual/DeviceDnsMismatch.pm
Normal file
@@ -0,0 +1,24 @@
|
||||
package App::Netdisco::DB::Result::Virtual::DeviceDnsMismatch;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
use base 'App::Netdisco::DB::Result::Device';
|
||||
|
||||
__PACKAGE__->load_components('Helper::Row::SubClass');
|
||||
__PACKAGE__->subclass;
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
__PACKAGE__->table('device_dns_mismatch');
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(<<'ENDSQL');
|
||||
SELECT *
|
||||
FROM device
|
||||
WHERE dns IS NULL
|
||||
OR name IS NULL
|
||||
OR regexp_replace(lower(dns), ? || '$', '')
|
||||
!= regexp_replace(lower(name), ? || '$', '')
|
||||
ENDSQL
|
||||
|
||||
1;
|
||||
48
lib/App/Netdisco/DB/Result/Virtual/DeviceLinks.pm
Normal file
48
lib/App/Netdisco/DB/Result/Virtual/DeviceLinks.pm
Normal file
@@ -0,0 +1,48 @@
|
||||
package App::Netdisco::DB::Result::Virtual::DeviceLinks;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
|
||||
__PACKAGE__->table('device_links');
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(<<ENDSQL
|
||||
SELECT dp.ip AS left_ip, dp.port AS left_port, di.ip AS right_ip, dp.remote_port AS right_port
|
||||
FROM ( SELECT device_port.ip, device_port.port, device_port.remote_ip, device_port.remote_port
|
||||
FROM device_port
|
||||
WHERE device_port.remote_port IS NOT NULL
|
||||
GROUP BY device_port.ip, device_port.port, device_port.remote_ip, device_port.remote_port
|
||||
ORDER BY device_port.ip) dp
|
||||
LEFT JOIN device_ip di ON dp.remote_ip = di.alias
|
||||
WHERE di.ip IS NOT NULL
|
||||
ORDER BY dp.ip
|
||||
ENDSQL
|
||||
);
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
'left_ip' => {
|
||||
data_type => 'inet',
|
||||
},
|
||||
'left_port' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'right_ip' => {
|
||||
data_type => 'inet',
|
||||
},
|
||||
'right_port' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->has_many('left_vlans', 'App::Netdisco::DB::Result::DevicePortVlan',
|
||||
{ 'foreign.ip' => 'self.left_ip', 'foreign.port' => 'self.left_port' },
|
||||
{ join_type => 'INNER' } );
|
||||
|
||||
__PACKAGE__->has_many('right_vlans', 'App::Netdisco::DB::Result::DevicePortVlan',
|
||||
{ 'foreign.ip' => 'self.right_ip', 'foreign.port' => 'self.right_port' },
|
||||
{ join_type => 'INNER' } );
|
||||
|
||||
1;
|
||||
87
lib/App/Netdisco/DB/Result/Virtual/DevicePoeStatus.pm
Normal file
87
lib/App/Netdisco/DB/Result/Virtual/DevicePoeStatus.pm
Normal file
@@ -0,0 +1,87 @@
|
||||
package App::Netdisco::DB::Result::Virtual::DevicePoeStatus;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
|
||||
__PACKAGE__->table('device_poe_status');
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(<<'ENDSQL');
|
||||
SELECT DISTINCT ON (dp.ip,dp.module)
|
||||
dp.ip,
|
||||
dp.module,
|
||||
dp.power::bigint,
|
||||
dp.status,
|
||||
d.dns,
|
||||
d.name,
|
||||
d.model,
|
||||
d.location,
|
||||
COUNT(dpp.port) OVER (PARTITION BY dp.ip, dp.module) AS poe_capable_ports,
|
||||
SUM(CASE WHEN dpp.status = 'deliveringPower' THEN 1 ELSE 0 END) OVER (PARTITION BY dp.ip, dp.module) AS poe_powered_ports,
|
||||
SUM(CASE WHEN dpp.admin = 'false' THEN 1 ELSE 0 END) OVER (PARTITION BY dp.ip, dp.module) AS poe_disabled_ports,
|
||||
SUM(CASE WHEN dpp.status ILIKE '%fault' THEN 1
|
||||
ELSE 0 END) OVER (PARTITION BY dp.ip, dp.module) AS poe_errored_ports,
|
||||
SUM(CASE WHEN dpp.status = 'deliveringPower' AND dpp.class = 'class4' THEN 30.0
|
||||
WHEN dpp.status = 'deliveringPower' AND dpp.class = 'class2' THEN 7.0
|
||||
WHEN dpp.status = 'deliveringPower' AND dpp.class = 'class1' THEN 4.0
|
||||
WHEN dpp.status = 'deliveringPower' AND dpp.class = 'class3' THEN 15.4
|
||||
WHEN dpp.status = 'deliveringPower' AND dpp.class = 'class0' THEN 15.4
|
||||
WHEN dpp.status = 'deliveringPower' AND dpp.class IS NULL THEN 15.4
|
||||
ELSE 0 END) OVER (PARTITION BY dp.ip, dp.module) AS poe_power_committed,
|
||||
SUM(CASE WHEN (dpp.power IS NULL OR dpp.power = '0') THEN 0
|
||||
ELSE round(dpp.power/1000.0, 1) END) OVER (PARTITION BY dp.ip, dp.module) AS poe_power_delivering
|
||||
FROM device_power dp
|
||||
JOIN device_port_power dpp ON dpp.ip = dp.ip
|
||||
AND dpp.module = dp.module
|
||||
JOIN device d ON dp.ip = d.ip
|
||||
ENDSQL
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
'ip' => {
|
||||
data_type => 'inet',
|
||||
},
|
||||
'module' => {
|
||||
data_type => 'integer',
|
||||
},
|
||||
'power' => {
|
||||
data_type => 'integer',
|
||||
},
|
||||
'status' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'dns' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'name' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'model' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'location' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'poe_capable_ports' => {
|
||||
data_type => 'bigint',
|
||||
},
|
||||
'poe_powered_ports' => {
|
||||
data_type => 'bigint',
|
||||
},
|
||||
'poe_disabled_ports' => {
|
||||
data_type => 'bigint',
|
||||
},
|
||||
'poe_errored_ports' => {
|
||||
data_type => 'bigint',
|
||||
},
|
||||
'poe_power_committed' => {
|
||||
data_type => 'numeric',
|
||||
},
|
||||
'poe_power_delivering' => {
|
||||
data_type => 'numeric',
|
||||
},
|
||||
);
|
||||
|
||||
1;
|
||||
61
lib/App/Netdisco/DB/Result/Virtual/DuplexMismatch.pm
Normal file
61
lib/App/Netdisco/DB/Result/Virtual/DuplexMismatch.pm
Normal file
@@ -0,0 +1,61 @@
|
||||
package App::Netdisco::DB::Result::Virtual::DuplexMismatch;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
|
||||
__PACKAGE__->table('duplex_mismatch');
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(<<ENDSQL
|
||||
SELECT dp.ip AS left_ip, d1.dns AS left_dns, dp.port AS left_port, dp.duplex AS left_duplex,
|
||||
di.ip AS right_ip, d2.dns AS right_dns, dp.remote_port AS right_port, dp2.duplex AS right_duplex
|
||||
FROM ( SELECT device_port.ip, device_port.remote_ip, device_port.port, device_port.duplex, device_port.remote_port
|
||||
FROM device_port
|
||||
WHERE
|
||||
device_port.remote_port IS NOT NULL
|
||||
AND device_port.up NOT ILIKE '%down%'
|
||||
GROUP BY device_port.ip, device_port.remote_ip, device_port.port, device_port.duplex, device_port.remote_port
|
||||
ORDER BY device_port.ip) dp
|
||||
LEFT JOIN device_ip di ON dp.remote_ip = di.alias
|
||||
LEFT JOIN device d1 ON dp.ip = d1.ip
|
||||
LEFT JOIN device d2 ON di.ip = d2.ip
|
||||
LEFT JOIN device_port dp2 ON (di.ip = dp2.ip AND dp.remote_port = dp2.port)
|
||||
WHERE di.ip IS NOT NULL
|
||||
AND dp.duplex <> dp2.duplex
|
||||
AND dp.ip <= di.ip
|
||||
AND dp2.up NOT ILIKE '%down%'
|
||||
ORDER BY dp.ip
|
||||
ENDSQL
|
||||
);
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
'left_ip' => {
|
||||
data_type => 'inet',
|
||||
},
|
||||
'left_dns' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'left_port' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'left_duplex' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'right_ip' => {
|
||||
data_type => 'inet',
|
||||
},
|
||||
'right_dns' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'right_port' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'right_duplex' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
);
|
||||
|
||||
1;
|
||||
13
lib/App/Netdisco/DB/Result/Virtual/GenericReport.pm
Normal file
13
lib/App/Netdisco/DB/Result/Virtual/GenericReport.pm
Normal file
@@ -0,0 +1,13 @@
|
||||
package App::Netdisco::DB::Result::Virtual::GenericReport;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
__PACKAGE__->table("generic_report");
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(q{});
|
||||
|
||||
1;
|
||||
19
lib/App/Netdisco/DB/Result/Virtual/NodeIp4.pm
Normal file
19
lib/App/Netdisco/DB/Result/Virtual/NodeIp4.pm
Normal file
@@ -0,0 +1,19 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::Virtual::NodeIp4;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'App::Netdisco::DB::Result::NodeIp';
|
||||
|
||||
__PACKAGE__->load_components('Helper::Row::SubClass');
|
||||
__PACKAGE__->subclass;
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
__PACKAGE__->table("node_ip4");
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(q{
|
||||
SELECT * FROM node_ip WHERE family(ip) = 4
|
||||
});
|
||||
|
||||
1;
|
||||
19
lib/App/Netdisco/DB/Result/Virtual/NodeIp6.pm
Normal file
19
lib/App/Netdisco/DB/Result/Virtual/NodeIp6.pm
Normal file
@@ -0,0 +1,19 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::Virtual::NodeIp6;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'App::Netdisco::DB::Result::NodeIp';
|
||||
|
||||
__PACKAGE__->load_components('Helper::Row::SubClass');
|
||||
__PACKAGE__->subclass;
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
__PACKAGE__->table("node_ip6");
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(q{
|
||||
SELECT * FROM node_ip WHERE family(ip) = 6
|
||||
});
|
||||
|
||||
1;
|
||||
49
lib/App/Netdisco/DB/Result/Virtual/NodeMonitor.pm
Normal file
49
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;
|
||||
27
lib/App/Netdisco/DB/Result/Virtual/NodeWithAge.pm
Normal file
27
lib/App/Netdisco/DB/Result/Virtual/NodeWithAge.pm
Normal file
@@ -0,0 +1,27 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::Virtual::NodeWithAge;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'App::Netdisco::DB::Result::Node';
|
||||
|
||||
__PACKAGE__->load_components('Helper::Row::SubClass');
|
||||
__PACKAGE__->subclass;
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
__PACKAGE__->table("node_with_age");
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(q{
|
||||
SELECT *,
|
||||
replace( date_trunc( 'minute', age( now(), time_last + interval '30 second' ) ) ::text, 'mon', 'month')
|
||||
AS time_last_age
|
||||
FROM node
|
||||
});
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"time_last_age",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
);
|
||||
|
||||
1;
|
||||
68
lib/App/Netdisco/DB/Result/Virtual/NodesDiscovered.pm
Normal file
68
lib/App/Netdisco/DB/Result/Virtual/NodesDiscovered.pm
Normal file
@@ -0,0 +1,68 @@
|
||||
package App::Netdisco::DB::Result::Virtual::NodesDiscovered;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
|
||||
__PACKAGE__->table('nodes_discovered');
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(<<ENDSQL
|
||||
SELECT d.ip,
|
||||
d.dns,
|
||||
d.name,
|
||||
p.port,
|
||||
p.remote_ip,
|
||||
p.remote_port,
|
||||
p.remote_type,
|
||||
p.remote_id
|
||||
FROM device_port p,
|
||||
device d
|
||||
WHERE d.ip = p.ip
|
||||
AND NOT EXISTS
|
||||
(SELECT 1
|
||||
FROM device_port q
|
||||
WHERE q.ip = p.remote_ip
|
||||
AND q.port = p.remote_port)
|
||||
AND NOT EXISTS
|
||||
(SELECT 1
|
||||
FROM device_ip a,
|
||||
device_port q
|
||||
WHERE a.alias = p.remote_ip
|
||||
AND q.ip = a.ip
|
||||
AND q.port = p.remote_port)
|
||||
AND (p.remote_id IS NOT NULL OR p.remote_type IS NOT NULL)
|
||||
ORDER BY d.name, p.port
|
||||
ENDSQL
|
||||
);
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
'ip' => {
|
||||
data_type => 'inet',
|
||||
},
|
||||
'dns' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'name' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'port' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'remote_ip' => {
|
||||
data_type => 'inet',
|
||||
},
|
||||
'remote_port' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'remote_type' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'remote_id' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
);
|
||||
|
||||
1;
|
||||
32
lib/App/Netdisco/DB/Result/Virtual/OrphanedDevices.pm
Normal file
32
lib/App/Netdisco/DB/Result/Virtual/OrphanedDevices.pm
Normal file
@@ -0,0 +1,32 @@
|
||||
package App::Netdisco::DB::Result::Virtual::OrphanedDevices;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
use base 'App::Netdisco::DB::Result::Device';
|
||||
|
||||
__PACKAGE__->load_components('Helper::Row::SubClass');
|
||||
__PACKAGE__->subclass;
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
__PACKAGE__->table('orphaned_devices');
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(<<'ENDSQL');
|
||||
SELECT *
|
||||
FROM device
|
||||
WHERE ip NOT IN
|
||||
( SELECT DISTINCT dp.ip AS ip
|
||||
FROM
|
||||
(SELECT device_port.ip,
|
||||
device_port.remote_ip
|
||||
FROM device_port
|
||||
WHERE device_port.remote_port IS NOT NULL
|
||||
GROUP BY device_port.ip,
|
||||
device_port.remote_ip
|
||||
ORDER BY device_port.ip) dp
|
||||
LEFT JOIN device_ip di ON dp.remote_ip = di.alias
|
||||
WHERE di.ip IS NOT NULL)
|
||||
ENDSQL
|
||||
|
||||
1;
|
||||
42
lib/App/Netdisco/DB/Result/Virtual/PollerPerformance.pm
Normal file
42
lib/App/Netdisco/DB/Result/Virtual/PollerPerformance.pm
Normal file
@@ -0,0 +1,42 @@
|
||||
package App::Netdisco::DB::Result::Virtual::PollerPerformance;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
|
||||
__PACKAGE__->table('poller_performance');
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(<<ENDSQL
|
||||
SELECT action, entered, to_char( entered, 'YYYY-MM-DD HH24:MI:SS' ) AS entered_stamp,
|
||||
COUNT( device ) AS number, MIN( started ) AS start, MAX( finished ) AS end,
|
||||
justify_interval( extract ( epoch FROM( max( finished ) - min( started ) ) ) * interval '1 second' ) AS elapsed
|
||||
FROM admin
|
||||
WHERE action IN ( 'discover', 'macsuck', 'arpnip', 'nbtstat' )
|
||||
GROUP BY action, entered
|
||||
HAVING count( device ) > 1
|
||||
ORDER BY entered DESC, elapsed DESC
|
||||
LIMIT 30
|
||||
ENDSQL
|
||||
);
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"action",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"entered",
|
||||
{ data_type => "timestamp", is_nullable => 1 },
|
||||
"entered_stamp",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"number",
|
||||
{ data_type => "integer", is_nullable => 1 },
|
||||
"start",
|
||||
{ data_type => "timestamp", is_nullable => 1 },
|
||||
"end",
|
||||
{ data_type => "timestamp", is_nullable => 1 },
|
||||
"elapsed",
|
||||
{ data_type => "interval", is_nullable => 1 },
|
||||
);
|
||||
|
||||
1;
|
||||
46
lib/App/Netdisco/DB/Result/Virtual/PortUtilization.pm
Normal file
46
lib/App/Netdisco/DB/Result/Virtual/PortUtilization.pm
Normal file
@@ -0,0 +1,46 @@
|
||||
package App::Netdisco::DB::Result::Virtual::PortUtilization;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
|
||||
__PACKAGE__->table('port_utilization');
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(<<ENDSQL
|
||||
SELECT d.dns AS dns, d.ip as ip,
|
||||
sum(CASE WHEN (dp.type != 'propVirtual') THEN 1 ELSE 0 END) as port_count,
|
||||
sum(CASE WHEN (dp.type != 'propVirtual' AND dp.up_admin = 'up' AND dp.up = 'up') THEN 1 ELSE 0 END) as ports_in_use,
|
||||
sum(CASE WHEN (dp.type != 'propVirtual' AND dp.up_admin != 'up') THEN 1 ELSE 0 END) as ports_shutdown,
|
||||
sum(CASE WHEN (dp.type != 'propVirtual' AND dp.up_admin = 'up' AND dp.up != 'up') THEN 1 ELSE 0 END) as ports_free
|
||||
FROM device d LEFT JOIN device_port dp
|
||||
ON d.ip = dp.ip
|
||||
GROUP BY d.dns, d.ip
|
||||
ORDER BY d.dns, d.ip
|
||||
ENDSQL
|
||||
);
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
'dns' => {
|
||||
data_type => 'text',
|
||||
},
|
||||
'ip' => {
|
||||
data_type => 'inet',
|
||||
},
|
||||
'port_count' => {
|
||||
data_type => 'integer',
|
||||
},
|
||||
'ports_in_use' => {
|
||||
data_type => 'integer',
|
||||
},
|
||||
'ports_shutdown' => {
|
||||
data_type => 'integer',
|
||||
},
|
||||
'ports_free' => {
|
||||
data_type => 'integer',
|
||||
},
|
||||
);
|
||||
|
||||
1;
|
||||
42
lib/App/Netdisco/DB/Result/Virtual/SlowDevices.pm
Normal file
42
lib/App/Netdisco/DB/Result/Virtual/SlowDevices.pm
Normal file
@@ -0,0 +1,42 @@
|
||||
package App::Netdisco::DB::Result::Virtual::SlowDevices;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
|
||||
__PACKAGE__->table('slow_devices');
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(<<ENDSQL
|
||||
SELECT a.action, a.device, a.started, a.finished,
|
||||
justify_interval(extract(epoch FROM (a.finished - a.started)) * interval '1 second') AS elapsed
|
||||
FROM admin a
|
||||
INNER JOIN (
|
||||
SELECT device, action, max(started) AS started
|
||||
FROM admin
|
||||
WHERE status = 'done'
|
||||
AND action IN ('discover','macsuck','arpnip')
|
||||
GROUP BY action, device
|
||||
) b
|
||||
ON a.device = b.device AND a.started = b.started
|
||||
ORDER BY elapsed desc, action, device
|
||||
LIMIT 20
|
||||
ENDSQL
|
||||
);
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"action",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"device",
|
||||
{ data_type => "inet", is_nullable => 1 },
|
||||
"started",
|
||||
{ data_type => "timestamp", is_nullable => 1 },
|
||||
"finished",
|
||||
{ data_type => "timestamp", is_nullable => 1 },
|
||||
"elapsed",
|
||||
{ data_type => "interval", is_nullable => 1 },
|
||||
);
|
||||
|
||||
1;
|
||||
54
lib/App/Netdisco/DB/Result/Virtual/SubnetUtilization.pm
Normal file
54
lib/App/Netdisco/DB/Result/Virtual/SubnetUtilization.pm
Normal file
@@ -0,0 +1,54 @@
|
||||
package App::Netdisco::DB::Result::Virtual::SubnetUtilization;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
|
||||
__PACKAGE__->table('cidr_ips');
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(<<'ENDSQL');
|
||||
SELECT net as subnet,
|
||||
power(2, (32 - masklen(net))) as subnet_size,
|
||||
count(DISTINCT ip) as active,
|
||||
round(100 * count(DISTINCT ip) / (power(2, (32 - masklen(net))))) as percent
|
||||
FROM (
|
||||
SELECT DISTINCT net, ni.ip
|
||||
FROM subnets s1, node_ip ni
|
||||
WHERE s1.net <<= ?::cidr
|
||||
AND ni.ip <<= s1.net
|
||||
AND ((
|
||||
ni.time_first IS null
|
||||
AND ni.time_last IS null
|
||||
) OR (
|
||||
ni.time_last >= ?
|
||||
AND ni.time_last <= ?
|
||||
))
|
||||
AND s1.last_discover >= ?
|
||||
UNION
|
||||
SELECT DISTINCT net, di.alias as ip
|
||||
FROM subnets s2, device_ip di JOIN device d USING (ip)
|
||||
WHERE s2.net <<= ?::cidr
|
||||
AND di.alias <<= s2.net
|
||||
AND s2.last_discover >= ?
|
||||
AND d.last_discover >= ?
|
||||
) as joined
|
||||
GROUP BY net
|
||||
ORDER BY percent ASC
|
||||
ENDSQL
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"subnet",
|
||||
{ data_type => "cidr", is_nullable => 0 },
|
||||
"subnet_size",
|
||||
{ data_type => "integer", is_nullable => 0 },
|
||||
"active",
|
||||
{ data_type => "integer", is_nullable => 0 },
|
||||
"percent",
|
||||
{ data_type => "integer", is_nullable => 0 },
|
||||
);
|
||||
|
||||
1;
|
||||
54
lib/App/Netdisco/DB/Result/Virtual/UnDirEdgesAgg.pm
Normal file
54
lib/App/Netdisco/DB/Result/Virtual/UnDirEdgesAgg.pm
Normal file
@@ -0,0 +1,54 @@
|
||||
package App::Netdisco::DB::Result::Virtual::UnDirEdgesAgg;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
|
||||
__PACKAGE__->table('undir_edges_agg');
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(<<'ENDSQL');
|
||||
SELECT left_ip,
|
||||
array_agg(right_ip) AS links
|
||||
FROM
|
||||
( SELECT dp.ip AS left_ip,
|
||||
di.ip AS right_ip
|
||||
FROM
|
||||
(SELECT device_port.ip,
|
||||
device_port.remote_ip
|
||||
FROM device_port
|
||||
WHERE device_port.remote_port IS NOT NULL
|
||||
GROUP BY device_port.ip,
|
||||
device_port.remote_ip) dp
|
||||
LEFT JOIN device_ip di ON dp.remote_ip = di.alias
|
||||
WHERE di.ip IS NOT NULL
|
||||
UNION SELECT di.ip AS left_ip,
|
||||
dp.ip AS right_ip
|
||||
FROM
|
||||
(SELECT device_port.ip,
|
||||
device_port.remote_ip
|
||||
FROM device_port
|
||||
WHERE device_port.remote_port IS NOT NULL
|
||||
GROUP BY device_port.ip,
|
||||
device_port.remote_ip) dp
|
||||
LEFT JOIN device_ip di ON dp.remote_ip = di.alias
|
||||
WHERE di.ip IS NOT NULL ) AS foo
|
||||
GROUP BY left_ip
|
||||
ORDER BY left_ip
|
||||
ENDSQL
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
'left_ip' => {
|
||||
data_type => 'inet',
|
||||
},
|
||||
'links' => {
|
||||
data_type => 'inet[]',
|
||||
}
|
||||
);
|
||||
|
||||
__PACKAGE__->belongs_to('device', 'App::Netdisco::DB::Result::Device',
|
||||
{ 'foreign.ip' => 'self.left_ip' });
|
||||
|
||||
1;
|
||||
61
lib/App/Netdisco/DB/Result/Virtual/UndiscoveredNeighbors.pm
Normal file
61
lib/App/Netdisco/DB/Result/Virtual/UndiscoveredNeighbors.pm
Normal file
@@ -0,0 +1,61 @@
|
||||
package App::Netdisco::DB::Result::Virtual::UndiscoveredNeighbors;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use utf8;
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
|
||||
__PACKAGE__->table('undiscovered_neighbors');
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(<<'ENDSQL');
|
||||
SELECT DISTINCT ON (p.remote_ip) d.ip,
|
||||
d.name,
|
||||
d.dns,
|
||||
p.port,
|
||||
p.remote_ip,
|
||||
p.remote_id,
|
||||
p.remote_type,
|
||||
p.remote_port,
|
||||
a.log,
|
||||
a.finished
|
||||
FROM device_port p
|
||||
JOIN device d
|
||||
ON d.ip = p.ip
|
||||
LEFT JOIN admin a
|
||||
ON (p.remote_ip = a.device AND a.action = 'discover')
|
||||
WHERE
|
||||
(p.remote_ip NOT IN (SELECT alias FROM device_ip))
|
||||
OR
|
||||
((p.remote_ip IS NULL) AND p.is_uplink)
|
||||
ORDER BY
|
||||
p.remote_ip ASC,
|
||||
a.finished DESC
|
||||
ENDSQL
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"ip",
|
||||
{ data_type => "inet", is_nullable => 0 },
|
||||
"name",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"dns",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"port",
|
||||
{ data_type => "text", is_nullable => 0 },
|
||||
"remote_ip",
|
||||
{ data_type => "inet", is_nullable => 1 },
|
||||
"remote_port",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"remote_type",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"remote_id",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"log",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"finished",
|
||||
{ data_type => "timestamp", is_nullable => 1 },
|
||||
);
|
||||
|
||||
1;
|
||||
30
lib/App/Netdisco/DB/Result/Virtual/UserRole.pm
Normal file
30
lib/App/Netdisco/DB/Result/Virtual/UserRole.pm
Normal file
@@ -0,0 +1,30 @@
|
||||
use utf8;
|
||||
package App::Netdisco::DB::Result::Virtual::UserRole;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
|
||||
__PACKAGE__->table("user_role");
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(<<ENDSQL
|
||||
SELECT username, 'port_control' AS role FROM users
|
||||
WHERE port_control
|
||||
UNION
|
||||
SELECT username, 'admin' AS role FROM users
|
||||
WHERE admin
|
||||
UNION
|
||||
SELECT username, 'ldap' AS role FROM users
|
||||
WHERE ldap
|
||||
ENDSQL
|
||||
);
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
'username' => { data_type => 'text' },
|
||||
'role' => { data_type => 'text' },
|
||||
);
|
||||
|
||||
1;
|
||||
195
lib/App/Netdisco/DB/ResultSet.pm
Normal file
195
lib/App/Netdisco/DB/ResultSet.pm
Normal file
@@ -0,0 +1,195 @@
|
||||
package App::Netdisco::DB::ResultSet;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::ResultSet';
|
||||
|
||||
__PACKAGE__->load_components(
|
||||
qw{Helper::ResultSet::SetOperations Helper::ResultSet::Shortcut});
|
||||
|
||||
=head1 ADDITIONAL METHODS
|
||||
|
||||
=head2 get_distinct_col( $column )
|
||||
|
||||
Returns an asciibetical sorted list of the distinct values in the given column
|
||||
of the Device table. This is useful for web forms when you want to provide a
|
||||
drop-down list of possible options.
|
||||
|
||||
=cut
|
||||
|
||||
sub get_distinct_col {
|
||||
my ( $rs, $col ) = @_;
|
||||
return $rs unless $col;
|
||||
|
||||
return $rs->search(
|
||||
{},
|
||||
{ columns => [$col],
|
||||
order_by => $col,
|
||||
distinct => 1
|
||||
}
|
||||
)->get_column($col)->all;
|
||||
}
|
||||
|
||||
=head2 get_datatables_data( $params )
|
||||
|
||||
Returns a ResultSet for DataTables Server-side processing which populates
|
||||
the displayed table. Evaluates the supplied query parameters for filtering,
|
||||
paging, and ordering information. Note: query paramters are expected to be
|
||||
passed as a reference to an expanded hash of hashes.
|
||||
|
||||
Filtering if present, will generate simple LIKE matching conditions for each
|
||||
searchable column (searchability indicated by query parameters) after each
|
||||
column is casted to text. Conditions are combined as disjunction (OR).
|
||||
Note: this does not match the built-in DataTables filtering which does it
|
||||
word by word on any field.
|
||||
|
||||
=cut
|
||||
|
||||
sub get_datatables_data {
|
||||
my $rs = shift;
|
||||
my $params = shift;
|
||||
my $attrs = shift;
|
||||
|
||||
die "condition parameter to search_by_field must be hashref\n"
|
||||
if ref {} ne ref $params
|
||||
or 0 == scalar keys %$params;
|
||||
|
||||
# -- Paging
|
||||
$rs = $rs->_with_datatables_paging($params);
|
||||
|
||||
# -- Ordering
|
||||
$rs = $rs->_with_datatables_order_clause($params);
|
||||
|
||||
# -- Filtering
|
||||
$rs = $rs->_with_datatables_where_clause($params);
|
||||
|
||||
return $rs;
|
||||
}
|
||||
|
||||
=head2 get_datatables_filtered_count( $params )
|
||||
|
||||
Returns the total records, after filtering (i.e. the total number of
|
||||
records after filtering has been applied - not just the number of records
|
||||
being returned for this page of data) for a datatables ResultSet and
|
||||
query parameters. Note: query paramters are expected to be passed as a
|
||||
reference to an expanded hash of hashes.
|
||||
|
||||
=cut
|
||||
|
||||
sub get_datatables_filtered_count {
|
||||
my $rs = shift;
|
||||
my $params = shift;
|
||||
|
||||
return $rs->_with_datatables_where_clause($params)->count;
|
||||
|
||||
}
|
||||
|
||||
sub _with_datatables_order_clause {
|
||||
my $rs = shift;
|
||||
my $params = shift;
|
||||
my $attrs = shift;
|
||||
|
||||
my @order = ();
|
||||
|
||||
if ( defined $params->{'order'}{0} ) {
|
||||
for ( my $i = 0; $i < (scalar keys %{$params->{'order'}}); $i++ ) {
|
||||
|
||||
# build direction, must be '-asc' or '-desc' (cf. SQL::Abstract)
|
||||
# we only get 'asc' or 'desc', so they have to be prefixed with '-'
|
||||
my $direction = '-' . $params->{'order'}{$i}{'dir'};
|
||||
|
||||
# We only get the column index (starting from 0), so we have to
|
||||
# translate the index into a column name.
|
||||
my $column_name = _datatables_index_to_column( $params,
|
||||
$params->{'order'}{$i}{'column'} );
|
||||
|
||||
# Prefix with table alias if no prefix
|
||||
my $csa = $rs->current_source_alias;
|
||||
$column_name =~ s/^(\w+)$/$csa\.$1/x;
|
||||
push @order, { $direction => $column_name };
|
||||
}
|
||||
}
|
||||
|
||||
$rs = $rs->order_by( \@order );
|
||||
return $rs;
|
||||
}
|
||||
|
||||
# NOTE this does not match the built-in DataTables filtering which does it
|
||||
# word by word on any field.
|
||||
#
|
||||
# General filtering using LIKE, this will not be efficient as is will not
|
||||
# be able to use indexes.
|
||||
|
||||
sub _with_datatables_where_clause {
|
||||
my $rs = shift;
|
||||
my $params = shift;
|
||||
my $attrs = shift;
|
||||
|
||||
my %where = ();
|
||||
|
||||
if ( defined $params->{'search'}{'value'}
|
||||
&& $params->{'search'}{'value'} )
|
||||
{
|
||||
my $search_string = $params->{'search'}{'value'};
|
||||
for ( my $i = 0; $i < (scalar keys %{$params->{'columns'}}); $i++ ) {
|
||||
|
||||
# Iterate over each column and check if it is searchable.
|
||||
# If so, add a constraint to the where clause restricting the given
|
||||
# column. In the query, the column is identified by it's index, we
|
||||
# need to translate the index to the column name.
|
||||
if ( $params->{'columns'}{$i}{'searchable'}
|
||||
and $params->{'columns'}{$i}{'searchable'} eq 'true' )
|
||||
{
|
||||
my $column = _datatables_index_to_column( $params, $i );
|
||||
my $csa = $rs->current_source_alias;
|
||||
$column =~ s/^(\w+)$/$csa\.$1/x;
|
||||
|
||||
# Cast everything to text for LIKE search
|
||||
$column = $column . '::text';
|
||||
push @{ $where{'-or'} },
|
||||
{ $column => { -like => '%' . $search_string . '%' } };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rs = $rs->search( \%where, $attrs );
|
||||
return $rs;
|
||||
}
|
||||
|
||||
sub _with_datatables_paging {
|
||||
my $rs = shift;
|
||||
my $params = shift;
|
||||
my $attrs = shift;
|
||||
|
||||
my $limit = $params->{'length'};
|
||||
|
||||
my $offset = 0;
|
||||
if ( defined $params->{'start'} && $params->{'start'} ) {
|
||||
$offset = $params->{'start'};
|
||||
}
|
||||
$attrs->{'offset'} = $offset;
|
||||
|
||||
$rs = $rs->search( {}, $attrs );
|
||||
$rs = $rs->limit($limit) if ($limit and $limit > 0);
|
||||
|
||||
return $rs;
|
||||
}
|
||||
|
||||
# Use the DataTables columns.data definition to derive the column
|
||||
# name from the index.
|
||||
|
||||
sub _datatables_index_to_column {
|
||||
my $params = shift;
|
||||
my $i = shift;
|
||||
|
||||
my $field;
|
||||
|
||||
if ( !defined($i) ) {
|
||||
$i = 0;
|
||||
}
|
||||
$field = $params->{'columns'}{$i}{'data'};
|
||||
return $field;
|
||||
}
|
||||
|
||||
1;
|
||||
45
lib/App/Netdisco/DB/ResultSet/Admin.pm
Normal file
45
lib/App/Netdisco/DB/ResultSet/Admin.pm
Normal file
@@ -0,0 +1,45 @@
|
||||
package App::Netdisco::DB::ResultSet::Admin;
|
||||
use base 'App::Netdisco::DB::ResultSet';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
__PACKAGE__->load_components(qw/
|
||||
+App::Netdisco::DB::ExplicitLocking
|
||||
/);
|
||||
|
||||
=head1 ADDITIONAL METHODS
|
||||
|
||||
=head2 with_times
|
||||
|
||||
This is a modifier for any C<search()> (including the helpers below) which
|
||||
will add the following additional synthesized columns to the result set:
|
||||
|
||||
=over 4
|
||||
|
||||
=item entered_stamp
|
||||
|
||||
=item started_stamp
|
||||
|
||||
=item finished_stamp
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub with_times {
|
||||
my ($rs, $cond, $attrs) = @_;
|
||||
|
||||
return $rs
|
||||
->search_rs($cond, $attrs)
|
||||
->search({},
|
||||
{
|
||||
'+columns' => {
|
||||
entered_stamp => \"to_char(entered, 'YYYY-MM-DD HH24:MI')",
|
||||
started_stamp => \"to_char(started, 'YYYY-MM-DD HH24:MI')",
|
||||
finished_stamp => \"to_char(finished, 'YYYY-MM-DD HH24:MI')",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
1;
|
||||
612
lib/App/Netdisco/DB/ResultSet/Device.pm
Normal file
612
lib/App/Netdisco/DB/ResultSet/Device.pm
Normal file
@@ -0,0 +1,612 @@
|
||||
package App::Netdisco::DB::ResultSet::Device;
|
||||
use base 'App::Netdisco::DB::ResultSet';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use NetAddr::IP::Lite ':lower';
|
||||
|
||||
=head1 ADDITIONAL METHODS
|
||||
|
||||
=head2 with_times
|
||||
|
||||
This is a modifier for any C<search()> (including the helpers below) which
|
||||
will add the following additional synthesized columns to the result set:
|
||||
|
||||
=over 4
|
||||
|
||||
=item uptime_age
|
||||
|
||||
=item last_discover_stamp
|
||||
|
||||
=item last_macsuck_stamp
|
||||
|
||||
=item last_arpnip_stamp
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub with_times {
|
||||
my ($rs, $cond, $attrs) = @_;
|
||||
|
||||
return $rs
|
||||
->search_rs($cond, $attrs)
|
||||
->search({},
|
||||
{
|
||||
'+columns' => {
|
||||
uptime_age => \("replace(age(timestamp 'epoch' + uptime / 100 * interval '1 second', "
|
||||
."timestamp '1970-01-01 00:00:00-00')::text, 'mon', 'month')"),
|
||||
last_discover_stamp => \"to_char(last_discover, 'YYYY-MM-DD HH24:MI')",
|
||||
last_macsuck_stamp => \"to_char(last_macsuck, 'YYYY-MM-DD HH24:MI')",
|
||||
last_arpnip_stamp => \"to_char(last_arpnip, 'YYYY-MM-DD HH24:MI')",
|
||||
since_last_discover => \"extract(epoch from (age(now(), last_discover)))",
|
||||
since_last_macsuck => \"extract(epoch from (age(now(), last_macsuck)))",
|
||||
since_last_arpnip => \"extract(epoch from (age(now(), last_arpnip)))",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
=head2 search_aliases( {$name or $ip or $prefix}, \%options? )
|
||||
|
||||
Tries to find devices in Netdisco which have an identity corresponding to
|
||||
C<$name>, C<$ip> or C<$prefix>.
|
||||
|
||||
The search is across all aliases of the device, as well as its "root IP"
|
||||
identity. Note that this search will try B<not> to use DNS, in case the current
|
||||
name for an IP does not correspond to the data within Netdisco.
|
||||
|
||||
Passing a zero value to the C<partial> key of the C<options> hashref will
|
||||
prevent partial matching of a host name. Otherwise the default is to perform
|
||||
a partial, case-insensitive search on the host name fields.
|
||||
|
||||
=cut
|
||||
|
||||
sub search_aliases {
|
||||
my ($rs, $q, $options) = @_;
|
||||
$q ||= '255.255.255.255'; # hack to return empty resultset on error
|
||||
$options ||= {};
|
||||
$options->{partial} = 1 if !defined $options->{partial};
|
||||
|
||||
# rough approximation of IP addresses (v4 in v6 not supported).
|
||||
# this helps us avoid triggering any DNS.
|
||||
my $by_ip = ($q =~ m{^(?:[.0-9/]+|[:0-9a-f/]+)$}i) ? 1 : 0;
|
||||
|
||||
my $clause;
|
||||
if ($by_ip) {
|
||||
my $ip = NetAddr::IP::Lite->new($q)
|
||||
or return undef; # could be a MAC address!
|
||||
$clause = [
|
||||
'me.ip' => { '<<=' => $ip->cidr },
|
||||
'device_ips.alias' => { '<<=' => $ip->cidr },
|
||||
];
|
||||
}
|
||||
else {
|
||||
$q = "\%$q\%" if ($options->{partial} and $q !~ m/\%/);
|
||||
$clause = [
|
||||
'me.name' => { '-ilike' => $q },
|
||||
'me.dns' => { '-ilike' => $q },
|
||||
'device_ips.dns' => { '-ilike' => $q },
|
||||
];
|
||||
}
|
||||
|
||||
return $rs->search(
|
||||
{
|
||||
-or => $clause,
|
||||
},
|
||||
{
|
||||
order_by => [qw/ me.dns me.ip /],
|
||||
join => 'device_ips',
|
||||
distinct => 1,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
=head2 search_for_device( $name or $ip or $prefix )
|
||||
|
||||
This is a wrapper for C<search_aliases> which:
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
Disables partial matching on host names
|
||||
|
||||
=item *
|
||||
|
||||
Returns only the first result of any found devices
|
||||
|
||||
=back
|
||||
|
||||
If not matching devices are found, C<undef> is returned.
|
||||
|
||||
=cut
|
||||
|
||||
sub search_for_device {
|
||||
my ($rs, $q, $options) = @_;
|
||||
$options ||= {};
|
||||
$options->{partial} = 0;
|
||||
return $rs->search_aliases($q, $options)->first();
|
||||
}
|
||||
|
||||
=head2 search_by_field( \%cond, \%attrs? )
|
||||
|
||||
This variant of the standard C<search()> method returns a ResultSet of Device
|
||||
entries. It is written to support web forms which accept fields that match and
|
||||
locate Devices in the database.
|
||||
|
||||
The hashref parameter should contain fields from the Device table which will
|
||||
be intelligently used in a search query.
|
||||
|
||||
In addition, you can provide the key C<matchall> which, given a True or False
|
||||
value, controls whether fields must all match or whether any can match, to
|
||||
select a row.
|
||||
|
||||
Supported keys:
|
||||
|
||||
=over 4
|
||||
|
||||
=item matchall
|
||||
|
||||
If a True value, fields must all match to return a given row of the Device
|
||||
table, otherwise any field matching will cause the row to be included in
|
||||
results.
|
||||
|
||||
=item name
|
||||
|
||||
Can match the C<name> field as a substring.
|
||||
|
||||
=item location
|
||||
|
||||
Can match the C<location> field as a substring.
|
||||
|
||||
=item description
|
||||
|
||||
Can match the C<description> field as a substring (usually this field contains
|
||||
a description of the vendor operating system).
|
||||
|
||||
=item model
|
||||
|
||||
Will match exactly the C<model> field.
|
||||
|
||||
=item os
|
||||
|
||||
Will match exactly the C<os> field, which is the operating sytem.
|
||||
|
||||
=item os_ver
|
||||
|
||||
Will match exactly the C<os_ver> field, which is the operating sytem software version.
|
||||
|
||||
=item vendor
|
||||
|
||||
Will match exactly the C<vendor> (manufacturer).
|
||||
|
||||
=item dns
|
||||
|
||||
Can match any of the Device IP address aliases as a substring.
|
||||
|
||||
=item ip
|
||||
|
||||
Can be a string IP or a NetAddr::IP object, either way being treated as an
|
||||
IPv4 or IPv6 prefix within which the device must have one IP address alias.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub search_by_field {
|
||||
my ($rs, $p, $attrs) = @_;
|
||||
|
||||
die "condition parameter to search_by_field must be hashref\n"
|
||||
if ref {} ne ref $p or 0 == scalar keys %$p;
|
||||
|
||||
my $op = $p->{matchall} ? '-and' : '-or';
|
||||
|
||||
# this is a bit of an inelegant trick to catch junk data entry,
|
||||
# whilst avoiding returning *all* entries in the table
|
||||
if ($p->{ip} and 'NetAddr::IP::Lite' ne ref $p->{ip}) {
|
||||
$p->{ip} = ( NetAddr::IP::Lite->new($p->{ip})
|
||||
|| NetAddr::IP::Lite->new('255.255.255.255') );
|
||||
}
|
||||
|
||||
# For Search on Layers
|
||||
my @layer_search = ( '_', '_', '_', '_', '_', '_', '_' );
|
||||
# @layer_search is computer indexed, left->right
|
||||
my $layers = $p->{layers};
|
||||
if ( defined $layers && ref $layers ) {
|
||||
foreach my $layer (@$layers) {
|
||||
next unless defined $layer and length($layer);
|
||||
next if ( $layer < 1 || $layer > 7 );
|
||||
$layer_search[ $layer - 1 ] = 1;
|
||||
}
|
||||
}
|
||||
elsif ( defined $layers ) {
|
||||
$layer_search[ $layers - 1 ] = 1;
|
||||
}
|
||||
# the database field is in order 87654321
|
||||
my $layer_string = join( '', reverse @layer_search );
|
||||
|
||||
return $rs
|
||||
->search_rs({}, $attrs)
|
||||
->search({
|
||||
$op => [
|
||||
($p->{name} ? ('me.name' =>
|
||||
{ '-ilike' => "\%$p->{name}\%" }) : ()),
|
||||
($p->{location} ? ('me.location' =>
|
||||
{ '-ilike' => "\%$p->{location}\%" }) : ()),
|
||||
($p->{description} ? ('me.description' =>
|
||||
{ '-ilike' => "\%$p->{description}\%" }) : ()),
|
||||
($p->{layers} ? ('me.layers' =>
|
||||
{ '-ilike' => "\%$layer_string" }) : ()),
|
||||
|
||||
($p->{model} ? ('me.model' =>
|
||||
{ '-in' => $p->{model} }) : ()),
|
||||
($p->{os} ? ('me.os' =>
|
||||
{ '-in' => $p->{os} }) : ()),
|
||||
($p->{os_ver} ? ('me.os_ver' =>
|
||||
{ '-in' => $p->{os_ver} }) : ()),
|
||||
($p->{vendor} ? ('me.vendor' =>
|
||||
{ '-in' => $p->{vendor} }) : ()),
|
||||
|
||||
($p->{dns} ? (
|
||||
-or => [
|
||||
'me.dns' => { '-ilike' => "\%$p->{dns}\%" },
|
||||
'device_ips.dns' => { '-ilike' => "\%$p->{dns}\%" },
|
||||
]) : ()),
|
||||
|
||||
($p->{ip} ? (
|
||||
-or => [
|
||||
'me.ip' => { '<<=' => $p->{ip}->cidr },
|
||||
'device_ips.alias' => { '<<=' => $p->{ip}->cidr },
|
||||
]) : ()),
|
||||
],
|
||||
},
|
||||
{
|
||||
order_by => [qw/ me.dns me.ip /],
|
||||
(($p->{dns} or $p->{ip}) ? (
|
||||
join => 'device_ips',
|
||||
distinct => 1,
|
||||
) : ()),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
=head2 search_fuzzy( $value )
|
||||
|
||||
This method accepts a single parameter only and returns a ResultSet of rows
|
||||
from the Device table where one field matches the passed parameter.
|
||||
|
||||
The following fields are inspected for a match:
|
||||
|
||||
=over 4
|
||||
|
||||
=item contact
|
||||
|
||||
=item serial
|
||||
|
||||
=item location
|
||||
|
||||
=item name
|
||||
|
||||
=item description
|
||||
|
||||
=item dns
|
||||
|
||||
=item ip (including aliases)
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub search_fuzzy {
|
||||
my ($rs, $q) = @_;
|
||||
|
||||
die "missing param to search_fuzzy\n"
|
||||
unless $q;
|
||||
$q = "\%$q\%" if $q !~ m/\%/;
|
||||
|
||||
# basic IP check is a string match
|
||||
my $ip_clause = [
|
||||
'me.ip::text' => { '-ilike' => $q },
|
||||
'device_ips.alias::text' => { '-ilike' => $q },
|
||||
];
|
||||
|
||||
# but also allow prefix search
|
||||
(my $qc = $q) =~ s/\%//g;
|
||||
if (my $ip = NetAddr::IP::Lite->new($qc)) {
|
||||
$ip_clause = [
|
||||
'me.ip' => { '<<=' => $ip->cidr },
|
||||
'device_ips.alias' => { '<<=' => $ip->cidr },
|
||||
];
|
||||
}
|
||||
|
||||
return $rs->search(
|
||||
{
|
||||
-or => [
|
||||
'me.contact' => { '-ilike' => $q },
|
||||
'me.serial' => { '-ilike' => $q },
|
||||
'me.location' => { '-ilike' => $q },
|
||||
'me.name' => { '-ilike' => $q },
|
||||
'me.description' => { '-ilike' => $q },
|
||||
-or => [
|
||||
'me.dns' => { '-ilike' => $q },
|
||||
'device_ips.dns' => { '-ilike' => $q },
|
||||
],
|
||||
-or => $ip_clause,
|
||||
],
|
||||
},
|
||||
{
|
||||
order_by => [qw/ me.dns me.ip /],
|
||||
join => 'device_ips',
|
||||
distinct => 1,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
=head2 carrying_vlan( \%cond, \%attrs? )
|
||||
|
||||
my $set = $rs->carrying_vlan({ vlan => 123 });
|
||||
|
||||
Like C<search()>, this returns a ResultSet of matching rows from the Device
|
||||
table.
|
||||
|
||||
The returned devices each are aware of the given Vlan.
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
The C<cond> parameter must be a hashref containing a key C<vlan> with
|
||||
the value to search for.
|
||||
|
||||
=item *
|
||||
|
||||
Results are ordered by the Device DNS and IP fields.
|
||||
|
||||
=item *
|
||||
|
||||
Related rows from the C<device_vlan> table will be prefetched.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub carrying_vlan {
|
||||
my ($rs, $cond, $attrs) = @_;
|
||||
|
||||
die "vlan number required for carrying_vlan\n"
|
||||
if ref {} ne ref $cond or !exists $cond->{vlan};
|
||||
|
||||
return $rs
|
||||
->search_rs({ 'vlans.vlan' => $cond->{vlan} },
|
||||
{
|
||||
order_by => [qw/ me.dns me.ip /],
|
||||
columns => [
|
||||
'me.ip', 'me.dns',
|
||||
'me.model', 'me.os',
|
||||
'me.vendor', 'vlans.vlan',
|
||||
'vlans.description'
|
||||
],
|
||||
join => 'vlans'
|
||||
})
|
||||
->search({}, $attrs);
|
||||
}
|
||||
|
||||
=head2 carrying_vlan_name( \%cond, \%attrs? )
|
||||
|
||||
my $set = $rs->carrying_vlan_name({ name => 'Branch Office' });
|
||||
|
||||
Like C<search()>, this returns a ResultSet of matching rows from the Device
|
||||
table.
|
||||
|
||||
The returned devices each are aware of the named Vlan.
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
The C<cond> parameter must be a hashref containing a key C<name> with
|
||||
the value to search for. The value may optionally include SQL wildcard
|
||||
characters.
|
||||
|
||||
=item *
|
||||
|
||||
Results are ordered by the Device DNS and IP fields.
|
||||
|
||||
=item *
|
||||
|
||||
Related rows from the C<device_vlan> table will be prefetched.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub carrying_vlan_name {
|
||||
my ($rs, $cond, $attrs) = @_;
|
||||
|
||||
die "vlan name required for carrying_vlan_name\n"
|
||||
if ref {} ne ref $cond or !exists $cond->{name};
|
||||
|
||||
$cond->{'vlans.description'} = { '-ilike' => delete $cond->{name} };
|
||||
|
||||
return $rs
|
||||
->search_rs({}, {
|
||||
order_by => [qw/ me.dns me.ip /],
|
||||
columns => [
|
||||
'me.ip', 'me.dns',
|
||||
'me.model', 'me.os',
|
||||
'me.vendor', 'vlans.vlan',
|
||||
'vlans.description'
|
||||
],
|
||||
join => 'vlans'
|
||||
})
|
||||
->search($cond, $attrs);
|
||||
}
|
||||
|
||||
=head2 has_layer( $layer )
|
||||
|
||||
my $rset = $rs->has_layer(3);
|
||||
|
||||
This predefined C<search()> returns a ResultSet of matching rows from the
|
||||
Device table of devices advertising support of the supplied layer in the
|
||||
OSI Model.
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
The C<layer> parameter must be an integer between 1 and 7.
|
||||
|
||||
=cut
|
||||
|
||||
sub has_layer {
|
||||
my ( $rs, $layer ) = @_;
|
||||
|
||||
die "layer required and must be between 1 and 7\n"
|
||||
if !$layer || $layer < 1 || $layer > 7;
|
||||
|
||||
return $rs->search_rs( \[ 'substring(layers,9-?, 1)::int = 1', $layer ] );
|
||||
}
|
||||
|
||||
=back
|
||||
|
||||
=head2 get_models
|
||||
|
||||
Returns a sorted list of Device models with the following columns only:
|
||||
|
||||
=over 4
|
||||
|
||||
=item vendor
|
||||
|
||||
=item model
|
||||
|
||||
=item count
|
||||
|
||||
=back
|
||||
|
||||
Where C<count> is the number of instances of that Vendor's Model in the
|
||||
Netdisco database.
|
||||
|
||||
=cut
|
||||
|
||||
sub get_models {
|
||||
my $rs = shift;
|
||||
return $rs->search({}, {
|
||||
select => [ 'vendor', 'model', { count => 'ip' } ],
|
||||
as => [qw/vendor model count/],
|
||||
group_by => [qw/vendor model/],
|
||||
order_by => [{-asc => 'vendor'}, {-asc => 'model'}],
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
=head2 get_releases
|
||||
|
||||
Returns a sorted list of Device OS releases with the following columns only:
|
||||
|
||||
=over 4
|
||||
|
||||
=item os
|
||||
|
||||
=item os_ver
|
||||
|
||||
=item count
|
||||
|
||||
=back
|
||||
|
||||
Where C<count> is the number of devices running that OS release in the
|
||||
Netdisco database.
|
||||
|
||||
=cut
|
||||
|
||||
sub get_releases {
|
||||
my $rs = shift;
|
||||
return $rs->search({}, {
|
||||
select => [ 'os', 'os_ver', { count => 'ip' } ],
|
||||
as => [qw/os os_ver count/],
|
||||
group_by => [qw/os os_ver/],
|
||||
order_by => [{-asc => 'os'}, {-asc => 'os_ver'}],
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
=head2 with_port_count
|
||||
|
||||
This is a modifier for any C<search()> which
|
||||
will add the following additional synthesized column to the result set:
|
||||
|
||||
=over 4
|
||||
|
||||
=item port_count
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub with_port_count {
|
||||
my ($rs, $cond, $attrs) = @_;
|
||||
|
||||
return $rs
|
||||
->search_rs($cond, $attrs)
|
||||
->search({},
|
||||
{
|
||||
'+columns' => {
|
||||
port_count =>
|
||||
$rs->result_source->schema->resultset('DevicePort')
|
||||
->search(
|
||||
{
|
||||
'dp.ip' => { -ident => 'me.ip' },
|
||||
'dp.type' => { '!=' => 'propVirtual' },
|
||||
},
|
||||
{ alias => 'dp' }
|
||||
)->count_rs->as_query,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
=head1 SPECIAL METHODS
|
||||
|
||||
=head2 delete( \%options? )
|
||||
|
||||
Overrides the built-in L<DBIx::Class> delete method to more efficiently
|
||||
handle the removal or archiving of nodes.
|
||||
|
||||
=cut
|
||||
|
||||
sub delete {
|
||||
my $self = shift;
|
||||
|
||||
my $schema = $self->result_source->schema;
|
||||
my $devices = $self->search(undef, { columns => 'ip' });
|
||||
|
||||
foreach my $set (qw/
|
||||
DeviceIp
|
||||
DeviceVlan
|
||||
DevicePower
|
||||
DeviceModule
|
||||
Community
|
||||
/) {
|
||||
$schema->resultset($set)->search(
|
||||
{ ip => { '-in' => $devices->as_query } },
|
||||
)->delete;
|
||||
}
|
||||
|
||||
$schema->resultset('Admin')->search({
|
||||
device => { '-in' => $devices->as_query },
|
||||
})->delete;
|
||||
|
||||
$schema->resultset('Topology')->search({
|
||||
-or => [
|
||||
{ dev1 => { '-in' => $devices->as_query } },
|
||||
{ dev2 => { '-in' => $devices->as_query } },
|
||||
],
|
||||
})->delete;
|
||||
|
||||
$schema->resultset('DevicePort')->search(
|
||||
{ ip => { '-in' => $devices->as_query } },
|
||||
)->delete(@_);
|
||||
|
||||
# now let DBIC do its thing
|
||||
return $self->next::method();
|
||||
}
|
||||
|
||||
1;
|
||||
107
lib/App/Netdisco/DB/ResultSet/DeviceModule.pm
Normal file
107
lib/App/Netdisco/DB/ResultSet/DeviceModule.pm
Normal file
@@ -0,0 +1,107 @@
|
||||
package App::Netdisco::DB::ResultSet::DeviceModule;
|
||||
use base 'App::Netdisco::DB::ResultSet';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
=head1 ADDITIONAL METHODS
|
||||
|
||||
=head2 search_by_field( \%cond, \%attrs? )
|
||||
|
||||
This variant of the standard C<search()> method returns a ResultSet of Device
|
||||
Module entries. It is written to support web forms which accept fields that
|
||||
match and locate Device Modules in the database.
|
||||
|
||||
The hashref parameter should contain fields from the Device Module table
|
||||
which will be intelligently used in a search query.
|
||||
|
||||
In addition, you can provide the key C<matchall> which, given a True or False
|
||||
value, controls whether fields must all match or whether any can match, to
|
||||
select a row.
|
||||
|
||||
Supported keys:
|
||||
|
||||
=over 4
|
||||
|
||||
=item matchall
|
||||
|
||||
If a True value, fields must all match to return a given row of the Device
|
||||
table, otherwise any field matching will cause the row to be included in
|
||||
results.
|
||||
|
||||
=item description
|
||||
|
||||
Can match the C<description> field as a substring.
|
||||
|
||||
=item name
|
||||
|
||||
Can match the C<name> field as a substring.
|
||||
|
||||
=item type
|
||||
|
||||
Can match the C<type> field as a substring.
|
||||
|
||||
=item model
|
||||
|
||||
Can match the C<model> field as a substring.
|
||||
|
||||
=item serial
|
||||
|
||||
Can match the C<serial> field as a substring.
|
||||
|
||||
=item class
|
||||
|
||||
Will match exactly the C<class> field.
|
||||
|
||||
=item ips
|
||||
|
||||
List of Device IPs containing modules.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub search_by_field {
|
||||
my ( $rs, $p, $attrs ) = @_;
|
||||
|
||||
die "condition parameter to search_by_field must be hashref\n"
|
||||
if ref {} ne ref $p
|
||||
or 0 == scalar keys %$p;
|
||||
|
||||
my $op = $p->{matchall} ? '-and' : '-or';
|
||||
|
||||
return $rs->search_rs( {}, $attrs )->search(
|
||||
{ $op => [
|
||||
( $p->{description}
|
||||
? ( 'me.description' =>
|
||||
{ '-ilike' => "\%$p->{description}\%" } )
|
||||
: ()
|
||||
),
|
||||
( $p->{name}
|
||||
? ( 'me.name' => { '-ilike' => "\%$p->{name}\%" } )
|
||||
: ()
|
||||
),
|
||||
( $p->{type}
|
||||
? ( 'me.type' => { '-ilike' => "\%$p->{type}\%" } )
|
||||
: ()
|
||||
),
|
||||
( $p->{model}
|
||||
? ( 'me.model' => { '-ilike' => "\%$p->{model}\%" } )
|
||||
: ()
|
||||
),
|
||||
( $p->{serial}
|
||||
? ( 'me.serial' => { '-ilike' => "\%$p->{serial}\%" } )
|
||||
: ()
|
||||
),
|
||||
|
||||
( $p->{class}
|
||||
? ( 'me.class' => { '-in' => $p->{class} } )
|
||||
: ()
|
||||
),
|
||||
( $p->{ips} ? ( 'me.ip' => { '-in' => $p->{ips} } ) : () ),
|
||||
],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
1;
|
||||
174
lib/App/Netdisco/DB/ResultSet/DevicePort.pm
Normal file
174
lib/App/Netdisco/DB/ResultSet/DevicePort.pm
Normal file
@@ -0,0 +1,174 @@
|
||||
package App::Netdisco::DB::ResultSet::DevicePort;
|
||||
use base 'App::Netdisco::DB::ResultSet';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
__PACKAGE__->load_components(qw/
|
||||
+App::Netdisco::DB::ExplicitLocking
|
||||
/);
|
||||
|
||||
=head1 ADDITIONAL METHODS
|
||||
|
||||
=head2 with_times
|
||||
|
||||
This is a modifier for any C<search()> (including the helpers below) which
|
||||
will add the following additional synthesized columns to the result set:
|
||||
|
||||
=over 4
|
||||
|
||||
=item lastchange_stamp
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub with_times {
|
||||
my ($rs, $cond, $attrs) = @_;
|
||||
|
||||
return $rs
|
||||
->search_rs($cond, $attrs)
|
||||
->search({},
|
||||
{
|
||||
'+columns' => { lastchange_stamp =>
|
||||
\("to_char(device.last_discover - (device.uptime - me.lastchange) / 100 * interval '1 second', "
|
||||
."'YYYY-MM-DD HH24:MI:SS')") },
|
||||
join => 'device',
|
||||
});
|
||||
}
|
||||
|
||||
=head2 with_free_ports
|
||||
|
||||
This is a modifier for any C<search()> (including the helpers below) which
|
||||
will add the following additional synthesized columns to the result set:
|
||||
|
||||
=over 4
|
||||
|
||||
=item is_free
|
||||
|
||||
=back
|
||||
|
||||
In the C<$cond> hash (the first parameter) pass in the C<age_num> which must
|
||||
be an integer, and the C<age_unit> which must be a string of either C<days>,
|
||||
C<weeks>, C<months> or C<years>.
|
||||
|
||||
=cut
|
||||
|
||||
sub with_is_free {
|
||||
my ($rs, $cond, $attrs) = @_;
|
||||
|
||||
my $interval = (delete $cond->{age_num}) .' '. (delete $cond->{age_unit});
|
||||
|
||||
return $rs
|
||||
->search_rs($cond, $attrs)
|
||||
->search({},
|
||||
{
|
||||
'+columns' => { is_free =>
|
||||
\["me.up != 'up' and "
|
||||
."age(now(), to_timestamp(extract(epoch from device.last_discover) "
|
||||
."- (device.uptime - me.lastchange)/100)) "
|
||||
."> ?::interval",
|
||||
[{} => $interval]] },
|
||||
join => 'device',
|
||||
});
|
||||
}
|
||||
|
||||
=head2 only_free_ports
|
||||
|
||||
This is a modifier for any C<search()> (including the helpers below) which
|
||||
will restrict results based on whether the port is considered "free".
|
||||
|
||||
In the C<$cond> hash (the first parameter) pass in the C<age_num> which must
|
||||
be an integer, and the C<age_unit> which must be a string of either C<days>,
|
||||
C<weeks>, C<months> or C<years>.
|
||||
|
||||
=cut
|
||||
|
||||
sub only_free_ports {
|
||||
my ($rs, $cond, $attrs) = @_;
|
||||
|
||||
my $interval = (delete $cond->{age_num}) .' '. (delete $cond->{age_unit});
|
||||
|
||||
return $rs
|
||||
->search_rs($cond, $attrs)
|
||||
->search(
|
||||
{
|
||||
'me.up' => { '!=' => 'up' },
|
||||
},{
|
||||
where =>
|
||||
\["age(now(), to_timestamp(extract(epoch from device.last_discover) "
|
||||
."- (device.uptime - me.lastchange)/100)) "
|
||||
."> ?::interval",
|
||||
[{} => $interval]],
|
||||
join => 'device' },
|
||||
);
|
||||
}
|
||||
|
||||
=head2 with_vlan_count
|
||||
|
||||
This is a modifier for any C<search()> (including the helpers below) which
|
||||
will add the following additional synthesized columns to the result set:
|
||||
|
||||
=over 4
|
||||
|
||||
=item vlan_count
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub with_vlan_count {
|
||||
my ($rs, $cond, $attrs) = @_;
|
||||
|
||||
return $rs
|
||||
->search_rs($cond, $attrs)
|
||||
->search({},
|
||||
{
|
||||
'+columns' => { vlan_count =>
|
||||
$rs->result_source->schema->resultset('DevicePortVlan')
|
||||
->search(
|
||||
{
|
||||
'dpv.ip' => { -ident => 'me.ip' },
|
||||
'dpv.port' => { -ident => 'me.port' },
|
||||
},
|
||||
{ alias => 'dpv' }
|
||||
)->count_rs->as_query
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
=head1 SPECIAL METHODS
|
||||
|
||||
=head2 delete( \%options? )
|
||||
|
||||
Overrides the built-in L<DBIx::Class> delete method to more efficiently
|
||||
handle the removal or archiving of nodes.
|
||||
|
||||
=cut
|
||||
|
||||
sub delete {
|
||||
my $self = shift;
|
||||
|
||||
my $schema = $self->result_source->schema;
|
||||
my $ports = $self->search(undef, { columns => 'ip' });
|
||||
|
||||
foreach my $set (qw/
|
||||
DevicePortPower
|
||||
DevicePortVlan
|
||||
DevicePortWireless
|
||||
DevicePortSsid
|
||||
/) {
|
||||
$schema->resultset($set)->search(
|
||||
{ ip => { '-in' => $ports->as_query }},
|
||||
)->delete;
|
||||
}
|
||||
|
||||
$schema->resultset('Node')->search(
|
||||
{ switch => { '-in' => $ports->as_query }},
|
||||
)->delete(@_);
|
||||
|
||||
# now let DBIC do its thing
|
||||
return $self->next::method();
|
||||
}
|
||||
|
||||
1;
|
||||
39
lib/App/Netdisco/DB/ResultSet/DevicePortLog.pm
Normal file
39
lib/App/Netdisco/DB/ResultSet/DevicePortLog.pm
Normal file
@@ -0,0 +1,39 @@
|
||||
package App::Netdisco::DB::ResultSet::DevicePortLog;
|
||||
use base 'App::Netdisco::DB::ResultSet';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
__PACKAGE__->load_components(qw/
|
||||
+App::Netdisco::DB::ExplicitLocking
|
||||
/);
|
||||
|
||||
=head1 ADDITIONAL METHODS
|
||||
|
||||
=head2 with_times
|
||||
|
||||
This is a modifier for any C<search()> which will add the following additional
|
||||
synthesized column to the result set:
|
||||
|
||||
=over 4
|
||||
|
||||
=item creation_stamp
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub with_times {
|
||||
my ($rs, $cond, $attrs) = @_;
|
||||
|
||||
return $rs
|
||||
->search_rs($cond, $attrs)
|
||||
->search({},
|
||||
{
|
||||
'+columns' => {
|
||||
creation_stamp => \"to_char(creation, 'YYYY-MM-DD HH24:MI:SS')",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
1;
|
||||
48
lib/App/Netdisco/DB/ResultSet/DevicePortSsid.pm
Normal file
48
lib/App/Netdisco/DB/ResultSet/DevicePortSsid.pm
Normal file
@@ -0,0 +1,48 @@
|
||||
package App::Netdisco::DB::ResultSet::DevicePortSsid;
|
||||
use base 'App::Netdisco::DB::ResultSet';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
__PACKAGE__->load_components(
|
||||
qw/
|
||||
+App::Netdisco::DB::ExplicitLocking
|
||||
/
|
||||
);
|
||||
|
||||
=head1 ADDITIONAL METHODS
|
||||
|
||||
=head2 get_ssids
|
||||
|
||||
Returns a sorted list of SSIDs with the following columns only:
|
||||
|
||||
=over 4
|
||||
|
||||
=item ssid
|
||||
|
||||
=item broadcast
|
||||
|
||||
=item count
|
||||
|
||||
=back
|
||||
|
||||
Where C<count> is the number of instances of the SSID in the Netdisco
|
||||
database.
|
||||
|
||||
=cut
|
||||
|
||||
sub get_ssids {
|
||||
my $rs = shift;
|
||||
|
||||
return $rs->search(
|
||||
{},
|
||||
{ select => [ 'ssid', 'broadcast', { count => 'ssid' } ],
|
||||
as => [qw/ ssid broadcast count /],
|
||||
group_by => [qw/ ssid broadcast /],
|
||||
order_by => { -desc => [qw/count/] },
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
1;
|
||||
78
lib/App/Netdisco/DB/ResultSet/DevicePower.pm
Normal file
78
lib/App/Netdisco/DB/ResultSet/DevicePower.pm
Normal file
@@ -0,0 +1,78 @@
|
||||
package App::Netdisco::DB::ResultSet::DevicePower;
|
||||
use base 'App::Netdisco::DB::ResultSet';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
=head1 ADDITIONAL METHODS
|
||||
|
||||
|
||||
=head2 with_poestats
|
||||
|
||||
This is a modifier for any C<search()> which will add the following
|
||||
additional synthesized columns to the result set:
|
||||
|
||||
=over 4
|
||||
|
||||
=item poe_capable_ports
|
||||
|
||||
Count of ports which have the ability to supply PoE.
|
||||
|
||||
=item poe_powered_ports
|
||||
|
||||
Count of ports with PoE administratively disabled.
|
||||
|
||||
=item poe_disabled_ports
|
||||
|
||||
Count of ports which are delivering power.
|
||||
|
||||
=item poe_errored_ports
|
||||
|
||||
Count of ports either reporting a fault or in test mode.
|
||||
|
||||
=item poe_power_committed
|
||||
|
||||
Total power that has been negotiated and therefore committed on ports
|
||||
actively supplying power.
|
||||
|
||||
=item poe_power_delivering
|
||||
|
||||
Total power as measured on ports actively supplying power.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub with_poestats {
|
||||
my ($rs, $cond, $attrs) = @_;
|
||||
|
||||
return $rs
|
||||
->search_rs($cond, $attrs)
|
||||
->search({},
|
||||
{
|
||||
'columns' => {
|
||||
ip => \"DISTINCT ON (me.ip, me.module) me.ip",
|
||||
module => 'module',
|
||||
power => 'power::bigint',
|
||||
status => 'status',
|
||||
poe_capable_ports => \"COUNT(ports.port) OVER (PARTITION BY me.ip, me.module)",
|
||||
poe_powered_ports => \"SUM(CASE WHEN ports.status = 'deliveringPower' THEN 1 ELSE 0 END) OVER (PARTITION BY me.ip, me.module)",
|
||||
poe_disabled_ports => \"SUM(CASE WHEN ports.admin = 'false' THEN 1 ELSE 0 END) OVER (PARTITION BY me.ip, me.module)",
|
||||
poe_errored_ports => \"SUM(CASE WHEN ports.status ILIKE '%fault' THEN 1 ELSE 0 END) OVER (PARTITION BY me.ip, me.module)",
|
||||
poe_power_committed => \("SUM(CASE "
|
||||
. "WHEN ports.status = 'deliveringPower' AND ports.class = 'class0' THEN 15.4 "
|
||||
. "WHEN ports.status = 'deliveringPower' AND ports.class = 'class1' THEN 4.0 "
|
||||
. "WHEN ports.status = 'deliveringPower' AND ports.class = 'class2' THEN 7.0 "
|
||||
. "WHEN ports.status = 'deliveringPower' AND ports.class = 'class3' THEN 15.4 "
|
||||
. "WHEN ports.status = 'deliveringPower' AND ports.class = 'class4' THEN 30.0 "
|
||||
. "WHEN ports.status = 'deliveringPower' AND ports.class IS NULL THEN 15.4 "
|
||||
. "ELSE 0 END) OVER (PARTITION BY me.ip, me.module)"),
|
||||
poe_power_delivering => \("SUM(CASE WHEN (ports.power IS NULL OR ports.power = '0') "
|
||||
. "THEN 0 ELSE round(ports.power/1000.0, 1) END) "
|
||||
. "OVER (PARTITION BY me.ip, me.module)")
|
||||
},
|
||||
join => 'ports'
|
||||
});
|
||||
}
|
||||
|
||||
1;
|
||||
149
lib/App/Netdisco/DB/ResultSet/Node.pm
Normal file
149
lib/App/Netdisco/DB/ResultSet/Node.pm
Normal file
@@ -0,0 +1,149 @@
|
||||
package App::Netdisco::DB::ResultSet::Node;
|
||||
use base 'App::Netdisco::DB::ResultSet';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
__PACKAGE__->load_components(qw/
|
||||
+App::Netdisco::DB::ExplicitLocking
|
||||
/);
|
||||
|
||||
=head1 ADDITIONAL METHODS
|
||||
|
||||
=head2 search_by_mac( \%cond, \%attrs? )
|
||||
|
||||
my $set = $rs->search_by_mac({mac => '00:11:22:33:44:55', active => 1});
|
||||
|
||||
Like C<search()>, this returns a ResultSet of matching rows from the Node
|
||||
table.
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
The C<cond> parameter must be a hashref containing a key C<mac> with
|
||||
the value to search for.
|
||||
|
||||
=item *
|
||||
|
||||
Results are ordered by time last seen.
|
||||
|
||||
=item *
|
||||
|
||||
Additional columns C<time_first_stamp> and C<time_last_stamp> provide
|
||||
preformatted timestamps of the C<time_first> and C<time_last> fields.
|
||||
|
||||
=item *
|
||||
|
||||
A JOIN is performed on the Device table and the Device C<dns> column
|
||||
prefetched.
|
||||
|
||||
=back
|
||||
|
||||
To limit results only to active nodes, set C<< {active => 1} >> in C<cond>.
|
||||
|
||||
=cut
|
||||
|
||||
sub search_by_mac {
|
||||
my ($rs, $cond, $attrs) = @_;
|
||||
|
||||
die "mac address required for search_by_mac\n"
|
||||
if ref {} ne ref $cond or !exists $cond->{mac};
|
||||
|
||||
$cond->{'me.mac'} = delete $cond->{mac};
|
||||
|
||||
return $rs
|
||||
->search_rs({}, {
|
||||
order_by => {'-desc' => 'time_last'},
|
||||
'+columns' => [
|
||||
'device.dns',
|
||||
{ time_first_stamp => \"to_char(time_first, 'YYYY-MM-DD HH24:MI')" },
|
||||
{ time_last_stamp => \"to_char(time_last, 'YYYY-MM-DD HH24:MI')" },
|
||||
],
|
||||
join => 'device',
|
||||
})
|
||||
->search($cond, $attrs);
|
||||
}
|
||||
|
||||
=head1 SPECIAL METHODS
|
||||
|
||||
=head2 delete( \%options? )
|
||||
|
||||
Overrides the built-in L<DBIx::Class> delete method to more efficiently
|
||||
handle the removal or archiving of nodes.
|
||||
|
||||
=cut
|
||||
|
||||
sub delete {
|
||||
my $self = shift;
|
||||
my ($opts) = @_;
|
||||
$opts = {} if (ref {} ne ref $opts);
|
||||
|
||||
my $schema = $self->result_source->schema;
|
||||
my $nodes = $self->search(undef, { columns => 'mac' });
|
||||
|
||||
if (exists $opts->{archive_nodes} and $opts->{archive_nodes}) {
|
||||
foreach my $set (qw/
|
||||
NodeIp
|
||||
NodeNbt
|
||||
NodeMonitor
|
||||
Node
|
||||
/) {
|
||||
$schema->resultset($set)->search(
|
||||
{ mac => { '-in' => $nodes->as_query }},
|
||||
)->update({ active => \'false' });
|
||||
}
|
||||
|
||||
$schema->resultset('NodeWireless')
|
||||
->search({ mac => { '-in' => $nodes->as_query }})->delete;
|
||||
|
||||
# avoid letting DBIC delete nodes
|
||||
return 0E0;
|
||||
}
|
||||
elsif (exists $opts->{only_nodes} and $opts->{only_nodes}) {
|
||||
# now let DBIC do its thing
|
||||
return $self->next::method();
|
||||
}
|
||||
elsif (exists $opts->{keep_nodes} and $opts->{keep_nodes}) {
|
||||
# avoid letting DBIC delete nodes
|
||||
return 0E0;
|
||||
}
|
||||
else {
|
||||
# for node_ip and node_nbt *only* delete if there are no longer
|
||||
# any active nodes referencing the IP or NBT (hence 2nd IN clause).
|
||||
foreach my $set (qw/
|
||||
NodeIp
|
||||
NodeNbt
|
||||
/) {
|
||||
$schema->resultset($set)->search({
|
||||
'me.mac' => { '-in' => $schema->resultset($set)->search({
|
||||
'-and' => [
|
||||
-bool => 'nodes.active',
|
||||
'me.mac' => { '-in' => $nodes->as_query }
|
||||
]
|
||||
},
|
||||
{
|
||||
columns => 'mac',
|
||||
join => 'nodes',
|
||||
group_by => 'me.mac',
|
||||
having => \[ 'count(nodes.mac) = 0' ],
|
||||
})->as_query,
|
||||
},
|
||||
})->delete;
|
||||
}
|
||||
|
||||
foreach my $set (qw/
|
||||
NodeMonitor
|
||||
NodeWireless
|
||||
/) {
|
||||
$schema->resultset($set)->search(
|
||||
{ mac => { '-in' => $nodes->as_query }},
|
||||
)->delete;
|
||||
}
|
||||
|
||||
# now let DBIC do its thing
|
||||
return $self->next::method();
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
219
lib/App/Netdisco/DB/ResultSet/NodeIp.pm
Normal file
219
lib/App/Netdisco/DB/ResultSet/NodeIp.pm
Normal file
@@ -0,0 +1,219 @@
|
||||
package App::Netdisco::DB::ResultSet::NodeIp;
|
||||
use base 'App::Netdisco::DB::ResultSet';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
__PACKAGE__->load_components(qw/
|
||||
+App::Netdisco::DB::ExplicitLocking
|
||||
/);
|
||||
|
||||
my $search_attr = {
|
||||
order_by => {'-desc' => 'time_last'},
|
||||
'+columns' => [
|
||||
'oui.company',
|
||||
'oui.abbrev',
|
||||
{ time_first_stamp => \"to_char(time_first, 'YYYY-MM-DD HH24:MI')" },
|
||||
{ time_last_stamp => \"to_char(time_last, 'YYYY-MM-DD HH24:MI')" },
|
||||
],
|
||||
join => 'oui'
|
||||
};
|
||||
|
||||
=head1 with_times
|
||||
|
||||
This is a modifier for any C<search()> (including the helpers below) which
|
||||
will add the following additional synthesized columns to the result set:
|
||||
|
||||
=over 4
|
||||
|
||||
=item time_first_stamp
|
||||
|
||||
=item time_last_stamp
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub with_times {
|
||||
my ($rs, $cond, $attrs) = @_;
|
||||
|
||||
return $rs
|
||||
->search_rs({}, $search_attr)
|
||||
->search($cond, $attrs);
|
||||
}
|
||||
|
||||
=head1 search_by_ip( \%cond, \%attrs? )
|
||||
|
||||
my $set = $rs->search_by_ip({ip => '192.0.2.1', active => 1});
|
||||
|
||||
Like C<search()>, this returns a ResultSet of matching rows from the
|
||||
NodeIp table.
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
The C<cond> parameter must be a hashref containing a key C<ip> with the value
|
||||
to search for. Value can either be a simple string of IPv4 or IPv6, or a
|
||||
L<NetAddr::IP::Lite> object in which case all results within the CIDR/Prefix
|
||||
will be retrieved.
|
||||
|
||||
=item *
|
||||
|
||||
Results are ordered by time last seen.
|
||||
|
||||
=item *
|
||||
|
||||
Additional columns C<time_first_stamp> and C<time_last_stamp> provide
|
||||
preformatted timestamps of the C<time_first> and C<time_last> fields.
|
||||
|
||||
=item *
|
||||
|
||||
A JOIN is performed on the OUI table and the OUI C<company> column prefetched.
|
||||
|
||||
=back
|
||||
|
||||
To limit results only to active IPs, set C<< {active => 1} >> in C<cond>.
|
||||
|
||||
=cut
|
||||
|
||||
sub search_by_ip {
|
||||
my ($rs, $cond, $attrs) = @_;
|
||||
|
||||
die "ip address required for search_by_ip\n"
|
||||
if ref {} ne ref $cond or !exists $cond->{ip};
|
||||
|
||||
# handle either plain text IP or NetAddr::IP (/32 or CIDR)
|
||||
my ($op, $ip) = ('=', delete $cond->{ip});
|
||||
|
||||
if ('NetAddr::IP::Lite' eq ref $ip and $ip->num > 1) {
|
||||
$op = '<<=';
|
||||
$ip = $ip->cidr;
|
||||
}
|
||||
$cond->{ip} = { $op => $ip };
|
||||
|
||||
return $rs
|
||||
->search_rs({}, $search_attr)
|
||||
->search($cond, $attrs);
|
||||
}
|
||||
|
||||
=head1 search_by_name( \%cond, \%attrs? )
|
||||
|
||||
my $set = $rs->search_by_name({dns => 'foo.example.com', active => 1});
|
||||
|
||||
Like C<search()>, this returns a ResultSet of matching rows from the
|
||||
NodeIp table.
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
The NodeIp table must have a C<dns> column for this search to work. Typically
|
||||
this column is the IP's DNS PTR record, cached at the time of Netdisco Arpnip.
|
||||
|
||||
=item *
|
||||
|
||||
The C<cond> parameter must be a hashref containing a key C<dns> with the value
|
||||
to search for. The value may optionally include SQL wildcard characters.
|
||||
|
||||
=item *
|
||||
|
||||
Results are ordered by time last seen.
|
||||
|
||||
=item *
|
||||
|
||||
Additional columns C<time_first_stamp> and C<time_last_stamp> provide
|
||||
preformatted timestamps of the C<time_first> and C<time_last> fields.
|
||||
|
||||
=item *
|
||||
|
||||
A JOIN is performed on the OUI table and the OUI C<company> column prefetched.
|
||||
|
||||
=back
|
||||
|
||||
To limit results only to active IPs, set C<< {active => 1} >> in C<cond>.
|
||||
|
||||
=cut
|
||||
|
||||
sub search_by_dns {
|
||||
my ($rs, $cond, $attrs) = @_;
|
||||
|
||||
die "dns field required for search_by_dns\n"
|
||||
if ref {} ne ref $cond or !exists $cond->{dns};
|
||||
|
||||
$cond->{dns} = { '-ilike' => delete $cond->{dns} };
|
||||
|
||||
return $rs
|
||||
->search_rs({}, $search_attr)
|
||||
->search($cond, $attrs);
|
||||
}
|
||||
|
||||
=head1 search_by_mac( \%cond, \%attrs? )
|
||||
|
||||
my $set = $rs->search_by_mac({mac => '00:11:22:33:44:55', active => 1});
|
||||
|
||||
Like C<search()>, this returns a ResultSet of matching rows from the
|
||||
NodeIp table.
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
The C<cond> parameter must be a hashref containing a key C<mac> with the value
|
||||
to search for.
|
||||
|
||||
=item *
|
||||
|
||||
Results are ordered by time last seen.
|
||||
|
||||
=item *
|
||||
|
||||
Additional columns C<time_first_stamp> and C<time_last_stamp> provide
|
||||
preformatted timestamps of the C<time_first> and C<time_last> fields.
|
||||
|
||||
=item *
|
||||
|
||||
A JOIN is performed on the OUI table and the OUI C<company> column prefetched.
|
||||
|
||||
=back
|
||||
|
||||
To limit results only to active IPs, set C<< {active => 1} >> in C<cond>.
|
||||
|
||||
=cut
|
||||
|
||||
sub search_by_mac {
|
||||
my ($rs, $cond, $attrs) = @_;
|
||||
|
||||
die "mac address required for search_by_mac\n"
|
||||
if ref {} ne ref $cond or !exists $cond->{mac};
|
||||
|
||||
return $rs
|
||||
->search_rs({}, $search_attr)
|
||||
->search($cond, $attrs);
|
||||
}
|
||||
|
||||
=head2 ip_version( $version )
|
||||
|
||||
my $rset = $rs->ip_version(4);
|
||||
|
||||
This predefined C<search()> returns a ResultSet of matching rows from the
|
||||
NodeIp table of nodes with addresses of the supplied IP version.
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
The C<version> parameter must be an integer either 4 or 6.
|
||||
|
||||
=cut
|
||||
|
||||
sub ip_version {
|
||||
my ( $rs, $version ) = @_;
|
||||
|
||||
die "ip_version input must be either 4 or 6\n"
|
||||
unless $version && ( $version == 4 || $version == 6 );
|
||||
|
||||
return $rs->search_rs( \[ 'family(me.ip) = ?', $version ] );
|
||||
}
|
||||
|
||||
1;
|
||||
189
lib/App/Netdisco/DB/ResultSet/NodeNbt.pm
Normal file
189
lib/App/Netdisco/DB/ResultSet/NodeNbt.pm
Normal file
@@ -0,0 +1,189 @@
|
||||
package App::Netdisco::DB::ResultSet::NodeNbt;
|
||||
use base 'App::Netdisco::DB::ResultSet';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
__PACKAGE__->load_components(qw/
|
||||
+App::Netdisco::DB::ExplicitLocking
|
||||
/);
|
||||
|
||||
my $search_attr = {
|
||||
order_by => {'-desc' => 'time_last'},
|
||||
'+columns' => [
|
||||
'oui.company',
|
||||
{ time_first_stamp => \"to_char(time_first, 'YYYY-MM-DD HH24:MI')" },
|
||||
{ time_last_stamp => \"to_char(time_last, 'YYYY-MM-DD HH24:MI')" },
|
||||
],
|
||||
join => 'oui'
|
||||
};
|
||||
|
||||
=head1 with_times
|
||||
|
||||
This is a modifier for any C<search()> (including the helpers below) which
|
||||
will add the following additional synthesized columns to the result set:
|
||||
|
||||
=over 4
|
||||
|
||||
=item time_first_stamp
|
||||
|
||||
=item time_last_stamp
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub with_times {
|
||||
my ($rs, $cond, $attrs) = @_;
|
||||
|
||||
return $rs
|
||||
->search_rs({}, $search_attr)
|
||||
->search($cond, $attrs);
|
||||
}
|
||||
|
||||
=head1 search_by_ip( \%cond, \%attrs? )
|
||||
|
||||
my $set = $rs->search_by_ip({ip => '192.0.2.1', active => 1});
|
||||
|
||||
Like C<search()>, this returns a ResultSet of matching rows from the
|
||||
NodeNbt table.
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
The C<cond> parameter must be a hashref containing a key C<ip> with the value
|
||||
to search for. Value can either be a simple string of IPv4 or IPv6, or a
|
||||
L<NetAddr::IP::Lite> object in which case all results within the CIDR/Prefix
|
||||
will be retrieved.
|
||||
|
||||
=item *
|
||||
|
||||
Results are ordered by time last seen.
|
||||
|
||||
=item *
|
||||
|
||||
Additional columns C<time_first_stamp> and C<time_last_stamp> provide
|
||||
preformatted timestamps of the C<time_first> and C<time_last> fields.
|
||||
|
||||
=item *
|
||||
|
||||
A JOIN is performed on the OUI table and the OUI C<company> column prefetched.
|
||||
|
||||
=back
|
||||
|
||||
To limit results only to active IPs, set C<< {active => 1} >> in C<cond>.
|
||||
|
||||
=cut
|
||||
|
||||
sub search_by_ip {
|
||||
my ($rs, $cond, $attrs) = @_;
|
||||
|
||||
die "ip address required for search_by_ip\n"
|
||||
if ref {} ne ref $cond or !exists $cond->{ip};
|
||||
|
||||
# handle either plain text IP or NetAddr::IP (/32 or CIDR)
|
||||
my ($op, $ip) = ('=', delete $cond->{ip});
|
||||
|
||||
if ('NetAddr::IP::Lite' eq ref $ip and $ip->num > 1) {
|
||||
$op = '<<=';
|
||||
$ip = $ip->cidr;
|
||||
}
|
||||
$cond->{ip} = { $op => $ip };
|
||||
|
||||
return $rs
|
||||
->search_rs({}, $search_attr)
|
||||
->search($cond, $attrs);
|
||||
}
|
||||
|
||||
=head1 search_by_name( \%cond, \%attrs? )
|
||||
|
||||
my $set = $rs->search_by_name({nbname => 'MYNAME', active => 1});
|
||||
|
||||
Like C<search()>, this returns a ResultSet of matching rows from the
|
||||
NodeNbt table.
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
The C<cond> parameter must be a hashref containing a key C<nbname> with the
|
||||
value to search for. The value may optionally include SQL wildcard characters.
|
||||
|
||||
=item *
|
||||
|
||||
Results are ordered by time last seen.
|
||||
|
||||
=item *
|
||||
|
||||
Additional columns C<time_first_stamp> and C<time_last_stamp> provide
|
||||
preformatted timestamps of the C<time_first> and C<time_last> fields.
|
||||
|
||||
=item *
|
||||
|
||||
A JOIN is performed on the OUI table and the OUI C<company> column prefetched.
|
||||
|
||||
=back
|
||||
|
||||
To limit results only to active IPs, set C<< {active => 1} >> in C<cond>.
|
||||
|
||||
=cut
|
||||
|
||||
sub search_by_name {
|
||||
my ($rs, $cond, $attrs) = @_;
|
||||
|
||||
die "nbname field required for search_by_name\n"
|
||||
if ref {} ne ref $cond or !exists $cond->{nbname};
|
||||
|
||||
$cond->{nbname} = { '-ilike' => delete $cond->{nbname} };
|
||||
|
||||
return $rs
|
||||
->search_rs({}, $search_attr)
|
||||
->search($cond, $attrs);
|
||||
}
|
||||
|
||||
=head1 search_by_mac( \%cond, \%attrs? )
|
||||
|
||||
my $set = $rs->search_by_mac({mac => '00:11:22:33:44:55', active => 1});
|
||||
|
||||
Like C<search()>, this returns a ResultSet of matching rows from the
|
||||
NodeNbt table.
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
The C<cond> parameter must be a hashref containing a key C<mac> with the value
|
||||
to search for.
|
||||
|
||||
=item *
|
||||
|
||||
Results are ordered by time last seen.
|
||||
|
||||
=item *
|
||||
|
||||
Additional columns C<time_first_stamp> and C<time_last_stamp> provide
|
||||
preformatted timestamps of the C<time_first> and C<time_last> fields.
|
||||
|
||||
=item *
|
||||
|
||||
A JOIN is performed on the OUI table and the OUI C<company> column prefetched.
|
||||
|
||||
=back
|
||||
|
||||
To limit results only to active IPs, set C<< {active => 1} >> in C<cond>.
|
||||
|
||||
=cut
|
||||
|
||||
sub search_by_mac {
|
||||
my ($rs, $cond, $attrs) = @_;
|
||||
|
||||
die "mac address required for search_by_mac\n"
|
||||
if ref {} ne ref $cond or !exists $cond->{mac};
|
||||
|
||||
return $rs
|
||||
->search_rs({}, $search_attr)
|
||||
->search($cond, $attrs);
|
||||
}
|
||||
|
||||
1;
|
||||
11
lib/App/Netdisco/DB/ResultSet/NodeWireless.pm
Normal file
11
lib/App/Netdisco/DB/ResultSet/NodeWireless.pm
Normal file
@@ -0,0 +1,11 @@
|
||||
package App::Netdisco::DB::ResultSet::NodeWireless;
|
||||
use base 'App::Netdisco::DB::ResultSet';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
__PACKAGE__->load_components(qw/
|
||||
+App::Netdisco::DB::ExplicitLocking
|
||||
/);
|
||||
|
||||
1;
|
||||
11
lib/App/Netdisco/DB/ResultSet/Subnet.pm
Normal file
11
lib/App/Netdisco/DB/ResultSet/Subnet.pm
Normal file
@@ -0,0 +1,11 @@
|
||||
package App::Netdisco::DB::ResultSet::Subnet;
|
||||
use base 'App::Netdisco::DB::ResultSet';
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
__PACKAGE__->load_components(qw/
|
||||
+App::Netdisco::DB::ExplicitLocking
|
||||
/);
|
||||
|
||||
1;
|
||||
@@ -0,0 +1,372 @@
|
||||
BEGIN;
|
||||
|
||||
-- admin table - Queue for admin tasks sent from front-end for back-end processing.
|
||||
|
||||
CREATE TABLE admin (
|
||||
job serial,
|
||||
entered TIMESTAMP DEFAULT now(),
|
||||
started TIMESTAMP,
|
||||
finished TIMESTAMP,
|
||||
device inet,
|
||||
port text,
|
||||
action text,
|
||||
subaction text,
|
||||
status text,
|
||||
username text,
|
||||
userip inet,
|
||||
log text,
|
||||
debug boolean
|
||||
);
|
||||
|
||||
CREATE INDEX idx_admin_entered ON admin(entered);
|
||||
CREATE INDEX idx_admin_status ON admin(status);
|
||||
CREATE INDEX idx_admin_action ON admin(action);
|
||||
|
||||
CREATE TABLE device (
|
||||
ip inet PRIMARY KEY,
|
||||
creation TIMESTAMP DEFAULT now(),
|
||||
dns text,
|
||||
description text,
|
||||
uptime bigint,
|
||||
contact text,
|
||||
name text,
|
||||
location text,
|
||||
layers varchar(8),
|
||||
ports integer,
|
||||
mac macaddr,
|
||||
serial text,
|
||||
model text,
|
||||
ps1_type text,
|
||||
ps2_type text,
|
||||
ps1_status text,
|
||||
ps2_status text,
|
||||
fan text,
|
||||
slots integer,
|
||||
vendor text,
|
||||
os text,
|
||||
os_ver text,
|
||||
log text,
|
||||
snmp_ver integer,
|
||||
snmp_comm text,
|
||||
snmp_class text,
|
||||
vtp_domain text,
|
||||
last_discover TIMESTAMP,
|
||||
last_macsuck TIMESTAMP,
|
||||
last_arpnip TIMESTAMP
|
||||
);
|
||||
|
||||
-- Indexing for speed-ups
|
||||
CREATE INDEX idx_device_dns ON device(dns);
|
||||
CREATE INDEX idx_device_layers ON device(layers);
|
||||
CREATE INDEX idx_device_vendor ON device(vendor);
|
||||
CREATE INDEX idx_device_model ON device(model);
|
||||
|
||||
CREATE TABLE device_ip (
|
||||
ip inet,
|
||||
alias inet,
|
||||
subnet cidr,
|
||||
port text,
|
||||
dns text,
|
||||
creation TIMESTAMP DEFAULT now(),
|
||||
PRIMARY KEY(ip,alias)
|
||||
);
|
||||
|
||||
-- Indexing for speed ups
|
||||
CREATE INDEX idx_device_ip_ip ON device_ip(ip);
|
||||
CREATE INDEX idx_device_ip_alias ON device_ip(alias);
|
||||
CREATE INDEX idx_device_ip_ip_port ON device_ip(ip,port);
|
||||
|
||||
CREATE TABLE device_module (
|
||||
ip inet not null,
|
||||
index integer,
|
||||
description text,
|
||||
type text,
|
||||
parent integer,
|
||||
name text,
|
||||
class text,
|
||||
pos integer,
|
||||
hw_ver text,
|
||||
fw_ver text,
|
||||
sw_ver text,
|
||||
serial text,
|
||||
model text,
|
||||
fru boolean,
|
||||
creation TIMESTAMP DEFAULT now(),
|
||||
last_discover TIMESTAMP,
|
||||
PRIMARY KEY(ip,index)
|
||||
);
|
||||
|
||||
CREATE TABLE device_port (
|
||||
ip inet,
|
||||
port text,
|
||||
creation TIMESTAMP DEFAULT now(),
|
||||
descr text,
|
||||
up text,
|
||||
up_admin text,
|
||||
type text,
|
||||
duplex text,
|
||||
duplex_admin text,
|
||||
speed text,
|
||||
name text,
|
||||
mac macaddr,
|
||||
mtu integer,
|
||||
stp text,
|
||||
remote_ip inet,
|
||||
remote_port text,
|
||||
remote_type text,
|
||||
remote_id text,
|
||||
vlan text,
|
||||
pvid integer,
|
||||
lastchange bigint,
|
||||
PRIMARY KEY(port,ip)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_device_port_ip ON device_port(ip);
|
||||
CREATE INDEX idx_device_port_remote_ip ON device_port(remote_ip);
|
||||
-- For the duplex mismatch finder :
|
||||
CREATE INDEX idx_device_port_ip_port_duplex ON device_port(ip,port,duplex);
|
||||
CREATE INDEX idx_device_port_ip_up_admin ON device_port(ip,up_admin);
|
||||
CREATE INDEX idx_device_port_mac ON device_port(mac);
|
||||
|
||||
CREATE TABLE device_port_log (
|
||||
id serial,
|
||||
ip inet,
|
||||
port text,
|
||||
reason text,
|
||||
log text,
|
||||
username text,
|
||||
userip inet,
|
||||
action text,
|
||||
creation TIMESTAMP DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_device_port_log_1 ON device_port_log(ip,port);
|
||||
CREATE INDEX idx_device_port_log_user ON device_port_log(username);
|
||||
|
||||
CREATE TABLE device_port_power (
|
||||
ip inet,
|
||||
port text,
|
||||
module integer,
|
||||
admin text,
|
||||
status text,
|
||||
class text,
|
||||
power integer,
|
||||
PRIMARY KEY(port,ip)
|
||||
);
|
||||
|
||||
CREATE TABLE device_port_ssid (
|
||||
ip inet,
|
||||
port text,
|
||||
ssid text,
|
||||
broadcast boolean,
|
||||
bssid macaddr
|
||||
);
|
||||
|
||||
CREATE INDEX idx_device_port_ssid_ip_port ON device_port_ssid(ip,port);
|
||||
|
||||
CREATE TABLE device_port_vlan (
|
||||
ip inet,
|
||||
port text,
|
||||
vlan integer,
|
||||
native boolean not null default false,
|
||||
creation TIMESTAMP DEFAULT now(),
|
||||
last_discover TIMESTAMP DEFAULT now(),
|
||||
vlantype text,
|
||||
PRIMARY KEY(ip,port,vlan)
|
||||
);
|
||||
|
||||
CREATE TABLE device_port_wireless (
|
||||
ip inet,
|
||||
port text,
|
||||
channel integer,
|
||||
power integer
|
||||
);
|
||||
|
||||
CREATE INDEX idx_device_port_wireless_ip_port ON device_port_wireless(ip,port);
|
||||
|
||||
CREATE TABLE device_power (
|
||||
ip inet,
|
||||
module integer,
|
||||
power integer,
|
||||
status text,
|
||||
PRIMARY KEY(ip,module)
|
||||
);
|
||||
|
||||
CREATE TABLE device_vlan (
|
||||
ip inet,
|
||||
vlan integer,
|
||||
description text,
|
||||
creation TIMESTAMP DEFAULT now(),
|
||||
last_discover TIMESTAMP DEFAULT now(),
|
||||
PRIMARY KEY(ip,vlan)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE log (
|
||||
id serial,
|
||||
creation TIMESTAMP DEFAULT now(),
|
||||
class text,
|
||||
entry text,
|
||||
logfile text
|
||||
);
|
||||
|
||||
CREATE TABLE node (
|
||||
mac macaddr,
|
||||
switch inet,
|
||||
port text,
|
||||
vlan text default '0',
|
||||
active boolean,
|
||||
oui varchar(8),
|
||||
time_first timestamp default now(),
|
||||
time_recent timestamp default now(),
|
||||
time_last timestamp default now(),
|
||||
PRIMARY KEY(mac,switch,port,vlan)
|
||||
);
|
||||
|
||||
-- Indexes speed things up a LOT
|
||||
CREATE INDEX idx_node_switch_port_active ON node(switch,port,active);
|
||||
CREATE INDEX idx_node_switch_port ON node(switch,port);
|
||||
CREATE INDEX idx_node_switch ON node(switch);
|
||||
CREATE INDEX idx_node_mac ON node(mac);
|
||||
CREATE INDEX idx_node_mac_active ON node(mac,active);
|
||||
-- CREATE INDEX idx_node_oui ON node(oui);
|
||||
|
||||
CREATE TABLE node_ip (
|
||||
mac macaddr,
|
||||
ip inet,
|
||||
active boolean,
|
||||
time_first timestamp default now(),
|
||||
time_last timestamp default now(),
|
||||
PRIMARY KEY(mac,ip)
|
||||
);
|
||||
|
||||
-- Indexing speed ups.
|
||||
CREATE INDEX idx_node_ip_ip ON node_ip(ip);
|
||||
CREATE INDEX idx_node_ip_ip_active ON node_ip(ip,active);
|
||||
CREATE INDEX idx_node_ip_mac ON node_ip(mac);
|
||||
CREATE INDEX idx_node_ip_mac_active ON node_ip(mac,active);
|
||||
|
||||
CREATE TABLE node_monitor (
|
||||
mac macaddr,
|
||||
active boolean,
|
||||
why text,
|
||||
cc text,
|
||||
date TIMESTAMP DEFAULT now(),
|
||||
PRIMARY KEY(mac)
|
||||
);
|
||||
|
||||
-- node_nbt - Hold Netbios information for each node.
|
||||
|
||||
CREATE TABLE node_nbt (
|
||||
mac macaddr PRIMARY KEY,
|
||||
ip inet,
|
||||
nbname text,
|
||||
domain text,
|
||||
server boolean,
|
||||
nbuser text,
|
||||
active boolean,
|
||||
time_first timestamp default now(),
|
||||
time_last timestamp default now()
|
||||
);
|
||||
|
||||
-- Indexing speed ups.
|
||||
CREATE INDEX idx_node_nbt_mac ON node_nbt(mac);
|
||||
CREATE INDEX idx_node_nbt_nbname ON node_nbt(nbname);
|
||||
CREATE INDEX idx_node_nbt_domain ON node_nbt(domain);
|
||||
CREATE INDEX idx_node_nbt_mac_active ON node_nbt(mac,active);
|
||||
|
||||
-- Add "vlan" column to node table
|
||||
-- ALTER TABLE node ADD COLUMN vlan text default '0';
|
||||
|
||||
alter table node drop constraint node_pkey;
|
||||
alter table node add primary key (mac, switch, port, vlan);
|
||||
|
||||
CREATE TABLE node_wireless (
|
||||
mac macaddr,
|
||||
ssid text default '',
|
||||
uptime integer,
|
||||
maxrate integer,
|
||||
txrate integer,
|
||||
sigstrength integer,
|
||||
sigqual integer,
|
||||
rxpkt integer,
|
||||
txpkt integer,
|
||||
rxbyte bigint,
|
||||
txbyte bigint,
|
||||
time_last timestamp default now(),
|
||||
PRIMARY KEY(mac,ssid)
|
||||
);
|
||||
|
||||
|
||||
-- Add "ssid" column to node_wireless table
|
||||
-- ALTER TABLE node_wireless ADD ssid text default '';
|
||||
|
||||
alter table node_wireless drop constraint node_wireless_pkey;
|
||||
alter table node_wireless add primary key (mac, ssid);
|
||||
|
||||
|
||||
|
||||
CREATE TABLE oui (
|
||||
oui varchar(8) PRIMARY KEY,
|
||||
company text
|
||||
);
|
||||
|
||||
|
||||
-- process table - Queue to coordinate between processes in multi-process mode.
|
||||
|
||||
CREATE TABLE process (
|
||||
controller integer not null,
|
||||
device inet not null,
|
||||
action text not null,
|
||||
status text,
|
||||
count integer,
|
||||
creation TIMESTAMP DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE sessions (
|
||||
id char(32) NOT NULL PRIMARY KEY,
|
||||
creation TIMESTAMP DEFAULT now(),
|
||||
a_session text
|
||||
);
|
||||
|
||||
CREATE TABLE subnets (
|
||||
net cidr NOT NULL,
|
||||
creation timestamp default now(),
|
||||
last_discover timestamp default now(),
|
||||
PRIMARY KEY(net)
|
||||
);
|
||||
|
||||
-- Add "topology" table to augment manual topo file
|
||||
CREATE TABLE topology (
|
||||
dev1 inet not null,
|
||||
port1 text not null,
|
||||
dev2 inet not null,
|
||||
port2 text not null
|
||||
);
|
||||
|
||||
|
||||
|
||||
-- This table logs login and logout / change requests for users
|
||||
|
||||
CREATE TABLE user_log (
|
||||
entry serial,
|
||||
username varchar(50),
|
||||
userip inet,
|
||||
event text,
|
||||
details text,
|
||||
creation TIMESTAMP DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE users (
|
||||
username varchar(50) PRIMARY KEY,
|
||||
password text,
|
||||
creation TIMESTAMP DEFAULT now(),
|
||||
last_on TIMESTAMP,
|
||||
port_control boolean DEFAULT false,
|
||||
ldap boolean DEFAULT false,
|
||||
admin boolean DEFAULT false,
|
||||
fullname text,
|
||||
note text
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE device_port_vlan ADD COLUMN vlantype text;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE node_ip ADD COLUMN dns text;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE node DROP CONSTRAINT node_pkey;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE node_wireless DROP CONSTRAINT node_wireless_pkey;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE node ADD COLUMN vlan text DEFAULT '0' NOT NULL;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE node_wireless ADD COLUMN ssid text DEFAULT '' NOT NULL;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE INDEX device_port_power_idx_ip_port on device_port_power (ip, port);
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,7 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE admin DROP CONSTRAINT IF EXISTS admin_pkey;
|
||||
|
||||
ALTER TABLE admin ADD PRIMARY KEY (job);
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE node ADD PRIMARY KEY (mac, switch, port, vlan);
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE node_wireless ADD PRIMARY KEY (mac, ssid);
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,14 @@
|
||||
BEGIN;
|
||||
|
||||
-- Database Schema Modifications for upgrading from 0.9x to 0.93
|
||||
|
||||
ALTER TABLE device_port ADD COLUMN remote_type text;
|
||||
ALTER TABLE device_port ADD COLUMN remote_id text;
|
||||
ALTER TABLE device_port ADD COLUMN vlan text;
|
||||
|
||||
ALTER TABLE device ADD COLUMN vtp_domain text;
|
||||
|
||||
ALTER TABLE users ADD COLUMN fullname text;
|
||||
ALTER TABLE users ADD COLUMN note text;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,10 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE UNIQUE INDEX jobs_queued ON admin (
|
||||
action,
|
||||
coalesce(subaction, '_x_'),
|
||||
coalesce(device, '255.255.255.255'),
|
||||
coalesce(port, '_x_')
|
||||
) WHERE status LIKE 'queued%';
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,7 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE topology ADD CONSTRAINT topology_dev1_port1 UNIQUE (dev1, port1);
|
||||
|
||||
ALTER TABLE topology ADD CONSTRAINT topology_dev2_port2 UNIQUE (dev2, port2);
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE device_port ADD COLUMN "manual_topo" bool DEFAULT false NOT NULL;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,6 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE device_port ADD COLUMN "is_uplink" bool;
|
||||
ALTER TABLE device_port ADD COLUMN "is_uplink_admin" bool;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE device_ip DROP CONSTRAINT "device_ip_alias";
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
-- ALTER TABLE device_port ALTER COLUMN remote_id TYPE bytea USING remote_id::bytea;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
-- ALTER TABLE device_port ALTER COLUMN remote_id TYPE text USING remote_id::text;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE device ADD COLUMN snmp_comm_rw text;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE device DROP COLUMN snmp_comm_rw;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,9 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE "community" (
|
||||
"ip" inet NOT NULL,
|
||||
"snmp_comm_rw" text,
|
||||
PRIMARY KEY ("ip")
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,38 @@
|
||||
BEGIN;
|
||||
|
||||
-- Netdisco
|
||||
-- Database Schema Modifications
|
||||
-- UPGRADE from 0.93 to 0.94
|
||||
|
||||
ALTER TABLE device_port ADD COLUMN lastchange bigint;
|
||||
|
||||
ALTER TABLE log ADD COLUMN logfile text;
|
||||
|
||||
CREATE TABLE user_log (
|
||||
entry serial,
|
||||
username varchar(50),
|
||||
userip inet,
|
||||
event text,
|
||||
details text,
|
||||
creation TIMESTAMP DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE node_nbt (
|
||||
mac macaddr PRIMARY KEY,
|
||||
ip inet,
|
||||
nbname text,
|
||||
domain text,
|
||||
server boolean,
|
||||
nbuser text,
|
||||
active boolean,
|
||||
time_first timestamp default now(),
|
||||
time_last timestamp default now()
|
||||
);
|
||||
|
||||
-- Indexing speed ups.
|
||||
CREATE INDEX idx_node_nbt_mac ON node_nbt(mac);
|
||||
CREATE INDEX idx_node_nbt_nbname ON node_nbt(nbname);
|
||||
CREATE INDEX idx_node_nbt_domain ON node_nbt(domain);
|
||||
CREATE INDEX idx_node_nbt_mac_active ON node_nbt(mac,active);
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,8 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE node_wireless ALTER COLUMN rxpkt TYPE bigint;
|
||||
ALTER TABLE node_wireless ALTER COLUMN txpkt TYPE bigint;
|
||||
ALTER TABLE node_wireless ALTER COLUMN rxbyte TYPE bigint;
|
||||
ALTER TABLE node_wireless ALTER COLUMN txbyte TYPE bigint;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
DROP INDEX IF EXISTS jobs_queued;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE community ADD COLUMN snmp_auth_tag text;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
UPDATE node SET vlan = '0' WHERE vlan IS NULL;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE INDEX node_ip_idx_ip_active ON node_ip (ip, active);
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,7 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE device_port_vlan DROP CONSTRAINT device_port_vlan_pkey;
|
||||
|
||||
ALTER TABLE device_port_vlan ADD PRIMARY KEY (ip, port, vlan, native);
|
||||
|
||||
COMMIT;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user