From edc544255ed57c01e19551a3b1cbe28fbdf7c830 Mon Sep 17 00:00:00 2001 From: digimer Date: Wed, 8 Nov 2023 12:00:48 -0500 Subject: [PATCH] Rebased with main and resolved conflicts. This branch resolves issue #462; Auto growing PVs. Specifically, it looks at the LVM PVs on the host and checks to see if there is unused free space after the backing partition. If there is, it auto-grows the partition and then resizes the PV. This featu re is designed to make life easier for users who deleted the auto-created '/home' partition during the anaconda disk partitioning tool. * Created Storage->auto_grow_pv() that does the above. * Added the missing hidden method name _create_rsync_wrapper in the Storage module POD. * Added a call to Storage->auto_grow_pv() in anvil-configure-host and anvil-version-changes for nodes and DR. Signed-off-by: digimer --- Anvil/Tools.pm | 3 + Anvil/Tools/Storage.pm | 240 +++++++++++++++++++++++++++++- share/words.xml | 22 ++- tools/anvil-configure-host | 4 + tools/anvil-manage-server-storage | 4 +- tools/anvil-version-changes | 6 + 6 files changed, 275 insertions(+), 4 deletions(-) diff --git a/Anvil/Tools.pm b/Anvil/Tools.pm index 0a37e938..32d83268 100644 --- a/Anvil/Tools.pm +++ b/Anvil/Tools.pm @@ -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", @@ -1271,6 +1272,8 @@ 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", diff --git a/Anvil/Tools/Storage.pm b/Anvil/Tools/Storage.pm index 06d381f5..d90685bf 100644 --- a/Anvil/Tools/Storage.pm +++ b/Anvil/Tools/Storage.pm @@ -16,6 +16,7 @@ our $VERSION = "3.0.0"; my $THIS_FILE = "Storage.pm"; ### Methods; +# auto_grow_pv # backup # change_mode # change_owner @@ -113,6 +114,243 @@ 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__, 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__, level => $debug, 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__, 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__, level => $debug, 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__, level => $debug, key => "log_0822", variables => { + free_space => $anvil->Convert->bytes_to_human_readable({'bytes' => $size}), + device_path => $device_path, + pv_partition => $pv_partition, + }}); + + ### Grow the partition + # parted --align optimal /dev/sda ---pretend-input-tty resizepart 2 100% Yes; echo $? + my $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 }}); + 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__, 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}{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__, 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__, 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__, level => 1, key => "log_0826", variables => { + pv_name => $pv_name, + output => $output, + }}); + } + + # Done. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 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__, level => $debug, 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. @@ -5647,7 +5885,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. diff --git a/share/words.xml b/share/words.xml index e795a34d..8dbf914b 100644 --- a/share/words.xml +++ b/share/words.xml @@ -2633,6 +2633,21 @@ The file: [#!variable!file!#] needs to be updated. The difference is: The server: [#!variable!server_name!#] libvirt definition will now be updated. Check to verify that the connection to the server: [#!variable!server_name!#] is valid. The network mapping flag is NOT set. + No device found for PV: [#!variable!pv_name!#], skipping it. + Found: [#!variable!free_space!#] free space after the PV partition: [#!variable!device_path!#:#!variable!pv_partition!#]! Will grow the partition to use the free space. + Found: [#!variable!free_space!#] free space after the PV partition: [#!variable!device_path!#:#!variable!pv_partition!#]. This is too small for auto-growing the partition. + Found the PV partition: [#!variable!device_path!#:#!variable!device_partition!#], but there's another partition after it. Not going to grow it, of course. + The partition: [#!variable!pv_name!#] appears to have been grown successfully. The new partition scheme is: +==== +#!variable!output!# +==== + + The resize appears to have been successful. The physical volume: [#!variable!pv_name!#] details are now: +==== +#!variable!output!# +==== + + The physical volume: [#!variable!pv_name!#] has been resized! The host name: [#!variable!target!#] does not resolve to an IP address. @@ -3933,7 +3948,11 @@ We will wait: [#!variable!waiting!#] seconds and then try again. We'll give up i [ Warning ] - The file: [#!variable!file_path!#] needed to provision the server: [#!variable!server_name!#] was found, but it's not ready yet. [ Warning ] - Waiting for a bit, and then will check if files are ready. [ Warning ] - There is a duplicate storage group named: [#!variable!group_name!#]. Keeping the group with UUID: [#!variable!keep_uuid!#], and deleting the group with the UUID: [#!variable!delete_uuid!#] - Please specify a storage group to use to add the new drive to. + [ Warning ] - The system call: [#!variable!shell_call!#] returned the non-zero return code: [#!variable!return_code!#]. The command output, if anything, was: +==== +#!variable!output!# +==== + Warning! [ Warning ] - When trying to create the local meta-data on: [#!variable!drbd_resource!#/#!variable!next_drbd_volume!#] [ Warning ] - using the command: [#!variable!shell_call!#] @@ -3970,6 +3989,7 @@ We will try to proceed anyway. #!variable!error!# ==== + Please specify a storage group to use to add the new drive to. diff --git a/tools/anvil-configure-host b/tools/anvil-configure-host index 4f02b3c7..48f2add2 100755 --- a/tools/anvil-configure-host +++ b/tools/anvil-configure-host @@ -66,6 +66,10 @@ overwrite_variables_with_switches($anvil); # Set maintenance mode $anvil->System->maintenance_mode({set => 1, debug => 2}); +# Check to see if there's a PV to grow. +$anvil->Storage->auto_grow_pv({debug => 2}); + +# Reconfigure the network. reconfigure_network($anvil); # Record that we've configured this machine. diff --git a/tools/anvil-manage-server-storage b/tools/anvil-manage-server-storage index f88bf088..421ca2fd 100755 --- a/tools/anvil-manage-server-storage +++ b/tools/anvil-manage-server-storage @@ -417,11 +417,11 @@ sub manage_disk_add }}); if (not $anvil->data->{switches}{'storage-group'}) { - print $anvil->Words->string({key => 'warning_0159'})."\n"; + print $anvil->Words->string({key => 'warning_0168'})."\n"; show_storage_groups($anvil); $anvil->Job->update_progress({ progress => 100, - message => "warning_0159", + message => "warning_0168", }) if $anvil->data->{switches}{'job-uuid'}; $anvil->nice_exit({exit_code => 1}); } diff --git a/tools/anvil-version-changes b/tools/anvil-version-changes index c49176e9..6dec57fb 100755 --- a/tools/anvil-version-changes +++ b/tools/anvil-version-changes @@ -116,6 +116,9 @@ sub node_checks # Make sure logind is update to handle fencing properly # see - https://access.redhat.com/solutions/1578823 $anvil->Cluster->configure_logind({debug => 2}); + + # Look for unused free space + $anvil->Storage->auto_grow_pv({debug => 2}); return(0); } @@ -128,6 +131,9 @@ sub dr_checks # RHBZ #1961562 - https://bugzilla.redhat.com/show_bug.cgi?id=1961562#c16 handle_bz1961562($anvil); + # Look for unused free space + $anvil->Storage->auto_grow_pv({debug => 2}); + # Make sure DRBD compiled after a kernel upgrade. $anvil->DRBD->_initialize_kmod({debug => 2});