Merge branch 'MDL-72077-ro-debug' of https://github.com/catalyst/moodle

This commit is contained in:
Jun Pataleta 2024-11-20 15:24:47 +08:00
commit d5190768cb
No known key found for this signature in database
GPG Key ID: F83510526D99E2C7
4 changed files with 153 additions and 11 deletions

View File

@ -153,6 +153,7 @@ trait moodle_read_slave_trait {
$this->pprefix = $prefix;
$this->pdboptions = $dboptions;
$logconnection = false;
if ($dboptions) {
if (isset($dboptions['readonly'])) {
$this->wantreadslave = true;
@ -180,8 +181,11 @@ trait moodle_read_slave_trait {
}
if (count($slaves) > 1) {
// Randomise things a bit.
shuffle($slaves);
// Don't shuffle for unit tests as order is important for them to pass.
if (!PHPUNIT_TEST) {
// Randomise things a bit.
shuffle($slaves);
}
}
// Find first connectable readonly slave.
@ -198,9 +202,17 @@ trait moodle_read_slave_trait {
try {
$this->raw_connect($rodb['dbhost'], $rodb['dbuser'], $rodb['dbpass'], $dbname, $prefix, $dboptions);
$this->dbhreadonly = $this->get_db_handle();
if ($logconnection) {
debugging(
"Readonly db connection succeeded for host {$rodb['dbhost']}"
);
}
break;
} catch (dml_connection_exception $e) { // phpcs:ignore
// If readonly slave is not connectable we'll have to do without it.
} catch (dml_connection_exception $e) {
debugging(
"Readonly db connection failed for host {$rodb['dbhost']}: {$e->debuginfo}"
);
$logconnection = true;
}
}
// ... lock_db queries always go to master.
@ -212,7 +224,19 @@ trait moodle_read_slave_trait {
}
}
if (!$this->dbhreadonly) {
$this->set_dbhwrite();
try {
$this->set_dbhwrite();
} catch (dml_connection_exception $e) {
debugging(
"Readwrite db connection failed for host {$this->pdbhost}: {$e->debuginfo}"
);
throw $e;
}
if ($logconnection) {
debugging(
"Readwrite db connection succeeded for host {$this->pdbhost}"
);
}
}
return true;

View File

@ -40,7 +40,7 @@ require_once(__DIR__.'/fixtures/read_slave_moodle_database_mock_mysqli.php');
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \mysqli_native_moodle_database
*/
class dml_mysqli_read_slave_test extends \base_testcase {
final class dml_mysqli_read_slave_test extends \database_driver_testcase {
/**
* Test readonly handle is not used for reading from special pg_*() call queries,
* pg_try_advisory_lock and pg_advisory_unlock.
@ -136,25 +136,102 @@ class dml_mysqli_read_slave_test extends \base_testcase {
*
* @return void
*/
public function test_real_readslave_connect_fail(): void {
public function test_real_readslave_connect_fail_host(): void {
global $DB;
if ($DB->get_dbfamily() != 'mysql') {
$this->markTestSkipped('Not mysql');
}
$invalidhost = 'host.that.is.not';
// Open second connection.
$cfg = $DB->export_dbconfig();
if (!isset($cfg->dboptions)) {
$cfg->dboptions = [];
}
$cfg->dboptions['readonly'] = [
'instance' => ['host.that.is.not'],
'instance' => [$invalidhost],
'connecttimeout' => 1
];
$db2 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary);
$this->resetDebugging();
$db2 = \moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary);
$db2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions);
$this->assertTrue(count($db2->get_records('user')) > 0);
$debugging = array_map(function ($d) {
return $d->message;
}, $this->getDebuggingMessages());
$this->resetDebugging();
$this->assertEquals(2, count($debugging));
$this->assertMatchesRegularExpression(
sprintf(
'/%s%s/',
preg_quote("Readonly db connection failed for host {$invalidhost}:"),
'.* Name or service not known',
$cfg->dbname
),
$debugging[0]
);
$this->assertEquals("Readwrite db connection succeeded for host {$cfg->dbhost}", $debugging[1]);
}
/**
* Test connection failure
*
* @return void
*/
public function test_real_readslave_connect_fail_dbname(): void {
global $DB;
if ($DB->get_dbfamily() != 'mysql') {
$this->markTestSkipped("Not mysql");
}
$invaliddb = 'cannot-exist-really';
// Open second connection.
$cfg = $DB->export_dbconfig();
$cfg->dbname = $invaliddb;
if (!isset($cfg->dboptions)) {
$cfg->dboptions = [];
}
$cfg->dboptions['readonly'] = [
'instance' => [$cfg->dbhost],
'connecttimeout' => 1,
];
$this->resetDebugging();
$db2 = \moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary);
try {
$db2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions);
} catch (\dml_connection_exception $e) { // phpcs:ignore
// We cannot go with expectException() because it would skip the rest.
}
$debugging = array_map(function ($d) {
return $d->message;
}, $this->getDebuggingMessages());
$this->resetDebugging();
$this->assertEquals(2, count($debugging));
$this->assertMatchesRegularExpression(
sprintf(
'/%s%s/',
preg_quote("Readonly db connection failed for host {$cfg->dbhost}: "),
"Access denied for user .* to database '$invaliddb'",
$cfg->dbname
),
$debugging[0]
);
$this->assertMatchesRegularExpression(
sprintf(
'/%s%s/',
preg_quote("Readwrite db connection failed for host {$cfg->dbhost}: "),
'Access denied for user .* '.preg_quote("to database '$invaliddb'"),
$cfg->dbname
),
$debugging[1]
);
}
}

