From a8f0894986650c0f9853452d1b71755a20ded98c Mon Sep 17 00:00:00 2001 From: Oliver Gorwits Date: Thu, 4 Apr 2013 14:47:28 +0100 Subject: [PATCH] implementation of refresh, discover, and store_device --- Netdisco/lib/App/Netdisco/Daemon/Queue.pm | 2 +- .../lib/App/Netdisco/Daemon/Worker/Manager.pm | 2 +- .../lib/App/Netdisco/Daemon/Worker/Poller.pm | 1 + .../Netdisco/Daemon/Worker/Poller/Discover.pm | 21 +++- Netdisco/lib/App/Netdisco/Util/DNS.pm | 53 +++++++++ .../lib/App/Netdisco/Util/DiscoverAndStore.pm | 102 ++++++++++++++++++ 6 files changed, 178 insertions(+), 3 deletions(-) create mode 100644 Netdisco/lib/App/Netdisco/Util/DNS.pm create mode 100644 Netdisco/lib/App/Netdisco/Util/DiscoverAndStore.pm diff --git a/Netdisco/lib/App/Netdisco/Daemon/Queue.pm b/Netdisco/lib/App/Netdisco/Daemon/Queue.pm index 7a354cd0..d333ebcd 100644 --- a/Netdisco/lib/App/Netdisco/Daemon/Queue.pm +++ b/Netdisco/lib/App/Netdisco/Daemon/Queue.pm @@ -33,7 +33,7 @@ sub capacity_for { debug "checking local capacity for action $action"; my $action_map = { - Poller => [qw//], + Poller => [qw/refresh discover/], Interactive => [qw/location contact portcontrol portname vlan power/], }; diff --git a/Netdisco/lib/App/Netdisco/Daemon/Worker/Manager.pm b/Netdisco/lib/App/Netdisco/Daemon/Worker/Manager.pm index 0b20ee68..823bccad 100644 --- a/Netdisco/lib/App/Netdisco/Daemon/Worker/Manager.pm +++ b/Netdisco/lib/App/Netdisco/Daemon/Worker/Manager.pm @@ -14,7 +14,7 @@ my $fqdn = hostfqdn || 'localhost'; my $role_map = { (map {$_ => 'Poller'} - qw//), + qw/refresh discover/), (map {$_ => 'Interactive'} qw/location contact portcontrol portname vlan power/) }; diff --git a/Netdisco/lib/App/Netdisco/Daemon/Worker/Poller.pm b/Netdisco/lib/App/Netdisco/Daemon/Worker/Poller.pm index 0dc199cf..250c950a 100644 --- a/Netdisco/lib/App/Netdisco/Daemon/Worker/Poller.pm +++ b/Netdisco/lib/App/Netdisco/Daemon/Worker/Poller.pm @@ -26,6 +26,7 @@ sub worker_body { my $job = schema('daemon')->resultset('Admin') ->new_result($candidate); my $jid = $job->job; + my $target = $job->action; next unless $self->can($target); debug "poll ($wid): can ${target}() for job $jid"; diff --git a/Netdisco/lib/App/Netdisco/Daemon/Worker/Poller/Discover.pm b/Netdisco/lib/App/Netdisco/Daemon/Worker/Poller/Discover.pm index 11e00f3c..5807ec7b 100644 --- a/Netdisco/lib/App/Netdisco/Daemon/Worker/Poller/Discover.pm +++ b/Netdisco/lib/App/Netdisco/Daemon/Worker/Poller/Discover.pm @@ -3,9 +3,13 @@ package App::Netdisco::Daemon::Worker::Poller::Discover; use Dancer qw/:moose :syntax :script/; use Dancer::Plugin::DBIC 'schema'; -use App::Netdisco::Util::SNMP ':all'; +use App::Netdisco::Util::SNMP 'snmp_connect'; +use App::Netdisco::Util::Device 'get_device'; +use App::Netdisco::Util::DiscoverAndStore ':all'; use App::Netdisco::Daemon::Worker::Interactive::Util ':all'; +use NetAddr::IP::Lite ':lower'; + use Role::Tiny; use namespace::clean; @@ -29,6 +33,21 @@ sub refresh { sub discover { my ($self, $job) = @_; + my $host = NetAddr::IP::Lite->new($job->device); + my $device = get_device($host->addr); + my $snmp = snmp_connect($device); + + if (!defined $snmp) { + return error("Discover failed: could not SNMP connect to $host"); + } + + store_device($device, $snmp); + #store_interfaces($ip, $snmp); + #store_vlans($ip, $snmp); + #store_power($ip, $snmp); + #store_modules($ip, $snmp); + + return done("Ended Discover for $host"); } 1; diff --git a/Netdisco/lib/App/Netdisco/Util/DNS.pm b/Netdisco/lib/App/Netdisco/Util/DNS.pm new file mode 100644 index 00000000..5e15dd1b --- /dev/null +++ b/Netdisco/lib/App/Netdisco/Util/DNS.pm @@ -0,0 +1,53 @@ +package App::Netdisco::Util::DNS; + +use strict; +use warnings FATAL => 'all'; + +use Net::DNS; + +use base 'Exporter'; +our @EXPORT = (); +our @EXPORT_OK = qw/ + hostname_from_ip +/; +our %EXPORT_TAGS = (all => \@EXPORT_OK); + +=head1 NAME + +App::Netdisco::Util::DNS + +=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 hostname_from_ip( $ip ) + +Given an IP address (either IPv4 or IPv6), return the canonical hostname. + +Returns C if no PTR record exists for the IP. + +=cut + +sub hostname_from_ip { + my $ip = shift; + + my $res = Net::DNS::Resolver->new; + my $query = $res->search($ip); + + if ($query) { + foreach my $rr ($query->answer) { + next unless $rr->type eq "PTR"; + return $rr->ptrdname; + } + } + + return undef; +} + +1; + diff --git a/Netdisco/lib/App/Netdisco/Util/DiscoverAndStore.pm b/Netdisco/lib/App/Netdisco/Util/DiscoverAndStore.pm new file mode 100644 index 00000000..de008893 --- /dev/null +++ b/Netdisco/lib/App/Netdisco/Util/DiscoverAndStore.pm @@ -0,0 +1,102 @@ +package App::Netdisco::Util::DiscoverAndStore; + +use Dancer qw/:syntax :script/; + +use App::Netdisco::Util::DNS 'hostname_from_ip'; +use NetAddr::IP::Lite ':lower'; + +use base 'Exporter'; +our @EXPORT = (); +our @EXPORT_OK = qw/ + store_device +/; +our %EXPORT_TAGS = (all => \@EXPORT_OK); + +=head1 NAME + +App::Netdisco::Util::DiscoverAndStore + +=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 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 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; + + # build device interfaces suitable for DBIC + my @interfaces; + foreach my $entry (keys %$ip_index) { + my $ip = NetAddr::IP::Lite->new($entry); + my $addr = $ip->addr; + + next if $addr eq '0.0.0.0'; + next if $ip->within(NetAddr::IP::Lite->new('127.0.0.0/8')); + 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; + + push @interfaces, { + alias => $addr, + port => $port, + subnet => $subnet, + dns => hostname_from_ip($addr), + }; + } + + # VTP Management Domain -- assume only one. + my $vtpdomains = $snmp->vtp_d_name; + my $vtpdomain; + if (defined $vtpdomains and scalar values %$vtpdomains) { + $device->vtp_domain( (values %$vtpdomains)[-1] ); + } + + my $hostname = hostname_from_ip($device->ip); + $device->dns($hostname) if length $hostname; + + my @properties = qw/ + snmp_ver snmp_comm + description uptime contact name location + layers ports mac serial model + ps1_type ps2_type ps1_status ps2_status + fan slots + vendor os os_ver + /; + + foreach my $property (@properties) { + $device->$property( $snmp->$property ); + } + + $device->snmp_class( $snmp->class ); + $device->last_discover(scalar localtime); + + schema('netdisco')->txn_do(sub { + $device->device_ips->delete; + $device->update_or_insert; + $device->device_ips->populate(\@interfaces); + }); +} + +1;