Skip to content

Commit

Permalink
Merge pull request #464 from ClusterLabs/auto_grow_pv
Browse files Browse the repository at this point in the history
This branch resolves issue #462; Auto growing PVs. Specifically, it l…
  • Loading branch information
digimer authored Nov 9, 2023
2 parents b8980b0 + 3b6da64 commit b04f121
Show file tree
Hide file tree
Showing 8 changed files with 397 additions and 5 deletions.
4 changes: 4 additions & 0 deletions Anvil/Tools.pm
Original file line number Diff line number Diff line change
Expand Up @@ -1256,6 +1256,7 @@ sub _set_paths
'osinfo-query' => "/usr/bin/osinfo-query",
pamscale => "/usr/bin/pamscale",
pamtopng => "/usr/bin/pamtopng",
parted => "/usr/sbin/parted",
passwd => "/usr/bin/passwd",
pcs => "/usr/sbin/anvil-pcs-wrapper",
perccli64 => "/opt/MegaRAID/perccli/perccli64",
Expand All @@ -1271,13 +1272,16 @@ sub _set_paths
postmap => "/usr/sbin/postmap",
postqueue => "/usr/sbin/postqueue",
pwd => "/usr/bin/pwd",
pvdisplay => "/usr/sbin/pvdisplay",
pvresize => "/usr/sbin/pvresize",
pvs => "/usr/sbin/pvs",
pvscan => "/usr/sbin/pvscan",
rm => "/usr/bin/rm",
rpm => "/usr/bin/rpm",
rsync => "/usr/bin/rsync",
sed => "/usr/bin/sed",
setsid => "/usr/bin/setsid", # See: https://serverfault.com/questions/1105733/virsh-command-hangs-when-script-runs-in-the-background
sfdisk => "/usr/sbin/sfdisk",
'shutdown' => "/usr/sbin/shutdown",
snmpget => "/usr/bin/snmpget",
snmpset => "/usr/bin/snmpset",
Expand Down
288 changes: 287 additions & 1 deletion Anvil/Tools/Storage.pm
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ our $VERSION = "3.0.0";
my $THIS_FILE = "Storage.pm";

### Methods;
# auto_grow_pv
# backup
# change_mode
# change_owner
Expand Down Expand Up @@ -113,6 +114,291 @@ sub parent
#############################################################################################################


=head2 auto_grow_pv
This looks at LVM PVs on the local host. For each one that is found, C<< parted >> is called to check if there's more that 1 GiB of free space available after it. If so, it will extend the PV partition to use the free space.
This method takes no parameters.
=cut
sub auto_grow_pv
{
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 => "Storage->_auto_grow_pv()" }});

# Look for disks that has unpartitioned space and grow it if needed.
my $host_uuid = $anvil->Get->host_uuid();
my $short_host_name = $anvil->Get->short_host_name();
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
's1:host_uuid' => $host_uuid,
's2:short_host_name' => $short_host_name,
}});

my $shell_call = $anvil->data->{path}{exe}{pvs}." --noheadings --units b -o pv_name,vg_name,pv_size,pv_free --separator ,";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }});
my ($output, $return_code) = $anvil->System->call({debug => 3, shell_call => $shell_call});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
output => $output,
return_code => $return_code,
}});
if ($return_code)
{
# Bad return code.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, priority => "alert", key => "warning_0159", variables => {
shell_call => $shell_call,
return_code => $return_code,
output => $output,
}});
next;
}
my $pv_found = 0;
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 }});
my ($pv_name, $used_by_vg, $pv_size, $pv_free) = (split/,/, $line);
$pv_size =~ s/B$//;
$pv_free =~ s/B$//;
my $pv_used = $pv_size - $pv_free;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
pv_name => $pv_name,
used_by_vg => $used_by_vg,
pv_size => $pv_size." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $pv_size}).")",
pv_free => $pv_free." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $pv_free}).")",
pv_used => $pv_used." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $pv_used}).")",
}});

