* Updated System->call() to take the 'timeout' parameter which, when set, prepends the call with 'timeout X <shell_call>' to make it easier to deal with calls that could potentially hang.

* Renamed scan-storage to scan-lvm as we only really care about LVM data in this agent. A dedicated scan-drbd will be created later. Got the agent to parse the pvs/vgs/lvs data.

Signed-off-by: Digimer <digimer@alteeve.ca>
main
Digimer 4 years ago
parent 33101f969a
commit 2f4a06f2e0
  1. 1
      Anvil/Tools.pm
  2. 6
      Anvil/Tools/Database.pm
  3. 13
      Anvil/Tools/System.pm
  4. 2
      rpm/SPECS/anvil.spec
  5. 213
      scancore-agents/scan-lvm/scan-lvm
  6. 200
      scancore-agents/scan-lvm/scan-lvm.sql
  7. 29
      scancore-agents/scan-lvm/scan-lvm.xml
  8. 100
      scancore-agents/scan-storage/scan-storage
  9. 42
      scancore-agents/scan-storage/scan-storage.sql
  10. 0
      scancore-agents/scan-storage/scan-storage.xml
  11. 12
      tools/scancore
  12. 2
      tools/striker-manage-install-target

@ -1166,6 +1166,7 @@ sub _set_paths
journalctl => "/usr/bin/journalctl", journalctl => "/usr/bin/journalctl",
logger => "/usr/bin/logger", logger => "/usr/bin/logger",
ls => "/usr/bin/ls", ls => "/usr/bin/ls",
lsblk => "/usr/bin/lsblk",
lvchange => "/usr/sbin/lvchange", lvchange => "/usr/sbin/lvchange",
lvs => "/usr/sbin/lvs", lvs => "/usr/sbin/lvs",
lvscan => "/usr/sbin/lvscan", lvscan => "/usr/sbin/lvscan",

