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