rename Core to Worker and move other packages around

This commit is contained in:
Oliver Gorwits
2017-09-03 18:45:00 +01:00
parent 4def0af0b0
commit 8eaa33770c
11 changed files with 71 additions and 67 deletions

View File

@@ -1,146 +0,0 @@
package App::Netdisco::Core::Plugin;
use Dancer ':syntax';
use Dancer::Plugin;
use Dancer::Factory::Hook;
use App::Netdisco::Util::Permission qw/check_acl_no check_acl_only/;
use Scope::Guard;
use Try::Tiny;
# track the phases seen so we can recall them in order
set( '_nd2core_hooks' => [] );
register 'register_core_worker' => sub {
my ($self, $workerconf, $code) = @_;
return error "bad param to register_core_worker"
unless ((ref sub {} eq ref $code) and (ref {} eq ref $workerconf));
# needs to be here for caller() context
my ($package, $action, $phase) = ((caller)[0], undef, undef);
if ($package =~ m/::(Discover|Arpnip|Macsuck|Expire|Nbtstat)$/) {
$action = lc $1;
}
if ($package =~ m/::(Discover|Arpnip|Macsuck|Expire|Nbtstat)::(\w+)/) {
$action = lc $1; $phase = lc $2;
}
else { return error "worker Package does not match standard naming" }
$workerconf->{action} = $action;
$workerconf->{phase} = ($phase || '00init');
$workerconf->{primary} = $workerconf->{primary};
my $worker = sub {
my $job = shift or return false;
my $no = (exists $workerconf->{no} ? $workerconf->{no} : undef);
my $only = (exists $workerconf->{only} ? $workerconf->{only} : undef);
my @newuserconf = ();
my @userconf = @{ setting('device_auth') || [] };
# reduce device_auth by driver, worker's only/no
foreach my $stanza (@userconf) {
if (ref $job->device) {
next if $no and check_acl_no($job->device->ip, $no);
next if $only and not check_acl_only($job->device->ip, $only);
}
next if exists $stanza->{driver} and exists $workerconf->{driver}
and (($stanza->{driver} || '') ne ($workerconf->{driver} || ''));
push @newuserconf, $stanza;
}
# back up and restore device_auth
return false unless scalar @newuserconf;
my $guard = guard { set(device_auth => \@userconf) };
set(device_auth => \@newuserconf);
# run worker
my $happy = false;
try {
$code->($job, $workerconf);
$happy = true;
}
catch { debug $_ };
return $happy;
};
my $primary = ($workerconf->{primary} ? '_primary' : '');
my $hook = 'nd2core_'. $action .'_'. $phase . $primary;
if (not Dancer::Factory::Hook->instance->hook_is_registered($hook)) {
Dancer::Factory::Hook->instance->install_hooks($hook);
# track just the basic phase names which are used
push @{ setting('_nd2core_hooks') }, $hook
if $phase ne '00init' and 0 == length($primary);
}
Dancer::Factory::Hook->instance->register_hook($hook, $worker);
};
register_plugin;
true;
=head1 NAME
App::Netdisco::Core::Plugin - Netdisco Core Workers
=head1 Introduction
L<App::Netdisco>'s plugin system allows users to write I<workers> to gather
information from network devices using different I<transports> and store
results in the database.
For example, transports might be SNMP, SSH, or HTTPS. Workers might be
combining those transports with application protocols such as SNMP, NETCONF
(OpenConfig with XML), RESTCONF (OpenConfig with JSON), eAPI, or even CLI
scraping. The combination of transport and protocol is known as a I<driver>.
Workers can be restricted to certain vendor platforms using familiar ACL
syntax. They are also attached to specific phases in Netdisco's backend
operation (discover, macsuck, etc).
=head1 Application Configuration
The C<core_plugins> and C<extra_core_plugins> settings list in YAML format the
set of Perl module names which are the plugins to be loaded.
Any change should go into your local C<deployment.yml> configuration file. If
you want to view the default settings, see the C<share/config.yml> file in the
C<App::Netdisco> distribution.
=head1 How to Configure
The C<extra_core_plugins> setting is empty, and used only if you want to add
new plugins but not change the set enabled by default. If you do want to add
to or remove from the default set, then create a version of C<core_plugins>
instead.
Netdisco prepends "C<App::Netdisco::Core::Plugin::>" to any entry in the list.
For example, "C<Discover::Wireless::UniFi>" will load the
C<App::Netdisco::Core::Plugin::Discover::Wireless::UniFi> package.
You can prepend module names with "C<X::>" as shorthand for the "Netdisco
extension" namespace. For example, "C<X::Macsuck::WirelessNodes::UniFi>" will
load the L<App::NetdiscoX::Core::Plugin::Macsuck::WirelessNodes::UniFi>
module.
If an entry in the list starts with a "C<+>" (plus) sign then Netdisco attemps
to load the module as-is, without prepending anything to the name. This allows
you to have App::Netdiso Core plugins in other namespaces.
Plugin modules can either ship with the App::Netdisco distribution itself, or
be installed separately. Perl uses the standard C<@INC> path searching
mechanism to load the plugin modules. See the C<include_paths> and
C<site_local_files> settings in order to modify C<@INC> for loading local
plugins. As an example, if your plugin is called
"App::NetdiscoX::Core::Plugin::MyPluginName" then it could live at:
~netdisco/nd-site-local/lib/App/NetdiscoX/Core/Plugin/MyPluginName.pm
The order of the entries is significant, workers being executed in the order
which they appear in C<core_plugins> and C<extra_core_plugins> (although see
L<App::Netdisco::Manual::WritingCoreWorkers> for caveats).
=cut

