diff --git a/.travis.yml b/.travis.yml index ebd46574..4f36d747 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ before_install: - mkdir ~/netdisco-mibs - cd ~/netdisco-mibs install: - - curl -sL https://github.com/netdisco/netdisco-mibs/releases/download/4.007/netdisco-mibs.tar.gz | tar --strip-components=1 -zxf - + - curl -sL https://github.com/netdisco/netdisco-mibs/releases/download/4.008/netdisco-mibs.tar.gz | tar --strip-components=1 -zxf - - cpanm --quiet --notest PkgConfig Test::CChecker Alien::zlib::Static Alien::OpenSSL::Static Alien::SNMP::MAXTC before_script: - 'cd ${TRAVIS_BUILD_DIR}' diff --git a/Changes b/Changes index 982efde5..d8190fda 100644 --- a/Changes +++ b/Changes @@ -6,7 +6,13 @@ Version 3.55 [ENHANCEMENTS] - * Increase capture of i_vlan on router interfaces in L3::Cisco + * #196 Support for powerconnect 8164F + * Refactor Layer3::Dell for better support of newer models + * Increase capture of i_vlan on router interfaces in L3::Cisco + * Factor out logic to determine serial number from ENTITY-MIB in Layer2 and + Layer3 to new method entity_derived_serial in Entity class and added new + method entity_derived_os_ver in Entity class to determine OS version + in a similar manner [BUG FIXES] diff --git a/lib/SNMP/Info.pm b/lib/SNMP/Info.pm index c312b633..aeb402a6 100644 --- a/lib/SNMP/Info.pm +++ b/lib/SNMP/Info.pm @@ -792,7 +792,7 @@ See documentation in L for details. =item SNMP::Info::Layer3::Dell -Subclass for Dell PowerConnect switches. D-Link, the IBM BladeCenter +Subclass for Dell PowerConnect switches. The IBM BladeCenter Gigabit Ethernet Switch Module and some Linksys switches also use this module based upon MIB support. diff --git a/lib/SNMP/Info/Layer3/Dell.pm b/lib/SNMP/Info/Layer3/Dell.pm index 8a05d533..51e3d84b 100644 --- a/lib/SNMP/Info/Layer3/Dell.pm +++ b/lib/SNMP/Info/Layer3/Dell.pm @@ -50,12 +50,13 @@ $VERSION = '3.54'; 'RADLAN-rlInterfaces' => 'rlIfNumOfLoopbackPorts', 'RADLAN-HWENVIROMENT' => 'rlEnvPhysicalDescription', 'Dell-Vendor-MIB' => 'productIdentificationVersion', + 'DELL-REF-MIB' => 'dell6224Switch', ); %GLOBALS = ( %SNMP::Info::Layer3::GLOBALS, %SNMP::Info::LLDP::GLOBALS, - 'os_ver' => 'productIdentificationVersion', + 'dell_os_ver' => 'productIdentificationVersion', 'dell_id_name' => 'productIdentificationDisplayName', ); @@ -115,16 +116,22 @@ sub model { my $dell = shift; my $name = $dell->dell_id_name(); - my $descr = $dell->description(); + my $id = $dell->id(); if ( defined $name and $name =~ m/(\d+)/ ) { return $1; } - # Don't have a vendor MIB for D-Link - else { - return $descr; + return unless defined $id; + + my $model = SNMP::translateObj($id); + + if (defined $model) { + $model =~ s/^dell//; + $model =~ s/Switch$//; + return $model; } + return $id; } sub vendor { @@ -139,19 +146,19 @@ sub os { return $dell->_vendor(); } -sub serial { +sub os_ver { my $dell = shift; - my $numbers = $dell->dell_serial_no(); - - foreach my $key ( keys %$numbers ) { - my $serial = $numbers->{$key}; - return $serial if ( defined $serial and $serial !~ /^\s*$/ ); - next; + my $entity_os = $dell->entity_derived_os_ver(); + if ( defined $entity_os and $entity_os !~ /^\s*$/ ){ + return $entity_os; } - # Last resort - return $dell->SUPER::serial(); + my $dell_os = $dell->dell_os_ver(); + if ( defined $dell_os and $dell_os !~ /^\s*$/ ) { + return $dell_os; + } + return; } # check all fans, and report overall status @@ -160,41 +167,74 @@ sub fan { my $fan = $dell->dell_fan_desc() || {}; my $state = $dell->dell_fan_state() || {}; - my @messages = (); + + if (scalar keys %$fan) { + my @messages = (); - foreach my $k (keys %$fan) { - next if $state->{$k} and $state->{$k} eq 'normal'; - push @messages, "$fan->{$k}: $state->{$k}"; - } + foreach my $k (keys %$fan) { + next if $state->{$k} and $state->{$k} eq 'normal'; + push @messages, "$fan->{$k}: $state->{$k}"; + } - push @messages, ((scalar keys %$fan). " fans OK") - if scalar @messages == 0; + push @messages, ((scalar keys %$fan). " fans OK") + if scalar @messages == 0; - return (join ", ", @messages); + return (join ", ", @messages); + } + return; } -sub _ps_status { - my ($dell, $unit) = @_; +sub ps1_type { + my $dell = shift; - my $status = 'unknown'; - return $status if !defined $unit; + my $src = $dell->dell_pwr_src() || {}; - my $desc = $dell->dell_pwr_desc() || {}; - my $state = $dell->dell_pwr_state() || {}; - - foreach my $k (keys %$desc) { - next unless $desc->{$k} and $desc->{$k} eq "ps1_unit$unit"; - return ($state->{$k} || $status); + foreach my $k (sort keys %$src) { + next unless $src->{$k}; + return $src->{$k}; } - - return $status; + return; } -sub ps1_type { return 'internalRedundant' } -sub ps2_type { return 'internalRedundant' } +sub ps2_type { + my $dell = shift; -sub ps1_status { return (shift)->_ps_status(1) } -sub ps2_status { return (shift)->_ps_status(2) } + my $src = $dell->dell_pwr_src() || {}; + + my $i = 0; + foreach my $k (sort keys %$src) { + $i++; + next unless $src->{$k} and $i == 2; + return $src->{$k}; + } + return; +} + +sub ps1_status { + my $dell = shift; + + my $status = $dell->dell_pwr_state() || {}; + + foreach my $k (sort keys %$status) { + next unless $status->{$k}; + return $status->{$k}; + } + return; +} + +sub ps2_status { + my $dell = shift; + + my $status = $dell->dell_pwr_state() || {}; + + my $i = 0; + foreach my $k (sort keys %$status) { + $i++; + next unless $status->{$k} and $i == 2; + return $status->{$k}; + } + return; +} sub interfaces { my $dell = shift; @@ -214,6 +254,23 @@ sub interfaces { return $i_descr; } +sub i_duplex { + my $dell = shift; + my $partial = shift; + + my $dell_duplex = $dell->dell_duplex($partial) || {}; + + my %i_duplex; + foreach my $if ( keys %$dell_duplex ) { + my $duplex = $dell_duplex->{$if}; + next unless defined $duplex; + next if $duplex eq 'unknown'; + + $i_duplex{$if} = $duplex; + } + return \%i_duplex; +} + sub i_duplex_admin { my $dell = shift; my $partial = shift; @@ -226,11 +283,11 @@ sub i_duplex_admin { foreach my $if ( keys %$interfaces ) { my $duplex = $dell_duplex->{$if}; next unless defined $duplex; - my $auto = $dell_auto->{$if} || 'false'; + my $auto = $dell_auto->{$if} || 'disabled'; - $duplex = 'half' if ( $duplex =~ /half/i and $auto =~ /false/i ); - $duplex = 'full' if ( $duplex =~ /half/i and $auto =~ /false/i ); - $duplex = 'auto' if $auto =~ /true/i; + $duplex = 'half' if ( $duplex eq 'half' and $auto eq 'disabled' ); + $duplex = 'full' if ( $duplex eq 'full' and $auto eq 'disabled' ); + $duplex = 'auto' if $auto eq 'enabled'; $i_duplex_admin{$if} = $duplex; } return \%i_duplex_admin; @@ -242,7 +299,6 @@ sub _vendor { my $id = $dell->id() || 'undef'; my %oidmap = ( 2 => 'ibm', - 171 => 'dlink', 674 => 'dell', 3955 => 'linksys', ); @@ -252,7 +308,7 @@ sub _vendor { return $oidmap{$id}; } else { - return 'dlink'; + return $id; } } @@ -449,6 +505,10 @@ Returns the map between SNMP Interface Identifier (iid) and physical port name. Uses name if available instead of description since descriptions are sometimes not unique. +=item $dell->i_duplex() + +Returns reference to map of IIDs to current link duplex. + =item $dell->i_duplex_admin() Returns reference to hash of iid to current link administrative duplex diff --git a/xt/lib/Test/SNMP/Info/Layer3/Dell.pm b/xt/lib/Test/SNMP/Info/Layer3/Dell.pm index 21036ac9..30ae649d 100644 --- a/xt/lib/Test/SNMP/Info/Layer3/Dell.pm +++ b/xt/lib/Test/SNMP/Info/Layer3/Dell.pm @@ -33,28 +33,292 @@ use Test::Class::Most parent => 'My::Test::Class'; use SNMP::Info::Layer3::Dell; -# Remove this startup override once we have full method coverage -sub startup : Tests(startup => 1) { - my $test = shift; - $test->SUPER::startup(); - - $test->todo_methods(1); -} - sub setup : Tests(setup) { my $test = shift; $test->SUPER::setup; # Start with a common cache that will serve most tests my $cache_data = { - '_layers' => 3, + '_layers' => 4, '_description' => 'Powerconnect 8024F, 3.1.4.5, VxWorks 6.5', - # DELL-WLAN-MIB::powerConnect.3024 - '_id' => '.1.3.6.1.4.1.674.10895.3024', - 'store' => {}, + # DELL-REF-MIB::dell8024FSwitch + '_id' => '.1.3.6.1.4.1.674.10895.3024', + '_i_description' => 1, + '_i_name' => 1, + + # These don't exist in the 8024F, but pretend they do to simplify fan, + # ps*, and duplex_admin test code, there is no equivalent in newer models + '_dell_fan_desc' => 1, + '_dell_fan_state' => 1, + '_dell_pwr_src' => 1, + '_dell_pwr_state' => 1, + '_dell_pwr_desc' => 1, + '_dell_duplex' => 1, + '_dell_duplex_admin' => 1, + '_dell_auto' => 1, + + # ENTITY-MIB used for coverage of entity_derived_os_ver in os_ver + '_e_parent' => 1, + '_e_class' => 1, + 'store' => { + 'i_description' => { + 1 => 'Ethernet Interface', + 2 => 'Ethernet Interface', + 3 => 'Ethernet Interface' + }, + 'i_name' => {1 => 'g1', 2 => 'g2'}, + 'dell_fan_desc' => {67109249 => 'fan1', 67109250 => 'fan2'}, + 'dell_fan_state' => {67109249 => 'normal', 67109250 => 'warning'}, + 'dell_pwr_src' => {67109185 => 'ac', 67109186 => 'unknown'}, + 'dell_pwr_state' => {67109185 => 'normal', 67109186 => 'notPresent'}, + 'dell_pwr_desc' => {67109185 => 'ps1', 67109186 => 'ps2'}, + 'dell_duplex' => {1 => 'full', 2 => 'half', 3 => 'unknown'}, + 'dell_duplex_admin' => {1 => 'full', 2 => 'half', 3 => 'none'}, + 'dell_auto' => {1 => 'disabled', 2 => 'disabled', 3 => 'enabled'}, + 'e_parent' => {1 => 0, 2 => 1, 3 => 2, 54 => 1, 55 => 54}, + 'e_class' => { + 1 => 'stack', + 2 => 'chassis', + 3 => 'module', + 54 => 'chassis', + 55 => 'module' + }, + }, }; $test->{info}->cache($cache_data); } +# This class is also used for devices that don't have the Dell IANA private +# enterprise number (674). If we ever create a new class to cover these +# devices these tests should serve as a reminder to remove the applicable +# from this class +sub device_type : Tests(+2) { + my $test = shift; + $test->SUPER::device_type(); + + # IBM BladeCenter 4-Port GB Ethernet Switch Module + my $cache_data = { + '_layers' => 2, + '_description' => 'IBM Gigabit Ethernet Switch Module', + '_id' => '.1.3.6.1.4.1.2' + }; + $test->{info}->cache($cache_data); + is($test->{info}->device_type, + 'SNMP::Info::Layer3::Dell', + 'IBM BladeCenter 4-Port GB Ethernet Switch Module'); + $test->{info}->clear_cache(); + + # Linksys 2024/2048 + $cache_data = { + '_layers' => 2, + '_description' => '48-Port 10/100/1000 Gigabit Switch w/WebView', + '_id' => '.1.3.6.1.4.1.3955.6.1.2024.1' + }; + $test->{info}->cache($cache_data); + is($test->{info}->device_type, + 'SNMP::Info::Layer3::Dell', 'Linksys 2024/2048'); + $test->{info}->clear_cache(); +} + +sub model : Tests(5) { + my $test = shift; + + can_ok($test->{info}, 'model'); + + is($test->{info}->model(), + '8024F', q(Model has expected value using 'sysObjectID')); + + # Non Dell sysObjectID's won't resolve and should return a partially + # resolved OID + $test->{info}{_id} = '.1.3.6.1.4.1.3955.6.1.2024.1'; + is($test->{info}->model(), + 'enterprises.3955.6.1.2024.1', + q(Non Dell returns partially resolved 'sysObjectID')); + + # On older switches, sysObjectID will not resolve, this is from + # Dell-Vendor-MIB::productIdentificationDisplayName which is not populated + # on newer models such as the dell8024FSwitch which snmp data is in the + # test setup method to populate the default cache + $test->{info}{_dell_id_name} = 'PowerConnect 5324'; + is($test->{info}->model(), + '5324', q(Older 'productIdentificationDisplayName' returns expected model)); + + $test->{info}->clear_cache(); + is($test->{info}->model(), undef, q(No id returns undef model)); +} + +sub vendor : Tests(5) { + my $test = shift; + + can_ok($test->{info}, 'vendor'); + is($test->{info}->vendor(), 'dell', q(Vendor returns 'dell')); + + $test->{info}{_id} = '.1.3.6.1.4.1.3955.6.1.2024.1'; + is($test->{info}->vendor(), 'linksys', q(Vendor returns 'linksys')); + + $test->{info}{_id} = '.1.3.6.1.4.1.2'; + is($test->{info}->vendor(), 'ibm', q(Vendor returns 'ibm')); + + $test->{info}->clear_cache(); + is($test->{info}->vendor(), + 'undef', q(No 'sysObjectID' returns 'undef' string)); +} + +sub os : Tests(5) { + my $test = shift; + + can_ok($test->{info}, 'os'); + is($test->{info}->os(), 'dell', q(OS returns 'dell')); + + $test->{info}{_id} = '.1.3.6.1.4.1.3955.6.1.2024.1'; + is($test->{info}->os(), 'linksys', q(OS returns 'linksys')); + + $test->{info}{_id} = '.1.3.6.1.4.1.2'; + is($test->{info}->os(), 'ibm', q(OS returns 'ibm')); + + $test->{info}->clear_cache(); + is($test->{info}->os(), 'undef', q(No 'sysObjectID' returns 'undef' string)); +} + +sub os_ver : Tests(4) { + my $test = shift; + + can_ok($test->{info}, 'os_ver'); + + # The call to SUPER::serial() will use the entity_derived_serial method + # which uses a partial fetch for e_serial which ignores the cache + # and reloads data therefore we must use the mocked session. + my $data + = {'ENTITY-MIB::entPhysicalSoftwareRev' => + {1 => undef, 2 => '5.1.2.3', 3 => undef, 54 => '6.1.2.3', 55 => undef}, + }; + $test->{info}{sess}{Data} = $data; + + is($test->{info}->os_ver(), + '5.1.2.3', q(OS version has expected value using 'ENTITY-MIB')); + + $test->{info}->clear_cache(); + + # Manually populate cache entry + $test->{info}{_dell_os_ver} = '1.0.0.45'; + is($test->{info}->os_ver(), + '1.0.0.45', + q(OS version has expected value using 'productIdentificationVersion')); + + $test->{info}->clear_cache(); + is($test->{info}->os_ver(), undef, q(No data retruns undef)); +} + +sub fan : Tests(4) { + my $test = shift; + + can_ok($test->{info}, 'fan'); + is($test->{info}->fan(), 'fan2: warning', q(Returns fan not in normal state)); + + # All fans normal returns a distinct string for this class + $test->{info}{store}{dell_fan_state} + = {67109249 => 'normal', 67109250 => 'normal'}; + is($test->{info}->fan(), '2 fans OK', q(All fans ok)); + + $test->{info}->clear_cache(); + is($test->{info}->fan(), undef, q(No fan data returns undef)); +} + +sub ps1_type : Tests(3) { + my $test = shift; + + can_ok($test->{info}, 'ps1_type'); + is($test->{info}->ps1_type(), 'ac'); + + $test->{info}->clear_cache(); + is($test->{info}->ps1_type(), + undef, q(No power supply data returns type undef)); +} + +sub ps2_type : Tests(3) { + my $test = shift; + + can_ok($test->{info}, 'ps2_type'); + is($test->{info}->ps2_type(), 'unknown'); + + $test->{info}->clear_cache(); + is($test->{info}->ps2_type(), + undef, q(No power supply data returns type undef)); +} + +sub ps1_status : Tests(3) { + my $test = shift; + + can_ok($test->{info}, 'ps1_status'); + is($test->{info}->ps1_status(), 'normal'); + + $test->{info}->clear_cache(); + is($test->{info}->ps1_status(), + undef, q(No power supply data returns status undef)); +} + +sub ps2_status : Tests(3) { + my $test = shift; + + can_ok($test->{info}, 'ps2_status'); + is($test->{info}->ps2_status(), 'notPresent'); + + $test->{info}->clear_cache(); + is($test->{info}->ps2_status(), + undef, q(No power supply data returns status undef)); +} + +sub interfaces : Tests(3) { + my $test = shift; + + can_ok($test->{info}, 'interfaces'); + + my $expected = {1 => 'g1', 2 => 'g2', 3 => 'Ethernet Interface'}; + + cmp_deeply($test->{info}->interfaces(), + $expected, q(Interfaces have expected values)); + + $test->{info}->clear_cache(); + cmp_deeply($test->{info}->interfaces(), {}, q(No data returns empty hash)); +} + +sub i_duplex : Tests(3) { + my $test = shift; + + can_ok($test->{info}, 'i_duplex'); + + my $expected = {1 => 'full', 2 => 'half'}; + + cmp_deeply($test->{info}->i_duplex(), + $expected, q(Interfaces have expected duplex values)); + + $test->{info}->clear_cache(); + cmp_deeply($test->{info}->i_duplex(), + {}, q(No duplex admin data returns empty hash)); +} + +sub i_duplex_admin : Tests(3) { + my $test = shift; + + can_ok($test->{info}, 'i_duplex_admin'); + + my $expected = {1 => 'full', 2 => 'half', 3 => 'auto'}; + + cmp_deeply($test->{info}->i_duplex_admin(), + $expected, q(Interfaces have expected duplex admin values)); + + $test->{info}->clear_cache(); + cmp_deeply($test->{info}->i_duplex_admin(), + {}, q(No duplex admin data returns empty hash)); +} + +sub qb_fdb_index : Tests(2) { + my $test = shift; + + can_ok($test->{info}, 'qb_fdb_index'); + is($test->{info}->qb_fdb_index(), undef); + +} + 1;