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