]> git.sesse.net Git - nms/blob - clients/snmpfetch.pl
initial import
[nms] / clients / snmpfetch.pl
1 #! /usr/bin/perl
2 use BER;
3 use DBI;
4 use POSIX;
5 use Time::HiRes;
6 use Net::Telnet;
7 use strict;
8 use warnings;
9 require 'SNMP_Session.pm';
10
11 my $password = 'removed';
12 my $timeout = 15;
13
14 my $dbh = DBI->connect("dbi:Pg:dbname=nms;host=localhost", "nms", "nms")
15         or die "Couldn't connect to database";
16 $dbh->{AutoCommit} = 0;
17
18 # normal mode: fetch switches from the database
19 # instant mode: poll the switches specified on the command line
20 my $instant = (defined($ARGV[0]));
21 my $qualification;
22 if ($instant) {
23         $qualification = "sysname=?";
24 } else {
25         $qualification = <<"EOF";
26   (last_updated IS NULL OR now() - last_updated > poll_frequency)
27   AND (locked='f' OR now() - last_updated > '15 minutes'::interval)
28 EOF
29 }
30
31 my $qswitch = $dbh->prepare(<<"EOF")
32 SELECT 
33   *,
34   DATE_TRUNC('second', now() - last_updated - poll_frequency) AS overdue
35 FROM
36   switches
37   NATURAL LEFT JOIN switchtypes
38 WHERE $qualification
39 ORDER BY
40   priority DESC,
41   overdue DESC
42 LIMIT 1
43 FOR UPDATE OF switches
44 EOF
45         or die "Couldn't prepare qswitch";
46 my $qlock = $dbh->prepare("UPDATE switches SET locked='t', last_updated=now() WHERE switch=?")
47         or die "Couldn't prepare qlock";
48 my $qunlock = $dbh->prepare("UPDATE switches SET locked='f', last_updated=now() WHERE switch=?")
49         or die "Couldn't prepare qunlock";
50 my $qpoll = $dbh->prepare("INSERT INTO polls (time, switch, port, bytes_in, bytes_out) VALUES (timeofday()::timestamp,?,?,?,?)")
51         or die "Couldn't prepare qpoll";
52 my $qtemppoll = $dbh->prepare("INSERT INTO temppoll (time, switch, temp) VALUES (timeofday()::timestamp,?::text::int,?::text::float)")
53         or die "Couldn't prepare qtemppoll";
54 my $qcpupoll = $dbh->prepare("INSERT INTO cpuloadpoll (time, switch, entity, value) VALUES (timeofday()::timestamp,?::text::int,?,?)")
55         or die "Couldn't prepare qtemppoll";
56
57 while (1) {
58         my $sysname;
59         if ($instant) {
60                 $sysname = shift @ARGV;
61                 exit if (!defined($sysname));
62                 $qswitch->execute($sysname)
63                         or die "Couldn't get switch";
64         } else {
65                 # Find a switch to grab
66                 $qswitch->execute()
67                         or die "Couldn't get switch";
68         }
69         my $switch = $qswitch->fetchrow_hashref();
70
71         if (!defined($switch)) {
72                 $dbh->commit;
73
74                 if ($instant) {
75                         mylog("No such switch $sysname available, quitting.");
76                         exit;
77                 } else {        
78                         mylog("No available switches in pool, sleeping.");
79                         sleep 60;
80                         next;
81                 }
82         }
83
84         $qlock->execute($switch->{'switch'})
85                 or die "Couldn't lock switch";
86         $dbh->commit;
87
88         if ($switch->{'locked'}) {
89                 mylog("WARNING: Lock timed out on $switch->{'ip'}, breaking lock");
90         }
91
92         my $msg;
93         if (defined($switch->{'overdue'})) {
94                 $msg = sprintf "Polling ports %s on %s (%s), %s overdue.",
95                         $switch->{'ports'}, $switch->{'ip'}, $switch->{'sysname'}, $switch->{'overdue'};
96         } else {
97                 $msg = sprintf "Polling ports %s on %s (%s), never polled before.",
98                         $switch->{'ports'}, $switch->{'ip'}, $switch->{'sysname'};
99         }
100         mylog($msg);
101
102         my $ip = $switch->{'ip'};
103
104         if ($ip eq '127.0.0.1') {
105                 mylog("Polling disabled for this switch, skipping.");
106                 $qunlock->execute($switch->{'switch'})
107                         or die "Couldn't unlock switch";
108                 $dbh->commit;
109                 next;
110         }
111
112         my $community = $switch->{'community'};
113         my $start = [Time::HiRes::gettimeofday];
114         eval {
115                 my $session;
116                 if ($switch->{'wide_counters'}) {
117                         $session = SNMPv2c_Session->open($ip, $community, 161)
118                                 or die "Couldn't talk to switch";
119                 } else {
120                         $session = SNMP_Session->open($ip, $community, 161)
121                                 or die "Couldn't talk to switch";
122                 }
123                 my @ports = expand_ports($switch->{'ports'});
124
125                 for my $port (@ports) {
126                         my $in = fetch_data($session, $port, 0, $switch->{'wide_counters'});
127                         my $out = fetch_data($session, $port, 1, $switch->{'wide_counters'});
128
129                         $qpoll->execute($switch->{'switch'}, $port, $in, $out);
130                 }
131                 my $conn = switch_connect($ip);
132                 if (!defined($conn)) {
133                         print "Could not connect to switch ".$switch->{'switch'}."\n";
134                 } elsif ($switch->{'switchtype'} eq 'es3024') { 
135                         my @data = switch_exec('sys monitor status', $conn);
136                         my @fields = split(/\s+/, $data[2]);
137                         # The temp fields are 6, 7, 8
138                         print "$fields[7] + $fields[8] + $fields[9]\n";
139                         my $avgtemp = ($fields[7] + $fields[8] + $fields[9]) / 3;
140                         print $avgtemp." avgtemp\n";
141                         $qtemppoll->execute($switch->{'switch'},
142                                         $avgtemp) or die "Could not exec qtemppoll";
143                 } elsif ($switch->{'switchtype'} eq 'cisco6509') {
144                         for my $i (1..5) {
145                                 # find the ID
146                                 my $oid = BER::encode_oid(1, 3, 6, 1, 4, 1, 9, 9, 109, 1, 1, 1, 1, 2, $i);
147                                 my $entity = fetch_snmp($session, $oid);
148
149                                 next if (!defined($entity));
150                         
151                                 # last-minute load for the given entity
152                                 $oid = BER::encode_oid(1, 3, 6, 1, 4, 1, 9, 9, 109, 1, 1, 1, 1, 4, $i);
153                                 my $value = fetch_snmp($session, $oid);
154                                 
155                                 $qcpupoll->execute($switch->{'switch'}, $entity, $value)
156                                         or die "Could not exec qcpupoll";
157                         }
158                 }
159                 $session->close;
160         };
161         if ($@) {
162                 mylog("ERROR: $@ (during poll of $ip)");
163                 $dbh->rollback;
164         }
165         
166         my $elapsed = Time::HiRes::tv_interval($start);
167         $msg = sprintf "Polled $switch->{'ip'} in %5.3f seconds.", $elapsed;            
168         mylog($msg);
169
170         $qunlock->execute($switch->{'switch'})
171                 or die "Couldn't unlock switch";
172         $dbh->commit;
173 }
174
175 sub fetch_data {
176         my ($session, $port, $out, $wide_counters) = @_;
177         
178         my $oid;
179         if ($wide_counters) {
180                 if ($out) {
181                         $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 10, $port);  # interfaces.ifTable.ifEntry.ifHCOutOctets
182                 } else {
183                         $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 6, $port);  # interfaces.ifTable.ifEntry.ifHCInOctets
184                 }
185         } else {
186                 if ($out) {
187                         $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 2, 2, 1, 16, $port);  # interfaces.ifTable.ifEntry.ifOutOctets
188                 } else {
189                         $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 2, 2, 1, 10, $port);  # interfaces.ifTable.ifEntry.ifInOctets
190                 }
191         }
192
193         return fetch_snmp($session, $oid);
194 }
195         
196 sub fetch_snmp {
197         my ($session, $oid) = @_;
198
199         if ($session->get_request_response($oid)) {
200                 my ($bindings) = $session->decode_get_response ($session->{pdu_buffer});
201                 my $binding;
202                 while ($bindings ne '') {
203                         ($binding,$bindings) = &decode_sequence ($bindings);
204                         my ($oid,$value) = &decode_by_template ($binding, "%O%@");
205                         return BER::pretty_print($value);
206                 }
207         }
208         die "Couldn't get info from switch";
209 }
210
211 sub expand_ports {
212         my $in = shift;
213         my @ranges = split /,/, $in;
214         my @ret = ();
215
216         for my $range (@ranges) {
217                 if ($range =~ /^\d+$/) {
218                         push @ret, $range;
219                 } elsif ($range =~ /^(\d+)-(\d+)$/) {
220                         for my $i ($1..$2) {
221                                 push @ret, $i;
222                         }
223                 } else {
224                         die "Couldn't understand '$range' in ports";
225                 }
226         }
227
228         return (sort { $a <=> $b } @ret); 
229 }
230
231 sub mylog {
232         my $msg = shift;
233         my $time = POSIX::ctime(time);
234         $time =~ s/\n.*$//;
235         printf STDERR "[%s] %s\n", $time, $msg;
236 }
237
238 sub switch_exec {
239         my ($cmd, $conn) = @_;
240
241         # Send the command and get data from switch
242 #       $conn->dump_log(*STDOUT);
243         my @data = $conn->cmd($cmd);
244         my @lines = ();
245         foreach my $line (@data) {
246                 # Remove escape-7 sequence
247                 $line =~ s/\x1b\x37//g;
248                 push @lines, $line;
249         }
250
251         return @lines;
252 }
253
254 sub switch_connect {
255         my ($ip) = @_;
256
257         my $conn = new Net::Telnet(     Timeout => $timeout,
258                                         Dump_Log => '/tmp/dumplog-tempfetch',
259                                         Errmode => 'return',
260                                         Prompt => '/es-3024|e(\-)?\d+\-\dsw>/i');
261         my $ret = $conn->open(  Host => $ip);
262         if (!$ret || $ret != 1) {
263                 return (0);
264         }
265         # XXX: Just send the password as text, I did not figure out how to
266         # handle authentication with only password through $conn->login().
267         #$conn->login(  Prompt => '/password[: ]*$/i',
268         #              Name => $password,
269         #              Password => $password);
270         my @data = $conn->cmd($password);
271         # Get rid of banner
272         $conn->get;
273         return $conn;
274 }
275