* Created Database->get_bridges() that, surprise, loads data from the 'bridges' table.

* Started work on Get->available_resources() that will take an 'anvil_uuid' and figure out what resources are still available for use by new servers or that can be added to existing servers.
* Fixed a bug in ScanCore->agent_startup() where tables weren't being generated properly from the agent's SQL file.
* Made Storage->change_mode() return silently if it's called without a mode being passed. This happens frequently and is harmless so it's not worth filling the logs with errors.
* Renamed the 'start_time' key to 'at_start' when recording files' MD5 sums in Storage->record_md5sums and ->check_md5sums.
* When we moved the directory scan logic out of the 'scancore' daemon and into 'Storage->scan_directory', the logic to record scan agent names in 'scancore::agent::<file>' was removed. This broke a few things and, so, it was restored when it was found that a file starts with 'scan-' and the directory matches the scancore agent directory.
* Moved the 'scancore' daemon's 'load_agent_strings' to 'Words'
* Updated Words->parse_banged_string() to look for variables in the format 'value=X:units=Y' and translate it properly.
* Fixed a bug in scan-ipmitool where discovered sensor INSERT SQL queries were queued, but not committed.
* Fixed a bug in scan-storcli where a while loop was broken, preventing execution.
* Fixed a bug in the 'scancore' daemon where it wouldn't exit if sums changed. Fixed a bug where alerts weren't being sent between loops. Fixed a bug where command-line log level wasn't surviving inside the main loop.

Signed-off-by: Digimer <digimer@alteeve.ca>
main
Digimer 4 years ago
parent 96bc1f0b78
commit 1d03a386d3
  1. 118
      Anvil/Tools/Database.pm
  2. 12
      Anvil/Tools/Email.pm
  3. 102
      Anvil/Tools/Get.pm
  4. 12
      Anvil/Tools/ScanCore.pm
  5. 77
      Anvil/Tools/Storage.pm
  6. 1
      Anvil/Tools/Striker.pm
  7. 1
      Anvil/Tools/System.pm
  8. 68
      Anvil/Tools/Words.pm
  9. 11
      ocf/alteeve/server
  10. 6
      scancore-agents/scan-apc-pdu/scan-apc-pdu
  11. 2
      scancore-agents/scan-drbd/scan-drbd
  12. 12
      scancore-agents/scan-hardware/scan-hardware
  13. 27
      scancore-agents/scan-ipmitool/scan-ipmitool
  14. 3
      scancore-agents/scan-storcli/scan-storcli
  15. 16
      share/words.xml
  16. 8
      tools/anvil-join-anvil
  17. 53
      tools/scancore
  18. 31
      tools/test.pl

@ -24,6 +24,7 @@ my $THIS_FILE = "Database.pm";
# disconnect # disconnect
# get_alerts # get_alerts
# get_anvils # get_anvils
# get_bridges
# get_fences # get_fences
# get_host_from_uuid # get_host_from_uuid
# get_hosts # get_hosts
@ -2054,6 +2055,115 @@ WHERE
} }
=head2 get_bridges
This loads the known bridge devices into the C<< anvil::data >> hash at:
* bridges::bridge_host_uuid::<host_uuid>::bridge_uuid::<bridge_uuid>::bridge_name
* bridges::bridge_host_uuid::<host_uuid>::bridge_uuid::<bridge_uuid>::bridge_id
* bridges::bridge_host_uuid::<host_uuid>::bridge_uuid::<bridge_uuid>::bridge_mac_address
* bridges::bridge_host_uuid::<host_uuid>::bridge_uuid::<bridge_uuid>::bridge_mtu
* bridges::bridge_host_uuid::<host_uuid>::bridge_uuid::<bridge_uuid>::bridge_stp_enabled
* bridges::bridge_host_uuid::<host_uuid>::bridge_uuid::<bridge_uuid>::modified_date
And, to allow for lookup by name;
* bridges::bridge_host_uuid::<host_uuid>::bridge_name::<bridge_name>::bridge_uuid
If the hash was already populated, it is cleared before repopulating to ensure no stale data remains.
B<<Note>>: Deleted bridges (ones where C<< bridge_id >> is set to C<< DELETED >>) are ignored. See the C<< include_deleted >> parameter to include them.
Parameters;
=head3 include_deleted (Optional, default 0)
If set to C<< 1 >>, deleted bridges are included when loading the data. When C<< 0 >> is set, the default, any bridges with C<< bridge_id >> set to C<< DELETED >> are ignored.
=cut
sub get_bridges
{
my $self = shift;
my $parameter = shift;
my $anvil = $self->parent;
my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Database->get_bridges()" }});
my $include_deleted = defined $parameter->{include_deleted} ? $parameter->{include_deleted} : 0;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
include_deleted => $include_deleted,
}});
if (exists $anvil->data->{bridges})
{
delete $anvil->data->{bridges};
}
my $query = "
SELECT
bridge_uuid,
bridge_host_uuid,
bridge_name,
bridge_id,
bridge_mac_address,
bridge_mtu,
bridge_stp_enabled,
modified_date
FROM
bridges ";
if (not $include_deleted)
{
$query .= "
WHERE
bridge_id != 'DELETED'";
}
$query .= "
;";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { query => $query }});
my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__});
my $count = @{$results};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
results => $results,
count => $count,
}});
foreach my $row (@{$results})
{
my $bridge_uuid = $row->[0];
my $bridge_host_uuid = $row->[1];
my $bridge_name = $row->[2];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
bridge_uuid => $bridge_uuid,
bridge_host_uuid => $bridge_host_uuid,
bridge_name => $bridge_name,
}});
# Record the data in the hash, too.
$anvil->data->{bridges}{bridge_host_uuid}{$bridge_host_uuid}{bridge_uuid}{$bridge_uuid}{bridge_name} = $bridge_name;
$anvil->data->{bridges}{bridge_host_uuid}{$bridge_host_uuid}{bridge_uuid}{$bridge_uuid}{bridge_id} = $row->[3];
$anvil->data->{bridges}{bridge_host_uuid}{$bridge_host_uuid}{bridge_uuid}{$bridge_uuid}{bridge_mac_address} = $row->[4];
$anvil->data->{bridges}{bridge_host_uuid}{$bridge_host_uuid}{bridge_uuid}{$bridge_uuid}{bridge_mtu} = $row->[5];
$anvil->data->{bridges}{bridge_host_uuid}{$bridge_host_uuid}{bridge_uuid}{$bridge_uuid}{bridge_stp_enabled} = $row->[6];
$anvil->data->{bridges}{bridge_host_uuid}{$bridge_host_uuid}{bridge_uuid}{$bridge_uuid}{modified_date} = $row->[7];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"bridges::bridge_host_uuid::${bridge_host_uuid}::bridge_uuid::${bridge_uuid}::bridge_name" => $anvil->data->{bridges}{bridge_host_uuid}{$bridge_host_uuid}{bridge_uuid}{$bridge_uuid}{bridge_name},
"bridges::bridge_host_uuid::${bridge_host_uuid}::bridge_uuid::${bridge_uuid}::bridge_id" => $anvil->data->{bridges}{bridge_uuid}{$bridge_uuid}{bridge_id},
"bridges::bridge_host_uuid::${bridge_host_uuid}::bridge_uuid::${bridge_uuid}::bridge_mac_address" => $anvil->data->{bridges}{bridge_uuid}{$bridge_uuid}{bridge_mac_address},
"bridges::bridge_host_uuid::${bridge_host_uuid}::bridge_uuid::${bridge_uuid}::bridge_mtu" => $anvil->data->{bridges}{bridge_uuid}{$bridge_uuid}{bridge_mtu},
"bridges::bridge_host_uuid::${bridge_host_uuid}::bridge_uuid::${bridge_uuid}::bridge_stp_enabled" => $anvil->data->{bridges}{bridge_uuid}{$bridge_uuid}{bridge_stp_enabled},
"bridges::bridge_host_uuid::${bridge_host_uuid}::bridge_uuid::${bridge_uuid}::modified_date" => $anvil->data->{bridges}{bridge_uuid}{$bridge_uuid}{modified_date},
}});
# Make it easier to look up by bridge name.
$anvil->data->{bridges}{bridge_host_uuid}{$bridge_host_uuid}{bridge_name}{$bridge_name}{bridge_uuid} = $bridge_uuid;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"bridges::bridge_host_uuid::${bridge_host_uuid}::bridge_name::${bridge_name}::bridge_uuid" => $anvil->data->{bridges}{bridge_host_uuid}{$bridge_host_uuid}{bridge_name}{$bridge_name}{bridge_uuid},
}});
}
return(0);
}
=head2 get_fences =head2 get_fences
This loads the known fence devices into the C<< anvil::data >> hash at: This loads the known fence devices into the C<< anvil::data >> hash at:
@ -2070,7 +2180,7 @@ And, to allow for lookup by name;
* fences::fence_name::<fence_name>::fence_arguments * fences::fence_name::<fence_name>::fence_arguments
* fences::fence_name::<fence_name>::modified_date * fences::fence_name::<fence_name>::modified_date
If the hash was already populated, it is cleared before repopulating to ensure no stray data remains. If the hash was already populated, it is cleared before repopulating to ensure no stale data remains.
B<<Note>>: Deleted devices (ones where C<< fence_arguments >> is set to C<< DELETED >>) are ignored. See the C<< include_deleted >> parameter to include them. B<<Note>>: Deleted devices (ones where C<< fence_arguments >> is set to C<< DELETED >>) are ignored. See the C<< include_deleted >> parameter to include them.
@ -3224,7 +3334,7 @@ And, to allow for lookup by name;
* manifests::manifest_name::<manifest_name>::manifest_note * manifests::manifest_name::<manifest_name>::manifest_note
* manifests::manifest_name::<manifest_name>::modified_date * manifests::manifest_name::<manifest_name>::modified_date
If the hash was already populated, it is cleared before repopulating to ensure no stray data remains. If the hash was already populated, it is cleared before repopulating to ensure no stale data remains.
B<<Note>>: Deleted devices (ones where C<< manifest_note >> is set to C<< DELETED >>) are ignored. See the C<< include_deleted >> parameter to include them. B<<Note>>: Deleted devices (ones where C<< manifest_note >> is set to C<< DELETED >>) are ignored. See the C<< include_deleted >> parameter to include them.
@ -3906,7 +4016,7 @@ And, to allow for lookup by name;
B<< Note >>: The C<< modified_date >> is cast as a unix time stamp. B<< Note >>: The C<< modified_date >> is cast as a unix time stamp.
If the hash was already populated, it is cleared before repopulating to ensure no stray data remains. If the hash was already populated, it is cleared before repopulating to ensure no stale data remains.
This method takes no parameters. This method takes no parameters.
@ -4009,7 +4119,7 @@ And, to allow for lookup by name;
* upses::ups_name::<ups_name>::modified_date * upses::ups_name::<ups_name>::modified_date
* upses::ups_name::<ups_name>::power_uuid * upses::ups_name::<ups_name>::power_uuid
If the hash was already populated, it is cleared before repopulating to ensure no stray data remains. If the hash was already populated, it is cleared before repopulating to ensure no stale data remains.
B<<Note>>: Deleted devices (ones where C<< ups_ip_address >> is set to C<< DELETED >>) are ignored. See the C<< include_deleted >> parameter to include them. B<<Note>>: Deleted devices (ones where C<< ups_ip_address >> is set to C<< DELETED >>) are ignored. See the C<< include_deleted >> parameter to include them.

