From 85a506f0983544ac5e06634fb6329a5fd9db4d01 Mon Sep 17 00:00:00 2001 From: adefaria Date: Fri, 4 Apr 2014 19:08:46 -0700 Subject: [PATCH] Some changes to Machines and Rexe --- etc/machines.conf | 16 +++ lib/Machines.pm | 354 +++++++++++++++++++++++++++++++++------------- lib/machines.sql | 138 ++++++++++++++++++ test/testrexec.pl | 29 ++-- 4 files changed, 428 insertions(+), 109 deletions(-) create mode 100644 etc/machines.conf create mode 100644 lib/machines.sql diff --git a/etc/machines.conf b/etc/machines.conf new file mode 100644 index 0000000..17f2146 --- /dev/null +++ b/etc/machines.conf @@ -0,0 +1,16 @@ +############################################################################### +# +# File: $RCSfile: machines.conf,v $ +# Revision: $Revision: 1.0 $ +# Description: Config file for Machines +# Author: Andrew@ClearSCM.com +# Created: Fri Apr 4 14:29:21 PDT 2014 +# Modified: $Date: $ +# Language: conf +# +# (c) Copyright 2014, ClearSCM, Inc., all rights reserved +# +############################################################################### +MACHINES_SERVER: adefaria-lt +MACHINES_USERNAME: machines +MACHINES_PASSWORD: machines diff --git a/lib/Machines.pm b/lib/Machines.pm index 804b35a..c872eda 100644 --- a/lib/Machines.pm +++ b/lib/Machines.pm @@ -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 () { + my ($self) = @_; + + if ($self->{msg}) { + if ($self->{errno}) { + carp $self->{msg}; + } else { + cluck $self->{msg}; + } # if + } # if +} # _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 () - -Construct a new Machines object. The following OO style arguments are -supported: - -Parameters: - -=for html
- -=over - -=item file: - -Name of an alternate file from which to read machine information. This -is intended as a quick alternative. - -=back - -=for html
+=head2 new ($server) -Returns: - -=for html
- -=over - -=item Machines object - -=back - -=for html
+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
- -=over - -=item none - -=back - -=for html
- -Returns: - -=begin html - -
- -=end html - -=over - -=item Array of machine hash records - -=back - -=for html
- -=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; diff --git a/lib/machines.sql b/lib/machines.sql new file mode 100644 index 0000000..0d9a2d4 --- /dev/null +++ b/lib/machines.sql @@ -0,0 +1,138 @@ +system-- ----------------------------------------------------------------------------- +-- +-- File: $RCSfile: machines.sql,v $ +-- Revision: $Revision: 1.0 $ +-- Description: Create the machines database +-- Author: Andrew@DeFaria.com +-- Created: Fri Apr 4 10:31:11 PDT 2014 +-- Modified: $Date: $ +-- Language: SQL +-- +-- Copyright (c) 2014, ClearSCM, Inc., all rights reserved +-- +-- ----------------------------------------------------------------------------- +-- Warning: The following line will delete the old database! +drop database if exists machines; + +-- Create a new database +create database machines; + +-- Now let's focus on this new database +use machines; + +-- system: Define what makes up a system or machine +create table system ( + name varchar (255) not null, + alias varchar (255), + active enum ( + 'true', + 'false' + ) not null default 'true', + admin tinytext, + email tinytext, + os tinytext, + type enum ( + 'Linux', + 'Unix', + 'Windows' + ) not null, + region tinytext, + lastheardfrom datetime, + description text, + loadavgHist enum ( + '1 month', + '2 months', + '3 months', + '4 months', + '5 months', + '6 months', + '7 months', + '8 months', + '9 months', + '10 months', + '11 months', + '1 year' + ) not null default '6 months', + loadavgThreshold float (4,2) default 5.00, + + primary key (name) +) engine=innodb; -- system + +-- package: A package is any software package that we wish to keep track of +create table package ( + system varchar (255) not null, + name varchar (255) not null, + version tinytext not null, + vendor tinytext, + description text, + + key packageIndex (name), + key systemIndex (system), + foreign key systemLink (system) references system (name) + on delete cascade + on update cascade, + primary key (system, name) +) engine=innodb; -- package + +-- filesystem: A systems file systems that we are monitoring +create table filesystem ( + system varchar (255) not null, + filesystem varchar (255) not null, + fstype tinytext not null, + mount tinytext, + threshold int default 90, + notification varchar (255), + filesystemHist enum ( + '1 month', + '2 months', + '3 months', + '4 months', + '5 months', + '6 months', + '7 months', + '8 months', + '9 months', + '10 months', + '11 months', + '1 year' + ) not null default '6 months', + + key filesystemIndex (filesystem), + foreign key systemLink (system) references system (name) + on delete cascade + on update cascade, + primary key (system, filesystem) +) engine=innodb; -- filesystem + +-- fs: Contains a snapshot reading of a filesystem at a given date and time +create table fs ( + system varchar(255) not null, + filesystem varchar(255) not null, + mount varchar(255) not null, + timestamp datetime not null, + size bigint, + used bigint, + free bigint, + reserve bigint, + + key mountIndex (mount), + primary key (system, filesystem, timestamp), + foreign key filesystemLink (system, filesystem) + references filesystem (system, filesystem) + on delete cascade + on update cascade +) engine=innodb; -- fs + +-- loadavg: Contains a snapshot reading of a system's load average +create table loadavg ( + system varchar(255) not null, + timestamp datetime not null, + uptime tinytext, + users int, + loadavg float (4,2), + + primary key (system, timestamp), + foreign key systemLink (system) references system (name) + on delete cascade + on update cascade +) engine=innodb; -- loadavg \ No newline at end of file diff --git a/test/testrexec.pl b/test/testrexec.pl index 5f35bc3..07ab995 100755 --- a/test/testrexec.pl +++ b/test/testrexec.pl @@ -14,6 +14,16 @@ my $hostname = $ENV{HOST} || 'localhost'; my $username = $ENV{USERNAME}; my $password = $ENV{PASSWORD}; +my $command = $ENV{COMMAND}; + +if (@ARGV) { + $command = join ' ', @ARGV; +} else { + $command = 'ls /tmp' unless $command; +} # if + +print "Attempting to connect to $username\@$hostname to execute \"$command\"\n"; + my $remote = Rexec->new ( host => $hostname, username => $username, @@ -24,19 +34,18 @@ my $remote = Rexec->new ( if ($remote) { print "Connected to $username\@$hostname using " . $remote->{protocol} . " protocol\n"; - - $cmd = "/bin/ls /nonexistent"; - @output = $remote->execute ($cmd); + print "Executing command \"$command\" on $hostname as $username\n"; + @output = $remote->execute ($command); $status = $remote->status; - print "$cmd status: $status\n"; + print "\"$command\" status: $status\n"; - $remote->print_lines; - - print "$_\n" foreach ($remote->execute ('cat /etc/passwd')); + if (@output == 0) { + print "No lines of output received!\n"; + } else { + print "$_\n" foreach (@output); + } # if } else { print "Unable to connect to $username@$hostname\n"; -} # if - - +} # if \ No newline at end of file -- 2.17.1