Initial commit
[clearscm.git] / clearadm / cleartasks.pl
1 #!/usr/bin/perl
2
3 =pod
4
5 =head1 NAME $RCSfile: cleartasks.pl,v $
6
7 Scrub Clearadm records
8
9 =head1 VERSION
10
11 =over
12
13 =item Author
14
15 Andrew DeFaria <Andrew@ClearSCM.com>
16
17 =item Revision
18
19 $Revision: 1.25 $
20
21 =item Created:
22
23 Sun Jan  2 19:40:28 EST 2011
24
25 =item Modified:
26
27 $Date: 2013/06/02 18:47:26 $
28
29 =back
30
31 =head1 SYNOPSIS
32
33  Usage cleartasks.pl: [-u|sage] [-ve|rbose] [-deb|ug]
34
35  Where:
36    -u|sage:     Displays usage
37  
38    -v|erbose:   Be verbose
39    -de|bug:     Output debug messages
40    
41    -da|emon:    Run in daemon mode (Default: yes)
42    -p|idfile:   File to be created with the pid written to it (Default:
43                 cleartasks.pid). Note: pidfile is only written if -daemon is
44                 specified.
45                        
46 =head1 DESCRIPTION
47
48 Examine the Clearadm schedule and perform the tasks required.
49
50 =cut
51
52 use strict;
53 use warnings;
54
55 use FindBin;
56 use Getopt::Long;
57
58 use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib";
59
60 use Clearadm;
61 use Clearexec;
62 use DateUtils;
63 use Display;
64 use TimeUtils;
65 use Utils;
66
67 my $VERSION  = '$Revision: 1.25 $';
68   ($VERSION) = ($VERSION =~ /\$Revision: (.*) /);
69
70 my $logfile = "$Clearadm::CLEAROPTS{CLEARADM_LOGDIR}/$FindBin::Script.log";           
71 my $pidfile = "$Clearadm::CLEAROPTS{CLEARADM_RUNDIR}/$FindBin::Script.pid";
72 my $daemon  = 1;
73
74 # Augment PATH with $Clearadm::CLEAROPTS{CLEARADM_BASE}
75 $ENV{PATH} .= ":$Clearadm::CLEAROPTS{CLEARADM_BASE}";
76
77 my ($clearadm, $clearexec);
78
79 sub HandleSystemNotCheckingIn (%) {
80   my (%system) = @_;
81    
82   my $startTime = time;
83   
84   my $message = "Unable to connect to system $system{name}:$system{port}";
85
86   my %runlog = (
87     task     => 'System checkin',
88     started  => Today2SQLDatetime,
89     status   => 1,
90     message  => $message,
91     system   => $system{name},
92   );
93
94   my ($err, $msg, $lastid) = $clearadm->AddRunlog (%runlog);
95   
96   $clearadm->Error ("Unable to add to runlog (Status: $err)\n$msg") if $err;
97    
98   # Check to see if we should notify anybody about this non-responding system
99   my %notification = $clearadm->GetNotification ('System checkin'); 
100           
101   my $when            = Today2SQLDatetime;
102   my $nomorethan      = lc $notification{nomorethan};
103   my $systemLink      = $Clearadm::CLEAROPTS{CLEARADM_WEBBASE};
104      $systemLink     .= "/systemdetails.cgi?system=$system{name}";
105   my $runlogLink      = $Clearadm::CLEAROPTS{CLEARADM_WEBBASE};
106      $runlogLink     .= "/runlog.cgi?id=$lastid";
107    my $subject         = "System is not responding (Is clearagent running?)";
108      $message = <<"END";      
109 <center>
110 <h1><font color="red">Alert</font> System not responding!</h1>
111 </center>
112
113 <p>On $when the system <a href="$systemLink">$system{name}</a> was <a 
114 href="$runlogLink">not responding</a> to clearagent requests. This can happen if
115 clearagent is not setup and running on the system.</p> 
116 END
117      
118   $clearadm->Notify (
119     $notification{name},
120     $subject,
121     $message,
122     'System Checkin',
123     $system{name},
124     undef,
125     $lastid,
126   );
127               
128   verbose "$system{name}: $subject";
129   
130   return;
131 } # HandleSystemNotCheckingIn
132
133 sub SystemsCheckin () {
134   foreach ($clearadm->FindSystem) {
135     my %system = %$_;
136     
137     next if $system{active} eq 'false';
138     
139     verbose "Contacting system $system{name}:$system{port}";
140     
141     my $startTime = time;
142     
143     my $status = $clearexec->connectToServer (
144       $system{name},
145       $system{port}
146     );
147     
148     unless ($status) {
149       HandleSystemNotCheckingIn %system;
150       next;
151     } # unless
152     
153     $clearexec->disconnectFromServer;
154     
155     verbose 'Successfully checked in with system: '
156           . "$system{name}:$system{port}";
157     
158     display __FILE__ . " DEBUG: System undefined 1" unless $system{name};
159     $clearadm->UpdateSystem (
160       $system{name},
161       (lastheardfrom => Today2SQLDatetime)
162     );
163   
164     $clearadm->ClearNotifications ($system{name})
165       if $system{notification} and $system{notification} eq 'Heartbeat';
166   } # foreach
167   
168   return;
169 } # SystemsCheckin
170
171 sub UpdateRunlog ($$$$) {
172   my ($status, $startTime, $task, $output) = @_;
173   
174   my %runlog = (
175     task    => $$task{name},
176     system  => $$task{system},
177     started => Today2SQLDatetime,
178   );
179
180   $runlog{status} = $status;
181     
182   if ($status == 0) {
183     if (@$output) {
184       $runlog{message} = join "\n", @$output;
185     } else {
186       $runlog{message}  = 'Successful execution of ';
187       $runlog{message} .= "$$task{name}: $$task{command}";
188     } # if
189   } else {
190     if (@$output) {
191       $runlog{message} = join "\n", @$output;
192     } else {
193       $runlog{message}  = 'Unable to execute ';
194       $runlog{message} .= "$$task{name}: $$task{command} ";
195       $runlog{message} .= join (' ', @$output);
196     } # if
197   } # if
198     
199   my ($err, $msg, $lastid) = $clearadm->AddRunlog (%runlog);
200     
201   $clearadm->Error ($msg, $err) if $err;
202
203   return $lastid;
204 } # UpdateRunlog
205
206 sub MakeSystemLink ($) {
207   my ($system) = @_;
208   
209   return "$Clearadm::CLEAROPTS{CLEARADM_WEBBASE}/systemdetails.cgi?system="
210        . $system;
211 } # MakeSystemLink
212
213 sub MakeLoadavgLink ($) {
214   my ($system) = @_;
215
216   return "$Clearadm::CLEAROPTS{CLEARADM_WEBBASE}/plot.cgi?type=loadavg&system="
217        . "$system&scaling=Hour&points=24";
218 } # MakeLoadavgLink
219
220 sub ProcessLoadavgErrors ($$$$@) {
221   # TODO: Also need to handle the case where the error was something other
222   # than "Load average over threshold". Perhaps by having different return
223   # status. Also, runlog entry #22169 never reported!
224   my ($notification, $task, $system, $lastid, @output) = @_;
225   
226   my $when = Today2SQLDatetime;
227   
228   foreach (@output) {
229     # We need to log this output. Write it to STDOUT
230     display $_;
231
232     my ($subject, $message, $currLoadavg, $threshold, $systemLink, $loadavgLink);
233
234     if (/System: (\w+) Loadavg (\d+\.\d+) Threshold (\d+\.\d+)/) {
235       $system       = $1;
236       $currLoadavg  = $2;
237       $threshold    = $3;
238       $systemLink   = MakeSystemLink $system;
239       $loadavgLink  = MakeLoadavgLink $system;
240       $subject      = "Load average of $currLoadavg exceeds threshold ";
241       $subject     .= "($threshold)";
242       $message      = <<"END";      
243 <center>
244 <h1><font color="red">Alert</font> Load Average is over the threshold!</h1>
245 </center>
246
247 <p>On $when the system <a href="$systemLink">$system</a>'s load avg
248 (<a href="$loadavgLink">$currLoadavg</a>) had exceeded the threshold set for
249 this system ($threshold).</p> 
250 END
251     } elsif (/ERROR.*system\s+(\S+):/) {
252       $system     = $1;
253       $systemLink = MakeSystemLink $system;
254       $subject    = "Error trying to obtain Loadavg";
255       $message    = <<"END";
256 <center>
257 <h1><font color="red">Alert</font> Unable to obtain Loadavg!</h1>
258 </center>
259
260 <p>On $when we were unable to obtain the Loadavg for
261 system <a href="$systemLink">$system</a>.</p>
262
263 <p>The following was the error message:</p>
264 <pre>$_</pre>
265 END
266     } else {
267       $message = <<"END";
268 <p>On $when on the system $system, we were unable to parse the Loadavg output. This is what we saw:</p>
269
270 <pre>
271 END
272       $message .= join "\n", @output;
273       $message .= "</pre>";
274       $clearadm->Error ($message, -1);
275       
276       last;
277     } # if
278
279     $clearadm->Notify (
280       $notification,
281       $subject,
282       $message,
283       $task,
284       $system,
285       undef,
286       $lastid,
287     );
288   } # foreach
289   
290   return;
291 } # ProcessLoadAvgErrors
292
293 sub ProcessFilesystemErrors ($$$$@) {
294   # TODO: Also need to handle the case where the error was something other
295   # than "Filesystem over threshold". Perhaps by having different return
296   # status.
297   my ($notification, $task, $system, $lastid, @output) = @_;
298
299   my $when = Today2SQLDatetime;
300
301   my %system;
302   
303   foreach (@output) {
304     # We need to log this output. Write it to STDOUT
305     display $_;
306     
307     if (/System:\s*(\S+)\s*Filesystem:\s*(\S+)\s*Used:\s*(\d+\.\d+)%\s*Threshold:\s*(\d+)/) {
308       my %fsinfo = (
309         filesystem => $2,
310         usedPct    => $3,
311         threshold  => $4
312       );
313       
314       if ($system{$1}) {
315          $system{$1} = [$system{$1}, \%fsinfo];
316       } else {
317         $system{$1} = \%fsinfo;
318       } # if
319     } # if
320   } # foreach
321    
322   foreach my $systemName (keys %system) {
323     my @fsinfo;
324     
325     if (ref $system{$systemName} eq 'HASH') {
326        push @fsinfo, $system{$systemName};
327     } else {
328        push @fsinfo, @{$system{$systemName}};
329     } # if
330
331     my $systemLink = MakeSystemLink ($systemName);
332     my $subject    = 'Filesystem has exceeded threshold';
333     my $message = <<"END";      
334 <center>
335 <h1><font color="red">Alert</font> Filesystem is over the threshold!</h1>
336 </center>
337
338 <p>On $when the following filesystems on <a href="$systemLink">$systemName</a>
339 were over their threshold.</p>
340
341 <ul>
342 END
343     foreach (@fsinfo) {
344       my %fsinfo = %{$_};
345       my $filesystemLink  = $Clearadm::CLEAROPTS{CLEARADM_WEBBASE};
346          $filesystemLink .= "/plot.cgi?type=filesystem&system=$systemName";
347          $filesystemLink .= "&filesystem=$fsinfo{filesystem}";
348          $filesystemLink .= '&scaling=Day&points=7';
349       $message .= "<li>Filesystem <a href=\"$filesystemLink\">";
350       $message .= "$fsinfo{filesystem}</a> is $fsinfo{usedPct}% full. Threshold is ";
351       $message .= "$fsinfo{threshold}%</li>";
352     } # foreach
353       
354     $message .= "</ul>";
355     
356     $clearadm->Notify (
357       $notification,
358       $subject,
359       $message,
360       $task,
361       $systemName,
362       undef,
363       $lastid,
364     );
365   } # foreach
366   
367   return;
368 } # ProcessFilesystemErrors
369
370 sub NonZeroReturn ($$$$$$) {
371   my ($system, $notification, $status, $lastid, $output, $task) = @_;
372
373   my @output = @{$output};
374   my %task   = %{$task};
375   
376   my $when = Today2SQLDatetime;
377     
378   my $subject      = "Non zero return from $task{command} "
379                    . "executing on $system";
380   my $taskLink     = $Clearadm::CLEAROPTS{CLEARADM_WEBBASE};
381      $taskLink    .= "/tasks.cgi?task=$task{name}";
382   my $similarLink  = $Clearadm::CLEAROPTS{CLEARADM_WEBBASE};
383      $similarLink .= "/runlog.cgi?system=$task{system}"
384                   . "&status=$status&"
385                   . "&task=$task{name}";
386   my $runlogLink   = $Clearadm::CLEAROPTS{CLEARADM_WEBBASE};
387      $runlogLink  .= "/runlog.cgi?id=$lastid";
388   my $message      = <<"END";
389 <center>
390 <h1><font color="red">Alert</font> Non zero status from script execution!</h1>
391 </center>
392
393 <p>On $when, while executing <a href="$taskLink">$task{name}</a> on
394 $task{system}, a non zero status of $status was returned. Here is the resulting
395 output:</p><blockquote><pre>
396 END
397
398   $message .= join "\n", @output;
399   $message .= <<"END";
400 </pre></blockquote>
401 <p>You may wish to examine the individual <a href="$runlogLink">runlog entry</a>
402 that caused this alert or a list of <a href="$similarLink">similar 
403 failures</a>.</p>
404 END
405
406   $message .= "</pre></blockquote>";
407   
408   $clearadm->Notify (
409     $notification,
410     $subject,
411     $message,
412     $task,
413     $system,
414     undef,
415     $lastid,
416   );
417   
418   return;   
419 } # NonZeroReturn
420
421 sub ExecuteTask ($%) {
422   my ($sleep, %task) = @_;
423   
424   my ($status, @output, %system, $subject, $message);
425
426   verbose_nolf "Performing task $task{name}";
427   
428   my %notification = $clearadm->GetNotification ($task{notification});
429        
430   my $startTime = time;
431   
432   if ($task{system} =~ /localhost/i) {
433     verbose " on localhost";
434     ($status, @output) = Execute "$task{command} 2>&1";
435   } else {
436     %system = $clearadm->GetSystem ($task{system});
437     
438     verbose " on $system{name}";
439
440     $status = $clearexec->connectToServer (
441       $system{name},
442       $system{port}
443     );
444     
445     unless ($status) {
446       $output[0] = "Unable to connect to system $system{name}:$system{port} to "
447                  . "execute $task{command}";
448       $status = -1;
449     } else {
450       ($status, @output) = $clearexec->execute ($task{command});
451       
452       $output[0] = "Unable to exec $task{command} on $system{name}"
453         if $status == -1;
454     } # unless
455     
456     $clearexec->disconnectFromServer;    
457   } # if
458
459   my $lastid = UpdateRunlog ($status, $startTime, \%task, \@output);
460     
461   if ($status != 0) {
462     if ($notification{cond}
463       and $notification{cond} =~ /non zero return/i) {
464       NonZeroReturn (
465         $system{name},
466         $notification{name},
467         $status,
468         $lastid,
469         \@output,
470         \%task
471       );
472     } elsif ($notification{cond} =~ /loadavg over threshold/i) {
473       ProcessLoadavgErrors ($notification{name}, $task{name}, $system{name}, $lastid, @output);
474     } elsif ($notification{cond} =~ /filesystem over threshold/i) {
475       ProcessFilesystemErrors ($notification{name}, $task{name}, $system{name}, $lastid, @output);
476     } # if
477   } else {
478     $clearadm->ClearNotifications ($task{system});
479   } # if
480         
481   my ($err, $msg) = $clearadm->UpdateSchedule (
482     $task{schedulename},
483     ( 'lastrunid' => $lastid ),
484   );
485     
486   $clearadm->Error ($msg, $err) if $err;  
487   
488   $sleep -= time - $startTime;
489   
490   return $sleep;
491 } # ExecuteTask
492
493 # Main
494 GetOptions (
495   'usage'     => sub { Usage },
496   'verbose'   => sub { set_verbose },
497   'debug'     => sub { set_debug },
498   'daemon!'   => \$daemon,
499   'pidfile=s' => \$pidfile,
500 ) or Usage "Invalid parameter";
501
502 Usage 'Extraneous options: ' . join ' ', @ARGV
503   if @ARGV;
504
505 EnterDaemonMode $logfile, $logfile, $pidfile
506   if $daemon;
507
508 display "$FindBin::Script V$VERSION started at " . localtime;
509
510 $clearadm  = Clearadm->new;
511 $clearexec = Clearexec->new;
512
513 $clearadm->SetNotify;
514
515 while () {
516   # First check in with all systems
517   SystemsCheckin;
518   
519   my ($sleep, @workItems) = $clearadm->GetWork;
520   
521   foreach (@workItems) {
522     my %scheduledTask = %{$_};
523     
524     $scheduledTask{system} ||= 'All systems';
525     
526     if ($scheduledTask{system} =~ /all systems/i) {
527       foreach my $system ($clearadm->FindSystem) {
528         $scheduledTask{system} = $$system{name};
529         $sleep = ExecuteTask $sleep, %scheduledTask;
530       } # foreach
531     } else {
532       $sleep = ExecuteTask $sleep, %scheduledTask;
533     } # if
534   } # foreach  
535   
536   if ($sleep > 0) {
537     verbose "Sleeping for $sleep seconds";
538     sleep $sleep;
539   } # if  
540 } # foreach
541
542 =pod
543
544 =head1 CONFIGURATION AND ENVIRONMENT
545
546 DEBUG: If set then $debug is set to this level.
547
548 VERBOSE: If set then $verbose is set to this level.
549
550 TRACE: If set then $trace is set to this level.
551
552 =head1 DEPENDENCIES
553
554 =head2 Perl Modules
555
556 L<FindBin>
557
558 L<Getopt::Long|Getopt::Long>
559
560 =head2 ClearSCM Perl Modules
561
562 =begin man 
563
564  Clearadm
565  Clearexec
566  DateUtils
567  Display
568  TimeUtils
569  Utils
570
571 =end man
572
573 =begin html
574
575 <blockquote>
576 <a href="http://clearscm.com/php/cvs_man.php?file=clearadm/lib/Clearadm.pm">Clearadm</a><br>
577 <a href="http://clearscm.com/php/cvs_man.php?file=clearadm/lib/Clearexec.pm">Clearexec</a><br>
578 <a href="http://clearscm.com/php/cvs_man.php?file=lib/DateUtils.pm">DateUtils</a><br>
579 <a href="http://clearscm.com/php/cvs_man.php?file=lib/Display.pm">Display</a><br>
580 <a href="http://clearscm.com/php/cvs_man.php?file=lib/TimeUtils.pm">TimeUtils</a><br>
581 <a href="http://clearscm.com/php/cvs_man.php?file=lib/Utils.pm">Utils</a><br>
582 </blockquote>
583
584 =end html
585
586 =head1 BUGS AND LIMITATIONS
587
588 There are no known bugs in this script
589
590 Please report problems to Andrew DeFaria <Andrew@ClearSCM.com>.
591
592 =head1 LICENSE AND COPYRIGHT
593
594 Copyright (c) 2010, ClearSCM, Inc. All rights reserved.
595
596 =cut