@ -386,8 +386,9 @@ sub send_alerts
} }
# Load the alerts # Load the alerts
$anvil->Database->get_alerts({debug => 2}); $anvil->Database->get_alerts({debug => $debug});
$anvil->Database->get_recipients({debug => 2}); $anvil->Database->get_recipients({debug => $debug});
my $host_uuid = $anvil->Get->host_uuid; my $host_uuid = $anvil->Get->host_uuid;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { host_uuid => $host_uuid }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { host_uuid => $host_uuid }});
foreach my $alert_uuid (keys %{$anvil->data->{alerts}{alert_uuid}}) foreach my $alert_uuid (keys %{$anvil->data->{alerts}{alert_uuid}})
@ -447,10 +448,14 @@ sub send_alerts
key_string => $alert_message, key_string => $alert_message,
}); });
# A lot of multi-line strings start with an opening new line. This removes that.
$message =~ s/^\n//;
$message =~ s/\n$//s;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { message => $message }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { message => $message }});
if ($alert_title) if ($alert_title)
{ {
my $title = $anvil->Words->parse_banged_string({ my $title = "[ ".$alert_set_by." ] ".$anvil->Words->parse_banged_string({
language => $recipient_language, language => $recipient_language,
key_string => $alert_title, key_string => $alert_title,
@ -528,7 +533,6 @@ sub send_alerts
}); });
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { footer => $footer }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { footer => $footer }});
### TODO: Determine if any of the sorts need to be reversed
# Build the message body now. # Build the message body now.
my $body = ""; my $body = "";
foreach my $alert_sort_position (sort {$a cmp $b} keys %{$anvil->data->{alerts}{queue}{$recipient_uuid}}) foreach my $alert_sort_position (sort {$a cmp $b} keys %{$anvil->data->{alerts}{queue}{$recipient_uuid}})

@ -18,6 +18,7 @@ my $THIS_FILE = "Get.pm";
### Methods; ### Methods;
# anvil_name_from_uuid # anvil_name_from_uuid
# anvil_version # anvil_version
# available_resources
# bridges # bridges
# cgi # cgi
# date_and_time # date_and_time
@ -311,6 +312,107 @@ fi;
return($version); return($version);
} }
=head2 available_resources
Parameters;
=head3 anvil_uuid (required)
This is the Anvil! UUID for which we're getting available resources from.
=cut
sub available_resources
{
my $self = shift;
my $parameter = shift;
my $anvil = $self->parent;
my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Get->available_resources()" }});
my $anvil_uuid = defined $parameter->{anvil_uuid} ? $parameter->{anvil_uuid} : "";
if (not $anvil_uuid)
{
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Get->available_resources()", parameter => "anvil_uuid" }});
return("!!error!!");
}
# Get the node UUIDs for this anvil.
my $query = "
SELECT
anvil_name,
anvil_node1_host_uuid,
anvil_node2_host_uuid,
anvil_dr1_host_uuid
FROM
anvils
WHERE
anvil_uuid = ".$anvil->Database->quote($anvil_uuid)."
;";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { query => $query }});
my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__});
my $count = @{$results};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
results => $results,
count => $count,
}});
if (not $count)
{
# Not found.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "error_0169", variables => { anvil_uuid => $anvil_uuid }});
return("!!error!!");
}
# Load data
$anvil->Database->get_hosts({debug => $debug});
$anvil->Database->get_bridges({debug => $debug});
# Get the details.
my $anvil_name = $results->[0]->[0];
my $node1_host_uuid = $results->[0]->[1];
my $node2_host_uuid = $results->[0]->[2];
my $dr1_host_uuid = $results->[0]->[3];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
anvil_name => $anvil_name,
node1_host_uuid => $node1_host_uuid,
node2_host_uuid => $node2_host_uuid,
dr1_host_uuid => $dr1_host_uuid,
}});
foreach my $host_uuid ($node1_host_uuid, $node2_host_uuid, $dr1_host_uuid)
{
# Start collecting data.
my $host_name = $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_name};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
host_uuid => $host_uuid,
host_name => $host_name,
}});
# Gather bridge data.
foreach my $bridge_name (sort {$a cmp $b} keys %{$anvil->data->{bridges}{bridge_host_uuid}{$host_uuid}{bridge_name}})
{
my $bridge_uuid = $anvil->data->{bridges}{bridge_host_uuid}{$host_uuid}{bridge_name}{$bridge_name}{bridge_uuid};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
bridge_uuid => $bridge_uuid,
bridge_name => $bridge_name,
}});
if (not exists $anvil->data->{anvil_resources}{$anvil_uuid}{bridges}{$bridge_name})
{
$anvil->data->{anvil_resources}{$anvil_uuid}{bridges}{$bridge_name}{on} = [];
}
push @{$anvil->data->{anvil_resources}{$anvil_uuid}{bridges}{$bridge_name}{on}}, $host_uuid;
}
# Get the CPU and RAM data
}
return(0);
}
=head2 bridges =head2 bridges
This finds a list of bridges on the host. Bridges that are found are stored is ' This finds a list of bridges on the host. Bridges that are found are stored is '