View File

@ -267,18 +267,37 @@ class dml_pgsql_read_slave_test extends \advanced_testcase {
$this->markTestSkipped('Not postgres');
}
$invalidhost = 'host.that.is.not';
// Open second connection.
$cfg = $DB->export_dbconfig();
if (!isset($cfg->dboptions)) {
$cfg->dboptions = array();
}
$cfg->dboptions['readonly'] = [
'instance' => ['host.that.is.not'],
'instance' => [$invalidhost],
'connecttimeout' => 1
];
$this->resetDebugging();
$db2 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary);
$db2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions);
$this->assertTrue(count($db2->get_records('user')) > 0);
$debugging = array_map(function ($d) {
return $d->message;
}, $this->getDebuggingMessages());
$this->resetDebugging();
$this->assertEquals(2, count($debugging));
$this->assertMatchesRegularExpression(
sprintf(
'/%s%s/',
preg_quote("Readonly db connection failed for host {$invalidhost}: "),
'.* Name or service not known',
$cfg->dbname
),
$debugging[0]
);
$this->assertEquals("Readwrite db connection succeeded for host {$cfg->dbhost}", $debugging[1]);
}
}

View File

@ -40,7 +40,7 @@ require_once(__DIR__.'/../../tests/fixtures/event_fixtures.php');
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \moodle_read_slave_trait
*/
class dml_read_slave_test extends \base_testcase {
final class dml_read_slave_test extends \database_driver_testcase {
/** @var float */
static private $dbreadonlylatency = 0.8;
@ -452,6 +452,8 @@ class dml_read_slave_test extends \base_testcase {
* @return void
*/
public function test_read_only_conn_fail(): void {
$this->resetDebugging();
$DB = $this->new_db(false, 'test_ro_fail');
$this->assertEquals(0, $DB->perf_get_reads_slave());
@ -461,6 +463,15 @@ class dml_read_slave_test extends \base_testcase {
$this->assertEquals('test_rw::test:test', $handle);
$readsslave = $DB->perf_get_reads_slave();
$this->assertEquals(0, $readsslave);
$debugging = array_map(function ($d) {
return $d->message;
}, $this->getDebuggingMessages());
$this->resetDebugging();
$this->assertEquals([
'Readonly db connection failed for host test_ro_fail: test_ro_fail',
'Readwrite db connection succeeded for host test_rw',
], $debugging);
}
/**
@ -470,6 +481,8 @@ class dml_read_slave_test extends \base_testcase {
* @return void
*/
public function test_read_only_conn_first_fail(): void {
$this->resetDebugging();
$DB = $this->new_db(false, ['test_ro_fail', 'test_ro_ok']);
$this->assertEquals(0, $DB->perf_get_reads_slave());
@ -479,6 +492,15 @@ class dml_read_slave_test extends \base_testcase {
$this->assertEquals('test_ro_ok::test:test', $handle);
$readsslave = $DB->perf_get_reads_slave();
$this->assertEquals(1, $readsslave);
$debugging = array_map(function ($d) {
return $d->message;
}, $this->getDebuggingMessages());
$this->resetDebugging();
$this->assertEquals([
'Readonly db connection failed for host test_ro_fail: test_ro_fail',
'Readonly db connection succeeded for host test_ro_ok',
], $debugging);
}
/**