248137185a9aab4522625207cf7bef0a05a26302
[buildfarm-server.git] / bf-alerts.pl
1 #!/usr/bin/perl
2
3 =comment
4
5 Copyright (c) 2003-2010, Andrew Dunstan
6
7 See accompanying License file for license details
8
9 =cut 
10
11 use strict;
12
13 use Digest::SHA1  qw(sha1_hex);
14 use MIME::Base64;
15 use DBI;
16 use DBD::Pg;
17 use Data::Dumper;
18 use Mail::Send;
19 use Storable qw(thaw);
20
21 use vars qw($dbhost $dbname $dbuser $dbpass $dbport
22        $all_stat $fail_stat $change_stat $green_stat
23        $default_host
24 );
25
26 require "$ENV{BFConfDir}/BuildFarmWeb.pl";
27
28 die "no dbname" unless $dbname;
29 die "no dbuser" unless $dbuser;
30
31 # don't use configged dbuser/dbpass
32
33 $dbuser=""; $dbpass="";
34
35 my $dsn="dbi:Pg:dbname=$dbname";
36 $dsn .= ";host=$dbhost" if $dbhost;
37 $dsn .= ";port=$dbport" if $dbport;
38
39 my $db = DBI->connect($dsn,$dbuser,$dbpass);
40
41 die $DBI::errstr unless $db;
42
43 my $clear_old = $db->do(q[
44
45     DELETE FROM alerts
46     WHERE sysname IN
47       (SELECT name FROM buildsystems WHERE no_alerts)
48                            ]);
49
50
51 my $sth = $db->prepare(q[
52
53     SELECT DISTINCT ON (sysname, branch) 
54          sysname, branch, 
55          extract(epoch from snapshot at time zone 'GMT')::int as snapshot, 
56          frozen_conf as config
57     FROM build_status s join buildsystems b on (s.sysname = b.name)
58     WHERE NOT b.no_alerts and
59        snapshot > current_timestamp - interval '30 days'
60     ORDER BY sysname, branch, snapshot desc
61
62                           ]);
63
64 $sth->execute;
65
66 my @last_heard;
67
68 while (my $row = $sth->fetchrow_hashref)
69 {
70     push(@last_heard, $row);
71 }
72
73 $sth->finish;
74
75 my $sql = q[
76
77    SELECT sysname, branch, 
78             extract(epoch from first_alert) as first_alert, 
79             extract(epoch from last_notification) as last_notification
80    FROM alerts
81
82             ];
83
84 my $alerts = $db->selectall_hashref($sql,['sysname','branch']);
85
86 my @need_cleared;
87 my @need_alerts;
88
89 my $clear_sth = $db->prepare(q[
90
91   DELETE FROM alerts
92   WHERE sysname = ?
93   AND branch = ?
94                       ]);
95
96 my $update_sth = $db->prepare(q[
97
98   UPDATE alerts
99   SET last_notification = timestamp '1970-01-01' + ( interval '1 second' * $1)
100   WHERE sysname = $2
101   AND branch = $3
102                       ]);
103
104 my $insert_sth = $db->prepare(q[
105
106   INSERT INTO alerts ( sysname, branch, first_alert, last_notification )
107   VALUES ($1, $2,  
108           timestamp '1970-01-01' + ( interval '1 second' * $3),
109           timestamp '1970-01-01' + ( interval '1 second' * $4))
110                       ]);
111
112
113 my $now = time;
114 my $lts = scalar(localtime);
115 print "starting alert run: $lts\n";
116
117 foreach my $sysbranch (@last_heard)
118 {
119         # not all versions of DBD::Pg decode modern bytea literals nicely. cope.
120         $sysbranch->{config} =~ s/^(\\?x)([a-fA-F0-9]+)$/pack('H*',$2)/e;
121
122
123     my $client_conf = thaw $sysbranch->{config};
124
125     my %client_alert_settings = %{ $client_conf->{alerts} || {} };
126     my $setting = $client_alert_settings{$sysbranch->{branch}};
127     unless ($setting && $setting->{alert_after} && $setting->{alert_every})
128     {
129         # if no valid setting, clear any alert and keep going
130         if ($alerts->{$sysbranch->{sysname}}->{$sysbranch->{branch}})
131         {
132             $clear_sth->execute($sysbranch->{sysname},$sysbranch->{branch});
133             push(@need_cleared,[$sysbranch]);
134         }
135         next;
136     }
137     # ok, we have valid settings. should the alert be on?
138     my $hours_since_heard = ($now - $sysbranch->{snapshot}) / 3600;
139     # yep
140     print 
141         "have settings for $sysbranch->{sysname}:$sysbranch->{branch} ",
142         "hours since heard = $hours_since_heard, ",
143         "setting = $setting->{alert_after} / $setting->{alert_every} \n";
144
145     if ($hours_since_heard > $setting->{alert_after})
146     {
147         my $known_alert = 
148             $alerts->{$sysbranch->{sysname}}->{$sysbranch->{branch}};
149         if ($known_alert && 
150             ($now - (3600 * $setting->{alert_every})) >
151             $known_alert->{last_notification})
152         {
153             # check if it's too old - 15 days and twice initial seems plenty
154             if ($hours_since_heard > 360 && 
155                      $hours_since_heard  > 2 * $setting->{alert_after} )
156             {
157                 print "alert is too old ... giving up\n";
158                 next;
159             }
160
161             # old alert, but time to alert again
162             print "alert is on, but time to alert again\n";
163             $update_sth->execute($now,
164                                  $sysbranch->{sysname},
165                                  $sysbranch->{branch},
166                                  );
167             push(@need_alerts,[$sysbranch,$setting]);
168             print "alert updated\n";
169         }
170         elsif ( ! $known_alert )
171         {
172             # new alert
173             print "new alert needed\n";
174             $insert_sth->execute($sysbranch->{sysname},
175                                  $sysbranch->{branch},
176                                  $now,$now);
177             print "new record inserted\n";
178             push(@need_alerts,[$sysbranch,$setting]);
179         }
180     }
181     # nope, so clear the alert if it exists
182     elsif ($alerts->{$sysbranch->{sysname}}->{$sysbranch->{branch}})
183     {
184         print "clear exisiting alerts";
185         $clear_sth->execute($sysbranch->{sysname},$sysbranch->{branch});
186         push(@need_cleared,[$sysbranch,$setting]);
187     }
188     
189 }
190
191 print "start emails\n";
192
193 my $addr_sth = $db->prepare(q[
194
195   SELECT owner_email
196   FROM buildsystems
197   WHERE name = ?
198                  ]);
199
200
201 my $me = `id -un`; chomp $me;
202 my $host = `hostname`; chomp ($host);
203 $host = $default_host unless ($host =~ m/[.]/ || !defined($default_host));
204
205 my $from_addr = "PG Build Farm <$me\@$host>";
206 $from_addr =~ tr /\r\n//d;
207
208
209
210 foreach my $clearme (@need_cleared)
211 {
212     my ($sysbranch, $setting) = @$clearme;
213     my ($animal, $branch) = ($sysbranch->{sysname},$sysbranch->{branch});
214     my $text;
215     if ($setting)
216     {
217         my $hours = sprintf("%.2f",($now - $sysbranch->{snapshot}) / 3600);
218         $text = "$sysbranch->{sysname} has now reported " .
219             "on $sysbranch->{branch} $hours hours ago.";
220     }
221     else
222     {
223         $text = "$sysbranch->{sysname} has lost alarm settings on branch: " .
224             "$sysbranch->{branch}. Resetting alarm to off.";
225     }
226     my $msg = new Mail::Send;
227
228     $msg->set('From',$from_addr);
229
230     $addr_sth->execute($animal);
231
232     my $mailto = $addr_sth->fetchrow_array;
233
234     print "sending clear to $mailto\n";
235
236     # $sth->finish;
237
238     $msg->to($mailto);
239     $msg->subject("PGBuildfarm member $animal Branch $branch Alert cleared");
240     my $fh = $msg->open;
241     print $fh "\n\n$text\n"; 
242     $fh->close;
243
244     print "alert cleared $animal $branch\n";
245 }
246
247 foreach my $clearme (@need_alerts)
248 {
249     my ($sysbranch, $setting) = @$clearme;
250     my ($animal, $branch) = ($sysbranch->{sysname},$sysbranch->{branch});
251     my $hours = sprintf("%.2f",($now - $sysbranch->{snapshot}) / 3600);
252     my $text = "$sysbranch->{sysname} has not reported " .
253         "on $sysbranch->{branch} for $hours hours.";
254     my $msg = new Mail::Send;
255
256     $msg->set('From',$from_addr);
257
258     $addr_sth->execute($animal);
259
260     my ($mailto) = $addr_sth->fetchrow_array;
261
262     # $sth->finish;
263
264     print "sending alert to $mailto\n";
265
266     $msg->to($mailto);
267
268     $msg->subject("PGBuildfarm member $animal Branch $branch " .
269                   "Alert notification");
270     my $fh = $msg->open;
271     print $fh "\n\n$text\n"; 
272     $fh->close;
273
274     print "alert sent $animal $branch\n";
275 }
276
277
278 print "=================================\n";
279
280
281