@ -527,7 +527,7 @@ sub check_agent_data
{ {
# Something went wrong. # Something went wrong.
my $changed = $anvil->Alert->check_alert_sent({ my $changed = $anvil->Alert->check_alert_sent({
debug => 2, debug => $debug,
record_locator => "schema_load_failure", record_locator => "schema_load_failure",
set_by => $agent, set_by => $agent,
}); });
@ -552,7 +552,7 @@ sub check_agent_data
{ {
# If there was an alert, clear it. # If there was an alert, clear it.
my $changed = $anvil->Alert->check_alert_sent({ my $changed = $anvil->Alert->check_alert_sent({
debug => 2, debug => $debug,
record_locator => "schema_load_failure", record_locator => "schema_load_failure",
set_by => $agent, set_by => $agent,
clear => 1, clear => 1,
@ -567,7 +567,7 @@ sub check_agent_data
}}); }});
$anvil->Alert->register({ $anvil->Alert->register({
debug => 2, debug => $debug,
alert_level => "warning", alert_level => "warning",
clear_alert => 1, clear_alert => 1,
message => "message_0182,!!agent_name!".$agent."!!,!!file!".$schema_file."!!", message => "message_0182,!!agent_name!".$agent."!!,!!file!".$schema_file."!!",

@ -207,6 +207,10 @@ B<NOTE>: This is only used when C<< background >> is set to C<< 1 >>.
If set, the C<< STDOUT >> output will be sent to the corresponding file. If this isn't a full path, the file will be placed under C<< /tmp/ >>. If set, the C<< STDOUT >> output will be sent to the corresponding file. If this isn't a full path, the file will be placed under C<< /tmp/ >>.
=head3 timeout (optional, default '0')
If set, a timeout will be placed on the call. If the call takes more than C<< timeout >> seconds, the call will fail and the C<< return_code >> will be C<< 124 >>.
=cut =cut
sub call sub call
{ {
@ -224,6 +228,7 @@ sub call
my $source = defined $parameter->{source} ? $parameter->{source} : $THIS_FILE; my $source = defined $parameter->{source} ? $parameter->{source} : $THIS_FILE;
my $stderr_file = defined $parameter->{stderr_file} ? $parameter->{stderr_file} : ""; my $stderr_file = defined $parameter->{stderr_file} ? $parameter->{stderr_file} : "";
my $stdout_file = defined $parameter->{stdout_file} ? $parameter->{stdout_file} : ""; my $stdout_file = defined $parameter->{stdout_file} ? $parameter->{stdout_file} : "";
my $timeout = defined $parameter->{timeout} ? $parameter->{timeout} : 0;
my $redirect = $redirect_stderr ? " 2>&1" : ""; my $redirect = $redirect_stderr ? " 2>&1" : "";
$anvil->Log->variables({source => $source, line => $line, level => $debug, secure => $secure, list => { $anvil->Log->variables({source => $source, line => $line, level => $debug, secure => $secure, list => {
background => $background, background => $background,
@ -284,6 +289,14 @@ sub call
"s2:arguments" => $arguments, "s2:arguments" => $arguments,
}}); }});
} }
if ($timeout)
{
# Prepend a timeout.
$shell_call = $anvil->data->{path}{exe}{timeout}." ".$timeout." ".$shell_call;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => $secure, list => { shell_call => $shell_call }});
}
if ($background) if ($background)
{ {
# Prepend '/tmp/' to STDOUT and/or STDERR output files, if needed. # Prepend '/tmp/' to STDOUT and/or STDERR output files, if needed.

@ -46,6 +46,7 @@ Requires: iproute
Requires: lsscsi Requires: lsscsi
Requires: mailx Requires: mailx
Requires: mlocate Requires: mlocate
Requires: nvme-cli
Requires: perl-Capture-Tiny Requires: perl-Capture-Tiny
Requires: perl-Data-Dumper Requires: perl-Data-Dumper
Requires: perl-Data-Validate-Domain Requires: perl-Data-Validate-Domain
@ -78,6 +79,7 @@ Requires: postgresql-contrib
Requires: postgresql-plperl Requires: postgresql-plperl
Requires: rsync Requires: rsync
Requires: screen Requires: screen
Requires: smartmontools
Requires: syslinux Requires: syslinux
Requires: tmux Requires: tmux
Requires: usbutils Requires: usbutils

@ -0,0 +1,213 @@
#!/usr/bin/perl
#
# This scans the LVM (logical volume management) components (PV, VG and LV).
#
# Examples;
#
# Exit codes;
# 0 = Normal exit.
# 1 = Startup failure (not running as root, no DB, bad file read, etc)
#
# TODO:
# -
#
use strict;
use warnings;
use Anvil::Tools;
use Data::Dumper;
use JSON;
# Disable buffering
$| = 1;
# Prevent a discrepency between UID/GID and EUID/EGID from throwing an error.
$< = $>;
$( = $);
my $THIS_FILE = ($0 =~ /^.*\/(.*)$/)[0];
my $running_directory = ($0 =~ /^(.*?)\/$THIS_FILE$/)[0];
if (($running_directory =~ /^\./) && ($ENV{PWD}))
{
$running_directory =~ s/^\./$ENV{PWD}/;
}
my $anvil = Anvil::Tools->new({log_level => 2, log_secure => 1});
$anvil->Log->level({set => 2});
$anvil->Log->secure({set => 1});
# Make sure we're running as 'root'
# $< == real UID, $> == effective UID
if (($< != 0) && ($> != 0))
{
# Not root
print $anvil->Words->string({key => "error_0005"})."\n";
$anvil->nice_exit({exit_code => 1});
}
# These are the threasholds for when to alert when swap is running out.
$anvil->data->{scancore}{'scan-lvm'}{disable} = 0;
$anvil->data->{switches}{force} = 0;
$anvil->Storage->read_config();
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0115", variables => { program => $THIS_FILE }});
# Read switches
$anvil->Get->switches;
# If we're disabled and '--force' wasn't used, exit.
if (($anvil->data->{scancore}{'scan-lvm'}{disable}) && (not $anvil->data->{switches}{force}))
{
# Exit.
$anvil->nice_exit({exit_code => 0});
}
# These are the tables used by this agent. The order matters as it controls to order the tables are created
# and sync'ed. For purges, this array is walked backwards.
$anvil->data->{scancore}{'scan-lvm'}{tables} = ["scan_lvm_pvs", "scan_lvm_vgs", "scan_lvm_lvs"];
# Handle start-up tasks
my $problem = $anvil->ScanCore->agent_startup({
debug => 3,
agent => $THIS_FILE,
tables => $anvil->data->{scancore}{'scan-lvm'}{tables},
});
if ($problem)
{
$anvil->nice_exit({exit_code => 1});
}
if ($anvil->data->{switches}{purge})
{
# This can be called when doing bulk-database purges.
$anvil->Database->purge_data({
debug => 2,
tables => $anvil->data->{scancore}{'scan-lvm'}{tables},
});
$anvil->nice_exit({exit_code => 0});
}
# Find the block devices on this host.
collect_data($anvil);
# Mark that we ran.
$anvil->Database->insert_or_update_updated({updated_by => $THIS_FILE});
$anvil->nice_exit({exit_code => 0});
#############################################################################################################
# Functions #
#############################################################################################################
sub collect_data
{
my ($anvil) = @_;
### TODO: Swap oput '--separator \#!\#' for '--reportformat json'
collect_pvs_data($anvil);
collect_vgs_data($anvil);
collect_lvs_data($anvil);
return(0);
}
sub collect_pvs_data
{
my ($anvil) = @_;
my ($output, $return_code) = $anvil->System->call({timeout => 15, shell_call => $anvil->data->{path}{exe}{pvscan}});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { output => $output, return_code => $return_code }});
($output, $return_code) = $anvil->System->call({timeout => 15, shell_call => $anvil->data->{path}{exe}{pvs}." --noheadings --units b --reportformat json -o pv_uuid,pv_name,vg_name,pv_attr,pv_size,pv_free"});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { output => $output, return_code => $return_code }});
my $json = JSON->new->allow_nonref;
my $pvs_data = $json->decode($output);
foreach my $hash_ref (@{$pvs_data->{report}->[0]->{pv}})
{
my $pv_uuid = $hash_ref->{pv_uuid};
$anvil->data->{lvm}{pv_uuid}{$pv_uuid}{name} = $hash_ref->{pv_name};
$anvil->data->{lvm}{pv_uuid}{$pv_uuid}{used_by_vg} = $hash_ref->{vg_name};
$anvil->data->{lvm}{pv_uuid}{$pv_uuid}{attributes} = $hash_ref->{pv_attr}; # TODO: Parse this out
$anvil->data->{lvm}{pv_uuid}{$pv_uuid}{size} = ($hash_ref->{pv_size} =~ /^(\d+)B/)[0];
$anvil->data->{lvm}{pv_uuid}{$pv_uuid}{free_space} = ($hash_ref->{pv_free} =~ /^(\d+)B/)[0];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
"lvm::pv_uuid::${pv_uuid}::name" => $anvil->data->{lvm}{pv_uuid}{$pv_uuid}{name},
"lvm::pv_uuid::${pv_uuid}::used_by_vg" => $anvil->data->{lvm}{pv_uuid}{$pv_uuid}{used_by_vg},
"lvm::pv_uuid::${pv_uuid}::attributes" => $anvil->data->{lvm}{pv_uuid}{$pv_uuid}{attributes},
"lvm::pv_uuid::${pv_uuid}::size" => $anvil->Convert->add_commas({number => $anvil->data->{lvm}{pv_uuid}{$pv_uuid}{size}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{lvm}{pv_uuid}{$pv_uuid}{size}}).")",
"lvm::pv_uuid::${pv_uuid}::free_space" => $anvil->Convert->add_commas({number => $anvil->data->{lvm}{pv_uuid}{$pv_uuid}{free_space}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{lvm}{pv_uuid}{$pv_uuid}{free_space}}).")",
}});
}
return(0);
}
sub collect_vgs_data
{
my ($anvil) = @_;
my ($output, $return_code) = $anvil->System->call({timeout => 15, shell_call => $anvil->data->{path}{exe}{vgscan}});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { output => $output, return_code => $return_code }});
($output, $return_code) = $anvil->System->call({timeout => 15, shell_call => $anvil->data->{path}{exe}{vgs}." --noheadings --units b --reportformat json -o vg_uuid,vg_name,vg_attr,vg_extent_size,vg_size,vg_free"});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { output => $output, return_code => $return_code }});
my $json = JSON->new->allow_nonref;
my $vgs_data = $json->decode($output);
foreach my $hash_ref (@{$vgs_data->{report}->[0]->{vg}})
{
my $vg_uuid = $hash_ref->{vg_uuid};
$anvil->data->{lvm}{vg_uuid}{$vg_uuid}{name} = $hash_ref->{vg_name};
$anvil->data->{lvm}{vg_uuid}{$vg_uuid}{attributes} = $hash_ref->{vg_attr};
$anvil->data->{lvm}{vg_uuid}{$vg_uuid}{extent_size} = ($hash_ref->{vg_extent_size} =~ /^(\d+)B/)[0];
$anvil->data->{lvm}{vg_uuid}{$vg_uuid}{size} = ($hash_ref->{vg_size} =~ /^(\d+)B/)[0];
$anvil->data->{lvm}{vg_uuid}{$vg_uuid}{free_space} = ($hash_ref->{vg_free} =~ /^(\d+)B/)[0];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
"lvm::vg_uuid::${vg_uuid}::name" => $anvil->data->{lvm}{vg_uuid}{$vg_uuid}{name},
"lvm::vg_uuid::${vg_uuid}::attributes" => $anvil->data->{lvm}{vg_uuid}{$vg_uuid}{attributes},
"lvm::vg_uuid::${vg_uuid}::extent_size" => $anvil->Convert->add_commas({number => $anvil->data->{lvm}{vg_uuid}{$vg_uuid}{extent_size}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{lvm}{vg_uuid}{$vg_uuid}{extent_size}}),
"lvm::vg_uuid::${vg_uuid}::size" => $anvil->Convert->add_commas({number => $anvil->data->{lvm}{vg_uuid}{$vg_uuid}{size}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{lvm}{vg_uuid}{$vg_uuid}{size}}),
"lvm::vg_uuid::${vg_uuid}::free_space" => $anvil->Convert->add_commas({number => $anvil->data->{lvm}{vg_uuid}{$vg_uuid}{free_space}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{lvm}{vg_uuid}{$vg_uuid}{free_space}}),
}});
}
return(0);
}
sub collect_lvs_data
{
my ($anvil) = @_;
my ($output, $return_code) = $anvil->System->call({timeout => 15, shell_call => $anvil->data->{path}{exe}{lvscan}});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { output => $output, return_code => $return_code }});
($output, $return_code) = $anvil->System->call({timeout => 15, shell_call => $anvil->data->{path}{exe}{lvs}." --noheadings --units b --reportformat json -o lv_name,vg_name,lv_attr,lv_size,lv_uuid,lv_path,devices"});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { output => $output, return_code => $return_code }});
my $json = JSON->new->allow_nonref;
my $lvs_data = $json->decode($output);
foreach my $hash_ref (@{$lvs_data->{report}->[0]->{lv}})
{
my $lv_uuid = $hash_ref->{lv_uuid};
$anvil->data->{lvm}{lv_uuid}{$lv_uuid}{name} = $hash_ref->{lv_name};
$anvil->data->{lvm}{lv_uuid}{$lv_uuid}{attributes} = $hash_ref->{lv_attr};
$anvil->data->{lvm}{lv_uuid}{$lv_uuid}{on_vg} = $hash_ref->{vg_name};
$anvil->data->{lvm}{lv_uuid}{$lv_uuid}{device_path} = $hash_ref->{lv_path};
$anvil->data->{lvm}{lv_uuid}{$lv_uuid}{size} = ($hash_ref->{lv_size} =~ /^(\d+)B/)[0];
$anvil->data->{lvm}{lv_uuid}{$lv_uuid}{on_pvs} = $hash_ref->{devices};
$anvil->data->{lvm}{lv_uuid}{$lv_uuid}{on_pvs} =~ s/\(\d+\)//g; # Remove the starting PE numver
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
"lvm::lv_uuid::${lv_uuid}::name" => $anvil->data->{lvm}{lv_uuid}{$lv_uuid}{name},
"lvm::lv_uuid::${lv_uuid}::attributes" => $anvil->data->{lvm}{lv_uuid}{$lv_uuid}{attributes},
"lvm::lv_uuid::${lv_uuid}::on_vg" => $anvil->data->{lvm}{lv_uuid}{$lv_uuid}{on_vg},
"lvm::lv_uuid::${lv_uuid}::device_path" => $anvil->data->{lvm}{lv_uuid}{$lv_uuid}{device_path},
"lvm::lv_uuid::${lv_uuid}::size" => $anvil->Convert->add_commas({number => $anvil->data->{lvm}{lv_uuid}{$lv_uuid}{size}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{lvm}{lv_uuid}{$lv_uuid}{size}}),
"lvm::lv_uuid::${lv_uuid}::on_pvs" => $anvil->data->{lvm}{lv_uuid}{$lv_uuid}{on_pvs},
}});
}
return(0);
}

