Merge branch 'master' of /opt/git/clearscm
[clearscm.git] / lib / Machines.pm
index 804b35a..c872eda 100644 (file)
@@ -54,119 +54,275 @@ package Machines;
 use strict;
 use warnings;
 
-use Display;
-use Utils;
+use Carp;
+use DBI;
+use FindBin;
 
-use base 'Exporter';
+use DateUtils;
+use Display;
+use GetConfig;
+
+our %MACHINESOPTS = GetConfig ("$FindBin::Bin/../etc/machines.conf");
+
+my $defaultFilesystemThreshold = 90;
+my $defaultFilesystemHist      = '6 months';
+my $defaultLoadavgHist         = '6 months';
+
+# Internal methods
+sub _dberror ($$) {
+  my ($self, $msg, $statement) = @_;
+
+  my $dberr    = $self->{db}->err;
+  my $dberrmsg = $self->{db}->errstr;
+  
+  $dberr    ||= 0;
+  $dberrmsg ||= 'Success';
+
+  my $message = '';
+  
+  if ($dberr) {
+    my $function = (caller (1)) [3];
+
+    $message = "$function: $msg\nError #$dberr: $dberrmsg\n"
+             . "SQL Statement: $statement";
+  } # if
+
+  return $dberr, $message;  
+} # _dberror
+
+sub _formatValues (@) {
+  my ($self, @values) = @_;
+  
+  my @returnValues;
+  
+  # Quote data values
+  push @returnValues, $_ eq '' ? 'null' : $self->{db}->quote ($_)  
+    foreach (@values);
+  
+  return @returnValues;
+} # _formatValues
+
+sub _formatNameValues (%) {
+  my ($self, %rec) = @_;
+  
+  my @nameValueStrs;
+  
+  push @nameValueStrs, "$_=" . $self->{db}->quote ($rec{$_})
+    foreach (keys %rec);
+    
+  return @nameValueStrs;
+} # _formatNameValues
+
+sub _error () {\r
+  my ($self) = @_;
+  
+  if ($self->{msg}) {
+    if ($self->{errno}) {
+      carp $self->{msg};
+    } else {
+      cluck $self->{msg};
+    } # if
+  } # if\r
+} # _error
+
+sub _addRecord ($%) {
+  my ($self, $table, %rec) = @_;
+  
+  my $statement  = "insert into $table (";
+     $statement .= join ',', keys %rec;
+     $statement .= ') values (';
+     $statement .= join ',', $self->_formatValues (values %rec);
+     $statement .= ')';
+  
+  my ($err, $msg);
+  
+  $self->{db}->do ($statement);
+  
+  return $self->_dberror ("Unable to add record to $table", $statement);
+} # _addRecord
+
+sub _checkRequiredFields ($$) {
+  my ($fields, $rec) = @_;
+  
+  foreach my $fieldname (@$fields) {
+    my $found = 0;
+    
+    foreach (keys %$rec) {
+      if ($fieldname eq $_) {
+         $found = 1;
+         last;
+      } # if
+    } # foreach
+    
+    return "$fieldname is required"
+      unless $found;
+  } # foreach
+  
+  return;
+} # _checkRequiredFields
+
+sub _connect (;$) {
+  my ($self, $dbserver) = @_;
+  
+  $dbserver ||= $MACHINESOPTS{MACHINES_SERVER};
+  
+  my $dbname   = 'machines';
+  my $dbdriver = 'mysql';
+
+  $self->{db} = DBI->connect (
+    "DBI:$dbdriver:$dbname:$dbserver", 
+    $MACHINESOPTS{MACHINES_USERNAME},
+    $MACHINESOPTS{MACHINES_PASSWORD},
+    {PrintError => 0},
+  ) or croak (
+    "Couldn't connect to $dbname database " 
+  . "as $MACHINESOPTS{MACHINESADM_USERNAME}\@$MACHINESOPTS{MACHINESADM_SERVER}"
+  );
+  
+  $self->{dbserver} = $dbserver;
+  
+  return;
+} # _connect
+
+sub _getRecords ($$) {
+  my ($self, $table, $condition) = @_;
+  
+  my ($err, $msg);
+    
+  my $statement = "select * from $table where $condition";
+  
+  my $sth = $self->{db}->prepare ($statement);
+  
+  unless ($sth) {
+    ($err, $msg) = $self->_dberror ('Unable to prepare statement', $statement);
+    
+    croak $msg;
+  } # if
+
+  my $attempts    = 0;
+  my $maxAttempts = 3;
+  my $sleepTime   = 30;
+  my $status;
+  
+  # We've been having the server going away. Supposedly it should reconnect so
+  # here we simply retry up to $maxAttempts times to re-execute the statement. 
+  # (Are there other places where we need to do this?)
+  $err = 2006;
+  
+  while ($err == 2006 and $attempts++ < $maxAttempts) {
+    $status = $sth->execute;
+    
+    if ($status) {
+      $err = 0;
+      last;
+    } else {
+      ($err, $msg) = $self->_dberror ('Unable to execute statement',
+                                      $statement);
+    } # if
+    
+    last if $err == 0;
+    
+    croak $msg unless $err == 2006;
+
+    my $timestamp = YMDHMS;
+      
+    $self->Error ("$timestamp: Unable to talk to DB server.\n\n$msg\n\n"
+                . "Will try again in $sleepTime seconds", -1);
+                
+    # Try to reconnect
+    $self->_connect ($self->{dbserver});
+
+    sleep $sleepTime;
+  } # while
+
+  $self->Error ("After $maxAttempts attempts I could not connect to the database", $err)
+    if ($err == 2006 and $attempts > $maxAttempts);
+  
+  my @records;
+  
+  while (my $row = $sth->fetchrow_hashref) {
+    push @records, $row;
+  } # while
+  
+  return @records;
+} # _getRecord
 
