Note: Broken Commit

* This is an in-progress commit working on adding the Database methods. Much of it is copy/pasted from v2 and none of the new code is tested yet. It will certainly fail to compile.

Signed-off-by: Digimer <digimer@alteeve.ca>
main
Digimer 8 years ago
parent e3f782a60c
commit 3ea562a79c
  1. 49
      AN/Tools.pm
  2. 46
      AN/Tools/Convert.pm
  3. 892
      AN/Tools/Database.pm
  4. 69
      AN/Tools/Get.pm
  5. 2
      AN/Tools/Storage.pm
  6. 666
      AN/Tools/System.pm
  7. 106
      AN/an-tools.xml
  8. 0
      scancore.sql
  9. 7
      striker.conf

@ -20,9 +20,11 @@ my $THIS_FILE = "Tools.pm";
# data # data
# environment # environment
# _add_hash_reference # _add_hash_reference
# _hostname
# _make_hash_reference # _make_hash_reference
# _set_defaults # _set_defaults
# _set_paths # _set_paths
# _short_hostname
use utf8; use utf8;
binmode(STDERR, ':encoding(utf-8)'); binmode(STDERR, ':encoding(utf-8)');
@ -445,6 +447,31 @@ sub _add_hash_reference
} }
} }
=head2 _hostname
This returns the (full) hostname for the machine this is running on.
=cut
sub _hostname
{
my $self = shift;
my $an = $self;
my $hostname = "";
if ($ENV{HOSTNAME})
{
# We have an environment variable, so use it.
$hostname = $ENV{HOSTNAME};
}
else
{
# The environment variable isn't set. Call 'hostname' on the command line.
$hostname = $an->System->call({shell_call => $an->data->{path}{exe}{hostname}});
}
return($hostname);
}
=head2 _get_hash_reference =head2 _get_hash_reference
This is called when we need to parse a double-colon separated string into two or more elements which represent keys in the 'C<< $an->data >>' hash. Once suitably split up, the value is read and returned. This is called when we need to parse a double-colon separated string into two or more elements which represent keys in the 'C<< $an->data >>' hash. Once suitably split up, the value is read and returned.
@ -573,6 +600,11 @@ sub _set_paths
configs => { configs => {
'pg_hba.conf' => "/var/lib/pgsql/data/pg_hba.conf", 'pg_hba.conf' => "/var/lib/pgsql/data/pg_hba.conf",
'postgresql.conf' => "/var/lib/pgsql/data/postgresql.conf", 'postgresql.conf' => "/var/lib/pgsql/data/postgresql.conf",
ssh_config => "/etc/ssh/ssh_config",
'striker.conf' => "/etc/striker/striker.conf",
},
data => {
passwd => "/etc/passwd",
}, },
directories => { directories => {
backups => "/usr/sbin/striker/backups", backups => "/usr/sbin/striker/backups",
@ -596,6 +628,7 @@ sub _set_paths
journalctl => "/usr/bin/journalctl", journalctl => "/usr/bin/journalctl",
logger => "/usr/bin/logger", logger => "/usr/bin/logger",
'mkdir' => "/usr/bin/mkdir", 'mkdir' => "/usr/bin/mkdir",
ping => "/usr/bin/ping",
psql => "/usr/bin/psql", psql => "/usr/bin/psql",
'postgresql-setup' => "/usr/bin/postgresql-setup", 'postgresql-setup' => "/usr/bin/postgresql-setup",
su => "/usr/bin/su", su => "/usr/bin/su",
@ -637,6 +670,22 @@ sub _set_paths
return(0); return(0);
} }
=head3 _short_hostname
This returns the short hostname for the machine this is running on. That is to say, the hostname up to the first '.'.
=cut
sub _short_hostname
{
my $self = shift;
my $an = $self;
my $short_host_name = $an->_hostname;
$short_host_name =~ s/\..*$//;
return($short_host_name);
}
=head1 Exit Codes =head1 Exit Codes
=head2 C<1> =head2 C<1>

@ -12,6 +12,7 @@ my $THIS_FILE = "Convert.pm";
### Methods; ### Methods;
# cidr # cidr
# hostname_to_ip
=pod =pod
@ -185,3 +186,48 @@ sub cidr
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { output => $output }}); $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { output => $output }});
return($output); return($output);
} }
=head2 hostname_to_ip
This method takes a hostname and tries to convert it to an IP address. If it fails, it will return C<< 0 >>.
Parameters;
=head3 hostname
This is the host name (or domain name) to try and convert to an IP address.
=cut
sub hostname_to_ip
{
my $self = shift;
my $parameter = shift;
my $an = $self->parent;
my $hostname = defined $parameter->{hostname} ? $parameter->{hostname} : "";
my $ip = 0;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { hostname => $hostname }});
if (not $hostname)
{
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0059"});
return($ip);
}
### TODO: Check local cached information later.
# Try to resolve it using 'gethostip'.
my $output = $an->System->call({shell_call => $an->data->{path}{exe}{gethostip}." -d $hostname"});
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { output => $output }});
foreach my $line (split/\n/, $output)
{
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { line => $line }});
if ($an->Validate->is_ipv4({ip => $line}))
{
$ip = $line;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { ip => $ip }});
}
}
return($ip);
}