@ -117,14 +117,10 @@ sub agent_startup
tables => $tables, tables => $tables,
}}); }});
if (not $agent)
{
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "ScanCore->agent_startup()", parameter => "agent" }});
return("!!error!!");
}
if ((not $tables) or (ref($tables) ne "ARRAY")) if ((not $tables) or (ref($tables) ne "ARRAY"))
{ {
my $schema_file = $anvil->data->{path}{directories}{scan_agents}."/".$agent."/".$agent.".sql"; my $schema_file = $anvil->data->{path}{directories}{scan_agents}."/".$agent."/".$agent.".sql";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { schema_file => $schema_file }});
if (-e $schema_file) if (-e $schema_file)
{ {
$tables = $anvil->Database->get_tables_from_schema({debug => $debug, schema_file => $schema_file}); $tables = $anvil->Database->get_tables_from_schema({debug => $debug, schema_file => $schema_file});
@ -136,6 +132,7 @@ sub agent_startup
} }
else else
{ {
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { schema_file => $schema_file }});
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "ScanCore->agent_startup()", parameter => "tables" }}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "ScanCore->agent_startup()", parameter => "tables" }});
return("!!error!!"); return("!!error!!");
} }
@ -254,6 +251,7 @@ sub call_scan_agents
$timeout = $anvil->data->{scancore}{$agent_name}{timeout}; $timeout = $anvil->data->{scancore}{$agent_name}{timeout};
} }
my $shell_call = $agent_path; my $shell_call = $agent_path;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { 'sys::log::level' => $anvil->data->{sys}{'log'}{level} }});
if ($anvil->data->{sys}{'log'}{level}) if ($anvil->data->{sys}{'log'}{level})
{ {
$shell_call .= " ".$anvil->data->{sys}{'log'}{level}; $shell_call .= " ".$anvil->data->{sys}{'log'}{level};
@ -261,7 +259,7 @@ sub call_scan_agents
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { shell_call => $shell_call }});
# Tell the user this agent is about to run... # Tell the user this agent is about to run...
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => $debug, key => "log_0252", variables => { $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0252", variables => {
agent_name => $agent_name, agent_name => $agent_name,
timeout => $timeout, timeout => $timeout,
}}); }});
@ -271,7 +269,7 @@ sub call_scan_agents
{ {
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }});
} }
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => $debug, key => "log_0557", variables => { $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0557", variables => {
agent_name => $agent_name, agent_name => $agent_name,
runtime => (time - $start_time), runtime => (time - $start_time),
return_code => $return_code, return_code => $return_code,

@ -374,7 +374,7 @@ sub change_mode
my $password = defined $parameter->{password} ? $parameter->{password} : ""; my $password = defined $parameter->{password} ? $parameter->{password} : "";
my $remote_user = defined $parameter->{remote_user} ? $parameter->{remote_user} : "root"; my $remote_user = defined $parameter->{remote_user} ? $parameter->{remote_user} : "root";
my $target = defined $parameter->{target} ? $parameter->{target} : ""; my $target = defined $parameter->{target} ? $parameter->{target} : "";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
mode => $mode, mode => $mode,
path => $path, path => $path,
port => $port, port => $port,
@ -383,28 +383,25 @@ sub change_mode
remote_user => $remote_user, remote_user => $remote_user,
}}); }});
my $error = 0; # This is often called without a mode, just return if that's the case.
if (not $mode)
{
return(0);
}
if (not $path) if (not $path)
{ {
# No path... # No path...
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Storage->change_mode()", parameter => "path" }}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Storage->change_mode()", parameter => "path" }});
$error = 1; return('!!error!!');
}
if (not $mode)
{
# No mode...
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Storage->change_mode()", parameter => "mode" }});
$error = 1;
} }
elsif (($mode !~ /^\d\d\d$/) && ($mode !~ /^\d\d\d\d$/) && ($mode !~ /^\w\+\w$/) && ($mode !~ /^\w\-\w$/)) if (($mode !~ /^\d\d\d$/) && ($mode !~ /^\d\d\d\d$/) && ($mode !~ /^\w\+\w$/) && ($mode !~ /^\w\-\w$/))
{ {
# Invalid mode # Invalid mode
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "alert", key => "log_0038", variables => { mode => $mode }}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "alert", key => "log_0038", variables => { mode => $mode }});
$error = 1; return('!!error!!');
} }
if (not $error)
{
my $shell_call = $anvil->data->{path}{exe}{'chmod'}." $mode $path"; my $shell_call = $anvil->data->{path}{exe}{'chmod'}." $mode $path";
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0011", variables => { shell_call => $shell_call }}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0011", variables => { shell_call => $shell_call }});
if ($anvil->Network->is_local({host => $target})) if ($anvil->Network->is_local({host => $target}))
@ -433,7 +430,6 @@ sub change_mode
return_code => $return_code, return_code => $return_code,
}}); }});
} }
}
return(0); return(0);
} }
@ -596,17 +592,17 @@ sub check_md5sums
# Have we changed? # Have we changed?
$anvil->data->{md5sum}{$caller}{now} = $anvil->Get->md5sum({file => $0}); $anvil->data->{md5sum}{$caller}{now} = $anvil->Get->md5sum({file => $0});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"md5sum::${caller}::start_time" => $anvil->data->{md5sum}{$caller}{start_time}, "md5sum::${caller}::at_start" => $anvil->data->{md5sum}{$caller}{at_start},
"md5sum::${caller}::now" => $anvil->data->{md5sum}{$caller}{now}, "md5sum::${caller}::now" => $anvil->data->{md5sum}{$caller}{now},
}}); }});
if ($anvil->data->{md5sum}{$caller}{start_time} ne $anvil->data->{md5sum}{$caller}{now}) if ($anvil->data->{md5sum}{$caller}{at_start} ne $anvil->data->{md5sum}{$caller}{now})
{ {
# Exit. # Exit.
$exit = 1; $exit = 1;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "alert", key => "log_0250", variables => { $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "alert", key => "log_0250", variables => {
file => $0, file => $0,
old_sum => $anvil->data->{md5sum}{$caller}{start_time}, old_sum => $anvil->data->{md5sum}{$caller}{at_start},
new_sum => $anvil->data->{md5sum}{$caller}{now}, new_sum => $anvil->data->{md5sum}{$caller}{now},
}}); }});
} }
@ -624,26 +620,26 @@ sub check_md5sums
}}); }});
# Is this the first time I've seen this module? # Is this the first time I've seen this module?
if (not defined $anvil->data->{md5sum}{$module_file}{start_time}) if (not defined $anvil->data->{md5sum}{$module_file}{at_start})
{ {
# New one! # New one!
$anvil->data->{md5sum}{$module_file}{start_time} = $module_sum; $anvil->data->{md5sum}{$module_file}{at_start} = $module_sum;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"md5sum::${module_file}::start_time" => $anvil->data->{md5sum}{$module_file}{start_time}, "md5sum::${module_file}::at_start" => $anvil->data->{md5sum}{$module_file}{at_start},
}}); }});
} }
$anvil->data->{md5sum}{$module_file}{now} = $module_sum; $anvil->data->{md5sum}{$module_file}{now} = $module_sum;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"md5sum::${module_file}::start_time" => $anvil->data->{md5sum}{$module_file}{start_time}, "md5sum::${module_file}::at_start" => $anvil->data->{md5sum}{$module_file}{at_start},
"md5sum::${module_file}::now" => $anvil->data->{md5sum}{$module_file}{now}, "md5sum::${module_file}::now" => $anvil->data->{md5sum}{$module_file}{now},
}}); }});
if ($anvil->data->{md5sum}{$module_file}{start_time} ne $anvil->data->{md5sum}{$module_file}{now}) if ($anvil->data->{md5sum}{$module_file}{at_start} ne $anvil->data->{md5sum}{$module_file}{now})
{ {
# Changed. # Changed.
$exit = 1; $exit = 1;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "alert", key => "log_0250", variables => { $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "alert", key => "log_0250", variables => {
file => $module_file, file => $module_file,
old_sum => $anvil->data->{md5sum}{$module_file}{start_time}, old_sum => $anvil->data->{md5sum}{$module_file}{at_start},
new_sum => $anvil->data->{md5sum}{$module_file}{now}, new_sum => $anvil->data->{md5sum}{$module_file}{now},
}}); }});
} }
@ -660,15 +656,15 @@ sub check_md5sums
$anvil->data->{md5sum}{$file}{now} = $words_sum; $anvil->data->{md5sum}{$file}{now} = $words_sum;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"md5sum::${file}::start_time" => $anvil->data->{md5sum}{$file}{start_time}, "md5sum::${file}::at_start" => $anvil->data->{md5sum}{$file}{at_start},
"md5sum::${file}::now" => $anvil->data->{md5sum}{$file}{now}, "md5sum::${file}::now" => $anvil->data->{md5sum}{$file}{now},
}}); }});
if ($anvil->data->{md5sum}{$file}{start_time} ne $anvil->data->{md5sum}{$file}{now}) if ($anvil->data->{md5sum}{$file}{at_start} ne $anvil->data->{md5sum}{$file}{now})
{ {
$exit = 1; $exit = 1;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "alert", key => "log_0250", variables => { $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "alert", key => "log_0250", variables => {
file => $file, file => $file,
old_sum => $anvil->data->{md5sum}{$file}{start_time}, old_sum => $anvil->data->{md5sum}{$file}{at_start},
new_sum => $anvil->data->{md5sum}{$file}{now}, new_sum => $anvil->data->{md5sum}{$file}{now},
}}); }});
} }
@ -2411,8 +2407,8 @@ sub record_md5sums
# Record the caller's MD5 sum # Record the caller's MD5 sum
my $caller = $0; my $caller = $0;
$anvil->data->{md5sum}{$caller}{start_time} = $anvil->Get->md5sum({file => $0}); $anvil->data->{md5sum}{$caller}{at_start} = $anvil->Get->md5sum({file => $0});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "md5sum::${caller}::start_time" => $anvil->data->{md5sum}{$caller}{start_time} }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "md5sum::${caller}::at_start" => $anvil->data->{md5sum}{$caller}{at_start} }});
# Record the sums of our perl modules. # Record the sums of our perl modules.
foreach my $module (sort {$a cmp $b} keys %INC) foreach my $module (sort {$a cmp $b} keys %INC)
@ -2425,8 +2421,8 @@ sub record_md5sums
module_sum => $module_sum, module_sum => $module_sum,
}}); }});
$anvil->data->{md5sum}{$module_file}{start_time} = $module_sum; $anvil->data->{md5sum}{$module_file}{at_start} = $module_sum;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "md5sum::${module_file}::start_time" => $anvil->data->{md5sum}{$module_file}{start_time} }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "md5sum::${module_file}::at_start" => $anvil->data->{md5sum}{$module_file}{at_start} }});
} }
# Record sum(s) for the words file(s). # Record sum(s) for the words file(s).
@ -2438,8 +2434,8 @@ sub record_md5sums
words_sum => $words_sum, words_sum => $words_sum,
}}); }});
$anvil->data->{md5sum}{$file}{start_time} = $words_sum; $anvil->data->{md5sum}{$file}{at_start} = $words_sum;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "md5sum::${file}::start_time" => $anvil->data->{md5sum}{$file}{start_time} }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "md5sum::${file}::at_start" => $anvil->data->{md5sum}{$file}{at_start} }});
} }
return(0); return(0);
@ -2697,6 +2693,10 @@ If the file is an actual file, the following information is set;
scan::directories::<parent_directory>::mimetype = <mimetype, as returned by File::MimeInfo->mimetype> scan::directories::<parent_directory>::mimetype = <mimetype, as returned by File::MimeInfo->mimetype>
scan::directories::<parent_directory>::executable = '0' or '1' scan::directories::<parent_directory>::executable = '0' or '1'
B<< Note >>: If the directory being scanned in the scan agent directory, and the file is executable and starts with c<< scan- >>, the file will be treated as a scan agent and stored in the special hash:
* scancore::agent::<file> = <full_path>
Parameters; Parameters;
=head3 directory (required) =head3 directory (required)
@ -2739,6 +2739,9 @@ sub scan_directory
search_for => $search_for, search_for => $search_for,
}}); }});
# This is used for storing scan agents we've found, when appropriate.
my $scan_agent_directory = $anvil->data->{path}{directories}{scan_agents};
# Setup the search variable, if needed. # Setup the search variable, if needed.
$anvil->data->{scan}{searched} = "" if not exists $anvil->data->{scan}{searched}; $anvil->data->{scan}{searched} = "" if not exists $anvil->data->{scan}{searched};
@ -2838,6 +2841,16 @@ sub scan_directory
"scan::directories::${full_path}::mimetype" => $anvil->data->{scan}{directories}{$full_path}{mimetype}, "scan::directories::${full_path}::mimetype" => $anvil->data->{scan}{directories}{$full_path}{mimetype},
"scan::directories::${full_path}::executable" => $anvil->data->{scan}{directories}{$full_path}{executable}, "scan::directories::${full_path}::executable" => $anvil->data->{scan}{directories}{$full_path}{executable},
}}); }});
# If this is a scan agent, we'll store info about it in a special hash.
if ((-x $full_path) && ($file =~ /^scan-/) && ($full_path =~ /^$scan_agent_directory/))
{
# Found a scan agent.
$anvil->data->{scancore}{agent}{$file} = $full_path;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"scancore::agent::${file}" => $anvil->data->{scancore}{agent}{$file},
}});
}
} }
} }
closedir(DIRECTORY); closedir(DIRECTORY);

