diff --git a/Netdisco/MANIFEST b/Netdisco/MANIFEST index eec6e513..dd5a459f 100644 --- a/Netdisco/MANIFEST +++ b/Netdisco/MANIFEST @@ -72,8 +72,6 @@ lib/App/Netdisco/DB/Result/Virtual/ActiveNodeWithAge.pm lib/App/Netdisco/DB/Result/Virtual/ApRadioChannelPower.pm lib/App/Netdisco/DB/Result/Virtual/CidrIps.pm lib/App/Netdisco/DB/Result/Virtual/DeviceLinks.pm -lib/App/Netdisco/DB/Result/Virtual/DevicePortVlanNative.pm -lib/App/Netdisco/DB/Result/Virtual/DevicePortVlanTagged.pm lib/App/Netdisco/DB/Result/Virtual/DuplexMismatch.pm lib/App/Netdisco/DB/Result/Virtual/NodeWithAge.pm lib/App/Netdisco/DB/Result/Virtual/PhonesDiscovered.pm @@ -119,6 +117,9 @@ lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-28-29-PostgreSQL.sql lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-29-30-PostgreSQL.sql lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-3-4-PostgreSQL.sql lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-30-31-PostgreSQL.sql +lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-31-32-PostgreSQL.sql +lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-32-33-PostgreSQL.sql +lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-33-34-PostgreSQL.sql lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-4-5-PostgreSQL.sql lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-5-6-PostgreSQL.sql lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-6-7-PostgreSQL.sql diff --git a/Netdisco/META.yml b/Netdisco/META.yml index 043af0ca..008a553d 100644 --- a/Netdisco/META.yml +++ b/Netdisco/META.yml @@ -71,4 +71,4 @@ resources: homepage: http://netdisco.org/ license: http://opensource.org/licenses/bsd-license.php repository: git://git.code.sf.net/p/netdisco/netdisco-ng -version: 2.021000 +version: 2.021000_004 diff --git a/Netdisco/lib/App/Netdisco.pm b/Netdisco/lib/App/Netdisco.pm index b2084b29..7020925c 100644 --- a/Netdisco/lib/App/Netdisco.pm +++ b/Netdisco/lib/App/Netdisco.pm @@ -7,7 +7,7 @@ use 5.010_000; use File::ShareDir 'dist_dir'; use Path::Class; -our $VERSION = '2.021000'; +our $VERSION = '2.021000_004'; BEGIN { if (not ($ENV{DANCER_APPDIR} || '') diff --git a/Netdisco/lib/App/Netdisco/Core/Discover.pm b/Netdisco/lib/App/Netdisco/Core/Discover.pm index c5ee4003..8ac82e6c 100644 --- a/Netdisco/lib/App/Netdisco/Core/Discover.pm +++ b/Netdisco/lib/App/Netdisco/Core/Discover.pm @@ -206,6 +206,7 @@ sub store_interfaces { my $i_stp_state = $snmp->i_stp_state; my $i_vlan = $snmp->i_vlan; my $i_lastchange = $snmp->i_lastchange; + my $agg_ports = $snmp->agg_ports; # clear the cached uptime and get a new one my $dev_uptime = $snmp->load_uptime; @@ -278,10 +279,22 @@ sub store_interfaces { type => $i_type->{$entry}, vlan => $i_vlan->{$entry}, pvid => $i_vlan->{$entry}, + is_master => 'false', + slave_of => undef, lastchange => $lc, }; } + # 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; + my $master = $interfaces->{ $agg_ports->{$sidx} } or next; + next unless exists $interfaces{$slave} and exists $interfaces{$master}; + + $interfaces{$slave}->{slave_of} = $master; + $interfaces{$master}->{is_master} = 'true'; + } + schema('netdisco')->resultset('DevicePort')->txn_do_locked(sub { my $gone = $device->ports->delete({keep_nodes => 1}); debug sprintf ' [%s] interfaces - removed %d interfaces', @@ -695,18 +708,19 @@ sub store_neighbors { # IP Phone and WAP detection type fixup if (defined $remote_type) { - my $phone_flag = grep {/phone/i} @$remote_cap; - my $ap_flag = grep {/wlanAccessPoint/} @$remote_cap; - if ($phone_flag or $remote_type =~ m/(mitel.5\d{3})/i) { - $remote_type = 'IP Phone: '. $remote_type - if $remote_type !~ /ip phone/i; - } - elsif ($ap_flag) { - $remote_type = 'AP: '. $remote_type; - } - else { - $remote_type ||= ''; - } + my $phone_flag = grep {/phone/i} @$remote_cap; + my $ap_flag = grep {/wlanAccessPoint/} @$remote_cap; + + if ($phone_flag or $remote_type =~ m/(mitel.5\d{3})/i) { + $remote_type = 'IP Phone: '. $remote_type + if $remote_type !~ /ip phone/i; + } + elsif ($ap_flag) { + $remote_type = 'AP: '. $remote_type; + } + else { + $remote_type ||= ''; + } } # hack for devices seeing multiple neighbors on the port @@ -719,7 +733,7 @@ sub store_neighbors { foreach my $n (@$remote_ip) { debug sprintf ' [%s] neigh - adding neighbor %s, type [%s], on %s to discovery queue', - $device->ip, $n, $remote_type, $port; + $device->ip, $n, ($remote_type || ''), $port; push @to_discover, [$n, $remote_type]; } } @@ -733,7 +747,7 @@ sub store_neighbors { if (wantarray) { debug sprintf ' [%s] neigh - adding neighbor %s, type [%s], on %s to discovery queue', - $device->ip, $remote_ip, $remote_type, $port; + $device->ip, $remote_ip, ($remote_type || ''), $port; push @to_discover, [$remote_ip, $remote_type]; } @@ -773,6 +787,26 @@ sub store_neighbors { is_uplink => \"true", manual_topo => \"false", }); + + if (defined $portrow->slave_of and + my $master = schema('netdisco')->resultset('DevicePort') + ->single({ip => $device->ip, port => $portrow->slave_of})) { + + if (not ($portrow->is_master or defined $master->slave_of)) { + # TODO needs refactoring - this is quite expensive + my $peer = schema('netdisco')->resultset('DevicePort')->find({ + ip => $portrow->neighbor->ip, + port => $portrow->remote_port, + }) if $portrow->neighbor; + + $master->update({ + remote_ip => ($peer ? $peer->ip : $remote_ip), + remote_port => ($peer ? $peer->slave_of : undef ), + is_uplink => \"true", + manual_topo => \"false", + }); + } + } } return @to_discover; @@ -860,7 +894,7 @@ sub discover_new_neighbors { if (not is_discoverable($device, $remote_type)) { debug sprintf ' queue - %s, type [%s] excluded by discover_* config', - $ip, $remote_type; + $ip, ($remote_type || ''); next; } diff --git a/Netdisco/lib/App/Netdisco/Core/Macsuck.pm b/Netdisco/lib/App/Netdisco/Core/Macsuck.pm index 1c082b7d..7426ce9e 100644 --- a/Netdisco/lib/App/Netdisco/Core/Macsuck.pm +++ b/Netdisco/lib/App/Netdisco/Core/Macsuck.pm @@ -57,8 +57,8 @@ sub do_macsuck { my $ip = $device->ip; # would be possible just to use now() on updated records, but by using this - # same value for them all, we _can_ if we want add a job at the end to - # select and do something with the updated set (no reason to yet, though) + # same value for them all, we can if we want add a job at the end to + # select and do something with the updated set (see set archive, below) my $now = 'to_timestamp('. (join '.', gettimeofday) .')'; my $total_nodes = 0; @@ -98,8 +98,10 @@ sub do_macsuck { debug sprintf ' [%s] macsuck - port %s vlan %s : %s nodes', $ip, $port, $vlan, scalar keys %{ $fwtable->{$vlan}->{$port} }; - # make sure this port is UP in netdisco - $device_ports->{$port}->update({up_admin => 'up', up => 'up'}); + # make sure this port is UP in netdisco (unless it's a lag master, + # because we can still see nodes without a functioning aggregate) + $device_ports->{$port}->update({up_admin => 'up', up => 'up'}) + if not $device_ports->{$port}->is_master; foreach my $mac (keys %{ $fwtable->{$vlan}->{$port} }) { @@ -113,8 +115,18 @@ sub do_macsuck { } } - debug sprintf ' [%s] macsuck - %s forwarding table entries', + debug sprintf ' [%s] macsuck - %s updated forwarding table entries', $ip, $total_nodes; + + # a use for $now ... need to archive dissapeared nodes + my $archived = schema('netdisco')->resultset('Node')->search({ + switch => $ip, + time_last => { '<' => \$now }, + })->update({ active => \'false' }); + + debug sprintf ' [%s] macsuck - removed %s fwd table entries to archive', + $ip, $archived; + $device->update({last_macsuck => \$now}); } @@ -324,14 +336,6 @@ sub _walk_fwtable { next; } - # TODO: add proper port channel support! - if ($port =~ m/port.channel/i) { - debug sprintf - ' [%s] macsuck %s - port %s is LAG member - skipping.', - $device->ip, $mac, $port; - next; - } - # this uses the cached $ports resultset to limit hits on the db my $device_port = $device_ports->{$port}; @@ -389,8 +393,14 @@ sub _walk_fwtable { next unless setting('macsuck_bleed'); } + # possibly move node to lag master + if (defined $device_port->slave_of + and exists $device_ports->{$device_port->slave_of}) { + $port = $device_port->slave_of; + $device_ports->{$port}->update({is_uplink => \'true'}); + } + my $vlan = $fw_vlan->{$idx} || $comm_vlan || '0'; - ++$cache->{$vlan}->{$port}->{$mac}; } diff --git a/Netdisco/lib/App/Netdisco/DB.pm b/Netdisco/lib/App/Netdisco/DB.pm index 7522a478..f35590f8 100644 --- a/Netdisco/lib/App/Netdisco/DB.pm +++ b/Netdisco/lib/App/Netdisco/DB.pm @@ -10,7 +10,7 @@ __PACKAGE__->load_namespaces( default_resultset_class => 'ResultSet', ); -our $VERSION = 33; # schema version used for upgrades, keep as integer +our $VERSION = 34; # schema version used for upgrades, keep as integer use Path::Class; use File::Basename; diff --git a/Netdisco/lib/App/Netdisco/DB/Result/DevicePort.pm b/Netdisco/lib/App/Netdisco/DB/Result/DevicePort.pm index 884f8796..69c62c32 100644 --- a/Netdisco/lib/App/Netdisco/DB/Result/DevicePort.pm +++ b/Netdisco/lib/App/Netdisco/DB/Result/DevicePort.pm @@ -53,6 +53,10 @@ __PACKAGE__->add_columns( { data_type => "text", is_nullable => 1 }, "remote_id", { data_type => "text", is_nullable => 1 }, + "is_master", + { data_type => "bool", is_nullable => 0, default_value => \"false" }, + "slave_of", + { data_type => "text", is_nullable => 1 }, "manual_topo", { data_type => "bool", is_nullable => 0, default_value => \"false" }, "is_uplink", @@ -179,6 +183,22 @@ __PACKAGE__->might_have( } ); +=head2 agg_master + +Returns another row from the C table if this port is slave +to another in a link aggregate. + +=cut + +__PACKAGE__->belongs_to( + agg_master => 'App::Netdisco::DB::Result::DevicePort', { + 'foreign.ip' => 'self.ip', + 'foreign.port' => 'self.slave_of', + }, { + join_type => 'LEFT', + } +); + =head2 neighbor_alias When a device port has an attached neighbor device, this relationship will @@ -231,7 +251,7 @@ __PACKAGE__->belongs_to( oui => 'App::Netdisco::DB::Result::Oui', =head1 ADDITIONAL METHODS -=head2 +=head2 neighbor Returns the Device entry for the neighbour Device on the given port. diff --git a/Netdisco/lib/App/Netdisco/DB/ResultSet/DevicePort.pm b/Netdisco/lib/App/Netdisco/DB/ResultSet/DevicePort.pm index 46429de0..71bdce76 100644 --- a/Netdisco/lib/App/Netdisco/DB/ResultSet/DevicePort.pm +++ b/Netdisco/lib/App/Netdisco/DB/ResultSet/DevicePort.pm @@ -64,9 +64,9 @@ sub with_is_free { ->search({}, { '+columns' => { is_free => - \["up != 'up' and " + \["me.up != 'up' and " ."age(now(), to_timestamp(extract(epoch from device.last_discover) " - ."- (device.uptime - lastchange)/100)) " + ."- (device.uptime - me.lastchange)/100)) " ."> ?::interval", [{} => $interval]] }, join => 'device', @@ -93,11 +93,11 @@ sub only_free_ports { ->search_rs($cond, $attrs) ->search( { - 'up' => { '!=' => 'up' }, + 'me.up' => { '!=' => 'up' }, },{ where => \["age(now(), to_timestamp(extract(epoch from device.last_discover) " - ."- (device.uptime - lastchange)/100)) " + ."- (device.uptime - me.lastchange)/100)) " ."> ?::interval", [{} => $interval]], join => 'device' }, diff --git a/Netdisco/lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-33-34-PostgreSQL.sql b/Netdisco/lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-33-34-PostgreSQL.sql new file mode 100644 index 00000000..8168f4bd --- /dev/null +++ b/Netdisco/lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-33-34-PostgreSQL.sql @@ -0,0 +1,7 @@ +BEGIN; + +ALTER TABLE device_port DROP COLUMN is_uplink_admin; +ALTER TABLE device_port ADD COLUMN "slave_of" text; +ALTER TABLE device_port ADD COLUMN "is_master" bool DEFAULT false NOT NULL; + +COMMIT; diff --git a/Netdisco/lib/App/Netdisco/Web/Plugin/Device/Ports.pm b/Netdisco/lib/App/Netdisco/Web/Plugin/Device/Ports.pm index b41cd28a..15a63233 100644 --- a/Netdisco/lib/App/Netdisco/Web/Plugin/Device/Ports.pm +++ b/Netdisco/lib/App/Netdisco/Web/Plugin/Device/Ports.pm @@ -63,7 +63,12 @@ get '/ajax/content/device/ports' => require_login sub { if (($prefer eq 'port') or not $prefer and $set->search({'me.port' => $f})->count) { - $set = $set->search({'me.port' => $f}); + $set = $set->search({ + -or => [ + 'me.port' => $f, + 'me.slave_of' => $f, + ], + }); } else { $set = $set->search({'me.name' => $f}); @@ -108,6 +113,13 @@ get '/ajax/content/device/ports' => require_login sub { $set = $set->search({-or => \@combi}); } + # get aggregate master status + $set = $set->search({}, { + 'join' => 'agg_master', + '+select' => [qw/agg_master.up_admin agg_master.up/], + '+as' => [qw/agg_master_up_admin agg_master_up/], + }); + # make sure query asks for formatted timestamps when needed $set = $set->with_times if param('c_lastchange'); diff --git a/Netdisco/share/config.yml b/Netdisco/share/config.yml index 4934d30f..a108286a 100644 --- a/Netdisco/share/config.yml +++ b/Netdisco/share/config.yml @@ -16,7 +16,7 @@ logger_format: '[%P] %L @%D> %m' # WEB FRONTEND # ------------ -domain_suffix: '' +domain_suffix: "" no_auth: false suggest_guest: false trust_remote_user: false @@ -24,9 +24,9 @@ trust_x_remote_user: false #ldap: # servers: [] # user_string: 'MYDOMAIN\%USER%' -# base: '' -# proxy_user: '' -# proxy_pass: '' +# base: "" +# proxy_user: "" +# proxy_pass: "" # opts: # debug: 3 # tls_opts: {} @@ -80,7 +80,7 @@ extra_web_plugins: [] community: ['public'] community_rw: ['private'] snmp_auth: [] -get_community: '' +get_community: "" bulkwalk_off: false bulkwalk_no: [] bulkwalk_repeaters: 20 @@ -117,7 +117,8 @@ ignore_interfaces: - 'StackPort' - 'Control Plane Interface' - 'SPAN (S|R)P Interface' - - 'StackSub' + - 'StackSub-.*' + - 'StackPort\d+' - 'netflow' - 'Vlan\d+-mpls layer' - 'BRI\S+-Bearer Channel' diff --git a/Netdisco/share/public/css/netdisco.css b/Netdisco/share/public/css/netdisco.css index bd49e849..ffc63734 100644 --- a/Netdisco/share/public/css/netdisco.css +++ b/Netdisco/share/public/css/netdisco.css @@ -158,9 +158,14 @@ div.content > div.tab-content table.nd_floatinghead thead { /* nudge cell content to the right when port_control controls are enabled */ .nd_editable-cell > .nd_this-port-only { margin-left: 18px; + margin-right: 18px; } +.nd_editable-cell > .nd_port-only-first { + margin-left: 9px; +} + .nd_editable-cell > .nd_editable-cell-content { - margin-left: 18px; + margin-right: 18px; } .table .nd_nudge-for-icon { padding-left: 25px; @@ -177,6 +182,10 @@ div.content > div.tab-content table.nd_floatinghead thead { text-align: center; } +td.nd_devport-icon i { + line-height: 18px; +} + /* undo nd_center-cell when in a modial dialog (which lives in table cell) */ .table .nd_center-cell .modal-body { text-align: left; @@ -261,7 +270,7 @@ td > form.nd_inline-form { cursor: pointer; color: black; float: left; - display: none; + visibility: hidden; margin-top: 3px; } .nd_log-icon:hover, .nd_log-icon:focus { diff --git a/Netdisco/share/public/javascripts/netdisco_portcontrol.js b/Netdisco/share/public/javascripts/netdisco_portcontrol.js index dd810a27..9426d46e 100644 --- a/Netdisco/share/public/javascripts/netdisco_portcontrol.js +++ b/Netdisco/share/public/javascripts/netdisco_portcontrol.js @@ -92,14 +92,16 @@ $(document).ready(function() { // toggle visibility of port up/down and edit controls $('.tab-content').on('mouseenter', '.nd_editable-cell', function() { - $(this).children('.nd_hand-icon,.nd_log-icon').show(); + $(this).children('.nd_hand-icon').show(); + $(this).children('.nd_log-icon').css('visibility', 'visible'); if (! $(this).is(':focus')) { $(this).children('.nd_edit-icon').show(); // ports $(this).siblings('td').find('.nd_device-details-edit').show(); // details } }); $('.tab-content').on('mouseleave', '.nd_editable-cell', function() { - $(this).children('.nd_hand-icon,.nd_log-icon').hide(); + $(this).children('.nd_hand-icon').hide(); + $(this).children('.nd_log-icon').css('visibility', 'hidden'); if (! $(this).is(':focus')) { $(this).children('.nd_edit-icon').hide(); // ports $(this).siblings('td').find('.nd_device-details-edit').hide(); // details diff --git a/Netdisco/share/views/ajax/device/ports.tt b/Netdisco/share/views/ajax/device/ports.tt index f314ec87..64e41860 100644 --- a/Netdisco/share/views/ajax/device/ports.tt +++ b/Netdisco/share/views/ajax/device/ports.tt @@ -17,7 +17,7 @@ [% FOREACH row IN results %] - + [% IF row.up_admin != 'up' %] [% ELSIF row.stp == 'blocking' %] @@ -29,6 +29,15 @@ [% ELSE %] [% END %] + [% IF row.slave_of %]
+ [% IF row.get_column('agg_master_up_admin') != 'up' %] + + [% ELSIF row.get_column('agg_master_up') == 'up' %] + + [% ELSE %] + + [% END %] + [% END %] [% FOREACH config IN settings._extra_device_port_cols %] @@ -66,10 +75,18 @@ [% ELSE %] [% END %] - - [% row.port | html_entity %] - + [% IF row.is_master %] +   + [% END %] + [% row.port | html_entity %] + [% IF row.slave_of %]
+ + [% row.slave_of | html_entity %] + [% END %] + [% END %] [% FOREACH config IN settings._extra_device_port_cols %] @@ -216,27 +233,36 @@ [% IF params.c_neighbors AND (row.remote_ip OR row.is_uplink) %] [% IF row.neighbor %] - - - [% row.neighbor.dns.remove(settings.domain_suffix) || row.neighbor.ip | html_entity %] - ([% row.remote_port | html_entity %] - [% ' id: '_ row.remote_id IF row.remote_id %] - [% ' type: '_ row.remote_type IF row.remote_type %]) - [% ELSIF row.remote_ip AND row.remote_port %] + [% IF row.remote_type AND row.remote_type.match('(?i)ip.phone') %]   [% ELSIF row.remote_type AND row.remote_type.match('(cisco\s+AIR-[L|C]?AP|-K9W8-|^AP:\s)') %]   - [% ELSE %] -   [% END %] - - [% row.remote_ip | html_entity %] (port: [% row.remote_port | html_entity %] - [% ' id: '_ row.remote_id IF row.remote_id %] - [% ' type: '_ row.remote_type IF row.remote_type %]) + + [% row.neighbor.dns.remove(settings.domain_suffix) || row.neighbor.ip | html_entity %] + [% ' - ' IF row.remote_port %][% row.remote_port | html_entity %]
+ [% IF params.neigh_id and (row.remote_id or row.remote_type) %] + ([% 'id: '_ row.remote_id IF row.remote_id %] + [% ' type: '_ row.remote_type IF row.remote_type %])
+ [% END %] + [% ELSIF row.remote_ip %] +   + [% IF row.remote_type AND row.remote_type.match('(?i)ip.phone') %] +   + [% ELSIF row.remote_type AND row.remote_type.match('(cisco\s+AIR-[L|C]?AP|-K9W8-|^AP:\s)') %] +   + [% END %] + + [% row.remote_ip | html_entity %] + [% ' - ' IF row.remote_port %][% row.remote_port | html_entity %]
+ [% IF params.neigh_id and (row.remote_id or row.remote_type) %] + ([% 'id: '_ row.remote_id IF row.remote_id %] + [% ' type: '_ row.remote_type IF row.remote_type %])
+ [% END %] [% ELSE %] -   (possible uplink) +   (possible uplink) [% END %] [% END %] [% IF params.c_nodes %] diff --git a/Netdisco/share/views/sidebar/device/ports.tt b/Netdisco/share/views/sidebar/device/ports.tt index bf15a56e..eb789ae1 100644 --- a/Netdisco/share/views/sidebar/device/ports.tt +++ b/Netdisco/share/views/sidebar/device/ports.tt @@ -43,6 +43,7 @@
  •   IP Phone
  •   Wireless Client
  •   Archived Data
  • +
  •   Link Aggregate
  • [% IF user_has_role('port_control') %]
  •   Click "Update View"
  • [% END %] @@ -104,6 +105,13 @@ +
  • + +