@ -11,7 +11,12 @@ our $VERSION = "3.0.0";
my $THIS_FILE = "Database.pm"; my $THIS_FILE = "Database.pm";
### Methods; ### Methods;
# connect
# get_local_id # get_local_id
# initialize
# query
# test_access
# write
=pod =pod
@ -67,6 +72,429 @@ sub parent
# Public methods # # Public methods #
############################################################################################################# #############################################################################################################
=head2 connect_to_databases
This method tries to connect to all databases it knows of. To define databases for a machine to connect to, load a configuration file with the following parameters;
database::1::host = an-striker01.alteeve.com
database::1::port = 5432
database::1::name = scancore
database::1::user = admin
database::1::password = Initial1
database::1::ping_before_connect = 1
database::2::host = an-striker02.alteeve.com
database::2::port = 5432
database::2::name = scancore
database::2::user = admin
database::2::password = Initial1
database::2::ping_before_connect = 1
The C<< 1 >> and C<< 2 >> are the IDs of the given databases. They can be any number and do not need to be sequential, they just need to be unique.
This module will return the number of databases that were successfully connected to. This makes it convenient to check and exit if no databases are available using a check like;
my $database_count = $an->Database->connect({file => $THIS_FILE});
if($database_count)
{
# Connected to: [$database_count] database(s)!
}
else
{
# No databases available, exiting.
}
Parameters;
=head3 file (required)
The C<< file >> parameter is used to check the special C<< updated >> table one all connected databases to see when that file (program name) last updated a given database. If the date stamp is the same on all connected databases, nothing further happens. If one of the databases differ, however, a resync will be requested.
=head3 tables (optional)
This is an optional array reference of tables to specifically check when connecting to databases. If specified, the table's most recent C<< modified_date >> time stamp will be read (specifically; C<< SELECT modified_date FROM $table ORDER BY modified_date DESC LIMIT 1 >>) and if a table doesn't return, or any of the time stamps are missing, a resync will be requested.
To use this, use;
$an->Database->connect({file => $THIS_FILE, tables => ("table1", "table2")});
=cut
sub connect
{
my $self = shift;
my $parameter = shift;
my $an = $self->parent;
$an->Log->entry({log_level => 3, message_key => "tools_log_0001", message_variables => { function => "connect_to_databases" }, file => $THIS_FILE, line => __LINE__});
my $file = defined $parameter->{file} ? $parameter->{file} : "";
my $tables = defined $parameter->{tables} ? $parameter->{tables} : "";
# We need the host_uuid before we connect.
if (not $an->data->{sys}{host_uuid})
{
$an->data->{sys}{host_uuid} = $an->Get->host_uuid;
}
# This will be used in a few cases where the local DB ID is needed (or the lack of it being set
# showing we failed to connect to the local DB).
$an->data->{sys}{local_db_id} = "";
# This will be set to '1' if either DB needs to be initialized or if the last_updated differs on any node.
$an->data->{database_resync_needed} = 0;
# Now setup or however-many connections
my $seen_connections = [];
my $connections = 0;
my $failed_connections = [];
my $successful_connections = [];
foreach my $id (sort {$a cmp $b} keys %{$an->data->{database}})
{
next if $id eq "general"; # This is used for global values.
my $driver = "DBI:Pg";
my $host = $an->data->{database}{$id}{host} ? $an->data->{database}{$id}{host} : ""; # This should fail
my $port = $an->data->{database}{$id}{port} ? $an->data->{database}{$id}{port} : 5432;
my $name = $an->data->{database}{$id}{name} ? $an->data->{database}{$id}{name} : ""; # This should fail
my $user = $an->data->{database}{$id}{user} ? $an->data->{database}{$id}{user} : ""; # This should fail
my $password = $an->data->{database}{$id}{password} ? $an->data->{database}{$id}{password} : "";
# If not set, we will always ping before connecting.
if ((not exists $an->data->{database}{$id}{ping_before_connect}) or (not defined $an->data->{database}{$id}{ping_before_connect}))
{
$an->data->{database}{$id}{ping_before_connect} = 1;
}
# Make sure the user didn't specify the same target twice.
my $target_host = "$host:$port";
my $duplicate = 0;
foreach my $existing_host (sort {$a cmp $b} @{$seen_connections})
{
if ($existing_host eq $target_host)
{
# User is connecting to the same target twice.
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0053", variables => { target => $target_host }});
$duplicate = 1;
}
}
if (not $duplicate)
{
push @{$seen_connections}, $target_host;
}
next if $duplicate;
# Log what we're doing.
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0054", variables => {
id => $id,
driver => $driver,
host => $host,
port => $port,
name => $name,
user => $user,
password => $an->Log->secure ? $password : "--",
}});
# Assemble my connection string
my $db_connect_string = "$driver:dbname=$name;host=$host;port=$port";
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
db_connect_string => $db_connect_string,
"database::${id}::ping_before_connect" => $an->data->{database}{$id}{ping_before_connect},
}});
if ($an->data->{database}{$id}{ping_before_connect})
{
# Can I ping?
my ($pinged) = $an->System->ping({ping => $host, count => 1});
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { pinged => $pinged }});
if (not $pinged)
{
# Didn't ping and 'database::<id>::ping_before_connect' not set. Record this
# is the failed connections array.
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", key => "log_0063", variables => { id => $id }});
push @{$failed_connections}, $id;
next;
}
}
# Connect!
my $dbh = "";
### NOTE: The Database->write() method, when passed an array, will automatically disable
### autocommit, do the bulk write, then commit when done.
# We connect with fatal errors, autocommit and UTF8 enabled.
eval { $dbh = DBI->connect($db_connect_string, $user, $password, {
RaiseError => 1,
AutoCommit => 1,
pg_enable_utf8 => 1
}); };
if ($@)
{
# Something went wrong...
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", key => "log_0064", variables => {
id => $id,
host => $host,
name => $name,
}});
push @{$failed_connections}, $id;
my $message_key = "log_0065";
my $variables = { dbi_error => $DBI::errstr };
if (not defined $DBI::errstr)
{
# General error
$variables = { dbi_error => $@ };
}
elsif ($DBI::errstr =~ /No route to host/)
{
$message_key = "log_0066";
$variables = { target => $host, port => $port };
}
elsif ($DBI::errstr =~ /no password supplied/)
{
$message_key = "log_0067";
$variables = { id => $id };
}
elsif ($DBI::errstr =~ /password authentication failed for user/)
{
$message_key = "log_0068";
$variables = {
id => $id,
name => $name,
host => $host,
user => $user,
};
}
elsif ($DBI::errstr =~ /Connection refused/)
{
$message_key = "log_0069";
$variables = {
name => $name,
host => $host,
port => $port,
};
}
elsif ($DBI::errstr =~ /Temporary failure in name resolution/i)
{
$message_key = "log_0070";
$variables = {
name => $name,
host => $host,
port => $port,
};
}
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", key => $message_key, variables => { $variables }});
}
elsif ($dbh =~ /^DBI::db=HASH/)
{
# Woot!
$connections++;
push @{$successful_connections}, $id;
$an->data->{cache}{db_fh}{$id} = $dbh;
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0071", variables => {
host => $host,
port => $port,
name => $name,
id => $id,
}});
# Now that I have connected, see if my 'hosts' table exists.
my $query = "SELECT COUNT(*) FROM pg_catalog.pg_tables WHERE tablename='hosts' AND schemaname='public';";
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }});
my $count = $an->Database->query({id => $id, query => $query, source => $THIS_FILE, line => __LINE__})->[0]->[0];
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { count => $count }});
if ($count < 1)
{
# Need to load the database.
$an->Database->initialize({id => $id});
}
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
"sys::read_db_id" => $an->data->{sys}{read_db_id},
"cache::db_fh::$id" => $an->data->{cache}{db_fh}{$id},
}});
# Set the first ID to be the one I read from later. Alternatively, if this host is
# local, use it.
if (($host eq $an->_hostname) or
($host eq $an->_short_hostname) or
($host eq "localhost") or
($host eq "127.0.0.1") or
(not $an->data->{sys}{read_db_id}))
{
$an->data->{sys}{read_db_id} = $id;
$an->data->{sys}{local_db_id} = $id;
$an->data->{sys}{use_db_fh} = $an->data->{cache}{db_fh}{$id};
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
"sys::read_db_id" => $an->data->{sys}{read_db_id},
"sys::use_db_fh" => $an->data->{sys}{use_db_fh}
});
}
### NOTE: Left off here.
# Get a time stamp for this run, if not yet gotten.
$an->Log->entry({log_level => 3, message_key => "an_variables_0002", message_variables => {
name1 => "cache::db_fh::$id", value1 => $an->data->{cache}{db_fh}{$id},
name2 => "sys::db_timestamp", value2 => $an->data->{sys}{db_timestamp}
}, file => $THIS_FILE, line => __LINE__});
if (not $an->data->{sys}{db_timestamp})
{
my $query = "SELECT cast(now() AS timestamp with time zone)";
$an->Log->entry({log_level => 3, message_key => "an_variables_0001", message_variables => {
name1 => "query", value1 => $query
}, file => $THIS_FILE, line => __LINE__});
$an->data->{sys}{db_timestamp} = $an->Database->query({id => $id, query => $query, source => $THIS_FILE, line => __LINE__})->[0]->[0];
$an->Log->entry({log_level => 3, message_key => "an_variables_0001", message_variables => {
name1 => "sys::db_timestamp", value1 => $an->data->{sys}{db_timestamp},
}, file => $THIS_FILE, line => __LINE__});
}
$an->Log->entry({log_level => 3, message_key => "an_variables_0003", message_variables => {
name1 => "sys::read_db_id", value1 => $an->data->{sys}{read_db_id},
name2 => "sys::use_db_fh", value2 => $an->data->{sys}{use_db_fh},
name3 => "sys::db_timestamp", value3 => $an->data->{sys}{db_timestamp},
}, file => $THIS_FILE, line => __LINE__});
}
}
# Do I have any connections? Don't die, if not, just return.
$an->Log->entry({log_level => 3, message_key => "an_variables_0001", message_variables => {
name1 => "connections", value1 => $connections,
}, file => $THIS_FILE, line => __LINE__});
if (not $connections)
{
# Failed to connect to any database. Log this, print to the caller and return.
$an->Log->entry({log_level => 1, message_key => "tools_log_0021", message_variables => {
title => $an->String->get({key => "tools_title_0003"}),
message => $an->String->get({key => "error_message_0060"}),
}, file => $THIS_FILE, line => __LINE__});
print $an->String->get({ key => "tools_log_0021", variables => {
title => $an->String->get({key => "tools_title_0003"}),
message => $an->String->get({key => "error_message_0060"}),
}})."\n";
return($connections);
}
# Report any failed DB connections
foreach my $id (@{$failed_connections})
{
# Copy my alert hash before I delete the id.
my $error_array = [];
# Delete this DB so that we don't try to use it later.
$an->Log->entry({log_level => 3, message_key => "error_title_0018", message_variables => {
id => $id
}, file => $THIS_FILE, line => __LINE__});
delete $an->data->{database}{$id};
# If I've not sent an alert about this DB loss before, send one now.
my $set = $an->Alert->check_alert_sent({
type => "warning",
alert_sent_by => $THIS_FILE,
alert_record_locator => $id,
alert_name => "connect_to_db",
modified_date => $an->data->{sys}{db_timestamp},
});
$an->Log->entry({log_level => 3, message_key => "an_variables_0001", message_variables => {
name1 => "set", value1 => $set
}, file => $THIS_FILE, line => __LINE__});
if ($set)
{
$an->Log->entry({log_level => 3, message_key => "an_variables_0001", message_variables => {
name1 => "error_array", value1 => $error_array
}, file => $THIS_FILE, line => __LINE__});
foreach my $hash (@{$error_array})
{
my $message_key = $hash->{message_key};
my $message_variables = $hash->{message_variables};
$an->Log->entry({log_level => 3, message_key => "an_variables_0003", message_variables => {
name1 => "hash", value1 => $hash,
name2 => "message_key", value2 => $message_key,
name3 => "message_variables", value3 => $message_variables,
}, file => $THIS_FILE, line => __LINE__});
# These are warning level alerts.
$an->Alert->register_alert({
alert_level => "warning",
alert_agent_name => "ScanCore",
alert_title_key => "an_alert_title_0004",
alert_message_key => $message_key,
alert_message_variables => $message_variables,
});
}
}
}
# Send an 'all clear' message if a now-connected DB previously wasn't.
foreach my $id (@{$successful_connections})
{
# Query to see if the newly connected host is in the DB yet. If it isn't, don't send an
# alert as it'd cause a duplicate UUID error.
my $query = "SELECT COUNT(*) FROM hosts WHERE host_name = ".$an->data->{sys}{use_db_fh}->quote($an->data->{database}{$id}{host}).";";
$an->Log->entry({log_level => 3, message_key => "an_variables_0001", message_variables => {
name1 => "query", value1 => $query
}, file => $THIS_FILE, line => __LINE__});
my $count = $an->Database->query({id => $id, query => $query, source => $THIS_FILE, line => __LINE__})->[0]->[0];
$an->Log->entry({log_level => 3, message_key => "an_variables_0001", message_variables => {
name1 => "count", value1 => $count
}, file => $THIS_FILE, line => __LINE__});
if ($count > 0)
{
my $cleared = $an->Alert->check_alert_sent({
type => "clear",
alert_sent_by => $THIS_FILE,
alert_record_locator => $id,
alert_name => "connect_to_db",
modified_date => $an->data->{sys}{db_timestamp},
});
if ($cleared)
{
$an->Alert->register_alert({
alert_level => "warning",
alert_agent_name => "ScanCore",
alert_title_key => "an_alert_title_0006",
alert_message_key => "cleared_message_0001",
alert_message_variables => {
name => $an->data->{database}{$id}{name},
host => $an->data->{database}{$id}{host},
port => $an->data->{database}{$id}{port} ? $an->data->{database}{$id}{port} : 5432,
},
});
}
}
}
$an->Log->entry({log_level => 3, message_key => "an_variables_0001", message_variables => {
name1 => "sys::host_uuid", value1 => $an->data->{sys}{host_uuid},
}, file => $THIS_FILE, line => __LINE__});
if ($an->data->{sys}{host_uuid} !~ /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/)
{
# derp
$an->Log->entry({log_level => 0, message_key => "error_message_0061", file => $THIS_FILE, line => __LINE__});
# Disconnect and set the connection count to '0'.
$an->DB->disconnect_from_databases();
$connections = 0;
}
# For now, we just find which DBs are behind and let each agent deal with bringing their tables up to
# date.
$an->DB->find_behind_databases({file => $file});
# Hold if a lock has been requested.
$an->DB->locking();
# Mark that we're not active.
$an->DB->mark_active({set => 1});
return($connections);
}
=head2 get_local_id =head2 get_local_id
This returns the database ID from 'C<< striker.conf >>' based on matching the 'C<< database::<id>::host >>' to the local machine's host name or one of the active IP addresses on the host. This returns the database ID from 'C<< striker.conf >>' based on matching the 'C<< database::<id>::host >>' to the local machine's host name or one of the active IP addresses on the host.
@ -87,6 +515,7 @@ sub get_local_id
my $network_details = $an->Get->network_details; my $network_details = $an->Get->network_details;
foreach my $id (sort {$a cmp $b} keys %{$an->data->{database}}) foreach my $id (sort {$a cmp $b} keys %{$an->data->{database}})
{ {
next if $id eq "general"; # This is used for global values.
if ($network_details->{hostname} eq $an->data->{database}{$id}{host}) if ($network_details->{hostname} eq $an->data->{database}{$id}{host})
{ {
$local_id = $id; $local_id = $id;
@ -101,6 +530,7 @@ sub get_local_id
my $subnet_mask = $network_details->{interface}{$interface}{netmask}; my $subnet_mask = $network_details->{interface}{$interface}{netmask};
foreach my $id (sort {$a cmp $b} keys %{$an->data->{database}}) foreach my $id (sort {$a cmp $b} keys %{$an->data->{database}})
{ {
next if $id eq "general"; # This is used for global values.
if ($ip_address eq $an->data->{database}{$id}{host}) if ($ip_address eq $an->data->{database}{$id}{host})
{ {
$local_id = $id; $local_id = $id;
@ -113,6 +543,424 @@ sub get_local_id
return($local_id); return($local_id);
} }
=head2 initialize
This will initialize an empty database.
=cut
sub initialize
{
my $self = shift;
my $parameter = shift;
my $an = $self->parent;
my $id = $parameter->{id} ? $parameter->{id} : $an->data->{sys}{read_db_id};
my $sql_file = $parameter->{sql_file} ? $parameter->{sql_file} : $an->data->{database}{$id}{core_sql};
my $success = 1;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
id => $id,
sql_file => $sql_file,
}});
if (not $id)
{
# No database to talk to...
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0077"});
return(0);
}
elsif (not defined $an->data->{cache}{db_fh}{$id})
{
# Database handle is gone.
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0078", variables => { id => $id }});
return(0);
}
if (not $sql_file)
{
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0079", variables => {
server => $an->data->{database}{$id}{host}.":".$an->data->{database}{$id}{port}." -> ".$an->data->{database}{$id}{name}
id => $id,
}});
return(0);
}
elsif (not -e $sql_file)
{
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0080", variables => {
server => $an->data->{database}{$id}{host}.":".$an->data->{database}{$id}{port}." -> ".$an->data->{database}{$id}{name}
id => $id,
sql_file => $sql_file,
}});
return(0);
}
elsif (not -r $sql_file)
{
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0081", variables => {
server => $an->data->{database}{$id}{host}.":".$an->data->{database}{$id}{port}." -> ".$an->data->{database}{$id}{name}
id => $id,
sql_file => $sql_file,
}});
return(0);
}
# Tell the user we need to initialize
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0082", variables => {
server => $an->data->{database}{$id}{host}.":".$an->data->{database}{$id}{port}." -> ".$an->data->{database}{$id}{name}
id => $id,
sql_file => $sql_file,
}});
$an->Log->entry({log_level => 1, title_key => "tools_title_0005", message_key => "tools_log_0020", message_variables => {
server => $an->data->{database}{$id}{host}.":".$an->data->{database}{$id}{port}." -> ".$an->data->{database}{$id}{name}
sql_file => $sql_file,
}, file => $THIS_FILE, line => __LINE__});
# Read in the SQL file and replace #!variable!name!# with the database owner name.
my $user = $an->data->{database}{$id}{user};
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { user => $user }});
my $sql = $an->Storage->read_file({file => $sql_file});
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { ">> sql" => $sql }});
$sql =~ s/#!variable!user!#/$user/sg;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { "<< sql" => $sql }});
### NOTE: Left off here
# Now that I am ready, disable autocommit, write and commit.
$an->Database->write({id => $id, query => $sql, source => $THIS_FILE, line => __LINE__});
$an->data->{sys}{db_initialized}{$id} = 1;
# Mark that we need to update the DB.
$an->data->{database_resync_needed} = 1;
return($success);
};
=head2 query
This performs a query and returns an array reference of array references (from C<< DBO->fetchall_arrayref >>). The first array contains all the returned rows and each row is an array reference of columns in that row.
If an error occurs, C<< undef >> will be returned.
For example, given the query;
scancore=# SELECT host_uuid, host_name, host_type FROM hosts ORDER BY host_name ASC;
host_uuid | host_name | host_type
--------------------------------------+--------------------------+-----------
e27fc9a0-2656-4aaf-80e6-fedb3c339037 | an-a01n01.alteeve.com | node
4bea6ddd-c3ff-43e9-8e9e-b2dea1923145 | an-a01n02.alteeve.com | node
ff852db7-c77a-403b-877f-91f85f3ad95c | an-striker01.alteeve.com | dashboard
2dd5aab1-65d6-4416-9bc1-98dc344aa08b | an-striker02.alteeve.com | dashboard
(4 rows)
The returned array would have four values, one for each returned row. Each row would be an array reference containing three values, one per row. So given the above example;
my $rows = $an->Database->query({query => "SELECT host_uuid, host_name, host_type FROM hosts ORDER BY host_name ASC;"});
foreach my $columns (@{$results})
{
my $host_uuid = $columns->[0];
my $host_name = $columns->[1];
my $host_type = $columns->[2];
print "Host: [$host_name] (UUID: [$host_uuid], type: [$host_type]).\n";
}
Would print;
Host: [an-a01n01.alteeve.com] (UUID: [e27fc9a0-2656-4aaf-80e6-fedb3c339037], type: [node]).
Host: [an-a01n02.alteeve.com] (UUID: [4bea6ddd-c3ff-43e9-8e9e-b2dea1923145], type: [node]).
Host: [an-striker01.alteeve.com] (UUID: [ff852db7-c77a-403b-877f-91f85f3ad95c], type: [dashboard]).
Host: [an-striker02.alteeve.com] (UUID: [2dd5aab1-65d6-4416-9bc1-98dc344aa08b], type: [dashboard]).
B<NOTE>: Do not sort the array references; They won't make any sense as the references are randomly created pointers. The arrays will be returned in the order of the returned data, so do your sorting in the query itself.
Parameters;
=head3 id (optional)
By default, the local database will be queried (if run on a machine with a database). Otherwise, the first database successfully connected to will be used for queries (as stored in C<< $an->data->{sys}{read_db_id} >>).
If you want to read from a specific database, though, you can set this parameter to the ID of the database (C<< database::<id>::host). If you specify a read from a database that isn't available, C<< undef >> will be returned.
=head3 line (optional)
To help with logging the source of a query, C<< line >> can be set to the line number of the script that requested the query. It is generally used along side C<< source >>.
=head3 query (required)
This is the SQL query to perform.
B<NOTE>: ALWAYS use C<< $an->data->{sys}{use_db_fh}->quote(...)>> when preparing data coming from ANY external source! Otherwise you'll end up XKCD 327'ing your database eventually...
=head3 secure (optional, defaul '0')
If set, the query will be treated as containing sensitive data and will only be logged if C<< $an->Log->secure >> is enabled.
=head3 source (optional)
To help with logging the source of a query, C<< source >> can be set to the name of the script that requested the query. It is generally used along side C<< line >>.
=cut
sub query
{
my $self = shift;
my $parameter = shift;
my $an = $self->parent;
my $id = $parameter->{id} ? $parameter->{id} : $an->data->{sys}{read_db_id};
my $line = $parameter->{line} ? $parameter->{line} : __LINE__;
my $query = $parameter->{query} ? $parameter->{query} : "";
my $secure = $parameter->{secure} ? $parameter->{secure} : 0;
my $source = $parameter->{source} ? $parameter->{source} : $THIS_FILE;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
id => $id,
"cache::db_fh::${id}" => $an->data->{cache}{db_fh}{$id},
line => $line,
query => ((not $an->Log->secure) && ($secure)) ? $query : "--",
secure => $secure,
source => $source,
}});
if (not $id)
{
# No database to talk to...
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0072"});
return(undef);
}
elsif (not defined $an->data->{cache}{db_fh}{$id})
{
# Database handle is gone.
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0073", variables => { id => $id }});
return(undef);
}
if (not $query)
{
# No query
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0084", variables => {
server => $an->data->{database}{$id}{host}.":".$an->data->{database}{$id}{port}." -> ".$an->data->{database}{$id}{name},
}});
return(undef);
}
### TODO: If I am still alive check if any locks need to be renewed.
#$an->Database->check_lock_age;
### TODO: Do I need to log the transaction?
#if ($an->Log->db_transactions())
if (1)
{
$an->Log->entry({source => $source, line => $line, secure => $secure, level => 2, key => "log_0074", variables => {
id => $id
query => $query,
}});
}
# Test access to the DB before we do the actual query
$an->Database->_test_access({id => $id});
# Do the query.
my $DBreq = $an->data->{cache}{db_fh}{$id}->prepare($query) or $an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0075", variables => {
query => ((not $an->Log->secure) && ($secure)),
server => $an->data->{database}{$id}{host}.":".$an->data->{database}{$id}{port}." -> ".$an->data->{database}{$id}{name},
db_error => $DBI::errstr,
}});
# Execute on the query
$DBreq->execute() or $an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0076", variables => {
query => ((not $an->Log->secure) && ($secure)),
server => $an->data->{database}{$id}{host}.":".$an->data->{database}{$id}{port}." -> ".$an->data->{database}{$id}{name},
db_error => $DBI::errstr,
}});
# Return the array
return($DBreq->fetchall_arrayref());
}
=head2 write
This records data to one or all of the databases. If an ID is passed, the query is written to one database only. Otherwise, it will be written to all DBs.
=cut
sub write
{
my $self = shift;
my $parameter = shift;
my $an = $self->parent;
my $id = $parameter->{id} ? $parameter->{id} : $an->data->{sys}{read_db_id};
my $line = $parameter->{line} ? $parameter->{line} : __LINE__;
my $query = $parameter->{query} ? $parameter->{query} : "";
my $secure = $parameter->{secure} ? $parameter->{secure} : 0;
my $source = $parameter->{source} ? $parameter->{source} : $THIS_FILE;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
id => $id,
"cache::db_fh::${id}" => $an->data->{cache}{db_fh}{$id},
line => $line,
query => ((not $an->Log->secure) && ($secure)) ? $query : "--",
secure => $secure,
source => $source,
}});
# We don't check if ID is set here because not being set simply means to write to all available DBs.
if (not $query)
{
# No query
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0085", variables => {
server => $an->data->{database}{$id}{host}.":".$an->data->{database}{$id}{port}." -> ".$an->data->{database}{$id}{name},
}});
return(undef);
}
# TODO: If I am still alive check if any locks need to be renewed.
#$an->DB->check_lock_age;
# This array will hold either just the passed DB ID or all of them, if no ID was specified.
my @db_ids;
if ($id)
{
push @db_ids, $id;
}
else
{
foreach my $id (sort {$a cmp $b} keys %{$an->data->{cache}{db_fh}})
{
push @db_ids, $id;
}
}
# Sort out if I have one or many queries.
my $limit = 25000;
my $count = 0;
my $query_set = [];
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { "database::general::maximum_batch_size" => $an->data->{database}{general}{maximum_batch_size} }});
if ($an->data->{database}{general}{maximum_batch_size})
{
if ($an->data->{database}{general}{maximum_batch_size} =~ /\D/)
{
# Bad value.
$an->data->{database}{general}{maximum_batch_size} = 25000;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { "database::general::maximum_batch_size" => $an->data->{database}{general}{maximum_batch_size} }});
}
# Use the set value now.
$limit = $an->data->{database}{general}{maximum_batch_size};
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { limit => $limit }});
}
if (ref($query) eq "ARRAY")
{
# Multiple things to enter.
$count = @{$query};
# If I am re-entering, then we'll proceed normally. If not, and if we have more than 10k
# queries, we'll split up the queries into 10k chunks and re-enter.
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
count => $count,
limit => $limit
reenter => $reenter,
}});
if (($count > $limit) && (not $reenter))
{
my $i = 0;
my $next = $limit;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { i => $i, 'next' => $next }});
foreach my $this_query (@{$query})
{
push @{$query_set}, $this_query;
$i++;
if ($i > $next)
{
# Commit this batch.
foreach my $id (@db_ids)
{
# Commit this chunk to this DB.
$an->DB->do_db_write({id => $id, query => $query_set, source => $THIS_FILE, line => $line, reenter => 1});
### TODO: Rework this so that we exit here (so that we can
### send an alert) if the RAM use is too high.
# This can get memory intensive, so check our RAM usage and
# bail if we're eating too much.
#my $ram_use = $an->System->check_memory({ program_name => $THIS_FILE });
# Wipe out the old set array, create it as a new anonymous array and reset 'i'.
undef $query_set;
$query_set = [];
$i = 0;
}
}
}
}
else
{
# Not enough to worry about or we're dealing with a chunk, proceed as normal.
foreach my $this_query (@{$query})
{
push @{$query_set}, $this_query;
}
}
}
else
{
push @{$query_set}, $query;
}
foreach my $id (@db_ids)
{
# Test access to the DB before we do the actual query
$an->Database->_test_access({id => $id});
# Do the actual query(ies)
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0085", variables => {
id => $id,
count => $count,
}});
if ($count)
{
# More than one query, so start a transaction block.
$an->data->{cache}{db_fh}{$id}->begin_work;
}
foreach my $query (@{$query_set})
{
# TODO: Record the query
#if ($an->Log->db_transactions())
if (1)
{
$an->Log->entry({source => $source, line => $line, secure => $secure, level => 2, key => "log_0074", variables => {
id => $id
query => $query,
}});
}
if (not $an->data->{cache}{db_fh}{$id})
{
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0089", variables => { id => $id }});
next;
}
# Do the do.
$an->data->{cache}{db_fh}{$id}->do($query) or $an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0090", variables => {
query => ((not $an->Log->secure) && ($secure)),
server => $an->data->{database}{$id}{host}.":".$an->data->{database}{$id}{port}." -> ".$an->data->{database}{$id}{name},
db_error => $DBI::errstr,
}});
}
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { count => $count }});
if ($count)
{
# Commit the changes.
$an->data->{cache}{db_fh}{$id}->commit();
}
}
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { count => $count }});
if ($count)
{
# Free up some memory.
undef $query_set;
}
return(0);
}
# =head3 # =head3
# #
@ -123,3 +971,47 @@ sub get_local_id
############################################################################################################# #############################################################################################################
# Private functions # # Private functions #
############################################################################################################# #############################################################################################################
=head2 _test_access
This method takes a database ID and performs a simple C<< SELECT 1 >> query, wrapped in a ten second C<< alarm >>. If the database has died, the query will hang and the C<< alarm >> will fire, killing this program. If the call returns, the C<< alarm >> is cancelled.
This exists to handle the loss of a database mid-run where a normal query, which isn't wrapped in a query, could hang indefinately.
=cut
sub _test_access
{
my $self = shift;
my $parameter = shift;
my $an = $self->parent;
my $id = $parameter->{id} ? $parameter->{id} : "";
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { id => $id }});
# Log our test
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0087", variables => {
server => $an->data->{database}{$id}{host}.":".$an->data->{database}{$id}{port}." -> ".$an->data->{database}{$id}{name},
}});
my $query = "SELECT 1";
my $DBreq = $an->data->{cache}{db_fh}{$id}->prepare($query) or $an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0075", variables => {
query => $query,
server => $an->data->{database}{$id}{host}.":".$an->data->{database}{$id}{port}." -> ".$an->data->{database}{$id}{name},
db_error => $DBI::errstr,
}});
# Give the test query a few seconds to respond, just in case we have some latency to a remote DB.
alarm(10);
$DBreq->execute() or $an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0076", variables => {
query => $query,
server => $an->data->{database}{$id}{host}.":".$an->data->{database}{$id}{port}." -> ".$an->data->{database}{$id}{name},
db_error => $DBI::errstr,
}});
# If we're here, we made contact.
alarm(0);
# Success!
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0088"});
return(0);
}

