diff --git a/lib/App/Netdisco/Backend/Worker/Interactive/DeviceActions.pm b/lib/App/Netdisco/Backend/Worker/Interactive/DeviceActions.pm index 62bfbe6c..09d6a558 100644 --- a/lib/App/Netdisco/Backend/Worker/Interactive/DeviceActions.pm +++ b/lib/App/Netdisco/Backend/Worker/Interactive/DeviceActions.pm @@ -23,7 +23,7 @@ sub _set_device_generic { # snmp connect using rw community my $info = snmp_connect_rw($ip) - or return job_error("Failed to connect to device [$ip] to update $slot"); + or return job_defer("Failed to connect to device [$ip] to update $slot"); my $method = 'set_'. $slot; my $rv = $info->$method($data); diff --git a/lib/App/Netdisco/Backend/Worker/Interactive/PortActions.pm b/lib/App/Netdisco/Backend/Worker/Interactive/PortActions.pm index e34d49e4..549fcb89 100644 --- a/lib/App/Netdisco/Backend/Worker/Interactive/PortActions.pm +++ b/lib/App/Netdisco/Backend/Worker/Interactive/PortActions.pm @@ -75,7 +75,7 @@ sub _set_port_generic { if ($device->vendor ne 'netdisco') { # snmp connect using rw community my $info = snmp_connect_rw($ip) - or return job_error("Failed to connect to device [$ip] to control port"); + or return job_defer("Failed to connect to device [$ip] to control port"); my $iid = get_iid($info, $port) or return job_error("Failed to get port ID for [$pn] from [$ip]"); @@ -128,7 +128,7 @@ sub power { # snmp connect using rw community my $info = snmp_connect_rw($ip) - or return job_error("Failed to connect to device [$ip] to control port"); + or return job_defer("Failed to connect to device [$ip] to control power"); my $powerid = get_powerid($info, $port) or return job_error("Failed to get power ID for [$pn] from [$ip]"); diff --git a/lib/App/Netdisco/Backend/Worker/Manager.pm b/lib/App/Netdisco/Backend/Worker/Manager.pm index 58b5bb76..f6763f1e 100644 --- a/lib/App/Netdisco/Backend/Worker/Manager.pm +++ b/lib/App/Netdisco/Backend/Worker/Manager.pm @@ -8,7 +8,8 @@ use App::Netdisco::Util::Backend; use Role::Tiny; use namespace::clean; -use App::Netdisco::JobQueue qw/jq_locked jq_getsome jq_getsomep jq_lock/; +use App::Netdisco::JobQueue + qw/jq_locked jq_getsome jq_getsomep jq_lock jq_prime_skiplist/; sub worker_begin { my $self = shift; @@ -19,6 +20,9 @@ sub worker_begin { debug "entering Manager ($wid) worker_begin()"; + # rebuild device skip hints + jq_prime_skiplist; + # requeue jobs locally debug "mgr ($wid): searching for jobs booked to this processing node"; my @jobs = jq_locked; diff --git a/lib/App/Netdisco/Backend/Worker/Poller/Arpnip.pm b/lib/App/Netdisco/Backend/Worker/Poller/Arpnip.pm index e132eb33..fb2ce593 100644 --- a/lib/App/Netdisco/Backend/Worker/Poller/Arpnip.pm +++ b/lib/App/Netdisco/Backend/Worker/Poller/Arpnip.pm @@ -1,7 +1,7 @@ package App::Netdisco::Backend::Worker::Poller::Arpnip; use App::Netdisco::Core::Arpnip 'do_arpnip'; -use App::Netdisco::Util::Device 'is_arpnipable'; +use App::Netdisco::Util::Device 'is_arpnipable_now'; use Role::Tiny; use namespace::clean; @@ -9,7 +9,7 @@ use namespace::clean; with 'App::Netdisco::Backend::Worker::Poller::Common'; sub arpnip_action { \&do_arpnip } -sub arpnip_filter { \&is_arpnipable } +sub arpnip_filter { \&is_arpnipable_now } sub arpnip_layer { 3 } sub arpwalk { (shift)->_walk_body('arpnip', @_) } diff --git a/lib/App/Netdisco/Backend/Worker/Poller/Common.pm b/lib/App/Netdisco/Backend/Worker/Poller/Common.pm index 8c08dea2..96e66f94 100644 --- a/lib/App/Netdisco/Backend/Worker/Poller/Common.pm +++ b/lib/App/Netdisco/Backend/Worker/Poller/Common.pm @@ -64,7 +64,7 @@ sub _single_body { my $snmp = snmp_connect($device); if (!defined $snmp) { - return job_error("$job_type failed: could not SNMP connect to $host"); + return job_defer("$job_type failed: could not SNMP connect to $host"); } unless ($snmp->has_layer( $job_layer )) { diff --git a/lib/App/Netdisco/Backend/Worker/Poller/Device.pm b/lib/App/Netdisco/Backend/Worker/Poller/Device.pm index 52f3d7c3..097733d3 100644 --- a/lib/App/Netdisco/Backend/Worker/Poller/Device.pm +++ b/lib/App/Netdisco/Backend/Worker/Poller/Device.pm @@ -3,7 +3,7 @@ package App::Netdisco::Backend::Worker::Poller::Device; use Dancer qw/:moose :syntax :script/; use App::Netdisco::Util::SNMP 'snmp_connect'; -use App::Netdisco::Util::Device qw/get_device is_discoverable/; +use App::Netdisco::Util::Device qw/get_device is_discoverable_now/; use App::Netdisco::Core::Discover ':all'; use App::Netdisco::Backend::Util ':all'; use App::Netdisco::JobQueue qw/jq_queued jq_insert/; @@ -54,13 +54,13 @@ sub discover { return job_done("discover skipped: $host is pseudo-device"); } - unless (is_discoverable($device->ip)) { + unless (is_discoverable_now($device)) { return job_defer("discover deferred: $host is not discoverable"); } my $snmp = snmp_connect($device); if (!defined $snmp) { - return job_error("discover failed: could not SNMP connect to $host"); + return job_defer("discover failed: could not SNMP connect to $host"); } store_device($device, $snmp); diff --git a/lib/App/Netdisco/Backend/Worker/Poller/Macsuck.pm b/lib/App/Netdisco/Backend/Worker/Poller/Macsuck.pm index 373ceaa3..bb927a31 100644 --- a/lib/App/Netdisco/Backend/Worker/Poller/Macsuck.pm +++ b/lib/App/Netdisco/Backend/Worker/Poller/Macsuck.pm @@ -1,7 +1,7 @@ package App::Netdisco::Backend::Worker::Poller::Macsuck; use App::Netdisco::Core::Macsuck 'do_macsuck'; -use App::Netdisco::Util::Device 'is_macsuckable'; +use App::Netdisco::Util::Device 'is_macsuckable_now'; use Role::Tiny; use namespace::clean; @@ -9,7 +9,7 @@ use namespace::clean; with 'App::Netdisco::Backend::Worker::Poller::Common'; sub macsuck_action { \&do_macsuck } -sub macsuck_filter { \&is_macsuckable } +sub macsuck_filter { \&is_macsuckable_now } sub macsuck_layer { 2 } sub macwalk { (shift)->_walk_body('macsuck', @_) } diff --git a/lib/App/Netdisco/Backend/Worker/Poller/Nbtstat.pm b/lib/App/Netdisco/Backend/Worker/Poller/Nbtstat.pm index e89a05c4..72c7c9ef 100644 --- a/lib/App/Netdisco/Backend/Worker/Poller/Nbtstat.pm +++ b/lib/App/Netdisco/Backend/Worker/Poller/Nbtstat.pm @@ -5,7 +5,7 @@ use Dancer::Plugin::DBIC 'schema'; use App::Netdisco::Core::Nbtstat qw/nbtstat_resolve_async store_nbt/; use App::Netdisco::Util::Node 'is_nbtstatable'; -use App::Netdisco::Util::Device qw/get_device is_discoverable/; +use App::Netdisco::Util::Device qw/get_device is_macsuckable/; use App::Netdisco::Backend::Util ':all'; use NetAddr::IP::Lite ':lower'; @@ -29,8 +29,8 @@ sub nbtstat { or job_error("nbtstat failed: unable to interpret device parameter"); my $host = $device->ip; - unless (is_discoverable($device->ip)) { - return job_defer("nbtstat deferred: $host is not discoverable"); + unless (is_macsuckable($device)) { + return job_defer("nbtstat deferred: $host is not macsuckable"); } # get list of nodes on device diff --git a/lib/App/Netdisco/DB.pm b/lib/App/Netdisco/DB.pm index 72898442..a6272a1e 100644 --- a/lib/App/Netdisco/DB.pm +++ b/lib/App/Netdisco/DB.pm @@ -11,7 +11,7 @@ __PACKAGE__->load_namespaces( ); our # try to hide from kwalitee - $VERSION = 41; # schema version used for upgrades, keep as integer + $VERSION = 42; # schema version used for upgrades, keep as integer use Path::Class; use File::ShareDir 'dist_dir'; diff --git a/lib/App/Netdisco/DB/Result/Admin.pm b/lib/App/Netdisco/DB/Result/Admin.pm index 439046fe..85f75dec 100644 --- a/lib/App/Netdisco/DB/Result/Admin.pm +++ b/lib/App/Netdisco/DB/Result/Admin.pm @@ -56,6 +56,36 @@ __PACKAGE__->set_primary_key("job"); # You can replace this text with custom code or comments, and it will be preserved on regeneration +=head1 RELATIONSHIPS + +=head2 device_skips( $backend?, $max_deferrals? ) + +Retuns the set of C entries which apply to this job. They match +the device IP, current backend, and job action. + +You probably want to use the ResultSet method C which completes this +query with a C host and C parameters (or sensible +defaults). + +=cut + +__PACKAGE__->has_many( device_skips => 'App::Netdisco::DB::Result::DeviceSkip', + sub { + my $args = shift; + return { + "$args->{foreign_alias}.backend" => { '=' => \'?' }, + "$args->{foreign_alias}.device" + => { -ident => "$args->{self_alias}.device" }, + -or => [ + { "$args->{foreign_alias}.actionset" + => { '@>' => \"string_to_array($args->{self_alias}.action,'')" } }, + { "$args->{foreign_alias}.deferrals" => { '>=' => \'?' } }, + ], + }; + }, + { cascade_copy => 0, cascade_update => 0, cascade_delete => 0 } +); + =head1 METHODS =head2 summary diff --git a/lib/App/Netdisco/DB/Result/DeviceSkip.pm b/lib/App/Netdisco/DB/Result/DeviceSkip.pm new file mode 100644 index 00000000..bed6f17b --- /dev/null +++ b/lib/App/Netdisco/DB/Result/DeviceSkip.pm @@ -0,0 +1,55 @@ +use utf8; +package App::Netdisco::DB::Result::DeviceSkip; + +use strict; +use warnings; + +use List::MoreUtils (); + +use base 'DBIx::Class::Core'; +__PACKAGE__->table("device_skip"); +__PACKAGE__->add_columns( + "backend", + { data_type => "text", is_nullable => 0 }, + "device", + { data_type => "inet", is_nullable => 0 }, + "actionset", + { data_type => "text[]", is_nullable => 0, default_value => '{}' }, + "deferrals", + { data_type => "integer", is_nullable => 1, default_value => '0' }, +); + +__PACKAGE__->set_primary_key("backend", "device"); + +__PACKAGE__->add_unique_constraint( + device_skip_pkey => [qw/backend device/]); + +=head1 METHODS + +=head2 increment_deferrals + +Increments the C field in the row, only if the row is in storage. +There is a race in the update, but this is not worrying for now. + +=cut + +sub increment_deferrals { + my $row = shift; + return unless $row->in_storage; + return $row->update({ deferrals => (($row->deferrals || 0) + 1) }); +} + +=head2 add_to_actionset + +=cut + +sub add_to_actionset { + my ($row, @badactions) = @_; + return unless $row->in_storage; + return unless scalar @badactions; + return $row->update({ actionset => + [ sort (List::MoreUtils::uniq( @{ $row->actionset || [] }, @badactions )) ] + }); +} + +1; diff --git a/lib/App/Netdisco/DB/ResultSet.pm b/lib/App/Netdisco/DB/ResultSet.pm index 7fbf8c56..953c8e80 100644 --- a/lib/App/Netdisco/DB/ResultSet.pm +++ b/lib/App/Netdisco/DB/ResultSet.pm @@ -5,8 +5,11 @@ use warnings; use base 'DBIx::Class::ResultSet'; -__PACKAGE__->load_components( - qw{Helper::ResultSet::SetOperations Helper::ResultSet::Shortcut}); +__PACKAGE__->load_components(qw/ + Helper::ResultSet::SetOperations + Helper::ResultSet::Shortcut + Helper::ResultSet::CorrelateRelationship +/); =head1 ADDITIONAL METHODS diff --git a/lib/App/Netdisco/DB/ResultSet/Admin.pm b/lib/App/Netdisco/DB/ResultSet/Admin.pm index 1d7d8bec..9b1ec4b7 100644 --- a/lib/App/Netdisco/DB/ResultSet/Admin.pm +++ b/lib/App/Netdisco/DB/ResultSet/Admin.pm @@ -4,12 +4,34 @@ use base 'App::Netdisco::DB::ResultSet'; use strict; use warnings; +use Net::Domain 'hostfqdn'; + __PACKAGE__->load_components(qw/ +App::Netdisco::DB::ExplicitLocking /); =head1 ADDITIONAL METHODS +=head2 skipped + +Retuns a correlated subquery for the set of C entries that apply +to some jobs. They match the device IP, current backend, and job action. + +Pass the C FQDN (or the current host will be used as a default), and +the C (or 10 will be used as the default). + +=cut + +sub skipped { + my ($rs, $backend, $max_deferrals) = @_; + $backend ||= (hostfqdn || 'localhost'); + $max_deferrals ||= 10; + + return $rs->correlate('device_skips')->search(undef, { + bind => [[deferrals => $max_deferrals], [backend => $backend]], + }); +} + =head2 with_times This is a modifier for any C (including the helpers below) which diff --git a/lib/App/Netdisco/DB/ResultSet/Device.pm b/lib/App/Netdisco/DB/ResultSet/Device.pm index a3b6a4ac..2e8820f0 100644 --- a/lib/App/Netdisco/DB/ResultSet/Device.pm +++ b/lib/App/Netdisco/DB/ResultSet/Device.pm @@ -596,9 +596,14 @@ sub delete { )->delete; } - $schema->resultset('Admin')->search({ - device => { '-in' => $devices->as_query }, - })->delete; + foreach my $set (qw/ + Admin + DeviceSkip + /) { + $schema->resultset($set)->search( + { device => { '-in' => $devices->as_query } }, + )->delete; + } $schema->resultset('Topology')->search({ -or => [ diff --git a/lib/App/Netdisco/JobQueue.pm b/lib/App/Netdisco/JobQueue.pm index fd93e726..1c5a059e 100644 --- a/lib/App/Netdisco/JobQueue.pm +++ b/lib/App/Netdisco/JobQueue.pm @@ -13,6 +13,7 @@ our @EXPORT_OK = qw/ jq_getsomep jq_locked jq_queued + jq_prime_skiplist jq_log jq_userlog jq_lock @@ -57,6 +58,12 @@ Netdisco job instance interface (see below). Returns a list of IP addresses of devices which currently have a job of the given C<$job_type> queued (e.g. C, C, etc). +=head2 jq_prime_skiplist() + +Sets up a table of hints for the backend daemon manager to help avoid picking +jobs from the queue that it cannot process due to C<*_no> configuration +settings. + =head2 jq_log() Returns a list of the most recent 50 jobs in the queue. Jobs are returned as diff --git a/lib/App/Netdisco/JobQueue/PostgreSQL.pm b/lib/App/Netdisco/JobQueue/PostgreSQL.pm index efcc15a1..0b9605b3 100644 --- a/lib/App/Netdisco/JobQueue/PostgreSQL.pm +++ b/lib/App/Netdisco/JobQueue/PostgreSQL.pm @@ -3,7 +3,11 @@ package App::Netdisco::JobQueue::PostgreSQL; use Dancer qw/:moose :syntax :script/; use Dancer::Plugin::DBIC 'schema'; +use App::Netdisco::Util::Device + qw/is_discoverable is_macsuckable is_arpnipable/; use App::Netdisco::Backend::Job; + + use Net::Domain 'hostfqdn'; use Module::Load (); use Try::Tiny; @@ -15,26 +19,35 @@ our @EXPORT_OK = qw/ jq_getsomep jq_locked jq_queued - jq_log - jq_userlog + jq_prime_skiplist jq_lock jq_defer jq_complete + jq_log + jq_userlog jq_insert jq_delete /; our %EXPORT_TAGS = ( all => \@EXPORT_OK ); +# this can take a few seconds - only do it once +our $fqdn = undef; + sub _getsome { my ($num_slots, $where) = @_; return () if ((!defined $num_slots) or ($num_slots < 1)); return () if ((!defined $where) or (ref {} ne ref $where)); - my $rs = schema('netdisco')->resultset('Admin') - ->search( - { status => 'queued', %$where }, - { order_by => 'random()', rows => $num_slots }, - ); + my $fqdn ||= (hostfqdn || 'localhost'); + my $jobs = schema('netdisco')->resultset('Admin'); + + my $rs = $jobs->search({ + status => 'queued', + device => { '-not_in' => + $jobs->skipped($fqdn, setting('workers')->{'max_deferrals'}) + ->columns('device')->as_query }, + %$where, + }, { order_by => 'random()', rows => $num_slots }); my @returned = (); while (my $job = $rs->next) { @@ -61,7 +74,7 @@ sub jq_getsomep { } sub jq_locked { - my $fqdn = hostfqdn || 'localhost'; + my $fqdn ||= (hostfqdn || 'localhost'); my @returned = (); my $rs = schema('netdisco')->resultset('Admin') @@ -83,26 +96,65 @@ sub jq_queued { })->get_column('device')->all; } -sub jq_log { - return schema('netdisco')->resultset('Admin')->search({}, { - order_by => { -desc => [qw/entered device action/] }, - rows => 50, - })->with_times->hri->all; +# given a device, tests if any of the primary acls applies +# returns a list of job actions to be denied/skipped on this host. +sub _get_denied_actions { + my $device = shift; + my @badactions = (); + + push @badactions, ('discover', @{ setting('job_prio')->{high} }) + if not is_discoverable($device); + + push @badactions, (qw/macsuck nbtstat/) + if not is_macsuckable($device); + + push @badactions, 'arpnip' + if not is_arpnipable($device); + + return @badactions; } -sub jq_userlog { - my $user = shift; - return schema('netdisco')->resultset('Admin')->search({ - username => $user, - finished => { '>' => \"(now() - interval '5 seconds')" }, - })->with_times->all; +sub jq_prime_skiplist { + my $fqdn ||= (hostfqdn || 'localhost'); + my @devices = schema('netdisco')->resultset('Device')->all; + my $rs = schema('netdisco')->resultset('DeviceSkip'); + my %actionset = (); + + foreach my $d (@devices) { + my @badactions = _get_denied_actions($d); + $actionset{$d->ip} = \@badactions if scalar @badactions; + } + + schema('netdisco')->txn_do(sub { + $rs->search({ backend => $fqdn })->delete; + $rs->populate([ + map {{ + backend => $fqdn, + device => $_, + actionset => $actionset{$_}, + }} keys %actionset + ]); + }); } sub jq_lock { my $job = shift; - my $fqdn = hostfqdn || 'localhost'; + my $fqdn ||= (hostfqdn || 'localhost'); my $happy = false; + # need to handle device discovered since backend daemon started + # and the skiplist was primed. these should be checked against + # the various acls and have device_skip entry added if needed, + # and return false if it should have been skipped. + my @badactions = _get_denied_actions($job->device); + if (scalar @badactions) { + schema('netdisco')->resultset('DeviceSkip')->find_or_create({ + backend => $fqdn, device => $job->device, + },{ key => 'device_skip_pkey' })->add_to_actionset(@badactions); + + return false if scalar grep {$_ eq $job->action} @badactions; + } + # lock db row and update to show job has been picked try { schema('netdisco')->txn_do(sub { @@ -136,11 +188,23 @@ sub jq_lock { sub jq_defer { my $job = shift; + my $fqdn ||= (hostfqdn || 'localhost'); my $happy = false; + # note this taints all actions on the device. for example if both + # macsuck and arpnip are allowed, but macsuck fails 10 times, then + # arpnip (and every other action) will be prevented on the device. + + # seeing as defer is only triggered by an SNMP connect failure, this + # behaviour seems reasonable, to me (or desirable, perhaps). + try { - # lock db row and update to show job is available schema('netdisco')->txn_do(sub { + schema('netdisco')->resultset('DeviceSkip')->find_or_create({ + backend => $fqdn, device => $job->device, + },{ key => 'device_skip_pkey' })->increment_deferrals; + + # lock db row and update to show job is available schema('netdisco')->resultset('Admin') ->find($job->job, {for => 'update'}) ->update({ status => 'queued', started => undef }); @@ -178,6 +242,21 @@ sub jq_complete { return $happy; } +sub jq_log { + return schema('netdisco')->resultset('Admin')->search({}, { + order_by => { -desc => [qw/entered device action/] }, + rows => 50, + })->with_times->hri->all; +} + +sub jq_userlog { + my $user = shift; + return schema('netdisco')->resultset('Admin')->search({ + username => $user, + finished => { '>' => \"(now() - interval '5 seconds')" }, + })->with_times->all; +} + sub jq_insert { my $jobs = shift; $jobs = [$jobs] if ref [] ne ref $jobs; diff --git a/lib/App/Netdisco/Util/Device.pm b/lib/App/Netdisco/Util/Device.pm index 55936eaa..ac06c9fc 100644 --- a/lib/App/Netdisco/Util/Device.pm +++ b/lib/App/Netdisco/Util/Device.pm @@ -11,9 +11,9 @@ our @EXPORT_OK = qw/ delete_device renumber_device match_devicetype - is_discoverable - is_arpnipable - is_macsuckable + is_discoverable is_discoverable_now + is_arpnipable is_arpnipable_now + is_macsuckable is_macsuckable_now /; our %EXPORT_TAGS = (all => \@EXPORT_OK); @@ -144,6 +144,8 @@ sub match_devicetype { @{setting($setting_name) || []}); } +sub _bail_msg { debug $_[0]; return 0; } + =head2 is_discoverable( $ip, $device_type? ) Given an IP address, returns C if Netdisco on this host is permitted by @@ -159,8 +161,6 @@ Returns false if the host is not permitted to discover the target device. =cut -sub _bail_msg { debug $_[0]; return 0; } - sub is_discoverable { my ($ip, $remote_type) = @_; my $device = get_device($ip) or return 0; @@ -175,16 +175,32 @@ sub is_discoverable { return _bail_msg("is_discoverable: device failed to match discover_only") unless check_acl_only($device, 'discover_only'); - # cannot check last_discover for as yet undiscovered devices :-) - return 1 if not $device->in_storage; + return 1; +} - if ($device->since_last_discover and setting('discover_min_age') - and $device->since_last_discover < setting('discover_min_age')) { +=head2 is_discoverable_now( $ip, $device_type? ) - return _bail_msg("is_discoverable: time since last discover less than discover_min_age"); +Same as C, but also checks the last_discover field if the +device is in storage, and returns false if that host has been too recently +discovered. + +Returns false if the host is not permitted to discover the target device. + +=cut + +sub is_discoverable_now { + my ($ip, $remote_type) = @_; + my $device = get_device($ip) or return 0; + + if ($device->in_storage) { + if ($device->since_last_discover and setting('discover_min_age') + and $device->since_last_discover < setting('discover_min_age')) { + + return _bail_msg("is_discoverable: time since last discover less than discover_min_age"); + } } - return 1; + return is_discoverable(@_); } =head2 is_arpnipable( $ip ) @@ -209,6 +225,23 @@ sub is_arpnipable { return _bail_msg("is_arpnipable: device failed to match arpnip_only") unless check_acl_only($device, 'arpnip_only'); + return 1; +} + +=head2 is_arpnipable_now( $ip ) + +Same as C, but also checks the last_arpnip field if the +device is in storage, and returns false if that host has been too recently +arpnipped. + +Returns false if the host is not permitted to arpnip the target device. + +=cut + +sub is_arpnipable_now { + my $ip = shift; + my $device = get_device($ip) or return 0; + return _bail_msg("is_arpnipable: cannot arpnip an undiscovered device") if not $device->in_storage; @@ -218,7 +251,7 @@ sub is_arpnipable { return _bail_msg("is_arpnipable: time since last arpnip less than arpnip_min_age"); } - return 1; + return is_arpnipable(@_); } =head2 is_macsuckable( $ip ) @@ -243,6 +276,23 @@ sub is_macsuckable { return _bail_msg("is_macsuckable: device failed to match macsuck_only") unless check_acl_only($device, 'macsuck_only'); + return 1; +} + +=head2 is_macsuckable_now( $ip ) + +Same as C, but also checks the last_macsuck field if the +device is in storage, and returns false if that host has been too recently +macsucked. + +Returns false if the host is not permitted to macsuck the target device. + +=cut + +sub is_macsuckable_now { + my $ip = shift; + my $device = get_device($ip) or return 0; + return _bail_msg("is_macsuckable: cannot macsuck an undiscovered device") if not $device->in_storage; @@ -252,7 +302,7 @@ sub is_macsuckable { return _bail_msg("is_macsuckable: time since last macsuck less than macsuck_min_age"); } - return 1; + return is_macsuckable(@_); } 1; diff --git a/lib/App/Netdisco/Web/Plugin/AdminTask/TimedOutDevices.pm b/lib/App/Netdisco/Web/Plugin/AdminTask/TimedOutDevices.pm new file mode 100644 index 00000000..f8184dd2 --- /dev/null +++ b/lib/App/Netdisco/Web/Plugin/AdminTask/TimedOutDevices.pm @@ -0,0 +1,28 @@ +package App::Netdisco::Web::Plugin::AdminTask::TimedOutDevices; + +use Dancer ':syntax'; +use Dancer::Plugin::Ajax; +use Dancer::Plugin::DBIC; +use Dancer::Plugin::Auth::Extensible; + +use App::Netdisco::Web::Plugin; + +register_admin_task({ + tag => 'timedoutdevices', + label => 'SNMP Connect Failures', +}); + +ajax '/ajax/content/admin/timedoutdevices' => require_role admin => sub { + my @results = schema('netdisco')->resultset('DeviceSkip')->search({ + deferrals => { '>' => 0 } + },{ order_by => + [{ -desc => 'deferrals' }, { -asc => [qw/device backend/] }] + })->hri->all; + + content_type('text/html'); + template 'ajax/admintask/timedoutdevices.tt', { + results => \@results, + }, { layout => undef }; +}; + +true; diff --git a/share/config.yml b/share/config.yml index 017a02b6..70704018 100644 --- a/share/config.yml +++ b/share/config.yml @@ -71,6 +71,7 @@ web_plugins: - AdminTask::SlowDevices - AdminTask::UndiscoveredNeighbors - AdminTask::OrphanedDevices + - AdminTask::TimedOutDevices - AdminTask::UserLog - AdminTask::Users - Search::Device @@ -205,6 +206,7 @@ workers: tasks: 'AUTO * 2' sleep_time: 1 min_runtime: 0 + max_deferrals: 10 queue: PostgreSQL dns: diff --git a/share/schema_versions/App-Netdisco-DB-41-42-PostgreSQL.sql b/share/schema_versions/App-Netdisco-DB-41-42-PostgreSQL.sql new file mode 100644 index 00000000..0b64289b --- /dev/null +++ b/share/schema_versions/App-Netdisco-DB-41-42-PostgreSQL.sql @@ -0,0 +1,11 @@ +BEGIN; + +CREATE TABLE "device_skip" ( + "backend" text NOT NULL, + "device" inet NOT NULL, + "actionset" text[] DEFAULT '{}', + "deferrals" integer DEFAULT 0, + PRIMARY KEY ("backend", "device") +); + +COMMIT; diff --git a/share/views/ajax/admintask/timedoutdevices.tt b/share/views/ajax/admintask/timedoutdevices.tt new file mode 100644 index 00000000..f7ac0a23 --- /dev/null +++ b/share/views/ajax/admintask/timedoutdevices.tt @@ -0,0 +1,31 @@ +[% IF NOT results.size %] +
No significant events to report.
+[% ELSE %] + + + + + + + + + + [% FOREACH row IN results %] + + + + + + [% END %] + +
Poller HostDeviceFailed Connections
[% row.backend | html_entity %][% row.device | html_entity %][% row.deferrals | html_entity %]
+[% END %] + +