From 5ef870590e4960e6cbf478c957ab9e6d2f50df90 Mon Sep 17 00:00:00 2001 From: Stephen Bee Date: Thu, 2 May 2024 12:47:18 -0500 Subject: [PATCH] Unblock remote MySQL blocker by temporarily disabling during upgrade. Case RE-34: Changelog: Unblock remote MySQL blocker by temporarily disabling the remote profile during the upgrade. --- elevate-cpanel | 203 ++++++++++++++--- lib/Elevate/Blockers/Databases.pm | 19 -- lib/Elevate/Components/DatabaseUpgrade.pm | 166 +++++++++++++- script/elevate-cpanel.PL | 24 +- t/blocker-Databases.t | 28 --- t/components-DatabaseUpgrade.t | 258 ++++++++++++++++++++++ 6 files changed, 610 insertions(+), 88 deletions(-) create mode 100644 t/components-DatabaseUpgrade.t diff --git a/elevate-cpanel b/elevate-cpanel index 36ebb194d..ff9bd6e6a 100755 --- a/elevate-cpanel +++ b/elevate-cpanel @@ -592,7 +592,6 @@ BEGIN { # Suppress load of all of these at earliest point. use Cpanel::SafeRun::Simple (); use Cpanel::DB::Map::Collection::Index (); use Cpanel::Exception (); - use Cpanel::MysqlUtils::MyCnf::Basic (); # use Elevate::Blockers::Base(); our @ISA; @@ -607,7 +606,6 @@ BEGIN { # Suppress load of all of these at earliest point. my $ok = 1; $self->_warning_if_postgresql_installed; $ok = 0 unless $self->_blocker_acknowledge_postgresql_datadir; - $ok = 0 unless $self->_blocker_remote_mysql; $ok = 0 unless $self->_blocker_old_mysql; $ok = 0 unless $self->_blocker_mysql_upgrade_in_progress; $self->_warning_mysql_not_enabled(); @@ -680,21 +678,6 @@ BEGIN { # Suppress load of all of these at earliest point. return ( keys %user_hash ); } - sub _blocker_remote_mysql ($self) { - - my $pretty_distro_name = $self->upgrade_to_pretty_name(); - - if ( Cpanel::MysqlUtils::MyCnf::Basic::is_remote_mysql() ) { - return $self->has_blocker( <<~"EOS" ); - The system is currently setup to use a remote database server. - We cannot elevate the system to $pretty_distro_name - unless the system is configured to use the local database server. - EOS - } - - return 0; - } - sub _blocker_old_mysql ($self) { my $mysql_is_provided_by_cloudlinux = Elevate::Database::is_database_provided_by_cloudlinux(0); @@ -5456,6 +5439,20 @@ EOS our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } + use Cpanel::MysqlUtils::RemoteMySQL::ProfileManager (); + + use File::Slurper; + use Try::Tiny; + + use Cpanel::MysqlUtils::MyCnf::Basic (); + use Cpanel::MysqlUtils::RemoteMySQL::ProfileManager (); + use Cpanel::PasswdStrength::Generate (); + use Cpanel::JSON (); + use Cpanel::SafeRun::Simple (); + use Cpanel::Encoder::URI (); + + use constant MYSQL_PROFILE_FILE => '/var/cpanel/elevate-mysql-profile'; + # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } @@ -5465,6 +5462,8 @@ EOS return if Elevate::Database::is_database_version_supported( Elevate::Database::get_local_database_version() ); + $self->_ensure_localhost_mysql_profile_is_active(1); + Elevate::Database::upgrade_database_server(); return; @@ -5472,6 +5471,152 @@ EOS sub post_leapp ($self) { + if ( -e MYSQL_PROFILE_FILE ) { + my $original_profile = File::Slurper::read_text(MYSQL_PROFILE_FILE) // 'localhost'; + INFO(qq{Reactivating "$original_profile" MySQL profile}); + + my $output = $self->ssystem_capture_output( '/usr/local/cpanel/scripts/manage_mysql_profiles', '--activate', "$original_profile" ); + my $stdout = join qq{\n}, @{ $output->{'stdout'} }; + + unless ( $stdout =~ m{MySQL profile activation done} ) { + die <<~"EOS"; + Unable to reactivate the original remote MySQL profile "$original_profile": + + $stdout + + Please resolve the reported problems then run this script again with: + + /scripts/elevate-cpanel --continue + + EOS + } + + unlink MYSQL_PROFILE_FILE; + } + + return; + } + + sub _ensure_localhost_mysql_profile_is_active ( $self, $should_create_localhost_profile ) { + + if ( Cpanel::MysqlUtils::MyCnf::Basic::is_local_mysql() ) { + return; + } + + my $profile_manager = Cpanel::MysqlUtils::RemoteMySQL::ProfileManager->new(); + + try { + $profile_manager->validate_profile('localhost'); + $self->_activate_localhost_profile($profile_manager); + } + + catch { + if ($should_create_localhost_profile) { + INFO("Attempting to create new localhost MySQL profile..."); + $self->_create_new_localhost_profile($profile_manager); + $self->_ensure_localhost_mysql_profile_is_active(0); + } + else { + die "Unable to generate/enable localhost MySQL profile: $_\n"; + } + }; + + return; + } + + sub _create_new_localhost_profile ( $self, $profile_manager ) { + + my $active_profile = $profile_manager->get_active_profile('dont_die'); + File::Slurper::write_text( MYSQL_PROFILE_FILE, $active_profile ); + + my $password = Cpanel::PasswdStrength::Generate::generate_password( 16, no_othersymbols => 1 ); + + try { + $profile_manager->create_profile( + { + 'name' => 'localhost', + 'mysql_user' => 'root', + 'mysql_pass' => $password, + 'mysql_host' => 'localhost', + 'mysql_port' => Cpanel::MysqlUtils::MyCnf::Basic::getmydbport('root') || 3306, + 'setup_via' => 'Auto-generated localhost profile during elevate.', + }, + { 'overwrite' => 1 }, + ); + } + catch { + die <<~"EOS"; + Unable to generate a functioning MySQL DB profile for the local MySQL server. + + The following error was encountered: + + $@ + EOS + }; + + $self->_set_local_mysql_root_password($password); + + $profile_manager->save_changes_to_disk(); + + $self->_activate_localhost_profile($profile_manager); + + return; + } + + sub _set_local_mysql_root_password ( $self, $password ) { + INFO("Resetting password for local root MySQL user..."); + + my $encoded_password = Cpanel::Encoder::URI::uri_encode_str($password); + + my $output = Cpanel::SafeRun::Simple::saferunnoerror( q{/bin/sh}, q{-c}, qq{/usr/local/cpanel/bin/whmapi1 --output=json set_local_mysql_root_password password='$encoded_password'} ); + my $result = eval { Cpanel::JSON::Load($output); } // {}; + + unless ( $result->{metadata}{result} ) { + + my $errors = join qq{\n\n}, @{ $result->{'metadata'}{'errors'} }; + + die <<~"EOS"; + Unable to set root password for the localhost MySQL server. + + The following errors occurred: + + $errors + + Please resolve the reported problems then run this script again with: + + /scripts/elevate-cpanel --continue + + EOS + } + + return; + } + + sub _activate_localhost_profile { + my ( $self, $profile_manager ) = @_; + + if ($profile_manager) { + $profile_manager->{'_transaction_obj'}->close_or_die(); + } + + INFO("Activating “localhost” MySQL profile"); + + my $output = $self->ssystem_capture_output(qw{/usr/local/cpanel/scripts/manage_mysql_profiles --activate localhost}); + my $stdout = join qq{\n}, @{ $output->{'stdout'} }; + + if ( $stdout !~ m{MySQL profile activation done} ) { + die <<~"EOS"; + Unable to activate a MySQL DB profile for "localhost": + + $stdout + + Please resolve the reported problems then run this script again with: + + /scripts/elevate-cpanel --continue + + EOS + } + return; } @@ -9065,17 +9210,19 @@ 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( 'cPanelPlugins' => 'post_leapp' ); - $self->run_component_once( 'PECL' => 'post_leapp' ); - $self->run_component_once( 'WPToolkit' => 'post_leapp' ); - $self->run_component_once( 'InfluxDB' => 'post_leapp' ); - $self->run_component_once( 'JetBackup' => 'post_leapp' ); - $self->run_component_once( 'Kernel' => 'post_leapp' ); - $self->run_component_once( 'KernelCare' => 'post_leapp' ); - $self->run_component_once( 'NixStats' => 'post_leapp' ); - $self->run_component_once( 'LiteSpeed' => 'post_leapp' ); + $self->run_component_once( 'MySQL' => 'post_leapp' ); + $self->run_component_once( 'PerlXS' => 'post_leapp' ); + $self->run_component_once( 'DatabaseUpgrade' => 'post_leapp' ); + $self->run_component_once( 'cPanelPlugins' => 'post_leapp' ); + $self->run_component_once( 'PECL' => 'post_leapp' ); + $self->run_component_once( 'WPToolkit' => 'post_leapp' ); + $self->run_component_once( 'InfluxDB' => 'post_leapp' ); + $self->run_component_once( 'JetBackup' => 'post_leapp' ); + $self->run_component_once( 'Kernel' => 'post_leapp' ); + $self->run_component_once( 'KernelCare' => 'post_leapp' ); + $self->run_component_once( 'Imunify' => 'post_leapp' ); + $self->run_component_once( 'NixStats' => 'post_leapp' ); + $self->run_component_once( 'LiteSpeed' => 'post_leapp' ); return; } diff --git a/lib/Elevate/Blockers/Databases.pm b/lib/Elevate/Blockers/Databases.pm index a7a2f5349..ec42de5f8 100644 --- a/lib/Elevate/Blockers/Databases.pm +++ b/lib/Elevate/Blockers/Databases.pm @@ -22,7 +22,6 @@ use Cpanel::JSON (); use Cpanel::SafeRun::Simple (); use Cpanel::DB::Map::Collection::Index (); use Cpanel::Exception (); -use Cpanel::MysqlUtils::MyCnf::Basic (); use parent qw{Elevate::Blockers::Base}; @@ -34,7 +33,6 @@ sub check ($self) { my $ok = 1; $self->_warning_if_postgresql_installed; $ok = 0 unless $self->_blocker_acknowledge_postgresql_datadir; - $ok = 0 unless $self->_blocker_remote_mysql; $ok = 0 unless $self->_blocker_old_mysql; $ok = 0 unless $self->_blocker_mysql_upgrade_in_progress; $self->_warning_mysql_not_enabled(); @@ -107,23 +105,6 @@ sub _has_mapped_postgresql_dbs ($self) { return ( keys %user_hash ); } -sub _blocker_remote_mysql ($self) { - - my $pretty_distro_name = $self->upgrade_to_pretty_name(); - - # If we are setup to use remote MySQL, then attempting an upgrade will fail - # TODO: Temporarily disable remote MySQL to allow the database upgrade - if ( Cpanel::MysqlUtils::MyCnf::Basic::is_remote_mysql() ) { - return $self->has_blocker( <<~"EOS" ); - The system is currently setup to use a remote database server. - We cannot elevate the system to $pretty_distro_name - unless the system is configured to use the local database server. - EOS - } - - return 0; -} - 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/DatabaseUpgrade.pm b/lib/Elevate/Components/DatabaseUpgrade.pm index bf62a2e65..ff014b9c8 100644 --- a/lib/Elevate/Components/DatabaseUpgrade.pm +++ b/lib/Elevate/Components/DatabaseUpgrade.pm @@ -1,7 +1,6 @@ package Elevate::Components::DatabaseUpgrade; =encoding utf-8 - =head1 NAME Elevate::Components::DatabaseUpgrade @@ -16,6 +15,20 @@ use Elevate::Database (); use parent qw{Elevate::Components::Base}; +use Cpanel::MysqlUtils::RemoteMySQL::ProfileManager (); + +use File::Slurper; +use Try::Tiny; + +use Cpanel::MysqlUtils::MyCnf::Basic (); +use Cpanel::MysqlUtils::RemoteMySQL::ProfileManager (); +use Cpanel::PasswdStrength::Generate (); +use Cpanel::JSON (); +use Cpanel::SafeRun::Simple (); +use Cpanel::Encoder::URI (); + +use constant MYSQL_PROFILE_FILE => '/var/cpanel/elevate-mysql-profile'; + use Log::Log4perl qw(:easy); sub pre_leapp ($self) { @@ -26,6 +39,8 @@ sub pre_leapp ($self) { # If the database version is supported on the new OS version, then no need to upgrade return if Elevate::Database::is_database_version_supported( Elevate::Database::get_local_database_version() ); + $self->_ensure_localhost_mysql_profile_is_active(1); + Elevate::Database::upgrade_database_server(); return; @@ -33,7 +48,154 @@ sub pre_leapp ($self) { sub post_leapp ($self) { - # Nothing to do + if ( -e MYSQL_PROFILE_FILE ) { + my $original_profile = File::Slurper::read_text(MYSQL_PROFILE_FILE) // 'localhost'; + INFO(qq{Reactivating "$original_profile" MySQL profile}); + + my $output = $self->ssystem_capture_output( '/usr/local/cpanel/scripts/manage_mysql_profiles', '--activate', "$original_profile" ); + my $stdout = join qq{\n}, @{ $output->{'stdout'} }; + + unless ( $stdout =~ m{MySQL profile activation done} ) { + die <<~"EOS"; + Unable to reactivate the original remote MySQL profile "$original_profile": + + $stdout + + Please resolve the reported problems then run this script again with: + + /scripts/elevate-cpanel --continue + + EOS + } + + unlink MYSQL_PROFILE_FILE; + } + + return; +} + +sub _ensure_localhost_mysql_profile_is_active ( $self, $should_create_localhost_profile ) { + + if ( Cpanel::MysqlUtils::MyCnf::Basic::is_local_mysql() ) { + return; + } + + my $profile_manager = Cpanel::MysqlUtils::RemoteMySQL::ProfileManager->new(); + + # Validate that the current “localhost” profile exists, and contains valid settings. + try { + $profile_manager->validate_profile('localhost'); + $self->_activate_localhost_profile($profile_manager); + } + + # Otherwise attempt to recreate it, overwriting the existing profile. + catch { + if ($should_create_localhost_profile) { + INFO("Attempting to create new localhost MySQL profile..."); + $self->_create_new_localhost_profile($profile_manager); + $self->_ensure_localhost_mysql_profile_is_active(0); + } + else { + die "Unable to generate/enable localhost MySQL profile: $_\n"; + } + }; + + return; +} + +sub _create_new_localhost_profile ( $self, $profile_manager ) { + + my $active_profile = $profile_manager->get_active_profile('dont_die'); + File::Slurper::write_text( MYSQL_PROFILE_FILE, $active_profile ); + + my $password = Cpanel::PasswdStrength::Generate::generate_password( 16, no_othersymbols => 1 ); + + try { + $profile_manager->create_profile( + { + 'name' => 'localhost', + 'mysql_user' => 'root', + 'mysql_pass' => $password, + 'mysql_host' => 'localhost', + 'mysql_port' => Cpanel::MysqlUtils::MyCnf::Basic::getmydbport('root') || 3306, + 'setup_via' => 'Auto-generated localhost profile during elevate.', + }, + { 'overwrite' => 1 }, + ); + } + catch { + die <<~"EOS"; + Unable to generate a functioning MySQL DB profile for the local MySQL server. + + The following error was encountered: + + $@ + EOS + }; + + $self->_set_local_mysql_root_password($password); + + $profile_manager->save_changes_to_disk(); + + $self->_activate_localhost_profile($profile_manager); + + return; +} + +sub _set_local_mysql_root_password ( $self, $password ) { + INFO("Resetting password for local root MySQL user..."); + + my $encoded_password = Cpanel::Encoder::URI::uri_encode_str($password); + + my $output = Cpanel::SafeRun::Simple::saferunnoerror( q{/bin/sh}, q{-c}, qq{/usr/local/cpanel/bin/whmapi1 --output=json set_local_mysql_root_password password='$encoded_password'} ); + my $result = eval { Cpanel::JSON::Load($output); } // {}; + + unless ( $result->{metadata}{result} ) { + + my $errors = join qq{\n\n}, @{ $result->{'metadata'}{'errors'} }; + + die <<~"EOS"; + Unable to set root password for the localhost MySQL server. + + The following errors occurred: + + $errors + + Please resolve the reported problems then run this script again with: + + /scripts/elevate-cpanel --continue + + EOS + } + + return; +} + +sub _activate_localhost_profile { + my ( $self, $profile_manager ) = @_; + + if ($profile_manager) { + $profile_manager->{'_transaction_obj'}->close_or_die(); + } + + INFO("Activating “localhost” MySQL profile"); + + my $output = $self->ssystem_capture_output(qw{/usr/local/cpanel/scripts/manage_mysql_profiles --activate localhost}); + my $stdout = join qq{\n}, @{ $output->{'stdout'} }; + + if ( $stdout !~ m{MySQL profile activation done} ) { + die <<~"EOS"; + Unable to activate a MySQL DB profile for "localhost": + + $stdout + + Please resolve the reported problems then run this script again with: + + /scripts/elevate-cpanel --continue + + EOS + } + return; } diff --git a/script/elevate-cpanel.PL b/script/elevate-cpanel.PL index 64727cdc4..742ccc60b 100755 --- a/script/elevate-cpanel.PL +++ b/script/elevate-cpanel.PL @@ -1335,17 +1335,19 @@ 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( 'cPanelPlugins' => 'post_leapp' ); - $self->run_component_once( 'PECL' => 'post_leapp' ); - $self->run_component_once( 'WPToolkit' => 'post_leapp' ); - $self->run_component_once( 'InfluxDB' => 'post_leapp' ); - $self->run_component_once( 'JetBackup' => 'post_leapp' ); - $self->run_component_once( 'Kernel' => 'post_leapp' ); - $self->run_component_once( 'KernelCare' => 'post_leapp' ); - $self->run_component_once( 'NixStats' => 'post_leapp' ); - $self->run_component_once( 'LiteSpeed' => 'post_leapp' ); + $self->run_component_once( 'MySQL' => 'post_leapp' ); + $self->run_component_once( 'PerlXS' => 'post_leapp' ); + $self->run_component_once( 'DatabaseUpgrade' => 'post_leapp' ); + $self->run_component_once( 'cPanelPlugins' => 'post_leapp' ); + $self->run_component_once( 'PECL' => 'post_leapp' ); + $self->run_component_once( 'WPToolkit' => 'post_leapp' ); + $self->run_component_once( 'InfluxDB' => 'post_leapp' ); + $self->run_component_once( 'JetBackup' => 'post_leapp' ); + $self->run_component_once( 'Kernel' => 'post_leapp' ); + $self->run_component_once( 'KernelCare' => 'post_leapp' ); + $self->run_component_once( 'Imunify' => 'post_leapp' ); + $self->run_component_once( 'NixStats' => 'post_leapp' ); + $self->run_component_once( 'LiteSpeed' => 'post_leapp' ); return; } diff --git a/t/blocker-Databases.t b/t/blocker-Databases.t index 10e691ee6..5efeade9b 100644 --- a/t/blocker-Databases.t +++ b/t/blocker-Databases.t @@ -48,34 +48,6 @@ my $mock_elevate = Test::MockFile->file('/var/cpanel/elevate'); is( $db->_blocker_mysql_upgrade_in_progress(), 0, q[MySQL upgrade is not in progress.] ); } -{ - note 'Remote MySQL blocker'; - - clear_messages_seen(); - - my $is_remote_mysql = 0; - - my $mock_basic = Test::MockModule->new('Cpanel::MysqlUtils::MyCnf::Basic'); - $mock_basic->redefine( - is_remote_mysql => sub { return $is_remote_mysql; }, - ); - - is( $db->_blocker_remote_mysql(), 0, 'No blocker if remote MySQL disabled' ); - no_messages_seen(); - - # Test blocker on remote mysql - $is_remote_mysql = 1; - like( - $db->_blocker_remote_mysql(), - { - id => q[Elevate::Blockers::Databases::_blocker_remote_mysql], - msg => qr/The system is currently setup to use a remote database server/, - }, - 'Returns blocker if remote MySQL is enabled' - ); - message_seen( 'WARN', qr/remote database server/ ); -} - { note 'cPanel MySQL behavior'; diff --git a/t/components-DatabaseUpgrade.t b/t/components-DatabaseUpgrade.t new file mode 100644 index 000000000..e9f09d626 --- /dev/null +++ b/t/components-DatabaseUpgrade.t @@ -0,0 +1,258 @@ +#!/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::components; + +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 qw/strict/; + +use lib $FindBin::Bin . "/lib"; +use Test::Elevate; + +use cPstrict; + +use strict; +use warnings; + +use constant PROFILE_FILE => Elevate::Components::DatabaseUpgrade::MYSQL_PROFILE_FILE; + +my $db_upgrade = bless {}, 'Elevate::Components::DatabaseUpgrade'; + +{ + note('Checking pre_leapp'); + + my $mock_elevate_database = Test::MockModule->new('Elevate::Database'); + $mock_elevate_database->redefine( + 'is_database_provided_by_cloudlinux' => 0, + 'is_database_version_supported' => 0, + 'upgrade_database_server' => 0, + 'get_local_database_version' => '5.7', + ); + + my $mock_db_upgrade = Test::MockModule->new('Elevate::Components::DatabaseUpgrade'); + + my @_ensure_localhost_mysql_profile_is_active_params; + $mock_db_upgrade->redefine( + '_ensure_localhost_mysql_profile_is_active', + sub { + ( undef, @_ensure_localhost_mysql_profile_is_active_params ) = @_; + return; + } + ); + + $db_upgrade->pre_leapp(); + is \@_ensure_localhost_mysql_profile_is_active_params, [1], '_ensure_localhost_mysql_profile_is_active was called with correct params'; + + clear_messages_seen(); +} + +{ + note('Checking post_leapp'); + + my @cmds; + my @stdout; + my $mock_db_upgrade = Test::MockModule->new('Elevate::Components::DatabaseUpgrade'); + $mock_db_upgrade->redefine( + 'ssystem_capture_output', + sub ( $, @args ) { + push @cmds, [@args]; + return { status => 0, stdout => \@stdout, stderr => [] }; + } + ); + + my $mock_profile_file = Test::MockFile->file(PROFILE_FILE); + $db_upgrade->post_leapp(); + no_messages_seen(); + + $mock_profile_file->contents('original-db-profile-name'); + like( + dies { $db_upgrade->post_leapp() }, + qr/Unable to reactivate/, + 'Died as expected when profile activation done is not seen' + ); + + message_seen( 'INFO', 'Reactivating "original-db-profile-name" MySQL profile' ); + is \@cmds, [ + [ + '/usr/local/cpanel/scripts/manage_mysql_profiles', + '--activate', + 'original-db-profile-name' + ] + ], + 'Expected "manage_mysql_profiles" command was executed'; + @cmds = (); + + no_messages_seen(); + ok -e PROFILE_FILE, 'profile file still exists after failed reactivation'; + + @stdout = ('MySQL profile activation done'); + $db_upgrade->post_leapp(); + message_seen( 'INFO', 'Reactivating "original-db-profile-name" MySQL profile' ); + is \@cmds, [ + [ + '/usr/local/cpanel/scripts/manage_mysql_profiles', + '--activate', + 'original-db-profile-name' + ] + ], + 'Expected "manage_mysql_profiles" command was executed'; + @cmds = (); + no_messages_seen(); + ok !-e PROFILE_FILE, 'profile file removed after successful reactivation'; + + clear_messages_seen(); +} + +{ + note('Checking _ensure_localhost_mysql_profile_is_active'); + + my $mock_mysqlutils = Test::MockModule->new('Cpanel::MysqlUtils::MyCnf::Basic'); + $mock_mysqlutils->redefine( 'is_local_mysql', 1 ); + + my $instantiated_pm = 0; + my $mock_pm = Test::MockModule->new('Cpanel::MysqlUtils::RemoteMySQL::ProfileManager'); + $mock_pm->redefine( 'new', sub { $instantiated_pm = 1; return bless {}, 'Cpanel::MysqlUtils::RemoteMySQL::ProfileManager' } ); + + $db_upgrade->_ensure_localhost_mysql_profile_is_active(0); + is $instantiated_pm, 0, 'ProfileManager instance not created when is_local_mysql returns true'; + + $mock_mysqlutils->redefine( 'is_local_mysql', 0 ); + $mock_pm->redefine( 'validate_profile', sub { die 'foo' } ); + + like( + dies { $db_upgrade->_ensure_localhost_mysql_profile_is_active(0) }, + qr/Unable to generate/, + 'Died as expected when profile validation fails and should_create_localhost_profile is false' + ); + + my $mock_db_upgrade = Test::MockModule->new('Elevate::Components::DatabaseUpgrade'); + $mock_db_upgrade->redefine( '_activate_localhost_profile', sub { die 'moo' } ); + + $mock_pm->redefine( 'validate_profile', 1 ); + like( + dies { $db_upgrade->_ensure_localhost_mysql_profile_is_active(0) }, + qr/Unable to generate/, + 'Died as expected when _activate_localhost_profile fails and should_create_localhost_profile is false' + ); + + my $created_new_localhost_profile = 0; + $mock_db_upgrade->redefine( '_create_new_localhost_profile', sub { $created_new_localhost_profile = 1 } ); + eval { $db_upgrade->_ensure_localhost_mysql_profile_is_active(1) }; + is $created_new_localhost_profile, 1, '_create_new_localhost_profile() was called when existing profile failed to validate/activate'; + message_seen( 'INFO', qr/Attempting to create new localhost MySQL profile/ ); + + clear_messages_seen(); +} + +{ + note('Checking _set_local_mysql_root_password'); + + my @cmds; + my $stdout = '{"metadata":{"reason":"OK","version":1,"result":1,"command":"set_local_mysql_root_password"},"data":{"configs_updated":1,"profile_updated":1,"password_reset":1}}'; + my $mock_saferun = Test::MockModule->new('Cpanel::SafeRun::Simple'); + $mock_saferun->redefine( 'saferunnoerror', sub { push @cmds, \@_; return $stdout } ); + + ok lives { $db_upgrade->_set_local_mysql_root_password('F00b4r!@#%$%') }, '_set_local_mysql_root_password with successful api call lives'; + message_seen( 'INFO', qr/Resetting password for local root MySQL user/ ); + + is \@cmds, [ + [ + '/bin/sh', + '-c', + '/usr/local/cpanel/bin/whmapi1 --output=json set_local_mysql_root_password password=\'F00b4r%21%40%23%25%24%25\'' + ] + ], + 'Exected saferun command was run'; + + $stdout = '{"metadata":{"version":1,"reason":"Failed to perform “set_root_mysql_password” action. 1 error occurred.","command":"set_local_mysql_root_password","error_count":1,"errors":["(XID kv75bw) The given password is too weak. Please enter a password with a strength rating of 65 or higher."],"result":0}}'; + like( + dies { $db_upgrade->_set_local_mysql_root_password('F00b4') }, + qr/The following errors occurred/, + 'Handles errors from JSON API result correctly', + ); + + clear_messages_seen(); + +} + +{ + note('Checking _activate_localhost_profile'); + + my @cmds; + my @stdout; + my $mock_db_upgrade = Test::MockModule->new('Elevate::Components::DatabaseUpgrade'); + $mock_db_upgrade->redefine( + 'ssystem_capture_output', + sub ( $, @args ) { + push @cmds, [@args]; + return { status => 0, stdout => \@stdout, stderr => [] }; + } + ); + + like( + dies { $db_upgrade->_activate_localhost_profile() }, + qr/Unable to activate/, + 'Died as expected when profile activation string not present in manage_mysql_profiles output', + ); + + message_seen( 'INFO', qr/Activating.*MySQL profile/ ); + is \@cmds, [ + [ + '/usr/local/cpanel/scripts/manage_mysql_profiles', + '--activate', + 'localhost' + ] + ], + 'Expected manage_mysql_profiles command was executed'; + @cmds = (); + + @stdout = ('MySQL profile activation done'); + ok lives { $db_upgrade->_activate_localhost_profile() }, ' _activate_localhost_profile() did not die on successful activation call'; + + clear_messages_seen(); +} + +{ + note('Checking _create_new_localhost_profile'); + + my $mock_mysqlutils = Test::MockModule->new('Cpanel::MysqlUtils::MyCnf::Basic'); + $mock_mysqlutils->redefine( 'getmydbport', 3306 ); + + my $mock_profile_file = Test::MockFile->file(PROFILE_FILE); + my ( @create_profile_params, $save_changes_to_disk_called, $create_profile_called ); + my $mock_pm = Test::MockModule->new('Cpanel::MysqlUtils::RemoteMySQL::ProfileManager'); + $mock_pm->redefine( + 'new' => sub { return bless {}, 'Cpanel::MysqlUtils::RemoteMySQL::ProfileManager' }, + 'create_profile' => sub { $create_profile_called = 1; return 1 }, + 'get_active_profile' => sub { return 'cow-goes-moo' }, + 'save_changes_to_disk' => sub { $save_changes_to_disk_called = 1 }, + ); + my $profile_manager = Cpanel::MysqlUtils::RemoteMySQL::ProfileManager->new(); + + my ( $called_set_local_mysql_root_password, $called_activate_localhost_profile ); + my $mock_db_upgrade = Test::MockModule->new('Elevate::Components::DatabaseUpgrade'); + $mock_db_upgrade->redefine( '_set_local_mysql_root_password' => sub { $called_set_local_mysql_root_password = 1; return 1 } ); + $mock_db_upgrade->redefine( '_activate_localhost_profile', sub { $called_activate_localhost_profile = 1 } ); + + ok lives { $db_upgrade->_create_new_localhost_profile($profile_manager) }, '_create_new_localhost_profile did not die'; + is $called_set_local_mysql_root_password, 1, '_set_local_mysql_root_password was called'; + is $called_activate_localhost_profile, 1, '_activate_localhost_profile was called'; + is $save_changes_to_disk_called, 1, 'ProfileManager->save_changes_to_disk() was called'; + is $create_profile_called, 1, 'ProfileManager->create_profile() was called'; + + no_messages_seen(); + +} + +done_testing();