Merge "Add support for 'hu-formal'"
[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 * @group Database
22 * @file
23 *
24 * @covers \Wikimedia\Rdbms\LoadBalancer
25 */
26
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;
32
33 class LoadBalancerTest extends MediaWikiTestCase {
34 public function testWithoutReplica() {
35 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
36
37 $servers = [
38 [
39 'host' => $wgDBserver,
40 'dbname' => $wgDBname,
41 'tablePrefix' => $this->dbPrefix(),
42 'user' => $wgDBuser,
43 'password' => $wgDBpassword,
44 'type' => $wgDBtype,
45 'dbDirectory' => $wgSQLiteDataDir,
46 'load' => 0,
47 'flags' => DBO_TRX // REPEATABLE-READ for consistency
48 ],
49 ];
50
51 $lb = new LoadBalancer( [
52 'servers' => $servers,
53 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() )
54 ] );
55
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' );
59
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 );
64
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" );
68
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" );
73
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" );
78
79 $dbwAuto2 = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTO );
80 $this->assertEquals( $dbwAuto2, $dbwAuto, "CONN_TRX_AUTO reuses connections" );
81
82 $lb->closeAll();
83 }
84
85 public function testWithReplica() {
86 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
87
88 $servers = [
89 [ // master
90 'host' => $wgDBserver,
91 'dbname' => $wgDBname,
92 'tablePrefix' => $this->dbPrefix(),
93 'user' => $wgDBuser,
94 'password' => $wgDBpassword,
95 'type' => $wgDBtype,
96 'dbDirectory' => $wgSQLiteDataDir,
97 'load' => 0,
98 'flags' => DBO_TRX // REPEATABLE-READ for consistency
99 ],
100 [ // emulated replica
101 'host' => $wgDBserver,
102 'dbname' => $wgDBname,
103 'tablePrefix' => $this->dbPrefix(),
104 'user' => $wgDBuser,
105 'password' => $wgDBpassword,
106 'type' => $wgDBtype,
107 'dbDirectory' => $wgSQLiteDataDir,
108 'load' => 100,
109 'flags' => DBO_TRX // REPEATABLE-READ for consistency
110 ]
111 ];
112
113 $lb = new LoadBalancer( [
114 'servers' => $servers,
115 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() ),
116 'loadMonitorClass' => LoadMonitorNull::class
117 ] );
118
119 $dbw = $lb->getConnection( DB_MASTER );
120 $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
121 $this->assertEquals(
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 );
127
128 $dbr = $lb->getConnection( DB_REPLICA );
129 $this->assertTrue( $dbr->getLBInfo( 'replica' ), 'replica shows as replica' );
130 $this->assertEquals(
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 );
136
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" );
141
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" );
146
147 $dbwAuto2 = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTO );
148 $this->assertEquals( $dbwAuto2, $dbwAuto, "CONN_TRX_AUTO reuses connections" );
149
150 $lb->closeAll();
151 }
152
153 private function assertWriteForbidden( Database $db ) {
154 try {
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' );
160
161 if ( !$constraint->evaluate( $ex->getMessage(), '', true ) ) {
162 // re-throw original error, to preserve stack trace
163 throw $ex;
164 }
165 } finally {
166 $db->rollback( __METHOD__, 'flush' );
167 }
168 }
169
170 private function assertWriteAllowed( Database $db ) {
171 $table = $db->tableName( 'some_table' );
172 try {
173 $db->dropTable( 'some_table' ); // clear for sanity
174 // Use only basic SQL and trivial types for these queries for compatibility
175 $this->assertNotSame(
176 false,
177 $db->query( "CREATE TABLE $table (id INT, time INT)", __METHOD__ ),
178 "table created"
179 );
180 $this->assertNotSame(
181 false,
182 $db->query( "DELETE FROM $table WHERE id=57634126", __METHOD__ ),
183 "delete query"
184 );
185 $this->assertNotSame(
186 false,
187 $db->query( "DROP TABLE $table", __METHOD__ ),
188 "table dropped"
189 );
190 } finally {
191 $db->rollback( __METHOD__, 'flush' );
192 }
193 }
194
195 public function testServerAttributes() {
196 $servers = [
197 [ // master
198 'dbname' => 'my_unittest_wiki',
199 'tablePrefix' => 'unittest_',
200 'type' => 'sqlite',
201 'dbDirectory' => "some_directory",
202 'load' => 0
203 ]
204 ];
205
206 $lb = new LoadBalancer( [
207 'servers' => $servers,
208 'localDomain' => new DatabaseDomain( 'my_unittest_wiki', null, 'unittest_' ),
209 'loadMonitorClass' => LoadMonitorNull::class
210 ] );
211
212 $this->assertTrue( $lb->getServerAttributes( 0 )[Database::ATTR_DB_LEVEL_LOCKING] );
213
214 $servers = [
215 [ // master
216 'host' => 'db1001',
217 'user' => 'wikiuser',
218 'password' => 'none',
219 'dbname' => 'my_unittest_wiki',
220 'tablePrefix' => 'unittest_',
221 'type' => 'mysql',
222 'load' => 100
223 ],
224 [ // emulated replica
225 'host' => 'db1002',
226 'user' => 'wikiuser',
227 'password' => 'none',
228 'dbname' => 'my_unittest_wiki',
229 'tablePrefix' => 'unittest_',
230 'type' => 'mysql',
231 'load' => 100
232 ]
233 ];
234
235 $lb = new LoadBalancer( [
236 'servers' => $servers,
237 'localDomain' => new DatabaseDomain( 'my_unittest_wiki', null, 'unittest_' ),
238 'loadMonitorClass' => LoadMonitorNull::class
239 ] );
240
241 $this->assertFalse( $lb->getServerAttributes( 1 )[Database::ATTR_DB_LEVEL_LOCKING] );
242 }
243 }