MasterSlaveReplicationTest.php 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172
  1. <?php
  2. /*
  3. * This file is part of the Predis package.
  4. *
  5. * (c) Daniele Alessandri <suppakilla@gmail.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Predis\Connection\Replication;
  11. use Predis\Command;
  12. use Predis\Connection;
  13. use Predis\Replication\ReplicationStrategy;
  14. use Predis\Response;
  15. use PredisTestCase;
  16. /**
  17. *
  18. */
  19. class MasterSlaveReplicationTest extends PredisTestCase
  20. {
  21. /**
  22. * @group disconnected
  23. */
  24. public function testAddingConnectionsToReplication()
  25. {
  26. $master = $this->getMockConnection('tcp://host1?alias=master');
  27. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  28. $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
  29. $replication = new MasterSlaveReplication();
  30. $replication->add($master);
  31. $replication->add($slave1);
  32. $replication->add($slave2);
  33. $this->assertSame($master, $replication->getConnectionById('master'));
  34. $this->assertSame($slave1, $replication->getConnectionById('slave1'));
  35. $this->assertSame($slave2, $replication->getConnectionById('slave2'));
  36. $this->assertSame($master, $replication->getMaster());
  37. $this->assertSame(array($slave1, $slave2), $replication->getSlaves());
  38. }
  39. /**
  40. * @group disconnected
  41. */
  42. public function testRemovingConnectionsFromReplication()
  43. {
  44. $master = $this->getMockConnection('tcp://host1?alias=master');
  45. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  46. $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
  47. $replication = new MasterSlaveReplication();
  48. $replication->add($master);
  49. $replication->add($slave1);
  50. $this->assertTrue($replication->remove($slave1));
  51. $this->assertFalse($replication->remove($slave2));
  52. $this->assertSame($master, $replication->getMaster());
  53. $this->assertSame(array(), $replication->getSlaves());
  54. }
  55. /**
  56. * @group disconnected
  57. */
  58. public function testAddingConnectionsToReplicationWithoutAliasesResultsInCustomId()
  59. {
  60. $slave1 = $this->getMockConnection('tcp://host1');
  61. $slave2 = $this->getMockConnection('tcp://host2:6380');
  62. $replication = new MasterSlaveReplication();
  63. $replication->add($slave1);
  64. $replication->add($slave2);
  65. $this->assertSame($slave1, $replication->getConnectionById('slave-host1:6379'));
  66. $this->assertSame($slave2, $replication->getConnectionById('slave-host2:6380'));
  67. }
  68. /**
  69. * @group disconnected
  70. * @expectedException \Predis\ClientException
  71. * @expectedExceptionMessage No available connection for replication
  72. */
  73. public function testThrowsExceptionOnEmptyReplication()
  74. {
  75. $replication = new MasterSlaveReplication();
  76. $replication->connect();
  77. }
  78. /**
  79. * @group disconnected
  80. */
  81. public function testConnectsToOneOfSlaves()
  82. {
  83. $master = $this->getMockConnection('tcp://host1?alias=master');
  84. $master->expects($this->never())->method('connect');
  85. $slave = $this->getMockConnection('tcp://host2?alias=slave1');
  86. $slave->expects($this->once())->method('connect');
  87. $replication = new MasterSlaveReplication();
  88. $replication->add($master);
  89. $replication->add($slave);
  90. $replication->connect();
  91. }
  92. /**
  93. * @group disconnected
  94. */
  95. public function testConnectsToMasterOnMissingSlaves()
  96. {
  97. $master = $this->getMockConnection('tcp://host1?alias=master');
  98. $replication = new MasterSlaveReplication();
  99. $replication->add($master);
  100. $replication->connect();
  101. $this->assertSame($master, $replication->getCurrent());
  102. }
  103. /**
  104. * @group disconnected
  105. */
  106. public function testIsConnectedReturnsTrueIfAtLeastOneConnectionIsOpen()
  107. {
  108. $master = $this->getMockConnection('tcp://host1?alias=master');
  109. $master->expects($this->never())->method('isConnected')->will($this->returnValue(false));
  110. $slave = $this->getMockConnection('tcp://host2?alias=slave1');
  111. $slave->expects($this->once())->method('isConnected')->will($this->returnValue(true));
  112. $replication = new MasterSlaveReplication();
  113. $replication->add($master);
  114. $replication->add($slave);
  115. $replication->connect();
  116. $this->assertTrue($replication->isConnected());
  117. }
  118. /**
  119. * @group disconnected
  120. */
  121. public function testIsConnectedReturnsFalseIfAllConnectionsAreClosed()
  122. {
  123. $master = $this->getMockConnection('tcp://host1?alias=master');
  124. $master->expects($this->any())->method('isConnected')->will($this->returnValue(false));
  125. $slave = $this->getMockConnection('tcp://host2?alias=slave1');
  126. $slave->expects($this->any())->method('isConnected')->will($this->returnValue(false));
  127. $replication = new MasterSlaveReplication();
  128. $replication->add($master);
  129. $replication->add($slave);
  130. $this->assertFalse($replication->isConnected());
  131. $replication->connect();
  132. $replication->disconnect();
  133. $this->assertFalse($replication->isConnected());
  134. }
  135. /**
  136. * @group disconnected
  137. */
  138. public function testDisconnectForcesCurrentConnectionToDisconnect()
  139. {
  140. $master = $this->getMockConnection('tcp://host1?alias=master');
  141. $master->expects($this->once())->method('disconnect');
  142. $slave = $this->getMockConnection('tcp://host2?alias=slave1');
  143. $slave->expects($this->once())->method('disconnect');
  144. $replication = new MasterSlaveReplication();
  145. $replication->add($master);
  146. $replication->add($slave);
  147. $replication->disconnect();
  148. }
  149. /**
  150. * @group disconnected
  151. */
  152. public function testCanSwitchConnectionByAlias()
  153. {
  154. $master = $this->getMockConnection('tcp://host1?alias=master');
  155. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  156. $replication = new MasterSlaveReplication();
  157. $replication->add($master);
  158. $replication->add($slave1);
  159. $this->assertNull($replication->getCurrent());
  160. $replication->switchTo('master');
  161. $this->assertSame($master, $replication->getCurrent());
  162. $replication->switchTo('slave1');
  163. $this->assertSame($slave1, $replication->getCurrent());
  164. }
  165. /**
  166. * @group disconnected
  167. * @expectedException \InvalidArgumentException
  168. * @expectedExceptionMessage Invalid connection or connection not found.
  169. */
  170. public function testThrowsErrorWhenSwitchingToUnknownConnectionByAlias()
  171. {
  172. $replication = new MasterSlaveReplication();
  173. $replication->add($this->getMockConnection('tcp://host1?alias=master'));
  174. $replication->add($this->getMockConnection('tcp://host2?alias=slave1'));
  175. $replication->switchTo('unknown');
  176. }
  177. /**
  178. * @group disconnected
  179. */
  180. public function testCanSwitchConnectionByInstance()
  181. {
  182. $master = $this->getMockConnection('tcp://host1?alias=master');
  183. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  184. $replication = new MasterSlaveReplication();
  185. $replication->add($master);
  186. $replication->add($slave1);
  187. $this->assertNull($replication->getCurrent());
  188. $replication->switchTo($master);
  189. $this->assertSame($master, $replication->getCurrent());
  190. $replication->switchTo($slave1);
  191. $this->assertSame($slave1, $replication->getCurrent());
  192. }
  193. /**
  194. * @group disconnected
  195. * @expectedException \InvalidArgumentException
  196. * @expectedExceptionMessage Invalid connection or connection not found.
  197. */
  198. public function testThrowsErrorWhenSwitchingToUnknownConnectionByInstance()
  199. {
  200. $replication = new MasterSlaveReplication();
  201. $replication->add($this->getMockConnection('tcp://host1?alias=master'));
  202. $replication->add($this->getMockConnection('tcp://host2?alias=slave1'));
  203. $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
  204. $replication->switchTo($slave2);
  205. }
  206. /**
  207. * @group disconnected
  208. */
  209. public function testCanSwitchToMaster()
  210. {
  211. $master = $this->getMockConnection('tcp://host1?alias=master');
  212. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  213. $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
  214. $replication = new MasterSlaveReplication();
  215. $replication->add($master);
  216. $replication->add($slave1);
  217. $replication->add($slave2);
  218. $this->assertNull($replication->getCurrent());
  219. $replication->switchToMaster();
  220. $this->assertSame($master, $replication->getCurrent());
  221. }
  222. /**
  223. * @group disconnected
  224. * @expectedException \InvalidArgumentException
  225. * @expectedExceptionMessage Invalid connection or connection not found.
  226. */
  227. public function testThrowsErrorOnSwitchToMasterWithNoMasterDefined()
  228. {
  229. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  230. $replication = new MasterSlaveReplication();
  231. $replication->add($slave1);
  232. $replication->switchToMaster();
  233. }
  234. /**
  235. * @group disconnected
  236. *
  237. * @todo We should find a way to test that the slave is indeed randomly selected.
  238. */
  239. public function testCanSwitchToRandomSlave()
  240. {
  241. $master = $this->getMockConnection('tcp://host1?alias=master');
  242. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  243. $replication = new MasterSlaveReplication();
  244. $replication->add($master);
  245. $replication->add($slave1);
  246. $this->assertNull($replication->getCurrent());
  247. $replication->switchToSlave();
  248. $this->assertSame($slave1, $replication->getCurrent());
  249. }
  250. /**
  251. * @group disconnected
  252. * @expectedException \InvalidArgumentException
  253. * @expectedExceptionMessage Invalid connection or connection not found.
  254. */
  255. public function testThrowsErrorOnSwitchToRandomSlaveWithNoSlavesDefined()
  256. {
  257. $master = $this->getMockConnection('tcp://host1?alias=master');
  258. $replication = new MasterSlaveReplication();
  259. $replication->add($master);
  260. $replication->switchToSlave();
  261. }
  262. /**
  263. * @group disconnected
  264. */
  265. public function testUsesSlavesOnReadOnlyCommands()
  266. {
  267. $commands = $this->getCommandFactory();
  268. $master = $this->getMockConnection('tcp://host1?alias=master');
  269. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  270. $replication = new MasterSlaveReplication();
  271. $replication->add($master);
  272. $replication->add($slave1);
  273. $cmd = $commands->createCommand('exists', array('foo'));
  274. $this->assertSame($slave1, $replication->getConnection($cmd));
  275. $cmd = $commands->createCommand('get', array('foo'));
  276. $this->assertSame($slave1, $replication->getConnection($cmd));
  277. }
  278. /**
  279. * @group disconnected
  280. */
  281. public function testUsesMasterOnWriteRequests()
  282. {
  283. $commands = $this->getCommandFactory();
  284. $master = $this->getMockConnection('tcp://host1?alias=master');
  285. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  286. $replication = new MasterSlaveReplication();
  287. $replication->add($master);
  288. $replication->add($slave1);
  289. $cmd = $commands->createCommand('set', array('foo', 'bar'));
  290. $this->assertSame($master, $replication->getConnection($cmd));
  291. $cmd = $commands->createCommand('get', array('foo'));
  292. $this->assertSame($master, $replication->getConnection($cmd));
  293. }
  294. /**
  295. * @group disconnected
  296. */
  297. public function testUsesMasterOnReadRequestsWhenNoSlavesAvailable()
  298. {
  299. $commands = $this->getCommandFactory();
  300. $master = $this->getMockConnection('tcp://host1?alias=master');
  301. $replication = new MasterSlaveReplication();
  302. $replication->add($master);
  303. $cmd = $commands->createCommand('exists', array('foo'));
  304. $this->assertSame($master, $replication->getConnection($cmd));
  305. $cmd = $commands->createCommand('set', array('foo', 'bar'));
  306. $this->assertSame($master, $replication->getConnection($cmd));
  307. }
  308. /**
  309. * @group disconnected
  310. */
  311. public function testSwitchesFromSlaveToMasterOnWriteRequestss()
  312. {
  313. $commands = $this->getCommandFactory();
  314. $master = $this->getMockConnection('tcp://host1?alias=master');
  315. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  316. $replication = new MasterSlaveReplication();
  317. $replication->add($master);
  318. $replication->add($slave1);
  319. $cmd = $commands->createCommand('exists', array('foo'));
  320. $this->assertSame($slave1, $replication->getConnection($cmd));
  321. $cmd = $commands->createCommand('set', array('foo', 'bar'));
  322. $this->assertSame($master, $replication->getConnection($cmd));
  323. $cmd = $commands->createCommand('exists', array('foo'));
  324. $this->assertSame($master, $replication->getConnection($cmd));
  325. }
  326. /**
  327. * @group disconnected
  328. */
  329. public function testWritesCommandToCorrectConnection()
  330. {
  331. $commands = $this->getCommandFactory();
  332. $cmdExists = $commands->createCommand('exists', array('foo'));
  333. $cmdSet = $commands->createCommand('set', array('foo', 'bar'));
  334. $master = $this->getMockConnection('tcp://host1?alias=master');
  335. $master->expects($this->once())->method('writeRequest')->with($cmdSet);
  336. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  337. $slave1->expects($this->once())->method('writeRequest')->with($cmdExists);
  338. $replication = new MasterSlaveReplication();
  339. $replication->add($master);
  340. $replication->add($slave1);
  341. $replication->writeRequest($cmdExists);
  342. $replication->writeRequest($cmdSet);
  343. }
  344. /**
  345. * @group disconnected
  346. */
  347. public function testReadsCommandFromCorrectConnection()
  348. {
  349. $commands = $this->getCommandFactory();
  350. $cmdExists = $commands->createCommand('exists', array('foo'));
  351. $cmdSet = $commands->createCommand('set', array('foo', 'bar'));
  352. $master = $this->getMockConnection('tcp://host1?alias=master');
  353. $master->expects($this->once())->method('readResponse')->with($cmdSet);
  354. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  355. $slave1->expects($this->once())->method('readResponse')->with($cmdExists);
  356. $replication = new MasterSlaveReplication();
  357. $replication->add($master);
  358. $replication->add($slave1);
  359. $replication->readResponse($cmdExists);
  360. $replication->readResponse($cmdSet);
  361. }
  362. /**
  363. * @group disconnected
  364. */
  365. public function testExecutesCommandOnCorrectConnection()
  366. {
  367. $commands = $this->getCommandFactory();
  368. $cmdExists = $commands->createCommand('exists', array('foo'));
  369. $cmdSet = $commands->createCommand('set', array('foo', 'bar'));
  370. $master = $this->getMockConnection('tcp://host1?alias=master');
  371. $master->expects($this->once())->method('executeCommand')->with($cmdSet);
  372. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  373. $slave1->expects($this->once())->method('executeCommand')->with($cmdExists);
  374. $replication = new MasterSlaveReplication();
  375. $replication->add($master);
  376. $replication->add($slave1);
  377. $replication->executeCommand($cmdExists);
  378. $replication->executeCommand($cmdSet);
  379. }
  380. /**
  381. * @group disconnected
  382. */
  383. public function testWatchTriggersSwitchToMasterConnection()
  384. {
  385. $commands = $this->getCommandFactory();
  386. $cmdWatch = $commands->createCommand('watch', array('foo'));
  387. $master = $this->getMockConnection('tcp://host1?alias=master');
  388. $master->expects($this->once())->method('executeCommand')->with($cmdWatch);
  389. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  390. $slave1->expects($this->never())->method('executeCommand');
  391. $replication = new MasterSlaveReplication();
  392. $replication->add($master);
  393. $replication->add($slave1);
  394. $replication->executeCommand($cmdWatch);
  395. }
  396. /**
  397. * @group disconnected
  398. */
  399. public function testMultiTriggersSwitchToMasterConnection()
  400. {
  401. $commands = $this->getCommandFactory();
  402. $cmdMulti = $commands->createCommand('multi');
  403. $master = $this->getMockConnection('tcp://host1?alias=master');
  404. $master->expects($this->once())->method('executeCommand')->with($cmdMulti);
  405. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  406. $slave1->expects($this->never())->method('executeCommand');
  407. $replication = new MasterSlaveReplication();
  408. $replication->add($master);
  409. $replication->add($slave1);
  410. $replication->executeCommand($cmdMulti);
  411. }
  412. /**
  413. * @group disconnected
  414. */
  415. public function testEvalTriggersSwitchToMasterConnection()
  416. {
  417. $commands = $this->getCommandFactory();
  418. $cmdEval = $commands->createCommand('eval', array("return redis.call('info')"));
  419. $master = $this->getMockConnection('tcp://host1?alias=master');
  420. $master->expects($this->once())->method('executeCommand')->with($cmdEval);
  421. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  422. $slave1->expects($this->never())->method('executeCommand');
  423. $replication = new MasterSlaveReplication();
  424. $replication->add($master);
  425. $replication->add($slave1);
  426. $replication->executeCommand($cmdEval);
  427. }
  428. /**
  429. * @group disconnected
  430. */
  431. public function testSortTriggersSwitchToMasterConnectionOnStoreModifier()
  432. {
  433. $commands = $this->getCommandFactory();
  434. $cmdSortNormal = $commands->createCommand('sort', array('key'));
  435. $cmdSortStore = $commands->createCommand('sort', array('key', array('store' => 'key:store')));
  436. $master = $this->getMockConnection('tcp://host1?alias=master');
  437. $master->expects($this->once())->method('executeCommand')->with($cmdSortStore);
  438. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  439. $slave1->expects($this->once())->method('executeCommand')->with($cmdSortNormal);
  440. $replication = new MasterSlaveReplication();
  441. $replication->add($master);
  442. $replication->add($slave1);
  443. $replication->executeCommand($cmdSortNormal);
  444. $replication->executeCommand($cmdSortStore);
  445. }
  446. /**
  447. * @group disconnected
  448. */
  449. public function testDiscardsUnreachableSlaveAndExecutesReadOnlyCommandOnNextSlave()
  450. {
  451. $commands = $this->getCommandFactory();
  452. $cmdExists = $commands->createCommand('exists', array('key'));
  453. $master = $this->getMockConnection('tcp://host1?alias=master');
  454. $master->expects($this->never())->method('executeCommand');
  455. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  456. $slave1->expects($this->once())
  457. ->method('executeCommand')
  458. ->with($cmdExists)
  459. ->will($this->throwException(new Connection\ConnectionException($slave1)));
  460. $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
  461. $slave2->expects($this->once())
  462. ->method('executeCommand')
  463. ->with($cmdExists)
  464. ->will($this->returnValue(1));
  465. $replication = new MasterSlaveReplication();
  466. $replication->add($master);
  467. $replication->add($slave1);
  468. $replication->add($slave2);
  469. $replication->switchTo($slave1);
  470. $response = $replication->executeCommand($cmdExists);
  471. $this->assertSame(1, $response);
  472. $this->assertNull($replication->getConnectionById('slave1'));
  473. $this->assertSame($slave2, $replication->getConnectionById('slave2'));
  474. }
  475. /**
  476. * @group disconnected
  477. */
  478. public function testDiscardsUnreachableSlavesAndExecutesReadOnlyCommandOnMaster()
  479. {
  480. $commands = $this->getCommandFactory();
  481. $cmdExists = $commands->createCommand('exists', array('key'));
  482. $master = $this->getMockConnection('tcp://host1?alias=master');
  483. $master->expects($this->once())
  484. ->method('executeCommand')
  485. ->with($cmdExists)
  486. ->will($this->returnValue(1));
  487. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  488. $slave1->expects($this->once())
  489. ->method('executeCommand')
  490. ->with($cmdExists)
  491. ->will($this->throwException(new Connection\ConnectionException($slave1)));
  492. $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
  493. $slave2->expects($this->once())
  494. ->method('executeCommand')
  495. ->with($cmdExists)
  496. ->will($this->throwException(new Connection\ConnectionException($slave2)));
  497. $replication = new MasterSlaveReplication();
  498. $replication->add($master);
  499. $replication->add($slave1);
  500. $replication->add($slave2);
  501. $replication->switchTo($slave1);
  502. $response = $replication->executeCommand($cmdExists);
  503. $this->assertSame(1, $response);
  504. $this->assertNull($replication->getConnectionById('slave1'));
  505. $this->assertNull($replication->getConnectionById('slave2'));
  506. }
  507. /**
  508. * @group disconnected
  509. */
  510. public function testSucceedOnReadOnlyCommandAndNoConnectionSetAsMaster()
  511. {
  512. $commands = $this->getCommandFactory();
  513. $cmdExists = $commands->createCommand('exists', array('key'));
  514. $slave1 = $this->getMockConnection('tcp://host1?alias=slave1');
  515. $slave1->expects($this->once())
  516. ->method('executeCommand')
  517. ->with($cmdExists)
  518. ->will($this->returnValue(1));
  519. $replication = new MasterSlaveReplication();
  520. $replication->add($slave1);
  521. $response = $replication->executeCommand($cmdExists);
  522. $this->assertSame(1, $response);
  523. }
  524. /**
  525. * @group disconnected
  526. * @expectedException \Predis\Replication\MissingMasterException
  527. * @expectedMessage No master server available for replication
  528. */
  529. public function testFailsOnWriteCommandAndNoConnectionSetAsMaster()
  530. {
  531. $commands = $this->getCommandFactory();
  532. $cmdSet = $commands->createCommand('set', array('key', 'value'));
  533. $slave1 = $this->getMockConnection('tcp://host1?alias=slave1');
  534. $slave1->expects($this->never())->method('executeCommand');
  535. $replication = new MasterSlaveReplication();
  536. $replication->add($slave1);
  537. $replication->executeCommand($cmdSet);
  538. }
  539. /**
  540. * @group disconnected
  541. */
  542. public function testDiscardsSlaveWhenRespondsLOADINGAndExecutesReadOnlyCommandOnNextSlave()
  543. {
  544. $master = $this->getMockConnection('tcp://host1?alias=master');
  545. $master->expects($this->never())
  546. ->method('executeCommand');
  547. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  548. $slave1->expects($this->once())
  549. ->method('executeCommand')
  550. ->with($this->isRedisCommand(
  551. 'EXISTS', array('key')
  552. ))
  553. ->will($this->returnValue(
  554. new Response\Error('LOADING')
  555. ));
  556. $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
  557. $slave2->expects($this->once())
  558. ->method('executeCommand')
  559. ->with($this->isRedisCommand(
  560. 'EXISTS', array('key')
  561. ))
  562. ->will($this->returnValue(1));
  563. $replication = new MasterSlaveReplication();
  564. $replication->add($master);
  565. $replication->add($slave1);
  566. $replication->add($slave2);
  567. $replication->switchTo($slave1);
  568. $response = $replication->executeCommand(
  569. Command\RawCommand::create('exists', 'key')
  570. );
  571. $this->assertSame(1, $response);
  572. $this->assertNull($replication->getConnectionById('slave1'));
  573. $this->assertSame($slave2, $replication->getConnectionById('slave2'));
  574. }
  575. /**
  576. * @group disconnected
  577. * @expectedException \Predis\Connection\ConnectionException
  578. */
  579. public function testFailsOnUnreachableMaster()
  580. {
  581. $commands = $this->getCommandFactory();
  582. $cmdSet = $commands->createCommand('set', array('key', 'value'));
  583. $master = $this->getMockConnection('tcp://host1?alias=master');
  584. $master->expects($this->once())
  585. ->method('executeCommand')
  586. ->with($cmdSet)
  587. ->will($this->throwException(new Connection\ConnectionException($master)));
  588. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  589. $slave1->expects($this->never())
  590. ->method('executeCommand');
  591. $replication = new MasterSlaveReplication();
  592. $replication->add($master);
  593. $replication->add($slave1);
  594. $replication->executeCommand($cmdSet);
  595. }
  596. /**
  597. * @group disconnected
  598. * @expectedException \Predis\NotSupportedException
  599. * @expectedExceptionMessage The command 'INFO' is not allowed in replication mode.
  600. */
  601. public function testThrowsExceptionOnNonSupportedCommand()
  602. {
  603. $cmd = $this->getCommandFactory()->createCommand('info');
  604. $replication = new MasterSlaveReplication();
  605. $replication->add($this->getMockConnection('tcp://host1?alias=master'));
  606. $replication->add($this->getMockConnection('tcp://host2?alias=slave1'));
  607. $replication->getConnection($cmd);
  608. }
  609. /**
  610. * @group disconnected
  611. */
  612. public function testCanOverrideReadOnlyFlagForCommands()
  613. {
  614. $commands = $this->getCommandFactory();
  615. $cmdSet = $commands->createCommand('set', array('foo', 'bar'));
  616. $cmdGet = $commands->createCommand('get', array('foo'));
  617. $master = $this->getMockConnection('tcp://host1?alias=master');
  618. $master->expects($this->once())->method('executeCommand')->with($cmdGet);
  619. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  620. $slave1->expects($this->once())->method('executeCommand')->with($cmdSet);
  621. $replication = new MasterSlaveReplication();
  622. $replication->add($master);
  623. $replication->add($slave1);
  624. $replication->getReplicationStrategy()->setCommandReadOnly($cmdSet->getId(), true);
  625. $replication->getReplicationStrategy()->setCommandReadOnly($cmdGet->getId(), false);
  626. $replication->executeCommand($cmdSet);
  627. $replication->executeCommand($cmdGet);
  628. }
  629. /**
  630. * @group disconnected
  631. */
  632. public function testAcceptsCallableToOverrideReadOnlyFlagForCommands()
  633. {
  634. $commands = $this->getCommandFactory();
  635. $cmdExistsFoo = $commands->createCommand('exists', array('foo'));
  636. $cmdExistsBar = $commands->createCommand('exists', array('bar'));
  637. $master = $this->getMockConnection('tcp://host1?alias=master');
  638. $master->expects($this->once())->method('executeCommand')->with($cmdExistsBar);
  639. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  640. $slave1->expects($this->once())->method('executeCommand')->with($cmdExistsFoo);
  641. $replication = new MasterSlaveReplication();
  642. $replication->add($master);
  643. $replication->add($slave1);
  644. $replication->getReplicationStrategy()->setCommandReadOnly('exists', function ($cmd) {
  645. list($arg1) = $cmd->getArguments();
  646. return $arg1 === 'foo';
  647. });
  648. $replication->executeCommand($cmdExistsFoo);
  649. $replication->executeCommand($cmdExistsBar);
  650. }
  651. /**
  652. * @group disconnected
  653. */
  654. public function testCanSetReadOnlyFlagForEvalScripts()
  655. {
  656. $commands = $this->getCommandFactory();
  657. $cmdEval = $commands->createCommand('eval', array($script = "return redis.call('info');"));
  658. $cmdEvalSha = $commands->createCommand('evalsha', array($scriptSHA1 = sha1($script)));
  659. $master = $this->getMockConnection('tcp://host1?alias=master');
  660. $master->expects($this->never())->method('executeCommand');
  661. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  662. $slave1->expects($this->exactly(2))
  663. ->method('executeCommand')
  664. ->with($this->logicalOr($cmdEval, $cmdEvalSha));
  665. $replication = new MasterSlaveReplication();
  666. $replication->add($master);
  667. $replication->add($slave1);
  668. $replication->getReplicationStrategy()->setScriptReadOnly($script);
  669. $replication->executeCommand($cmdEval);
  670. $replication->executeCommand($cmdEvalSha);
  671. }
  672. /**
  673. * @group disconnected
  674. * @expectedException \Predis\ClientException
  675. * @expectedMessage Discovery requires a connection factory
  676. */
  677. public function testDiscoveryRequiresConnectionFactory()
  678. {
  679. $master = $this->getMockConnection('tcp://host1?alias=master');
  680. $replication = new MasterSlaveReplication();
  681. $replication->add($master);
  682. $replication->discover();
  683. }
  684. /**
  685. * @group disconnected
  686. */
  687. public function testDiscoversReplicationConfigurationFromMaster()
  688. {
  689. $connFactory = new Connection\Factory();
  690. $cmdInfo = Command\RawCommand::create('INFO', 'REPLICATION');
  691. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  692. $master->expects($this->once())
  693. ->method('executeCommand')
  694. ->with($cmdInfo)
  695. ->will($this->returnValue('
  696. # Replication
  697. role:master
  698. connected_slaves:2
  699. slave0:ip=127.0.0.1,port=6382,state=online,offset=12979,lag=0
  700. slave1:ip=127.0.0.1,port=6383,state=online,offset=12979,lag=1
  701. master_repl_offset:12979
  702. repl_backlog_active:1
  703. repl_backlog_size:1048576
  704. repl_backlog_first_byte_offset:2
  705. repl_backlog_histlen:12978
  706. '));
  707. $replication = new MasterSlaveReplication();
  708. $replication->setConnectionFactory($connFactory);
  709. $replication->add($master);
  710. $replication->discover();
  711. $this->assertCount(2, $slaves = $replication->getSlaves());
  712. $this->assertContainsOnlyInstancesOf('Predis\Connection\ConnectionInterface', $slaves);
  713. $this->assertSame('127.0.0.1:6381', (string) $replication->getMaster());
  714. $this->assertSame('127.0.0.1:6382', (string) $slaves[0]);
  715. $this->assertSame('127.0.0.1:6383', (string) $slaves[1]);
  716. }
  717. /**
  718. * @group disconnected
  719. */
  720. public function testDiscoversReplicationConfigurationFromSlave()
  721. {
  722. $cmdInfo = $command = Command\RawCommand::create('INFO', 'REPLICATION');
  723. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  724. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  725. $slave2 = $this->getMockConnection('tcp://127.0.0.1:6383?alias=slave2');
  726. $connFactory = $this->getMock('Predis\Connection\Factory');
  727. $connFactory->expects($this->at(0))
  728. ->method('create')
  729. ->with(array('host' => '127.0.0.1', 'port' => '6381', 'alias' => 'master'))
  730. ->will($this->returnValue($master));
  731. $connFactory->expects($this->at(1))
  732. ->method('create')
  733. ->with(array('host' => '127.0.0.1', 'port' => '6382'))
  734. ->will($this->returnValue($slave1));
  735. $connFactory->expects($this->at(2))
  736. ->method('create')
  737. ->with(array('host' => '127.0.0.1', 'port' => '6383'))
  738. ->will($this->returnValue($slave2));
  739. $slave1->expects($this->once())
  740. ->method('executeCommand')
  741. ->with($cmdInfo)
  742. ->will($this->returnValue('
  743. # Replication
  744. role:slave
  745. master_host:127.0.0.1
  746. master_port:6381
  747. master_link_status:up
  748. master_last_io_seconds_ago:8
  749. master_sync_in_progress:0
  750. slave_repl_offset:17715532
  751. slave_priority:100
  752. slave_read_only:1
  753. connected_slaves:0
  754. master_repl_offset:0
  755. repl_backlog_active:0
  756. repl_backlog_size:1048576
  757. repl_backlog_first_byte_offset:0
  758. repl_backlog_histlen:0
  759. '));
  760. $master->expects($this->once())
  761. ->method('executeCommand')
  762. ->with($cmdInfo)
  763. ->will($this->returnValue('
  764. # Replication
  765. role:master
  766. connected_slaves:2
  767. slave0:ip=127.0.0.1,port=6382,state=online,offset=12979,lag=0
  768. slave1:ip=127.0.0.1,port=6383,state=online,offset=12979,lag=1
  769. master_repl_offset:12979
  770. repl_backlog_active:1
  771. repl_backlog_size:1048576
  772. repl_backlog_first_byte_offset:2
  773. repl_backlog_histlen:12978
  774. '));
  775. $replication = new MasterSlaveReplication();
  776. $replication->setConnectionFactory($connFactory);
  777. $replication->add($slave1);
  778. $replication->discover();
  779. $this->assertCount(2, $slaves = $replication->getSlaves());
  780. $this->assertContainsOnlyInstancesOf('Predis\Connection\ConnectionInterface', $slaves);
  781. $this->assertSame('127.0.0.1:6381', (string) $replication->getMaster());
  782. $this->assertSame('127.0.0.1:6382', (string) $slaves[0]);
  783. $this->assertSame('127.0.0.1:6383', (string) $slaves[1]);
  784. }
  785. /**
  786. * @group disconnected
  787. */
  788. public function testDiscoversReplicationConfigurationFromSlaveIfMasterFails()
  789. {
  790. $cmdInfo = $command = Command\RawCommand::create('INFO', 'REPLICATION');
  791. $masterKO = $this->getMockConnection('tcp://127.0.0.1:7381?alias=master');
  792. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  793. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  794. $slave2 = $this->getMockConnection('tcp://127.0.0.1:6383?alias=slave2');
  795. $connFactory = $this->getMock('Predis\Connection\Factory');
  796. $connFactory->expects($this->at(0))
  797. ->method('create')
  798. ->with(array('host' => '127.0.0.1', 'port' => '6381', 'alias' => 'master'))
  799. ->will($this->returnValue($master));
  800. $connFactory->expects($this->at(1))
  801. ->method('create')
  802. ->with(array('host' => '127.0.0.1', 'port' => '6382'))
  803. ->will($this->returnValue($slave1));
  804. $connFactory->expects($this->at(2))
  805. ->method('create')
  806. ->with(array('host' => '127.0.0.1', 'port' => '6383'))
  807. ->will($this->returnValue($slave2));
  808. $masterKO->expects($this->once())
  809. ->method('executeCommand')
  810. ->with($cmdInfo)
  811. ->will($this->throwException(new Connection\ConnectionException($masterKO)));
  812. $slave1->expects($this->once())
  813. ->method('executeCommand')
  814. ->with($cmdInfo)
  815. ->will($this->returnValue('
  816. # Replication
  817. role:slave
  818. master_host:127.0.0.1
  819. master_port:6381
  820. master_link_status:up
  821. master_last_io_seconds_ago:8
  822. master_sync_in_progress:0
  823. slave_repl_offset:17715532
  824. slave_priority:100
  825. slave_read_only:1
  826. connected_slaves:0
  827. master_repl_offset:0
  828. repl_backlog_active:0
  829. repl_backlog_size:1048576
  830. repl_backlog_first_byte_offset:0
  831. repl_backlog_histlen:0
  832. '));
  833. $master->expects($this->once())
  834. ->method('executeCommand')
  835. ->with($cmdInfo)
  836. ->will($this->returnValue('
  837. # Replication
  838. role:master
  839. connected_slaves:2
  840. slave0:ip=127.0.0.1,port=6382,state=online,offset=12979,lag=0
  841. slave1:ip=127.0.0.1,port=6383,state=online,offset=12979,lag=1
  842. master_repl_offset:12979
  843. repl_backlog_active:1
  844. repl_backlog_size:1048576
  845. repl_backlog_first_byte_offset:2
  846. repl_backlog_histlen:12978
  847. '));
  848. $replication = new MasterSlaveReplication();
  849. $replication->setConnectionFactory($connFactory);
  850. $replication->add($masterKO);
  851. $replication->add($slave1);
  852. $replication->discover();
  853. $this->assertCount(2, $slaves = $replication->getSlaves());
  854. $this->assertContainsOnlyInstancesOf('Predis\Connection\ConnectionInterface', $slaves);
  855. $this->assertSame('127.0.0.1:6381', (string) $replication->getMaster());
  856. $this->assertSame('127.0.0.1:6382', (string) $slaves[0]);
  857. $this->assertSame('127.0.0.1:6383', (string) $slaves[1]);
  858. }
  859. /**
  860. * @group disconnected
  861. * @expectedException \Predis\ClientException
  862. * @expectedMessage Automatic discovery requires a connection factory
  863. */
  864. public function testAutomaticDiscoveryRequiresConnectionFactory()
  865. {
  866. $master = $this->getMockConnection('tcp://host1?alias=master');
  867. $replication = new MasterSlaveReplication();
  868. $replication->add($master);
  869. $replication->setAutoDiscovery(true);
  870. }
  871. /**
  872. * @group disconnected
  873. */
  874. public function testAutomaticDiscoveryOnUnreachableServer()
  875. {
  876. $cmdInfo = $command = Command\RawCommand::create('INFO', 'REPLICATION');
  877. $cmdExists = $command = Command\RawCommand::create('EXISTS', 'key');
  878. $slaveKO = $this->getMockConnection('tcp://127.0.0.1:7382?alias=slaveKO');
  879. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  880. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  881. $connFactory = $this->getMock('Predis\Connection\Factory');
  882. $connFactory->expects($this->once())
  883. ->method('create')
  884. ->with(array('host' => '127.0.0.1', 'port' => '6382'))
  885. ->will($this->returnValue($slave1));
  886. $slaveKO->expects($this->once())
  887. ->method('executeCommand')
  888. ->with($cmdExists)
  889. ->will($this->throwException(new Connection\ConnectionException($slaveKO)));
  890. $slave1->expects($this->once())
  891. ->method('executeCommand')
  892. ->with($cmdExists)
  893. ->will($this->returnValue(1));
  894. $master->expects($this->once())
  895. ->method('executeCommand')
  896. ->with($cmdInfo)
  897. ->will($this->returnValue('
  898. # Replication
  899. role:master
  900. connected_slaves:2
  901. slave0:ip=127.0.0.1,port=6382,state=online,offset=12979,lag=0
  902. master_repl_offset:12979
  903. repl_backlog_active:1
  904. repl_backlog_size:1048576
  905. repl_backlog_first_byte_offset:2
  906. repl_backlog_histlen:12978
  907. '));
  908. $replication = new MasterSlaveReplication();
  909. $replication->setConnectionFactory($connFactory);
  910. $replication->setAutoDiscovery(true);
  911. $replication->add($master);
  912. $replication->add($slaveKO);
  913. $replication->executeCommand($cmdExists);
  914. }
  915. /**
  916. * @group disconnected
  917. */
  918. public function testExposesReplicationStrategy()
  919. {
  920. $replication = new MasterSlaveReplication();
  921. $this->assertInstanceOf('Predis\Replication\ReplicationStrategy', $replication->getReplicationStrategy());
  922. $strategy = new ReplicationStrategy();
  923. $replication = new MasterSlaveReplication($strategy);
  924. $this->assertSame($strategy, $replication->getReplicationStrategy());
  925. }
  926. /**
  927. * @group disconnected
  928. */
  929. public function testCanBeSerialized()
  930. {
  931. $master = $this->getMockConnection('tcp://host1?alias=master');
  932. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  933. $replication = new MasterSlaveReplication();
  934. $replication->add($master);
  935. $replication->add($slave1);
  936. $unserialized = unserialize(serialize($replication));
  937. $this->assertEquals($master, $unserialized->getConnectionById('master'));
  938. $this->assertEquals($slave1, $unserialized->getConnectionById('slave1'));
  939. }
  940. }