4 * Holds tests for LoadBalancer MediaWiki class.
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.
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.
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
24 * @covers \Wikimedia\Rdbms\LoadBalancer
27 use Wikimedia\Rdbms\DBError
;
28 use Wikimedia\Rdbms\DatabaseDomain
;
29 use Wikimedia\Rdbms\Database
;
30 use Wikimedia\Rdbms\LoadBalancer
;
31 use Wikimedia\Rdbms\LoadMonitorNull
;
33 class LoadBalancerTest
extends MediaWikiTestCase
{
34 public function testWithoutReplica() {
35 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
39 'host' => $wgDBserver,
40 'dbname' => $wgDBname,
41 'tablePrefix' => $this->dbPrefix(),
43 'password' => $wgDBpassword,
45 'dbDirectory' => $wgSQLiteDataDir,
47 'flags' => DBO_TRX
// REPEATABLE-READ for consistency
51 $lb = new LoadBalancer( [
52 'servers' => $servers,
53 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() )
56 $ld = DatabaseDomain
::newFromId( $lb->getLocalDomainID() );
57 $this->assertEquals( $wgDBname, $ld->getDatabase(), 'local domain DB set' );
58 $this->assertEquals( $this->dbPrefix(), $ld->getTablePrefix(), 'local domain prefix set' );
60 $dbw = $lb->getConnection( DB_MASTER
);
61 $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
62 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX
), "DBO_TRX set on master" );
63 $this->assertWriteAllowed( $dbw );
65 $dbr = $lb->getConnection( DB_REPLICA
);
66 $this->assertTrue( $dbr->getLBInfo( 'master' ), 'DB_REPLICA also gets the master' );
67 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX
), "DBO_TRX set on replica" );
69 $dbwAuto = $lb->getConnection( DB_MASTER
, [], false, $lb::CONN_TRX_AUTO
);
70 $this->assertFalse( $dbwAuto->getFlag( $dbw::DBO_TRX
), "No DBO_TRX with CONN_TRX_AUTO" );
71 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX
), "DBO_TRX still set on master" );
72 $this->assertNotEquals( $dbw, $dbwAuto, "CONN_TRX_AUTO uses separate connection" );
74 $dbrAuto = $lb->getConnection( DB_REPLICA
, [], false, $lb::CONN_TRX_AUTO
);
75 $this->assertFalse( $dbrAuto->getFlag( $dbw::DBO_TRX
), "No DBO_TRX with CONN_TRX_AUTO" );
76 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX
), "DBO_TRX still set on replica" );
77 $this->assertNotEquals( $dbr, $dbrAuto, "CONN_TRX_AUTO uses separate connection" );
79 $dbwAuto2 = $lb->getConnection( DB_MASTER
, [], false, $lb::CONN_TRX_AUTO
);
80 $this->assertEquals( $dbwAuto2, $dbwAuto, "CONN_TRX_AUTO reuses connections" );
85 public function testWithReplica() {
86 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
90 'host' => $wgDBserver,
91 'dbname' => $wgDBname,
92 'tablePrefix' => $this->dbPrefix(),
94 'password' => $wgDBpassword,
96 'dbDirectory' => $wgSQLiteDataDir,
98 'flags' => DBO_TRX
// REPEATABLE-READ for consistency
100 [ // emulated replica
101 'host' => $wgDBserver,
102 'dbname' => $wgDBname,
103 'tablePrefix' => $this->dbPrefix(),
105 'password' => $wgDBpassword,
107 'dbDirectory' => $wgSQLiteDataDir,
109 'flags' => DBO_TRX
// REPEATABLE-READ for consistency
113 $lb = new LoadBalancer( [
114 'servers' => $servers,
115 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() ),
116 'loadMonitorClass' => LoadMonitorNull
::class
119 $dbw = $lb->getConnection( DB_MASTER
);
120 $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
122 ( $wgDBserver != '' ) ?
$wgDBserver : 'localhost',
123 $dbw->getLBInfo( 'clusterMasterHost' ),
124 'cluster master set' );
125 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX
), "DBO_TRX set on master" );
126 $this->assertWriteAllowed( $dbw );
128 $dbr = $lb->getConnection( DB_REPLICA
);
129 $this->assertTrue( $dbr->getLBInfo( 'replica' ), 'replica shows as replica' );
131 ( $wgDBserver != '' ) ?
$wgDBserver : 'localhost',
132 $dbr->getLBInfo( 'clusterMasterHost' ),
133 'cluster master set' );
134 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX
), "DBO_TRX set on replica" );
135 $this->assertWriteForbidden( $dbr );
137 $dbwAuto = $lb->getConnection( DB_MASTER
, [], false, $lb::CONN_TRX_AUTO
);
138 $this->assertFalse( $dbwAuto->getFlag( $dbw::DBO_TRX
), "No DBO_TRX with CONN_TRX_AUTO" );
139 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX
), "DBO_TRX still set on master" );
140 $this->assertNotEquals( $dbw, $dbwAuto, "CONN_TRX_AUTO uses separate connection" );
142 $dbrAuto = $lb->getConnection( DB_REPLICA
, [], false, $lb::CONN_TRX_AUTO
);
143 $this->assertFalse( $dbrAuto->getFlag( $dbw::DBO_TRX
), "No DBO_TRX with CONN_TRX_AUTO" );
144 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX
), "DBO_TRX still set on replica" );
145 $this->assertNotEquals( $dbr, $dbrAuto, "CONN_TRX_AUTO uses separate connection" );
147 $dbwAuto2 = $lb->getConnection( DB_MASTER
, [], false, $lb::CONN_TRX_AUTO
);
148 $this->assertEquals( $dbwAuto2, $dbwAuto, "CONN_TRX_AUTO reuses connections" );
153 private function assertWriteForbidden( Database
$db ) {
155 $db->delete( 'some_table', [ 'id' => 57634126 ], __METHOD__
);
156 $this->fail( 'Write operation should have failed!' );
157 } catch ( DBError
$ex ) {
158 // check that the exception message contains "Write operation"
159 $constraint = new PHPUnit_Framework_Constraint_StringContains( 'Write operation' );
161 if ( !$constraint->evaluate( $ex->getMessage(), '', true ) ) {
162 // re-throw original error, to preserve stack trace
166 $db->rollback( __METHOD__
, 'flush' );
170 private function assertWriteAllowed( Database
$db ) {
171 $table = $db->tableName( 'some_table' );
173 $db->dropTable( 'some_table' ); // clear for sanity
174 // Use only basic SQL and trivial types for these queries for compatibility
175 $this->assertNotSame(
177 $db->query( "CREATE TABLE $table (id INT, time INT)", __METHOD__
),
180 $this->assertNotSame(
182 $db->query( "DELETE FROM $table WHERE id=57634126", __METHOD__
),
185 $this->assertNotSame(
187 $db->query( "DROP TABLE $table", __METHOD__
),
191 $db->rollback( __METHOD__
, 'flush' );
195 public function testServerAttributes() {
198 'dbname' => 'my_unittest_wiki',
199 'tablePrefix' => 'unittest_',
201 'dbDirectory' => "some_directory",
206 $lb = new LoadBalancer( [
207 'servers' => $servers,
208 'localDomain' => new DatabaseDomain( 'my_unittest_wiki', null, 'unittest_' ),
209 'loadMonitorClass' => LoadMonitorNull
::class
212 $this->assertTrue( $lb->getServerAttributes( 0 )[Database
::ATTR_DB_LEVEL_LOCKING
] );
217 'user' => 'wikiuser',
218 'password' => 'none',
219 'dbname' => 'my_unittest_wiki',
220 'tablePrefix' => 'unittest_',
224 [ // emulated replica
226 'user' => 'wikiuser',
227 'password' => 'none',
228 'dbname' => 'my_unittest_wiki',
229 'tablePrefix' => 'unittest_',
235 $lb = new LoadBalancer( [
236 'servers' => $servers,
237 'localDomain' => new DatabaseDomain( 'my_unittest_wiki', null, 'unittest_' ),
238 'loadMonitorClass' => LoadMonitorNull
::class
241 $this->assertFalse( $lb->getServerAttributes( 1 )[Database
::ATTR_DB_LEVEL_LOCKING
] );