#! /usr/bin/perl use DBI; use POSIX; use Time::HiRes; use Net::Telnet; use strict; use warnings; 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) { if (fork() == 0) { # child poll_loop(); exit; } } poll_loop(); } sub poll_loop { my @switches = @_; my $instant = (scalar @switches > 0); my $timeout = 15; my $dbh = nms::db_connect(); $dbh->{AutoCommit} = 0; $dbh->{RaiseError} = 1; 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 > '5 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, official_port) VALUES (timeofday()::timestamp,?,?,?,?,?,?,true)") 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 = nms::snmp_open_session($ip, $community); my @ports = expand_ports($switch->{'ports'}); for my $port (@ports) { my $in = $session->get("ifHCInOctets.$port"); if (!defined($in) || $in !~ /^\d+$/) { warn $switch->{'sysname'}.":$port: failed reading in"; next; } my $out = $session->get("ifHCOutOctets.$port"); if (!defined($out) || $out !~ /^\d+$/) { warn $switch->{'sysname'}.":$port: failed reading in"; next; } my $ine = $session->get("ifInErrors.$port"); $ine = -1 if (!defined($ine) || $ine !~ /^\d+$/); my $oute = $session->get("ifOutErrors.$port"); $oute = -1 if (!defined($oute) || $oute !~ /^\d+$/); $qpoll->execute($switch->{'switch'}, $port, $in, $out, $ine, $oute) || die "%s:%s: %s\n", $switch->{'switch'}, $port, $in; } }; 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 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; #}