66c0c6d89c6ad9bfaccaba1f99610e22f5044474
[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     my $client_conf = thaw $sysbranch->{config};
120
121     my %client_alert_settings = %{ $client_conf->{alerts} || {} };
122     my $setting = $client_alert_settings{$sysbranch->{branch}};
123     unless ($setting && $setting->{alert_after} && $setting->{alert_every})
124     {
125         # if no valid setting, clear any alert and keep going
126         if ($alerts->{$sysbranch->{sysname}}->{$sysbranch->{branch}})
127         {
128             $clear_sth->execute($sysbranch->{sysname},$sysbranch->{branch});
129             push(@need_cleared,[$sysbranch]);
130         }
131         next;
132     }
133     # ok, we have valid settings. should the alert be on?
134     my $hours_since_heard = ($now - $sysbranch->{snapshot}) / 3600;
135     # yep
136     print 
137         "have settings for $sysbranch->{sysname}:$sysbranch->{branch} ",
138         "hours since heard = $hours_since_heard, ",
139         "setting = $setting->{alert_after} / $setting->{alert_every} \n";
140
141     if ($hours_since_heard > $setting->{alert_after})
142     {
143         my $known_alert = 
144             $alerts->{$sysbranch->{sysname}}->{$sysbranch->{branch}};
145         if ($known_alert && 
146             ($now - (3600 * $setting->{alert_every})) >
147             $known_alert->{last_notification})
148         {
149             # check if it's too old - 15 days and twice initial seems plenty
150             if ($hours_since_heard > 360 && 
151                      $hours_since_heard  > 2 * $setting->{alert_after} )
152             {
153                 print "alert is too old ... giving up\n";
154                 next;
155             }
156
157             # old alert, but time to alert again
158             print "alert is on, but time to alert again\n";
159             $update_sth->execute($now,
160                                  $sysbranch->{sysname},
161                                  $sysbranch->{branch},
162                                  );
163             push(@need_alerts,[$sysbranch,$setting]);
164             print "alert updated\n";
165         }
166         elsif ( ! $known_alert )
167         {
168             # new alert
169             print "new alert needed\n";
170             $insert_sth->execute($sysbranch->{sysname},
171                                  $sysbranch->{branch},
172                                  $now,$now);
173             print "new record inserted\n";
174             push(@need_alerts,[$sysbranch,$setting]);
175         }
176     }
177     # nope, so clear the alert if it exists
178     elsif ($alerts->{$sysbranch->{sysname}}->{$sysbranch->{branch}})
179     {
180         print "clear exisiting alerts";
181         $clear_sth->execute($sysbranch->{sysname},$sysbranch->{branch});
182         push(@need_cleared,[$sysbranch,$setting]);
183     }
184     
185 }
186
187 print "start emails\n";
188
189 my $addr_sth = $db->prepare(q[
190
191   SELECT owner_email
192   FROM buildsystems
193   WHERE name = ?
194                  ]);
195
196
197 my $me = `id -un`; chomp $me;
198 my $host = `hostname`; chomp ($host);
199 $host = $default_host unless ($host =~ m/[.]/ || !defined($default_host));
200
201 my $from_addr = "PG Build Farm <$me\@$host>";
202 $from_addr =~ tr /\r\n//d;
203
204
205
206 foreach my $clearme (@need_cleared)
207 {
208     my ($sysbranch, $setting) = @$clearme;
209     my ($animal, $branch) = ($sysbranch->{sysname},$sysbranch->{branch});
210     my $text;
211     if ($setting)
212     {
213         my $hours = sprintf("%.2f",($now - $sysbranch->{snapshot}) / 3600);
214         $text = "$sysbranch->{sysname} has now reported " .
215             "on $sysbranch->{branch} $hours hours ago.";
216     }
217     else
218     {
219         $text = "$sysbranch->{sysname} has lost alarm settings on branch: " .
220             "$sysbranch->{branch}. Resetting alarm to off.";
221     }
222     my $msg = new Mail::Send;
223
224     $msg->set('From',$from_addr);
225
226     $addr_sth->execute($animal);
227
228     my $mailto = $addr_sth->fetchrow_array;
229
230     print "sending clear to $mailto\n";
231
232     # $sth->finish;
233
234     $msg->to($mailto);
235     $msg->subject("PGBuildfarm member $animal Branch $branch Alert cleared");
236     my $fh = $msg->open;
237     print $fh "\n\n$text\n"; 
238     $fh->close;
239
240     print "alert cleared $animal $branch\n";
241 }
242
243 foreach my $clearme (@need_alerts)
244 {
245     my ($sysbranch, $setting) = @$clearme;
246     my ($animal, $branch) = ($sysbranch->{sysname},$sysbranch->{branch});
247     my $hours = sprintf("%.2f",($now - $sysbranch->{snapshot}) / 3600);
248     my $text = "$sysbranch->{sysname} has not reported " .
249         "on $sysbranch->{branch} for $hours hours.";
250     my $msg = new Mail::Send;
251
252     $msg->set('From',$from_addr);
253
254     $addr_sth->execute($animal);
255
256     my ($mailto) = $addr_sth->fetchrow_array;
257
258     # $sth->finish;
259
260     print "sending alert to $mailto\n";
261
262     $msg->to($mailto);
263
264     $msg->subject("PGBuildfarm member $animal Branch $branch " .
265                   "Alert notification");
266     my $fh = $msg->open;
267     print $fh "\n\n$text\n"; 
268     $fh->close;
269
270     print "alert sent $animal $branch\n";
271 }
272
273
274 print "=================================\n";
275
276
277