@ -231,6 +231,7 @@ sub generate_manifest
return($manifest_uuid, $anvil_name); return($manifest_uuid, $anvil_name);
} }
=head2 get_fence_data =head2 get_fence_data
This parses the unified metadata file from the avaialable fence_devices on this host. If the unified file (location stored in C<< path::data::fences_unified_metadata >>, default is C<< /tmp/fences_unified_metadata.xml >> is not found or fails to parse, C<< 1 >> is returned. If the file is successfully parsed. C<< 0 >> is returned. This parses the unified metadata file from the avaialable fence_devices on this host. If the unified file (location stored in C<< path::data::fences_unified_metadata >>, default is C<< /tmp/fences_unified_metadata.xml >> is not found or fails to parse, C<< 1 >> is returned. If the file is successfully parsed. C<< 0 >> is returned.

@ -1267,6 +1267,7 @@ sub collect_ipmi_data
# Write the password to a temp file. # Write the password to a temp file.
$temp_file = "/tmp/scancore.".$anvil->Get->uuid({short => 1}); $temp_file = "/tmp/scancore.".$anvil->Get->uuid({short => 1});
$anvil->Storage->write_file({ $anvil->Storage->write_file({
debug => 2,
body => $ipmi_password, body => $ipmi_password,
secure => 1, secure => 1,
file => $temp_file, file => $temp_file,

@ -22,6 +22,7 @@ my $THIS_FILE = "Words.pm";
# key # key
# language # language
# language_list # language_list
# load_agent_strings
# parse_banged_string # parse_banged_string
# read # read
# shorten_string # shorten_string
@ -315,6 +316,50 @@ sub language_list
return(9); return(9);
} }
=head2 load_agent_strings
This loads the strings from all the ScanCore scan agents on this system.
The method takes no parameters.
=cut
sub load_agent_strings
{
my $self = shift;
my $parameter = shift;
my $anvil = $self->parent;
my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
"path::directories::scan_agents" => $anvil->data->{path}{directories}{scan_agents},
}});
$anvil->Storage->scan_directory({
debug => $debug,
directory => $anvil->data->{path}{directories}{scan_agents},
recursive => 1,
});
# Now loop through the agents I found and call them.
foreach my $agent_name (sort {$a cmp $b} keys %{$anvil->data->{scancore}{agent}})
{
my $agent_words = $anvil->data->{scancore}{agent}{$agent_name}.".xml";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { agent_words => $agent_words }});
if ((-e $agent_words) && (-r $agent_words))
{
# Read the words file.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0251", variables => {
agent_name => $agent_name,
file => $agent_words,
}});
$anvil->Words->read({file => $agent_words});
}
}
return(0);
}
=head2 parse_banged_string =head2 parse_banged_string
This takes a string (usually from a DB record) in the format C<< <string_key>[,!!var1!value1!!,!!var2!value2!!,...,!!varN!valueN!! >> and converts it into an actual string. This takes a string (usually from a DB record) in the format C<< <string_key>[,!!var1!value1!!,!!var2!value2!!,...,!!varN!valueN!! >> and converts it into an actual string.
@ -459,6 +504,29 @@ sub parse_banged_string
} }
else else
{ {
### TODO: If we ever decide to support imperial measurements or other
### forms of hyrogliphics, here's where we'll do it.
# Some variables are encoded with 'value=X:units=Y'. In those cases,
if ($value =~ /value=(.*?):units=(.*)$/)
{
my $this_value = $1;
my $this_unit = $2;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
this_value => $this_value,
this_unit => $this_unit,
}});
if ((uc($this_unit) eq "V") or ($this_unit =~ /Volt/i)) { $value = $this_value." #!string!unit_0033!#"; }
elsif ((uc($this_unit) eq "W") or ($this_unit =~ /Watt/i)) { $value = $this_value." #!string!unit_0034!#"; }
elsif (uc($this_unit) eq "RPM") { $value = $this_value." #!string!unit_0035!#"; }
elsif (uc($this_unit) eq "C") { $value = $this_value." #!string!unit_0036!#"; }
elsif (uc($this_unit) eq "F") { $value = $this_value." #!string!unit_0037!#"; }
elsif ($this_unit eq "%") { $value = $this_value." #!string!unit_0038!#"; }
elsif ((uc($this_unit) eq "A") or ($this_unit =~ /Amp/i)) { $value = $this_value." #!string!unit_0039!#"; }
else { $value = $this_value." ".$this_unit; }
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { value => $value }});
}
# Record the variable/value pair # Record the variable/value pair
$variables->{$variable} = $value; $variables->{$variable} = $value;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "variables->$variable" => $variables->{$variable} }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "variables->$variable" => $variables->{$variable} }});