@ -15,6 +15,7 @@ my $THIS_FILE = "Get.pm";
# host_uuid # host_uuid
# network_details # network_details
# switches # switches
# users_home
# uuid # uuid
=pod =pod
@ -361,6 +362,74 @@ sub switches
return(0); return(0);
} }
=head2 users_home
This method takes a user's name and returns the user's home directory. If the home directory isn't found, C<< 0 >> is returned.
Parameters;
=head3 user (required)
This is the user whose home directory you are looking for.
=cut
sub users_home
{
my $self = shift;
my $parameter = shift;
my $an = $self->parent;
my $home_directory = 0;
my $user = $parameter->{user} ? $parameter->{user} : "";
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { user => $user }});
# Make sure the user is only one digit. Sometimes $< (and others) will return multiple IDs.
if ($user =~ /^\d+ \d$/)
{
$user =~ s/^(\d+)\s.*$/$1/;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { user => $user }});
}
# If the user is numerical, convert it to a name.
if ($user =~ /^\d+$/)
{
$user = getpwuid($user);
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { user => $user }});
}
# Still don't have a name? fail...
if ($user eq "")
{
# No user? No bueno...
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0060"});
return($home_directory);
}
my $body = $an->Storage->read_file({file => $an->data->{path}{data}{passwd}});
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { body => $body }});
foreach my $line (split /\n/, $body)
{
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { line => $line }});
if ($line =~ /^$user:/)
{
$home_directory = (split/:/, $line)[5];
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { home_directory => $home_directory }});
last;
}
}
close $file_handle;
# Do I have the a user's $HOME now?
if (not $home_directory)
{
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0061", variables => { user => $user }});
}
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { home_directory => $home_directory }});
return($home_directory);
}
=head2 uuid =head2 uuid
This method returns a new UUID (using 'uuidgen' from the system). It takes no parameters. This method returns a new UUID (using 'uuidgen' from the system). It takes no parameters.