View File

@@ -1,282 +0,0 @@
package App::Netdisco::Core::Transport::SNMP;
use Dancer qw/:syntax :script/;
use App::Netdisco::Util::SNMP 'build_communities';
use App::Netdisco::Util::Device 'get_device';
use App::Netdisco::Util::Permission ':all';
use SNMP::Info;
use Try::Tiny;
use Module::Load ();
use Path::Class 'dir';
use base 'Dancer::Object::Singleton';
=head1 NAME
App::Netdisco::Core::Transport::SNMP
=head1 DESCRIPTION
Singleton for SNMP connections. Returns cached L<SNMP::Info> instance for a
given device IP, or else undef. All methods are class methods, for example:
App::Netdisco::Core::Transport::SNMP->reader_for( ... );
=cut
__PACKAGE__->attributes(qw/ readers writers /);
sub init {
my ( $class, $self ) = @_;
$self->readers( {} );
$self->writers( {} );
return $self;
}
=head1 reader_for( $ip, $useclass? )
Given an IP address, returns an L<SNMP::Info> instance configured for and
connected to that device. The IP can be any on the device, and the management
interface will be connected to.
If the device is known to Netdisco and there is a cached SNMP community
string, that community will be tried first, and then other community strings
from the application configuration will be tried.
If C<$useclass> is provided, it will be used as the L<SNMP::Info> device
class instead of the class in the Netdisco database.
Returns C<undef> if the connection fails.
=cut
sub reader_for {
my ($class, $ip, $useclass) = @_;
my $device = get_device($ip) or return undef;
my $readers = $class->instance->readers or return undef;
return $readers->{$device->ip} if exists $readers->{$device->ip};
debug sprintf 'snmp reader cache warm: [%s]', $device->ip;
return ($readers->{$device->ip}
= _snmp_connect_generic('read', $device, $useclass));
}
=head1 writer_for( $ip, $useclass? )
Same as C<reader_for> but uses the read-write community strings from the
application configuration file.
Returns C<undef> if the connection fails.
=cut
sub writer_for {
my ($class, $ip, $useclass) = @_;
my $device = get_device($ip) or return undef;
my $writers = $class->instance->writers or return undef;
return $writers->{$device->ip} if exists $writers->{$device->ip};
debug sprintf 'snmp writer cache warm: [%s]', $device->ip;
return ($writers->{$device->ip}
= _snmp_connect_generic('write', $device, $useclass));
}
sub _snmp_connect_generic {
my ($mode, $device, $useclass) = @_;
$mode ||= 'read';
my %snmp_args = (
AutoSpecify => 0,
DestHost => $device->ip,
# 0 is falsy. Using || with snmpretries equal to 0 will set retries to 2.
# check if the setting is 0. If not, use the default value of 2.
Retries => (setting('snmpretries') || setting('snmpretries') == 0 ? 0 : 2),
Timeout => (setting('snmptimeout') || 1000000),
NonIncreasing => (setting('nonincreasing') || 0),
BulkWalk => ((defined setting('bulkwalk_off') && setting('bulkwalk_off'))
? 0 : 1),
BulkRepeaters => (setting('bulkwalk_repeaters') || 20),
MibDirs => [ _build_mibdirs() ],
IgnoreNetSNMPConf => 1,
Debug => ($ENV{INFO_TRACE} || 0),
DebugSNMP => ($ENV{SNMP_TRACE} || 0),
);
# an override for bulkwalk
$snmp_args{BulkWalk} = 0 if check_acl_no($device, 'bulkwalk_no');
# further protect against buggy Net-SNMP, and disable bulkwalk
if ($snmp_args{BulkWalk}
and ($SNMP::VERSION eq '5.0203' || $SNMP::VERSION eq '5.0301')) {
warning sprintf
"[%s] turning off BulkWalk due to buggy Net-SNMP - please upgrade!",
$device->ip;
$snmp_args{BulkWalk} = 0;
}
# get the community string(s)
my @communities = build_communities($device, $mode);
# which SNMP versions to try and in what order
my @versions =
( check_acl_no($device->ip, 'snmpforce_v3') ? (3)
: check_acl_no($device->ip, 'snmpforce_v2') ? (2)
: check_acl_no($device->ip, 'snmpforce_v1') ? (1)
: (reverse (1 .. (setting('snmpver') || 3))) );
# use existing or new device class
my @classes = ($useclass || 'SNMP::Info');
if ($device->snmp_class and not $useclass) {
unshift @classes, $device->snmp_class;
}
my $info = undef;
COMMUNITY: foreach my $comm (@communities) {
next unless $comm;
VERSION: foreach my $ver (@versions) {
next unless $ver;
next if $ver eq 3 and exists $comm->{community};
next if $ver ne 3 and !exists $comm->{community};
CLASS: foreach my $class (@classes) {
next unless $class;
my %local_args = (%snmp_args, Version => $ver);
$info = _try_connect($device, $class, $comm, $mode, \%local_args,
($useclass ? 0 : 1) );
last COMMUNITY if $info;
}
}
}
return $info;
}
sub _try_connect {
my ($device, $class, $comm, $mode, $snmp_args, $reclass) = @_;
my %comm_args = _mk_info_commargs($comm);
my $debug_comm = '<hidden>';
if ($ENV{SHOW_COMMUNITY}) {
$debug_comm = ($comm->{community} ||
(sprintf 'v3:%s:%s/%s', ($comm->{user},
($comm->{auth}->{proto} || 'noAuth'),
($comm->{priv}->{proto} || 'noPriv'))) );
}
my $info = undef;
try {
debug
sprintf '[%s] try_connect with ver: %s, class: %s, comm: %s',
$snmp_args->{DestHost}, $snmp_args->{Version}, $class, $debug_comm;
Module::Load::load $class;
$info = $class->new(%$snmp_args, %comm_args) or return;
$info = ($mode eq 'read' ? _try_read($info, $device, $comm)
: _try_write($info, $device, $comm));
# first time a device is discovered, re-instantiate into specific class
if ($reclass and $info and $info->device_type ne $class) {
$class = $info->device_type;
debug
sprintf '[%s] try_connect with ver: %s, new class: %s, comm: %s',
$snmp_args->{DestHost}, $snmp_args->{Version}, $class, $debug_comm;
Module::Load::load $class;
$info = $class->new(%$snmp_args, %comm_args);
}
}
catch {
debug $_;
};
return $info;
}
sub _try_read {
my ($info, $device, $comm) = @_;
return undef unless (
(not defined $info->error)
and defined $info->uptime
and ($info->layers or $info->description)
and $info->class
);
$device->in_storage
? $device->update({snmp_ver => $info->snmp_ver})
: $device->set_column(snmp_ver => $info->snmp_ver);
if ($comm->{community}) {
$device->in_storage
? $device->update({snmp_comm => $comm->{community}})
: $device->set_column(snmp_comm => $comm->{community});
}
# regardless of device in storage, save the hint
$device->update_or_create_related('community',
{snmp_auth_tag_read => $comm->{tag}}) if $comm->{tag};
return $info;
}
sub _try_write {
my ($info, $device, $comm) = @_;
my $loc = $info->load_location;
$info->set_location($loc) or return undef;
return undef unless ($loc eq $info->load_location);
$device->in_storage
? $device->update({snmp_ver => $info->snmp_ver})
: $device->set_column(snmp_ver => $info->snmp_ver);
# one of these two cols must be set
$device->update_or_create_related('community', {
($comm->{tag} ? (snmp_auth_tag_write => $comm->{tag}) : ()),
($comm->{community} ? (snmp_comm_rw => $comm->{community}) : ()),
});
return $info;
}
sub _mk_info_commargs {
my $comm = shift;
return () unless ref {} eq ref $comm and scalar keys %$comm;
return (Community => $comm->{community})
if exists $comm->{community};
my $seclevel =
(exists $comm->{auth} ?
(exists $comm->{priv} ? 'authPriv' : 'authNoPriv' )
: 'noAuthNoPriv');
return (
SecName => $comm->{user},
SecLevel => $seclevel,
( exists $comm->{auth} ? (
AuthProto => uc ($comm->{auth}->{proto} || 'MD5'),
AuthPass => ($comm->{auth}->{pass} || ''),
( exists $comm->{priv} ? (
PrivProto => uc ($comm->{priv}->{proto} || 'DES'),
PrivPass => ($comm->{priv}->{pass} || ''),
) : ()),
) : ()),
);
}
sub _build_mibdirs {
my $home = (setting('mibhome') || dir(($ENV{NETDISCO_HOME} || $ENV{HOME}), 'netdisco-mibs'));
return map { dir($home, $_)->stringify }
@{ setting('mibdirs') || _get_mibdirs_content($home) };
}
sub _get_mibdirs_content {
my $home = shift;
my @list = map {s|$home/||; $_} grep {m/[a-z0-9]/} grep {-d} glob("$home/*");
return \@list;
}
true;