Merge "Add SPARQL client to core"
[lhc/web/wiklou.git] / tests / phpunit / includes / db / LoadBalancerTest.php
1 <?php
2
3 use Wikimedia\Rdbms\DBError;
4 use Wikimedia\Rdbms\LoadBalancer;
5 use Wikimedia\Rdbms\DatabaseDomain;
6 use Wikimedia\Rdbms\Database;
7
8 /**
9 * Holds tests for LoadBalancer MediaWiki class.
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License along
22 * with this program; if not, write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24 * http://www.gnu.org/copyleft/gpl.html
25 *
26 * @group Database
27 * @file
28 *
29 * @covers \Wikimedia\Rdbms\LoadBalancer
30 */
31 class LoadBalancerTest extends MediaWikiTestCase {
32 public function testWithoutReplica() {
33 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
34
35 $servers = [
36 [
37 'host' => $wgDBserver,
38 'dbname' => $wgDBname,
39 'tablePrefix' => $this->dbPrefix(),
40 'user' => $wgDBuser,
41 'password' => $wgDBpassword,
42 'type' => $wgDBtype,
43 'dbDirectory' => $wgSQLiteDataDir,
44 'load' => 0,
45 'flags' => DBO_TRX // REPEATABLE-READ for consistency
46 ],
47 ];
48
49 $lb = new LoadBalancer( [
50 'servers' => $servers,
51 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() )
52 ] );
53
54 $ld = DatabaseDomain::newFromId( $lb->getLocalDomainID() );
55 $this->assertEquals( $wgDBname, $ld->getDatabase(), 'local domain DB set' );
56 $this->assertEquals( $this->dbPrefix(), $ld->getTablePrefix(), 'local domain prefix set' );
57
58 $dbw = $lb->getConnection( DB_MASTER );
59 $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
60 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on master" );
61 $this->assertWriteAllowed( $dbw );
62
63 $dbr = $lb->getConnection( DB_REPLICA );
64 $this->assertTrue( $dbr->getLBInfo( 'master' ), 'DB_REPLICA also gets the master' );
65 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on replica" );
66
67 $dbwAuto = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTO );
68 $this->assertFalse( $dbwAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTO" );
69 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on master" );
70 $this->assertNotEquals( $dbw, $dbwAuto, "CONN_TRX_AUTO uses separate connection" );
71
72 $dbrAuto = $lb->getConnection( DB_REPLICA, [], false, $lb::CONN_TRX_AUTO );
73 $this->assertFalse( $dbrAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTO" );
74 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on replica" );
75 $this->assertNotEquals( $dbr, $dbrAuto, "CONN_TRX_AUTO uses separate connection" );
76
77 $dbwAuto2 = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTO );
78 $this->assertEquals( $dbwAuto2, $dbwAuto, "CONN_TRX_AUTO reuses connections" );
79
80 $lb->closeAll();
81 }
82
83 public function testWithReplica() {
84 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
85
86 $servers = [
87 [ // master
88 'host' => $wgDBserver,
89 'dbname' => $wgDBname,
90 'tablePrefix' => $this->dbPrefix(),
91 'user' => $wgDBuser,
92 'password' => $wgDBpassword,
93 'type' => $wgDBtype,
94 'dbDirectory' => $wgSQLiteDataDir,
95 'load' => 0,
96 'flags' => DBO_TRX // REPEATABLE-READ for consistency
97 ],
98 [ // emulated replica
99 'host' => $wgDBserver,
100 'dbname' => $wgDBname,
101 'tablePrefix' => $this->dbPrefix(),
102 'user' => $wgDBuser,
103 'password' => $wgDBpassword,
104 'type' => $wgDBtype,
105 'dbDirectory' => $wgSQLiteDataDir,
106 'load' => 100,
107 'flags' => DBO_TRX // REPEATABLE-READ for consistency
108 ]
109 ];
110
111 $lb = new LoadBalancer( [
112 'servers' => $servers,
113 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() ),
114 'loadMonitorClass' => LoadMonitorNull::class
115 ] );
116
117 $dbw = $lb->getConnection( DB_MASTER );
118 $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
119 $this->assertEquals(
120 ( $wgDBserver != '' ) ? $wgDBserver : 'localhost',
121 $dbw->getLBInfo( 'clusterMasterHost' ),
122 'cluster master set' );
123 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on master" );
124 $this->assertWriteAllowed( $dbw );
125
126 $dbr = $lb->getConnection( DB_REPLICA );
127 $this->assertTrue( $dbr->getLBInfo( 'replica' ), 'replica shows as replica' );
128 $this->assertEquals(
129 ( $wgDBserver != '' ) ? $wgDBserver : 'localhost',
130 $dbr->getLBInfo( 'clusterMasterHost' ),
131 'cluster master set' );
132 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on replica" );
133 $this->assertWriteForbidden( $dbr );
134
135 $dbwAuto = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTO );
136 $this->assertFalse( $dbwAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTO" );
137 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on master" );
138 $this->assertNotEquals( $dbw, $dbwAuto, "CONN_TRX_AUTO uses separate connection" );
139
140 $dbrAuto = $lb->getConnection( DB_REPLICA, [], false, $lb::CONN_TRX_AUTO );
141 $this->assertFalse( $dbrAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTO" );
142 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on replica" );
143 $this->assertNotEquals( $dbr, $dbrAuto, "CONN_TRX_AUTO uses separate connection" );
144
145 $dbwAuto2 = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTO );
146 $this->assertEquals( $dbwAuto2, $dbwAuto, "CONN_TRX_AUTO reuses connections" );
147
148 $lb->closeAll();
149 }
150
151 private function assertWriteForbidden( Database $db ) {
152 try {
153 $db->delete( 'some_table', [ 'id' => 57634126 ], __METHOD__ );
154 $this->fail( 'Write operation should have failed!' );
155 } catch ( DBError $ex ) {
156 // check that the exception message contains "Write operation"
157 $constraint = new PHPUnit_Framework_Constraint_StringContains( 'Write operation' );
158
159 if ( !$constraint->evaluate( $ex->getMessage(), '', true ) ) {
160 // re-throw original error, to preserve stack trace
161 throw $ex;
162 }
163 } finally {
164 $db->rollback( __METHOD__, 'flush' );
165 }
166 }
167
168 private function assertWriteAllowed( Database $db ) {
169 $table = $db->tableName( 'some_table' );
170 try {
171 $db->dropTable( 'some_table' ); // clear for sanity
172 // Use only basic SQL and trivial types for these queries for compatibility
173 $this->assertNotSame(
174 false,
175 $db->query( "CREATE TABLE $table (id INT, time INT)", __METHOD__ ),
176 "table created"
177 );
178 $this->assertNotSame(
179 false,
180 $db->query( "DELETE FROM $table WHERE id=57634126", __METHOD__ ),
181 "delete query"
182 );
183 $this->assertNotSame(
184 false,
185 $db->query( "DROP TABLE $table", __METHOD__ ),
186 "table dropped"
187 );
188 } finally {
189 $db->rollback( __METHOD__, 'flush' );
190 }
191 }
192
193 }