@ -1143,7 +1143,7 @@ pmsuspended - The domain has been suspended by guest power management, e.g. ente
{ {
# Wait. # Wait.
$loop = 1; $loop = 1;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0525"}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0525", variables => { server_name => $server }});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { loop => $loop }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { loop => $loop }});
sleep 2; sleep 2;
@ -1152,13 +1152,16 @@ pmsuspended - The domain has been suspended by guest power management, e.g. ente
elsif ($state eq "shut off") elsif ($state eq "shut off")
{ {
# Exit with OCF_NOT_RUNNING (rc: 7); # Exit with OCF_NOT_RUNNING (rc: 7);
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0526"}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0526", variables => { server_name => $server }});
$anvil->nice_exit({exit_code => 7}); $anvil->nice_exit({exit_code => 7});
} }
else else
{ {
# In some fashion or another, the server is running. Exit with OCF_SUCCESS (rc: 0) # In some fashion or another, the server is running. Exit with OCF_SUCCESS (rc: 0)
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, key => "log_0527", variables => { 'state' => $state }}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0527", variables => {
'state' => $state,
server_name => $server,
}});
$anvil->nice_exit({exit_code => 0}); $anvil->nice_exit({exit_code => 0});
} }
} }
@ -1168,7 +1171,7 @@ pmsuspended - The domain has been suspended by guest power management, e.g. ente
if (not $found) if (not $found)
{ {
# Exit with OCF_NOT_RUNNING (rc: 7); # Exit with OCF_NOT_RUNNING (rc: 7);
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0526"}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0526", variables => { server_name => $server }});
$anvil->nice_exit({exit_code => 7}); $anvil->nice_exit({exit_code => 7});
} }
} }

@ -933,10 +933,10 @@ WHERE
old_total_wattage_draw => $old_total_wattage_draw, old_total_wattage_draw => $old_total_wattage_draw,
new_total_wattage_draw => $new_total_wattage_draw, new_total_wattage_draw => $new_total_wattage_draw,
}; };
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_apc_pdu_message_0009", variables => $variables}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_apc_pdu_message_0020", variables => $variables});
$anvil->Alert->register({ $anvil->Alert->register({
alert_level => "warning", alert_level => "notice",
message => "scan_apc_pdu_message_0009", message => "scan_apc_pdu_message_0020",
variables => $variables, variables => $variables,
set_by => $THIS_FILE, set_by => $THIS_FILE,
sort_position => $anvil->data->{'scan-apc-pdu'}{alert_sort}++, sort_position => $anvil->data->{'scan-apc-pdu'}{alert_sort}++,

@ -73,7 +73,7 @@ if (($anvil->data->{scancore}{'scan-drbd'}{disable}) && (not $anvil->data->{swit
} }
# Handle start-up tasks # Handle start-up tasks
my $problem = $anvil->ScanCore->agent_startup({agent => $THIS_FILE}); my $problem = $anvil->ScanCore->agent_startup({debug => 2, agent => $THIS_FILE});
if ($problem) if ($problem)
{ {
$anvil->nice_exit({exit_code => 1}); $anvil->nice_exit({exit_code => 1});

@ -959,7 +959,7 @@ sub find_changes
say_swap => $say_swap, say_swap => $say_swap,
swap_percent => $new_swap_percent_used, swap_percent => $new_swap_percent_used,
}; };
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "scan_hardware_alert_0020", variables => $variables}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 0, level => 1, key => "scan_hardware_alert_0020", variables => $variables});
$anvil->Alert->register({alert_level => "warning", message => "scan_hardware_alert_0020", variables => $variables, set_by => $THIS_FILE }); $anvil->Alert->register({alert_level => "warning", message => "scan_hardware_alert_0020", variables => $variables, set_by => $THIS_FILE });
} }
} }
@ -983,7 +983,7 @@ sub find_changes
say_swap => $say_swap, say_swap => $say_swap,
swap_percent => $new_swap_percent_used, swap_percent => $new_swap_percent_used,
}; };
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "scan_hardware_alert_0021", variables => $variables}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 0, level => 1, key => "scan_hardware_alert_0021", variables => $variables});
$anvil->Alert->register({alert_level => "warning", message => "scan_hardware_alert_0021", variables => $variables, set_by => $THIS_FILE }); $anvil->Alert->register({alert_level => "warning", message => "scan_hardware_alert_0021", variables => $variables, set_by => $THIS_FILE });
} }
} }
@ -1142,7 +1142,7 @@ INSERT INTO
old_model => $old_scan_hardware_ram_module_model, old_model => $old_scan_hardware_ram_module_model,
old_serial_number => $old_scan_hardware_ram_module_serial_number, old_serial_number => $old_scan_hardware_ram_module_serial_number,
}; };
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "scan_hardware_alert_0023", variables => $variables}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 0, level => 1, key => "scan_hardware_alert_0023", variables => $variables});
$anvil->Alert->register({alert_level => "warning", message => "scan_hardware_alert_0023", variables => $variables, set_by => $THIS_FILE}); $anvil->Alert->register({alert_level => "warning", message => "scan_hardware_alert_0023", variables => $variables, set_by => $THIS_FILE});
} }
else else
@ -1159,7 +1159,7 @@ INSERT INTO
old_model => $old_scan_hardware_ram_module_model, old_model => $old_scan_hardware_ram_module_model,
old_serial_number => $old_scan_hardware_ram_module_serial_number, old_serial_number => $old_scan_hardware_ram_module_serial_number,
}; };
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "scan_hardware_alert_0024", variables => $variables}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 0, level => 1, key => "scan_hardware_alert_0024", variables => $variables});
$anvil->Alert->register({alert_level => "warning", message => "scan_hardware_alert_0024", variables => $variables, set_by => $THIS_FILE}); $anvil->Alert->register({alert_level => "warning", message => "scan_hardware_alert_0024", variables => $variables, set_by => $THIS_FILE});
} }
@ -1191,7 +1191,7 @@ WHERE
model => $new_scan_hardware_ram_module_model, model => $new_scan_hardware_ram_module_model,
serial_number => $new_scan_hardware_ram_module_serial_number, serial_number => $new_scan_hardware_ram_module_serial_number,
}; };
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "scan_hardware_alert_0025", variables => $variables}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 0, level => 2, key => "scan_hardware_alert_0025", variables => $variables});
$anvil->Alert->register({alert_level => "notice", message => "scan_hardware_alert_0025", variables => $variables, set_by => $THIS_FILE}); $anvil->Alert->register({alert_level => "notice", message => "scan_hardware_alert_0025", variables => $variables, set_by => $THIS_FILE});
# INSERT # INSERT
@ -1256,7 +1256,7 @@ INSERT INTO
old_model => $old_scan_hardware_ram_module_model, old_model => $old_scan_hardware_ram_module_model,
old_serial_number => $old_scan_hardware_ram_module_serial_number, old_serial_number => $old_scan_hardware_ram_module_serial_number,
}; };
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "scan_hardware_alert_0026", variables => $variables}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 0, level => 2, key => "scan_hardware_alert_0026", variables => $variables});
$anvil->Alert->register({alert_level => "warning", message => "scan_hardware_alert_0026", variables => $variables, set_by => $THIS_FILE}); $anvil->Alert->register({alert_level => "warning", message => "scan_hardware_alert_0026", variables => $variables, set_by => $THIS_FILE});
my $query = " my $query = "

