455 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			455 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/perl
 | |
| #
 | |
| # test_class_mocked.pl
 | |
| #
 | |
| # Copyright (c) 2012 Eric Miller
 | |
| # All rights reserved.
 | |
| #
 | |
| # Redistribution and use in source and binary forms, with or without
 | |
| # modification, are permitted provided that the following conditions are met:
 | |
| #
 | |
| #     * Redistributions of source code must retain the above copyright notice,
 | |
| #       this list of conditions and the following disclaimer.
 | |
| #     * Redistributions in binary form must reproduce the above copyright
 | |
| #       notice, this list of conditions and the following disclaimer in the
 | |
| #       documentation and/or other materials provided with the distribution.
 | |
| #     * Neither the name of the University of California, Santa Cruz nor the
 | |
| #       names of its contributors may be used to endorse or promote products
 | |
| #       derived from this software without specific prior written permission.
 | |
| #
 | |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 | |
| # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 | |
| # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 | |
| # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 | |
| # LIABLE FOR # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 | |
| # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 | |
| # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 | |
| # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 | |
| # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 | |
| # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 | |
| # POSSIBILITY OF SUCH DAMAGE.
 | |
| 
 | |
| use strict;
 | |
| use warnings;
 | |
| use Carp;
 | |
| use FindBin;
 | |
| use lib "$FindBin::Bin/../../..";
 | |
| use File::Slurp qw(slurp);
 | |
| use Getopt::Long;
 | |
| use Pod::Usage;
 | |
| use SNMP::Info;
 | |
| use Test::MockObject::Extends;
 | |
| 
 | |
| my $EMPTY = q{};
 | |
| 
 | |
| # Default Values
 | |
| my $class = $EMPTY;
 | |
| my @dump  = ();
 | |
| my $debug = 0;
 | |
| my $mibdirs;
 | |
| my $ignore = 0;
 | |
| my $help   = 0;
 | |
| my $file;
 | |
| my %dumped;
 | |
| 
 | |
| GetOptions(
 | |
|     'c|class=s'  => \$class,
 | |
|     'i|ignore'   => \$ignore,
 | |
|     'p|print=s'  => \@dump,
 | |
|     'x|debug+'   => \$debug,
 | |
|     'm|mibdir=s' => \$mibdirs,
 | |
|     'file=s'     => \$file,
 | |
|     'h|?|help'   => sub { pod2usage(1); },
 | |
| );
 | |
| 
 | |
| if ( !$file ) {
 | |
|     pod2usage(1);
 | |
| }
 | |
| 
 | |
| if ( $ignore && !defined $mibdirs ) {
 | |
|     print "mibdirs must be provided if ignoring snmp.conf \n\n";
 | |
|     pod2usage(1);
 | |
| }
 | |
| 
 | |
| local $ENV{'SNMPCONFPATH'} = $EMPTY     if $ignore;
 | |
| local $ENV{'MIBDIRS'}      = "$mibdirs" if $ignore;
 | |
| 
 | |
| if ( defined $mibdirs ) {
 | |
|     SNMP::addMibDirs($mibdirs);
 | |
| }
 | |
| 
 | |
| $class = $class ? "SNMP::Info::$class" : 'SNMP::Info';
 | |
| 
 | |
| ( my $mod = "$class.pm" )
 | |
|     =~ s{::}{/}g;    # SNMP::Info::Layer3 => SNMP/Info/Layer3.pm
 | |
| if ( !eval { require $mod; 1; } ) {
 | |
|     croak "Could not load $class. Error Message: $@\n";
 | |
| }
 | |
| 
 | |
| my $class_ver = $class->VERSION();
 | |
| 
 | |
| print
 | |
|     "Class $class ($class_ver) loaded from SNMP::Info $SNMP::Info::VERSION.\n";
 | |
| 
 | |
| if ( scalar @dump ) { print 'Dumping : ', join( q{,}, @dump ), "\n" }
 | |
| 
 | |
| my $mocked = create_mock_session();
 | |
| 
 | |
| my $dev = $class->new(
 | |
|     'AutoSpecify' => 0,
 | |
|     'BulkWalk'    => 0,
 | |
|     'Debug'       => $debug,
 | |
|     'Session'     => $mocked,
 | |
| ) or die "\n";
 | |
| 
 | |
| print 'Detected Class: ', $dev->device_type(), "\n";
 | |
| print "Using    Class: $class (-c to change)\n";
 | |
| 
 | |
| my $layers = $dev->layers();
 | |
| my $descr  = $dev->description();
 | |
| 
 | |
