3 =head1 NAME $RCSfile: Clearadm.pm,v $
5 Object oriented interface to Clearadm.
13 Andrew DeFaria <Andrew@ClearSCM.com>
21 Tue Dec 07 09:13:27 EST 2010
25 $Date: 2012/11/09 06:43:26 $
31 Provides the Clearadm object which handles all interaction with the Clearadm
32 database. Similar add/change/delete/update methods for other record types. In
33 general you must orient your record hashs to have the appropriately named
34 keys that correspond to the database. Also see mothod documentation for
35 specifics about the method you are envoking.
37 # Create new Clearadm object
38 my $clearadm = new Clearadm;
43 alias => 'defaria.com',
44 admin => 'Andrew DeFaria',
45 os => 'Linux defaria.com 2.6.32-25-generic-pae #45-Ubuntu SMP Sat Oct 16 21:01:33 UTC 2010 i686 GNU/Linux',
47 description => 'Home server',
50 my ($err, $msg) = $clearadm->AddSystem(%system);
52 # Find systems matching 'jup'
53 my @systems = $clearadm->FindSystem('jup');
55 # Get a system by name
56 my %system = $clearadm->GetSystem('jupiter');
60 'region' => 'East Coast',
63 my ($err, $msg) = $clearadm->UpdateSystem ('jupiter', %update);
65 # Delete system (Warning: will delete all related records regarding this
67 my ($err, $msg) = $clearadm->DeleteSystem('jupiter');
71 This package provides and object oriented interface to the Clearadm database.
72 Methods are provided to manipulate records by adding, updating and deleting
73 them. In general you need to specify a hash which contains keys and values
74 corresponding to the database field names and values.
78 The following methods are available:
90 use Net::Domain qw(hostdomain);
95 use lib "$FindBin::Bin", "$FindBin::Bin/../../lib";
104 my $conf = dirname(__FILE__) . '/../etc/clearadm.conf';
106 our %CLEAROPTS = GetConfig($conf);
109 our $VERSION = '$Revision: 1.54 $';
110 ($VERSION) = ($VERSION =~ /\$Revision: (.*) /);
112 $CLEAROPTS{CLEARADM_USERNAME} = $ENV{CLEARADM_USERNAME}
113 ? $ENV{CLEARADM_USERNAME}
114 : $CLEAROPTS{CLEARADM_USERNAME}
115 ? $CLEAROPTS{CLEARADM_USERNAME}
117 $CLEAROPTS{CLEARADM_PASSWORD} = $ENV{CLEARADM_PASSWORD}
118 ? $ENV{CLEARADM_PASSWORD}
119 : $CLEAROPTS{CLEARADM_PASSWORD}
120 ? $CLEAROPTS{CLEARADM_PASSWORD}
122 $CLEAROPTS{CLEARADM_SERVER} = $ENV{CLEARADM_SERVER}
123 ? $ENV{CLEARADM_SERVER}
124 : $CLEAROPTS{CLEARADM_SERVER}
125 ? $CLEAROPTS{CLEARADM_SERVER}
128 my $defaultFilesystemThreshold = 90;
129 my $defaultFilesystemHist = '6 months';
130 my $defaultLoadavgHist = '6 months';
134 my ($self, $msg, $statement) = @_;
136 my $dberr = $self->{db}->err;
137 my $dberrmsg = $self->{db}->errstr;
140 $dberrmsg ||= 'Success';
145 my $function = (caller(1)) [3];
147 $message = "$function: $msg\nError #$dberr: $dberrmsg\n"
148 . "SQL Statement: $statement";
151 return $dberr, $message;
154 sub _formatValues(@) {
155 my ($self, @values) = @_;
160 push @returnValues, $_ eq '' ? 'null' : $self->{db}->quote($_) for (@values);
162 return @returnValues;
165 sub _formatNameValues(%) {
166 my ($self, %rec) = @_;
170 push @nameValueStrs, "$_=" . $self->{db}->quote($rec{$_}) for (keys %rec);
172 return @nameValueStrs;
173 } # _formatNameValues
176 my ($self, $table, %rec) = @_;
178 my $statement = "insert into $table (";
179 $statement .= join ',', keys %rec;
180 $statement .= ') values (';
181 $statement .= join ',', $self->_formatValues(values %rec);
186 $self->{db}->do($statement);
188 return $self->_dberror("Unable to add record to $table", $statement);
191 sub _deleteRecord($;$) {
192 my ($self, $table, $condition) = @_;
196 my $statement = "select count(*) from $table ";
197 $statement .= "where $condition" if $condition;
199 my $sth = $self->{db}->prepare($statement)
200 or return $self->_dberror('Unable to prepare statement', $statement);
203 or return $self->_dberror('Unable to execute statement', $statement);
205 my @row = $sth->fetchrow_array;
215 return ($count, 'Records deleted') if $count == 0;
217 $statement = "delete from $table ";
218 $statement .= "where $condition" if $condition;
220 $self->{db}->do($statement);
222 if ($self->{db}->err) {
223 return $self->_dberror("Unable to delete record from $table", $statement);
225 return $count, 'Records deleted';
229 sub _updateRecord($$%) {
230 my ($self, $table, $condition, %rec) = @_;
232 my $statement = "update $table set ";
233 $statement .= join ',', $self->_formatNameValues(%rec);
234 $statement .= " where $condition" if $condition;
236 $self->{db}->do($statement);
238 return $self->_dberror("Unable to update record in $table", $statement);
241 sub _checkRequiredFields($$) {
242 my ($fields, $rec) = @_;
244 for my $fieldname (@$fields) {
248 if ($fieldname eq $_) {
254 return "$fieldname is required" unless $found;
258 } # _checkRequiredFields
260 sub _getRecords($$;$) {
261 my ($self, $table, $condition, $additional) = @_;
267 my $statement = "select * from $table";
268 $statement .= " where $condition" if $condition;
269 $statement .= $additional;
271 my $sth = $self->{db}->prepare($statement);
274 ($err, $msg) = $self->_dberror('Unable to prepare statement', $statement);
284 # We've been having the server going away. Supposedly it should reconnect so
285 # here we simply retry up to $maxAttempts times to re-execute the statement.
286 # (Are there other places where we need to do this?)
289 while ($err == 2006 and $attempts++ < $maxAttempts) {
290 $status = $sth->execute;
296 ($err, $msg) = $self->_dberror('Unable to execute statement',
302 croak $msg unless $err == 2006;
304 my $timestamp = YMDHMS;
306 $self->Error("$timestamp: Unable to talk to DB server.\n\n$msg\n\n"
307 . "Will try again in $sleepTime seconds", -1);
310 $self->_connect($self->{dbserver});
315 $self->Error("After $maxAttempts attempts I could not connect to the database", $err)
316 if ($err == 2006 and $attempts > $maxAttempts);
320 while (my $row = $sth->fetchrow_hashref) {
327 sub _aliasSystem($) {
328 my ($self, $system) = @_;
330 my %system = $self->GetSystem($system);
333 return $system{name};
342 my $statement = 'select last_insert_id()';
344 my $sth = $self->{db}->prepare($statement);
349 ($err, $msg) = $self->_dberror('Unable to prepare statement', $statement);
354 my $status = $sth->execute;
357 ($err, $msg) = $self->_dberror('Unable to execute statement', $statement);
364 my @row = $sth->fetchrow_array;
370 my ($self, $dbserver) = @_;
372 $dbserver ||= $CLEAROPTS{CLEARADM_SERVER};
374 my $dbname = 'clearadm';
375 my $dbdriver = 'mysql';
377 $self->{db} = DBI->connect(
378 "DBI:$dbdriver:$dbname:$dbserver",
379 $CLEAROPTS{CLEARADM_USERNAME},
380 $CLEAROPTS{CLEARADM_PASSWORD},
383 "Couldn't connect to $dbname database "
384 . "as $CLEAROPTS{CLEARADM_USERNAME}\@$CLEAROPTS{CLEARADM_SERVER}"
387 $self->{dbserver} = $dbserver;
393 my ($class, $dbserver) = @_;
395 my $self = bless {}, $class;
397 $self->_connect($dbserver);
405 $self->{NOTIFY} = $CLEAROPTS{CLEARADM_NOTIFY};
411 my ($self, $msg, $errno) = @_;
413 # If $errno is specified we need to stop. However we need to notify somebody
414 # that cleartasks is no longer running.
418 if ($self->{NOTIFY}) {
420 to => $self->{NOTIFY},
421 subject => 'Internal error occurred in Clearadm',
422 data => "<p>An unexpected, internal error occurred in Clearadm:</p><p>$msg</p>",
426 exit $errno if $errno > 0;
434 my ($self, %system) = @_;
436 my @requiredFields = (
440 my $result = _checkRequiredFields \@requiredFields, \%system;
442 return -1, "AddSystem: $result" if $result;
444 $system{loadavgHist} ||= $defaultLoadavgHist;
446 return $self->_addRecord('system', %system);
449 sub DeleteSystem($) {
450 my ($self, $name) = @_;
452 return $self->_deleteRecord('system', "name='$name'");
455 sub UpdateSystem ($%) {
456 my ($self, $name, %update) = @_;
458 return $self->_updateRecord('system', "name='$name'", %update);
462 my ($self, $system) = @_;
464 return unless $system;
466 my @records = $self->_getRecords(
468 "name='$system' or alias like '%$system%'"
472 return %{$records[0]};
479 my ($self, $system) = @_;
483 my $condition = "name like '%$system%' or alias like '%$system%'";
485 return $self->_getRecords('system', $condition);
488 sub SearchSystem(;$) {
\r
489 my ($self, $condition) = @_;
491 $condition = "name like '%'" unless $condition;
493 return $self->_getRecords('system', $condition);
\r
497 my ($self, %package) = @_;
499 my @requiredFields = (
505 my $result = _checkRequiredFields \@requiredFields, \%package;
507 return -1, "AddPackage: $result" if $result;
509 return $self->_addRecord('package', %package);
512 sub DeletePackage($$) {
513 my ($self, $system, $name) = @_;
515 return $self->_deleteRecord(
517 "(system='$system' or alias='$system') and name='$name'");
520 sub UpdatePackage($$%) {
521 my ($self, $system, $name, %update) = @_;
523 $system = $self->_aliasSystem($system);
525 return unless $system;
527 return $self->_updateRecord('package', "system='$system'", %update);
531 my ($self, $system, $name) = @_;
533 $system = $self->_aliasSystem($system);
535 return unless $system;
538 my @records = $self->_getRecords(
540 "system='$system' and name='$name'"
544 return %{$records[0]};
550 sub FindPackage($;$) {
551 my ($self, $system, $name) = @_;
555 $system = $self->_aliasSystem($system);
557 return unless $system;
559 my $condition = "system='$system' and name like '%$name%'";
561 return $self->_getRecords('package', $condition);
564 sub AddFilesystem(%) {
565 my ($self, %filesystem) = @_;
567 my @requiredFields = (
573 my $result = _checkRequiredFields \@requiredFields, \%filesystem;
575 return -1, "AddFilesystem: $result" if $result;
577 # Default filesystem threshold
578 $filesystem{threshold} ||= $defaultFilesystemThreshold;
580 return $self->_addRecord('filesystem', %filesystem);
583 sub DeleteFilesystem($$) {
584 my ($self, $system, $filesystem) = @_;
586 $system = $self->_aliasSystem($system);
588 return unless $system;
590 return $self->_deleteRecord(
592 "system='$system' and filesystem='$filesystem'"
596 sub UpdateFilesystem($$%) {
597 my ($self, $system, $filesystem, %update) = @_;
599 $system = $self->_aliasSystem($system);
601 return unless $system;
603 return $self->_updateRecord(
605 "system='$system' and filesystem='$filesystem'",
610 sub GetFilesystem($$) {
611 my ($self, $system, $filesystem) = @_;
613 $system = $self->_aliasSystem($system);
615 return unless $system;
616 return unless $filesystem;
618 my @records = $self->_getRecords(
620 "system='$system' and filesystem='$filesystem'"
624 return %{$records[0]};
630 sub FindFilesystem($;$) {
631 my ($self, $system, $filesystem) = @_;
635 $system = $self->_aliasSystem($system);
637 return unless $system;
639 my $condition = "system='$system' and filesystem like '%$filesystem%'";
641 return $self->_getRecords('filesystem', $condition);
645 my ($self, %vob) = @_;
647 my @requiredFields = (
652 my $result = _checkRequiredFields \@requiredFields, \%vob;
654 return -1, "AddVob: $result" if $result;
656 return $self->_addRecord('vob', %vob);
660 my ($self, $tag, $region) = @_;
662 return $self->_deleteRecord('vob', "tag='$tag' and region='$region'");
666 my ($self, $tag, $region) = @_;
670 # Windows vob tags begin with "\", which is problematic. The solution is to
674 my @records = $self->_getRecords('vob', "tag='$tag' and region='$region'");
677 return %{$records[0]};
683 sub FindVobStorage(;$$) {
684 my ($self, $tag, $region) = @_;
689 # Windows vob tags begin with "\", which is problematic. The solution is to
693 my $condition = "tag like '%$tag%'";
695 $condition .= " and region='$region'" if $region;
697 return $self->_getRecords('vobstorage', $condition);
701 my ($self, $tag, $region) = @_;
706 # Windows vob tags begin with "\", which is problematic. The solution is to
710 my $condition = "tag like '%$tag%'";
712 $condition .= " and region='$region'" if $region;
714 return $self->_getRecords('vob', $condition);
718 my ($self, %vob) = @_;
720 # Windows vob tags begin with "\", which is problematic. The solution is to
722 my $vobtag = $vob{tag};
724 $vobtag =~ s/^\\/\\\\/;
726 return $self->_updateRecord('vob', "tag='$vobtag' and region='$vob{region}'", %vob);
730 my ($self, %view) = @_;
732 my @requiredFields = (
737 my $result = _checkRequiredFields \@requiredFields, \%view;
739 return -1, "AddView: $result" if $result;
741 return $self->_addRecord('view', %view);
745 my ($self, $tag, $region) = @_;
747 return $self->_deleteRecord('vob', "tag='$tag' and region='$region'");
751 my ($self, %view) = @_;
753 return $self->_updateRecord('view', "tag='$view{tag}' and region='$view{region}'", %view);
757 my ($self, $tag, $region) = @_;
761 my @records = $self->_getRecords('view', "tag='$tag' and region='$region'");
764 return %{$records[0]};
770 sub FindView(;$$$$) {
771 my ($self, $tag, $region, $ownerName) = @_;
776 push @conditions, "tag like '%$tag%'" if $tag;
777 push @conditions, "region = '$region'" if $region;
778 push @conditions, "ownerName like '$ownerName'" if $ownerName;
780 $condition = join " and ", @conditions if @conditions;
782 return $self->_getRecords('view', $condition);
786 my ($self, %fs) = @_;
788 my @requiredFields = (
793 my $result = _checkRequiredFields \@requiredFields, \%fs;
795 return -1, "AddFS: $result"
799 $fs{timestamp} = Today2SQLDatetime;
801 return $self->_addRecord('fs', %fs);
805 my ($self, $system, $filesystem) = @_;
807 my %filesystem = $self->GetFilesystem($system, $filesystem);
809 return unless %filesystem;
811 my %task = $self->GetTask('scrub');
813 $self->Error("Unable to find scrub task!", 1) unless %task;
816 my $today = Today2SQLDatetime;
818 # TODO: SubtractDays uses just an approximation (i.e. subtracting 30 days when
819 # in February is not right.
820 if ($filesystem{filesystemHist} =~ /(\d+) month/i) {
822 } elsif ($filesystem{filesystemHist} =~ /(\d+) year/i) {
826 my $oldage = SubtractDays $today, $days;
828 my ($dberr, $dbmsg) = $self->_deleteRecord(
830 "system='$system' and filesystem='$filesystem' and timestamp<='$oldage'"
833 if ($dbmsg eq 'Records deleted') {
834 return (0, $dbmsg) if $dberr == 0;
838 $runlog{task} = $task{name};
839 $runlog{started} = $today;
842 "Scrubbed $dberr fs records for filesystem $system:$filesystem";
844 my ($err, $msg) = $self->AddRunlog(%runlog);
846 $self->Error("Unable to add runlog - (Error: $err)\n$msg") if $err;
849 return ($dberr, $dbmsg);
853 my ($self, $system) = @_;
855 my %system = $self->GetSystem($system);
857 return unless %system;
859 my %task = $self->GetTask('loadavg');
861 $self->Error("Unable to find loadavg task!", 1) unless %task;
864 my $today = Today2SQLDatetime;
866 # TODO: SubtractDays uses just an approximation (i.e. subtracting 30 days when
867 # in February is not right.
868 if ($system{loadavgHist} =~ /(\d+) month/i) {
870 } elsif ($system{loadavgHist} =~ /(\d+) year/i) {
874 my $oldage = SubtractDays $today, $days;
876 my ($dberr, $dbmsg) = $self->_deleteRecord(
878 "system='$system' and timestamp<='$oldage'"
881 if ($dbmsg eq 'Records deleted') {
882 return (0, $dbmsg) if $dberr == 0;
886 $runlog{task} = $task{name};
887 $runlog{started} = $today;
890 "Scrubbed $dberr loadavg records for system $system";
892 my ($err, $msg) = $self->AddRunlog(%runlog);
894 $self->Error("Unable to add runload (Error: $err)\n$msg") if $err;
897 return ($dberr, $dbmsg);
900 sub TrimStorage($$$) {
901 my ($self, $type, $tag, $region) = @_;
903 my $today = Today2SQLDatetime;
905 my $oldage = SubtractDays $today, $Clearadm::CLEAROPTS{CLEARADM_SCRUBDAYS};
907 my $table = $type =~ /vob/i
911 my ($dberr, $dbmsg) = $self->_deleteRecord(
913 "tag='$tag' and region='$region' and timestamp<='$oldage'"
916 if ($dbmsg eq 'Records deleted') {
917 return (0, $dbmsg) if $dberr == 0;
921 $runlog{task} = 'Scrub';
922 $runlog{started} = $today;
925 "Scrubbed $dberr ${type}storage records";
927 my ($err, $msg) = $self->AddRunlog(%runlog);
929 $self->Error("Unable to add runload (Error: $err)\n$msg") if $err;
932 return ($dberr, $dbmsg);
935 sub ValidateCCObjects() {
938 my $vobRecordsDeleted = 0;
939 my $viewRecordsDeleted = 0;
941 for my $region ($Clearcase::CC->regions) {
942 for my $type (qw(vob view)) {
944 verbose "Processing ${type}s in $region";
946 if ($type eq 'vob') {
947 verbose "Finding all vobs in region $region";
948 @ccobjs = $self->FindVob(undef, $region);
949 verbose 'Found ' . scalar @ccobjs . ' vobs to process';
950 } elsif ($type eq 'view') {
951 verbose "Finding all views in region $region";
952 @ccobjs = $self->FindView(undef, $region);
953 verbose 'Found ' . scalar @ccobjs . ' views to process';
956 for my $object (@ccobjs) {
957 my %ccobjrec = %$object;
959 verbose "Processing $ccobjrec{tag}:$ccobjrec{region}";
963 if ($type eq 'vob') {
964 $ccobj = Clearcase::Vob->new($ccobjrec{tag}, $ccobjrec{region});
966 $ccobj = Clearcase::View->new($ccobjrec{tag}, $ccobjrec{region});
969 verbose_nolf 'Checking if ' . $ccobj->{tag} . ':' . $ccobj->{region} . ' exists anymore...';
971 if ($ccobj->exists) {
972 verbose ' it does! Skipping...';
975 verbose ' it doesn\'t!';
978 #next if $ccobj->exists;
980 verbose "Deleting $type $ccobjrec{tag}:$ccobjrec{region}";
982 my ($recordsDeleted, $msg) = $self->_deleteRecord($type,
983 "tag='$ccobjrec{tag}' and region='$ccobjrec{region}'");
985 if ($msg ne 'Records deleted') {
986 return ($recordsDeleted, $msg);
988 $viewRecordsDeleted += $recordsDeleted if $type eq 'view';
989 $vobRecordsDeleted += $recordsDeleted if $type eq 'vob';
995 return ($viewRecordsDeleted, $vobRecordsDeleted);
996 } # ValidateCCObjects
999 my ($self, $system, $filesystem, $start, $end, $count, $interval) = @_;
1001 $system = $self->_aliasSystem($system);
1003 return unless $system;
1004 return unless $filesystem;
1006 $interval ||= 'Minute';
1008 my $size = $interval =~ /month/i
1010 : $interval =~ /day/i
1012 : $interval =~ /hour/i
1016 undef $start if $start and $start =~ /earliest/i;
1017 undef $end if $end and $end =~ /latest/i;
1019 my $condition = "system='$system' and filesystem='$filesystem'";
1020 $condition .= " and timestamp>='$start'" if $start;
1021 $condition .= " and timestamp<='$end'" if $end;
1023 $condition .= " group by left(timestamp,$size)";
1026 # We can't simply do a "limit 0, $count" as that just gets the front end of
1027 # the records return (i.e. if $count = say 10 and the timestamp range
1028 # returns 40 rows we'll see only rows 1-10, not rows 31-40). We need limit
1029 # $offset, $count where $offset = the number of qualifying records minus
1031 my $nbrRecs = $self->Count('fs', $condition);
1032 my $offset = $nbrRecs - $count;
1034 # Offsets of < 0 are not allowed.
1038 $condition .= " limit $offset, $count";
1041 my $statement = <<"END";
1046 left(timestamp,$size) as timestamp,
1058 my $sth = $self->{db}->prepare($statement);
1061 ($err, $msg) = $self->_dberror('Unable to prepare statement', $statement);
1066 my $status = $sth->execute;
1069 ($err, $msg) = $self->_dberror('Unable to execute statement', $statement);
1076 while (my $row = $sth->fetchrow_hashref) {
1077 push @records, $row;
1083 sub GetLatestFS($$) {
1084 my ($self, $system, $filesystem) = @_;
1086 $system = $self->_aliasSystem($system);
1088 return unless $system;
1089 return unless $filesystem;
1091 my @records = $self->_getRecords(
1093 "system='$system' and filesystem='$filesystem'"
1094 . " order by timestamp desc limit 0, 1",
1098 return %{$records[0]};
1105 my ($self, %loadavg) = @_;
1107 my @requiredFields = (
1111 my $result = _checkRequiredFields \@requiredFields, \%loadavg;
1113 return -1, "AddLoadavg: $result" if $result;
1116 $loadavg{timestamp} = Today2SQLDatetime;
1118 return $self->_addRecord('loadavg', %loadavg);
1121 sub GetLoadavg($;$$$$) {
1122 my ($self, $system, $start, $end, $count, $interval) = @_;
1124 $system = $self->_aliasSystem($system);
1126 return unless $system;
1128 $interval ||= 'Minute';
1130 my $size = $interval =~ /month/i
1132 : $interval =~ /day/i
1134 : $interval =~ /hour/i
1140 undef $start if $start and $start =~ /earliest/i;
1141 undef $end if $end and $end =~ /latest/i;
1143 $condition .= " system='$system'" if $system;
1144 $condition .= " and timestamp>='$start'" if $start;
1145 $condition .= " and timestamp<='$end'" if $end;
1147 $condition .= " group by left(timestamp,$size)";
1150 # We can't simply do a "limit 0, $count" as that just gets the front end of
1151 # the records return (i.e. if $count = say 10 and the timestamp range
1152 # returns 40 rows we'll see only rows 1-10, not rows 31-40). We need limit
1153 # $offset, $count where $offset = the number of qualifying records minus
1155 my $nbrRecs = $self->Count('loadavg', $condition);
1156 my $offset = $nbrRecs - $count;
1158 # Offsets of < 0 are not allowed.
1159 $offset = 0 if $offset < 0;
1161 $condition .= " limit $offset, $count";
1164 my $statement = <<"END";
1167 left(timestamp,$size) as timestamp,
1170 avg(loadavg) as loadavg
1178 my $sth = $self->{db}->prepare($statement);
1181 ($err, $msg) = $self->_dberror('Unable to prepare statement', $statement);
1186 my $status = $sth->execute;
1189 ($err, $msg) = $self->_dberror('Unable to execute statement', $statement);
1196 while (my $row = $sth->fetchrow_hashref) {
1197 push @records, $row;
1203 sub GetLatestLoadavg($) {
1204 my ($self, $system) = @_;
1206 $system = $self->_aliasSystem($system);
1208 return unless $system;
1210 my @records = $self->_getRecords(
1213 . " order by timestamp desc limit 0, 1",
1217 return %{$records[0]};
1221 } # GetLatestLoadavg
1223 sub GetStoragePool($$$;$$$$$) {
1224 my ($self, $type, $tag, $storage, $region, $start, $end, $count, $interval) = @_;
1226 $interval ||= 'Day';
1227 $region ||= $Clearcase::CC->region;
1229 return unless $type =~ /vob/i or $type =~ /view/;
1231 my $size = $interval =~ /month/i
1233 : $interval =~ /day/i
1235 : $interval =~ /hour/i
1239 undef $start if $start and $start =~ /earliest/i;
1240 undef $end if $end and $end =~ /latest/i;
1242 # Windows vob tags begin with "\", which is problematic. The solution is to
1244 $tag =~ s/^\\/\\\\/;
1247 my $table = $type eq 'vob' ? 'vobstorage' : 'viewstorage';
1249 $condition = "tag='$tag' and region='$region'";
1250 $condition .= " and timestamp>='$start'" if $start;
1251 $condition .= " and timestamp<='$end'" if $end;
1253 $condition .= " group by left(timestamp,$size)";
1256 # We can't simply do a "limit 0, $count" as that just gets the front end of
1257 # the records return (i.e. if $count = say 10 and the timestamp range
1258 # returns 40 rows we'll see only rows 1-10, not rows 31-40). We need limit
1259 # $offset, $count where $offset = the number of qualifying records minus
1261 my $nbrRecs = $self->Count($table, $condition);
1262 my $offset = $nbrRecs - $count;
1264 # Offsets of < 0 are not allowed.
1265 $offset = 0 if $offset < 0;
1267 $condition .= " limit $offset, $count";
1270 my $statement = <<"END";
1274 left(timestamp,$size) as timestamp,
1275 avg($storage) as size
1283 my $sth = $self->{db}->prepare($statement);
1286 ($err, $msg) = $self->_dberror('Unable to prepare statement', $statement);
1291 my $status = $sth->execute;
1294 ($err, $msg) = $self->_dberror('Unable to execute statement', $statement);
1301 while (my $row = $sth->fetchrow_hashref) {
1302 push @records, $row;
1309 my ($self, %task) = @_;
1311 my @requiredFields = (
1316 my $result = _checkRequiredFields \@requiredFields, \%task;
1318 return -1, "AddTask: $result" if $result;
1320 return $self->_addRecord('task', %task);
1324 my ($self, $name) = @_;
1326 return $self->_deleteRecord('task', "name='$name'");
1330 my ($self, $name) = @_;
1334 my $condition = "name like '%$name%'";
1336 return $self->_getRecords('task', $condition);
1340 my ($self, $name) = @_;
1342 return unless $name;
1344 my @records = $self->_getRecords('task', "name='$name'");
1347 return %{$records[0]};
1353 sub UpdateTask($%) {
1354 my ($self, $name, %update) = @_;
1356 return $self->_updateRecord('task', "name='$name'", %update);
1359 sub AddSchedule(%) {
1360 my ($self, %schedule) = @_;
1362 my @requiredFields = (
1366 my $result = _checkRequiredFields \@requiredFields, \%schedule;
1368 return -1, "AddSchedule: $result" if $result;
1370 return $self->_addRecord('schedule', %schedule);
1373 sub DeleteSchedule($) {
1374 my ($self, $name) = @_;
1376 return $self->_deleteRecord('schedule', "name='$name'");
1379 sub FindSchedule(;$$) {
1380 my ($self, $name, $task) = @_;
1385 my $condition = "name like '%$name%'";
1386 $condition .= ' and ';
1387 $condition .= "task like '%$task%'";
1389 return $self->_getRecords('schedule', $condition);
1392 sub GetSchedule($) {
1393 my ($self, $name) = @_;
1395 my @records = $self->_getRecords('schedule', "name='$name'");
1398 return %{$records[0]};
1404 sub UpdateSchedule($%) {
1405 my ($self, $name, %update) = @_;
1407 return $self->_updateRecord('schedule', "name='$name'", %update);
1411 my ($self, %runlog) = @_;
1413 my @requiredFields = (
1417 my $result = _checkRequiredFields \@requiredFields, \%runlog;
1419 return -1, "AddRunlog: $result" if $result;
1421 $runlog{ended} = Today2SQLDatetime;
1423 $runlog{system} = hostname if $runlog{system} =~ /localhost/i;
1425 my ($err, $msg) = $self->_addRecord('runlog', %runlog);
1427 return ($err, $msg, $self->_getLastID);
1430 sub DeleteRunlog($) {
1431 my ($self, $condition) = @_;
1433 return $self->_deleteRecord('runlog', $condition);
1436 sub FindRunlog(;$$$$$$) {
1437 my ($self, $task, $system, $status, $id, $start, $page) = @_;
1439 # If ID is specified then that's all that really matters as it uniquely
1440 # identifies a runlog entry;
1441 my ($condition, $conditions);
1445 if ($task !~ /all/i) {
1447 $condition = "task like '%$task%'";
1450 if ($system !~ /all/i) {
1451 $condition .= ' and ' if $conditions;
1452 $condition .= "system like '%$system%'";
1457 $condition .= ' and ' if $conditions;
1459 if ($status =~ /!(-*\d+)/) {
1460 $condition .= "status<>$1";
1462 $condition .= "status=$status"
1466 # Need defined here as $start may be 0!
1467 if (defined $start) {
1469 $limit = "limit $start, $page";
1472 $condition = "id=$id";
1475 return $self->_getRecords('runlog', $condition, " order by started desc $limit");
1479 my ($self, $id) = @_;
1483 my @records = $self->_getRecords('runlog', "id=$id");
1486 return %{$records[0]};
1492 sub UpdateRunlog($%) {
1493 my ($self, $id, %update) = @_;
1495 return $self->_updateRecord('runlog', "id=$id", %update);
1499 my ($self, $table, $condition) = @_;
1501 $condition = $condition ? 'where ' . $condition : '';
1505 my $statement = "select count(*) from $table $condition";
1507 my $sth = $self->{db}->prepare($statement);
1510 ($err, $msg) = $self->_dberror('Unable to prepare statement', $statement);
1515 my $status = $sth->execute;
1518 ($err, $msg) = $self->_dberror('Unable to execute statement', $statement);
1523 # Hack! Statements such as the following:
1525 # select count(*) from fs where system='jupiter' and filesystem='/dev/sdb5'
1526 # > group by left(timestamp,10);
1538 # 7 rows in set (0.00 sec)
1540 # Here we want 7 but what we see in $records[0] is 49. So the hack is that if
1541 # statement contains "group by" then we assume we have the above and return
1542 # scalar @records, otherwise we return $records[0];
1543 if ($statement =~ /group by/i) {
1544 my $allrows = $sth->fetchall_arrayref;
1546 return scalar @{$allrows};
1548 my @records = $sth->fetchrow_array;
1554 # GetWork returns two items, the number of seconds to wait before the next task
1555 # and array of hash records of work to be done immediately. The caller should
1556 # execute the work to be done, timing it, and subtracting it from the $sleep
1557 # time returned. If the caller exhausts the $sleep time then they should call
1564 my $statement = <<"END";
1566 schedule.name as schedulename,
1568 task.system as system,
1570 schedule.notification,
1572 runlog.started as lastrun
1575 schedule left join runlog on schedule.lastrunid=runlog.id
1577 schedule.task=task.name
1578 and schedule.active='true'
1582 my $sth = $self->{db}->prepare($statement);
1585 ($err, $msg) = $self->_dberror('Unable to prepare statement', $statement);
1590 my $status = $sth->execute;
1593 ($err, $msg) = $self->_dberror('Unable to execute statement', $statement);
1601 while (my $row = $sth->fetchrow_hashref) {
1602 if ($$row{system} !~ /localhost/i) {
1603 my %system = $self->GetSystem($$row{system});
1605 # Skip inactive systems
1606 next if $system{active} eq 'false';
1609 # If started is not defined then this task was never run so run it now.
1610 unless ($$row{lastrun}) {
1611 push @records, $row;
1615 # TODO: Handle frequencies better.
1618 if ($$row{frequency} =~ /(\d+) seconds/i) {
1620 } elsif ($$row{frequency} =~ /(\d+) minute/i) {
1622 } elsif ($$row{frequency} =~ /(\d+) hour/i) {
1623 $seconds = $1 * 60 * 60;
1624 } elsif ($$row{frequency} =~ /(\d+) day/i) {
1625 $seconds= $1 * 60 * 60 * 24;
1627 warning "Don't know how to handle frequencies like $$row{frequency}";
1631 my $today = Today2SQLDatetime;
1632 my $lastrun = Add($$row{lastrun}, (seconds => $seconds));
1633 my $waitTime = DateToEpoch($lastrun) - DateToEpoch($today);
1635 if ($waitTime < 0) {
1636 # We're late - push this onto records and move on
1637 push @records, $row;
1640 $sleep ||= $waitTime;
1642 if ($sleep > $waitTime) {
1647 # Even if there is nothing to do the caller should sleep a bit and come back
1648 # to us. So if it ends up there's nothing past due, and nothing upcoming, then
1649 # sleep for a minute and return here. Somebody may have added a new task next
1650 # time we're called.
1651 if (@records == 0 and not $sleep) {
1655 return ($sleep, @records);
1658 sub GetUniqueList($$) {
1659 my ($self, $table, $field) = @_;
1663 my $statement = "select $field from $table group by $field";
1665 my $sth = $self->{db}->prepare($statement);
1668 ($err, $msg) = $self->_dberror('Unable to prepare statement', $statement);
1673 my $status = $sth->execute;
1676 ($err, $msg) = $self->_dberror('Unable to execute statement', $statement);
1683 while (my @row = $sth->fetchrow_array) {
1685 push @values, $row[0];
1687 push @values, '<NULL>';
1695 my ($self, %alert) = @_;
1697 my @requiredFields = (
1702 my $result = _checkRequiredFields \@requiredFields, \%alert;
1704 return -1, "AddAlert: $result" if $result;
1706 return $self->_addRecord('alert', %alert);
1709 sub DeleteAlert($) {
1710 my ($self, $name) = @_;
1712 return $self->_deleteRecord('alert', "name='$name'");
1716 my ($self, $alert) = @_;
1720 my $condition = "name like '%$alert%'";
1722 return $self->_getRecords('alert', $condition);
1726 my ($self, $name) = @_;
1731 my @records = $self->_getRecords('alert', "name='$name'");
1734 return %{$records[0]};
1740 sub SendAlert($$$$$$$) {
1752 my $footing = '<hr><p style="text-align: center;">';
1753 $footing .= '<font color="#bbbbbb">';
1754 my $year = (localtime)[5] + 1900;
1755 $footing .= "<a href='$CLEAROPTS{CLEARADM_WEBBASE}'>Clearadm</a><br>";
1756 $footing .= "Copyright © $year, ClearSCM, Inc. - All rights reserved";
1758 my %alert = $self->GetAlert($alert);
1760 if ($alert{type} eq 'email') {
1761 my $from = 'Clearadm@' . hostdomain;
1766 subject => "Clearadm Alert: $system: $subject",
1769 footing => $footing,
1772 $self->Error("Don't know how to send $alert{type} alerts\n"
1773 . "Subject: $subject\n"
1774 . "Message: $message", 1);
1781 notification => $notification,
1782 runlog => $runlogID,
1783 timestamp => Today2SQLDatetime,
1784 message => $subject,
1787 return $self->AddAlertlog(%alertlog);
1790 sub GetLastAlert($$) {
1791 my ($self, $notification, $system) = @_;
1793 my $statement = <<"END";
1800 notification='$notification'
1801 and system='$system'
1808 my $sth = $self->{db}->prepare($statement)
1809 or return $self->_dberror('Unable to prepare statement', $statement);
1812 or return $self->_dberror('Unable to execute statement', $statement);
1814 my $alertlog= $sth->fetchrow_hashref;
1825 sub GetLastTaskFailure($$) {
1826 my ($self, $task, $system) = @_;
1828 my $statement = <<"END";
1837 and system='$system'
1845 my $sth = $self->{db}->prepare($statement)
1846 or return $self->_dberror('Unable to prepare statement', $statement);
1849 or return $self->_dberror('Unable to execute statement', $statement);
1851 my $runlog= $sth->fetchrow_hashref;
1855 if ($$runlog{ended}) {
1859 # If we didn't get any ended in the last call then there's nothing that
1860 # qualified. Still let's return a record (%runlog) that has a valid id so
1861 # that the caller can update that runlog with alerted = 'true'.
1862 $statement = <<"END";
1870 and system='$system'
1877 $sth = $self->{db}->prepare($statement)
1878 or return $self->_dberror('Unable to prepare statement', $statement);
1881 or return $self->_dberror('Unable to execute statement', $statement);
1883 $runlog = $sth->fetchrow_hashref;
1892 } # GetLastTaskFailure
1894 sub Notify($$$$$$) {
1906 $runlogID = $self->_getLastID
1911 # Update filesystem, if $filesystem was specified
1913 ($err, $msg) = $self->UpdateFilesystem(
1916 notification => $notification,
1920 $self->Error("Unable to set notification for filesystem $system:$filesystem "
1921 . "(Status: $err)\n$msg", $err) if $err;
1925 ($err, $msg) = $self->UpdateSystem(
1927 notification => $notification,
1931 my %notification = $self->GetNotification($notification);
1933 my %lastnotified = $self->GetLastAlert($notification, $system);
1935 if (%lastnotified and $lastnotified{timestamp}) {
1936 my $today = Today2SQLDatetime;
1937 my $lastnotified = $lastnotified{timestamp};
1939 if ($notification{nomorethan} =~ /hour/i) {
1940 $lastnotified = Add($lastnotified, (hours => 1));
1941 } elsif ($notification{nomorethan} =~ /day/i) {
1942 $lastnotified = Add($lastnotified, (days => 1));
1943 } elsif ($notification{nomorethan} =~ /week/i) {
1944 $lastnotified = Add($lastnotified, (days => 7));
1945 } elsif ($notification{nomorethan} =~ /month/i) {
1946 $lastnotified = Add($lastnotified, (month => 1));
1949 # If you want to fake an alert in the debugger just change $diff accordingly
1950 my $diff = Compare($today, $lastnotified);
1952 return if $diff <= 0;
1955 my $when = Today2SQLDatetime;
1956 my $nomorethan = lc $notification{nomorethan};
1957 my %alert = $self->GetAlert($notification{alert});
1958 my $to = $alert{who};
1960 # If $to is null then this means to send the alert to the admin for the
1964 my %system = $self->GetSystem($system);
1966 $to = $system{email};
1968 # If we don't know what system this error occurred on we'll have to notify
1969 # the "super user" defined as $self->{NOTIFY} (The receiver of last
1971 $to = $self->{NOTIFY};
1976 Error "To undefined";
1979 $message .= "<p>You will receive this alert no more than $nomorethan.</p>";
1981 ($err, $msg) = $self->SendAlert(
1982 $notification{alert},
1984 $notification{name},
1991 $self->Error("Unable to send alert (Status: $err)\n$msg", $err) if $err;
1993 verbose "Sent alert to $to";
1995 # Update runlog to indicate we notified the user for this execution
1996 ($err, $msg) = $self->UpdateRunlog(
2002 $self->Error("Unable to update runlog (Status: $err)\n$msg", $err) if $err;
2007 sub ClearNotifications($$;$) {
2008 my ($self, $system, $filesystem) = @_;
2013 ($err, $msg) = $self->UpdateFilesystem(
2015 $filesystem, (notification => undef),
2018 error "Unable to clear notification for filesystem $system:$filesystem "
2019 . "(Status: $err)\n$msg", $err
2022 # Check to see any of this system's filesystems have notifications. If none
2023 # then it's save to say we've turned off the last notification for a
2024 # filesystem involved with this system and if $system{notification} was
2025 # 'Filesystem' then we can toggle off the notification on the system too
2026 my $filesystemsAlerted = 0;
2028 for ($self->FindFilesystem($system)) {
2029 $filesystemsAlerted++
2030 if $$_{notification};
2033 my %system = $self->GetSystem($system);
2035 return unless $system;
2037 if ($system{notification} and
2038 $system{notification} eq 'Filesystem' and
2039 $filesystemsAlerted == 0) {
2040 ($err, $msg) = $self->UpdateSystem($system, (notification => undef));
2042 $self->Error("Unable to clear notification for system $system "
2043 . "(Status: $err)\n$msg", $err) if $err;
2046 ($err, $msg) = $self->UpdateSystem($system, (notification => undef));
2048 $self->Error("Unable to clear notification for system $system "
2049 . "(Status: $err)\n$msg", $err) if $err;
2053 } # ClearNotifications
2055 sub SystemAlive(%) {
2056 my ($self, %system) = @_;
2058 # If we've never heard from this system then we will assume that the system
2059 # has not been set up to run clearagent and has never checked in. In any event
2060 # we cannot say the system died because we've never known it to be alive!
2061 return 1 unless $system{lastheardfrom};
2063 # If a system is not active (may have been temporarily been deactivated) then
2064 # we don't want to turn on the bells and whistles alerting people it's down.
2065 return 1 if $system{active} eq 'false';
2067 my $today = Today2SQLDatetime;
2068 my $lastheardfrom = $system{lastheardfrom};
2070 my $tenMinutes = 10 * 60;
2072 $lastheardfrom = Add($lastheardfrom, (seconds => $tenMinutes));
2074 if (DateToEpoch($lastheardfrom) < DateToEpoch($today)) {
2075 $self->UpdateSystem(
2077 notification => 'Heartbeat'
2083 if ($system{notification}) {
2084 $self->UpdateSystem(
2086 notification => undef
2094 sub UpdateAlert($%) {
2095 my ($self, $name, %update) = @_;
2097 return $self->_updateRecord(
2104 sub AddAlertlog(%) {
2105 my ($self, %alertlog) = @_;
2107 my @requiredFields = (
2112 my $result = _checkRequiredFields \@requiredFields, \%alertlog;
2114 return -1, "AddAlertlog: $result" if $result;
2117 $alertlog{timestamp} = Today2SQLDatetime;
2119 return $self->_addRecord('alertlog', %alertlog);
2122 sub DeleteAlertlog($) {
2123 my ($self, $condition) = @_;
2128 if ($condition =~ /all/i) {
2129 return $self->_deleteRecord('alertlog');
2131 return $self->_deleteRecord('alertlog', $condition);
2135 sub FindAlertlog(;$$$$$) {
2136 my ($self, $alert, $system, $notification, $start, $page) = @_;
2140 $notification ||= '';
2142 my $condition = "alert like '%$alert%'";
2143 $condition .= ' and ';
2144 $condition .= "system like '%$system%'";
2145 $condition .= ' and ';
2146 $condition .= "notification like '%$notification%'";
2147 $condition .= " order by timestamp desc";
2149 if (defined $start) {
2151 $condition .= " limit $start, $page";
2154 return $self->_getRecords('alertlog', $condition);
2157 sub GetAlertlog($) {
2158 my ($self, $alert) = @_;
2160 return unless $alert;
2162 my @records = $self->_getRecords('alertlog', "alert='$alert'");
2165 return %{$records[0]};
2171 sub UpdateAlertlog($%) {
2172 my ($self, $alert, %update) = @_;
2174 return $self->_updateRecord(
2181 sub AddNotification(%) {
2182 my ($self, %notification) = @_;
2184 my @requiredFields = (
2190 my $result = _checkRequiredFields \@requiredFields, \%notification;
2192 return -1, "AddNotification: $result" if $result;
2194 return $self->_addRecord('notification', %notification);
2197 sub DeleteNotification($) {
2198 my ($self, $name) = @_;
2200 return $self->_deleteRecord('notification', "name='$name'");
2203 sub FindNotification(;$$) {
2204 my ($self, $name, $cond, $ordering) = @_;
2208 my $condition = "name like '%$name%'";
2209 $condition .= " and $cond"
2212 return $self->_getRecords('notification', $condition);
2213 } # FindNotification
2215 sub GetNotification($) {
2216 my ($self, $name) = @_;
2218 return unless $name;
2220 my @records = $self->_getRecords('notification', "name='$name'");
2223 return %{$records[0]};
2229 sub UpdateNotification($%) {
2230 my ($self, $name, %update) = @_;
2232 return $self->_updateRecord(
2237 } # UpdateNotification
2239 sub AddVobStorage(%) {
2240 my ($self, %vobstorage) = @_;
2242 my @requiredFields = (
2246 my $result = _checkRequiredFields \@requiredFields, \%vobstorage;
2248 return -1, "AddVobStorage: $result" if $result;
2251 $vobstorage{timestamp} = Today2SQLDatetime;
2253 return $self->_addRecord('vobstorage', %vobstorage);
2256 sub AddViewStorage(%) {
2257 my ($self, %viewstorage) = @_;
2259 my @requiredFields = (
2263 my $result = _checkRequiredFields \@requiredFields, \%viewstorage;
2265 return -1, "AddViewStorage: $result" if $result;
2268 $viewstorage{timestamp} = Today2SQLDatetime;
2270 return $self->_addRecord('viewstorage', %viewstorage);
2277 =head1 CONFIGURATION AND ENVIRONMENT
2279 DEBUG: If set then $debug is set to this level.
2281 VERBOSE: If set then $verbose is set to this level.
2283 TRACE: If set then $trace is set to this level.
2295 L<Net::Domain|Net::Domain>
2297 =head2 ClearSCM Perl Modules
2311 <a href="http://clearscm.com/php/scm_man.php?file=lib/DateUtils.pm">DateUtils</a><br>
2312 <a href="http://clearscm.com/php/scm_man.php?file=lib/Display.pm">Display</a><br>
2313 <a href="http://clearscm.com/php/scm_man.php?file=lib/GetConfig.pm">GetConfig</a><br>
2314 <a href="http://clearscm.com/php/scm_man.php?file=lib/Mail.pm">Mail</a><br>
2319 =head1 BUGS AND LIMITATIONS
2321 There are no known bugs in this module
2323 Please report problems to Andrew DeFaria <Andrew@ClearSCM.com>.
2325 =head1 LICENSE AND COPYRIGHT
2327 Copyright (c) 2010, ClearSCM, Inc. All rights reserved.