Merge branch 'master' into og-api-tokens-simple

This commit is contained in:
Oliver Gorwits
2019-03-11 17:55:31 +00:00
committed by GitHub
65 changed files with 794 additions and 334 deletions

View File

@@ -4,7 +4,7 @@ use strict;
use warnings;
use 5.010_000;
our $VERSION = '2.040002';
our $VERSION = '2.040007';
use App::Netdisco::Configuration;
=head1 NAME
@@ -312,7 +312,7 @@ 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.
Clint Wise, Max Kosmach, Bernhard Augenstein and Nick Nauwelaerts (aquafin.be).
We probably forgot some names - sorry about that :-(.

View File

@@ -262,20 +262,27 @@ sub renumber {
foreach my $set (qw/
DeviceIp
DeviceModule
DevicePower
DeviceVlan
DevicePort
DevicePortLog
DevicePortPower
DevicePortProperties
DevicePortSsid
DevicePortVlan
DevicePortWireless
DevicePower
DeviceVlan
/) {
$schema->resultset($set)
->search({ip => $old_ip})
->update({ip => $new_ip});
}
$schema->resultset('DeviceSkip')
->search({device => $new_ip})->delete;
$schema->resultset('DeviceSkip')
->search({device => $old_ip})
->update({device => $new_ip});
$schema->resultset('DevicePort')
->search({remote_ip => $old_ip})
->update({remote_ip => $new_ip});

View File

@@ -29,6 +29,8 @@ __PACKAGE__->add_columns(
{ data_type => "bigint", is_nullable => 1 },
"faststart",
{ data_type => "boolean", is_nullable => 1 },
"ifindex",
{ data_type => "bigint", is_nullable => 1 },
);
__PACKAGE__->set_primary_key("port", "ip");

View File

@@ -15,32 +15,61 @@ __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, ld.dns AS left_dns, ld.name AS left_name,
array_agg(dp.port) AS left_port, array_agg(dp.name) AS left_descr,
sum( COALESCE(dpp.raw_speed,0) ) as aggspeed,
count(*) AS aggports,
di.ip AS right_ip, rd.dns AS right_dns, rd.name AS right_name,
array_agg(dp.remote_port) AS right_port, array_agg(dp2.name) AS right_descr
WITH BothWays AS
( SELECT dp.ip AS left_ip,
ld.dns AS left_dns,
ld.name AS left_name,
array_agg(dp.port) AS left_port,
array_agg(dp.name) AS left_descr,
FROM device_port dp
LEFT OUTER JOIN device_port_properties dpp USING (ip, port)
INNER JOIN device ld ON dp.ip = ld.ip
INNER JOIN device_ip di ON dp.remote_ip = di.alias
INNER JOIN device rd ON di.ip = rd.ip
LEFT OUTER JOIN device_port dp2
ON (di.ip = dp2.ip
AND ((dp.remote_port = dp2.port)
OR (dp.remote_port = dp2.name)
OR (dp.remote_port = dp2.descr)))
count(dpp.*) AS aggports,
sum(COALESCE(dpp.raw_speed, 0)) AS aggspeed,
WHERE dp.remote_port IS NOT NULL
AND dp.port !~* 'vlan'
AND (dp.descr IS NULL OR dp.descr !~* 'vlan')
AND (dp.type IS NULL OR dp.type !~* '^(53|ieee8023adLag|propVirtual|l2vlan|l3ipvlan|135|136|137)\$')
AND (dp.is_master = 'false' OR dp.slave_of IS NOT NULL)
AND dp.ip <= di.ip
GROUP BY left_ip, left_dns, left_name, right_ip, right_dns, right_name
ORDER BY dp.ip
di.ip AS right_ip,
rd.dns AS right_dns,
rd.name AS right_name,
array_agg(dp.remote_port) AS right_port,
array_agg(dp2.name) AS right_descr
FROM device_port dp
LEFT OUTER JOIN device_port_properties dpp ON (
(dp.ip = dpp.ip) AND (dp.port = dpp.port)
AND (dp.type IS NULL
OR dp.type !~* '^(53|ieee8023adLag|propVirtual|l2vlan|l3ipvlan|135|136|137)\$')
AND (dp.is_master = 'false'
OR dp.slave_of IS NOT NULL) )
INNER JOIN device ld ON dp.ip = ld.ip
INNER JOIN device_ip di ON dp.remote_ip = di.alias
INNER JOIN device rd ON di.ip = rd.ip
LEFT OUTER JOIN device_port dp2 ON (di.ip = dp2.ip
AND ((dp.remote_port = dp2.port)
OR (dp.remote_port = dp2.name)
OR (dp.remote_port = dp2.descr)))
WHERE dp.remote_port IS NOT NULL
AND dp.port !~* 'vlan'
AND (dp.descr IS NULL OR dp.descr !~* 'vlan')
GROUP BY left_ip,
left_dns,
left_name,
right_ip,
right_dns,
right_name )
SELECT *
FROM BothWays b
WHERE NOT EXISTS
( SELECT *
FROM BothWays b2
WHERE b2.right_ip = b.left_ip
AND b2.right_port = b.left_port
AND b2.left_ip < b.left_ip )
ORDER BY 1,
2
ENDSQL
);

View File

@@ -15,6 +15,7 @@ __PACKAGE__->result_source_instance->view_definition(<<'ENDSQL');
d.ip, d.name, d.dns,
p.port, p.name AS port_description,
p.remote_ip, p.remote_id, p.remote_type, p.remote_port,
dpp.remote_is_wap, dpp.remote_is_phone,
l.log AS comment,
a.log, a.finished
@@ -23,6 +24,7 @@ __PACKAGE__->result_source_instance->view_definition(<<'ENDSQL');
INNER JOIN device d USING (ip)
LEFT OUTER JOIN device_skip ds
ON ('discover' = ANY(ds.actionset) AND p.remote_ip = ds.device)
LEFT OUTER JOIN device_port_properties dpp USING (ip, port)
LEFT OUTER JOIN device_port_log l USING (ip, port)
LEFT OUTER JOIN admin a
ON (p.remote_ip = a.device AND a.action = 'discover')
@@ -58,6 +60,10 @@ __PACKAGE__->add_columns(
{ data_type => "text", is_nullable => 1 },
"remote_id",
{ data_type => "text", is_nullable => 1 },
"remote_is_wap",
{ data_type => "boolean", is_nullable => 1 },
"remote_is_phone",
{ data_type => "boolean", is_nullable => 1 },
"comment",
{ data_type => "text", is_nullable => 1 },
"log",

View File

@@ -3,7 +3,10 @@ use base 'App::Netdisco::DB::ResultSet';
use strict;
use warnings;
use Try::Tiny;
use NetAddr::IP::Lite ':lower';
require Dancer::Logger;
=head1 ADDITIONAL METHODS
@@ -44,16 +47,16 @@ sub with_times {
->search({},
{
'+columns' => {
uptime_age => \("replace(age(timestamp 'epoch' + uptime / 100 * interval '1 second', "
uptime_age => \("replace(age(timestamp 'epoch' + me.uptime / 100 * interval '1 second', "
."timestamp '1970-01-01 00:00:00-00')::text, 'mon', 'month')"),
first_seen_stamp => \"to_char(me.creation, 'YYYY-MM-DD HH24:MI')",
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')",
last_discover_stamp => \"to_char(me.last_discover, 'YYYY-MM-DD HH24:MI')",
last_macsuck_stamp => \"to_char(me.last_macsuck, 'YYYY-MM-DD HH24:MI')",
last_arpnip_stamp => \"to_char(me.last_arpnip, 'YYYY-MM-DD HH24:MI')",
since_first_seen => \"extract(epoch from (age(now(), me.creation)))",
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)))",
since_last_discover => \"extract(epoch from (age(now(), me.last_discover)))",
since_last_macsuck => \"extract(epoch from (age(now(), me.last_macsuck)))",
since_last_arpnip => \"extract(epoch from (age(now(), me.last_arpnip)))",
},
});
}
@@ -591,12 +594,22 @@ handle the removal or archiving of nodes.
=cut
sub _plural { (shift || 0) == 1 ? 'entry' : 'entries' };
sub delete {
my $self = shift;
my $schema = $self->result_source->schema;
my $devices = $self->search(undef, { columns => 'ip' });
my $ip = undef;
{
no autovivification;
try { $ip ||= $devices->{attrs}->{where}->{ip} };
try { $ip ||= $devices->{attrs}->{where}->{'me.ip'} };
}
$ip ||= 'netdisco';
foreach my $set (qw/
DeviceIp
DeviceVlan
@@ -604,9 +617,12 @@ sub delete {
DeviceModule
Community
/) {
$schema->resultset($set)->search(
my $gone = $schema->resultset($set)->search(
{ ip => { '-in' => $devices->as_query } },
)->delete;
Dancer::Logger::debug sprintf ' [%s] db/device - removed %d %s from %s',
$ip, $gone, _plural($gone), $set if defined Dancer::Logger::logger();
}
foreach my $set (qw/
@@ -618,13 +634,16 @@ sub delete {
)->delete;
}
$schema->resultset('Topology')->search({
my $gone = $schema->resultset('Topology')->search({
-or => [
{ dev1 => { '-in' => $devices->as_query } },
{ dev2 => { '-in' => $devices->as_query } },
],
})->delete;
Dancer::Logger::debug sprintf ' [%s] db/device - removed %d manual topology %s',
$ip, $gone, _plural($gone) if defined Dancer::Logger::logger();
$schema->resultset('DevicePort')->search(
{ ip => { '-in' => $devices->as_query } },
)->delete(@_);

View File

@@ -4,6 +4,9 @@ use base 'App::Netdisco::DB::ResultSet';
use strict;
use warnings;
use Try::Tiny;
require Dancer::Logger;
__PACKAGE__->load_components(qw/
+App::Netdisco::DB::ExplicitLocking
/);
@@ -222,12 +225,22 @@ handle the removal or archiving of nodes.
=cut
sub _plural { (shift || 0) == 1 ? 'entry' : 'entries' };
sub delete {
my $self = shift;
my $schema = $self->result_source->schema;
my $ports = $self->search(undef, { columns => 'ip' });
my $ip = undef;
{
no autovivification;
try { $ip ||= ${ $ports->{attrs}->{where}->{ip}->{'-in'} }->[1]->[1] };
try { $ip ||= $ports->{attrs}->{where}->{'me.ip'} };
}
$ip ||= 'netdisco';
foreach my $set (qw/
DevicePortPower
DevicePortProperties
@@ -235,9 +248,12 @@ sub delete {
DevicePortWireless
DevicePortSsid
/) {
$schema->resultset($set)->search(
my $gone = $schema->resultset($set)->search(
{ ip => { '-in' => $ports->as_query }},
)->delete;
Dancer::Logger::debug sprintf ' [%s] db/ports - removed %d port %s from %s',
$ip, $gone, _plural($gone), $set if defined Dancer::Logger::logger();
}
$schema->resultset('Node')->search(

View File

@@ -75,21 +75,21 @@ sub arpnip {
$expect->send( $args->{enable_password} ."\n" );
}
$prompt = qr/#/;
$prompt = qr/#\s*$/;
($pos, $error, $match, $before, $after) = $expect->expect(10, -re, $prompt);
$expect->send("terminal pager 2147483647\n");
($pos, $error, $match, $before, $after) = $expect->expect(5, -re, $prompt);
$expect->send("show names\n");
($pos, $error, $match, $before, $after) = $expect->expect(60, -re, $prompt);
my @names = split(m/\n/, $before);
$expect->send("terminal pager 2147483647\n");
($pos, $error, $match, $before, $after) = $expect->expect(5, -re, $prompt);
$expect->send("show arp\n");
($pos, $error, $match, $before, $after) = $expect->expect(60, -re, $prompt);
my @lines = split(m/\n/, $before);
my @arpentries = ();
my @lines = split(m/\n/, $before);
# ifname 192.0.2.1 0011.2233.4455 123
my $linereg = qr/[A-z0-9\-\.]+\s([A-z0-9\-\.]+)\s
@@ -98,7 +98,7 @@ sub arpnip {
foreach my $line (@lines) {
if ($line =~ $linereg) {
my ($ip, $mac) = ($1, $2);
if ($ip !~ m/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/) {
if ($ip !~ m/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/) {
foreach my $name (@names) {
if ($name =~ qr/name\s([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\s([\w-]*)/x) {
if ($ip eq $2) {
@@ -107,7 +107,7 @@ sub arpnip {
}
}
}
if ($ip =~ m/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/) {
if ($ip =~ m/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/) {
push @arpentries, { mac => $mac, ip => $ip };
}
}

View File

@@ -10,7 +10,7 @@ our @EXPORT_OK = qw/
get_device
delete_device
renumber_device
match_devicetype
match_to_setting
is_discoverable is_discoverable_now
is_arpnipable is_arpnipable_now
is_macsuckable is_macsuckable_now
@@ -100,8 +100,8 @@ sub delete_device {
=head2 renumber_device( $current_ip, $new_ip )
Will update all records in Netdisco referring to the device with
C<$current_ip> to use C<$new_ip> instead, followed by renumbering the device
iteself.
C<$current_ip> to use C<$new_ip> instead, followed by renumbering the
device itself.
Returns true if the transaction completes, else returns false.
@@ -120,7 +120,7 @@ sub renumber_device {
schema('netdisco')->resultset('UserLog')->create({
username => session('logged_in_user'),
userip => scalar eval {request->remote_address},
event => (sprintf "Renumber device %s to %s", $device->ip, $new_ip),
event => (sprintf "Renumber device %s to %s", $ip, $new_ip),
});
$happy = 1;
@@ -129,7 +129,7 @@ sub renumber_device {
return $happy;
}
=head2 match_devicetype( $type, $setting_name )
=head2 match_to_setting( $type, $setting_name )
Given a C<$type> (which may be any text value), returns true if any of the
list of regular expressions in C<$setting_name> is matched, otherwise returns
@@ -137,7 +137,7 @@ false.
=cut
sub match_devicetype {
sub match_to_setting {
my ($type, $setting_name) = @_;
return 0 unless $type and $setting_name;
return (scalar grep {$type =~ m/$_/}
@@ -146,7 +146,7 @@ sub match_devicetype {
sub _bail_msg { debug $_[0]; return 0; }
=head2 is_discoverable( $ip, $device_type? )
=head2 is_discoverable( $ip, [$device_type, \@device_capabilities]? )
Given an IP address, returns C<true> if Netdisco on this host is permitted by
the local configuration to discover the device.
@@ -154,20 +154,32 @@ the local configuration to discover the device.
The configuration items C<discover_no> and C<discover_only> are checked
against the given IP.
If C<$device_type> is also given, then C<discover_no_type> will also be
checked.
If C<$device_type> is also given, then C<discover_no_type> will be checked.
Also respects C<discover_phones> and C<discover_waps> if either are set to
false.
Returns false if the host is not permitted to discover the target device.
=cut
sub is_discoverable {
my ($ip, $remote_type) = @_;
my ($ip, $remote_type, $remote_cap) = @_;
my $device = get_device($ip) or return 0;
$remote_type ||= '';
$remote_cap ||= [];
if (match_devicetype($remote_type, 'discover_no_type')) {
return _bail_msg("is_discoverable: $device matched discover_no_type");
}
return _bail_msg("is_discoverable: $device matches wap_platforms but discover_waps is not enabled")
if ((not setting('discover_waps')) and
(match_to_setting($remote_type, 'wap_platforms') or
scalar grep {match_to_setting($_, 'wap_capabilities')} @$remote_cap));
return _bail_msg("is_discoverable: $device matches phone_platforms but discover_phones is not enabled")
if ((not setting('discover_phones')) and
(match_to_setting($remote_type, 'phone_platforms') or
scalar grep {match_to_setting($_, 'phone_capabilities')} @$remote_cap));
return _bail_msg("is_discoverable: $device matched discover_no_type")
if (match_to_setting($remote_type, 'discover_no_type'));
return _bail_msg("is_discoverable: $device matched discover_no")
if check_acl_no($device, 'discover_no');

View File

@@ -105,7 +105,8 @@ sub port_reconfig_check {
# uplink check
return "forbidden: port [$name] on [$ip] is an uplink"
if $port->remote_type and not $has_phone and not setting('portctl_uplinks');
if ($port->is_uplink or $port->remote_type)
and not $has_phone and not setting('portctl_uplinks');
# phone check
return "forbidden: port [$name] on [$ip] is a phone"
@@ -215,18 +216,10 @@ sub is_vlan_interface {
Returns true if the C<$port> L<DBIx::Class> object has a phone connected.
This uses a simple check on the I<type> of the remote connected device, and
therefore might sometimes return a false-negative result.
=cut
sub port_has_phone {
my $port = shift;
my $has_phone = ($port->remote_type
and $port->remote_type =~ /ip.phone/i) ? 1 : 0;
return $has_phone;
return (shift)->with_properties->remote_is_phone;
}
1;

View File

@@ -7,7 +7,7 @@ use Dancer::Plugin::Auth::Extensible;
use URI ();
use URL::Encode 'url_params_mixed';
use App::Netdisco::Util::Device 'match_devicetype';
use App::Netdisco::Util::Device 'match_to_setting';
# build view settings for port connected nodes and devices
set('connected_properties' => [
@@ -20,7 +20,7 @@ hook 'before_template' => sub {
my $tokens = shift;
# allow checking of discoverability of remote connected device
$tokens->{has_snmp} = sub { not match_devicetype(shift, 'discover_no_type') };
$tokens->{has_snmp} = sub { not match_to_setting(shift, 'discover_no_type') };
my $defaults = var('sidebar_defaults')->{'device_ports'}
or return;

View File

@@ -9,7 +9,7 @@ use App::Netdisco::Web::Plugin;
use Path::Class 'file';
use Safe;
use vars qw/$config @data/;
our ($config, @data);
foreach my $report (@{setting('reports')}) {
my $r = $report->{tag};

View File

@@ -27,6 +27,8 @@ get '/ajax/content/admin/undiscoveredneighbors' => require_role admin => sub {
# create a new row object to avoid hitting the DB in get_device()
my $dev = schema('netdisco')->resultset('Device')->new({ip => $r->{remote_ip}});
next unless is_discoverable( $dev, $r->{remote_type} );
next if (not setting('discover_waps')) and $r->{remote_is_wap};
next if (not setting('discover_phones')) and $r->{remote_is_phone};
push @discoverable_results, $r;
}
return unless scalar @discoverable_results;

View File

@@ -125,18 +125,13 @@ sub make_link_infostring {
(my $left_name = lc($link->{left_dns} || $link->{left_name} || $link->{left_ip})) =~ s/$domain$//;
(my $right_name = lc($link->{right_dns} || $link->{right_name} || $link->{right_ip})) =~ s/$domain$//;
if ($link->{aggports} == 1) {
return sprintf '<b>%s:%s</b> (%s)<br><b>%s:%s</b> (%s)',
$left_name, $link->{left_port}->[0],
($link->{left_descr}->[0] || 'no description'),
$right_name, $link->{right_port}->[0],
($link->{right_descr}->[0] || 'no description');
}
else {
return sprintf '<b>%s:(%s)</b><br><b>%s:(%s)</b>',
$left_name, join(',', @{$link->{left_port}}),
$right_name, join(',', @{$link->{right_port}});
}
my @zipped = List::MoreUtils::zip6
@{$link->{left_port}}, @{$link->{left_descr}},
@{$link->{right_port}}, @{$link->{right_descr}};
return join '<br><br>', map { sprintf '<b>%s:%s</b> (%s)<br><b>%s:%s</b> (%s)',
$left_name, $_->[0], ($_->[1] || 'no description'),
$right_name, $_->[2], ($_->[3] || 'no description') } @zipped;
}
ajax '/ajax/data/device/netmap' => require_login sub {
@@ -179,17 +174,6 @@ ajax '/ajax/data/device/netmap' => require_login sub {
]) : ())
}, { result_class => 'DBIx::Class::ResultClass::HashRefInflator' });
if ($vlan) {
$links = $links->search({
-or => [
{ 'left_vlans.vlan' => $vlan },
{ 'right_vlans.vlan' => $vlan },
],
}, {
join => [qw/left_vlans right_vlans/],
});
}
while (my $link = $links->next) {
push @{$data{'links'}}, {
FROMID => $link->{left_ip},
@@ -217,10 +201,19 @@ ajax '/ajax/data/device/netmap' => require_login sub {
join => 'throughput',
})->with_times;
# filter by vlan for all or neighbors only
if ($vlan) {
$devices = $devices->search(
{ 'vlans.vlan' => $vlan },
{ join => 'vlans' }
);
}
DEVICE: while (my $device = $devices->next) {
# if in neighbors or vlan mode then use %ok_dev to filter
next DEVICE if (($mapshow eq 'neighbors') or $vlan)
and (not $ok_dev{$device->ip});
# if in neighbors mode then use %ok_dev to filter
next DEVICE if ($device->ip ne $qdev->ip)
and ($mapshow eq 'neighbors')
and (not $ok_dev{$device->ip}); # showing only neighbors but no link
# if location picked then filter
next DEVICE if ((scalar @lgrplist) and ((!defined $device->location)

View File

@@ -25,25 +25,6 @@ get '/ajax/content/device/ports' => require_login sub {
if ($f) {
if (($prefer eq 'vlan') or (not $prefer and $f =~ m/^\d+$/)) {
return unless $f =~ m/^\d+$/;
if (param('invert')) {
$set = $set->search({
'me.vlan' => { '!=' => $f },
'port_vlans.vlan' => [
'-or' => { '!=' => $f }, { '=' => undef }
],
}, { join => 'port_vlans' });
}
else {
$set = $set->search({
-or => {
'me.vlan' => $f,
'port_vlans.vlan' => $f,
},
}, { join => 'port_vlans' });
}
return unless $set->count;
}
else {
if (param('partial')) {
@@ -120,7 +101,7 @@ get '/ajax/content/device/ports' => require_login sub {
# now begin to join tables depending on the selected columns/options
# get vlans on the port
# leave this query dormant (lazy) unless c_vmember is set
# leave this query dormant (lazy) unless c_vmember is set or vlan filtering
my $vlans = $set->search({}, {
select => [
'port',
@@ -131,7 +112,7 @@ get '/ajax/content/device/ports' => require_login sub {
group_by => 'me.port',
});
if (param('c_vmember')) {
if (param('c_vmember') or ($prefer eq 'vlan') or (not $prefer and $f =~ m/^\d+$/)) {
$vlans = { map {(
$_->port => {
# DBIC smart enough to work out this should be an arrayref :)
@@ -194,13 +175,37 @@ get '/ajax/content/device/ports' => require_login sub {
# also get remote LLDP inventory if asked for
$set = $set->with_remote_inventory if param('n_inventory');
# sort ports (empty set would be a 'no records' msg)
my $results = [ sort { &App::Netdisco::Util::Web::sort_port($a->port, $b->port) } $set->all ];
return unless scalar @$results;
# run query
my @results = $set->all;
# filter for tagged vlan using existing agg query,
# which is better than join inflation
if (($prefer eq 'vlan') or (not $prefer and $f =~ m/^\d+$/)) {
if (param('invert')) {
@results = grep {
(!defined $_->vlan or $_->vlan ne $f)
and
(0 == scalar grep {defined and $_ ne $f} @{ $vlans->{$_->port}->{vlan_set} })
} @results;
}
else {
@results = grep {
($_->vlan eq $f)
or
(scalar grep {defined and $_ eq $f} @{ $vlans->{$_->port}->{vlan_set} })
} @results;
}
}
# sort ports
@results = sort { &App::Netdisco::Util::Web::sort_port($a->port, $b->port) } @results;
# empty set would be a 'no records' msg
return unless scalar @results;
if (request->is_ajax) {
template 'ajax/device/ports.tt', {
results => $results,
results => \@results,
nodes => $nodes_name,
ips => $ips_name,
device => $device,
@@ -210,7 +215,7 @@ get '/ajax/content/device/ports' => require_login sub {
else {
header( 'Content-Type' => 'text/comma-separated-values' );
template 'ajax/device/ports_csv.tt', {
results => $results,
results => \@results,
nodes => $nodes_name,
ips => $ips_name,
device => $device,

View File

@@ -0,0 +1,36 @@
package App::Netdisco::Web::Plugin::Device::Vlans;
use strict;
use warnings;
use Dancer ':syntax';
use Dancer::Plugin::Ajax;
use Dancer::Plugin::DBIC;
use Dancer::Plugin::Auth::Extensible;
use App::Netdisco::Web::Plugin;
register_device_tab({ tag => 'vlans', label => 'VLANs', provides_csv => 1 });
get '/ajax/content/device/vlans' => require_login sub {
my $q = param('q');
my $device = schema('netdisco')->resultset('Device')
->search_for_device($q) or send_error('Bad device', 400);
my @results = $device->vlans->search( {}, { order_by => 'vlan' } )->hri->all;
return unless scalar @results;
if (request->is_ajax) {
my $json = to_json( \@results );
template 'ajax/device/vlans.tt', { results => $json },
{ layout => undef };
}
else {
header( 'Content-Type' => 'text/comma-separated-values' );
template 'ajax/device/vlans_csv.tt', { results => \@results },
{ layout => undef };
}
};
true;

View File

@@ -110,12 +110,12 @@ get '/ajax/content/report/ipinventory' => require_login sub {
'ip', 'mac', 'dns', 'time_last', 'time_first',
'active', 'node', 'age'
],
order_by => [{-asc => 'ip'}, {-desc => 'active'}],
order_by => [{-asc => 'ip'}, {-desc => 'active'}, {-asc => 'node'}],
}
)->as_query;
my $rs;
if ( $start && $end ) {
if ( $start and $end ) {
$start = $start . ' 00:00:00';
$end = $end . ' 23:59:59';

View File

@@ -27,35 +27,48 @@ ajax '/ajax/content/search/node' => require_login sub {
my @active = (param('archived') ? () : (-bool => 'active'));
my (@times, @wifitimes, @porttimes);
if ($start and $end) {
if ( $start and $end ) {
$start = $start . ' 00:00:00';
$end = $end . ' 23:59:59';
if ($agenot) {
@times = (-or => [
time_first => [ { '<', $start }, undef ],
time_last => { '>', $end },
time_first => [ undef ],
time_last => [ { '<', $start }, { '>', $end } ]
]);
@wifitimes = (-or => [
time_last => { '<', $start },
time_last => { '>', $end },
time_last => [ undef ],
time_last => [ { '<', $start }, { '>', $end } ],
]);
@porttimes = (-or => [
creation => { '<', $start },
creation => { '>', $end },
creation => [ undef ],
creation => [ { '<', $start }, { '>', $end } ]
]);
}
else {
@times = (-and => [
time_first => { '>=', $start },
time_last => { '<=', $end },
@times = (-or => [
-and => [
time_first => undef,
time_last => undef,
],
-and => [
time_last => { '>=', $start },
time_last => { '<=', $end },
],
]);
@wifitimes = (-and => [
time_last => { '>=', $start },
time_last => { '<=', $end },
@wifitimes = (-or => [
time_last => undef,
-and => [
time_last => { '>=', $start },
time_last => { '<=', $end },
],
]);
@porttimes = (-and => [
creation => { '>=', $start },
creation => { '<=', $end },
@porttimes = (-or => [
creation => undef,
-and => [
creation => { '>=', $start },
creation => { '<=', $end },
],
]);
}
}

View File

@@ -49,7 +49,7 @@ ajax '/ajax/data/port/typeahead' => require_login sub {
if $port;
my $results = [
map {{ label => (sprintf "%s (%s)", $_->port, $_->name), value => $_->port }}
map {{ label => (sprintf "%s (%s)", $_->port, ($_->name || '')), value => $_->port }}
sort { &App::Netdisco::Util::Web::sort_port($a->port, $b->port) } $set->all
];

View File

@@ -45,7 +45,7 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
# only enqueue if device is not already discovered,
# discover_* config permits the discovery
foreach my $neighbor (@to_discover) {
my ($ip, $remote_type, $remote_id) = @$neighbor;
my ($ip, $remote_id) = @$neighbor;
if ($seen_ip{ $ip }++) {
debug sprintf
' queue - skip: IP %s is already queued from %s',
@@ -63,13 +63,6 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
my $newdev = get_device($ip);
next if $newdev->in_storage;
if (not is_discoverable($newdev, $remote_type)) {
debug sprintf
' queue - skip: %s of type [%s] excluded by discover_* config',
$ip, ($remote_type || '');
next;
}
# risk of things going wrong...?
# https://quickview.cloudapps.cisco.com/quickview/bug/CSCur12254
@@ -114,6 +107,7 @@ sub store_neighbors {
or return (); # already checked!
# first allow any manually configured topology to be set
# and do this before we cache the rows in vars->{'device_ports'}
set_manual_topology($device);
if (!defined $snmp->has_topo) {
@@ -126,6 +120,12 @@ sub store_neighbors {
my $c_port = $snmp->c_port;
my $c_id = $snmp->c_id;
my $c_platform = $snmp->c_platform;
my $c_cap = $snmp->c_cap;
# cache the device ports to save hitting the database for many single rows
vars->{'device_ports'} =
{ map {($_->port => $_)} $device->ports->reset->all };
my $device_ports = vars->{'device_ports'};
# v4 and v6 neighbor tables
my $c_ip = ($snmp->c_ip || {});
@@ -144,12 +144,12 @@ sub store_neighbors {
next;
}
my $port = $interfaces->{ $c_if->{$entry} };
my $portrow = schema('netdisco')->resultset('DevicePort')
->single({ip => $device->ip, port => $port});
# WRT #475 this is SAFE because we check against known ports below
my $port = $interfaces->{ $c_if->{$entry} } or next;
my $portrow = $device_ports->{$port};
if (!defined $portrow) {
info sprintf ' [%s] neigh - local port %s not in database!',
debug sprintf ' [%s] neigh - local port %s already skipped, ignoring',
$device->ip, $port;
next;
}
@@ -161,7 +161,7 @@ sub store_neighbors {
}
if ($portrow->manual_topo) {
info sprintf ' [%s] neigh - %s has manually defined topology',
debug sprintf ' [%s] neigh - %s has manually defined topology',
$device->ip, $port;
next;
}
@@ -170,12 +170,13 @@ sub store_neighbors {
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} || [];
next unless $remote_ip;
my $r_netaddr = NetAddr::IP::Lite->new($remote_ip);
if ($r_netaddr and ($r_netaddr->addr ne $remote_ip)) {
info sprintf ' [%s] neigh - IP on %s: using %s as canonical form of %s',
debug sprintf ' [%s] neigh - IP on %s: using %s as canonical form of %s',
$device->ip, $port, $r_netaddr->addr, $remote_ip;
$remote_ip = $r_netaddr->addr;
}
@@ -189,7 +190,7 @@ sub store_neighbors {
if ($remote_id) {
my $devices = schema('netdisco')->resultset('Device');
my $neigh = $devices->single({name => $remote_id});
info sprintf
debug sprintf
' [%s] neigh - bad address %s on port %s, searching for %s instead',
$device->ip, $remote_ip, $port, $remote_id;
@@ -207,7 +208,7 @@ sub store_neighbors {
(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
debug sprintf
' [%s] neigh - trying to find neighbor %s by MAC %s',
$device->ip, $remote_id, $mac->as_ieee;
$neigh = $devices->single({mac => $mac->as_ieee});
@@ -221,17 +222,17 @@ sub store_neighbors {
if ($neigh) {
$remote_ip = $neigh->ip;
info sprintf ' [%s] neigh - found %s with IP %s',
debug sprintf ' [%s] neigh - found %s with IP %s',
$device->ip, $remote_id, $remote_ip;
}
else {
info sprintf ' [%s] neigh - could not find %s, skipping',
debug 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',
debug sprintf ' [%s] neigh - skipping unuseable address %s on port %s',
$device->ip, $remote_ip, $port;
next;
}
@@ -240,7 +241,15 @@ sub store_neighbors {
# what we came here to do.... discover the neighbor
debug sprintf ' [%s] neigh - %s with ID [%s] on %s',
$device->ip, $remote_ip, ($remote_id || ''), $port;
push @to_discover, [$remote_ip, $remote_type, $remote_id];
if (is_discoverable($remote_ip, $remote_type, $remote_cap)) {
push @to_discover, [$remote_ip, $remote_id];
}
else {
debug sprintf
' [%s] neigh - skip: %s of type [%s] excluded by discover_* config',
$device->ip, $remote_ip, ($remote_type || '');
}
$remote_port = $c_port->{$entry};
if (defined $remote_port) {
@@ -248,18 +257,18 @@ sub store_neighbors {
$remote_port =~ s/[^\d\s\/\.,()\w:-]+//gi;
}
else {
info sprintf ' [%s] neigh - no remote port found for port %s at %s',
debug sprintf ' [%s] neigh - no remote port found for port %s at %s',
$device->ip, $port, $remote_ip;
}
$portrow->update({
$portrow = $portrow->update({
remote_ip => $remote_ip,
remote_port => $remote_port,
remote_type => $remote_type,
remote_id => $remote_id,
is_uplink => \"true",
manual_topo => \"false",
});
})->discard_changes();
# update master of our aggregate to be a neighbor of
# the master on our peer device (a lot of iffs to get there...).

View File

@@ -22,8 +22,8 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
my $eigrp_peers = $snmp->eigrp_peers || {};
return Status->info(" [$device] neigh - no BGP, OSPF, or EIGRP peers")
unless ((scalar values %$ospf_peers) or (scalar values %$bgp_peers)
or (scalar values %$eigrp_peers));
unless ((scalar values %$ospf_peers) or (scalar values %$ospf_routers)
or (scalar values %$bgp_peers) or (scalar values %$eigrp_peers));
my $count = 0;
foreach my $ip ((values %$ospf_peers), (values %$ospf_routers),

View File

@@ -32,6 +32,10 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
};
}
# cache the device ports to save hitting the database for many single rows
my $device_ports = vars->{'device_ports'}
|| { map {($_->port => $_)} $device->ports->all };
my $interfaces = $snmp->interfaces;
my $p_ifindex = $snmp->peth_port_ifindex;
my $p_admin = $snmp->peth_port_admin;
@@ -42,11 +46,16 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
# 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;
# WRT #475 this is SAFE because we check against known ports below
my $port = $interfaces->{ $p_ifindex->{$entry} } or next;
if (!defined $device_ports->{$port}) {
debug sprintf ' [%s] power - local port %s already skipped, ignoring',
$device->ip, $port;
next;
}
my ($module) = split m/\./, $entry;
push @portpower, {
port => $port,
module => $module,

View File

@@ -8,7 +8,7 @@ use App::Netdisco::Transport::SNMP ();
use Dancer::Plugin::DBIC 'schema';
use Encode;
use App::Netdisco::Util::Device 'match_devicetype';
use App::Netdisco::Util::Device 'match_to_setting';
register_worker({ phase => 'main', driver => 'snmp' }, sub {
my ($job, $workerconf) = @_;
@@ -21,10 +21,20 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
my $interfaces = $snmp->interfaces || {};
my %properties = ();
# cache the device ports to save hitting the database for many single rows
my $device_ports = vars->{'device_ports'}
|| { map {($_->port => $_)} $device->ports->all };
my $raw_speed = $snmp->i_speed_raw || {};
foreach my $idx (keys %$raw_speed) {
my $port = $interfaces->{$idx} or next;
if (!defined $device_ports->{$port}) {
debug sprintf ' [%s] properties/speed - local port %s already skipped, ignoring',
$device->ip, $port;
next;
}
$properties{ $port }->{raw_speed} = $raw_speed->{$idx};
}
@@ -32,6 +42,12 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
foreach my $idx (keys %$err_cause) {
my $port = $interfaces->{$idx} or next;
if (!defined $device_ports->{$port}) {
debug sprintf ' [%s] properties/errdis - local port %s already skipped, ignoring',
$device->ip, $port;
next;
}
$properties{ $port }->{error_disable_cause} = $err_cause->{$idx};
}
@@ -39,6 +55,12 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
foreach my $idx (keys %$faststart) {
my $port = $interfaces->{$idx} or next;
if (!defined $device_ports->{$port}) {
debug sprintf ' [%s] properties/faststart - local port %s already skipped, ignoring',
$device->ip, $port;
next;
}
$properties{ $port }->{faststart} = $faststart->{$idx};
}
@@ -54,17 +76,22 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
foreach my $idx (keys %$c_if) {
my $port = $interfaces->{ $c_if->{$idx} } or next;
if (!defined $device_ports->{$port}) {
debug sprintf ' [%s] properties/lldpcap - local port %s already skipped, ignoring',
$device->ip, $port;
next;
}
my $remote_cap = $c_cap->{$idx} || [];
my $remote_type = Encode::decode('UTF-8', $c_platform->{$idx} || '');
$properties{ $port }->{remote_is_wap} = 'true'
if scalar grep {match_devicetype($_, 'wap_capabilities')} @$remote_cap
or match_devicetype($remote_type, 'wap_platforms');
if scalar grep {match_to_setting($_, 'wap_capabilities')} @$remote_cap
or match_to_setting($remote_type, 'wap_platforms');
$properties{ $port }->{remote_is_phone} = 'true'
if scalar grep {match_devicetype($_, 'phone_capabilities')} @$remote_cap
or match_devicetype($remote_type, 'phone_platforms');
if scalar grep {match_to_setting($_, 'phone_capabilities')} @$remote_cap
or match_to_setting($remote_type, 'phone_platforms');
next unless scalar grep {defined && m/^inventory$/} @{ $rem_media_cap->{$idx} };
@@ -74,17 +101,28 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
$properties{ $port }->{remote_serial} = $rem_serial->{ $idx };
}
foreach my $idx (keys %$interfaces) {
my $port = $interfaces->{$idx} or next;
if (!defined $device_ports->{$port}) {
debug sprintf ' [%s] properties/ifindex - local port %s already skipped, ignoring',
$device->ip, $port;
next;
}
$properties{ $port }->{ifindex} = $idx;
}
return Status->info(" [$device] no port properties to record")
unless scalar keys %properties;
schema('netdisco')->txn_do(sub {
my $gone = $device->properties_ports->delete;
debug sprintf ' [%s] props - removed %d ports with properties',
debug sprintf ' [%s] properties - removed %d ports with properties',
$device->ip, $gone;
$device->properties_ports->populate(
[map {{ port => $_, %{ $properties{$_} } }} keys %properties] );
return Status->info(sprintf ' [%s] props - added %d new port properties',
return Status->info(sprintf ' [%s] properties - added %d new port properties',
$device->ip, scalar keys %properties);
});
});

View File

@@ -151,7 +151,7 @@ register_worker({ phase => 'early', driver => 'snmp' }, sub {
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',
debug sprintf ' [%s] interfaces - device uptime wrapped %d times - correcting',
$device->ip, $dev_uptime_wrapped;
$device->uptime( $dev_uptime + $dev_uptime_wrapped * 2**32 );
}
@@ -190,7 +190,7 @@ register_worker({ phase => 'early', driver => 'snmp' }, sub {
my $lc = $i_lastchange->{$entry} || 0;
if (not $dev_uptime_wrapped and $lc > $dev_uptime) {
info sprintf ' [%s] interfaces - device uptime wrapped (%s) - correcting',
debug sprintf ' [%s] interfaces - device uptime wrapped (%s) - correcting',
$device->ip, $port;
$device->uptime( $dev_uptime + 2**32 );
$dev_uptime_wrapped = 1;
@@ -238,6 +238,7 @@ register_worker({ phase => 'early', driver => 'snmp' }, sub {
# 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;
next unless defined $agg_ports->{$sidx}; # slave without a master?!
my $master = $interfaces->{ $agg_ports->{$sidx} } or next;
next unless exists $interfaces{$slave} and exists $interfaces{$master};

View File

@@ -33,6 +33,10 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
};
}
# cache the device ports to save hitting the database for many single rows
my $device_ports = vars->{'device_ports'}
|| { map {($_->port => $_)} $device->ports->all };
my $i_vlan = $snmp->i_vlan;
my $i_vlan_membership = $snmp->i_vlan_membership;
my $i_vlan_type = $snmp->i_vlan_type;
@@ -42,8 +46,13 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
my @portvlans = ();
foreach my $entry (keys %$i_vlan_membership) {
my %port_vseen = ();
my $port = $interfaces->{$entry};
next unless defined $port;
my $port = $interfaces->{$entry} or next;
if (!defined $device_ports->{$port}) {
debug sprintf ' [%s] vlans - local port %s already skipped, ignoring',
$device->ip, $port;
next;
}
my $type = $i_vlan_type->{$entry};

View File

@@ -18,6 +18,10 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
my $ssidlist = $snmp->i_ssidlist;
return unless scalar keys %$ssidlist;
# cache the device ports to save hitting the database for many single rows
my $device_ports = vars->{'device_ports'}
|| { map {($_->port => $_)} $device->ports->all };
my $interfaces = $snmp->interfaces;
my $ssidbcast = $snmp->i_ssidbcast;
my $ssidmac = $snmp->i_ssidmac;
@@ -36,6 +40,12 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
next;
}
if (!defined $device_ports->{$port}) {
debug sprintf ' [%s] wireless - local port %s already skipped, ignoring',
$device->ip, $port;
next;
}
push @ssids, {
port => $port,
ssid => $ssidlist->{$entry},
@@ -64,6 +74,12 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
next;
}
if (!defined $device_ports->{$port}) {
debug sprintf ' [%s] wireless - local port %s already skipped, ignoring',
$device->ip, $port;
next;
}
push @channels, {
port => $port,
channel => $channel->{$entry},

View File

@@ -7,7 +7,7 @@ use aliased 'App::Netdisco::Worker::Status';
use App::Netdisco::Transport::SNMP ();
use App::Netdisco::Util::Permission 'check_acl_no';
use App::Netdisco::Util::PortMAC 'get_port_macs';
use App::Netdisco::Util::Device 'match_devicetype';
use App::Netdisco::Util::Device 'match_to_setting';
use App::Netdisco::Util::Node 'check_mac';
use App::Netdisco::Util::SNMP 'snmp_comm_reindex';
use Dancer::Plugin::DBIC 'schema';
@@ -248,7 +248,6 @@ sub get_vlan_list {
# 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;
@@ -299,6 +298,8 @@ sub walk_fwtable {
next;
}
# WRT #475 this is SAFE because we check against known ports below
# but we do need the SNMP interface IDs to get the job done
my $port = $interfaces->{$iid};
unless (defined $port) {
@@ -318,6 +319,7 @@ sub walk_fwtable {
# this uses the cached $ports resultset to limit hits on the db
my $device_port = $device_ports->{$port};
# WRT #475 ... see? :-)
unless (defined $device_port) {
debug sprintf
' [%s] macsuck %s - port %s is not in database - skipping.',
@@ -340,7 +342,7 @@ sub walk_fwtable {
# neighbors otherwise it would kill the DB with device lookups.
my $neigh_cannot_macsuck = eval { # can fail
check_acl_no(($device_port->neighbor || "0 but true"), 'macsuck_unsupported') ||
match_devicetype($device_port->remote_type, 'macsuck_unsupported_type') };
match_to_setting($device_port->remote_type, 'macsuck_unsupported_type') };
if ($device_port->is_uplink) {
if ($neigh_cannot_macsuck) {

View File

@@ -41,11 +41,11 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
next unless defined $mac; # avoid null entries
# there can be more rows in txrate than other tables
my $txrate = defined $txrates->[$#$txrates]
my $txrate = (ref $txrates and defined $txrates->[$#$txrates])
? int($txrates->[$#$txrates])
: undef;
my $maxrate = defined $rates->[$#$rates]
my $maxrate = (ref $rates and defined $rates->[$#$rates])
? int($rates->[$#$rates])
: undef;

View File

@@ -1,5 +1,8 @@
package App::Netdisco::Worker::Plugin::MakeRancidConf;
use strict;
use warnings;
use Dancer ':syntax';
use Dancer::Plugin::DBIC;
@@ -8,7 +11,7 @@ use aliased 'App::Netdisco::Worker::Status';
use Path::Class;
use List::Util qw/pairkeys pairfirst/;
use File::Slurper 'write_text';
use File::Slurper qw/read_lines write_text/;
use App::Netdisco::Util::Permission 'check_acl_no';
register_worker({ phase => 'main' }, sub {
@@ -16,14 +19,31 @@ register_worker({ phase => 'main' }, sub {
my $config = setting('rancid') || {};
my $domain_suffix = setting('domain_suffix') || '';
my $delimiter = $config->{delimiter} || ':';
my $delimiter = $config->{delimiter} || ';';
my $down_age = $config->{down_age} || '1 day';
my $default_group = $config->{default_group} || 'default';
my $rancidhome = $config->{rancid_home}
my $rancidconf = $config->{rancid_conf} || '/etc/rancid';
my $rancidcvsroot = $config->{rancid_cvsroot}
|| dir($ENV{NETDISCO_HOME}, 'rancid')->stringify;
mkdir $rancidhome if ! -d $rancidhome;
return Status->error("cannot create or see rancid home: $rancidhome")
if ! -d $rancidhome;
mkdir $rancidcvsroot if ! -d $rancidcvsroot;
return Status->error("cannot create or access rancid cvsroot: $rancidcvsroot")
if ! -d $rancidcvsroot;
my $allowed_types = {};
foreach my $type (qw/base conf/) {
my $type_file = file($rancidconf, "rancid.types.$type")->stringify;
debug sprintf("trying rancid configuration file %s\n", $type_file);
next unless -f $type_file;
my @lines = read_lines($type_file);
foreach my $line (@lines) {
next if $line =~ m/^(?:\#|\$)/;
$allowed_types->{$1} += 1 if $line =~ m/^([a-z0-9_\-]+);login;.*$/;
}
}
return Status->error("You didn't have any device types configured in your rancid installation.")
if ! scalar keys %$allowed_types;
my $devices = schema('netdisco')->resultset('Device')->search(undef, {
'+columns' => { old =>
@@ -32,24 +52,37 @@ register_worker({ phase => 'main' }, sub {
$config->{groups} ||= { default => 'any' };
$config->{vendormap} ||= {};
$config->{excluded} ||= {};
$config->{by_ip} ||= {};
$config->{by_hostname} ||= {};
my $routerdb = {};
while (my $d = $devices->next) {
my $name =
check_acl_no($d, $config->{by_ip}) ? $d->ip : ($d->dns || $d->name);
$name =~ s/$domain_suffix$//
if check_acl_no($d, $config->{by_hostname});
if (check_acl_no($d, $config->{excluded})) {
debug " skipping $d: device excluded of export";
next
}
my $name = check_acl_no($d, $config->{by_ip}) ? $d->ip : ($d->dns || $d->name);
$name =~ s/$domain_suffix$// if check_acl_no($d, $config->{by_hostname});
my ($group) =
pairkeys pairfirst { check_acl_no($d, $b) } %{ $config->{groups} };
(pairkeys pairfirst { check_acl_no($d, $b) } %{ $config->{groups} }) || $default_group;
my ($vendor) =
(pairkeys pairfirst { check_acl_no($d, $b) } %{ $config->{vendormap} })
|| $d->vendor;
if ($vendor =~ m/(?:enterprises\.|netdisco)/) {
if (not ($name and $vendor)) {
debug " skipping $d: the name or vendor is not defined";
next
} elsif ($vendor =~ m/(?:enterprises\.|netdisco)/) {
debug " skipping $d with unresolved vendor: $vendor";
next;
} elsif (scalar keys %$allowed_types and !exists($allowed_types->{$vendor})) {
debug " skipping $d: $vendor doesn't exist in rancid's vendor list";
next;
}
push @{$routerdb->{$group}},
@@ -58,12 +91,14 @@ register_worker({ phase => 'main' }, sub {
}
foreach my $group (keys %$routerdb) {
mkdir dir($rancidhome, $group)->stringify;
my $content = join "\n", @{$routerdb->{$group}};
write_text(file($rancidhome, $group, 'router.db')->stringify, "${content}\n");
mkdir dir($rancidcvsroot, $group)->stringify;
my $content = "#\n# Router list file for rancid group $group.\n";
$content .= "# Generate automatically by App::Netdisco::Worker::Plugin::MakeRancidConf\n#\n";
$content .= join "\n", sort @{$routerdb->{$group}};
write_text(file($rancidcvsroot, $group, 'router.db')->stringify, "${content}\n");
}
return Status->done('Wrote RANCID configuration.');
return Status->done('Wrote rancid configuration.');
});
true;
@@ -72,17 +107,20 @@ true;
=head1 NAME
MakeRancidConf - Generate RANCID Configuration
MakeRancidConf - Generate rancid Configuration
=head1 INTRODUCTION
This worker will generate a RANCID configuration for all devices in Netdisco.
This worker will generate a rancid configuration for all devices in Netdisco.
Optionally you can provide configuration to control the output, however the
defaults are sane, and will create one RANCID group called "C<default>" which
contains all devices. Those devices not discovered successfully within the
past day will be marked as "down" for RANCID to skip. Configuration is saved
to the "rancid" subdirectory of Netdisco's home folder.
defaults are sane for rancid versions 3.x and will create one rancid group
called C<default> which contains all devices. Those devices not discovered
successfully within the past day will be marked as C<down> for rancid to skip.
Configuration is saved to the F<~/rancid> subdirectory of Netdisco's home folder.
Note that this only generates the router.db files, you will still need to
configure rancid's F<.cloginrc> and schedule C<rancid-run> to run.
You could run this worker at 09:05 each day using the following configuration:
@@ -90,95 +128,142 @@ You could run this worker at 09:05 each day using the following configuration:
makerancidconf:
when: '5 9 * * *'
Since MakeRancidConf is a worker module it can also be run via C<netdisco-do>:
netdisco-do makerancidconf
=head1 CONFIGURATION
Here is a complete example of the configuration, which must be called
"C<rancid>". All keys are optional:
C<rancid>. All keys are optional:
rancid:
rancid_home: "$ENV{NETDISCO_HOME}/rancid" # default
down_age: '1 day' # default
delimiter: ':' # default
rancid_cvsroot: '$ENV{NETDISCO_HOME}/rancid' # default
rancid_conf: '/etc/rancid' # default
down_age: '1 day' # default
delimiter: ';' # default
default_group: 'default' # default
excluded:
excludegroup1: 'host_group1_acl'
excludegroup2: 'host_group2_acl'
groups:
groupname1: 'host_group1_acl'
groupname2: 'host_group2_acl'
groupname1: 'host_group3_acl'
groupname2: 'host_group4_acl'
vendormap:
vname1: 'host_group3_acl'
vname2: 'host_group4_acl'
by_ip: 'host_group5_acl'
by_hostname: 'host_group6_acl'
vname1: 'host_group5_acl'
vname2: 'host_group6_acl'
by_ip: 'host_group7_acl'
by_hostname: 'host_group8_acl'
Note that the default home for writing files is not "C</var/lib/rancid>" so
you may wish to set this (especially if migrating from the old
Note that the default directory for writing files is not F</var/lib/rancid> so
you may wish to set this in C<rancid_cvsroot>, (especially if migrating from the old
C<netdisco-rancid-export> script).
Any values above that are a Host Group ACL will take either a single item or
list of Network Identifiers or Device Properties. See the L<ACL
Any values above that are a host group ACL will take either a single item or
a list of network identifiers or device properties. See the L<ACL
documentation|https://github.com/netdisco/netdisco/wiki/Configuration#access-control-lists>
wiki page for full details. We advise you to use the "C<host_groups>" setting
wiki page for full details. We advise you to use the C<host_groups> setting
and then refer to named entries in that, for example:
host_groups:
coredevices: '192.0.2.0/24'
edgedevices: '172.16.0.0/16'
grp-nxos: 'os:nx-os'
rancid:
groups:
core_devices: 'group:coredevices'
edge_devices: 'group:edgedevices'
vendormap:
cisco-nx: 'group:grp-nxos'
by_ip: 'any'
=head2 C<rancid_home>
Do not forget that rancid also needs configuring when adding a new group,
such as scheduling the group to run, adding it to F<rancid.conf>, setting up the
email config and creating the repository with C<rancid-cvs>.
The location to write RANCID Group configuration files into. A subdirectory
for each Group will be created.
=head2 C<rancid_conf>
The location where the rancid configuration (F<rancid.types.base> and
F<rancid.types.conf>) is installed. It will be used to check the existance
of device types before exporting the devices to the rancid configuration. if no match
is found the device will not be added to rancid.
=head2 C<rancid_cvsroot>
The location to write rancid group configuration files (F<router.db>) into. A
subdirectory for each group will be created.
=head2 C<down_age>
This should be the same or greater than the interval between regular discover
jobs on your network. Devices which have not been discovered within this time
will be marked as "C<down>" to RANCID.
will be marked as C<down> to rancid.
The format is any time interval known and understood by PostgreSQL, such as at
L<https://www.postgresql.org/docs/8.4/static/functions-datetime.html>.
L<https://www.postgresql.org/docs/10/static/functions-datetime.html>.
=head2 C<delimiter>
Set this to the delimiter character if needed to be different from the
default.
Set this to the delimiter character for your F<router.db> entries if needed to
be different from the default, the default is C<;>.
=head2 C<default_group>
Put devices into this group if they do not match any other groups defined.
=head2 C<excluded>
This dictionary defines a list of devices that you do not wish to export to
rancid configuration.
The value should be a L<Netdisco ACL|https://github.com/netdisco/netdisco/wiki/Configuration#access-control-lists>
to select devices in the Netdisco database.
=head2 C<groups>
This dictionary maps RANCID Group names with configuration which will match
This dictionary maps rancid group names with configuration which will match
devices in the Netdisco database.
The left hand side (key) should be the RANCID group name, the right hand side
The left hand side (key) should be the rancid group name, the right hand side
(value) should be a L<Netdisco
ACL|https://github.com/netdisco/netdisco/wiki/Configuration#access-control-lists>
to select devices in the Netdisco database.
=head2 C<vendormap>
If the device Vendor in Netdisco is not the same as the RANCID vendor script,
configure a mapping here.
If the device vendor in Netdisco is not the same as the rancid vendor script or
device type, configure a mapping here.
The left hand side (key) should be the RANCID vendor, the right hand side
The left hand side (key) should be the rancid device type, the right hand side
(value) should be a L<Netdisco
ACL|https://github.com/netdisco/netdisco/wiki/Configuration#access-control-lists>
to select devices in the Netdisco database.
Note that vendors might have a large array of operating systems which require
different rancid modules. Mapping operating systems to rancid device types is
a good solution to use the correct device type. Example:
host_groups:
grp-ciscosb: 'os:ros'
rancid:
vendormap:
cisco-sb: 'group:grp-ciscosb'
=head2 C<by_ip>
L<Netdisco
ACL|https://github.com/netdisco/netdisco/wiki/Configuration#access-control-lists>
to select devices which will be written to the RANCID config as an IP address,
instead of the DNS FQDN or SNMP host name.
to select devices which will be written to the rancid config as an IP address,
instead of the DNS FQDN or SNMP hostname.
=head2 C<by_hostname>
L<Netdisco
ACL|https://github.com/netdisco/netdisco/wiki/Configuration#access-control-lists>
to select devices which will have the unqualified host name written to the
RANCID config. This is done simply by stripping the C<domain_suffix>
to select devices which will have the unqualified hostname written to the
rancid config. This is done simply by stripping the C<domain_suffix>
configuration setting from the device FQDN.
=head1 SEE ALSO

View File

@@ -47,4 +47,3 @@ register_worker({ phase => 'main' }, sub {
});
true;

View File

@@ -17,9 +17,9 @@ register_worker({ phase => 'check' }, sub {
or return Status->error(sprintf "Unknown port name [%s] on device %s",
$job->port, $job->device);
my $vlan_reconfig_check = vlan_reconfig_check(vars->{'port'});
return Status->error("Cannot alter vlan: $vlan_reconfig_check")
if $vlan_reconfig_check;
my $port_reconfig_check = port_reconfig_check(vars->{'port'});
return Status->error("Cannot alter port: $port_reconfig_check")
if $port_reconfig_check;
return Status->done('PortControl is able to run');
});
@@ -56,7 +56,7 @@ sub _action {
my $rv = $snmp->set_i_up_admin($data, $iid);
if (!defined $rv) {
return Status->error(sprintf 'Failed to set [%s] up_admin to [%s] on $device: %s',
return Status->error(sprintf "Failed to set [%s] up_admin to [%s] on $device: %s",
$pn, $data, ($snmp->error || ''));
}

View File

@@ -19,9 +19,9 @@ register_worker({ phase => 'check' }, sub {
or return Status->error(sprintf "Unknown port name [%s] on device %s",
$job->port, $job->device);
my $vlan_reconfig_check = vlan_reconfig_check(vars->{'port'});
return Status->error("Cannot alter vlan: $vlan_reconfig_check")
if $vlan_reconfig_check;
my $port_reconfig_check = port_reconfig_check(vars->{'port'});
return Status->error("Cannot alter port: $port_reconfig_check")
if $port_reconfig_check;
return Status->error("No PoE service on port [$pn] on device $device")
unless vars->{'port'}->power;
@@ -40,7 +40,7 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
# snmp connect using rw community
my $snmp = App::Netdisco::Transport::SNMP->writer_for($device)
or return Status->defer("failed to connect to $device to update vlan");
or return Status->defer("failed to connect to $device to set power");
my $powerid = get_powerid($snmp, vars->{'port'})
or return Status->error("failed to get power ID for [$pn] from $device");

View File

@@ -18,13 +18,14 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
my ($device, $port, $extra) = map {$job->$_} qw/device port extra/;
$extra ||= 'interfaces'; my $class = undef;
($class, $extra) = split(/::([^:]+)$/, $extra);
if ($class and $extra) {
$class = 'SNMP::Info::'.$class;
}
else {
$extra = $class;
undef $class;
my @values = split /::/, $extra;
$extra = pop @values;
if (scalar(@values)) {
$class = "SNMP::Info";
foreach my $v (@values) {
last if ($v eq '');
$class = $class.'::'.$v;
}
}
my $i = App::Netdisco::Transport::SNMP->reader_for($device, $class);