| if ( !defined $layers || !defined $descr ) {
 | |
|     die "Are you sure you specified a file created with make_snmpdata.pl ?\n";
 | |
| }
 | |
| 
 | |
| print "\nFetching base info...\n\n";
 | |
| 
 | |
| my @base_fns = qw/vendor model os os_ver description contact location
 | |
|     layers mac serial/;
 | |
| 
 | |
| foreach my $fn (@base_fns) {
 | |
|     test_global( $dev, $fn );
 | |
| }
 | |
| 
 | |
| print "\nFetching interface info...\n\n";
 | |
| 
 | |
| my @fns = qw/interfaces i_type i_ignore i_description i_mtu i_speed i_mac i_up
 | |
|     i_up_admin i_name i_duplex i_duplex_admin i_stp_state
 | |
|     i_vlan i_pvid i_lastchange/;
 | |
| 
 | |
| foreach my $fn (@fns) {
 | |
|     test_fn( $dev, $fn );
 | |
| }
 | |
| 
 | |
| print "\nFetching VLAN info...\n\n";
 | |
| 
 | |
| my @vlan = qw/v_index v_name/;
 | |
| 
 | |
| foreach my $fn (@vlan) {
 | |
|     test_fn( $dev, $fn );
 | |
| }
 | |
| 
 | |
| print "\nFetching topology info...\n\n";
 | |
| 
 | |
| my @topo = qw/c_if c_ip c_port c_id c_platform/;
 | |
| 
 | |
| foreach my $fn (@topo) {
 | |
|     test_fn( $dev, $fn );
 | |
| }
 | |
| 
 | |
| print "\nFetching module info...\n\n";
 | |
| 
 | |
| my @modules = qw/e_descr e_type e_parent e_name e_class e_pos e_hwver
 | |
|                 e_fwver e_swver e_model e_serial e_fru/;
 | |
| 
 | |
| foreach my $fn (@modules) {
 | |
|     test_fn( $dev, $fn );
 | |
| }
 | |
| 
 | |
| foreach my $fn (@dump) {
 | |
|     if ( !$dumped{$fn} ) { test_fn( $dev, $fn ) }
 | |
| }
 | |
| 
 | |
| #--------------------------------
 | |
| 
 | |