@ -627,7 +627,7 @@ sub read_config
This reads in a file and returns the contents of the file as a single string variable. This reads in a file and returns the contents of the file as a single string variable.
$an->Storage->read_file({file => "/tmp/foo"}); my $body = $an->Storage->read_file({file => "/tmp/foo"});
If it fails to find the file, or the file is not readable, 'C<< undef >>' is returned. If it fails to find the file, or the file is not readable, 'C<< undef >>' is returned.

@ -6,6 +6,7 @@ package AN::Tools::System;
use strict; use strict;
use warnings; use warnings;
use Data::Dumper; use Data::Dumper;
use Net::SSH2;
our $VERSION = "3.0.0"; our $VERSION = "3.0.0";
my $THIS_FILE = "System.pm"; my $THIS_FILE = "System.pm";
@ -13,6 +14,10 @@ my $THIS_FILE = "System.pm";
### Methods; ### Methods;
# call # call
# check_daemon # check_daemon
# check_memory
# ping
# read_ssh_config
# remote_call
# start_daemon # start_daemon
# stop_daemon # stop_daemon
@ -184,6 +189,667 @@ sub check_daemon
return($return); return($return);
} }
=head2 check_memory
=cut
sub check_memory
{
my $self = shift;
my $parameter = shift;
my $an = $self->parent;
my $program_name = defined $parameter->{program_name} ? $parameter->{program_name} : "";
my $program_pid = defined $parameter->{program_pid} ? $parameter->{program_pid} : 0;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
program_name => $program_name,
program_pid => $program_pid,
}});
if ((not $program_name) && (not $program_pid))
{
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0086"});
return("");
}
my $used_ram = 0;
### TODO: This needs to call the new version of 'anvil-report-memory' to get the amount of memory and
### return the answer to the caller.
return($used_ram);
}
=head2 ping
This method will attempt to ping a target, by hostname or IP, and returns C<< 1 >> if successful, and C<< 0 >> if not.
Example;
# Test access to the internet. Allow for three attempts to account for network jitter.
my $pinged = $an->System->ping({
ping => "google.ca",
count => 3,
});
# Test 9000-byte jumbo-frame access to a target over the BCN.
my $jumbo_to_peer = $an->System->ping({
ping => "an-a01n02.bcn",
count => 1,
payload => 9000,
fragment => 0,
});
# Check to see if an Anvil! node has internet access
my $pinged = $an->System->ping({
target => "an-a01n01.alteeve.com",
port => 22,
password => "super secret",
ping => "google.ca",
count => 3,
});
Parameters;
=head3 count (optional, default '1')
This tells the method how many time to try to ping the target. The method will return as soon as any ping attemp succeeds (unlike pinging from the command line, which always pings the requested count times).
=head3 fragment (optional, default '1')
When set to C<< 0 >>, the ping will fail if the packet has to be fragmented. This is meant to be used along side C<< payload >> for testing MTU sizes.
=head3 password (optional)
This is the password used to access a remote machine. This is used when pinging from a remote machine to a given ping target.
=head3 payload (optional)
This can be used to force the ping packet size to a larger number of bytes. It is most often used along side C<< fragment => 0 >> as a way to test if jumbo frames are working as expected.
B<NOTE>: The payload will have 28 bytes removed to account for ICMP overhead. So if you want to test an MTU of '9000', specify '9000' here. You do not need to account for the ICMP overhead yourself.
=head3 port (optional, default '22')
This is the port used to access a remote machine. This is used when pinging from a remote machine to a given ping target.
B<NOTE>: See C<< System->remote_call >> for additional information on specifying the SSH port as part of the target.
=head3 target (optional)
This is the host name or IP address of a remote machine that you want to run the ping on. This is used to test a remote machine's access to a given ping target.
=cut
sub ping
{
my $self = shift;
my $parameter = shift;
my $an = $self->parent;
$an->Log->entry({log_level => 3, message_key => "tools_log_0001", message_variables => { function => "ping" }, file => $THIS_FILE, line => __LINE__});
# If we were passed a target, try pinging from it instead of locally
my $count = $parameter->{count} ? $parameter->{count} : 1; # How many times to try to ping it? Will exit as soon as one succeeds
my $fragment = $parameter->{fragment} ? $parameter->{fragment} : 1; # Allow fragmented packets? Set to '0' to check MTU.
my $password = $parameter->{password} ? $parameter->{password} : "";
my $payload = $parameter->{payload} ? $parameter->{payload} : 0; # The size of the ping payload. Use when checking MTU.
my $ping = $parameter->{ping} ? $parameter->{ping} : "";
my $port = $parameter->{port} ? $parameter->{port} : "";
my $target = $parameter->{target} ? $parameter->{target} : "";
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
count => $count,
fragment => $fragment,
payload => $payload,
password => $an->Log->secure ? $password : "--",
ping => $ping,
port => $port,
target => $target,
}});
# If the payload was set, take 28 bytes off to account for ICMP overhead.
if ($payload)
{
$payload -= 28;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { payload => $payload }});
}
# Build the call
my $shell_call = $an->data->{path}{exe}{'ping'}." -W 1 -n $ping -c 1";
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }});
if (not $fragment)
{
$shell_call .= " -M do";
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }});
}
if ($payload)
{
$shell_call .= " -s $payload";
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }});
}
my $pinged = 0;
my $average_ping_time = 0;
foreach my $try (1..$count)
{
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { count => $count, try => $try }});
last if $pinged;
my $output = "";
# If the 'target' is set, we'll call over SSH unless 'target' is 'local' or our hostname.
if (($target) && ($target ne "local") && ($target ne $an->hostname) && ($target ne $an->short_hostname))
{
### Remote calls
$output = $an->System->remote_call({
shell_call => $shell_call,
target => $target,
port => $port,
password => $password,
});
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { output => $output }});
}
else
{
### Local calls
$output = $an->System->call({shell_call => $an->data->{path}{exe}{systemctl}." start ".$say_daemon."; ".$an->data->{path}{exe}{'echo'}." return_code:\$?"});
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { output => $output }});
}
foreach my $line (split/\n/, $output)
{
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { line => $line }});
if ($line =~ /(\d+) packets transmitted, (\d+) received/)
{
# This isn't really needed, but might help folks watching the logs.
my $pings_sent = $1;
my $pings_received = $2;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
pings_sent => $pings_sent,
pings_received => $pings_received,
}});
if ($pings_received)
{
# Contact!
$pinged = 1;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { pinged => $pinged }});
}
else
{
# Not yet... Sleep to give time for transient network problems to
# pass.
sleep 1;
}
}
if ($line =~ /min\/avg\/max\/mdev = .*?\/(.*?)\//)
{
$average_ping_time = $1;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { average_ping_time => $average_ping_time }});
}
}
}
# 0 == Ping failed
# 1 == Ping success
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
pinged => $pinged,
average_ping_time => $average_ping_time,
}});
return($pinged, $average_ping_time);
}
=head2 read_ssh_config
This reads /etc/ssh/ssh_config and notes hosts with defined ports. When found, the associated port will be automatically used for a given host name or IP address.
Matches will have their ports stored in C<< hosts::<host_name>::port >>.
This method takes no parameters.
=cut
sub read_ssh_config
{
my $self = shift;
my $an = $self->parent;
# This will hold the raw contents of the file.
$an->data->{raw}{ssh_config} = $an->Storage->read_file({file => $an->data->{path}{configs}{ssh_config}});
foreach my $line (split/\n/, $an->data->{raw}{ssh_config})
{
$line =~ s/#.*$//;
$line =~ s/\s+$//;
next if not $line;
if ($line =~ /^host (.*)/i)
{
$this_host = $1;
next;
}
next if not $this_host;
if ($line =~ /port (\d+)/i)
{
my $port = $1;
$an->data->{hosts}{$this_host}{port} = $port;
}
}
return(0);
}
=head2 remote_call
This does a remote call over SSH. The connection is held open and the file handle for the target is cached and re-used unless a specific ssh_fh is passed or a request to close the connection is received.
Example;
# Call 'hostname' on a node.
my ($error, $output) = $an->System->remote_call({
target => "an-a01n01.alteeve.com",
user => "admin",
password => "super secret password",
shell_call => "/usr/bin/hostname",
});
# Make a call with sensitive data that you want logged only if $an->Log->secure is set and close the
# connection when done.
my ($error, $output) = $an->System->remote_call({
target => "an-a01n01.alteeve.com",
user => "root",
password => "super secret password",
shell_call => "/usr/sbin/fence_ipmilan -a an-a01n02.ipmi -l admin -p \"super secret password\" -o status",
secure => 1,
close => 1,
});
B<NOTE>: By default, a connection to a target will be held open and cached to increase performance for future connections.
Parameters;
=head3 close (optional, default '0')
If set, the connection to the target will be closed at the end of the call.
=head3 no_cache (optional, default '0')
If set, and if an existing cached connection is open, it will be closed and a new connection to the target will be established.
=head3 password (optional)
This is the password used to connect to the remote target as the given user.
B<NOTE>: Passwordless SSH is supported. If you can ssh to the target as the given user without a password, then no password needs to be given here.
=head3 port (optional, default '22')
This is the TCP port to use when connecting to the C<< target >>. The default is port 22.
B<NOTE>: See C<< target >> for optional port definition.
=head3 secure (optional, default '0')
If set, the C<< shell_call >> is treated as containing sensitive data and will not be logged unless C<< $an->Log->secure >> is enabled.
=head3 shell_call (required)
This is the command to run on the target machine as the target user.
=head3 target (required)
This is the host name or IP address of the target machine that the C<< shell_call >> will be run on.
B<NOTE>: If the target matches an entry in '/etc/ssh/ssh_config', the port defined there is used. If the port is set as part of the target name, the port in 'ssh_config' is ignored.
B<NOTE>: If the C<< target >> is presented in the format C<< target:port >>, the port will be separated from the target and used as the TCP port. If the C<< port >> parameter is set, however, the port split off the C<< target >> will be ignored.
=head3 user (optional, default 'root')
This is the user account on the C<< target >> to connect as and to run the C<< shell_call >> as. The C<< password >> if so this user's account on the C<< target >>.
=cut
sub remote_call
{
my $self = shift;
my $parameter = shift;
my $an = $self->parent;
# Get the target and port so that we can create the ssh_fh key
my $port = defined $parameter->{port} ? $parameter->{port} : 22;
my $target = defined $parameter->{target} ? $parameter->{target} : "";
my $ssh_fh_key = $target.":".$port;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
port => $port,
target => $target,
}});
# This will store the SSH file handle for the given target after the initial connection.
$an->data->{cache}{ssh_fh}{$ssh_fh_key} = defined $an->data->{cache}{ssh_fh}{$ssh_fh_key} ? $an->data->{cache}{ssh_fh}{$ssh_fh_key} : "";
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { "cache::ssh_fh::${ssh_fh_key}" => $an->data->{cache}{ssh_fh}{$ssh_fh_key} }});
# Now pick up the rest of the variables.
my $close = defined $parameter->{'close'} ? $parameter->{'close'} : 0;
my $no_cache = defined $parameter->{no_cache} ? $parameter->{no_cache} : 0;
my $password = defined $parameter->{password} ? $parameter->{password} : $an->data->{sys}{root_password};
my $secure = defined $parameter->{secure} ? $parameter->{secure} : 0;
my $shell_call = defined $parameter->{shell_call} ? $parameter->{shell_call} : "";
my $user = defined $parameter->{user} ? $parameter->{user} : "root";
my $start_time = time;
my $ssh_fh = $an->data->{cache}{ssh_fh}{$ssh_fh_key};
# NOTE: The shell call might contain sensitive data, so we show '--' if 'secure' is set and $an->Log->secure is not.
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
'close' => $close,
password => $an->Log->secure ? $password : "--",
secure => $secure,
shell_call => ((not $an->Log->secure) && ($secure)) ? "--" : $shell_call,
ssh_fh => $ssh_fh,
start_time => $start_time,
user => $user,
}});
if (not $shell_call)
{
# No shell call
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0055"});
return(undef);
}
if (not $target)
{
# No target
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0056"});
return(undef);
}
if (not $user)
{
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0057"});
return(undef);
}
# If the user didn't pass a port, but there is an entry in 'hosts::<host>::port', use it.
if ((not $parameter->{port}) && ($an->data->{hosts}{$target}{port}))
{
$port = $an->data->{hosts}{$target}{port};
}
# Break out the port, if needed.
my $state;
my $error;
if ($target =~ /^(.*):(\d+)$/)
{
$target = $1;
$port = $2;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
port => $port,
target => $target,
}});
# If the user passed a port, override this.
if ($parameter->{port} =~ /^\d+$/)
{
$port = $parameter->{port};
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { port => $port }});
}
}
else
{
# In case the user is using ports in /etc/ssh/ssh_config, we'll want to check for an entry.
$an->System->read_ssh_config();
$an->data->{hosts}{$target}{port} = "" if not defined $an->data->{hosts}{$target}{port};
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { "hosts::${target}::port" => $an->data->{hosts}{$target}{port} }});
if ($an->data->{hosts}{$target}{port} =~ /^\d+$/)
{
$port = $an->data->{hosts}{$target}{port};
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { port => $port }});
}
}
# Make sure the port is valid.
if (($port !~ /^\d+$/) or ($port < 0) or ($port > 65536))
{
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0058", variables => { port => $port }});
return(undef);
}
# If the target is a host name, convert it to an IP.
if (not $an->Validate->is_ipv4({ip => $target}))
{
my $new_target = $an->Convert->hostname_to_ip({host_name => $target});
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { new_target => $new_target }});
if ($new_target)
{
$target = $new_target;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { target => $target }});
}
}
# If the user set 'no_cache', don't use any existing 'ssh_fh'.
if (($no_cache) && ($ssh_fh))
{
# Close the connection.
$ssh_fh->disconnect();
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "message_0010", variables => { target => $target }});
# For good measure, blank both variables.
$an->data->{cache}{ssh_fh}{$ssh_fh_key} = "";
$ssh_fh = "";
}
# These will be merged into a single 'output' array before returning.
my $stdout_output = [];
my $stderr_output = [];
# If I don't already have an active SSH file handle, connect now.
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { ssh_fh => $ssh_fh }});
if ($ssh_fh !~ /^Net::SSH2/)
{
$ssh_fh = Net::SSH2->new();
if (not $ssh_fh->connect($target, $port, Timeout => 10))
{
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", list => {
user => $user,
target => $target,
port => $port,
shell_call => $shell_call,
error => $@,
}});
# We'll now try to get a more useful message for the user and logs.
my $message_key = "message_0005";
my $variables = { target => $target };
if ($@ =~ /Bad hostname/i)
{
$message_key = "message_0001";
}
elsif ($@ =~ /Connection refused/i)
{
$message_key = "message_0002";
$variables = {
target => $target,
port => $port,
user => $user,
};
}
elsif ($@ =~ /No route to host/)
{
$message_key = "message_0003";
}
elsif ($@ =~ /timeout/)
{
$message_key = "message_0004";
}
$error = $an->Words->string({key => $message_key, variables => { $variables }});
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", key => $message_key, variables => { $variables }});
}
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { error => $error, ssh_fh => $ssh_fh }});
if (not $error)
{
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
user => $user,
password => $an->Log->secure ? $password : "--",
}});
if (not $ssh_fh->auth_password($user, $password))
{
# Can we log in without a password?
my $user = getpwuid($<);
my $home_directory = $an->Get->users_home({user => $user});
my $public_key = $home_directory."/.ssh/id_rsa.pub";
my $private_key = $home_directory."/.ssh/id_rsa";
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
user => $user,
home_directory => $home_directory,
public_key => $public_key,
private_key => $private_key,
}});
if ($ssh_fh->auth_publickey($user, $public_key, $private_key))
{
# We're in! Record the file handle for this target.
$an->data->{cache}{ssh_fh}{$ssh_fh_key} = $ssh_fh;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { "cache::ssh_fh::${ssh_fh_key}" => $an->data->{cache}{ssh_fh}{$ssh_fh_key} }});
# Log that we got in without a password.
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0062", variables => { target => $target }});
}
else
{
# This is for the user
$error = $an->Words->string({key => "message_0006", variables => { target => $target }});
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "message_0006", variables => { target => $target }});
}
}
else
{
# We're in! Record the file handle for this target.
$an->data->{cache}{ssh_fh}{$ssh_fh_key} = $ssh_fh;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { "cache::ssh_fh::${ssh_fh_key}" => $an->data->{cache}{ssh_fh}{$ssh_fh_key} }});
# Record our success
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "message_0007", variables => { target => $target }});
}
}
}
### Special thanks to Rafael Kitover (rkitover@gmail.com), maintainer of Net::SSH2, for helping me
### sort out the polling and data collection in this section.
#
# Open a channel and make the call.
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
error => $error,
ssh_fh => $ssh_fh,
}});
if (($ssh_fh =~ /^Net::SSH2/) && (not $error))
{
# We need to open a channel every time for 'exec' calls. We want to keep blocking off, but we
# need to enable it for the channel() call.
$ssh_fh->blocking(1);
my $channel = $ssh_fh->channel();
$ssh_fh->blocking(0);
# Make the shell call
if (not $channel)
{
# ... or not.
$ssh_fh = "";
$error = $an->Words->string({key => "message_0008", variables => { target => $target }});
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "message_0008", variables => { target => $target }});
}
else
{
### TODO: Timeout if the call doesn't respond in X seconds, closing the filehandle if hit.
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, secure => $secure, list => {
channel => $channel,
shell_call => $shell_call,
}});
$channel->exec("$shell_call");
# This keeps the connection open when the remote side is slow to return data, like in
# '/etc/init.d/rgmanager stop'.
my @poll = {
handle => $channel,
events => [qw/in err/],
};
# We'll store the STDOUT and STDERR data here.
my $stdout = "";
my $stderr = "";
# Not collect the data.
while(1)
{
$ssh_fh->poll(250, \@poll);
# Read in anything from STDOUT
while($channel->read(my $chunk, 80))
{
$stdout .= $chunk;
}
while ($stdout =~ s/^(.*)\n//)
{
my $line = $1;
$line =~ s/\r//g; # Remove \r from things like output of daemon start/stops.
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, secure => $secure, list => { "STDOUT:line" => $line }});
push @{$stdout_output}, $line;
}
# Read in anything from STDERR
while($channel->read(my $chunk, 80, 1))
{
$stderr .= $chunk;
}
while ($stderr =~ s/^(.*)\n//)
{
my $line = $1;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, secure => $secure, list => { "STDERR:line" => $line }});
push @{$stderr_output}, $line;
}
# Exit when we get the end-of-file.
last if $channel->eof;
}
if ($stdout)
{
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, secure => $secure, list => { stdout => $stdout }});
push @{$stdout_output}, $stdout;
}
if ($stderr)
{
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, secure => $secure, list => { stderr => $stderr }});
push @{$stderr_output}, $stderr;
}
}
}
# Merge the STDOUT and STDERR
my $output = [];
foreach my $line (@{$stderr_output}, @{$stdout_output})
{
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, secure => $secure, list => { line => $line }});
push @{$output}, $line;
}
# Close the connection if requested.
if ($close)
{
if ($ssh_fh)
{
# Close it.
$ssh_fh->disconnect();
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "message_0009", variables => { target => $target }});
}
# For good measure, blank both variables.
$an->data->{cache}{ssh_fh}{$ssh_fh_key} = "";
$ssh_fh = "";
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { "cache::ssh_fh::${ssh_fh_key}" => $an->data->{cache}{ssh_fh}{$ssh_fh_key} }});
}
$error = "" if not defined $error;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, secure => $secure, list => {
error => $error,
ssh_fh => $ssh_fh,
output => $output,
}});
return($error, $output);
};
=head2 start_daemon =head2 start_daemon
This method starts a daemon. The return code from the start request will be returned. This method starts a daemon. The return code from the start request will be returned.

