From 52021772fdcb025ad3764af5d90e5c9202309986 Mon Sep 17 00:00:00 2001 From: Oliver Gorwits Date: Sun, 2 Dec 2012 21:17:54 +0000 Subject: [PATCH] new netdisco-daemon for port_control activities --- Changes | 4 +- Netdisco/bin/netdisco-daemon | 243 +++++++++++++++++++++++ Netdisco/lib/Netdisco/DB/Result/Admin.pm | 1 + 3 files changed, 247 insertions(+), 1 deletion(-) create mode 100755 Netdisco/bin/netdisco-daemon diff --git a/Changes b/Changes index de25ed30..e2300def 100644 --- a/Changes +++ b/Changes @@ -3,17 +3,19 @@ [NEW FEATURES] * Network Map now shows all device neighbors and allows click-through nav + * Add new netdisco-daemon to handle port_control actions [ENHANCEMENTS] * eradicate checks for dns column in node_ip table - now assumed to be there * remove .pl from script names + * no longer deploy FK constraints with DB schema as they upset legacy netdisco.pm [BUG FIXES] * port cotrol user log check now looks for all actions * node search switchport link shows connected nodes again - * no longer deploy FK constraints with DB schema as they upset legacy netdisco.pm + * show device IP in search results when DNS is not available 0.7 - 2012-11-25 diff --git a/Netdisco/bin/netdisco-daemon b/Netdisco/bin/netdisco-daemon new file mode 100755 index 00000000..e887a27f --- /dev/null +++ b/Netdisco/bin/netdisco-daemon @@ -0,0 +1,243 @@ +#!/usr/bin/env perl + +use strict; +use warnings FATAL => 'all'; + +use Dancer ':script'; +use Dancer::Plugin::DBIC 'schema'; + +use Netdisco::DB; +use SNMP::Info; + +use Daemon::Generic::While1; +use Config::Tiny; +use File::Slurp; +use Try::Tiny; +use feature 'say'; + +newdaemon( + progname => 'netdisco-daemon', + ($> != 0 ? (pidbase => './') : ()), + configfile => '/etc/netdisco/netdisco.conf', + logpriority => 'daemon.info', +); + +sub gd_preconfig { + my $self = shift; + my $config = {}; + + if (-e $self->{configfile}) { + # read file and alter line continuations to be single lines + my $config_content = read_file($self->{configfile}); + $config_content =~ s/\\\n//sg; + + # parse config naively as .ini + $config = Config::Tiny->new()->read_string($config_content); + $self->gd_error(Config::Tiny->errstr) unless defined $config; + } + + $self->gd_error('No read-write community string has been set.') + unless length $config->{_}->{community_rw}; + + # store for later access + var(nd_config => $config); + + # add local settings + $config->{loc} = { + sleep_time => 5, + }; + + return (); # important +} + +sub get_device { + my ($self, $device) = @_; + + my $alias = schema('netdisco')->resultset('DeviceIp') + ->search({alias => $device})->first; + return if not eval { $alias->ip }; + + return schema('netdisco')->resultset('Device') + ->find({ip => $alias->ip}); +} + +sub build_mibdirs { + my $nd_config = var('nd_config'); + + my $mibhome = $nd_config->{_}->{mibhome}; + (my $mibdirs = $nd_config->{_}->{mibdirs}) =~ s/\s+//g; + + $mibdirs =~ s/\$mibhome/$mibhome/g; + return [ split /,/, $mibdirs ]; +} + +sub snmp_connect { + my ($self, $device) = @_; + my $nd_config = var('nd_config'); + + # TODO: really only supporing v2c at the moment + my %snmp_args = ( + DestHost => $device->ip, + Version => ($device->snmp_ver || $nd_config->{_}->{snmpver} || 2), + Retries => ($nd_config->{_}->{snmpretries} || 2), + Timeout => ($nd_config->{_}->{snmptimeout} || 1000000), + MibDirs => build_mibdirs(), + AutoSpecify => 1, + Debug => ($ENV{INFO_TRACE} || 0), + ); + + (my $comm = $nd_config->{_}->{community_rw}) =~ s/\s+//g; + my @communities = split /,/, $comm; + + my $info = undef; + COMMUNITY: foreach my $c (@communities) { + try { + $info = SNMP::Info->new(%snmp_args, Community => $c); + last COMMUNITY if ( + $info + and (not defined $info->error) + and length $info->uptime + ); + }; + } + + return $info; +} + +sub set_location { + my ($self, $job, $device, $info) = @_; + my $location = ($job->subaction || ''); + + try { + my $rv = $info->set_location($location); + if (!defined $rv) { + my $log = sprintf 'Failed to set location on [%s]: %s', + $job->device, ($info->error || ''); + return ('error', $log); + } + + # double check + $info->clear_cache; + my $new_location = ($info->location || ''); + if ($new_location ne $location) { + my $log = sprintf 'Failed to update location on [%s] to [%s]', + $job->device, ($location); + return ('error', $log); + } + + # update netdisco DB + $device->update({location => $location}); + + my $log = sprintf 'Updated location on [%s] to [%s]', + $job->device, $location; + return ('done', $log); + } + catch { + return( 'error', + (sprintf 'Failed to update location on [%s]: %s', $job->device, $_) + ); + }; +} + +sub do_job { + my ($self, $job) = @_; + my $nd_config = var('nd_config'); + + # get device details from db + my $device = $self->get_device($job->device) + or return (); + + # snmp connect using rw community + my $info = $self->snmp_connect($device) + or return (); + + # do update + my %dispatch = ( + location => 'set_location', + ); + + my $target = $dispatch{$job->action} + or return (); + + # return results + return $self->$target($job, $device, $info); +} + +sub gd_run_body { + my $self = shift; + my $nd_config = var('nd_config'); + + # get all pending jobs + my $rs = schema('netdisco')->resultset('Admin')->search({ + action => [qw/location contact portcontrol portname vlan/], + status => 'queued', + }); + + JOB: while (my $job = $rs->next) { + # filter for discover_* + my $device = NetAddr::IP::Lite->new($job->device) or next JOB; + + if (length $nd_config->{_}->{discover_no}) { + my @d_no = split /,\s*/, $nd_config->{_}->{discover_no}; + foreach my $item (@d_no) { + my $ip = NetAddr::IP::Lite->new($item) or next JOB; + next JOB if $ip->contains($device); + } + } + + if (length $nd_config->{_}->{discover_only}) { + my $okay = 0; + my @d_only = split /,\s*/, $nd_config->{_}->{discover_only}; + foreach my $item (@d_only) { + my $ip = NetAddr::IP::Lite->new($item) or next JOB; + ++$okay if $ip->contains($device); + } + next JOB if not $okay; + } + + # lock db table, check job state is still queued, update to running + try { + my $status_updated = schema('netdisco')->txn_do(sub { + my $row = schema('netdisco')->resultset('Admin')->find( + {job => $job->job}, + {for => 'update'} + ); + + return 0 if $row->status ne 'queued'; + $row->update({status => 'running', started => \'now()'}); + return 1; + }); + + next JOB if not $status_updated; + } + catch { + warn "error updating job status: $_\n"; + next JOB; + }; + + # do job + my ($status, $log) = $self->do_job($job); + + # revert to queued status if we failed to connect to device + if (not $status) { + try { + schema('netdisco')->resultset('Admin') + ->find($job->job) + ->update({status => 'queued', started => undef}); + } + catch { warn "error updating job: $_\n" }; + } + else { + # update job state to done/error with log + try { + schema('netdisco')->resultset('Admin') + ->find($job->job) + ->update({status => $status, log => $log, finished => \'now()'}); + } + catch { warn "error updating job: $_\n" }; + } + } + + $self->gd_sleep( $nd_config->{loc}->{sleep_time} ); +} + diff --git a/Netdisco/lib/Netdisco/DB/Result/Admin.pm b/Netdisco/lib/Netdisco/DB/Result/Admin.pm index cdb06868..46390a87 100644 --- a/Netdisco/lib/Netdisco/DB/Result/Admin.pm +++ b/Netdisco/lib/Netdisco/DB/Result/Admin.pm @@ -52,6 +52,7 @@ __PACKAGE__->add_columns( # 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 1;