| sub load_snmpdata {
 | |
|     my $data_file = shift;
 | |
| 
 | |
|     my @lines = slurp($data_file);
 | |
| 
 | |
|     my $snmp_data = {};
 | |
|     foreach my $line (@lines) {
 | |
| 	next if !$line;
 | |
| 	next if ( $line =~ /^#/ );
 | |
| 	if ( $line =~ /^(\S+::\w+)[.]?(\S+)*\s=\s(.*)$/ ) {
 | |
| 	    my ( $leaf, $iid, $val ) = ( $1, $2, $3 );
 | |
| 	    next if !$leaf;
 | |
| 	    $iid ||= 0;
 | |
| 	    $val =~ s/\"//g;
 | |
| 	    $snmp_data->{$leaf}->{$iid} = $val;
 | |
| 	}
 | |
|     }
 | |
|     return $snmp_data;
 | |
| }
 | |
| 
 | |
| sub create_mock_session {
 | |
| 
 | |
|     my $snmp_data = load_snmpdata($file);
 | |
| 
 | |
|     my $session = SNMP::Session->new(
 | |
| 	UseEnums    => 1,
 | |
| 	RetryNoSuch => 1,
 | |
| 	Data        => $snmp_data,
 | |
| 	DestHost    => '127.0.0.1',
 | |
| 	Community   => 'public',
 | |
| 	Version     => 2,
 | |
|     );
 | |
| 
 | |
|     my $mock_session = Test::MockObject::Extends->new($session);
 | |
| 
 | |
|     mock_get($mock_session);
 | |
|     mock_getnext($mock_session);
 | |
| 
 | |
|     return $mock_session;
 | |
| }
 | |
| 
 | |
| sub mock_get {
 | |
|     my $mock_session = shift;
 | |
| 
 | |
|     $mock_session->mock(
 | |
| 	'get',
 | |
| 	sub {
 | |
| 	    my $self = shift;
 | |
| 	    my $vars = shift;
 | |
| 	    my ( $leaf, $iid, $oid, $oid_name );
 | |
| 	    my $c_data = $self->{Data};
 | |
| 
 | |
| 	    # From SNMP::Info get will only be passed either an OID or
 | |
| 	    # SNMP::Varbind with a fully qualified leaf and potentially
 | |
| 	    # a partial
 | |
| 	    if ( ref($vars) =~ /SNMP::Varbind/ ) {
 | |
| 		( $leaf, $iid ) = @{$vars};
 | |
| 	    }
 | |
| 	    else {
 | |
| 		$oid = $vars;
 | |
| 		$oid_name = SNMP::translateObj( $oid, 0, 1 ) || $EMPTY;
 | |
| 		( $leaf, $iid ) = $oid_name =~ /^(\S+::\w+)[.]?(\S+)*$/;
 | |
| 	    }
 | |
| 
 | |
| 	    $iid ||= 0;
 | |
| 	    my $new_iid = $iid;
 | |
| 	    my $val     = $EMPTY;
 | |
| 	    my $data    = $c_data->{$leaf} || {};
 | |
| 	    my $count   = scalar keys %{$data} || 0;
 | |
| 	    if ( $count > 1 ) {
 | |
| 		my $found = 0;
 | |
| 		foreach my $d_iid ( sort keys %{$data} ) {
 | |
| 		    if ( $d_iid eq $iid ) {
 | |
| 			$val   = $data->{$d_iid};
 | |
| 			$found = 1;
 | |
| 			next;
 | |
| 		    }
 | |
| 		    elsif ( $found == 1 ) {
 | |
| 			$new_iid = $d_iid;
 | |
| 			last;
 | |
| 		    }
 | |
| 		}
 | |
| 		if ( $found && ( $new_iid eq $iid ) ) {
 | |
| 		    $leaf = 'unknown';
 | |
| 		}
 | |
| 	    }
 | |
| 	    else {
 | |
| 		$val  = $data->{$iid};
 | |
| 		$leaf = 'unknown';
 | |
| 	    }
 | |
| 
 | |
| 	    if ( ref $vars =~ /SNMP::Varbind/ ) {
 | |
| 		$vars->[0] = $leaf;
 | |
| 		$vars->[1] = $new_iid;
 | |
| 		$vars->[2] = $val;
 | |
| 	    }
 | |
| 	    return ( wantarray() ? $vars : $val );
 | |
| 	}
 | |
|     );
 | |
|     return;
 | |
| }
 | |
| 
 | |
| sub mock_getnext {
 | |
|     my $mock_session = shift;
 | |
| 
 | |
|     $mock_session->mock(
 | |
| 	'getnext',
 | |
| 	sub {
 | |
| 	    my $self = shift;
 | |
| 	    my $vars = shift;
 | |
| 	    my ( $leaf, $iid, $oid, $oid_name );
 | |
| 	    my $c_data = $self->{Data};
 | |
| 
 | |
| 	    # From SNMP::Info getnext will only be passed a SNMP::Varbind
 | |
| 	    # with a fully qualified leaf and potentially a partial
 | |
| 	    ( $leaf, $iid ) = @{$vars};
 | |
| 
 | |
| 	    unless (defined $iid) {
 | |
| 		$iid = -1;
 | |
| 	    }
 | |
| 	    my $new_iid = $iid;
 | |
| 	    my $val     = $EMPTY;
 | |
| 	    my $data    = $c_data->{$leaf};
 | |
| 	    my $count   = scalar keys %{$data} || 0;
 | |
| 	    if ( $count ) {
 | |
| 		my $found = 0;
 | |
| 		foreach my $d_iid ( sort keys %{$data} ) {
 | |
| 		    if ( $d_iid gt $iid && !$found ) {
 | |
| 			$val     = $data->{$d_iid};
 | |
| 			$new_iid = $d_iid;
 | |
| 			$found   = 1;
 | |
| 			next;
 | |
| 		    }
 | |
| 		    elsif ( $found == 1 ) {
 | |
| 			last;
 | |
| 		    }
 | |
| 		}
 | |
| 		if ( $found && ( $new_iid eq $iid ) ) {
 | |
| 		    $leaf = 'unknown';
 | |
| 		}
 | |
| 	    }
 | |
| 	    else {
 | |
| 		$val  = $data->{$iid};
 | |
| 		$leaf = 'unknown';
 | |
| 	    }
 | |
| 
 | |
| 	    $vars->[0] = $leaf;
 | |
| 	    $vars->[1] = $new_iid;
 | |
| 	    $vars->[2] = $val;
 | |
| 	    return ( wantarray() ? $vars : $val );
 | |
| 	}
 | |
|     );
 | |
|     return;
 | |
| }
 | |
| 
 | |
| sub test_global {
 | |
|     my $device = shift;
 | |
|     my $method = shift;
 | |
| 
 | |
|     my $value = $device->$method();
 | |
| 
 | |
|     if ( !defined $value ) {
 | |
| 	printf "%-20s Does not exist.\n", $method;
 | |
| 	return 0;
 | |
|     }
 | |
|     $value =~ s/[[:cntrl:]]+/ /g;
 | |
|     if ( length $value > 60 ) {
 | |
| 	$value = substr $value, 0, 60;
 | |
| 	$value .= '...';
 | |
|     }
 | |
|     printf "%-20s %s \n", $method, $value;
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| sub test_fn {
 | |
|     my $device = shift;
 | |
|     my $method = shift;
 | |
| 
 | |
|     my $results = $device->$method();
 | |
| 
 | |
|     # If accidentally called on a global, pass it along nicely.
 | |
|     if ( defined $results && !ref $results ) {
 | |
| 	return test_global( $dev, $method );
 | |
|     }
 | |
|     if ( !defined $results && !scalar keys %{$results} ) {
 | |
| 	printf "%-20s Empty Results.\n", $method;
 | |
| 	return 0;
 | |
|     }
 | |
| 
 | |
|     printf "%-20s %d rows.\n", $method, scalar keys %{$results};
 | |
|     if ( grep {/^$method$/} @dump ) {
 | |
| 	$dumped{$method} = 1;
 | |
| 	foreach my $iid ( keys %{$results} ) {
 | |
| 	    print "  $iid : ";
 | |
| 	    if ( ref( $results->{$iid} ) eq 'ARRAY' ) {
 | |
| 		print '[ ', join( ', ', @{ $results->{$iid} } ), ' ]';
 | |
| 	    }
 | |
| 	    else {
 | |
| 		print $results->{$iid};
 | |
| 	    }
 | |
| 	    print "\n";
 | |
| 	}
 | |
|     }
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| __END__
 | |
| 
 | |
| =head1 NAME
 | |
| 
 | |
| test_class_mocked.pl - Test a device against an SNMP::Info class using
 | |
| output from make_snmpdata.pl stored in a text file.
 | |
| 
 | |
| =head1 AUTHOR
 | |
| 
 | |
| Eric Miller
 | |
| 
 | |
| =head1 SYNOPSIS
 | |
| 
 | |
| test_class_mocked.pl [options]
 | |
| 
 | |
| Options:
 | |
| 
 | |
|     -class    SNMP::Info class to use, Layer2::Catalyst    
 | |
|     -file     File containing data gathered using make_snmpdata.pl
 | |
|     -print    Print values 
 | |
|     -debug    Debugging flag
 | |
|     -ignore   Ignore Net-SNMP configuration file
 | |
|     -mibdir   Directory containing MIB Files
 | |
|     -help     Brief help message
 | |
| 
 | |
| =head1 OPTIONS
 | |
| 
 | |
| =over 8
 | |
| 
 | |
| =item B<-class>
 | |
| 
 | |
| Specific SNMP::Info class to use.  Defaults to SNMP::Info if no specific
 | |
| class provided.
 | |
| 
 | |
| -class Layer2::Catalyst
 | |
| 
 | |
| =item B<-file>
 | |
| 
 | |
| File containing data gathered using make_snmpdata.pl.  No default and a
 | |
| mandatory option.
 | |
| 
 | |
| -file /data/mydevice.txt
 | |
| 
 | |
| =item B<-print>
 | |
| 
 | |
| Print values of a class method rather than summarizing.  May be repeated
 | |
| multiple times. 
 | |
| 
 | |
| -print i_description -print i_type
 | |
| 
 | |
| =item B<-debug>
 | |
| 
 | |
| Turns on SNMP::Info debug.
 | |
| 
 | |
| -debug
 | |
| 
 | |
| =item B<-ignore >
 | |
| 
 | |
| Ignore Net-SNMP configuration file snmp.conf.  If this used mibdirs must be
 | |
| provided.
 | |
| 
 | |
| -ignore
 | |
| 
 | |
| =item B<-mibdir>
 | |
| 
 | |
| Directory containing MIB Files.  Multiple directories should be separated by a
 | |
| colon ':'. 
 | |
| 
 | |
| -mibdir /usr/local/share/snmp/mibs/rfc:/usr/local/share/snmp/mibs/net-snmp
 | |
| 
 | |
| =item B<-help>
 | |
| 
 | |
| Print help message and exits.
 | |
| 
 | |
| =back
 | |
| 
 | |
| =head1 DESCRIPTION
 | |
| 
 | |
| B<test_class_mocked.pl> will test a device against an SNMP::Info class using
 | |
| snmpwalk output from the utility B<make_snmpdata.pl> stored in a text file.
 | |
| This allows debugging and testing without requiring network access to the
 | |
| device being tested.
 | |
| 
 | |
| =cut
 |