# Get the raw backing disk.
my $device_path = "";
my $pv_partition = 0;
if ($pv_name =~ /(\/dev\/nvme\d+n\d+)p(\d+)$/)
{
$device_path = $1;
$pv_partition = $2;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
device_path => $device_path,
pv_partition => $pv_partition,
}});
}
elsif ($pv_name =~ /(\/dev\/\w+)(\d+)$/)
{
$device_path = $1;
$pv_partition = $2;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
device_path => $device_path,
pv_partition => $pv_partition,
}});
}
else
{
# No device found for the PV.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0821", variables => { pv_name => $pv_name }});
next;
}

# See how much free space there is on the backing disk.
my $shell_call = $anvil->data->{path}{exe}{parted}." --align optimal ".$device_path." unit B print free";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }});
my ($output, $return_code) = $anvil->System->call({debug => 3, shell_call => $shell_call});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
output => $output,
return_code => $return_code,
}});
if ($return_code)
{
# Bad return code.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, priority => "alert", key => "warning_0159", variables => {
shell_call => $shell_call,
return_code => $return_code,
output => $output,
}});
next;
}
my $pv_found = 0;
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 ($pv_found)
{
#print "Checking if: [".$line."] is free space.\n";
if ($line =~ /^(\d+)B\s+(\d+)B\s+(\d+)B\s+Free Space/i)
{
my $start_byte = $1;
my $end_byte = $2;
my $size = $3;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
's1:start_byte' => $start_byte." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $start_byte}).")",
's2:end_byte' => $end_byte." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $end_byte}).")",
's3:size' => $pv_used." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $size}).")",
}});

# There's free space! If it's greater than 1 GiB, grow it automatically.
if ($size < 1073741824)
{
# Not enough free space
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0823", variables => {
free_space => $anvil->Convert->bytes_to_human_readable({'bytes' => $size}),
device_path => $device_path,
pv_partition => $pv_partition,
}});
next;
}
else
{
# Enough free space, grow!
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0822", variables => {
free_space => $anvil->Convert->bytes_to_human_readable({'bytes' => $size}),
device_path => $device_path,
pv_partition => $pv_partition,
}});

### Backup the partition table.
#sfdisk --dump /dev/sda > partition_table_backup_sda
my $device_name = ($device_path =~ /^\/dev\/(.*)$/)[0];
my $partition_backup = "/tmp/".$device_name.".partition_table_backup";
my $shell_call = $anvil->data->{path}{exe}{sfdisk}." --dump ".$device_path." > ".$partition_backup;
my $restore_shell_call = $anvil->data->{path}{exe}{sfdisk}." ".$device_path." < ".$partition_backup." --force";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
device_name => $device_name,
partition_backup => $partition_backup,
shell_call => $shell_call,
}});
my ($output, $return_code) = $anvil->System->call({debug => 3, shell_call => $shell_call});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
output => $output,
return_code => $return_code,
}});
if ($return_code)
{
# Bad return code.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, priority => "alert", key => "warning_0159", variables => {
shell_call => $shell_call,
return_code => $return_code,
output => $output,
}});
next;
}
else
{
# Tell the user about the backup.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "message_0361", variables => {
device_path => $device_path,
partition_backup => $partition_backup,
restore_command => $restore_shell_call,
}});
}

### Grow the partition
# parted --align optimal /dev/sda ---pretend-input-tty resizepart 2 100% Yes; echo $?
$shell_call = $anvil->data->{path}{exe}{parted}." --align optimal ".$device_path." ---pretend-input-tty resizepart ".$pv_partition." 100% Yes";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }});
($output, $return_code) = $anvil->System->call({debug => 3, shell_call => $shell_call});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
output => $output,
return_code => $return_code,
}});
if ($return_code)
{
# Bad return code.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, priority => "alert", key => "warning_0159", variables => {
shell_call => $shell_call,
return_code => $return_code,
output => $output,
}});

### Restore the partition table
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "err", key => "error_0467"});

$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { restore_shell_call => $restore_shell_call }});
my ($output, $return_code) = $anvil->System->call({debug => 3, shell_call => $restore_shell_call});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
output => $output,
return_code => $return_code,
}});