@ -733,10 +733,8 @@ INSERT INTO
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }});
push @{$anvil->data->{'scan-ipmitool'}{queries}}, $query; push @{$anvil->data->{'scan-ipmitool'}{queries}}, $query;
# TODO: Use the following variable/value pairs to have the sensor my $sensor_name = "name=".$scan_ipmitool_sensor_name.":units=".$new_scan_ipmitool_sensor_units;
# name and value translated/converted to imperial as needed. my $sensor_value = "value=".$new_scan_ipmitool_value_sensor_value.":units=".$new_scan_ipmitool_sensor_units;
my $sensor_name = "name=$scan_ipmitool_sensor_name:units=$new_scan_ipmitool_sensor_units";
my $sensor_value = "value=$new_scan_ipmitool_value_sensor_value:units=$new_scan_ipmitool_sensor_units";
my $message_key = "scan_ipmitool_message_0019"; my $message_key = "scan_ipmitool_message_0019";
my $level = "notice"; my $level = "notice";
my $variables = { my $variables = {
@ -763,8 +761,8 @@ INSERT INTO
my $sort_position = $level eq "warning" ? 1 : $anvil->data->{'scan-ipmitool'}{alert_sort}++; my $sort_position = $level eq "warning" ? 1 : $anvil->data->{'scan-ipmitool'}{alert_sort}++;
my $log_level = $level eq "warning" ? 1 : 2; my $log_level = $level eq "warning" ? 1 : 2;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $log_level, key => "scan_ipmitool_message_0019", variables => $variables}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $log_level, key => $message_key, variables => $variables});
$anvil->Alert->register({alert_level => $level, message => "scan_ipmitool_message_0019", variables => $variables, set_by => $THIS_FILE, sort_position => $sort_position}); $anvil->Alert->register({alert_level => $level, message => $message_key, variables => $variables, set_by => $THIS_FILE, sort_position => $sort_position});
} }
} }
} }
@ -881,8 +879,8 @@ INSERT INTO
# We need to let ScanCore translate the sensor data into a string for each # We need to let ScanCore translate the sensor data into a string for each
# user and their prefered language and units. As such, we're going to use a # user and their prefered language and units. As such, we're going to use a
# couple special variable strings for this. # couple special variable strings for this.
my $sensor_name = "name=$scan_ipmitool_sensor_name:units=$new_scan_ipmitool_sensor_units"; my $sensor_name = "name=".$scan_ipmitool_sensor_name.":units=".$new_scan_ipmitool_sensor_units;
my $sensor_value = "value=$new_scan_ipmitool_value_sensor_value:units=$new_scan_ipmitool_sensor_units"; my $sensor_value = "value=".$new_scan_ipmitool_value_sensor_value.":units=".$new_scan_ipmitool_sensor_units;
# If the sensor is not 'ok', set a warning level alert. # If the sensor is not 'ok', set a warning level alert.
my $level = "notice"; my $level = "notice";
@ -895,7 +893,7 @@ INSERT INTO
my $variables = { my $variables = {
host_name => $host_name, host_name => $host_name,
sensor => $scan_ipmitool_sensor_name, sensor_name => $scan_ipmitool_sensor_name,
sensor_value => $sensor_value, sensor_value => $sensor_value,
sensor_status => $new_scan_ipmitool_sensor_status, sensor_status => $new_scan_ipmitool_sensor_status,
high_critical => $new_scan_ipmitool_sensor_high_critical eq "" ? "--" : $new_scan_ipmitool_sensor_high_critical." ".$new_scan_ipmitool_sensor_units, high_critical => $new_scan_ipmitool_sensor_high_critical eq "" ? "--" : $new_scan_ipmitool_sensor_high_critical." ".$new_scan_ipmitool_sensor_units,
@ -905,8 +903,8 @@ INSERT INTO
}; };
my $sort_position = $level eq "warning" ? 1 : $anvil->data->{'scan-ipmitool'}{alert_sort}++; my $sort_position = $level eq "warning" ? 1 : $anvil->data->{'scan-ipmitool'}{alert_sort}++;
my $log_level = $level eq "warning" ? 1 : 2; my $log_level = $level eq "warning" ? 1 : 2;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $log_level, key => "scan_ipmitool_message_0019", variables => $variables}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $log_level, key => $message_key, variables => $variables});
$anvil->Alert->register({alert_level => $level, message => "scan_ipmitool_message_0019", variables => $variables, set_by => $THIS_FILE, sort_position => $sort_position}); $anvil->Alert->register({alert_level => $level, message => $message_key, variables => $variables, set_by => $THIS_FILE, sort_position => $sort_position});
} }
} }
@ -921,6 +919,10 @@ INSERT INTO
} }
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { "sys::scanning_myself" => $anvil->data->{sys}{scanning_myself} }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { "sys::scanning_myself" => $anvil->data->{sys}{scanning_myself} }});
# Now commit the changes.
$anvil->Database->write({debug => 2, query => $anvil->data->{'scan-ipmitool'}{queries}, source => $THIS_FILE, line => __LINE__});
$anvil->data->{'scan-ipmitool'}{queries} = [];
### Now add, update and delete 'temperature' entries. ### Now add, update and delete 'temperature' entries.
process_temperature($anvil, $host_name); process_temperature($anvil, $host_name);
@ -928,9 +930,6 @@ INSERT INTO
check_sensor_health($anvil, $host_name); check_sensor_health($anvil, $host_name);
} }
# Now commit the changes.
$anvil->Database->write({query => $anvil->data->{'scan-ipmitool'}{queries}, source => $THIS_FILE, line => __LINE__});
return(0); return(0);
} }

