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