#!/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)
#
# NOTE:
# -
#
# TODO:
# -
#
use strict;
use warnings;
use Anvil::Tools;
use Data::Dumper;
use JSON;
use XML::LibXML;
# 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();
# 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-filesystems'}{disable} = 0;
$anvil->data->{'scan-filesystems'}{alert_sort} = 2;
$anvil->data->{switches}{force} = 0;
$anvil->Storage->read_config();
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0115", variables => { program => $THIS_FILE }});
# Read switches
$anvil->Get->switches;
# Handle start-up tasks
my $problem = $anvil->ScanCore->agent_startup({agent => $THIS_FILE});
if ($problem)
{
$anvil->nice_exit({exit_code => 1});
}
if ($anvil->data->{switches}{purge})
{
# This can be called when doing bulk-database purges.
my $schema_file = $anvil->data->{path}{directories}{scan_agents}."/".$THIS_FILE."/".$THIS_FILE.".sql";
$anvil->Database->purge_data({
debug => 2,
tables => $anvil->Database->get_tables_from_schema({schema_file => $schema_file}),
});
$anvil->nice_exit({exit_code => 0});
}
# Find the block devices on this host.
collect_data($anvil);
# Load stored data.
read_last_scan($anvil);
# Loog for changes
find_changes($anvil);
# Shut down.
$anvil->ScanCore->agent_shutdown({agent => $THIS_FILE});
#############################################################################################################
# Functions #
#############################################################################################################
sub find_changes
{
my ($anvil) = @_;
foreach my $filesystem_internal_uuid (sort {$a cmp $b} keys %{$anvil->data->{new}{partition}})
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { filesystem_uuid => $filesystem_internal_uuid }});
# Skip LVM devices as they're tracked by 'scan-lvm'.
next if $anvil->data->{new}{partition}{$filesystem_internal_uuid}{type} eq "LVM2_member";
my $new_kernel_name = $anvil->data->{new}{partition}{$filesystem_internal_uuid}{kernel_name};
my $new_type = $anvil->data->{new}{partition}{$filesystem_internal_uuid}{type};
my $new_mount_point = $anvil->data->{new}{partition}{$filesystem_internal_uuid}{mount_point};
my $new_transport = $anvil->data->{new}{partition}{$filesystem_internal_uuid}{transport};
my $new_media_type = $anvil->data->{new}{partition}{$filesystem_internal_uuid}{media_type};
my $new_vendor = $anvil->data->{new}{partition}{$filesystem_internal_uuid}{vendor};
my $new_model = $anvil->data->{new}{partition}{$filesystem_internal_uuid}{model};
my $new_serial_number = $anvil->data->{new}{partition}{$filesystem_internal_uuid}{serial_number};
my $new_description = $anvil->data->{new}{partition}{$filesystem_internal_uuid}{description};
my $new_size = $anvil->data->{new}{partition}{$filesystem_internal_uuid}{size};
my $new_used = $anvil->data->{new}{partition}{$filesystem_internal_uuid}{used};
my $new_free = $new_size - $new_used;
my $new_free_percent = $anvil->Convert->round({number => ((($new_size - $new_used)/$new_size) * 100), places => 1});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
new_kernel_name => $new_kernel_name,
new_type => $new_type,
new_mount_point => $new_mount_point,
new_transport => $new_transport,
new_media_type => $new_media_type,
new_vendor => $new_vendor,
new_model => $new_model,
new_serial_number => $new_serial_number,
new_description => $new_description,
new_size => $anvil->Convert->add_commas({number => $new_size})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $new_size}),
new_used => $anvil->Convert->add_commas({number => $new_used})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $new_used}),
new_free => $anvil->Convert->add_commas({number => $new_free})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $new_free}),
new_free_percent => $new_free_percent,
}});
# Have we seen this partition before?
if (exists $anvil->data->{sql}{scan_filesystems}{scan_filesystem_internal_uuid}{$filesystem_internal_uuid})
{
my $filesystem_uuid = $anvil->data->{sql}{scan_filesystems}{scan_filesystem_internal_uuid}{$filesystem_internal_uuid}{filesystem_uuid};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { filesystem_uuid => $filesystem_uuid }});
# Note: We do not edit the description as the user may do so later in the WebUI
my $old_kernel_name = $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$filesystem_uuid}{scan_filesystem_kernel_name};
my $old_type = $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$filesystem_uuid}{scan_filesystem_type};
my $old_mount_point = $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$filesystem_uuid}{scan_filesystem_mount_point};
my $old_transport = $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$filesystem_uuid}{scan_filesystem_transport};
my $old_media_type = $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$filesystem_uuid}{scan_filesystem_media_type};
my $old_vendor = $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$filesystem_uuid}{scan_filesystem_vendor};
my $old_model = $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$filesystem_uuid}{scan_filesystem_model};
my $old_serial_number = $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$filesystem_uuid}{scan_filesystem_serial_number};
my $old_size = $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$filesystem_uuid}{scan_filesystem_size};
my $old_used = $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$filesystem_uuid}{scan_filesystem_used};
my $old_free = $old_size - $old_used;
my $old_free_percent = $anvil->Convert->round({number => ((($old_size - $old_used)/$old_size) * 100), places => 1});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
filesystem_uuid => $filesystem_uuid,
old_kernel_name => $old_kernel_name,
old_type => $old_type,
old_mount_point => $old_mount_point,
old_transport => $old_transport,
old_media_type => $old_media_type,
old_vendor => $old_vendor,
old_model => $old_model,
old_serial_number => $old_serial_number,
old_size => $anvil->Convert->add_commas({number => $old_size})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $old_size}),
old_used => $anvil->Convert->add_commas({number => $old_used})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $old_used}),
old_free => $anvil->Convert->add_commas({number => $old_free})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $old_free}),
old_free_percent => $old_free_percent,
}});
my $update = 0;
my $variables = {
filesystem_internal_uuid => $filesystem_internal_uuid,
new_kernel_name => $new_kernel_name,
old_kernel_name => $old_kernel_name,
new_type => $new_type,
old_type => $old_type,
new_mount_point => $new_mount_point,
old_mount_point => $old_mount_point,
new_transport => $new_transport,
old_transport => $old_transport,
new_media_type => $new_media_type,
old_media_type => $old_media_type,
new_vendor => $new_vendor,
old_vendor => $old_vendor,
new_model => $new_model,
old_model => $old_model,
new_serial_number => $new_serial_number,
old_serial_number => $old_serial_number,
new_hr_size => $anvil->Convert->bytes_to_human_readable({'bytes' => $new_size}),
old_hr_size => $anvil->Convert->bytes_to_human_readable({'bytes' => $old_size}),
new_byte_size => $anvil->Convert->add_commas({number => $new_size}),
old_byte_size => $anvil->Convert->add_commas({number => $old_size}),
new_hr_used => $anvil->Convert->bytes_to_human_readable({'bytes' => $new_used}),
old_hr_used => $anvil->Convert->bytes_to_human_readable({'bytes' => $old_used}),
new_byte_used => $anvil->Convert->add_commas({number => $new_used}),
old_byte_used => $anvil->Convert->add_commas({number => $old_used}),
new_hr_free => $anvil->Convert->bytes_to_human_readable({'bytes' => $new_free}),
old_hr_free => $anvil->Convert->bytes_to_human_readable({'bytes' => $old_free}),
new_byte_free => $anvil->Convert->add_commas({number => $new_free}),
old_byte_free => $anvil->Convert->add_commas({number => $old_free}),
new_free_percent => $new_free_percent,
old_free_percent => $old_free_percent,
};
if ($new_kernel_name ne $old_kernel_name)
{
# Kernel device name changed.
$update = 1;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_filesystem_alert_0005", variables => $variables});
$anvil->Alert->register({
alert_level => "notice",
message => "scan_filesystem_alert_0005",
sort_position => $anvil->data->{'scan-filesystems'}{alert_sort}++,
variables => $variables,
set_by => $THIS_FILE,
});
}
if ($new_type ne $old_type)
{
# File system type has changed. WAT?
$update = 1;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_filesystem_alert_0006", variables => $variables});
$anvil->Alert->register({
alert_level => "notice",
message => "scan_filesystem_alert_0006",
sort_position => $anvil->data->{'scan-filesystems'}{alert_sort}++,
variables => $variables,
set_by => $THIS_FILE,
});
}
if ($new_mount_point ne $old_mount_point)
{
# Mount point has changed
$update = 1;
# Is this a returning filesystem?
if ($old_mount_point eq "REMOVED")
{
# Yup, returned
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_filesystem_alert_0015", variables => $variables});
$anvil->Alert->register({
alert_level => "notice",
message => "scan_filesystem_alert_0015",
sort_position => $anvil->data->{'scan-filesystems'}{alert_sort}++,
variables => $variables,
set_by => $THIS_FILE,
});
}
else
{
# Nope, it's a remount
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_filesystem_alert_0007", variables => $variables});
$anvil->Alert->register({
alert_level => "notice",
message => "scan_filesystem_alert_0007",
sort_position => $anvil->data->{'scan-filesystems'}{alert_sort}++,
variables => $variables,
set_by => $THIS_FILE,
});
}
}
if ($new_transport ne $old_transport)
{
# Transport changed?! Could it be a dd to a new device?
$update = 1;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_filesystem_alert_0008", variables => $variables});
$anvil->Alert->register({
alert_level => "notice",
message => "scan_filesystem_alert_0008",
sort_position => $anvil->data->{'scan-filesystems'}{alert_sort}++,
variables => $variables,
set_by => $THIS_FILE,
});
}
if ($new_media_type ne $old_media_type)
{
# Media changed?! Could it be a dd to a new device?
$update = 1;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_filesystem_alert_0009", variables => $variables});
$anvil->Alert->register({
alert_level => "notice",
message => "scan_filesystem_alert_0009",
sort_position => $anvil->data->{'scan-filesystems'}{alert_sort}++,
variables => $variables,
set_by => $THIS_FILE,
});
}
if ($new_vendor ne $old_vendor)
{
# Vendor changed?! Could it be a dd to a new device?
$update = 1;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_filesystem_alert_0010", variables => $variables});
$anvil->Alert->register({
alert_level => "notice",
message => "scan_filesystem_alert_0010",
sort_position => $anvil->data->{'scan-filesystems'}{alert_sort}++,
variables => $variables,
set_by => $THIS_FILE,
});
}
if ($new_model ne $old_model)
{
# Model changed?! Could it be a dd to a new device?
$update = 1;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_filesystem_alert_0011", variables => $variables});
$anvil->Alert->register({
alert_level => "notice",
message => "scan_filesystem_alert_0011",
sort_position => $anvil->data->{'scan-filesystems'}{alert_sort}++,
variables => $variables,
set_by => $THIS_FILE,
});
}
if ($new_serial_number ne $old_serial_number)
{
# Serial Number changed?! Could it be a dd to a new device?
$update = 1;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_filesystem_alert_0012", variables => $variables});
$anvil->Alert->register({
alert_level => "notice",
message => "scan_filesystem_alert_0012",
sort_position => $anvil->data->{'scan-filesystems'}{alert_sort}++,
variables => $variables,
set_by => $THIS_FILE,
});
}
if ($new_size ne $old_size)
{
# Size changed, could be an LV resize or dd.
$update = 1;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_filesystem_alert_0013", variables => $variables});
$anvil->Alert->register({
alert_level => "notice",
message => "scan_filesystem_alert_0013",
sort_position => $anvil->data->{'scan-filesystems'}{alert_sort}++,
variables => $variables,
set_by => $THIS_FILE,
});
}
if ($new_used ne $old_used)
{
# This is expected to nearly constantly change.
$update = 1;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "scan_filesystem_alert_0014", variables => $variables});
$anvil->Alert->register({
alert_level => "info",
message => "scan_filesystem_alert_0014",
sort_position => $anvil->data->{'scan-filesystems'}{alert_sort}++,
variables => $variables,
set_by => $THIS_FILE,
});
}
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { update => $update }});
if ($update)
{
# Save the changed.
my $query = "
UPDATE
scan_filesystems
SET
scan_filesystem_type = ".$anvil->Database->quote($new_type).",
scan_filesystem_kernel_name = ".$anvil->Database->quote($new_kernel_name).",
scan_filesystem_mount_point = ".$anvil->Database->quote($new_mount_point).",
scan_filesystem_transport = ".$anvil->Database->quote($new_transport).",
scan_filesystem_media_type = ".$anvil->Database->quote($new_media_type).",
scan_filesystem_vendor = ".$anvil->Database->quote($new_vendor).",
scan_filesystem_model = ".$anvil->Database->quote($new_model).",
scan_filesystem_serial_number = ".$anvil->Database->quote($new_serial_number).",
scan_filesystem_description = ".$anvil->Database->quote($new_description).",
scan_filesystem_size = ".$anvil->Database->quote($new_size).",
scan_filesystem_used = ".$anvil->Database->quote($new_used).",
modified_date = ".$anvil->Database->quote($anvil->Database->refresh_timestamp)."
WHERE
scan_filesystem_uuid = ".$anvil->Database->quote($filesystem_uuid)."
;";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }});
$anvil->Database->write({query => $query, source => $THIS_FILE, line => __LINE__});
}
# Delete the SQL entry so we can tell which disappeared.
delete $anvil->data->{sql}{scan_filesystems}{scan_filesystem_internal_uuid}{$filesystem_internal_uuid};
delete $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$filesystem_uuid};
}
else
{
# It's new, add it.
my $scan_filesystem_uuid = $anvil->Get->uuid();
my $query = "
INSERT INTO
scan_filesystems
(
scan_filesystem_uuid,
scan_filesystem_host_uuid,
scan_filesystem_internal_uuid,
scan_filesystem_type,
scan_filesystem_kernel_name,
scan_filesystem_mount_point,
scan_filesystem_transport,
scan_filesystem_media_type,
scan_filesystem_vendor,
scan_filesystem_model,
scan_filesystem_serial_number,
scan_filesystem_description,
scan_filesystem_size,
scan_filesystem_used,
modified_date
) VALUES (
".$anvil->Database->quote($scan_filesystem_uuid).",
".$anvil->Database->quote($anvil->Get->host_uuid).",
".$anvil->Database->quote($filesystem_internal_uuid).",
".$anvil->Database->quote($new_type).",
".$anvil->Database->quote($new_kernel_name).",
".$anvil->Database->quote($new_mount_point).",
".$anvil->Database->quote($new_transport).",
".$anvil->Database->quote($new_media_type).",
".$anvil->Database->quote($new_vendor).",
".$anvil->Database->quote($new_model).",
".$anvil->Database->quote($new_serial_number).",
".$anvil->Database->quote($new_description).",
".$anvil->Database->quote($new_size).",
".$anvil->Database->quote($new_used).",
".$anvil->Database->quote($anvil->Database->refresh_timestamp)."
);";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }});
$anvil->Database->write({query => $query, source => $THIS_FILE, line => __LINE__});
# Register the notice level alert.
my $variables = {
filesystem_internal_uuid => $filesystem_internal_uuid,
new_kernel_name => $new_kernel_name,
new_type => $new_type,
new_mount_point => $new_mount_point,
new_transport => $new_transport,
new_media_type => $new_media_type,
new_vendor => $new_vendor,
new_model => $new_model,
new_serial_number => $new_serial_number,
new_description => $new_description,
new_hr_size => $anvil->Convert->bytes_to_human_readable({'bytes' => $new_size}),
new_byte_size => $anvil->Convert->add_commas({number => $new_size}),
new_hr_used => $anvil->Convert->bytes_to_human_readable({'bytes' => $new_used}),
new_byte_used => $anvil->Convert->add_commas({number => $new_used}),
new_hr_free => $anvil->Convert->bytes_to_human_readable({'bytes' => $new_free}),
new_byte_free => $anvil->Convert->add_commas({number => $new_free}),
new_free_percent => $new_free_percent,
};
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_filesystem_alert_0001", variables => $variables});
$anvil->Alert->register({
alert_level => "notice",
message => "scan_filesystem_alert_0001",
sort_position => $anvil->data->{'scan-filesystems'}{alert_sort}++,
variables => $variables,
set_by => $THIS_FILE,
});
}
# Now that we know the partition is in the database; If the free percent is less than 5, set
# a warning alert. If it's less than 15%, set a notice alert. Otherwise, if the free space is
# greater than 25%, clear any existing warnings.
my $percent_free_warn = 5;
my $percent_free_notice = 15;
my $percent_free_clear = 25;
my $variables = {
filesystem_internal_uuid => $filesystem_internal_uuid,
new_kernel_name => $new_kernel_name,
new_type => $new_type,
new_mount_point => $new_mount_point,
new_hr_size => $anvil->Convert->bytes_to_human_readable({'bytes' => $new_size}),
new_byte_size => $anvil->Convert->add_commas({number => $new_size}),
new_hr_free => $anvil->Convert->bytes_to_human_readable({'bytes' => $new_free}),
new_byte_free => $anvil->Convert->add_commas({number => $new_free}),
new_free_percent => $new_free_percent,
};
if ($new_free_percent < $percent_free_warn)
{
# See if we've warned the user.
my $changed = $anvil->Alert->check_alert_sent({
clear => 0,
record_locator => $filesystem_internal_uuid.":very_low_space",
set_by => $THIS_FILE,
});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { changed => $changed }});
if ($changed)
{
# First time we've fallen under 5%
my $alert_level = "warning";
if ($new_mount_point eq "<swap>")
{
$alert_level = "notice";
}
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_filesystem_alert_0002", variables => $variables});
$anvil->Alert->register({
alert_level => "warning",
message => "scan_filesystem_alert_0002",
sort_position => 1,
variables => $variables,
set_by => $THIS_FILE,
});
}
}
elsif ($new_free_percent < $percent_free_notice)
{
# Send the low space notice, if needed.
my $changed = $anvil->Alert->check_alert_sent({
clear => 0,
record_locator => $filesystem_internal_uuid.":low_space",
set_by => $THIS_FILE,
});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { changed => $changed }});
if ($changed)
{
# First time we've fallen under 5%
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_filesystem_alert_0003", variables => $variables});
$anvil->Alert->register({
alert_level => "notice",
message => "scan_filesystem_alert_0003",
sort_position => 1,
variables => $variables,
set_by => $THIS_FILE,
});
}
}
elsif ($new_free_percent < $percent_free_clear)
{
# Send the low space cleared message, if needed.
my $very_low_changed = $anvil->Alert->check_alert_sent({
clear => 1,
record_locator => $filesystem_internal_uuid.":very_low_space",
set_by => $THIS_FILE,
});
my $low_changed = $anvil->Alert->check_alert_sent({
clear => 1,
record_locator => $filesystem_internal_uuid.":low_space",
set_by => $THIS_FILE,
});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
very_low_changed => $very_low_changed,
low_changed => $low_changed,
}});
if (($very_low_changed) or ($low_changed))
{
# Clear the alert. If this is swap, make it a notice level alert.
my $alert_level = $very_low_changed ? "warning" : "notice";
if ($new_mount_point eq "<swap>")
{
$alert_level = "notice";
}
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_filesystem_alert_0004", variables => $variables});
$anvil->Alert->register({
alert_level => $alert_level,
clear_alert => 1,
message => "scan_filesystem_alert_0004",
sort_position => 1,
variables => $variables,
set_by => $THIS_FILE,
});
}
}
}
# Look now for lost file systems.
foreach my $filesystem_uuid (keys %{$anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}})
{
# Is this one we already know has left?
next if $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$filesystem_uuid}{scan_filesystem_mount_point} eq "REMOVED";
my $old_kernel_name = $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$filesystem_uuid}{scan_filesystem_kernel_name};
my $old_type = $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$filesystem_uuid}{scan_filesystem_type};
my $old_mount_point = $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$filesystem_uuid}{scan_filesystem_mount_point};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
filesystem_uuid => $filesystem_uuid,
old_kernel_name => $old_kernel_name,
old_type => $old_type,
old_mount_point => $old_mount_point,
}});
my $query = "
UPDATE
scan_filesystems
SET
scan_filesystem_mount_point = 'REMOVED',
modified_date = ".$anvil->Database->quote($anvil->Database->refresh_timestamp)."
WHERE
scan_filesystem_uuid = ".$anvil->Database->quote($filesystem_uuid)."
;";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }});
$anvil->Database->write({query => $query, source => $THIS_FILE, line => __LINE__});
my $update = 0;
my $variables = {
old_kernel_name => $old_kernel_name,
old_type => $old_type,
old_mount_point => $old_mount_point,
};
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "scan_filesystem_alert_0016", variables => $variables});
$anvil->Alert->register({
alert_level => "info",
message => "scan_filesystem_alert_0016",
sort_position => $anvil->data->{'scan-filesystems'}{alert_sort}++,
variables => $variables,
set_by => $THIS_FILE,
});
}
return(0);
}
# This reads in the last scan's data.
sub read_last_scan
{
my ($anvil) = @_;
# Load disk data. We load all drives, so that we can track removable media.
my $query = "
SELECT
scan_filesystem_uuid,
scan_filesystem_internal_uuid,
scan_filesystem_type,
scan_filesystem_kernel_name,
scan_filesystem_mount_point,
scan_filesystem_transport,
scan_filesystem_media_type,
scan_filesystem_vendor,
scan_filesystem_model,
scan_filesystem_serial_number,
scan_filesystem_description,
scan_filesystem_size,
scan_filesystem_used
FROM
scan_filesystems
WHERE
scan_filesystem_host_uuid = ".$anvil->Database->quote($anvil->Get->host_uuid)."
;";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, 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 => 2, list => {
results => $results,
count => $count,
}});
foreach my $row (@{$results})
{
# We've got an entry in the 'scan_filesystems' table, so now we'll look for data in the node and
# services tables.
my $scan_filesystem_uuid = $row->[0];
my $scan_filesystem_internal_uuid = $row->[1];
$anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_internal_uuid} = $scan_filesystem_internal_uuid;
$anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_type} = $row->[2];
$anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_kernel_name} = $row->[3];
$anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_mount_point} = $row->[4];
$anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_transport} = $row->[5];
$anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_media_type} = $row->[6];
$anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_vendor} = $row->[7];
$anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_model} = $row->[8];
$anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_serial_number} = $row->[9];
$anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_description} = $row->[10];
$anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_size} = $row->[11];
$anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_used} = $row->[12];
$anvil->data->{sql}{scan_filesystems}{scan_filesystem_internal_uuid}{$scan_filesystem_internal_uuid}{filesystem_uuid} = $scan_filesystem_uuid;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
"sql::scan_filesystems::scan_filesystem_uuid::${scan_filesystem_uuid}::scan_filesystem_internal_uuid" => $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_internal_uuid},
"sql::scan_filesystems::scan_filesystem_uuid::${scan_filesystem_uuid}::scan_filesystem_kernel_name" => $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_kernel_name},
"sql::scan_filesystems::scan_filesystem_uuid::${scan_filesystem_uuid}::scan_filesystem_mount_point" => $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_mount_point},
"sql::scan_filesystems::scan_filesystem_uuid::${scan_filesystem_uuid}::scan_filesystem_transport" => $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_transport},
"sql::scan_filesystems::scan_filesystem_uuid::${scan_filesystem_uuid}::scan_filesystem_media_type" => $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_media_type},
"sql::scan_filesystems::scan_filesystem_uuid::${scan_filesystem_uuid}::scan_filesystem_vendor" => $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_vendor},
"sql::scan_filesystems::scan_filesystem_uuid::${scan_filesystem_uuid}::scan_filesystem_model" => $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_model},
"sql::scan_filesystems::scan_filesystem_uuid::${scan_filesystem_uuid}::scan_filesystem_serial_number" => $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_serial_number},
"sql::scan_filesystems::scan_filesystem_uuid::${scan_filesystem_uuid}::scan_filesystem_description" => $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_description},
"sql::scan_filesystems::scan_filesystem_uuid::${scan_filesystem_uuid}::scan_filesystem_size" => $anvil->Convert->add_commas({number => $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_size}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_size}}),
"sql::scan_filesystems::scan_filesystem_uuid::${scan_filesystem_uuid}::scan_filesystem_used" => $anvil->Convert->add_commas({number => $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_used}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{sql}{scan_filesystems}{scan_filesystem_uuid}{$scan_filesystem_uuid}{scan_filesystem_used}}),
"sql::scan_filesystems::scan_filesystem_internal_uuid::${scan_filesystem_internal_uuid}::filesystem_uuid" => $anvil->data->{sql}{scan_filesystems}{scan_filesystem_internal_uuid}{$scan_filesystem_internal_uuid}{filesystem_uuid},
}});
}
return(0);
}
sub collect_data
{
my ($anvil) = @_;
### NOTE: If a drive is unmounted, we can't trust the sizes.
$anvil->Storage->parse_lsblk({debug => 3});
$anvil->Storage->parse_df({debug => 3});
foreach my $kernel_device_name (sort {$a cmp $b} keys %{$anvil->{storage}{lsblk}})
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { kernel_device_name => $kernel_device_name }});
if ($anvil->{storage}{lsblk}{$kernel_device_name}{type} eq "partition")
{
my $filesystem_internal_uuid = $anvil->{storage}{lsblk}{$kernel_device_name}{filesystem_internal_uuid};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { filesystem_internal_uuid => $filesystem_internal_uuid }});
$anvil->data->{new}{partition}{$filesystem_internal_uuid}{kernel_name} = $kernel_device_name;
$anvil->data->{new}{partition}{$filesystem_internal_uuid}{type} = $anvil->{storage}{lsblk}{$kernel_device_name}{filesystem_type};
$anvil->data->{new}{partition}{$filesystem_internal_uuid}{mount_point} = $anvil->{storage}{lsblk}{$kernel_device_name}{mount_point};
$anvil->data->{new}{partition}{$filesystem_internal_uuid}{transport} = $anvil->{storage}{lsblk}{$kernel_device_name}{transport};
$anvil->data->{new}{partition}{$filesystem_internal_uuid}{media_type} = $anvil->{storage}{lsblk}{$kernel_device_name}{rotating_drive} ? "platter" : "solid_state"; # This could be network as well someday
$anvil->data->{new}{partition}{$filesystem_internal_uuid}{vendor} = $anvil->{storage}{lsblk}{$kernel_device_name}{vendor};
$anvil->data->{new}{partition}{$filesystem_internal_uuid}{model} = $anvil->{storage}{lsblk}{$kernel_device_name}{model};
$anvil->data->{new}{partition}{$filesystem_internal_uuid}{serial_number} = $anvil->{storage}{lsblk}{$kernel_device_name}{serial_number};
$anvil->data->{new}{partition}{$filesystem_internal_uuid}{description} = $anvil->{storage}{lsblk}{$kernel_device_name}{partition_label};
$anvil->data->{new}{partition}{$filesystem_internal_uuid}{size} = $anvil->{storage}{lsblk}{$kernel_device_name}{size};
$anvil->data->{new}{partition}{$filesystem_internal_uuid}{used} = 0;
if (($anvil->data->{new}{partition}{$filesystem_internal_uuid}{mount_point}) && (exists $anvil->{storage}{df}{$kernel_device_name}))
{
# Look for space usage from 'df'
my $df_mount_point = $anvil->{storage}{df}{$kernel_device_name}{mount_point};
my $df_filesystem_type = $anvil->{storage}{df}{$kernel_device_name}{filesystem_type};
my $df_size = $anvil->{storage}{df}{$kernel_device_name}{size};
my $df_used = $anvil->{storage}{df}{$kernel_device_name}{used};
my $df_free = $anvil->{storage}{df}{$kernel_device_name}{free};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
df_filesystem_type => $df_filesystem_type,
df_mount_point => $df_mount_point,
size => $anvil->Convert->add_commas({number => $df_size})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $df_size}).")",
used => $anvil->Convert->add_commas({number => $df_used})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $df_used}).")",
free => $anvil->Convert->add_commas({number => $df_free})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $df_free}).")",
}});
# This is a check to see if the calculated free space matches the reported free
# space.
$anvil->data->{new}{partition}{$filesystem_internal_uuid}{size} = $df_size;
$anvil->data->{new}{partition}{$filesystem_internal_uuid}{used} = $df_used;
my $calculated_free = $anvil->data->{new}{partition}{$filesystem_internal_uuid}{size} - $anvil->data->{new}{partition}{$filesystem_internal_uuid}{used};
my $difference = $df_free - $calculated_free;
$difference =~ s/^-//;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
's1:calculated_free' => $anvil->Convert->add_commas({number => $calculated_free})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $calculated_free}).")",
's2:difference' => $anvil->Convert->add_commas({number => $difference})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $difference}).")",
}});
}
# If this is swap, there won't be any information from 'df', so get the used space
# from swapon.
if ($anvil->{storage}{lsblk}{$kernel_device_name}{mount_point} eq "<swap>")
{
my ($output, $return_code) = $anvil->System->call({shell_call => $anvil->data->{path}{exe}{swapon}." --show=UUID,USED --bytes --noheadings"});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
output => $output,
return_code => $return_code,
}});
foreach my $line (split/\n/, $output)
{
$line = $anvil->Words->clean_spaces({string => $line});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { line => $line }});
if ($line =~ /^$filesystem_internal_uuid (\d+)$/)
{
$anvil->data->{new}{partition}{$filesystem_internal_uuid}{used} = $1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
"new::partition::${filesystem_internal_uuid}::used" => $anvil->Convert->add_commas({number => $anvil->data->{new}{partition}{$filesystem_internal_uuid}{used}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{new}{partition}{$filesystem_internal_uuid}{used}}).")"
}});
last;
}
}
}
# Record the partition
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
"s1:new::partition::${filesystem_internal_uuid}::kernel_name" => $anvil->data->{new}{partition}{$filesystem_internal_uuid}{kernel_name},
"s2:new::partition::${filesystem_internal_uuid}::type" => $anvil->data->{new}{partition}{$filesystem_internal_uuid}{type},
"s3:new::partition::${filesystem_internal_uuid}::mount_point" => $anvil->data->{new}{partition}{$filesystem_internal_uuid}{mount_point},
"s4:new::partition::${filesystem_internal_uuid}::transport" => $anvil->data->{new}{partition}{$filesystem_internal_uuid}{transport},
"s5:new::partition::${filesystem_internal_uuid}::media_type" => $anvil->data->{new}{partition}{$filesystem_internal_uuid}{media_type},
"s6:new::partition::${filesystem_internal_uuid}::vendor" => $anvil->data->{new}{partition}{$filesystem_internal_uuid}{vendor},
"s7:new::partition::${filesystem_internal_uuid}::model" => $anvil->data->{new}{partition}{$filesystem_internal_uuid}{model},
"s8:new::partition::${filesystem_internal_uuid}::serial_number" => $anvil->data->{new}{partition}{$filesystem_internal_uuid}{serial_number},
"s9:new::partition::${filesystem_internal_uuid}::description" => $anvil->data->{new}{partition}{$filesystem_internal_uuid}{description},
"s10:new::partition::${filesystem_internal_uuid}::filesystem_internal_uuid" => $anvil->data->{new}{partition}{$filesystem_internal_uuid}{filesystem_internal_uuid},
"s10:new::partition::${filesystem_internal_uuid}::size" => $anvil->Convert->add_commas({number => $anvil->data->{new}{partition}{$filesystem_internal_uuid}{size}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{new}{partition}{$filesystem_internal_uuid}{size}}).")",
"s11:new::partition::${filesystem_internal_uuid}::used" => $anvil->Convert->add_commas({number => $anvil->data->{new}{partition}{$filesystem_internal_uuid}{used}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{new}{partition}{$filesystem_internal_uuid}{used}}).")",
}});
}
}
return(0);
}
# This collects data for buses and disk info.
sub collect_bus_data
{
my ($anvil) = @_;
my ($output, $return_code) = $anvil->System->call({shell_call => $anvil->data->{path}{exe}{lshw}." -class disk -class storage -xml"});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
output => $output,
return_code => $return_code,
}});
# We tried -json, but the output was not validly formatted.
local $@;
my $dom = eval { XML::LibXML->load_xml(string => $output); };
if ($@)
{
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, key => "warning_0053", variables => {
cib => $output,
error => $@,
}});
}
foreach my $node ($dom->findnodes('/list/node'))
{
my $id = $node->{id};
my $class = $node->{class};
my $description = $node->findvalue('./description');
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
id => $id,
class => $class,
description => $description,
}});
foreach my $device ($node->findnodes('./node'))
{
my $dev_id = $device->{id};
my $dev_class = $device->{class};
my $bus_info = $device->findvalue('./businfo');
my $path = $device->findvalue('./logicalname');
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
dev_id => $dev_id,
dev_class => $dev_class,
bus_info => $bus_info,
path => $path,
}});
}
### NOTE: Full CIB details;
### - https://clusterlabs.org/pacemaker/doc/en-US/Pacemaker/2.0/html-single/Pacemaker_Explained/index.html
# Successful parse!
# foreach my $nvpair ($dom->findnodes('/cib/configuration/crm_config/cluster_property_set/nvpair'))
# {
# my $nvpair_id = $nvpair->{id};
# foreach my $variable (sort {$a cmp $b} keys %{$nvpair})
# {
# next if $variable eq "id";
# $anvil->data->{cib}{parsed}{configuration}{crm_config}{cluster_property_set}{nvpair}{$nvpair_id}{$variable} = $nvpair->{$variable};
# $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
# "cib::parsed::configuration::crm_config::cluster_property_set::nvpair::${nvpair_id}::${variable}" => $anvil->data->{cib}{parsed}{configuration}{crm_config}{cluster_property_set}{nvpair}{$nvpair_id}{$variable},
# }});
# }
# }
# foreach my $node ($dom->findnodes('/cib/configuration/nodes/node'))
# {
# }
}
# my $json = JSON->new->allow_nonref;
# my $pvs_data = $json->decode($output);
# foreach my $hash_ref (@{$pvs_data->{report}->[0]->{pv}})
# {
# my $scan_filesystem_type = $hash_ref->{pv_uuid};
# $anvil->data->{filesystem}{scan_filesystem_type}{$scan_filesystem_type}{name} = $hash_ref->{pv_name};
# $anvil->data->{filesystem}{scan_filesystem_type}{$scan_filesystem_type}{used_by_vg} = $hash_ref->{vg_name};
# $anvil->data->{filesystem}{scan_filesystem_type}{$scan_filesystem_type}{attributes} = $hash_ref->{pv_attr}; # TODO: Parse this out
# $anvil->data->{filesystem}{scan_filesystem_type}{$scan_filesystem_type}{size} = ($hash_ref->{pv_size} =~ /^(\d+)B/)[0];
# $anvil->data->{filesystem}{scan_filesystem_type}{$scan_filesystem_type}{free_space} = ($hash_ref->{pv_free} =~ /^(\d+)B/)[0];
# $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
# "filesystem::scan_filesystem_uuid::${scan_filesystem_uuid}::name" => $anvil->data->{filesystem}{scan_filesystem_type}{$scan_filesystem_type}{name},
# "filesystem::scan_filesystem_uuid::${scan_filesystem_uuid}::used_by_vg" => $anvil->data->{filesystem}{scan_filesystem_type}{$scan_filesystem_type}{used_by_vg},
# "filesystem::scan_filesystem_uuid::${scan_filesystem_uuid}::attributes" => $anvil->data->{filesystem}{scan_filesystem_type}{$scan_filesystem_type}{attributes},
# "filesystem::scan_filesystem_uuid::${scan_filesystem_uuid}::size" => $anvil->Convert->add_commas({number => $anvil->data->{filesystem}{scan_filesystem_type}{$scan_filesystem_type}{size}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{filesystem}{scan_filesystem_type}{$scan_filesystem_type}{size}}).")",
# "filesystem::scan_filesystem_uuid::${scan_filesystem_uuid}::free_space" => $anvil->Convert->add_commas({number => $anvil->data->{filesystem}{scan_filesystem_type}{$scan_filesystem_type}{free_space}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{filesystem}{scan_filesystem_type}{$scan_filesystem_type}{free_space}}).")",
# }});
# }
return(0);
}
sub collect_fs_data
{
my ($anvil) = @_;
return(0);
}