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