diff options
Diffstat (limited to 'clients/snmpfetch.pl')
-rwxr-xr-x | clients/snmpfetch.pl | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/clients/snmpfetch.pl b/clients/snmpfetch.pl new file mode 100755 index 0000000..487ae11 --- /dev/null +++ b/clients/snmpfetch.pl @@ -0,0 +1,281 @@ +#! /usr/bin/perl +use BER; +use DBI; +use POSIX; +use Time::HiRes; +use Net::Telnet; +use strict; +use warnings; +require 'SNMP_Session.pm'; + +use lib '../include'; +use nms; +use threads; + +# normal mode: fetch switches from the database +# instant mode: poll the switches specified on the command line +if (defined($ARGV[0])) { + poll_loop(@ARGV); +} else { + my $threads = 50; + for (1..$threads) { + threads->create(\&poll_loop); + } + poll_loop(); +} + +sub poll_loop { + my @switches = @_; + my $instant = (scalar @switches > 0); + my $timeout = 15; + + my $dbh = nms::db_connect(); + $dbh->{AutoCommit} = 0; + + my $qualification; + if ($instant) { + $qualification = "sysname=?"; + } else { + $qualification = <<"EOF"; + (last_updated IS NULL OR now() - last_updated > poll_frequency) + AND (locked='f' OR now() - last_updated > '15 minutes'::interval) + AND ip is not null +EOF + } + + my $qswitch = $dbh->prepare(<<"EOF") +SELECT + *, + DATE_TRUNC('second', now() - last_updated - poll_frequency) AS overdue +FROM + switches + NATURAL LEFT JOIN switchtypes +WHERE $qualification +ORDER BY + priority DESC, + overdue DESC +LIMIT 1 +FOR UPDATE OF switches +EOF + or die "Couldn't prepare qswitch"; + my $qlock = $dbh->prepare("UPDATE switches SET locked='t', last_updated=now() WHERE switch=?") + or die "Couldn't prepare qlock"; + my $qunlock = $dbh->prepare("UPDATE switches SET locked='f', last_updated=now() WHERE switch=?") + or die "Couldn't prepare qunlock"; + my $qpoll = $dbh->prepare("INSERT INTO polls (time, switch, port, bytes_in, bytes_out, errors_in, errors_out) VALUES (timeofday()::timestamp,?,?,?,?,?,?)") + or die "Couldn't prepare qpoll"; + my $qtemppoll = $dbh->prepare("INSERT INTO temppoll (time, switch, temp) VALUES (timeofday()::timestamp,?::text::int,?::text::float)") + or die "Couldn't prepare qtemppoll"; + my $qcpupoll = $dbh->prepare("INSERT INTO cpuloadpoll (time, switch, entity, value) VALUES (timeofday()::timestamp,?::text::int,?,?)") + or die "Couldn't prepare qtemppoll"; + + while (1) { + my $sysname; + if ($instant) { + $sysname = shift @ARGV; + exit if (!defined($sysname)); + $qswitch->execute($sysname) + or die "Couldn't get switch"; + } else { + # Find a switch to grab + $qswitch->execute() + or die "Couldn't get switch"; + } + my $switch = $qswitch->fetchrow_hashref(); + + if (!defined($switch)) { + $dbh->commit; + + if ($instant) { + mylog("No such switch $sysname available, quitting."); + exit; + } else { + mylog("No available switches in pool, sleeping."); + sleep 15; + next; + } + } + + $qlock->execute($switch->{'switch'}) + or die "Couldn't lock switch"; + $dbh->commit; + + if ($switch->{'locked'}) { + mylog("WARNING: Lock timed out on $switch->{'ip'}, breaking lock"); + } + + my $msg; + if (defined($switch->{'overdue'})) { + $msg = sprintf "Polling ports %s on %s (%s), %s overdue.", + $switch->{'ports'}, $switch->{'ip'}, $switch->{'sysname'}, $switch->{'overdue'}; + } else { + $msg = sprintf "Polling ports %s on %s (%s), never polled before.", + $switch->{'ports'}, $switch->{'ip'}, $switch->{'sysname'}; + } + mylog($msg); + + my $ip = $switch->{'ip'}; + + if ($ip eq '127.0.0.1') { + mylog("Polling disabled for this switch, skipping."); + $qunlock->execute($switch->{'switch'}) + or die "Couldn't unlock switch"; + $dbh->commit; + next; + } + + my $community = $switch->{'community'}; + my $start = [Time::HiRes::gettimeofday]; + eval { + my $session; + if ($switch->{'wide_counters'}) { + $session = SNMPv2c_Session->open($ip, $community, 161) + or die "Couldn't talk to switch"; + } else { + $session = SNMP_Session->open($ip, $community, 161) + or die "Couldn't talk to switch"; + } + my @ports = expand_ports($switch->{'ports'}); + + for my $port (@ports) { + my $in = fetch_data($session, $port, 0, $switch->{'wide_counters'}); + die $switch->{'switch'}.":$port: failed reading in" if !defined $in; + my $out = fetch_data($session, $port, 1, $switch->{'wide_counters'}); + die $switch->{'switch'}.":$port: failed reading out" if !defined $out; + my $ine = fetch_errors($session, $port, 0); + die $switch->{'switch'}. ":$port: failed reading in-errors" if !defined $ine; + my $oute = fetch_errors($session, $port, 1); + die $switch->{'switch'}. ":$port: failed reading out-errors" if !defined $oute; + + $qpoll->execute($switch->{'switch'}, $port, $in, $out, $ine, $oute) || die "%s:%s: %s\n", $switch->{'switch'}, $port, $in; + } + $session->close; + }; + if ($@) { + mylog("ERROR: $@ (during poll of $ip)"); + $dbh->rollback; + } + + my $elapsed = Time::HiRes::tv_interval($start); + $msg = sprintf "Polled $switch->{'ip'} in %5.3f seconds.", $elapsed; + mylog($msg); + + $qunlock->execute($switch->{'switch'}) + or warn "Couldn't unlock switch"; + $dbh->commit; + } +} + +sub fetch_data { + my ($session, $port, $out, $wide_counters) = @_; + + my $oid; + if ($wide_counters) { + if ($out) { + $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 10, $port); # interfaces.ifTable.ifEntry.ifHCOutOctets + } else { + $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 6, $port); # interfaces.ifTable.ifEntry.ifHCInOctets + } + } else { + if ($out) { + $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 2, 2, 1, 16, $port); # interfaces.ifTable.ifEntry.ifOutOctets + } else { + $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 2, 2, 1, 10, $port); # interfaces.ifTable.ifEntry.ifInOctets + } + } + + return fetch_snmp($session, $oid); +} +sub fetch_errors { + my ($session, $port, $out) = @_; + + my $oid; + if ($out) { + $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 2, 2, 1, 20, $port); # interfaces.ifTable.ifEntry.ifOutErrors + } else { + $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 2, 2, 1, 14, $port); # interfaces.ifTable.ifEntry.ifInErrors + } + + return fetch_snmp($session, $oid); +} + +sub fetch_snmp { + my ($session, $oid) = @_; + + if ($session->get_request_response($oid)) { + my ($bindings) = $session->decode_get_response ($session->{pdu_buffer}); + my $binding; + while ($bindings ne '') { + ($binding,$bindings) = &decode_sequence ($bindings); + my ($oid,$value) = &decode_by_template ($binding, "%O%@"); + return BER::pretty_print($value); + } + } + die "Couldn't get info from switch"; +} + +sub expand_ports { + my $in = shift; + my @ranges = split /,/, $in; + my @ret = (); + + for my $range (@ranges) { + if ($range =~ /^\d+$/) { + push @ret, $range; + } elsif ($range =~ /^(\d+)-(\d+)$/) { + for my $i ($1..$2) { + push @ret, $i; + } + } else { + die "Couldn't understand '$range' in ports"; + } + } + + return (sort { $a <=> $b } @ret); +} + +sub mylog { + my $msg = shift; + my $time = POSIX::ctime(time); + $time =~ s/\n.*$//; + printf STDERR "[%s] %s\n", $time, $msg; +} + +#sub switch_exec { +# my ($cmd, $conn) = @_; +# +# # Send the command and get data from switch +## $conn->dump_log(*STDOUT); +# my @data = $conn->cmd($cmd); +# my @lines = (); +# foreach my $line (@data) { +# # Remove escape-7 sequence +# $line =~ s/\x1b\x37//g; +# push @lines, $line; +# } +# +# return @lines; +#} + +#sub switch_connect { +# my ($ip) = @_; +# +# my $conn = new Net::Telnet( Timeout => $timeout, +# Dump_Log => '/tmp/dumplog-tempfetch', +# Errmode => 'return', +# Prompt => '/es-3024|e(\-)?\d+\-\dsw>/i'); +# my $ret = $conn->open( Host => $ip); +# if (!$ret || $ret != 1) { +# return (0); +# } +# # XXX: Just send the password as text, I did not figure out how to +# # handle authentication with only password through $conn->login(). +# #$conn->login( Prompt => '/password[: ]*$/i', +# # Name => $password, +# # Password => $password); +# my @data = $conn->cmd($password); +# # Get rid of banner +# $conn->get; +# return $conn; +#} + |