MasterSlaveReplicationTest.php 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204
  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\Aggregate;
  11. use Predis\Connection;
  12. use Predis\Command;
  13. use Predis\Profile;
  14. use Predis\Replication\ReplicationStrategy;
  15. use Predis\Response;
  16. use PredisTestCase;
  17. /**
  18. *
  19. */
  20. class MasterSlaveReplicationTest extends PredisTestCase
  21. {
  22. /**
  23. * @group disconnected
  24. */
  25. public function testAddingConnectionsToReplication()
  26. {
  27. $master = $this->getMockConnection('tcp://host1?alias=master');
  28. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  29. $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
  30. $replication = new MasterSlaveReplication();
  31. $replication->add($master);
  32. $replication->add($slave1);
  33. $replication->add($slave2);
  34. $this->assertSame($master, $replication->getConnectionById('master'));
  35. $this->assertSame($slave1, $replication->getConnectionById('slave1'));
  36. $this->assertSame($slave2, $replication->getConnectionById('slave2'));
  37. $this->assertSame($master, $replication->getMaster());
  38. $this->assertSame(array($slave1, $slave2), $replication->getSlaves());
  39. }
  40. /**
  41. * @group disconnected
  42. */
  43. public function testRemovingConnectionsFromReplication()
  44. {
  45. $master = $this->getMockConnection('tcp://host1?alias=master');
  46. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  47. $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
  48. $replication = new MasterSlaveReplication();
  49. $replication->add($master);
  50. $replication->add($slave1);
  51. $this->assertTrue($replication->remove($slave1));
  52. $this->assertFalse($replication->remove($slave2));
  53. $this->assertSame($master, $replication->getMaster());
  54. $this->assertSame(array(), $replication->getSlaves());
  55. }
  56. /**
  57. * @group disconnected
  58. */
  59. public function testAddingConnectionsToReplicationWithoutAliasesResultsInCustomId()
  60. {
  61. $slave1 = $this->getMockConnection('tcp://host1');
  62. $slave2 = $this->getMockConnection('tcp://host2:6380');
  63. $replication = new MasterSlaveReplication();
  64. $replication->add($slave1);
  65. $replication->add($slave2);
  66. $this->assertSame($slave1, $replication->getConnectionById('slave-host1:6379'));
  67. $this->assertSame($slave2, $replication->getConnectionById('slave-host2:6380'));
  68. }
  69. /**
  70. * @group disconnected
  71. * @expectedException \Predis\ClientException
  72. * @expectedExceptionMessage No available connection for replication
  73. */
  74. public function testThrowsExceptionOnEmptyReplication()
  75. {
  76. $replication = new MasterSlaveReplication();
  77. $replication->connect();
  78. }
  79. /**
  80. * @group disconnected
  81. */
  82. public function testConnectsToOneOfSlaves()
  83. {
  84. $master = $this->getMockConnection('tcp://host1?alias=master');
  85. $master->expects($this->never())->method('connect');
  86. $slave = $this->getMockConnection('tcp://host2?alias=slave1');
  87. $slave->expects($this->once())->method('connect');
  88. $replication = new MasterSlaveReplication();
  89. $replication->add($master);
  90. $replication->add($slave);
  91. $replication->connect();
  92. }
  93. /**
  94. * @group disconnected
  95. */
  96. public function testConnectsToMasterOnMissingSlaves()
  97. {
  98. $master = $this->getMockConnection('tcp://host1?alias=master');
  99. $replication = new MasterSlaveReplication();
  100. $replication->add($master);
  101. $replication->connect();
  102. $this->assertSame($master, $replication->getCurrent());
  103. }
  104. /**
  105. * @group disconnected
  106. */
  107. public function testIsConnectedReturnsTrueIfAtLeastOneConnectionIsOpen()
  108. {
  109. $master = $this->getMockConnection('tcp://host1?alias=master');
  110. $master->expects($this->never())->method('isConnected')->will($this->returnValue(false));
  111. $slave = $this->getMockConnection('tcp://host2?alias=slave1');
  112. $slave->expects($this->once())->method('isConnected')->will($this->returnValue(true));
  113. $replication = new MasterSlaveReplication();
  114. $replication->add($master);
  115. $replication->add($slave);
  116. $replication->connect();
  117. $this->assertTrue($replication->isConnected());
  118. }
  119. /**
  120. * @group disconnected
  121. */
  122. public function testIsConnectedReturnsFalseIfAllConnectionsAreClosed()
  123. {
  124. $master = $this->getMockConnection('tcp://host1?alias=master');
  125. $master->expects($this->any())->method('isConnected')->will($this->returnValue(false));
  126. $slave = $this->getMockConnection('tcp://host2?alias=slave1');
  127. $slave->expects($this->any())->method('isConnected')->will($this->returnValue(false));
  128. $replication = new MasterSlaveReplication();
  129. $replication->add($master);
  130. $replication->add($slave);
  131. $this->assertFalse($replication->isConnected());
  132. $replication->connect();
  133. $replication->disconnect();
  134. $this->assertFalse($replication->isConnected());
  135. }
  136. /**
  137. * @group disconnected
  138. */
  139. public function testDisconnectForcesCurrentConnectionToDisconnect()
  140. {
  141. $master = $this->getMockConnection('tcp://host1?alias=master');
  142. $master->expects($this->once())->method('disconnect');
  143. $slave = $this->getMockConnection('tcp://host2?alias=slave1');
  144. $slave->expects($this->once())->method('disconnect');
  145. $replication = new MasterSlaveReplication();
  146. $replication->add($master);
  147. $replication->add($slave);
  148. $replication->disconnect();
  149. }
  150. /**
  151. * @group disconnected
  152. */
  153. public function testCanSwitchConnectionByAlias()
  154. {
  155. $master = $this->getMockConnection('tcp://host1?alias=master');
  156. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  157. $replication = new MasterSlaveReplication();
  158. $replication->add($master);
  159. $replication->add($slave1);
  160. $this->assertNull($replication->getCurrent());
  161. $replication->switchTo('master');
  162. $this->assertSame($master, $replication->getCurrent());
  163. $replication->switchTo('slave1');
  164. $this->assertSame($slave1, $replication->getCurrent());
  165. }
  166. /**
  167. * @group disconnected
  168. * @expectedException \InvalidArgumentException
  169. * @expectedExceptionMessage Invalid connection or connection not found.
  170. */
  171. public function testThrowsErrorWhenSwitchingToUnknownConnectionByAlias()
  172. {
  173. $replication = new MasterSlaveReplication();
  174. $replication->add($this->getMockConnection('tcp://host1?alias=master'));
  175. $replication->add($this->getMockConnection('tcp://host2?alias=slave1'));
  176. $replication->switchTo('unknown');
  177. }
  178. /**
  179. * @group disconnected
  180. */
  181. public function testCanSwitchConnectionByInstance()
  182. {
  183. $master = $this->getMockConnection('tcp://host1?alias=master');
  184. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  185. $replication = new MasterSlaveReplication();
  186. $replication->add($master);
  187. $replication->add($slave1);
  188. $this->assertNull($replication->getCurrent());
  189. $replication->switchTo($master);
  190. $this->assertSame($master, $replication->getCurrent());
  191. $replication->switchTo($slave1);
  192. $this->assertSame($slave1, $replication->getCurrent());
  193. }
  194. /**
  195. * @group disconnected
  196. * @expectedException \InvalidArgumentException
  197. * @expectedExceptionMessage Invalid connection or connection not found.
  198. */
  199. public function testThrowsErrorWhenSwitchingToUnknownConnectionByInstance()
  200. {
  201. $replication = new MasterSlaveReplication();
  202. $replication->add($this->getMockConnection('tcp://host1?alias=master'));
  203. $replication->add($this->getMockConnection('tcp://host2?alias=slave1'));
  204. $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
  205. $replication->switchTo($slave2);
  206. }
  207. /**
  208. * @group disconnected
  209. */
  210. public function testCanSwitchToMaster()
  211. {
  212. $master = $this->getMockConnection('tcp://host1?alias=master');
  213. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  214. $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
  215. $replication = new MasterSlaveReplication();
  216. $replication->add($master);
  217. $replication->add($slave1);
  218. $replication->add($slave2);
  219. $this->assertNull($replication->getCurrent());
  220. $replication->switchToMaster();
  221. $this->assertSame($master, $replication->getCurrent());
  222. }
  223. /**
  224. * @group disconnected
  225. * @expectedException \InvalidArgumentException
  226. * @expectedExceptionMessage Invalid connection or connection not found.
  227. */
  228. public function testThrowsErrorOnSwitchToMasterWithNoMasterDefined()
  229. {
  230. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  231. $replication = new MasterSlaveReplication();
  232. $replication->add($slave1);
  233. $replication->switchToMaster();
  234. }
  235. /**
  236. * @group disconnected
  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. $profile = Profile\Factory::getDefault();
  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 = $profile->createCommand('exists', array('foo'));
  274. $this->assertSame($slave1, $replication->getConnection($cmd));
  275. $cmd = $profile->createCommand('get', array('foo'));
  276. $this->assertSame($slave1, $replication->getConnection($cmd));
  277. }
  278. /**
  279. * @group disconnected
  280. */
  281. public function testUsesMasterOnWriteRequests()
  282. {
  283. $profile = Profile\Factory::getDefault();
  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 = $profile->createCommand('set', array('foo', 'bar'));
  290. $this->assertSame($master, $replication->getConnection($cmd));
  291. $cmd = $profile->createCommand('get', array('foo'));
  292. $this->assertSame($master, $replication->getConnection($cmd));
  293. }
  294. /**
  295. * @group disconnected
  296. */
  297. public function testUsesMasterOnReadRequestsWhenNoSlavesAvailable()
  298. {
  299. $profile = Profile\Factory::getDefault();
  300. $master = $this->getMockConnection('tcp://host1?alias=master');
  301. $replication = new MasterSlaveReplication();
  302. $replication->add($master);
  303. $cmd = $profile->createCommand('exists', array('foo'));
  304. $this->assertSame($master, $replication->getConnection($cmd));
  305. $cmd = $profile->createCommand('set', array('foo', 'bar'));
  306. $this->assertSame($master, $replication->getConnection($cmd));
  307. }
  308. /**
  309. * @group disconnected
  310. */
  311. public function testSwitchesFromSlaveToMasterOnWriteRequestss()
  312. {
  313. $profile = Profile\Factory::getDefault();
  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 = $profile->createCommand('exists', array('foo'));
  320. $this->assertSame($slave1, $replication->getConnection($cmd));
  321. $cmd = $profile->createCommand('set', array('foo', 'bar'));
  322. $this->assertSame($master, $replication->getConnection($cmd));
  323. $cmd = $profile->createCommand('exists', array('foo'));
  324. $this->assertSame($master, $replication->getConnection($cmd));
  325. }
  326. /**
  327. * @group disconnected
  328. */
  329. public function testWritesCommandToCorrectConnection()
  330. {
  331. $profile = Profile\Factory::getDefault();
  332. $cmdExists = $profile->createCommand('exists', array('foo'));
  333. $cmdSet = $profile->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. $profile = Profile\Factory::getDefault();
  350. $cmdExists = $profile->createCommand('exists', array('foo'));
  351. $cmdSet = $profile->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. $profile = Profile\Factory::getDefault();
  368. $cmdExists = $profile->createCommand('exists', array('foo'));
  369. $cmdSet = $profile->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. $profile = Profile\Factory::getDefault();
  386. $cmdWatch = $profile->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. $profile = Profile\Factory::getDefault();
  402. $cmdMulti = $profile->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. $profile = Profile\Factory::get('dev');
  418. $cmdEval = $profile->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. $profile = Profile\Factory::get('dev');
  434. $cmdSortNormal = $profile->createCommand('sort', array('key'));
  435. $cmdSortStore = $profile->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. $profile = Profile\Factory::getDefault();
  452. $cmdExists = $profile->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. $profile = Profile\Factory::getDefault();
  481. $cmdExists = $profile->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. $profile = Profile\Factory::getDefault();
  513. $cmdExists = $profile->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. $profile = Profile\Factory::getDefault();
  532. $cmdSet = $profile->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. $profile = Profile\Factory::getDefault();
  582. $cmdSet = $profile->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 = Profile\Factory::getDefault()->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. $profile = Profile\Factory::getDefault();
  615. $cmdSet = $profile->createCommand('set', array('foo', 'bar'));
  616. $cmdGet = $profile->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. $profile = Profile\Factory::getDefault();
  635. $cmdExistsFoo = $profile->createCommand('exists', array('foo'));
  636. $cmdExistsBar = $profile->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. $profile = Profile\Factory::get('dev');
  657. $cmdEval = $profile->createCommand('eval', array($script = "return redis.call('info');"));
  658. $cmdEvalSha = $profile->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. // ******************************************************************** //
  941. // ---- HELPER METHODS ------------------------------------------------ //
  942. // ******************************************************************** //
  943. /**
  944. * Returns a base mocked connection from Predis\Connection\NodeConnectionInterface.
  945. *
  946. * @param mixed $parameters Optional parameters.
  947. *
  948. * @return mixed
  949. */
  950. protected function getMockConnection($parameters = null)
  951. {
  952. $connection = $this->getMock('Predis\Connection\NodeConnectionInterface');
  953. if ($parameters) {
  954. $parameters = Connection\Parameters::create($parameters);
  955. $hash = "{$parameters->host}:{$parameters->port}";
  956. $connection->expects($this->any())
  957. ->method('getParameters')
  958. ->will($this->returnValue($parameters));
  959. $connection->expects($this->any())
  960. ->method('__toString')
  961. ->will($this->returnValue($hash));
  962. }
  963. return $connection;
  964. }
  965. }