@ -13,6 +13,18 @@ This is the AN::Tools master 'words' file.
<!-- Canadian English --> <!-- Canadian English -->
<language name="en_CA" long_name="Canadian English" description="Created by Madison Kelly (mkelly@alteeve.ca) for the AN::Tools suite of perl modules"> <language name="en_CA" long_name="Canadian English" description="Created by Madison Kelly (mkelly@alteeve.ca) for the AN::Tools suite of perl modules">
<!-- Messages for users (less technical than log entries), though sometimes used for logs, too. -->
<key name="message_0001">The host name: [#!variable!target!#] does not resolve to an IP address.</key>
<key name="message_0002">The connection to: [#!variable!target!#] on port: [#!variable!port!#] as the user: [#!variable!user!#] was refused. If you recently booted the target, it may be just about finished booting. It is normal for the connection to be refused for a brief time during the boot process.</key>
<key name="message_0003">There is no route to: [#!variable!target!#]. Is the machine in the process of booting up or powering off?</key>
<key name="message_0004">Timed out while waiting for a reply from: [#!variable!target!#]. Is the machine booting up? If so, please wait a minute or two and try again.</key>
<key name="message_0005">There was an unknown error while connecting to: [#!variable!target!#]. The error was: [#!variable!error!#]</key>
<key name="message_0006">We were unable to log in to: [#!variable!target!#]. Please check that the password is correct or that passwordless SSH is configured properly.</key>
<key name="message_0007">An SSH session was successfully opened to: [#!variable!target!#].</key>
<key name="message_0008">There was a problem establishing an SSH channel to the target: [#!variable!target!#] for shell call: [#!variable!shell_call!#]</key>
<key name="message_0009">The SSH session to: [#!variable!target!#] was successfully closed.</key>
<key name="message_0010">The SSH session to: [#!variable!target!#] was closed because 'no_cache' was set and there was an open SSH connection.</key>
<!-- Log entries --> <!-- Log entries -->
<key name="log_0001">Starting: [#!variable!program!#].</key> <key name="log_0001">Starting: [#!variable!program!#].</key>
<key name="log_0002"> <key name="log_0002">
@ -39,9 +51,9 @@ It also has replacement variables: [#!variable!first!#] and [#!variable!second!#
<key name="log_0017">Output: [#!variable!line!#].</key> <key name="log_0017">Output: [#!variable!line!#].</key>
<key name="log_0018">About to open the directory: [#!variable!directory!#]</key> <key name="log_0018">About to open the directory: [#!variable!directory!#]</key>
<key name="log_0019">Variables:</key> <key name="log_0019">Variables:</key>
<key name="log_0020"><![CDATA[[ Error ] - The module Storage->read_file() was called without a 'file' parameter, or the parameter was empty.]]></key> <key name="log_0020"><![CDATA[[ Error ] - The method Storage->read_file() was called without a 'file' parameter, or the parameter was empty.]]></key>
<key name="log_0021"><![CDATA[[ Error ] - The module Storage->read_file() was asked to read the file: [#!variable!file!#], but that file does not exist.]]></key> <key name="log_0021"><![CDATA[[ Error ] - The method Storage->read_file() was asked to read the file: [#!variable!file!#], but that file does not exist.]]></key>
<key name="log_0022"><![CDATA[[ Error ] - The module Storage->read_file() was asked to read the file: [#!variable!file!#] which exists but can't be read.]]></key> <key name="log_0022"><![CDATA[[ Error ] - The method Storage->read_file() was asked to read the file: [#!variable!file!#] which exists but can't be read.]]></key>
<key name="log_0023">Reading: [#!variable!line!#].</key> <key name="log_0023">Reading: [#!variable!line!#].</key>
<key name="log_0024"><![CDATA[[ Error ] - No template file passed to Template->get().]]></key> <key name="log_0024"><![CDATA[[ Error ] - No template file passed to Template->get().]]></key>
<key name="log_0025"><![CDATA[[ Error ] - No requested template file: [#!variable!source!#] does not exist. Is it missing in the active skin?]]></key> <key name="log_0025"><![CDATA[[ Error ] - No requested template file: [#!variable!source!#] does not exist. Is it missing in the active skin?]]></key>
@ -55,23 +67,81 @@ It also has replacement variables: [#!variable!first!#] and [#!variable!second!#
<key name="log_0033"><![CDATA[[ Warning ] - Words->read()' asked to read: [#!variable!file!#] which was not found.]]></key> <key name="log_0033"><![CDATA[[ Warning ] - Words->read()' asked to read: [#!variable!file!#] which was not found.]]></key>
<key name="log_0034"><![CDATA[[ Warning ] - AN::Tools::Words->read()' asked to read: [#!variable!file!#] which was not readable by: [#!variable!user!#] (uid/euid: [#!variable!uid!#]).]]></key> <key name="log_0034"><![CDATA[[ Warning ] - AN::Tools::Words->read()' asked to read: [#!variable!file!#] which was not readable by: [#!variable!user!#] (uid/euid: [#!variable!uid!#]).]]></key>
<key name="log_0035"><![CDATA[[ Warning ] - The config file: [#!variable!file!#] appears to have a malformed line: [#!variable!count!#:#!variable!line!#].]]></key> <key name="log_0035"><![CDATA[[ Warning ] - The config file: [#!variable!file!#] appears to have a malformed line: [#!variable!count!#:#!variable!line!#].]]></key>
<key name="log_0036"><![CDATA[[ Error ] - The module Storage->change_mode() was called without a 'target' parameter, or the parameter was empty.]]></key> <key name="log_0036"><![CDATA[[ Error ] - The method Storage->change_mode() was called without a 'target' parameter, or the parameter was empty.]]></key>
<key name="log_0037"><![CDATA[[ Error ] - The module Storage->change_mode() was called without a 'mode' parameter, or the parameter was empty.]]></key> <key name="log_0037"><![CDATA[[ Error ] - The method Storage->change_mode() was called without a 'mode' parameter, or the parameter was empty.]]></key>
<key name="log_0038"><![CDATA[[ Error ] - The module Storage->change_mode() was called without an invalid 'mode' parameter. It should have been three or four digits, but: [#!variable!mode!#] was passed.]]></key> <key name="log_0038"><![CDATA[[ Error ] - The method Storage->change_mode() was called without an invalid 'mode' parameter. It should have been three or four digits, but: [#!variable!mode!#] was passed.]]></key>
<key name="log_0039"><![CDATA[[ Error ] - The module Storage->change_owner() was called without a 'target' parameter, or the parameter was empty.]]></key> <key name="log_0039"><![CDATA[[ Error ] - The method Storage->change_owner() was called without a 'target' parameter, or the parameter was empty.]]></key>
<key name="log_0040"><![CDATA[[ Error ] - The module Storage->write_file() was asked to write the file: [#!variable!file!#] but it already exists and 'overwrite' was not set. Aborting.]]></key> <key name="log_0040"><![CDATA[[ Error ] - The method Storage->write_file() was asked to write the file: [#!variable!file!#] but it already exists and 'overwrite' was not set. Aborting.]]></key>
<key name="log_0041"><![CDATA[[ Error ] - The module Storage->write_file() was asked to write the file: [#!variable!file!#] but it is not a full path. Aborting.]]></key> <key name="log_0041"><![CDATA[[ Error ] - The method Storage->write_file() was asked to write the file: [#!variable!file!#] but it is not a full path. Aborting.]]></key>
<key name="log_0042"><![CDATA[[ Error ] - The module Words->string() was asked to process the string: [#!variable!string!#] which has insertion variables, but nothing was passed to the 'variables' parameter.]]></key> <key name="log_0042"><![CDATA[[ Error ] - The method Words->string() was asked to process the string: [#!variable!string!#] which has insertion variables, but nothing was passed to the 'variables' parameter.]]></key>
<key name="log_0043"><![CDATA[[ Error ] - The module System->call() was called but 'shell_call' was not passed or was empty.]]></key> <key name="log_0043"><![CDATA[[ Error ] - The method System->call() was called but 'shell_call' was not passed or was empty.]]></key>
<key name="log_0044"><![CDATA[[ Error ] - The module Storage->copy_file() was called but 'source' was not passed or was empty.]]></key> <key name="log_0044"><![CDATA[[ Error ] - The method Storage->copy_file() was called but 'source' was not passed or was empty.]]></key>
<key name="log_0045"><![CDATA[[ Error ] - The module Storage->copy_file() was called but 'target' was not passed or was empty.]]></key> <key name="log_0045"><![CDATA[[ Error ] - The method Storage->copy_file() was called but 'target' was not passed or was empty.]]></key>
<key name="log_0046"><![CDATA[[ Error ] - The module Storage->copy_file() was asked to copy: [#!variable!source!#] to: [#!variable!target!#], but the target already exists and 'overwrite' wasn't specified, so aborting.]]></key> <key name="log_0046"><![CDATA[[ Error ] - The method Storage->copy_file() was asked to copy: [#!variable!source!#] to: [#!variable!target!#], but the target already exists and 'overwrite' wasn't specified, so aborting.]]></key>
<key name="log_0047"><![CDATA[[ Error ] - The module Log->level() was passed an invalid log level: [#!variable!set!#]. Only '0', '1', '2', '3' or '4' are valid.]]></key> <key name="log_0047"><![CDATA[[ Error ] - The method Log->level() was passed an invalid log level: [#!variable!set!#]. Only '0', '1', '2', '3' or '4' are valid.]]></key>
<key name="log_0048"><![CDATA[[ Warning ] - Testing of AN::Tools is beginning. This will generate warnings and alerts and are not a concern.]]></key> <key name="log_0048"><![CDATA[[ Warning ] - Testing of AN::Tools is beginning. This will generate warnings and alerts and are not a concern.]]></key>
<key name="log_0049"><![CDATA[[ Warning ] - Testing of AN::Tools is complete.]]></key> <key name="log_0049"><![CDATA[[ Warning ] - Testing of AN::Tools is complete.]]></key>
<key name="log_0050"><![CDATA[[ Error ] - The module Storage->read_mode() was called without a 'target' parameter, or the parameter was empty.]]></key> <key name="log_0050"><![CDATA[[ Error ] - The method Storage->read_mode() was called without a 'target' parameter, or the parameter was empty.]]></key>
<key name="log_0051"><![CDATA[[ Error ] - The module Storage->change_owner() was asked to change the ownership of: [#!variable!target!#] which doesn't exist.]]></key> <key name="log_0051"><![CDATA[[ Error ] - The method Storage->change_owner() was asked to change the ownership of: [#!variable!target!#] which doesn't exist.]]></key>
<key name="log_0052"><![CDATA[[ Error ] - The module Storage->copy_file() was called but the source file: [#!variable!source!#] doesn't exist.]]></key> <key name="log_0052"><![CDATA[[ Error ] - The method Storage->copy_file() was called but the source file: [#!variable!source!#] doesn't exist.]]></key>
<key name="log_0053"><![CDATA[[ Error ] - The 'Database->connect()' method tried to connect to the same database twice: [#!variable!target!#].]]></key>
<key name="log_0054">
Connecting to Database with configuration ID: [#!variable!id!#]
- driver: .......... [#!variable!driver!#]
- host: ............ [#!variable!host!#]
- port: ............ [#!variable!port!#]
- postgres_password: [#!variable!postgres_password!#]
- name: ............ [#!variable!name!#]
- user: ............ [#!variable!user!#]
- password: ........ [#!variable!password!#]
</key>
<key name="log_0055"><![CDATA[[ Error ] - The method System->remote_call() was called but 'shell_call' was not passed or was empty.]]></key>
<key name="log_0056"><![CDATA[[ Error ] - The method System->remote_call() was called but 'target' was not passed or was empty.]]></key>
<key name="log_0057"><![CDATA[[ Error ] - The method System->remote_call() was called but 'user' was not passed or was empty.]]></key>
<key name="log_0058"><![CDATA[[ Error ] - The method System->remote_call() was called but the port: [#!variable!port!#] is invalid. It must be a digit between '1' and '65535'.]]></key>
<key name="log_0059"><![CDATA[[ Error ] - The method Convert->hostname_to_ip() was called but 'hostname' was not passed or was empty.]]></key>
<key name="log_0060"><![CDATA[[ Error ] - The method Get->users_home() was called but 'user' was not passed or was empty.]]></key>
<key name="log_0061"><![CDATA[[ Error ] - The method Get->users_home() was asked to find the home directory for the user: [#!variable!user!#], but was unable to do so.]]></key>
<key name="log_0062">SSH session opened without a password to: [#!variable!target!#].</key>
<key name="log_0063">The database with the ID: [#!variable!id!#] did not respond to pings and 'database::#!variable!id!#::ping_before_connect' is not set to '0' in '#!data!path::configs::striker.conf!#', skipping it.</key>
<key name="log_0064">[ Warning ] - The database: [#!variable!name!#] on host: [#!variable!host!#] with ID: [#!variable!id!#] can not be used, skipping it.</key>
<key name="log_0065">
The database connection error was:
----------
#!variable!dbi_error!#
----------
</key>
<key name="log_0066">Is the database server running on: [#!variable!target!#] and does the target's firewall allow connections on TCP port: [#!variable!port!#]?</key>
<key name="log_0067"><![CDATA[The password was not passed, and it is required. Please set: [database::#!variable!id!#::password = <password>] in: [#!data!path::configs::striker.conf!#].]]></key>
<key name="log_0068"><![CDATA[The database user name: [#!variable!user!#] on the host: [#!variable!host!#] is either not the owner of the database: [#!variable!name!#], or the password for that user is incorrect.
- Please edit: [#!data!path::configs::striker.conf!#]
* If the user name is not correct, please update:
database::#!variable!id!#::user = <user>
* If the user name is correct, please update:
database::#!variable!id!#::password = <password>
]]></key>
<key name="log_0069">The connection to the database: [#!variable!name!#] on host: [#!variable!host!#:#!variable!port!#] was refused. Is the database server running?</key>
<key name="log_0070">The connection to the database: [#!variable!name!#] on host: [#!variable!host!#:#!variable!port!#] failed because the name could not be translated to an IP address. Is this database server's host name in '/etc/hosts'?</key>
<key name="log_0071">Successfully Connected to the database: [#!variable!name!#] (id: [#!variable!id!#]) on host: [#!variable!host!#:#!variable!port!#].</key>
<key name="log_0072"><![CDATA[[ Error ] - The method Database->query() was called without a database ID to query and 'sys::read_db_id' doesn't contain a database ID, either. Are any databases available?]]></key>
<key name="log_0073"><![CDATA[[ Error ] - The method Database->query() was asked to query the database with ID: [#!variable!id!#] but there is no file handle open to the database. Was the connection lost?]]></key>
<key name="log_0074">About to query: [#!variable!id!#]:[#!variable!query!#]</key>
<key name="log_0075"><![CDATA[[ Error ] - Failed to prepare the database query: [#!variable!query!#] on: [#!variable!server!#]. The error was: [#!variable!db_error!#]. Note that if the query reports '--', the query was listed as containing sensitive data and '$an->Log->secure' is not set.]]></key>
<key name="log_0076"><![CDATA[[ Error ] - Failed to execute the database query: [#!variable!query!#] on: [#!variable!server!#]. The error was: [#!variable!db_error!#]. Note that if the query reports '--', the query was listed as containing sensitive data and '$an->Log->secure' is not set.]]></key>
<key name="log_0077"><![CDATA[[ Error ] - The method Database->initialize() was called without a database ID to query and 'sys::read_db_id' doesn't contain a database ID, either. Are any databases available?]]></key>
<key name="log_0078"><![CDATA[[ Error ] - The method Database->initialize() was asked to query the database with ID: [#!variable!id!#] but there is no file handle open to the database. Was the connection lost?]]></key>
<key name="log_0079"><![CDATA[[ Error ] - The method Database->initialize() was asked to initialize the database: [#!variable!server!#] (id: [#!variable!id!#]) but a core SQL file to load wasn't passed, and the 'database::#!variable!id!#::core_sql' variable isn't set. Unable to initialize without the core SQL file.]]></key>
<key name="log_0080"><![CDATA[[ Error ] - The method Database->initialize() was asked to initialize the database: [#!variable!server!#] (id: [#!variable!id!#]) but the core SQL file: [#!variable!sql_file!#] doesn't exist.]]></key>
<key name="log_0081"><![CDATA[[ Error ] - The method Database->initialize() was asked to initialize the database: [#!variable!server!#] (id: [#!variable!id!#]) but the core SQL file: [#!variable!sql_file!#] exist, but can't be read.]]></key>
<key name="log_0082">The database: [#!variable!server!#] needs to be initialized using: [#!variable!sql_file!#].</key>
<key name="log_0083"></key>
<key name="log_0084"><![CDATA[[ Error ] - The method Database->query() was asked to query the database: [#!variable!server!#] but no query was given.]]></key>
<key name="log_0085"><![CDATA[[ Error ] - The method Database->write() was asked to write to the database: [#!variable!server!#] but no query was given.]]></key>
<key name="log_0086"><![CDATA[[ Error ] - The method System->check_memory() was called without a program name or PID to check.]]></key>
<key name="log_0087">Testing access to the the database: [#!variable!server!#] prior to query or write. Program will exit if it fails.</key>
<key name="log_0088">Access confirmed.</key>
<key name="log_0089"><![CDATA[[ Error ] - The method Database->write() was asked to write to the database with ID: [#!variable!id!#] but there is no file handle open to the database. Was the connection lost?]]></key>
<key name="log_0090"><![CDATA[[ Error ] - Failed to 'do' the database query: [#!variable!query!#] on: [#!variable!server!#]. The error was: [#!variable!db_error!#]. Note that if the query reports '--', the query was listed as containing sensitive data and '$an->Log->secure' is not set.]]></key>
<!-- Test words. Do NOT change unless you update 't/Words.t' or tests will needlessly fail. --> <!-- Test words. Do NOT change unless you update 't/Words.t' or tests will needlessly fail. -->
<key name="t_0000">Test</key> <key name="t_0000">Test</key>

@ -4,6 +4,7 @@
database::1::host = 192.168.122.201 database::1::host = 192.168.122.201
database::1::port = 5432 database::1::port = 5432
database::1::name = scancore database::1::name = scancore
database::1::core_sql = /etc/scancore/scancore.sql
database::1::user = admin database::1::user = admin
database::1::password = Initial1 database::1::password = Initial1
database::1::ping_before_connect = 1 database::1::ping_before_connect = 1
@ -11,6 +12,12 @@ database::1::ping_before_connect = 1
database::2::host = an-striker02.alteeve.com database::2::host = an-striker02.alteeve.com
database::2::port = 5432 database::2::port = 5432
database::2::name = scancore database::2::name = scancore
database::1::core_sql = /etc/scancore/scancore.sql
database::2::user = admin database::2::user = admin
database::2::password = Initial1 database::2::password = Initial1
database::2::ping_before_connect = 1 database::2::ping_before_connect = 1
# This puts a limit on how many queries (writes, generally) to make in a single batch transaction. This is
# useful when doing very large transacions, like resync'ing a large table, by limiting how long a given
# transaction can take and how much memory is used.
database::general::maximum_batch_size = 25000

Loading…
Cancel
Save