Merge "Add safemode to special redirect parameters that are preserved"
[lhc/web/wiklou.git] / tests / phpunit / includes / db / LoadBalancerTest.php
1 <?php
2
3 /**
4 * Holds tests for LoadBalancer MediaWiki class.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * http://www.gnu.org/copyleft/gpl.html
20 *
21 * @file
22 */
23
24 use Wikimedia\Rdbms\DBError;
25 use Wikimedia\Rdbms\DatabaseDomain;
26 use Wikimedia\Rdbms\Database;
27 use Wikimedia\Rdbms\LoadBalancer;
28 use Wikimedia\Rdbms\LoadMonitorNull;
29 use Wikimedia\TestingAccessWrapper;
30
31 /**
32 * @group Database
33 * @group medium
34 * @covers \Wikimedia\Rdbms\LoadBalancer
35 */
36 class LoadBalancerTest extends MediaWikiTestCase {
37 private function makeServerConfig( $flags = DBO_DEFAULT ) {
38 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
39
40 return [
41 'host' => $wgDBserver,
42 'dbname' => $wgDBname,
43 'tablePrefix' => $this->dbPrefix(),
44 'user' => $wgDBuser,
45 'password' => $wgDBpassword,
46 'type' => $wgDBtype,
47 'dbDirectory' => $wgSQLiteDataDir,
48 'load' => 0,
49 'flags' => $flags
50 ];
51 }
52
53 /**
54 * @covers LoadBalancer::getLocalDomainID()
55 * @covers LoadBalancer::resolveDomainID()
56 */
57 public function testWithoutReplica() {
58 global $wgDBname;
59
60 $called = false;
61 $lb = new LoadBalancer( [
62 // Simulate web request with DBO_TRX
63 'servers' => [ $this->makeServerConfig( DBO_TRX ) ],
64 'queryLogger' => MediaWiki\Logger\LoggerFactory::getInstance( 'DBQuery' ),
65 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() ),
66 'chronologyCallback' => function () use ( &$called ) {
67 $called = true;
68 }
69 ] );
70
71 $ld = DatabaseDomain::newFromId( $lb->getLocalDomainID() );
72 $this->assertEquals( $wgDBname, $ld->getDatabase(), 'local domain DB set' );
73 $this->assertEquals( $this->dbPrefix(), $ld->getTablePrefix(), 'local domain prefix set' );
74 $this->assertSame( 'my_test_wiki', $lb->resolveDomainID( 'my_test_wiki' ) );
75 $this->assertSame( $ld->getId(), $lb->resolveDomainID( false ) );
76 $this->assertSame( $ld->getId(), $lb->resolveDomainID( $ld ) );
77 $this->assertFalse( $called );
78
79 $dbw = $lb->getConnection( DB_MASTER );
80 $this->assertTrue( $called );
81 $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
82 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on master" );
83 $this->assertWriteAllowed( $dbw );
84
85 $dbr = $lb->getConnection( DB_REPLICA );
86 $this->assertTrue( $dbr->getLBInfo( 'master' ), 'DB_REPLICA also gets the master' );
87 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on replica" );
88
89 if ( !$lb->getServerAttributes( $lb->getWriterIndex() )[$dbw::ATTR_DB_LEVEL_LOCKING] ) {
90 $dbwAuto = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTOCOMMIT );
91 $this->assertFalse(
92 $dbwAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
93 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on master" );
94 $this->assertNotEquals(
95 $dbw, $dbwAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
96
97 $dbrAuto = $lb->getConnection( DB_REPLICA, [], false, $lb::CONN_TRX_AUTOCOMMIT );
98 $this->assertFalse(
99 $dbrAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
100 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on replica" );
101 $this->assertNotEquals(
102 $dbr, $dbrAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
103
104 $dbwAuto2 = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTOCOMMIT );
105 $this->assertEquals( $dbwAuto2, $dbwAuto, "CONN_TRX_AUTOCOMMIT reuses connections" );
106 }
107
108 $lb->closeAll();
109 }
110
111 public function testWithReplica() {
112 global $wgDBserver;
113
114 // Simulate web request with DBO_TRX
115 $lb = $this->newMultiServerLocalLoadBalancer( [], [ 'flags' => DBO_TRX ] );
116
117 $this->assertEquals( 8, $lb->getServerCount() );
118 $this->assertTrue( $lb->hasReplicaServers() );
119 $this->assertTrue( $lb->hasStreamingReplicaServers() );
120
121 $dbw = $lb->getConnection( DB_MASTER );
122 $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
123 $this->assertEquals(
124 ( $wgDBserver != '' ) ? $wgDBserver : 'localhost',
125 $dbw->getLBInfo( 'clusterMasterHost' ),
126 'cluster master set' );
127 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on master" );
128 $this->assertWriteAllowed( $dbw );
129
130 $dbr = $lb->getConnection( DB_REPLICA );
131 $this->assertTrue( $dbr->getLBInfo( 'replica' ), 'replica shows as replica' );
132 $this->assertEquals(
133 ( $wgDBserver != '' ) ? $wgDBserver : 'localhost',
134 $dbr->getLBInfo( 'clusterMasterHost' ),
135 'cluster master set' );
136 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on replica" );
137 $this->assertWriteForbidden( $dbr );
138
139 if ( !$lb->getServerAttributes( $lb->getWriterIndex() )[$dbw::ATTR_DB_LEVEL_LOCKING] ) {
140 $dbwAuto = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTOCOMMIT );
141 $this->assertFalse(
142 $dbwAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
143 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on master" );
144 $this->assertNotEquals(
145 $dbw, $dbwAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
146
147 $dbrAuto = $lb->getConnection( DB_REPLICA, [], false, $lb::CONN_TRX_AUTOCOMMIT );
148 $this->assertFalse(
149 $dbrAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
150 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on replica" );
151 $this->assertNotEquals(
152 $dbr, $dbrAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
153
154 $dbwAuto2 = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTOCOMMIT );
155 $this->assertEquals( $dbwAuto2, $dbwAuto, "CONN_TRX_AUTOCOMMIT reuses connections" );
156 }
157
158 $lb->closeAll();
159 }
160
161 private function newSingleServerLocalLoadBalancer() {
162 global $wgDBname;
163
164 return new LoadBalancer( [
165 'servers' => [ $this->makeServerConfig() ],
166 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() )
167 ] );
168 }
169
170 private function newMultiServerLocalLoadBalancer(
171 $lbExtra = [], $srvExtra = [], $masterOnly = false
172 ) {
173 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
174
175 $servers = [
176 // Master DB
177 0 => $srvExtra + [
178 'host' => $wgDBserver,
179 'dbname' => $wgDBname,
180 'tablePrefix' => $this->dbPrefix(),
181 'user' => $wgDBuser,
182 'password' => $wgDBpassword,
183 'type' => $wgDBtype,
184 'dbDirectory' => $wgSQLiteDataDir,
185 'load' => $masterOnly ? 100 : 0,
186 ],
187 // Main replica DBs
188 1 => $srvExtra + [
189 'host' => $wgDBserver,
190 'dbname' => $wgDBname,
191 'tablePrefix' => $this->dbPrefix(),
192 'user' => $wgDBuser,
193 'password' => $wgDBpassword,
194 'type' => $wgDBtype,
195 'dbDirectory' => $wgSQLiteDataDir,
196 'load' => $masterOnly ? 0 : 100,
197 ],
198 2 => $srvExtra + [
199 'host' => $wgDBserver,
200 'dbname' => $wgDBname,
201 'tablePrefix' => $this->dbPrefix(),
202 'user' => $wgDBuser,
203 'password' => $wgDBpassword,
204 'type' => $wgDBtype,
205 'dbDirectory' => $wgSQLiteDataDir,
206 'load' => $masterOnly ? 0 : 100,
207 ],
208 // RC replica DBs
209 3 => $srvExtra + [
210 'host' => $wgDBserver,
211 'dbname' => $wgDBname,
212 'tablePrefix' => $this->dbPrefix(),
213 'user' => $wgDBuser,
214 'password' => $wgDBpassword,
215 'type' => $wgDBtype,
216 'dbDirectory' => $wgSQLiteDataDir,
217 'load' => 0,
218 'groupLoads' => [
219 'recentchanges' => 100,
220 'watchlist' => 100
221 ],
222 ],
223 // Logging replica DBs
224 4 => $srvExtra + [
225 'host' => $wgDBserver,
226 'dbname' => $wgDBname,
227 'tablePrefix' => $this->dbPrefix(),
228 'user' => $wgDBuser,
229 'password' => $wgDBpassword,
230 'type' => $wgDBtype,
231 'dbDirectory' => $wgSQLiteDataDir,
232 'load' => 0,
233 'groupLoads' => [
234 'logging' => 100
235 ],
236 ],
237 5 => $srvExtra + [
238 'host' => $wgDBserver,
239 'dbname' => $wgDBname,
240 'tablePrefix' => $this->dbPrefix(),
241 'user' => $wgDBuser,
242 'password' => $wgDBpassword,
243 'type' => $wgDBtype,
244 'dbDirectory' => $wgSQLiteDataDir,
245 'load' => 0,
246 'groupLoads' => [
247 'logging' => 100
248 ],
249 ],
250 // Maintenance query replica DBs
251 6 => $srvExtra + [
252 'host' => $wgDBserver,
253 'dbname' => $wgDBname,
254 'tablePrefix' => $this->dbPrefix(),
255 'user' => $wgDBuser,
256 'password' => $wgDBpassword,
257 'type' => $wgDBtype,
258 'dbDirectory' => $wgSQLiteDataDir,
259 'load' => 0,
260 'groupLoads' => [
261 'vslow' => 100
262 ],
263 ],
264 // Replica DB that only has a copy of some static tables
265 7 => $srvExtra + [
266 'host' => $wgDBserver,
267 'dbname' => $wgDBname,
268 'tablePrefix' => $this->dbPrefix(),
269 'user' => $wgDBuser,
270 'password' => $wgDBpassword,
271 'type' => $wgDBtype,
272 'dbDirectory' => $wgSQLiteDataDir,
273 'load' => 0,
274 'groupLoads' => [
275 'archive' => 100
276 ],
277 'is static' => true
278 ]
279 ];
280
281 return new LoadBalancer( $lbExtra + [
282 'servers' => $servers,
283 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() ),
284 'queryLogger' => MediaWiki\Logger\LoggerFactory::getInstance( 'DBQuery' ),
285 'loadMonitorClass' => LoadMonitorNull::class
286 ] );
287 }
288
289 private function assertWriteForbidden( Database $db ) {
290 try {
291 $db->delete( 'some_table', [ 'id' => 57634126 ], __METHOD__ );
292 $this->fail( 'Write operation should have failed!' );
293 } catch ( DBError $ex ) {
294 // check that the exception message contains "Write operation"
295 $constraint = new PHPUnit_Framework_Constraint_StringContains( 'Write operation' );
296
297 if ( !$constraint->evaluate( $ex->getMessage(), '', true ) ) {
298 // re-throw original error, to preserve stack trace
299 throw $ex;
300 }
301 }
302 }
303
304 private function assertWriteAllowed( Database $db ) {
305 $table = $db->tableName( 'some_table' );
306 // Trigger a transaction so that rollback() will remove all the tables.
307 // Don't do this for MySQL/Oracle as they auto-commit transactions for DDL
308 // statements such as CREATE TABLE.
309 $useAtomicSection = in_array( $db->getType(), [ 'sqlite', 'postgres', 'mssql' ], true );
310 try {
311 $db->dropTable( 'some_table' ); // clear for sanity
312 $this->assertNotEquals( $db::STATUS_TRX_ERROR, $db->trxStatus() );
313
314 if ( $useAtomicSection ) {
315 $db->startAtomic( __METHOD__ );
316 }
317 // Use only basic SQL and trivial types for these queries for compatibility
318 $this->assertNotSame(
319 false,
320 $db->query( "CREATE TABLE $table (id INT, time INT)", __METHOD__ ),
321 "table created"
322 );
323 $this->assertNotEquals( $db::STATUS_TRX_ERROR, $db->trxStatus() );
324 $this->assertNotSame(
325 false,
326 $db->query( "DELETE FROM $table WHERE id=57634126", __METHOD__ ),
327 "delete query"
328 );
329 $this->assertNotEquals( $db::STATUS_TRX_ERROR, $db->trxStatus() );
330 } finally {
331 if ( !$useAtomicSection ) {
332 // Drop the table to clean up, ignoring any error.
333 $db->dropTable( 'some_table' );
334 }
335 // Rollback the atomic section for sqlite's benefit.
336 $db->rollback( __METHOD__, 'flush' );
337 $this->assertNotEquals( $db::STATUS_TRX_ERROR, $db->trxStatus() );
338 }
339 }
340
341 public function testServerAttributes() {
342 $servers = [
343 [ // master
344 'dbname' => 'my_unittest_wiki',
345 'tablePrefix' => 'unittest_',
346 'type' => 'sqlite',
347 'dbDirectory' => "some_directory",
348 'load' => 0
349 ]
350 ];
351
352 $lb = new LoadBalancer( [
353 'servers' => $servers,
354 'localDomain' => new DatabaseDomain( 'my_unittest_wiki', null, 'unittest_' ),
355 'loadMonitorClass' => LoadMonitorNull::class
356 ] );
357
358 $this->assertTrue( $lb->getServerAttributes( 0 )[Database::ATTR_DB_LEVEL_LOCKING] );
359
360 $servers = [
361 [ // master
362 'host' => 'db1001',
363 'user' => 'wikiuser',
364 'password' => 'none',
365 'dbname' => 'my_unittest_wiki',
366 'tablePrefix' => 'unittest_',
367 'type' => 'mysql',
368 'load' => 100
369 ],
370 [ // emulated replica
371 'host' => 'db1002',
372 'user' => 'wikiuser',
373 'password' => 'none',
374 'dbname' => 'my_unittest_wiki',
375 'tablePrefix' => 'unittest_',
376 'type' => 'mysql',
377 'load' => 100
378 ]
379 ];
380
381 $lb = new LoadBalancer( [
382 'servers' => $servers,
383 'localDomain' => new DatabaseDomain( 'my_unittest_wiki', null, 'unittest_' ),
384 'loadMonitorClass' => LoadMonitorNull::class
385 ] );
386
387 $this->assertFalse( $lb->getServerAttributes( 1 )[Database::ATTR_DB_LEVEL_LOCKING] );
388 }
389
390 /**
391 * @covers LoadBalancer::openConnection()
392 * @covers LoadBalancer::getAnyOpenConnection()
393 */
394 function testOpenConnection() {
395 $lb = $this->newSingleServerLocalLoadBalancer();
396
397 $i = $lb->getWriterIndex();
398 $this->assertEquals( null, $lb->getAnyOpenConnection( $i ) );
399
400 $conn1 = $lb->getConnection( $i );
401 $this->assertNotEquals( null, $conn1 );
402 $this->assertEquals( $conn1, $lb->getAnyOpenConnection( $i ) );
403 $this->assertFalse( $conn1->getFlag( DBO_TRX ) );
404
405 $conn2 = $lb->getConnection( $i, [], false, $lb::CONN_TRX_AUTOCOMMIT );
406 $this->assertNotEquals( null, $conn2 );
407 $this->assertFalse( $conn2->getFlag( DBO_TRX ) );
408
409 if ( $lb->getServerAttributes( $i )[Database::ATTR_DB_LEVEL_LOCKING] ) {
410 $this->assertEquals( null,
411 $lb->getAnyOpenConnection( $i, $lb::CONN_TRX_AUTOCOMMIT ) );
412 $this->assertEquals( $conn1,
413 $lb->getConnection(
414 $i, [], false, $lb::CONN_TRX_AUTOCOMMIT ), $lb::CONN_TRX_AUTOCOMMIT );
415 } else {
416 $this->assertEquals( $conn2,
417 $lb->getAnyOpenConnection( $i, $lb::CONN_TRX_AUTOCOMMIT ) );
418 $this->assertEquals( $conn2,
419 $lb->getConnection( $i, [], false, $lb::CONN_TRX_AUTOCOMMIT ) );
420
421 $conn2->startAtomic( __METHOD__ );
422 try {
423 $lb->getConnection( $i, [], false, $lb::CONN_TRX_AUTOCOMMIT );
424 $conn2->endAtomic( __METHOD__ );
425 $this->fail( "No exception thrown." );
426 } catch ( DBUnexpectedError $e ) {
427 $this->assertEquals(
428 'Handle requested with CONN_TRX_AUTOCOMMIT yet it has a transaction',
429 $e->getMessage()
430 );
431 }
432 $conn2->endAtomic( __METHOD__ );
433 }
434
435 $lb->closeAll();
436 }
437
438 public function testTransactionCallbackChains() {
439 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
440
441 $servers = [
442 [
443 'host' => $wgDBserver,
444 'dbname' => $wgDBname,
445 'tablePrefix' => $this->dbPrefix(),
446 'user' => $wgDBuser,
447 'password' => $wgDBpassword,
448 'type' => $wgDBtype,
449 'dbDirectory' => $wgSQLiteDataDir,
450 'load' => 0,
451 'flags' => DBO_TRX // simulate a web request with DBO_TRX
452 ],
453 ];
454
455 $lb = new LoadBalancer( [
456 'servers' => $servers,
457 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() )
458 ] );
459
460 $conn1 = $lb->openConnection( $lb->getWriterIndex(), false );
461 $conn2 = $lb->openConnection( $lb->getWriterIndex(), '' );
462
463 $count = 0;
464 $lb->forEachOpenMasterConnection( function () use ( &$count ) {
465 ++$count;
466 } );
467 $this->assertEquals( 2, $count, 'Connection handle count' );
468
469 $tlCalls = 0;
470 $lb->setTransactionListener( 'test-listener', function () use ( &$tlCalls ) {
471 ++$tlCalls;
472 } );
473
474 $lb->beginMasterChanges( __METHOD__ );
475 $bc = array_fill_keys( [ 'a', 'b', 'c', 'd' ], 0 );
476 $conn1->onTransactionPreCommitOrIdle( function () use ( &$bc, $conn1, $conn2 ) {
477 $bc['a'] = 1;
478 $conn2->onTransactionPreCommitOrIdle( function () use ( &$bc, $conn1, $conn2 ) {
479 $bc['b'] = 1;
480 $conn1->onTransactionPreCommitOrIdle( function () use ( &$bc, $conn1, $conn2 ) {
481 $bc['c'] = 1;
482 $conn1->onTransactionPreCommitOrIdle( function () use ( &$bc, $conn1, $conn2 ) {
483 $bc['d'] = 1;
484 } );
485 } );
486 } );
487 } );
488 $lb->finalizeMasterChanges();
489 $lb->approveMasterChanges( [] );
490 $lb->commitMasterChanges( __METHOD__ );
491 $lb->runMasterTransactionIdleCallbacks();
492 $lb->runMasterTransactionListenerCallbacks();
493
494 $this->assertEquals( array_fill_keys( [ 'a', 'b', 'c', 'd' ], 1 ), $bc );
495 $this->assertEquals( 2, $tlCalls );
496
497 $tlCalls = 0;
498 $lb->beginMasterChanges( __METHOD__ );
499 $ac = array_fill_keys( [ 'a', 'b', 'c', 'd' ], 0 );
500 $conn1->onTransactionCommitOrIdle( function () use ( &$ac, $conn1, $conn2 ) {
501 $ac['a'] = 1;
502 $conn2->onTransactionCommitOrIdle( function () use ( &$ac, $conn1, $conn2 ) {
503 $ac['b'] = 1;
504 $conn1->onTransactionCommitOrIdle( function () use ( &$ac, $conn1, $conn2 ) {
505 $ac['c'] = 1;
506 $conn1->onTransactionCommitOrIdle( function () use ( &$ac, $conn1, $conn2 ) {
507 $ac['d'] = 1;
508 } );
509 } );
510 } );
511 } );
512 $lb->finalizeMasterChanges();
513 $lb->approveMasterChanges( [] );
514 $lb->commitMasterChanges( __METHOD__ );
515 $lb->runMasterTransactionIdleCallbacks();
516 $lb->runMasterTransactionListenerCallbacks();
517
518 $this->assertEquals( array_fill_keys( [ 'a', 'b', 'c', 'd' ], 1 ), $ac );
519 $this->assertEquals( 2, $tlCalls );
520
521 $conn1->close();
522 $conn2->close();
523 }
524
525 public function testDBConnRefReadsMasterAndReplicaRoles() {
526 $lb = $this->newSingleServerLocalLoadBalancer();
527
528 $rConn = $lb->getConnectionRef( DB_REPLICA );
529 $wConn = $lb->getConnectionRef( DB_MASTER );
530 $wConn2 = $lb->getConnectionRef( 0 );
531
532 $v = [ 'value' => '1', '1' ];
533 $sql = 'SELECT MAX(1) AS value';
534 foreach ( [ $rConn, $wConn, $wConn2 ] as $conn ) {
535 $conn->clearFlag( $conn::DBO_TRX );
536
537 $res = $conn->query( $sql, __METHOD__ );
538 $this->assertEquals( $v, $conn->fetchRow( $res ) );
539
540 $res = $conn->query( $sql, __METHOD__, $conn::QUERY_REPLICA_ROLE );
541 $this->assertEquals( $v, $conn->fetchRow( $res ) );
542 }
543
544 $wConn->getScopedLockAndFlush( 'key', __METHOD__, 1 );
545 $wConn2->getScopedLockAndFlush( 'key2', __METHOD__, 1 );
546 }
547
548 /**
549 * @expectedException \Wikimedia\Rdbms\DBReadOnlyRoleError
550 */
551 public function testDBConnRefWritesReplicaRole() {
552 $lb = $this->newSingleServerLocalLoadBalancer();
553
554 $rConn = $lb->getConnectionRef( DB_REPLICA );
555
556 $rConn->query( 'DELETE FROM sometesttable WHERE 1=0' );
557 }
558
559 /**
560 * @expectedException \Wikimedia\Rdbms\DBReadOnlyRoleError
561 */
562 public function testDBConnRefWritesReplicaRoleIndex() {
563 $lb = $this->newMultiServerLocalLoadBalancer();
564
565 $rConn = $lb->getConnectionRef( 1 );
566
567 $rConn->query( 'DELETE FROM sometesttable WHERE 1=0' );
568 }
569
570 /**
571 * @expectedException \Wikimedia\Rdbms\DBReadOnlyRoleError
572 */
573 public function testDBConnRefWritesReplicaRoleInsert() {
574 $lb = $this->newMultiServerLocalLoadBalancer();
575
576 $rConn = $lb->getConnectionRef( DB_REPLICA );
577
578 $rConn->insert( 'test', [ 't' => 1 ], __METHOD__ );
579 }
580
581 public function testQueryGroupIndex() {
582 $lb = $this->newMultiServerLocalLoadBalancer( [ 'defaultGroup' => false ] );
583 /** @var LoadBalancer $lbWrapper */
584 $lbWrapper = TestingAccessWrapper::newFromObject( $lb );
585
586 $rGeneric = $lb->getConnectionRef( DB_REPLICA );
587 $mainIndexPicked = $rGeneric->getLBInfo( 'serverIndex' );
588
589 $this->assertEquals( $mainIndexPicked, $lbWrapper->getExistingReaderIndex( false ) );
590 $this->assertTrue( in_array( $mainIndexPicked, [ 1, 2 ] ) );
591 for ( $i = 0; $i < 300; ++$i ) {
592 $rLog = $lb->getConnectionRef( DB_REPLICA, [] );
593 $this->assertEquals(
594 $mainIndexPicked,
595 $rLog->getLBInfo( 'serverIndex' ),
596 "Main index unchanged" );
597 }
598
599 $rRC = $lb->getConnectionRef( DB_REPLICA, [ 'recentchanges' ] );
600 $rWL = $lb->getConnectionRef( DB_REPLICA, [ 'watchlist' ] );
601
602 $this->assertEquals( 3, $rRC->getLBInfo( 'serverIndex' ) );
603 $this->assertEquals( 3, $rWL->getLBInfo( 'serverIndex' ) );
604
605 $rLog = $lb->getConnectionRef( DB_REPLICA, [ 'logging', 'watchlist' ] );
606 $logIndexPicked = $rLog->getLBInfo( 'serverIndex' );
607
608 $this->assertEquals( $logIndexPicked, $lbWrapper->getExistingReaderIndex( 'logging' ) );
609 $this->assertTrue( in_array( $logIndexPicked, [ 4, 5 ] ) );
610
611 for ( $i = 0; $i < 300; ++$i ) {
612 $rLog = $lb->getConnectionRef( DB_REPLICA, [ 'logging', 'watchlist' ] );
613 $this->assertEquals(
614 $logIndexPicked, $rLog->getLBInfo( 'serverIndex' ), "Index unchanged" );
615 }
616
617 $rVslow = $lb->getConnectionRef( DB_REPLICA, [ 'vslow', 'logging' ] );
618 $vslowIndexPicked = $rVslow->getLBInfo( 'serverIndex' );
619
620 $this->assertEquals( $vslowIndexPicked, $lbWrapper->getExistingReaderIndex( 'vslow' ) );
621 $this->assertEquals( 6, $vslowIndexPicked );
622 }
623
624 public function testNonZeroMasterLoad() {
625 $lb = $this->newMultiServerLocalLoadBalancer( [], [ 'flags' => DBO_DEFAULT ], true );
626 // Make sure that no infinite loop occurs (T226678)
627 $rGeneric = $lb->getConnectionRef( DB_REPLICA );
628 $this->assertEquals( $lb->getWriterIndex(), $rGeneric->getLBInfo( 'serverIndex' ) );
629 }
630 }