# Error out.
$anvil->nice_exit({exit_code => 1});
}
else
{
# Looks like it worked. Call print again to log the new value.
my $shell_call = $anvil->data->{path}{exe}{parted}." --align optimal ".$device_path." unit B print free";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }});
my ($output, $return_code) = $anvil->System->call({debug => 3, shell_call => $shell_call});
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0825", variables => {
pv_name => $pv_name,
output => $output,
}});
}

### Resize the PV.
$shell_call = $anvil->data->{path}{exe}{pvresize}." ".$pv_name;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }});
($output, $return_code) = $anvil->System->call({debug => 3, shell_call => $shell_call});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
output => $output,
return_code => $return_code,
}});
if ($return_code)
{
# Bad return code.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, priority => "alert", key => "warning_0159", variables => {
shell_call => $shell_call,
return_code => $return_code,
output => $output,
}});
next;
}
else
{
# Looks like it worked. Call print again to log the new value.
my $shell_call = $anvil->data->{path}{exe}{pvdisplay}." ".$pv_name;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }});
my ($output, $return_code) = $anvil->System->call({debug => 3, shell_call => $shell_call});
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0826", variables => {
pv_name => $pv_name,
output => $output,
}});
}

# Done.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0827", variables => { pv_name => $pv_name }});
}
}
else
{
# There's another partition after this PV, do nothing.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0824", variables => {
device_path => $device_path,
pv_partition => $pv_partition,
}});
next;
}
}
elsif ($line =~ /^$pv_partition\s/)
{
$pv_found = 1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { pv_found => $pv_found }});
}
else
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
device_path => $device_path,
pv_partition => $pv_partition,
pv_found => $pv_found,
}});
}
}
}

return(0);
}


=head2 backup
This will create a copy of the file under the C<< path::directories::backups >> directory with the datestamp as a suffix. The path is preserved under the backup directory. The path and file name are returned.
Expand Down Expand Up @@ -5647,7 +5933,7 @@ fi";
#############################################################################################################


=head2
=head2 _create_rsync_wrapper
This does the actual work of creating the C<< expect >> wrapper script and returns the path to that wrapper for C<< rsync >> calls.
Expand Down
2 changes: 2 additions & 0 deletions anvil.spec.in
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Requires: mlocate
Requires: net-snmp-utils
Requires: NetworkManager-initscripts-updown
Requires: nvme-cli
Requires: parted
Requires: pciutils
Requires: perl-Capture-Tiny
Requires: perl-Data-Dumper
Expand Down Expand Up @@ -110,6 +111,7 @@ Requires: tcpdump
Requires: tmux
Requires: unzip
Requires: usbutils
Requires: util-linux
Requires: vim
Requires: wget
# iptables-services conflicts with firewalld
Expand Down
8 changes: 8 additions & 0 deletions man/anvil-manage-host.8
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ Set the log level to 1, 2 or 3 respectively. Be aware that level 3 generates a s
\fB\-\-age\-out\-database\fR
This requests the database check for records that are too old and purge them.
.TP
\fB\-\-auto\-grow\-pv\fR
This looks at LVM physical volumes on the local host. For each one that is found, 'parted' is called to check if there's more that 1 GiB of free space available after it. If so, it will extend the PV partition to use the free space.
.TP
If you deleted the default '/home' partition during the install of a subnode or DR host, this should give you that space back.
.TP
\fB\-\-check\-configured\fR
Check to see if the host is marked as configured or yet.
.TP
Expand All @@ -33,6 +38,9 @@ This checks to see if the database is enabled or not.
\fB\-\-check\-network\-mapping\fR
This reports if the host is currently in network mapping (this disables several features and watches the network states much more frequently)
.TP
\fB\-\-confirm\fR
This confirms actions that would normally prompt the user to confirm before proceeding.
.TP
\fB\-\-database\-active\fR
This enables the database on the local Striker dashboard.
.TP
Expand Down
Loading

0 comments on commit b04f121

Please sign in to comment.