-our @EXPORT = qw (
-  all
-  new
-);
 
 sub new {
   my ($class, %parms) = @_;
 
 =pod
 
-=head2 new (<parms>)
-
-Construct a new Machines object. The following OO style arguments are
-supported:
-
-Parameters:
-
-=for html <blockquote>
-
-=over
-
-=item file:
-
-Name of an alternate file from which to read machine information. This
-is intended as a quick alternative.
-
-=back
-
-=for html </blockquote>
+=head2 new ($server)
 
-Returns:
-
-=for html <blockquote>
-
-=over
-
-=item Machines object
-
-=back
-
-=for html </blockquote>
+Construct a new Machines object.
 
 =cut
 
-  my $file = $parms{file} ? $parms{file} : "$FindBin::Bin/../etc/machines";
-
-  error "Unable to find $file", 1 if ! -f $file;
-
-  my %machines;
-
-  foreach (ReadFile $file) {
-    my @parts = split;
-
-    # Skip commented out or blank lines
-    next if $parts[0] =~ /^#/ or $parts[0] =~ /^$/;
-
-    $machines{$parts[0]} = $parts[1];
-  } # foreach
-
-  bless {
-    file     => $parms {file},
-    machines => \%machines,
-  }, $class; # bless
-
-  return $class;
+  # Merge %parms with %MACHINEOPTS
+  foreach (keys %parms) {
+    $MACHINESOPTS{$_} = $parms{$_};
+  } # foreach;
+  
+  my $self = bless {}, $class;
+  
+  $self->_connect ();
+  
+  return $self;
 } # new
 
-sub all () {
-  my ($self) = @_;
-
-=pod
-
-=head3 all ()
-
-Returns all known machines as an array of hashes
-
-Parameters:
-
-=for html <blockquote>
-
-=over
-
-=item none
-
-=back
-
-=for html </blockquote>
-
-Returns:
-
-=begin html
-
-<blockquote>
-
-=end html
-
-=over
-
-=item Array of machine hash records
-
-=back
-
-=for html </blockquote>
-
-=cut
-
-  return %{$self->{machines}};
-} # display
+sub add (%) {
+  my ($self, %system) = @_;
+  
+  my @requiredFields = qw(
+    name
+    admin
+    type
+  );
+
+  my $result = _checkRequiredFields \@requiredFields, \%system;
+  
+  return -1, "add: $result" if $result;
+  
+  $system{loadavgHist} ||= $defaultLoadavgHist;
+  
+  return $self->_addRecord ('system', %system);
+} # add
+
+sub delete ($) {
+  my ($self, $name) = @_;
+
+  return $self->_deleteRecord ('system', "name='$name'");  
+} # delete
+
+sub update ($%) {
+  my ($self, $name, %update) = @_;
+
+  return $self->_updateRecord ('system', "name='$name'", %update);
+} # update
+
+sub get ($) {
+  my ($self, $system) = @_;
+  
+  return unless $system;
+  
+  my @records = $self->_getRecords (
+    'system', 
+    "name='$system' or alias like '%$system%'"
+  );
+  
+  if ($records[0]) {
+    return %{$records[0]};
+  } else {
+        return;
+  } # if
+} # get
+
+sub find (;$) {
+  my ($self, $condition) = @_;
+
+  return $self->_getRecords ('system', $condition);
+} # find
 
 1;