Merge "Reset table sequences and skip some test assertions for sqlite"
[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
30 /**
31 * @group Database
32 * @covers \Wikimedia\Rdbms\LoadBalancer
33 */
34 class LoadBalancerTest extends MediaWikiTestCase {
35 public function testWithoutReplica() {
36 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
37
38 $servers = [
39 [
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' => DBO_TRX // REPEATABLE-READ for consistency
49 ],
50 ];
51
52 $lb = new LoadBalancer( [
53 'servers' => $servers,
54 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() )
55 ] );
56
57 $ld = DatabaseDomain::newFromId( $lb->getLocalDomainID() );
58 $this->assertEquals( $wgDBname, $ld->getDatabase(), 'local domain DB set' );
59 $this->assertEquals( $this->dbPrefix(), $ld->getTablePrefix(), 'local domain prefix set' );
60
61 $dbw = $lb->getConnection( DB_MASTER );
62 $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
63 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on master" );
64 $this->assertWriteAllowed( $dbw );
65
66 $dbr = $lb->getConnection( DB_REPLICA );
67 $this->assertTrue( $dbr->getLBInfo( 'master' ), 'DB_REPLICA also gets the master' );
68 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on replica" );
69
70 if ( !$lb->getServerAttributes( $lb->getWriterIndex() )[$dbw::ATTR_DB_LEVEL_LOCKING] ) {
71 $dbwAuto = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTOCOMMIT );
72 $this->assertFalse(
73 $dbwAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
74 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on master" );
75 $this->assertNotEquals(
76 $dbw, $dbwAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
77
78 $dbrAuto = $lb->getConnection( DB_REPLICA, [], false, $lb::CONN_TRX_AUTOCOMMIT );
79 $this->assertFalse(
80 $dbrAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
81 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on replica" );
82 $this->assertNotEquals(
83 $dbr, $dbrAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
84
85 $dbwAuto2 = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTOCOMMIT );
86 $this->assertEquals( $dbwAuto2, $dbwAuto, "CONN_TRX_AUTOCOMMIT reuses connections" );
87 }
88
89 $lb->closeAll();
90 }
91
92 public function testWithReplica() {
93 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
94
95 $servers = [
96 [ // master
97 'host' => $wgDBserver,
98 'dbname' => $wgDBname,
99 'tablePrefix' => $this->dbPrefix(),
100 'user' => $wgDBuser,
101 'password' => $wgDBpassword,
102 'type' => $wgDBtype,
103 'dbDirectory' => $wgSQLiteDataDir,
104 'load' => 0,
105 'flags' => DBO_TRX // REPEATABLE-READ for consistency
106 ],
107 [ // emulated replica
108 'host' => $wgDBserver,
109 'dbname' => $wgDBname,
110 'tablePrefix' => $this->dbPrefix(),
111 'user' => $wgDBuser,
112 'password' => $wgDBpassword,
113 'type' => $wgDBtype,
114 'dbDirectory' => $wgSQLiteDataDir,
115 'load' => 100,
116 'flags' => DBO_TRX // REPEATABLE-READ for consistency
117 ]
118 ];
119
120 $lb = new LoadBalancer( [
121 'servers' => $servers,
122 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() ),
123 'loadMonitorClass' => LoadMonitorNull::class
124 ] );
125
126 $dbw = $lb->getConnection( DB_MASTER );
127 $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
128 $this->assertEquals(
129 ( $wgDBserver != '' ) ? $wgDBserver : 'localhost',
130 $dbw->getLBInfo( 'clusterMasterHost' ),
131 'cluster master set' );
132 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on master" );
133 $this->assertWriteAllowed( $dbw );
134
135 $dbr = $lb->getConnection( DB_REPLICA );
136 $this->assertTrue( $dbr->getLBInfo( 'replica' ), 'replica shows as replica' );
137 $this->assertEquals(
138 ( $wgDBserver != '' ) ? $wgDBserver : 'localhost',
139 $dbr->getLBInfo( 'clusterMasterHost' ),
140 'cluster master set' );
141 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on replica" );
142 $this->assertWriteForbidden( $dbr );
143
144 if ( !$lb->getServerAttributes( $lb->getWriterIndex() )[$dbw::ATTR_DB_LEVEL_LOCKING] ) {
145 $dbwAuto = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTOCOMMIT );
146 $this->assertFalse(
147 $dbwAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
148 $this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on master" );
149 $this->assertNotEquals(
150 $dbw, $dbwAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
151
152 $dbrAuto = $lb->getConnection( DB_REPLICA, [], false, $lb::CONN_TRX_AUTOCOMMIT );
153 $this->assertFalse(
154 $dbrAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
155 $this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on replica" );
156 $this->assertNotEquals(
157 $dbr, $dbrAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
158
159 $dbwAuto2 = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTOCOMMIT );
160 $this->assertEquals( $dbwAuto2, $dbwAuto, "CONN_TRX_AUTOCOMMIT reuses connections" );
161 }
162
163 $lb->closeAll();
164 }
165
166 private function assertWriteForbidden( Database $db ) {
167 try {
168 $db->delete( 'some_table', [ 'id' => 57634126 ], __METHOD__ );
169 $this->fail( 'Write operation should have failed!' );
170 } catch ( DBError $ex ) {
171 // check that the exception message contains "Write operation"
172 $constraint = new PHPUnit_Framework_Constraint_StringContains( 'Write operation' );
173
174 if ( !$constraint->evaluate( $ex->getMessage(), '', true ) ) {
175 // re-throw original error, to preserve stack trace
176 throw $ex;
177 }
178 } finally {
179 $db->rollback( __METHOD__, 'flush' );
180 }
181 }
182
183 private function assertWriteAllowed( Database $db ) {
184 $table = $db->tableName( 'some_table' );
185 try {
186 $db->dropTable( 'some_table' ); // clear for sanity
187 // Use only basic SQL and trivial types for these queries for compatibility
188 $this->assertNotSame(
189 false,
190 $db->query( "CREATE TABLE $table (id INT, time INT)", __METHOD__ ),
191 "table created"
192 );
193 $this->assertNotSame(
194 false,
195 $db->query( "DELETE FROM $table WHERE id=57634126", __METHOD__ ),
196 "delete query"
197 );
198 $this->assertNotSame(
199 false,
200 $db->query( "DROP TABLE $table", __METHOD__ ),
201 "table dropped"
202 );
203 } finally {
204 $db->rollback( __METHOD__, 'flush' );
205 }
206 }
207
208 public function testServerAttributes() {
209 $servers = [
210 [ // master
211 'dbname' => 'my_unittest_wiki',
212 'tablePrefix' => 'unittest_',
213 'type' => 'sqlite',
214 'dbDirectory' => "some_directory",
215 'load' => 0
216 ]
217 ];
218
219 $lb = new LoadBalancer( [
220 'servers' => $servers,
221 'localDomain' => new DatabaseDomain( 'my_unittest_wiki', null, 'unittest_' ),
222 'loadMonitorClass' => LoadMonitorNull::class
223 ] );
224
225 $this->assertTrue( $lb->getServerAttributes( 0 )[Database::ATTR_DB_LEVEL_LOCKING] );
226
227 $servers = [
228 [ // master
229 'host' => 'db1001',
230 'user' => 'wikiuser',
231 'password' => 'none',
232 'dbname' => 'my_unittest_wiki',
233 'tablePrefix' => 'unittest_',
234 'type' => 'mysql',
235 'load' => 100
236 ],
237 [ // emulated replica
238 'host' => 'db1002',
239 'user' => 'wikiuser',
240 'password' => 'none',
241 'dbname' => 'my_unittest_wiki',
242 'tablePrefix' => 'unittest_',
243 'type' => 'mysql',
244 'load' => 100
245 ]
246 ];
247
248 $lb = new LoadBalancer( [
249 'servers' => $servers,
250 'localDomain' => new DatabaseDomain( 'my_unittest_wiki', null, 'unittest_' ),
251 'loadMonitorClass' => LoadMonitorNull::class
252 ] );
253
254 $this->assertFalse( $lb->getServerAttributes( 1 )[Database::ATTR_DB_LEVEL_LOCKING] );
255 }
256 }