@ -661,6 +661,8 @@ AND
} }
# Now, if any undeleted old entries remain, delete them from the database. # Now, if any undeleted old entries remain, delete them from the database.
foreach my $variable (sort {$a cmp $b} keys %{$anvil->data->{old}{temperature}})
{
foreach my $serial_number (sort {$a cmp $b} keys %{$anvil->data->{old}{temperature}{$variable}}) foreach my $serial_number (sort {$a cmp $b} keys %{$anvil->data->{old}{temperature}{$variable}})
{ {
my $temperature_uuid = $anvil->data->{old}{temperature}{$variable}{$serial_number}{temperature_uuid}; my $temperature_uuid = $anvil->data->{old}{temperature}{$variable}{$serial_number}{temperature_uuid};
@ -674,6 +676,7 @@ AND
}); });
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { temperature_uuid => $temperature_uuid }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { temperature_uuid => $temperature_uuid }});
} }
}
# Commit the queries. # Commit the queries.
$anvil->Database->write({query => $anvil->data->{'scan-storcli'}{queries}, source => $THIS_FILE, line => __LINE__}); $anvil->Database->write({query => $anvil->data->{'scan-storcli'}{queries}, source => $THIS_FILE, line => __LINE__});

@ -241,6 +241,7 @@ The error was:
<key name="error_0166">The resource: [#!variable!resource!#] in the config file: [#!variable!file!#] was found, but does not appear to be a valid UUID: [#!variable!uuid!#].</key> <key name="error_0166">The resource: [#!variable!resource!#] in the config file: [#!variable!file!#] was found, but does not appear to be a valid UUID: [#!variable!uuid!#].</key>
<key name="error_0167">The resource: [#!variable!resource!#] in the config file: [#!variable!file!#] was found, and we were asked to replace the 'scan_drbd_resource_uuid' but the new UUID: [#!variable!uuid!#] is not a valud UUID.</key> <key name="error_0167">The resource: [#!variable!resource!#] in the config file: [#!variable!file!#] was found, and we were asked to replace the 'scan_drbd_resource_uuid' but the new UUID: [#!variable!uuid!#] is not a valud UUID.</key>
<key name="error_0168">The 'fence_ipmilan' command: [#!variable!command!#] does not appear to be valid.</key> <key name="error_0168">The 'fence_ipmilan' command: [#!variable!command!#] does not appear to be valid.</key>
<key name="error_0169">The Anvil! UUID: [#!variable!anvil_uuid!#] doesn't appear to exist in the database.</key>
<!-- Table headers --> <!-- Table headers -->
<key name="header_0001">Current Network Interfaces and States</key> <key name="header_0001">Current Network Interfaces and States</key>
@ -1035,9 +1036,9 @@ The file: [#!variable!file!#] needs to be updated. The difference is:
<key name="log_0522">The 'libvirtd' daemon is not running. It may be starting up, will wait: [#!variable!wait_time!#] seconds...</key> <key name="log_0522">The 'libvirtd' daemon is not running. It may be starting up, will wait: [#!variable!wait_time!#] seconds...</key>
<key name="log_0523">Found the server to be running using it's PID. The state of the server can't be determined, however. Please start the 'libvirtd' daemon!</key> <key name="log_0523">Found the server to be running using it's PID. The state of the server can't be determined, however. Please start the 'libvirtd' daemon!</key>
<key name="log_0524">No PID for the server was found. It is not running on this host.</key> <key name="log_0524">No PID for the server was found. It is not running on this host.</key>
<key name="log_0525">The server is shutting down. Will wait for it to finish...</key> <key name="log_0525">The server: [#!variable!server_name!#] is shutting down. Will wait for it to finish...</key>
<key name="log_0526">The server is off.</key> <key name="log_0526">The server: [#!variable!server_name!#] is off.</key>
<key name="log_0527">The server is running (state is: [#!variable!state!#]).</key> <key name="log_0527">The server: [#!variable!server_name!#] is running (state is: [#!variable!state!#]).</key>
<key name="log_0528">We've been asked to migrating the server: [#!variable!server!#] to: [#!variable!target_host!#].</key> <key name="log_0528">We've been asked to migrating the server: [#!variable!server!#] to: [#!variable!target_host!#].</key>
<key name="log_0529">Checking server state after: [#!variable!server!#] was migrated to this host.</key> <key name="log_0529">Checking server state after: [#!variable!server!#] was migrated to this host.</key>
<key name="log_0530">Updating the postfix relay password file: [#!data!path::configs::postfix_relay_password!#].</key> <key name="log_0530">Updating the postfix relay password file: [#!data!path::configs::postfix_relay_password!#].</key>
@ -1071,7 +1072,7 @@ The file: [#!variable!file!#] needs to be updated. The difference is:
#!variable!difference!# #!variable!difference!#
==== ====
</key> </key>
<key name="log_0557">Scan agent: [#!variable!agent_name!#] exited after: [#!variable!runtime!#] seconds with the return code: [#!variable!return_code!#].</key> <key name="log_0557">- Scan agent: [#!variable!agent_name!#] exited after: [#!variable!runtime!#] seconds with the return code: [#!variable!return_code!#].</key>
<key name="log_0558">I'm not on the same network as: [#!variable!host_name!#]. Unable to check the power state.</key> <key name="log_0558">I'm not on the same network as: [#!variable!host_name!#]. Unable to check the power state.</key>
<key name="log_0559">The host: [#!variable!host_name!#] appears to be off, but there's no IPMI information, so unable to check the power state or power on the machine.</key> <key name="log_0559">The host: [#!variable!host_name!#] appears to be off, but there's no IPMI information, so unable to check the power state or power on the machine.</key>
<key name="log_0560">The host: [#!variable!host_name!#] has no IPMI information. Wouldn't be able to boot it, even if it's off, so skipping it.</key> <key name="log_0560">The host: [#!variable!host_name!#] has no IPMI information. Wouldn't be able to boot it, even if it's off, so skipping it.</key>
@ -1802,6 +1803,13 @@ Here we will inject 't_0006', which injects 't_0001' which has a variable: [#!st
<key name="unit_0030">Down</key> <key name="unit_0030">Down</key>
<key name="unit_0031">Mbps</key> <key name="unit_0031">Mbps</key>
<key name="unit_0032">waiting for job output...</key> <key name="unit_0032">waiting for job output...</key>
<key name="unit_0033">Volts</key>
<key name="unit_0034">Watts</key>
<key name="unit_0035">RPM</key>
<key name="unit_0036">Celsius</key>
<key name="unit_0037">Fahrenheit</key>
<key name="unit_0038">%</key>
<key name="unit_0039">Amps</key>
<!-- These are special. These are used to describe the UPSes that ScanCore supports. These <!-- These are special. These are used to describe the UPSes that ScanCore supports. These
are used when adding UPSes to the system for use in Install Manifests. are used when adding UPSes to the system for use in Install Manifests.

@ -499,18 +499,20 @@ sub configure_pacemaker
# If we're here, break up the command and turn it into the pcs call. # If we're here, break up the command and turn it into the pcs call.
my $old_switches = {}; my $old_switches = {};
my ($fence_agent, $arguments) = ($host_ipmi =~ /^\/.*\/(.*?)\s+(.*)$/); my ($fence_agent, $arguments) = ($host_ipmi =~ /^\/.*\/(.*?)\s+(.*)$/);
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
fence_agent => $fence_agent, fence_agent => $fence_agent,
arguments => $arguments =~ /passw/ ? $anvil->Log->is_secure($arguments) : $arguments, arguments => $arguments =~ /passw/ ? $anvil->Log->is_secure($arguments) : $arguments,
}}); }});
$pcs_add_command = $anvil->data->{path}{exe}{pcs}." stonith create ".$ipmi_stonith_name." ".$fence_agent." pcmk_host_list=\"".$node_name."\" "; $pcs_add_command = $anvil->data->{path}{exe}{pcs}." stonith create ".$ipmi_stonith_name." ".$fence_agent." pcmk_host_list=\"".$node_name."\" ";
my $switches = $anvil->System->parse_arguments({debug => 3, arguments => $arguments}); my $switches = $anvil->System->parse_arguments({arguments => $arguments});
foreach my $switch (sort {$a cmp $b} keys %{$switches}) foreach my $switch (sort {$a cmp $b} keys %{$switches})
{ {
# Ignore 'delay', we handle that in Cluster->set_delay(); # Ignore 'delay', we handle that in Cluster->set_delay(); Also,
# convert '#!SET!#' to 'true'.
my $value = $switches->{$switch}; my $value = $switches->{$switch};
$value =~ s/"/\\"/g; $value =~ s/"/\\"/g;
$value =~ s/#!SET!#/true/g;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
switch => $switch, switch => $switch,
value => $value, value => $value,

@ -66,7 +66,7 @@ $anvil->data->{switches}{'run-once'} = "";
$anvil->Get->switches; $anvil->Get->switches;
# Calculate my sum so that we can exit if it changes later. # Calculate my sum so that we can exit if it changes later.
$anvil->Storage->record_md5sums; $anvil->Storage->record_md5sums();
# Connect to DBs. # Connect to DBs.
wait_for_database($anvil); wait_for_database($anvil);
@ -79,11 +79,11 @@ startup_tasks($anvil);
# Load the strings from all the agents we know about before we process alerts so that we can include their # Load the strings from all the agents we know about before we process alerts so that we can include their
# messages in any emails we're going to send. # messages in any emails we're going to send.
load_agent_strings($anvil); $anvil->Words->load_agent_strings();
# Send a startup message immediately # Send a startup message immediately
$anvil->Email->check_config(); $anvil->Email->check_config();
$anvil->Alert->register({alert_level => "notice", message => "message_0179", set_by => $THIS_FILE}); $anvil->Alert->register({alert_level => "notice", message => "message_0179", set_by => $THIS_FILE, sort_position => 0});
$anvil->Email->send_alerts(); $anvil->Email->send_alerts();
# Disconnect. We'll reconnect inside the loop # Disconnect. We'll reconnect inside the loop
@ -95,13 +95,14 @@ $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level
while(1) while(1)
{ {
# Do the various pre-run tasks. # Do the various pre-run tasks.
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { 'sys::log::level' => $anvil->data->{sys}{'log'}{level} }});
prepare_for_run($anvil); prepare_for_run($anvil);
# Do we have at least one database? # Do we have at least one database?
if ($anvil->data->{sys}{database}{connections}) if ($anvil->data->{sys}{database}{connections})
{ {
# Run the normal tasks # Run the normal tasks
$anvil->ScanCore->call_agents({debug => 2}); $anvil->ScanCore->call_scan_agents({debug => 2});
# Do post-scan analysis. # Do post-scan analysis.
$anvil->ScanCore->post_scan_analysis({debug => 2}); $anvil->ScanCore->post_scan_analysis({debug => 2});
@ -121,6 +122,11 @@ while(1)
next; next;
} }
### TODO: Left off here
### - Agents are timing out?
# Send alerts.
$anvil->Email->send_alerts({debug => 2});
# Exit if 'run-once' selected. # Exit if 'run-once' selected.
if ($anvil->data->{switches}{'run-once'}) if ($anvil->data->{switches}{'run-once'})
{ {
@ -139,7 +145,7 @@ while(1)
{ {
$run_interval = $anvil->data->{scancore}{timing}{run_interval}; $run_interval = $anvil->data->{scancore}{timing}{run_interval};
} }
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "log_0249", variables => { run_interval => $run_interval }}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0249", variables => { run_interval => $run_interval }});
sleep($run_interval); sleep($run_interval);
# In case something has changed, exit. # In case something has changed, exit.
@ -169,6 +175,8 @@ sub cleanup_after_run
# Disconnect from the database(s) and sleep now. # Disconnect from the database(s) and sleep now.
$anvil->Database->disconnect(); $anvil->Database->disconnect();
exit_if_sums_changed($anvil);
# Now delete all the remaining agent data. # Now delete all the remaining agent data.
delete $anvil->data->{scancore}{agent}; delete $anvil->data->{scancore}{agent};
} }
@ -178,7 +186,9 @@ sub exit_if_sums_changed
{ {
my ($anvil) = @_; my ($anvil) = @_;
if ($anvil->Storage->check_md5sums) my $changed = $anvil->Storage->check_md5sums();
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { changed => $changed }});
if ($changed)
{ {
# NOTE: We exit with '0' to prevent systemctl from showing a scary red message. # NOTE: We exit with '0' to prevent systemctl from showing a scary red message.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "alert", key => "message_0014"}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "alert", key => "message_0014"});
@ -188,36 +198,6 @@ sub exit_if_sums_changed
return(0); return(0);
} }
# This loads the strings from all the agents we know about.
sub load_agent_strings
{
my ($anvil) = @_;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
"path::directories::scan_agents" => $anvil->data->{path}{directories}{scan_agents},
}});
scan_directory($anvil, $anvil->data->{path}{directories}{scan_agents});
# Now loop through the agents I found and call them.
foreach my $agent_name (sort {$a cmp $b} keys %{$anvil->data->{scancore}{agent}})
{
my $agent_words = $anvil->data->{scancore}{agent}{$agent_name}.".xml";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { agent_words => $agent_words }});
if ((-e $agent_words) && (-r $agent_words))
{
# Read the words file.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0251", variables => {
agent_name => $agent_name,
file => $agent_words,
}});
$anvil->Words->read({file => $agent_words});
}
}
return(0);
}
# Handle pre-run tasks. # Handle pre-run tasks.
sub prepare_for_run sub prepare_for_run
{ {
@ -226,6 +206,7 @@ sub prepare_for_run
$anvil->_set_paths(); $anvil->_set_paths();
$anvil->_set_defaults(); $anvil->_set_defaults();
$anvil->Storage->read_config(); $anvil->Storage->read_config();
$anvil->Get->switches();
$anvil->Words->read(); $anvil->Words->read();
$anvil->Database->connect(); $anvil->Database->connect();
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0132"}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0132"});

@ -26,36 +26,7 @@ $anvil->Get->switches;
$anvil->Database->connect; $anvil->Database->connect;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0132"}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0132"});
if (0) $anvil->Words->load_agent_strings({debug => 2});
{
foreach my $uuid ("4c4c4544-0043-4210-8043-c3c04f523533", "4c4c4544-0043-4210-8042-c3c04f523533", "30343536-3138-5355-4534-3238324b4842", "b4e46faf-0ebe-e211-a0d6-00262d0ca874", "4ba42b4e-9bf7-e311-a889-899427029de4")
{
my $variable_uuid = $anvil->Database->insert_or_update_variables({
debug => 2,
variable_name => 'system::stop_reason',
variable_value => 'thermal',
variable_default => '',
variable_description => 'striker_0279',
variable_section => 'system',
variable_source_uuid => $uuid,
variable_source_table => 'hosts',
});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { variable_uuid => $variable_uuid }});
}
}
if (1)
{
$anvil->ScanCore->post_scan_analysis({debug => 3});
}
if (0)
{
my $problem = $anvil->Striker->load_manifest({
debug => 2,
manifest_uuid => "006ee2cb-1fbd-4ea6-89d6-96cf3bc94940",
});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { problem => $problem }});
}
$anvil->nice_exit({exit_code => 0}); $anvil->nice_exit({exit_code => 0});

Loading…
Cancel
Save