From 2acb77eb5a008946c9d6a3ecae1f8b69493d42ad Mon Sep 17 00:00:00 2001 From: Sloane Bernstein Date: Wed, 7 Aug 2024 16:39:35 -0500 Subject: [PATCH] Upgrade PostgreSQL during ELevate Case RE-102, RE-637: Attempt to run an upgrade of the data directory for the system instance of PostgreSQL. Changelog: Attempt to upgrade PostgreSQL during ELevate. --- elevate-cpanel | 469 +++++++++++++++++++++------ lib/Elevate/Blockers/Databases.pm | 70 ---- lib/Elevate/Components/CCS.pm | 114 ++++--- lib/Elevate/Components/PostgreSQL.pm | 394 ++++++++++++++++++++++ lib/Elevate/Constants.pm | 2 + lib/Elevate/SystemctlService.pm | 23 ++ script/elevate-cpanel.PL | 3 + t/blocker-Databases.t | 172 ---------- t/components-CCS.t | 6 +- t/components-PostgreSQL.t | 95 ++++++ 10 files changed, 947 insertions(+), 401 deletions(-) create mode 100644 lib/Elevate/Components/PostgreSQL.pm create mode 100644 t/components-PostgreSQL.t diff --git a/elevate-cpanel b/elevate-cpanel index 640efba1..ed5c194a 100755 --- a/elevate-cpanel +++ b/elevate-cpanel @@ -53,6 +53,7 @@ BEGIN { # Suppress load of all of these at earliest point. $INC{'Elevate/Components/NixStats.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/PECL.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/PerlXS.pm'} = 'script/elevate-cpanel.PL.static'; + $INC{'Elevate/Components/PostgreSQL.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/UnconvertedModules.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/Repositories.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/RpmDB.pm'} = 'script/elevate-cpanel.PL.static'; @@ -130,6 +131,8 @@ BEGIN { # Suppress load of all of these at earliest point. serverbackup-setup }; + use constant POSTGRESQL_SYSTEM_DATADIR => '/var/lib/pgsql/data'; + 1; } # --- END lib/Elevate/Constants.pm @@ -612,84 +615,14 @@ BEGIN { # Suppress load of all of these at earliest point. # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } - use constant POSTGRESQL_ACK_TOUCH_FILE => q[/var/cpanel/acknowledge_postgresql_for_elevate]; - sub check ($self) { my $ok = 1; - $self->_warning_if_postgresql_installed; - $ok = 0 unless $self->_blocker_acknowledge_postgresql_datadir; $ok = 0 unless $self->_blocker_old_mysql; $ok = 0 unless $self->_blocker_mysql_upgrade_in_progress; $self->_warning_mysql_not_enabled(); return $ok; } - sub _warning_if_postgresql_installed ($self) { - return 0 unless Cpanel::Pkgr::is_installed('postgresql-server'); - - my $pg_full_ver = Cpanel::Pkgr::get_package_version('postgresql-server'); - my ($old_version) = $pg_full_ver =~ m/^(\d+\.\d+)/a; - return 1 if !$old_version || $old_version >= 10; - - my $pretty_distro_name = $self->upgrade_to_pretty_name(); - WARN("You have postgresql-server version $old_version installed. This will be upgraded irreversibly to version 10.0 when you switch to $pretty_distro_name"); - - return 2; - } - - sub _blocker_acknowledge_postgresql_datadir ($self) { - - return 0 unless Cpanel::Pkgr::is_installed('postgresql-server'); - - my $touch_file = POSTGRESQL_ACK_TOUCH_FILE; - return 0 if -e $touch_file; - - my @users_with_dbs = $self->_has_mapped_postgresql_dbs(); - return 0 unless scalar @users_with_dbs; - - my $message = <<~"EOS"; - One or more users on your system have associated PostgreSQL databases. - ELevate may upgrade the software packages associated with PostgreSQL - automatically, but if it does, it will *NOT* automatically update the - PostgreSQL data directory to work with the new version. Without an update - to the data directory, the upgraded PostgreSQL software will not start, in - order to ensure that your data does not become corrupted. - - For more information about PostgreSQL upgrades, please consider the - following resources: - - https://cpanel.github.io/elevate/blockers/#postgresql - https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/deploying_different_types_of_servers/using-databases#migrating-to-a-rhel-8-version-of-postgresql_using-postgresql - https://www.postgresql.org/docs/10/pgupgrade.html - - When you are ready to acknowledge that you have prepared to update the - PostgreSQL data directory, or that this warning does not apply to you, - please touch the following file to continue with the ELevate process: - - > touch $touch_file - - The following user(s) have PostgreSQL databases associated with their cPanel accounts: - EOS - - $message .= join "\n", sort(@users_with_dbs); - - return $self->has_blocker($message); - } - - sub _has_mapped_postgresql_dbs ($self) { - - my $dbindex = eval { Cpanel::DB::Map::Collection::Index->new( { db => 'PGSQL' } ); }; - if ( my $exception = $@ ) { - ERROR( 'Unable to read the database index file: ' . Cpanel::Exception::get_string($exception) ); - $self->has_blocker("Unable to read the database index file; you may need to rebuild it by running: /usr/local/cpanel/bin/dbindex"); - return (); - } - - my %user_hash = map { $dbindex->{dbindex}{$_} => 1 } keys %{ $dbindex->{dbindex} }; - - return ( keys %user_hash ); - } - sub _blocker_old_mysql ($self) { my $mysql_is_provided_by_cloudlinux = Elevate::Database::is_database_provided_by_cloudlinux(0); @@ -2803,7 +2736,6 @@ EOS } sub clean_up_pkg_cruft ($self) { - $self->move_pgsql_directory(); $self->remove_cpanel_ccs_home_directory(); return; } @@ -2813,47 +2745,12 @@ EOS return; } - sub move_pgsql_directory ($self) { - my $pg_dir = '/var/lib/pgsql'; - my $pg_backup_dir = '/var/lib/pgsql_pre_elevate'; - - File::Path::remove_tree($pg_backup_dir) if -e $pg_backup_dir && -d $pg_backup_dir; - - $pg_backup_dir .= '_' . time() . '_' . $$ if -e $pg_backup_dir; - - File::Path::remove_tree($pg_backup_dir) if -e $pg_backup_dir && -d $pg_backup_dir; - - if ( -e $pg_backup_dir ) { - die <<~"EOS"; - Unable to ensure a valid backup path for $pg_dir. - Please ensure that '/var/lib/pgsql_pre_elevate' does not exist on your system and execute this script again with - - /scripts/elevate-cpanel --continue - - EOS - } - - INFO( <<~"EOS" ); - Moving the PostgreSQL data dir located at $pg_dir to $pg_backup_dir - to ensure a functioning PostgreSQL server after the elevation completes. - EOS - - File::Copy::move( $pg_dir, $pg_backup_dir ) if -d $pg_dir; - - return; - } - sub remove_ccs_and_dependencies ($self) { my $zpush_installed = Cpanel::Pkgr::is_installed(ZPUSH_PACKAGE); Elevate::StageFile::update_stage_file( { zpush_installed => $zpush_installed } ); - my @ccs_dependencies = qw{ - postgresql - postgresql-devel - postgresql-server - }; - + my @ccs_dependencies; push @ccs_dependencies, ZPUSH_PACKAGE(); $self->yum->remove( CCS_PACKAGE(), @ccs_dependencies ); @@ -3021,6 +2918,8 @@ EOS sub post_leapp ($self) { return unless Elevate::StageFile::read_stage_file('ccs_installed'); + $self->run_once('move_pgsql_directory'); + $self->_install_ccs_and_dependencies(); $self->_clear_task_queue(); @@ -3028,6 +2927,8 @@ EOS $self->_ensure_ccs_service_is_up(); $self->run_once('import_ccs_data'); + $self->move_pgsql_directory_back(); + return; } @@ -3145,6 +3046,52 @@ EOS return; } + sub move_pgsql_directory ($self) { + my $pg_dir = '/var/lib/pgsql'; + my $pg_backup_dir = '/var/lib/pgsql_pre_elevate'; + + return unless -d $pg_dir; + + File::Path::remove_tree($pg_backup_dir) if -e $pg_backup_dir && -d $pg_backup_dir; + + INFO( <<~"EOS" ); + Temporarily moving the PostgreSQL data dir located at $pg_dir to $pg_backup_dir + due to issues with the CCS upgrade process. This script will attempt to move the directory + back to $pg_dir after CCS is upgraded. + EOS + + my $success = rename( $pg_dir, $pg_backup_dir ); + LOGDIE(qq[The system failed to move $pg_dir to $pg_backup_dir (reason: $!)!]) unless $success; + + return; + } + + sub move_pgsql_directory_back ($self) { + my $pg_dir = '/var/lib/pgsql'; + my $pg_backup_dir = '/var/lib/pgsql_pre_elevate'; + + return unless -e $pg_backup_dir; + + INFO(qq[Restoring system PostgreSQL instance...]); + + Elevate::SystemctlService->new( name => 'postgresql' )->stop(); # just in case + File::Path::remove_tree($pg_dir) if -e $pg_dir; + + my $result = rename( $pg_backup_dir, $pg_dir ); + if ( !$result ) { + my $msg = <<~"EOS"; + The system could not fully restore $pg_backup_dir to $pg_dir (reason: $!). + Restore this manually, and perform the update as recommended. + EOS + + LOGDIE($msg); + return; + } + + INFO(qq[The system returned the PostgreSQL data directory to $pg_dir.]); + return; + } + 1; } # --- END lib/Elevate/Components/CCS.pm @@ -5112,6 +5059,288 @@ EOS } # --- END lib/Elevate/Components/PerlXS.pm +{ # --- BEGIN lib/Elevate/Components/PostgreSQL.pm + + package Elevate::Components::PostgreSQL; + + use cPstrict; + + use Simple::Accessor qw{service}; + + # use Elevate::Components::Base(); + our @ISA; + BEGIN { push @ISA, qw(Elevate::Components::Base); } + + use Elevate::Constants (); + use Elevate::Notify (); + use Elevate::StageFile (); + use Elevate::SystemctlService (); + + use Cpanel::Pkgr (); + use Cpanel::SafeRun::Object (); + use Cpanel::Services::Enabled (); + use Whostmgr::Postgres (); + + # use Log::Log4perl qw(:easy); + INIT { Log::Log4perl->import(qw{:easy}); } + + use File::Copy::Recursive (); + use File::Slurp (); + + sub _build_service ($self) { + return Elevate::SystemctlService->new( name => 'postgresql' ); + } + + sub pre_leapp ($self) { + if ( Cpanel::Pkgr::is_installed('postgresql-server') ) { + $self->_store_postgresql_encoding_and_locale(); + $self->_disable_postgresql_service(); + $self->_backup_postgresql_datadir(); + } + + return; + } + + sub _store_postgresql_encoding_and_locale ($self) { + + return if $self->_gave_up_on_postgresql; # won't hurt + + INFO("Fetching encoding and locale information from PostgreSQL."); + + my $is_active_prior = $self->service->is_active; + + $self->service->start(); + + if ( $self->service->is_active ) { + my $psql_sro = Cpanel::SafeRun::Object->new( + program => '/usr/bin/psql', + args => [ + qw{-F | -At -U postgres -c}, + q{SELECT pg_encoding_to_char(encoding), datcollate, datctype FROM pg_catalog.pg_database WHERE datname = 'template1'}, + ], + ); + + if ( $psql_sro->CHILD_ERROR ) { + WARN("The system instance of PostgreSQL did not return information about the encoding and locale of core databases."); + WARN("ELevate will assume the system defaults and attempt an upgrade anyway."); + return; + } + + my $output = $psql_sro->stdout; + chomp $output; + + my ( $encoding, $collation, $ctype ) = split /\|/, $output; + Elevate::StageFile::update_stage_file( + { + postgresql_locale => { + encoding => $encoding, + collation => $collation, + ctype => $ctype, + } + } + ); + + $self->service->stop() unless $is_active_prior; + } + else { + WARN("The system instance of PostgreSQL could not start to give information about the encoding and locale of core databases."); + WARN("ELevate will assume the system defaults and attempt an upgrade anyway."); + } + + return; + } + + sub _disable_postgresql_service ($self) { + + if ( Cpanel::Services::Enabled::is_enabled('postgresql') ) { + Elevate::StageFile::update_stage_file( { 're-enable_postgresql_in_sm' => 1 } ); + Cpanel::Services::Enabled::touch_disable_file('postgresql'); + } + + return; + } + + sub _backup_postgresql_datadir ($self) { + + $self->service->stop() if $self->service->is_active; # for safety + + my $pgsql_datadir_path = Elevate::Constants::POSTGRESQL_SYSTEM_DATADIR; + my $pgsql_datadir_backup_path = $pgsql_datadir_path . '_elevate_' . time() . '_' . $$; + + INFO("Backing up the system PostgreSQL data directory at $pgsql_datadir_path to $pgsql_datadir_backup_path."); + + my ( $uid, $gid ) = ( scalar( getpwnam('postgres') ), scalar( getgrnam('postgres') ) ); + my $outcome = 0; + { + local ( $>, $) ) = ( $uid, "$gid $gid" ); + $outcome = File::Copy::Recursive::dircopy( $pgsql_datadir_path, $pgsql_datadir_backup_path ); + } + + if ( !$outcome ) { + ERROR("The system encountered an error while trying to make a backup."); + $self->_give_up_on_postgresql(); + } + else { + Elevate::Notify::add_final_notification( <<~"EOS" ); + ELevate backed up your system PostgreSQL data directory to $pgsql_datadir_backup_path + prior to any modification or attempt to upgrade, in case the upgrade needs to be performed + manually, or if old settings need to be referenced. + EOS + } + + return; + } + + sub post_leapp ($self) { + if ( Cpanel::Pkgr::is_installed('postgresql-server') ) { + $self->_perform_config_workaround(); + $self->_perform_postgresql_upgrade(); + $self->_re_enable_service_if_needed(); + $self->_run_whostmgr_postgres_update_config(); + } + + return; + } + + sub _perform_config_workaround ($self) { + return if $self->_gave_up_on_postgresql; + + my $pgconf_path = Elevate::Constants::POSTGRESQL_SYSTEM_DATADIR . '/postgresql.conf'; + return unless -e $pgconf_path; # if postgresql.conf isn't there, there's nothing to work around + + my $pgconf = eval { File::Slurper::read_text($pgconf_path) }; + if ($@) { + ERROR("Attempting to read $pgconf_path resulted in an error: $@"); + $self->_give_up_on_postgresql(); + return; + } + + my $changed = 0; + my @lines = split "\n", $pgconf; + foreach my $line (@lines) { + next if $line =~ m/^\s*$/a; + if ( $line =~ m/^\s*unix_socket_directories/ ) { + $line = "#$line"; + $changed = 1; + } + } + + if ($changed) { + push @lines, "unix_socket_directory = '/var/run/postgresql'"; + + INFO("Modifying $pgconf_path to work around a defect in the system's PostgreSQL upgrade package."); + + my $pgconf_altered = join "\n", @lines; + eval { File::Slurper::write_text( $pgconf_path, $pgconf_altered ) }; + + if ($@) { + ERROR("Attempting to overwrite $pgconf_path resulted in an error: $@"); + $self->_give_up_on_postgresql(); + } + } + + return; + } + + sub _perform_postgresql_upgrade ($self) { + return if $self->_gave_up_on_postgresql; + + INFO("Installing PostgreSQL update package:"); + $self->dnf->install('postgresql-upgrade'); + + my $opts = Elevate::StageFile::read_stage_file('postgresql_locale'); + my @args; + push @args, "--encoding=$opts->{encoding}" if $opts->{encoding}; + push @args, "--lc-collate=$opts->{collation}" if $opts->{collation}; + push @args, "--lc-ctype=$opts->{ctype}" if $opts->{ctype}; + + local $ENV{PGSETUP_INITDB_OPTIONS} = join ' ', @args if scalar @args > 0; + + INFO("Upgrading PostgreSQL data directory:"); + my $outcome = $self->ssystem( { keep_env => ( scalar @args > 0 ? 1 : 0 ) }, qw{/usr/bin/postgresql-setup --upgrade} ); + + if ( $outcome == 0 ) { + INFO("The PostgreSQL upgrade process was successful."); + Elevate::Notify::add_final_notification( <<~EOS ); + ELevate successfully ran the upgrade procedure on the system instance of + PostgreSQL. If no problems are reported with configuring the upgraded instance + to work with cPanel, you should proceed with applying any relevant + customizations to the configuration and authentication settings, since the + upgrade process reset this information to system defaults. + EOS + } + else { + ERROR("The upgrade attempt of the system PostgreSQL instance failed. See the log files mentioned in the output of postgresql-setup for more information."); + $self->_give_up_on_postgresql(); + } + + return; + } + + sub _re_enable_service_if_needed ($self) { + return if $self->_gave_up_on_postgresql; # keep disabled if there was a failure + + if ( Elevate::StageFile::read_stage_file( 're-enable_postgresql_in_sm', 0 ) ) { + Cpanel::Services::Enabled::remove_disable_files('postgresql'); + } + + return; + } + + sub _run_whostmgr_postgres_update_config ($self) { + return if $self->_gave_up_on_postgresql; + + INFO("Configuring PostgreSQL to work with cPanel's installation of phpPgAdmin."); + $self->service->start(); # less noisy when it's online, but still works + + my ( $success, $msg ) = Whostmgr::Postgres::update_config(); + if ( !$success ) { + ERROR("The system failed to update the PostgreSQL configuration: $msg"); + Elevate::Notify::add_final_notification( <<~EOS ); + ELevate could not configure the upgraded system PostgreSQL instance to work + with cPanel. See the log for additional information. Once the issue has been + addressed, perform this step manually using the "Postgres Config Install" area + in WHM: + + https://go.cpanel.net/whmdocsConfigurePostgreSQL + + Do this before restoring any customizations to PostgreSQL configuration or + authentication files, since performing this action resets these to cPanel + defaults. + EOS + } + + return; + } + + sub _give_up_on_postgresql ($self) { + ERROR('Skipping attempt to upgrade the system instance of PostgreSQL.'); + Elevate::StageFile::update_stage_file( { postgresql_give_up => 1 } ); + Elevate::Notify::add_final_notification( <<~EOS ); + The process of upgrading the system instance of PostgreSQL failed. The + PostgreSQL service has been disabled in the Service Manager in WHM: + + https://go.cpanel.net/whmdocsServiceManager + + If you do not have cPanel users who use PostgreSQL or otherwise do not use it, + you do not have to take any action. Otherwise, see the ELevate logs for further + information. + EOS + return; + } + + sub _gave_up_on_postgresql ($self) { + return Elevate::StageFile::read_stage_file( 'postgresql_give_up', 0 ); + } + + sub _given_up_on_postgresql { + goto &_gave_up_on_postgresql; + } + + 1; + +} # --- END lib/Elevate/Components/PostgreSQL.pm + { # --- BEGIN lib/Elevate/Components/UnconvertedModules.pm package Elevate::Components::UnconvertedModules; @@ -7849,6 +8078,15 @@ EOS return; } + sub start ($self) { + + return if $self->is_active; + + $self->ssystem( '/usr/bin/systemctl', 'start', $self->name ); + + return; + } + sub stop ($self) { return unless $self->is_active; @@ -7872,6 +8110,20 @@ EOS return; } + sub enable ( $self, %opts ) { + + return if $self->is_enabled; + + my $now = $opts{'now'} // 1; # by default enable it now... + + my @args = qw{ enable }; + push @args, '--now' if $now; + + $self->ssystem( '/usr/bin/systemctl', @args, $self->name ); + + return; + } + 1; } # --- END lib/Elevate/SystemctlService.pm @@ -8462,6 +8714,7 @@ use Elevate::Components::NICs (); use Elevate::Components::NixStats (); use Elevate::Components::PECL (); use Elevate::Components::PerlXS (); +use Elevate::Components::PostgreSQL (); use Elevate::Components::UnconvertedModules (); use Elevate::Components::Repositories (); use Elevate::Components::RpmDB (); @@ -9503,6 +9756,7 @@ sub run_final_components_pre_leapp ($self) { # order matters $self->run_component_once( 'RmMod' => 'pre_leapp' ); $self->run_component_once( 'MySQL' => 'pre_leapp' ); + $self->run_component_once( 'PostgreSQL' => 'pre_leapp' ); $self->run_component_once( 'Repositories' => 'pre_leapp' ); $self->run_component_once( 'cPanelPlugins' => 'pre_leapp' ); $self->run_component_once( 'PerlXS' => 'pre_leapp' ); @@ -9531,6 +9785,7 @@ sub post_leapp_update_restore ($self) { # plugins can use MySQL - restore database earlier $self->run_component_once( 'MySQL' => 'post_leapp' ); $self->run_component_once( 'PerlXS' => 'post_leapp' ); + $self->run_component_once( 'PostgreSQL' => 'post_leapp' ); $self->run_component_once( 'cPanelPlugins' => 'post_leapp' ); $self->run_component_once( 'DatabaseUpgrade' => 'post_leapp' ); $self->run_component_once( 'PECL' => 'post_leapp' ); diff --git a/lib/Elevate/Blockers/Databases.pm b/lib/Elevate/Blockers/Databases.pm index ec42de5f..d8cf1f45 100644 --- a/lib/Elevate/Blockers/Databases.pm +++ b/lib/Elevate/Blockers/Databases.pm @@ -27,84 +27,14 @@ use parent qw{Elevate::Blockers::Base}; use Log::Log4perl qw(:easy); -use constant POSTGRESQL_ACK_TOUCH_FILE => q[/var/cpanel/acknowledge_postgresql_for_elevate]; - sub check ($self) { my $ok = 1; - $self->_warning_if_postgresql_installed; - $ok = 0 unless $self->_blocker_acknowledge_postgresql_datadir; $ok = 0 unless $self->_blocker_old_mysql; $ok = 0 unless $self->_blocker_mysql_upgrade_in_progress; $self->_warning_mysql_not_enabled(); return $ok; } -sub _warning_if_postgresql_installed ($self) { - return 0 unless Cpanel::Pkgr::is_installed('postgresql-server'); - - my $pg_full_ver = Cpanel::Pkgr::get_package_version('postgresql-server'); - my ($old_version) = $pg_full_ver =~ m/^(\d+\.\d+)/a; - return 1 if !$old_version || $old_version >= 10; - - my $pretty_distro_name = $self->upgrade_to_pretty_name(); - WARN("You have postgresql-server version $old_version installed. This will be upgraded irreversibly to version 10.0 when you switch to $pretty_distro_name"); - - return 2; -} - -sub _blocker_acknowledge_postgresql_datadir ($self) { - - return 0 unless Cpanel::Pkgr::is_installed('postgresql-server'); - - my $touch_file = POSTGRESQL_ACK_TOUCH_FILE; - return 0 if -e $touch_file; - - my @users_with_dbs = $self->_has_mapped_postgresql_dbs(); - return 0 unless scalar @users_with_dbs; - - my $message = <<~"EOS"; - One or more users on your system have associated PostgreSQL databases. - ELevate may upgrade the software packages associated with PostgreSQL - automatically, but if it does, it will *NOT* automatically update the - PostgreSQL data directory to work with the new version. Without an update - to the data directory, the upgraded PostgreSQL software will not start, in - order to ensure that your data does not become corrupted. - - For more information about PostgreSQL upgrades, please consider the - following resources: - - https://cpanel.github.io/elevate/blockers/#postgresql - https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/deploying_different_types_of_servers/using-databases#migrating-to-a-rhel-8-version-of-postgresql_using-postgresql - https://www.postgresql.org/docs/10/pgupgrade.html - - When you are ready to acknowledge that you have prepared to update the - PostgreSQL data directory, or that this warning does not apply to you, - please touch the following file to continue with the ELevate process: - - > touch $touch_file - - The following user(s) have PostgreSQL databases associated with their cPanel accounts: - EOS - - $message .= join "\n", sort(@users_with_dbs); - - return $self->has_blocker($message); -} - -sub _has_mapped_postgresql_dbs ($self) { - - my $dbindex = eval { Cpanel::DB::Map::Collection::Index->new( { db => 'PGSQL' } ); }; - if ( my $exception = $@ ) { - ERROR( 'Unable to read the database index file: ' . Cpanel::Exception::get_string($exception) ); - $self->has_blocker("Unable to read the database index file; you may need to rebuild it by running: /usr/local/cpanel/bin/dbindex"); - return (); - } - - my %user_hash = map { $dbindex->{dbindex}{$_} => 1 } keys %{ $dbindex->{dbindex} }; - - return ( keys %user_hash ); -} - sub _blocker_old_mysql ($self) { my $mysql_is_provided_by_cloudlinux = Elevate::Database::is_database_provided_by_cloudlinux(0); diff --git a/lib/Elevate/Components/CCS.pm b/lib/Elevate/Components/CCS.pm index c9475065..a6107413 100644 --- a/lib/Elevate/Components/CCS.pm +++ b/lib/Elevate/Components/CCS.pm @@ -61,7 +61,6 @@ sub pre_leapp ($self) { } sub clean_up_pkg_cruft ($self) { - $self->move_pgsql_directory(); $self->remove_cpanel_ccs_home_directory(); return; } @@ -79,60 +78,12 @@ sub remove_cpanel_ccs_home_directory ($self) { return; } -=head1 move_pgsql_directory - -Removing the PKG will leave this directory in place -This results in PostGreSQL/CCS failing to start after leapp completes - -=cut - -sub move_pgsql_directory ($self) { - my $pg_dir = '/var/lib/pgsql'; - my $pg_backup_dir = '/var/lib/pgsql_pre_elevate'; - - # Remove the backup path if it exists as a directory - File::Path::remove_tree($pg_backup_dir) if -e $pg_backup_dir && -d $pg_backup_dir; - - # If we were unable to remove the backup path above, then change it to something that - # should be unique - $pg_backup_dir .= '_' . time() . '_' . $$ if -e $pg_backup_dir; - - # Make sure the path that should be unique does not exist - File::Path::remove_tree($pg_backup_dir) if -e $pg_backup_dir && -d $pg_backup_dir; - - # Give it up if we still do not have a candidate to back the data up to - if ( -e $pg_backup_dir ) { - die <<~"EOS"; - Unable to ensure a valid backup path for $pg_dir. - Please ensure that '/var/lib/pgsql_pre_elevate' does not exist on your system and execute this script again with - - /scripts/elevate-cpanel --continue - - EOS - } - - INFO( <<~"EOS" ); - Moving the PostgreSQL data dir located at $pg_dir to $pg_backup_dir - to ensure a functioning PostgreSQL server after the elevation completes. - EOS - - File::Copy::move( $pg_dir, $pg_backup_dir ) if -d $pg_dir; - - return; -} - sub remove_ccs_and_dependencies ($self) { my $zpush_installed = Cpanel::Pkgr::is_installed(ZPUSH_PACKAGE); Elevate::StageFile::update_stage_file( { zpush_installed => $zpush_installed } ); - # There are other dependencies but these are the 3 that we are concerned with - my @ccs_dependencies = qw{ - postgresql - postgresql-devel - postgresql-server - }; - + my @ccs_dependencies; push @ccs_dependencies, ZPUSH_PACKAGE(); $self->yum->remove( CCS_PACKAGE(), @ccs_dependencies ); @@ -324,6 +275,8 @@ sub _ensure_export_directory ($self) { sub post_leapp ($self) { return unless Elevate::StageFile::read_stage_file('ccs_installed'); + $self->run_once('move_pgsql_directory'); + $self->_install_ccs_and_dependencies(); # This needs to happen before verifying that the service is up @@ -334,6 +287,8 @@ sub post_leapp ($self) { $self->_ensure_ccs_service_is_up(); $self->run_once('import_ccs_data'); + $self->move_pgsql_directory_back(); + return; } @@ -458,4 +413,63 @@ sub _import_data_for_single_user ( $self, $user ) { return; } +=head1 move_pgsql_directory + +Because CCS re-uses scripts originally intended for use with the system +Postgres, leaving the system Postgres data directory in place sometimes results +in failure to apply schema updates to CCS, causing failures in importing user +data. This is technically a bug in CCS, but we will not be issuing a fix for +that. Instead, when needed, the system Postgres database shall be moved aside, +ensuring that all and only CCS processes apply to that instance of Postgres. + +=cut + +sub move_pgsql_directory ($self) { + my $pg_dir = '/var/lib/pgsql'; + my $pg_backup_dir = '/var/lib/pgsql_pre_elevate'; + + return unless -d $pg_dir; + + # Remove the backup path if it exists as a directory + File::Path::remove_tree($pg_backup_dir) if -e $pg_backup_dir && -d $pg_backup_dir; + + INFO( <<~"EOS" ); + Temporarily moving the PostgreSQL data dir located at $pg_dir to $pg_backup_dir + due to issues with the CCS upgrade process. This script will attempt to move the directory + back to $pg_dir after CCS is upgraded. + EOS + + # Using rename instead of File::Copy::move, because allowing copy+delete behavior introduces too many points of failure: + my $success = rename( $pg_dir, $pg_backup_dir ); + LOGDIE(qq[The system failed to move $pg_dir to $pg_backup_dir (reason: $!)!]) unless $success; + + return; +} + +sub move_pgsql_directory_back ($self) { + my $pg_dir = '/var/lib/pgsql'; + my $pg_backup_dir = '/var/lib/pgsql_pre_elevate'; + + return unless -e $pg_backup_dir; + + INFO(qq[Restoring system PostgreSQL instance...]); + + Elevate::SystemctlService->new( name => 'postgresql' )->stop(); # just in case + File::Path::remove_tree($pg_dir) if -e $pg_dir; + + my $result = rename( $pg_backup_dir, $pg_dir ); + if ( !$result ) { + my $msg = <<~"EOS"; + The system could not fully restore $pg_backup_dir to $pg_dir (reason: $!). + Restore this manually, and perform the update as recommended. + EOS + + LOGDIE($msg); + return; + } + + INFO(qq[The system returned the PostgreSQL data directory to $pg_dir.]); + return; +} + 1; diff --git a/lib/Elevate/Components/PostgreSQL.pm b/lib/Elevate/Components/PostgreSQL.pm new file mode 100644 index 00000000..018d69e4 --- /dev/null +++ b/lib/Elevate/Components/PostgreSQL.pm @@ -0,0 +1,394 @@ +package Elevate::Components::PostgreSQL; + +=encoding utf-8 + +=head1 NAME + +Elevate::Components::PostgreSQL + +=head1 DESCRIPTION + +Upgrades the system PostgreSQL instance. + +This is considered to be a best-effort task: if something fails in an expected +way, it will emit errors and notify the user but will B terminate the +ELevate process or otherwise cause it to be considered an overall failure. + +=cut + +use cPstrict; + +use Simple::Accessor qw{service}; + +use parent qw{Elevate::Components::Base}; + +use Elevate::Constants (); +use Elevate::Notify (); +use Elevate::StageFile (); +use Elevate::SystemctlService (); + +use Cpanel::Pkgr (); +use Cpanel::SafeRun::Object (); +use Cpanel::Services::Enabled (); +use Whostmgr::Postgres (); + +use Log::Log4perl qw(:easy); + +use File::Copy::Recursive (); +use File::Slurp (); + +sub _build_service ($self) { + return Elevate::SystemctlService->new( name => 'postgresql' ); +} + +=head1 BEFORE LEAPP + +=over + +=cut + +sub pre_leapp ($self) { + if ( Cpanel::Pkgr::is_installed('postgresql-server') ) { + $self->_store_postgresql_encoding_and_locale(); + $self->_disable_postgresql_service(); + $self->_backup_postgresql_datadir(); + } + + return; +} + +=item _store_postgresql_encoding_and_locale + +Fetch and record the encoding and immutable locale data of the C +database. This information is needed later to avoid RE-637. If this process +fails, something is probably wrong, but keep going anyway. + +=cut + +sub _store_postgresql_encoding_and_locale ($self) { + + return if $self->_gave_up_on_postgresql; # won't hurt + + INFO("Fetching encoding and locale information from PostgreSQL."); + + my $is_active_prior = $self->service->is_active; + + # PostgreSQL needs to be up to get this information: + $self->service->start(); + + if ( $self->service->is_active ) { + my $psql_sro = Cpanel::SafeRun::Object->new( + program => '/usr/bin/psql', + args => [ + qw{-F | -At -U postgres -c}, + q{SELECT pg_encoding_to_char(encoding), datcollate, datctype FROM pg_catalog.pg_database WHERE datname = 'template1'}, + ], + ); + + if ( $psql_sro->CHILD_ERROR ) { + WARN("The system instance of PostgreSQL did not return information about the encoding and locale of core databases."); + WARN("ELevate will assume the system defaults and attempt an upgrade anyway."); + return; + } + + my $output = $psql_sro->stdout; + chomp $output; + + my ( $encoding, $collation, $ctype ) = split /\|/, $output; + Elevate::StageFile::update_stage_file( + { + postgresql_locale => { + encoding => $encoding, + collation => $collation, + ctype => $ctype, + } + } + ); + + $self->service->stop() unless $is_active_prior; + } + else { + WARN("The system instance of PostgreSQL could not start to give information about the encoding and locale of core databases."); + WARN("ELevate will assume the system defaults and attempt an upgrade anyway."); + } + + return; +} + +=item _disable_postgresql_service + +Touches the file needed to get Service Manager to believe that PostgreSQL is +disabled. If the service is enabled, then upcp fails during the post-leapp +phase of the RpmDB component. Also doubles as the mechanism by which PostgreSQL +is disabled on a system where a step prior to the upgrade fails. + +=cut + +sub _disable_postgresql_service ($self) { + + if ( Cpanel::Services::Enabled::is_enabled('postgresql') ) { + Elevate::StageFile::update_stage_file( { 're-enable_postgresql_in_sm' => 1 } ); + Cpanel::Services::Enabled::touch_disable_file('postgresql'); + } + + return; +} + +=item _backup_postgresql_datadir + +While the user should have backed up their system, as a convenience and assurance, take our own backup. + +TODO: What happens if this runs out of disk space? Should we check first? + +=cut + +# XXX Is this really better than `cp -a $src $dst`? I hope nothing in there is owned by someone other than the postgres user... +sub _backup_postgresql_datadir ($self) { + + $self->service->stop() if $self->service->is_active; # for safety + + my $pgsql_datadir_path = Elevate::Constants::POSTGRESQL_SYSTEM_DATADIR; + my $pgsql_datadir_backup_path = $pgsql_datadir_path . '_elevate_' . time() . '_' . $$; + + INFO("Backing up the system PostgreSQL data directory at $pgsql_datadir_path to $pgsql_datadir_backup_path."); + + # Set EUID/EGID to postgres (this is not security critical; it's just to retain correct ownership within copy): + my ( $uid, $gid ) = ( scalar( getpwnam('postgres') ), scalar( getgrnam('postgres') ) ); + my $outcome = 0; + { + local ( $>, $) ) = ( $uid, "$gid $gid" ); + $outcome = File::Copy::Recursive::dircopy( $pgsql_datadir_path, $pgsql_datadir_backup_path ); + } + + if ( !$outcome ) { + ERROR("The system encountered an error while trying to make a backup."); + $self->_give_up_on_postgresql(); + } + else { + Elevate::Notify::add_final_notification( <<~"EOS" ); + ELevate backed up your system PostgreSQL data directory to $pgsql_datadir_backup_path + prior to any modification or attempt to upgrade, in case the upgrade needs to be performed + manually, or if old settings need to be referenced. + EOS + } + + return; +} + +=back + +=head1 AFTER LEAPP + +=over + +=cut + +sub post_leapp ($self) { + if ( Cpanel::Pkgr::is_installed('postgresql-server') ) { + $self->_perform_config_workaround(); + $self->_perform_postgresql_upgrade(); + $self->_re_enable_service_if_needed(); + $self->_run_whostmgr_postgres_update_config(); + } + + return; +} + +=item _perform_config_workaround + +There is a bug with the EL8 postgresql-upgrade package, namely that the support +for the C configuration directive which was +back-ported from 9.3 into the 9.2 package for EL7 is not being applied to the +version of 9.2 being built to support the upgrade. For this reason, we need to +alter the existing F file to comment out any +C directives and add a standard +C directive at the end instead. + +=cut + +sub _perform_config_workaround ($self) { + return if $self->_gave_up_on_postgresql; + + my $pgconf_path = Elevate::Constants::POSTGRESQL_SYSTEM_DATADIR . '/postgresql.conf'; + return unless -e $pgconf_path; # if postgresql.conf isn't there, there's nothing to work around + + my $pgconf = eval { File::Slurper::read_text($pgconf_path) }; + if ($@) { + ERROR("Attempting to read $pgconf_path resulted in an error: $@"); + $self->_give_up_on_postgresql(); + return; + } + + my $changed = 0; + my @lines = split "\n", $pgconf; + foreach my $line (@lines) { + next if $line =~ m/^\s*$/a; + if ( $line =~ m/^\s*unix_socket_directories/ ) { + $line = "#$line"; + $changed = 1; + } + } + + if ($changed) { + push @lines, "unix_socket_directory = '/var/run/postgresql'"; + + INFO("Modifying $pgconf_path to work around a defect in the system's PostgreSQL upgrade package."); + + my $pgconf_altered = join "\n", @lines; + eval { File::Slurper::write_text( $pgconf_path, $pgconf_altered ) }; + + if ($@) { + ERROR("Attempting to overwrite $pgconf_path resulted in an error: $@"); + $self->_give_up_on_postgresql(); + } + } + + return; +} + +=item _perform_postgresql_upgrade + +Performs the upgrade using the EL-provided C +script. If encoding and locale data were collected in the pre-leapp phase, use +them here when provisioning the new cluster. + +=cut + +sub _perform_postgresql_upgrade ($self) { + return if $self->_gave_up_on_postgresql; + + INFO("Installing PostgreSQL update package:"); + $self->dnf->install('postgresql-upgrade'); + + my $opts = Elevate::StageFile::read_stage_file('postgresql_locale'); + my @args; + push @args, "--encoding=$opts->{encoding}" if $opts->{encoding}; + push @args, "--lc-collate=$opts->{collation}" if $opts->{collation}; + push @args, "--lc-ctype=$opts->{ctype}" if $opts->{ctype}; + + local $ENV{PGSETUP_INITDB_OPTIONS} = join ' ', @args if scalar @args > 0; + + INFO("Upgrading PostgreSQL data directory:"); + my $outcome = $self->ssystem( { keep_env => ( scalar @args > 0 ? 1 : 0 ) }, qw{/usr/bin/postgresql-setup --upgrade} ); + + if ( $outcome == 0 ) { + INFO("The PostgreSQL upgrade process was successful."); + Elevate::Notify::add_final_notification( <<~EOS ); + ELevate successfully ran the upgrade procedure on the system instance of + PostgreSQL. If no problems are reported with configuring the upgraded instance + to work with cPanel, you should proceed with applying any relevant + customizations to the configuration and authentication settings, since the + upgrade process reset this information to system defaults. + EOS + } + else { + ERROR("The upgrade attempt of the system PostgreSQL instance failed. See the log files mentioned in the output of postgresql-setup for more information."); + $self->_give_up_on_postgresql(); + } + + return; +} + +=item _re_enable_service_if_needed + +If the upgrade was successful, and we disabled the PostgreSQL service in Service Manager, re-enable the service now. + +=cut + +sub _re_enable_service_if_needed ($self) { + return if $self->_gave_up_on_postgresql; # keep disabled if there was a failure + + if ( Elevate::StageFile::read_stage_file( 're-enable_postgresql_in_sm', 0 ) ) { + Cpanel::Services::Enabled::remove_disable_files('postgresql'); + } + + return; +} + +=item _run_whostmgr_postgres_update_config + +The upgrade completely reset the PostgreSQL configuration and authentication +methods, so this is the ideal time to invoke the WHM code to correctly +configure this for use with phpPgAdmin in cPanel. + +This process happens I the upgrade, so a failure here probably should +B result in skipping following steps, if any are added in the future. + +=cut + +sub _run_whostmgr_postgres_update_config ($self) { + return if $self->_gave_up_on_postgresql; + + INFO("Configuring PostgreSQL to work with cPanel's installation of phpPgAdmin."); + $self->service->start(); # less noisy when it's online, but still works + + my ( $success, $msg ) = Whostmgr::Postgres::update_config(); + if ( !$success ) { + ERROR("The system failed to update the PostgreSQL configuration: $msg"); + Elevate::Notify::add_final_notification( <<~EOS ); + ELevate could not configure the upgraded system PostgreSQL instance to work + with cPanel. See the log for additional information. Once the issue has been + addressed, perform this step manually using the "Postgres Config Install" area + in WHM: + + https://go.cpanel.net/whmdocsConfigurePostgreSQL + + Do this before restoring any customizations to PostgreSQL configuration or + authentication files, since performing this action resets these to cPanel + defaults. + EOS + } + + return; +} + +=back + +=head1 UTILITY METHODS + +=over + +=item _give_up_on_postgresql + +Invoke when all subsequent steps of the PostgreSQL upgrade should be skipped due to a failure. + +=cut + +sub _give_up_on_postgresql ($self) { + ERROR('Skipping attempt to upgrade the system instance of PostgreSQL.'); + Elevate::StageFile::update_stage_file( { postgresql_give_up => 1 } ); + Elevate::Notify::add_final_notification( <<~EOS ); + The process of upgrading the system instance of PostgreSQL failed. The + PostgreSQL service has been disabled in the Service Manager in WHM: + + https://go.cpanel.net/whmdocsServiceManager + + If you do not have cPanel users who use PostgreSQL or otherwise do not use it, + you do not have to take any action. Otherwise, see the ELevate logs for further + information. + EOS + return; +} + +=item _gave_up_on_postgresql + +=item _given_up_on_postgresql + +Returns true if an error has prompted us to skip the upgrade. + +=cut + +sub _gave_up_on_postgresql ($self) { + return Elevate::StageFile::read_stage_file( 'postgresql_give_up', 0 ); +} + +# alias +sub _given_up_on_postgresql { + goto &_gave_up_on_postgresql; +} + +=back + +=cut + +1; diff --git a/lib/Elevate/Constants.pm b/lib/Elevate/Constants.pm index 88c05119..80417bd2 100644 --- a/lib/Elevate/Constants.pm +++ b/lib/Elevate/Constants.pm @@ -52,4 +52,6 @@ use constant R1SOFT_AGENT_PACKAGES => qw{ serverbackup-setup }; +use constant POSTGRESQL_SYSTEM_DATADIR => '/var/lib/pgsql/data'; + 1; diff --git a/lib/Elevate/SystemctlService.pm b/lib/Elevate/SystemctlService.pm index 391409e2..db22941f 100644 --- a/lib/Elevate/SystemctlService.pm +++ b/lib/Elevate/SystemctlService.pm @@ -83,6 +83,15 @@ sub remove ($self) { return; } +sub start ($self) { + + return if $self->is_active; + + $self->ssystem( '/usr/bin/systemctl', 'start', $self->name ); + + return; +} + sub stop ($self) { return unless $self->is_active; @@ -106,4 +115,18 @@ sub disable ( $self, %opts ) { return; } +sub enable ( $self, %opts ) { + + return if $self->is_enabled; + + my $now = $opts{'now'} // 1; # by default enable it now... + + my @args = qw{ enable }; + push @args, '--now' if $now; + + $self->ssystem( '/usr/bin/systemctl', @args, $self->name ); + + return; +} + 1; diff --git a/script/elevate-cpanel.PL b/script/elevate-cpanel.PL index 711d9ca1..a8b29761 100755 --- a/script/elevate-cpanel.PL +++ b/script/elevate-cpanel.PL @@ -279,6 +279,7 @@ use Elevate::Components::NICs (); use Elevate::Components::NixStats (); use Elevate::Components::PECL (); use Elevate::Components::PerlXS (); +use Elevate::Components::PostgreSQL (); use Elevate::Components::UnconvertedModules (); use Elevate::Components::Repositories (); use Elevate::Components::RpmDB (); @@ -1320,6 +1321,7 @@ sub run_final_components_pre_leapp ($self) { # order matters $self->run_component_once( 'RmMod' => 'pre_leapp' ); $self->run_component_once( 'MySQL' => 'pre_leapp' ); + $self->run_component_once( 'PostgreSQL' => 'pre_leapp' ); $self->run_component_once( 'Repositories' => 'pre_leapp' ); $self->run_component_once( 'cPanelPlugins' => 'pre_leapp' ); $self->run_component_once( 'PerlXS' => 'pre_leapp' ); @@ -1348,6 +1350,7 @@ sub post_leapp_update_restore ($self) { # plugins can use MySQL - restore database earlier $self->run_component_once( 'MySQL' => 'post_leapp' ); $self->run_component_once( 'PerlXS' => 'post_leapp' ); + $self->run_component_once( 'PostgreSQL' => 'post_leapp' ); $self->run_component_once( 'cPanelPlugins' => 'post_leapp' ); $self->run_component_once( 'DatabaseUpgrade' => 'post_leapp' ); $self->run_component_once( 'PECL' => 'post_leapp' ); diff --git a/t/blocker-Databases.t b/t/blocker-Databases.t index 5efeade9..00565658 100644 --- a/t/blocker-Databases.t +++ b/t/blocker-Databases.t @@ -147,178 +147,6 @@ my $mock_elevate = Test::MockFile->file('/var/cpanel/elevate'); ); } -{ - note "Postgresql 9.6/CCS"; - - clear_messages_seen(); - - for my $os ( 'cent', 'cloud' ) { - set_os_to($os); - - my $expected_target_os = $os eq 'cent' ? 'AlmaLinux 8' : 'CloudLinux 8'; - - my $pkgr_mock = Test::MockModule->new('Cpanel::Pkgr'); - my %installed = ( 'cpanel-ccs-calendarserver' => 9.2, 'postgresql-server' => 9.2 ); - $pkgr_mock->redefine( 'is_installed' => sub ($rpm) { return defined $installed{$rpm} ? 1 : 0 } ); - $pkgr_mock->redefine( 'get_package_version' => sub ($rpm) { return $installed{$rpm} } ); - - is( $db->_warning_if_postgresql_installed, 2, "pg 9 is installed" ); - message_seen( 'WARN', "You have postgresql-server version 9.2 installed. This will be upgraded irreversibly to version 10.0 when you switch to $expected_target_os" ); - - $installed{'postgresql-server'} = '10.2'; - is( $db->_warning_if_postgresql_installed, 1, "pg 10 is installed so no warning" ); - no_messages_seen(); - - $installed{'postgresql-server'} = 'an_unexpected_version'; - is( $db->_warning_if_postgresql_installed, 1, "unknown pg version is installed so no warning" ); - no_messages_seen(); - - delete $installed{'postgresql-server'}; - is( $db->_warning_if_postgresql_installed, 0, "pg is not installed so no warning" ); - no_messages_seen(); - } - -} - -{ - note "PostgreSQL 9.2->10 acknowledgement"; - - my $pkgr_mock = Test::MockModule->new('Cpanel::Pkgr'); - my %installed = ( 'postgresql-server' => 9.2 ); - $pkgr_mock->redefine( 'is_installed' => sub ($rpm) { return defined $installed{$rpm} ? 1 : 0 } ); - $pkgr_mock->redefine( 'get_package_version' => sub ($rpm) { return $installed{$rpm} } ); - - my $mock_touchfile = Test::MockFile->file('/var/cpanel/acknowledge_postgresql_for_elevate'); - - my $db_mock = Test::MockModule->new('Elevate::Blockers::Databases'); - - my @mock_users = qw(cpuser1 cpuser2); - $db_mock->redefine( _has_mapped_postgresql_dbs => sub { return @mock_users } ); - - my $expected = { - id => q[Elevate::Blockers::Databases::_blocker_acknowledge_postgresql_datadir], - msg => <<~'EOS' - One or more users on your system have associated PostgreSQL databases. - ELevate may upgrade the software packages associated with PostgreSQL - automatically, but if it does, it will *NOT* automatically update the - PostgreSQL data directory to work with the new version. Without an update - to the data directory, the upgraded PostgreSQL software will not start, in - order to ensure that your data does not become corrupted. - - For more information about PostgreSQL upgrades, please consider the - following resources: - - https://cpanel.github.io/elevate/blockers/#postgresql - https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/deploying_different_types_of_servers/using-databases#migrating-to-a-rhel-8-version-of-postgresql_using-postgresql - https://www.postgresql.org/docs/10/pgupgrade.html - - When you are ready to acknowledge that you have prepared to update the - PostgreSQL data directory, or that this warning does not apply to you, - please touch the following file to continue with the ELevate process: - - > touch /var/cpanel/acknowledge_postgresql_for_elevate - - The following user(s) have PostgreSQL databases associated with their cPanel accounts: - cpuser1 - cpuser2 - EOS - }; - chomp $expected->{msg}; - is( - $db->_blocker_acknowledge_postgresql_datadir(), - $expected, - "PostgreSQL with cPanel users having databases and without ACK touch file is a blocker" - ); - - %installed = (); - is( $db->_blocker_acknowledge_postgresql_datadir(), 0, "No blocker if no postgresql-server package" ); - %installed = ( 'postgresql-server' => 9.2 ); - - $mock_touchfile->touch(); - is( $db->_blocker_acknowledge_postgresql_datadir(), 0, "No blocker if touch file present" ); - $mock_touchfile->unlink(); - - @mock_users = (); - is( $db->_blocker_acknowledge_postgresql_datadir(), 0, "No blocker if no users have PgSQL DBs" ); - @mock_users = qw(cpuser1 cpuser2); -} - -{ - note "check for PostgreSQL databases"; - - # This keeps Cpanel::Exception::get_string() from causing errors - # when trying to access locale files - local $Cpanel::Exception::LOCALIZE_STRINGS = 0; - - # Cpanel::Transaction::File::JSONReader will do some low-level file - # calls that get around Test::MockFile. This is needed to ensure that the - # contents of the mocked dbindex file are read rather than the real one. - my $mock_reader = Test::MockModule->new("Cpanel::Transaction::File::JSONReader"); - $mock_reader->redefine( - new => sub ( $class, %opts ) { - my $self = {}; - bless $self, 'Cpanel::Transaction::File::JSONReader'; - $self->{path} = $opts{path}; - return $self; - }, - get_data => sub ($self) { - my $contents = read_text( $self->{path} ); - return undef if !defined $contents; - - my $json_obj = JSON::XS->new->ascii->pretty->allow_nonref; - my $hr = $json_obj->decode($contents); - return $hr; - } - ); - - my $mock_dbindex_file = Test::MockFile->file( Cpanel::DB::Map::Collection::Index::_cache_file_path() ); - - # There should be an error for a bogus dbindex file. - $mock_dbindex_file->contents('this is not json'); - clear_messages_seen(); - is( [ $db->_has_mapped_postgresql_dbs() ], [], 'Bogus index file returns no users' ); - message_seen( 'ERROR', qr{Unable to read the database index file.*this is not json} ); - message_seen( 'WARN', qr{Elevation Blocker detected.*rebuild it by running:\s+/usr/local/cpanel/bin/dbindex}s ); - - $mock_dbindex_file->contents( <<~EOS ); - { - "PGSQL": { - "pgdb_01": "pgdb_user_01", - "pgdb_02": "pgdb_user_01", - "pgdb_03": "pgdb_user_01", - "pgdb_04": "pgdb_user_02", - "pgdb_05": "pgdb_user_02", - "pgdb_06": "pgdb_user_02", - "pgdb_07": "pgdb_user_03", - "pgdb_08": "pgdb_user_03", - "pgdb_09": "pgdb_user_03", - "pgdb_10": "pgdb_user_04" - }, - "MYSQL": { - "mysqldb_01": "cpuser_01", - "mysqldb_02": "cpuser_01", - "mysqldb_03": "cpuser_02", - "mysqldb_04": "cpuser_02", - "mysqldb_05": "cpuser_02" - } - } - EOS - - is( - [ $db->_has_mapped_postgresql_dbs() ], - bag { - item 'pgdb_user_01'; - item 'pgdb_user_02'; - item 'pgdb_user_03'; - item 'pgdb_user_04'; - end(); - }, - "_has_mapped_postgresql_dbs returns expected list of users" - ); - - no_messages_seen(); -} - { note 'Test CloudLinux MySQL blocker'; set_os_to('cloud'); diff --git a/t/components-CCS.t b/t/components-CCS.t index 70ac3782..eceb7403 100644 --- a/t/components-CCS.t +++ b/t/components-CCS.t @@ -77,7 +77,7 @@ my $mock_stagefile = Test::MockModule->new('Elevate::StageFile'); is( \@ssystem_and_die_params, - [qw{ /usr/bin/yum -y remove cpanel-ccs-calendarserver postgresql postgresql-devel postgresql-server cpanel-z-push }], + [qw{ /usr/bin/yum -y remove cpanel-ccs-calendarserver cpanel-z-push }], 'The expected packages were removed' ); @@ -114,7 +114,9 @@ my $mock_stagefile = Test::MockModule->new('Elevate::StageFile'); return; }, _ensure_ccs_service_is_up => 0, - run_once => sub { $ccs->import_ccs_data(); }, + move_pgsql_directory => 0, + move_pgsql_directory_back => 0, + run_once => sub { $_[0]->can( $_[1] )->( $_[0] ) }, _import_data_for_single_user => sub ( $self, $user ) { push @called_for_user, $user; }, ); diff --git a/t/components-PostgreSQL.t b/t/components-PostgreSQL.t new file mode 100644 index 00000000..250277fe --- /dev/null +++ b/t/components-PostgreSQL.t @@ -0,0 +1,95 @@ +#!/usr/local/cpanel/3rdparty/bin/perl + +# Copyright 2024 WebPros International, LLC +# All rights reserved. +# copyright@cpanel.net http://cpanel.net +# This code is subject to the cPanel license. Unauthorized copying is prohibited. + +package test::cpev::PostgreSQL; + +use FindBin; + +use Test2::V0; +use Test2::Tools::Explain; +use Test2::Plugin::NoWarnings; +use Test2::Tools::Exception; + +use Test::MockModule qw/strict/; +use Test::MockFile; + +use lib $FindBin::Bin . "/lib"; +use Test::Elevate; + +use cPstrict; + +use Elevate::Constants (); + +use constant PRE_LEAPP_METHODS => [ + qw( + _store_postgresql_encoding_and_locale + _disable_postgresql_service + _backup_postgresql_datadir + ) +]; + +use constant POST_LEAPP_METHODS => [ + qw( + _perform_config_workaround + _perform_postgresql_upgrade + _re_enable_service_if_needed + _run_whostmgr_postgres_update_config + ) +]; + +my $comp_pgsql = bless {}, 'Elevate::Components::PostgreSQL'; + +my $mock_pgsql = Test::MockModule->new('Elevate::Components::PostgreSQL'); +my $mock_stagefile = Test::MockModule->new('Elevate::StageFile'); + +my $mock_pkgr = Test::MockModule->new('Cpanel::Pkgr'); +my $installed; +$mock_pkgr->redefine( + is_installed => sub { return $_[0] eq 'postgresql-server' ? $installed : $mock_pkgr->original('postgresql-server')->(@_) }, +); + +{ + note "Checking pre_leapp"; + + $installed = 0; + $mock_pgsql->redefine( + map { + $_ => sub { die "shouldn't run" } + } PRE_LEAPP_METHODS->@* + ); + ok( lives { $comp_pgsql->pre_leapp() }, "Nothing is run if postgresql-server is not installed" ); + $mock_pgsql->unmock( PRE_LEAPP_METHODS->@* ); + + $installed = 1; +} + +{ + note "Checking post_leapp"; + + $installed = 0; + $mock_pgsql->redefine( + map { + $_ => sub { die "shouldn't run" } + } POST_LEAPP_METHODS->@* + ); + ok( lives { $comp_pgsql->post_leapp() }, "Nothing is run if postgresql-server is not installed" ); + $mock_pgsql->unmock( POST_LEAPP_METHODS->@* ); + + $installed = 1; + + my $mock_pg_conf = Test::MockFile->file( Elevate::Constants::POSTGRESQL_SYSTEM_DATADIR . '/postgresql.conf' ); + + $mock_pg_conf->contents("no matches"); + $comp_pgsql->_perform_config_workaround(); + is( $mock_pg_conf->contents, "no matches", "Nothing changes with postgresql.conf if no unix_socket_directories" ); + + $mock_pg_conf->contents("unix_socket_directories = '/var/run/postgresql, /tmp'"); + $comp_pgsql->_perform_config_workaround(); + is( $mock_pg_conf->contents, "#unix_socket_directories = '/var/run/postgresql, /tmp'\nunix_socket_directory = '/var/run/postgresql'", "Fix applied if unix_socket_directories is present" ); +} + +done_testing();