@ -0,0 +1,200 @@
-- This is the database schema for the 'scan-lvm Scan Agent'.
-- This table stores physical volume information
CREATE TABLE scan_lvm_pvs (
scan_lvm_pv_uuid uuid primary key, -- This comes from the PV itself.
scan_lvm_pv_host_uuid uuid not null,
scan_lvm_pv_name text not null, -- This is the name of the PV.
scan_lvm_pv_used_by_vg text not null, -- This is the name of the VG that uses this PV. If it's blank, then no VG uses it yet.
scan_lvm_pv_attributes text not null, -- This is the short 3-character attribute of the PV
scan_lvm_pv_size numeric not null, -- The size of the PV in bytes
scan_lvm_pv_free numeric not null, -- The free space, in bytes.
modified_date timestamp with time zone not null,
FOREIGN KEY(scan_lvm_pv_host_uuid) REFERENCES hosts(host_uuid)
);
ALTER TABLE scan_lvm_pvs OWNER TO admin;
CREATE TABLE history.scan_lvm_pvs (
history_id bigserial,
scan_lvm_pv_uuid uuid,
scan_lvm_pv_host_uuid uuid,
scan_lvm_pv_name text,
scan_lvm_pv_used_by_vg text,
scan_lvm_pv_attributes text,
scan_lvm_pv_size numeric,
scan_lvm_pv_free numeric,
modified_date timestamp with time zone not null
);
ALTER TABLE history.scan_lvm_pvs OWNER TO admin;
CREATE FUNCTION history_scan_lvm_pvs() RETURNS trigger
AS $$
DECLARE
history_scan_lvm_pvs RECORD;
BEGIN
SELECT INTO history_scan_lvm_pvs * FROM scan_lvm_pvs WHERE scan_lvm_pv_uuid=new.scan_lvm_pv_uuid;
INSERT INTO history.scan_lvm_pvs
(scan_lvm_pv_uuid,
scan_lvm_pv_host_uuid,
scan_lvm_pv_name,
scan_lvm_pv_used_by_vg,
scan_lvm_pv_attributes,
scan_lvm_pv_size,
scan_lvm_pv_free,
modified_date)
VALUES
(history_scan_lvm_pvs.scan_lvm_pv_uuid,
history_scan_lvm_pvs.scan_lvm_pv_host_uuid,
history_scan_lvm_pvs.scan_lvm_pv_name,
history_scan_lvm_pvs.scan_lvm_pv_used_by_vg,
history_scan_lvm_pvs.scan_lvm_pv_attributes,
history_scan_lvm_pvs.scan_lvm_pv_size,
history_scan_lvm_pvs.scan_lvm_pv_used,
history_scan_lvm_pvs.modified_date);
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
ALTER FUNCTION history_scan_lvm_pvs() OWNER TO admin;
CREATE TRIGGER trigger_scan_lvm_pvs
AFTER INSERT OR UPDATE ON scan_lvm_pvs
FOR EACH ROW EXECUTE PROCEDURE history_scan_lvm_pvs();
-- This table stores volume group information
CREATE TABLE scan_lvm_vgs (
scan_lvm_vg_uuid uuid primary key, -- This comes from the VG itself.
scan_lvm_vg_host_uuid uuid not null,
scan_lvm_vg_name text not null, -- This is the name of the VG.
scan_lvm_vg_attributes text not null, -- This is the short 6-character attribute of the VG
scan_lvm_vg_extent_size numeric not null, -- The size of each physical extent, in bytes.
scan_lvm_vg_size numeric not null, -- The size of the VG, in bytes.
scan_lvm_vg_free numeric not null, -- The free space in the VG, in bytes.
modified_date timestamp with time zone not null,
FOREIGN KEY(scan_lvm_vg_host_uuid) REFERENCES hosts(host_uuid)
);
ALTER TABLE scan_lvm_vgs OWNER TO admin;
CREATE TABLE history.scan_lvm_vgs (
history_id bigserial,
scan_lvm_vg_uuid uuid,
scan_lvm_vg_host_uuid uuid,
scan_lvm_vg_name text,
scan_lvm_vg_attributes text,
scan_lvm_vg_extent_size numeric,
scan_lvm_vg_size numeric,
scan_lvm_vg_free numeric,
modified_date timestamp with time zone not null
);
ALTER TABLE history.scan_lvm_vgs OWNER TO admin;
CREATE FUNCTION history_scan_lvm_vgs() RETURNS trigger
AS $$
DECLARE
history_scan_lvm_vgs RECORD;
BEGIN
SELECT INTO history_scan_lvm_vgs * FROM scan_lvm_vgs WHERE scan_lvm_vg_uuid=new.scan_lvm_vg_uuid;
INSERT INTO history.scan_lvm_vgs
(scan_lvm_vg_uuid,
scan_lvm_vg_host_uuid,
scan_lvm_vg_name,
scan_lvm_vg_attributes,
scan_lvm_vg_extent_size,
scan_lvm_vg_size,
scan_lvm_vg_free,
modified_date)
VALUES
(history_scan_lvm_vgs.scan_lvm_vg_uuid,
history_scan_lvm_vgs.scan_lvm_vg_host_uuid,
history_scan_lvm_vgs.scan_lvm_vg_name,
history_scan_lvm_vgs.scan_lvm_vg_attributes,
history_scan_lvm_vgs.scan_lvm_vg_extent_size,
history_scan_lvm_vgs.scan_lvm_vg_size,
history_scan_lvm_vgs.scan_lvm_vg_free,
history_scan_lvm_vgs.modified_date);
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
ALTER FUNCTION history_scan_lvm_vgs() OWNER TO admin;
CREATE TRIGGER trigger_scan_lvm_vgs
AFTER INSERT OR UPDATE ON scan_lvm_vgs
FOR EACH ROW EXECUTE PROCEDURE history_scan_lvm_vgs();
--lvs - lv_name,lv_uuid,lv_attr,vg_name,lv_size,lv_path,devices
CREATE TABLE scan_lvm_lvs (
scan_lvm_lv_uuid uuid primary key, -- This comes from the VG itself.
scan_lvm_lv_host_uuid uuid not null,
scan_lvm_lv_name text not null, -- This is the name of the VG.
scan_lvm_lv_attributes text not null, -- This is the short 10-character attribute of the LV
scan_lvm_lv_on_vg text not null, -- This is the name of the volume group this LV is on
scan_lvm_lv_size numeric not null, -- The size of the VG, in bytes.
scan_lvm_lv_free numeric not null, -- The free space in the VG, in bytes.
scan_lvm_lv_path text not null, -- The device path to this LV
scan_lvm_lv_on_pvs text not null, -- This is a comma-separated list of PVs this LV spans over.
modified_date timestamp with time zone not null,
FOREIGN KEY(scan_lvm_lv_host_uuid) REFERENCES hosts(host_uuid)
);
ALTER TABLE scan_lvm_lvs OWNER TO admin;
CREATE TABLE history.scan_lvm_lvs (
history_id bigserial,
scan_lvm_lv_uuid uuid,
scan_lvm_lv_host_uuid uuid,
scan_lvm_lv_name text,
scan_lvm_lv_attributes text,
scan_lvm_lv_on_vg text,
scan_lvm_lv_size numeric,
scan_lvm_lv_free numeric,
scan_lvm_lv_path text,
scan_lvm_lv_on_pvs text,
modified_date timestamp with time zone not null
);
ALTER TABLE history.scan_lvm_lvs OWNER TO admin;
CREATE FUNCTION history_scan_lvm_lvs() RETURNS trigger
AS $$
DECLARE
history_scan_lvm_lvs RECORD;
BEGIN
SELECT INTO history_scan_lvm_lvs * FROM scan_lvm_lvs WHERE scan_lvm_lv_uuid=new.scan_lvm_lv_uuid;
INSERT INTO history.scan_lvm_lvs
(scan_lvm_lv_uuid,
scan_lvm_lv_host_uuid,
scan_lvm_lv_name,
scan_lvm_lv_attributes,
scan_lvm_lv_on_vg,
scan_lvm_lv_size,
scan_lvm_lv_free,
scan_lvm_lv_path,
scan_lvm_lv_on_pvs,
modified_date)
VALUES
(history_scan_lvm_lvs.scan_lvm_lv_uuid,
history_scan_lvm_lvs.scan_lvm_lv_host_uuid,
history_scan_lvm_lvs.scan_lvm_lv_name,
history_scan_lvm_lvs.scan_lvm_lv_attributes,
history_scan_lvm_lvs.scan_lvm_lv_on_vg,
history_scan_lvm_lvs.scan_lvm_lv_size,
history_scan_lvm_lvs.scan_lvm_lv_free,
history_scan_lvm_lvs.scan_lvm_lv_path,
history_scan_lvm_lvs.scan_lvm_lv_on_pvs,
history_scan_lvm_lvs.modified_date);
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
ALTER FUNCTION history_scan_lvm_lvs() OWNER TO admin;
CREATE TRIGGER trigger_scan_lvm_lvs
AFTER INSERT OR UPDATE ON scan_lvm_lvs
FOR EACH ROW EXECUTE PROCEDURE history_scan_lvm_lvs();

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Company: Alteeve's Niche, Inc.
License: GPL v2+
Author: Madison Kelly <mkelly@alteeve.ca>
NOTE: All string keys MUST be prefixed with the agent name! ie: 'scan_lvm_log_0001'.
-->
<words>
<meta version="3.0.0" languages="en_CA,jp"/>
<!-- Canadian English -->
<language name="en_CA" long_name="Canadian English" description="ScanCore scan agent that monitors hardware, like RAM modules, CSS LED status, CPU information, etc.">
<!-- Alert entries -->
<key name="scan_lvm_alert_0001"></key>
<!-- Log entries -->
<key name="scan_lvm_log_0001">Starting: [#!variable!program!#].</key>
<!-- Message entries (usually meant to be alerts) -->
<key name="scan_lvm_message_0001"></key>
<!-- Units -->
<key name="scan_lvm_unit_0001"></key>
</language>
</words>

@ -1,100 +0,0 @@
#!/usr/bin/perl
#
# This scans the storage seen by the OS. That is to say, it monitors DRBD, LVM and backing devices. It does
# NOT monitor mdadm arrays, nor vendor-specific storage hardware. There should be dedicated scan agents for
# those tasks.
#
# Examples;
#
# Exit codes;
# 0 = Normal exit.
# 1 = Startup failure (not running as root, no DB, bad file read, etc)
#
# TODO:
# -
#
use strict;
use warnings;
use Anvil::Tools;
use Data::Dumper;
# Disable buffering
$| = 1;
# Prevent a discrepency between UID/GID and EUID/EGID from throwing an error.
$< = $>;
$( = $);
my $THIS_FILE = ($0 =~ /^.*\/(.*)$/)[0];
my $running_directory = ($0 =~ /^(.*?)\/$THIS_FILE$/)[0];
if (($running_directory =~ /^\./) && ($ENV{PWD}))
{
$running_directory =~ s/^\./$ENV{PWD}/;
}
my $anvil = Anvil::Tools->new({log_level => 2, log_secure => 1});
$anvil->Log->level({set => 2});
$anvil->Log->secure({set => 1});
# Make sure we're running as 'root'
# $< == real UID, $> == effective UID
if (($< != 0) && ($> != 0))
{
# Not root
print $anvil->Words->string({key => "error_0005"})."\n";
$anvil->nice_exit({exit_code => 1});
}
# These are the threasholds for when to alert when swap is running out.
$anvil->data->{scancore}{'scan-storage'}{disable} = 0;
$anvil->data->{switches}{force} = 0;
$anvil->Storage->read_config();
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0115", variables => { program => $THIS_FILE }});
# Read switches
$anvil->Get->switches;
# If we're disabled and '--force' wasn't used, exit.
if (($anvil->data->{scancore}{'scan-storage'}{disable}) && (not $anvil->data->{switches}{force}))
{
# Exit.
$anvil->nice_exit({exit_code => 0});
}
# These are the tables used by this agent. The order matters as it controls to order the tables are created
# and sync'ed. For purges, this array is walked backwards.
$anvil->data->{scancore}{'scan-storage'}{tables} = ["scan_storage", "scan_storage_ram_modules"];
# Handle start-up tasks
my $problem = $anvil->ScanCore->agent_startup({
debug => 3,
agent => $THIS_FILE,
tables => $anvil->data->{scancore}{'scan-storage'}{tables},
});
if ($problem)
{
$anvil->nice_exit({exit_code => 1});
}
if ($anvil->data->{switches}{purge})
{
# This can be called when doing bulk-database purges.
$anvil->Database->purge_data({
debug => 2,
tables => $anvil->data->{scancore}{'scan-storage'}{tables},
});
$anvil->nice_exit({exit_code => 0});
}
# Mark that we ran.
$anvil->Database->insert_or_update_updated({updated_by => $THIS_FILE});
$anvil->nice_exit({exit_code => 0});
#############################################################################################################
# Functions #
#############################################################################################################

@ -1,42 +0,0 @@
-- This is the database schema for the 'scan-storage Scan Agent'.
CREATE TABLE scan_storage (
scan_storage_uuid uuid primary key,
scan_storage_host_uuid uuid not null,
modified_date timestamp with time zone not null,
FOREIGN KEY(scan_storage_host_uuid) REFERENCES hosts(host_uuid)
);
ALTER TABLE scan_storage OWNER TO admin;
CREATE TABLE history.scan_storage (
history_id bigserial,
scan_storage_uuid uuid,
scan_storage_host_uuid uuid,
modified_date timestamp with time zone not null
);
ALTER TABLE history.scan_storage OWNER TO admin;
CREATE FUNCTION history_scan_storage() RETURNS trigger
AS $$
DECLARE
history_scan_storage RECORD;
BEGIN
SELECT INTO history_scan_storage * FROM scan_storage WHERE scan_storage_uuid=new.scan_storage_uuid;
INSERT INTO history.scan_storage
(scan_storage_uuid,
scan_storage_host_uuid,
modified_date)
VALUES
(history_scan_storage.scan_storage_uuid,
history_scan_storage.scan_storage_host_uuid,
history_scan_storage.modified_date);
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
ALTER FUNCTION history_scan_storage() OWNER TO admin;
CREATE TRIGGER trigger_scan_storage
AFTER INSERT OR UPDATE ON scan_storage
FOR EACH ROW EXECUTE PROCEDURE history_scan_storage();

@ -12,6 +12,8 @@
# TODO: # TODO:
# - Decide if it's worth having a separate ScanCore.log file or just feed into anvil.log. # - Decide if it's worth having a separate ScanCore.log file or just feed into anvil.log.
# - Examine limits in: https://www.freedesktop.org/software/systemd/man/systemd.exec.html#LimitCPU= # - Examine limits in: https://www.freedesktop.org/software/systemd/man/systemd.exec.html#LimitCPU=
# - Use 'nvme-cli' to write a scan-nvme scan agent, can get thermal and wear data
# -
use strict; use strict;
use warnings; use warnings;
@ -151,10 +153,10 @@ sub call_agents
scan_directory($anvil, $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. # Now loop through the agents I found and call them.
my $default_timeout = 30; my $timeout = 30;
if ((exists $anvil->data->{scancore}{timing}{agent_runtime}) && ($anvil->data->{scancore}{timing}{agent_runtime} =~ /^\d+$/)) if ((exists $anvil->data->{scancore}{timing}{agent_runtime}) && ($anvil->data->{scancore}{timing}{agent_runtime} =~ /^\d+$/))
{ {
$default_timeout = $anvil->data->{scancore}{timing}{agent_runtime}; $timeout = $anvil->data->{scancore}{timing}{agent_runtime};
} }
foreach my $agent_name (sort {$a cmp $b} keys %{$anvil->data->{scancore}{agent}}) foreach my $agent_name (sort {$a cmp $b} keys %{$anvil->data->{scancore}{agent}})
{ {
@ -184,12 +186,11 @@ sub call_agents
# Now call the agent. # Now call the agent.
my $start_time = time; my $start_time = time;
my $timeout = $default_timeout;
if (($anvil->data->{scancore}{$agent_name}{timeout}) && ($anvil->data->{scancore}{$agent_name}{timeout} =~ /^\d+$/)) if (($anvil->data->{scancore}{$agent_name}{timeout}) && ($anvil->data->{scancore}{$agent_name}{timeout} =~ /^\d+$/))
{ {
$timeout = $anvil->data->{scancore}{$agent_name}{timeout}; $timeout = $anvil->data->{scancore}{$agent_name}{timeout};
} }
my $shell_call = $anvil->data->{path}{exe}{timeout}." ".$timeout." ".$agent_path; my $shell_call = $agent_path;
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};
@ -201,7 +202,7 @@ sub call_agents
agent_name => $agent_name, agent_name => $agent_name,
timeout => $timeout, timeout => $timeout,
}}); }});
my ($output, $return_code) = $anvil->System->call({shell_call => $shell_call}); my ($output, $return_code) = $anvil->System->call({timeout => $timeout, shell_call => $shell_call});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { output => $output, return_code => $return_code }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { output => $output, return_code => $return_code }});
foreach my $line (split/\n/, $output) foreach my $line (split/\n/, $output)
{ {
@ -216,6 +217,7 @@ sub call_agents
# If the return code is '124', timeout popped. # If the return code is '124', timeout popped.
if ($return_code eq "124") if ($return_code eq "124")
{ {
### TODO: Check if this alert was set so it only goes out once.
# Register an alert... # Register an alert...
$anvil->Alert->register({set_by => $THIS_FILE, alert_level => "notice", message => "message_0180,!!agent_name!".$agent_name."!!,!!timeout!".$timeout."!!"}); $anvil->Alert->register({set_by => $THIS_FILE, alert_level => "notice", message => "message_0180,!!agent_name!".$agent_name."!!,!!timeout!".$timeout."!!"});
} }

@ -1973,6 +1973,7 @@ sub load_packages
"nss.x86_64", "nss.x86_64",
"numactl-libs.x86_64", "numactl-libs.x86_64",
"numad.x86_64", "numad.x86_64",
"nvme-cli.x86_64",
], ],
o => [ o => [
"openldap.x86_64", "openldap.x86_64",
@ -2342,6 +2343,7 @@ sub load_packages
"shadow-utils.x86_64", "shadow-utils.x86_64",
"shared-mime-info.x86_64", "shared-mime-info.x86_64",
"slang.x86_64", "slang.x86_64",
"smartmontools.x86_64",
"snappy.x86_64", "snappy.x86_64",
"sound-theme-freedesktop.noarch", "sound-theme-freedesktop.noarch",
"speexdsp.x86_64", "speexdsp.x86_64",

Loading…
Cancel
Save