RedisClusterTest.php 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268
  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\Cluster;
  11. use Predis\Command;
  12. use Predis\Connection;
  13. use Predis\Response;
  14. use PredisTestCase;
  15. /**
  16. *
  17. */
  18. class RedisClusterTest extends PredisTestCase
  19. {
  20. /**
  21. * @group disconnected
  22. */
  23. public function testAcceptsCustomConnectionFactory()
  24. {
  25. $factory = $this->getMock('Predis\Connection\FactoryInterface');
  26. $cluster = new RedisCluster($factory);
  27. $this->assertSame($factory, $cluster->getConnectionFactory());
  28. }
  29. /**
  30. * @group disconnected
  31. */
  32. public function testUsesRedisClusterStrategyByDefault()
  33. {
  34. $cluster = new RedisCluster(new Connection\Factory());
  35. $this->assertInstanceOf('Predis\Cluster\RedisStrategy', $cluster->getClusterStrategy());
  36. }
  37. /**
  38. * @group disconnected
  39. */
  40. public function testAcceptsCustomClusterStrategy()
  41. {
  42. $strategy = $this->getMock('Predis\Cluster\StrategyInterface');
  43. $cluster = new RedisCluster(new Connection\Factory(), $strategy);
  44. $this->assertSame($strategy, $cluster->getClusterStrategy());
  45. }
  46. /**
  47. * @group disconnected
  48. */
  49. public function testAddingConnectionsToCluster()
  50. {
  51. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
  52. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
  53. $cluster = new RedisCluster(new Connection\Factory());
  54. $cluster->add($connection1);
  55. $cluster->add($connection2);
  56. $this->assertSame(2, count($cluster));
  57. $this->assertSame($connection1, $cluster->getConnectionById('127.0.0.1:6379'));
  58. $this->assertSame($connection2, $cluster->getConnectionById('127.0.0.1:6380'));
  59. }
  60. /**
  61. * @group disconnected
  62. */
  63. public function testRemovingConnectionsFromCluster()
  64. {
  65. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
  66. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
  67. $connection3 = $this->getMockConnection('tcp://127.0.0.1:6371');
  68. $cluster = new RedisCluster(new Connection\Factory());
  69. $cluster->add($connection1);
  70. $cluster->add($connection2);
  71. $this->assertTrue($cluster->remove($connection1));
  72. $this->assertFalse($cluster->remove($connection3));
  73. $this->assertSame(1, count($cluster));
  74. }
  75. /**
  76. * @group disconnected
  77. */
  78. public function testRemovingConnectionsFromClusterByAlias()
  79. {
  80. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
  81. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
  82. $cluster = new RedisCluster(new Connection\Factory());
  83. $cluster->add($connection1);
  84. $cluster->add($connection2);
  85. $this->assertTrue($cluster->removeById('127.0.0.1:6380'));
  86. $this->assertFalse($cluster->removeById('127.0.0.1:6390'));
  87. $this->assertSame(1, count($cluster));
  88. }
  89. /**
  90. * @group disconnected
  91. */
  92. public function testCountReturnsNumberOfConnectionsInPool()
  93. {
  94. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
  95. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
  96. $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381');
  97. $cluster = new RedisCluster(new Connection\Factory());
  98. $cluster->add($connection1);
  99. $cluster->add($connection2);
  100. $cluster->add($connection3);
  101. $this->assertSame(3, count($cluster));
  102. $cluster->remove($connection3);
  103. $this->assertSame(2, count($cluster));
  104. }
  105. /**
  106. * @group disconnected
  107. */
  108. public function testConnectPicksRandomConnection()
  109. {
  110. $connect1 = false;
  111. $connect2 = false;
  112. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
  113. $connection1
  114. ->expects($this->any())
  115. ->method('connect')
  116. ->will($this->returnCallback(function () use (&$connect1) {
  117. $connect1 = true;
  118. }));
  119. $connection1
  120. ->expects($this->any())
  121. ->method('isConnected')
  122. ->will($this->returnCallback(function () use (&$connect1) {
  123. return $connect1;
  124. }));
  125. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
  126. $connection2
  127. ->expects($this->any())
  128. ->method('connect')
  129. ->will($this->returnCallback(function () use (&$connect2) {
  130. $connect2 = true;
  131. }));
  132. $connection2
  133. ->expects($this->any())
  134. ->method('isConnected')
  135. ->will($this->returnCallback(function () use (&$connect2) {
  136. return $connect2;
  137. }));
  138. $cluster = new RedisCluster(new Connection\Factory());
  139. $cluster->add($connection1);
  140. $cluster->add($connection2);
  141. $cluster->connect();
  142. $this->assertTrue($cluster->isConnected());
  143. if ($connect1) {
  144. $this->assertTrue($connect1);
  145. $this->assertFalse($connect2);
  146. } else {
  147. $this->assertFalse($connect1);
  148. $this->assertTrue($connect2);
  149. }
  150. }
  151. /**
  152. * @group disconnected
  153. */
  154. public function testDisconnectForcesAllConnectionsToDisconnect()
  155. {
  156. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
  157. $connection1
  158. ->expects($this->once())
  159. ->method('disconnect');
  160. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
  161. $connection2
  162. ->expects($this->once())
  163. ->method('disconnect');
  164. $cluster = new RedisCluster(new Connection\Factory());
  165. $cluster->add($connection1);
  166. $cluster->add($connection2);
  167. $cluster->disconnect();
  168. }
  169. /**
  170. * @group disconnected
  171. */
  172. public function testIsConnectedReturnsTrueIfAtLeastOneConnectionIsOpen()
  173. {
  174. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
  175. $connection1
  176. ->expects($this->once())
  177. ->method('isConnected')
  178. ->will($this->returnValue(false));
  179. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
  180. $connection2
  181. ->expects($this->once())
  182. ->method('isConnected')
  183. ->will($this->returnValue(true));
  184. $cluster = new RedisCluster(new Connection\Factory());
  185. $cluster->add($connection1);
  186. $cluster->add($connection2);
  187. $this->assertTrue($cluster->isConnected());
  188. }
  189. /**
  190. * @group disconnected
  191. */
  192. public function testIsConnectedReturnsFalseIfAllConnectionsAreClosed()
  193. {
  194. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
  195. $connection1
  196. ->expects($this->once())
  197. ->method('isConnected')
  198. ->will($this->returnValue(false));
  199. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
  200. $connection2
  201. ->expects($this->once())
  202. ->method('isConnected')
  203. ->will($this->returnValue(false));
  204. $cluster = new RedisCluster(new Connection\Factory());
  205. $cluster->add($connection1);
  206. $cluster->add($connection2);
  207. $this->assertFalse($cluster->isConnected());
  208. }
  209. /**
  210. * @group disconnected
  211. */
  212. public function testGetIteratorReturnsConnectionsMappedInSlotsMapWhenUseClusterSlotsIsDisabled()
  213. {
  214. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=0-5460');
  215. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6382?slots=5461-10922');
  216. $connection3 = $this->getMockConnection('tcp://127.0.0.1:6383?slots=10923-16383');
  217. $connection4 = $this->getMockConnection('tcp://127.0.0.1:6384');
  218. $cluster = new RedisCluster(new Connection\Factory());
  219. $cluster->useClusterSlots(false);
  220. $cluster->add($connection1);
  221. $cluster->add($connection2);
  222. $cluster->add($connection3);
  223. $cluster->add($connection4);
  224. $this->assertInstanceOf('Iterator', $iterator = $cluster->getIterator());
  225. $connections = iterator_to_array($iterator);
  226. $this->assertCount(3, $connections);
  227. $this->assertSame($connection1, $connections[0]);
  228. $this->assertSame($connection2, $connections[1]);
  229. $this->assertSame($connection3, $connections[2]);
  230. }
  231. /**
  232. * @group disconnected
  233. */
  234. public function testGetIteratorReturnsConnectionsMappedInSlotsMapFetchedFromRedisCluster()
  235. {
  236. $slotsmap = array(
  237. array(0, 5460, array('127.0.0.1', 6381), array()),
  238. array(5461, 10922, array('127.0.0.1', 6383), array()),
  239. array(10923, 16383, array('127.0.0.1', 6384), array()),
  240. );
  241. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=0-5460');
  242. $connection1
  243. ->expects($this->once())
  244. ->method('executeCommand')
  245. ->with($this->isRedisCommand(
  246. 'CLUSTER', array('SLOTS')
  247. ))
  248. ->will($this->returnValue($slotsmap));
  249. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6382?slots=5461-10922');
  250. $connection3 = $this->getMockConnection('tcp://127.0.0.1:6383');
  251. $connection4 = $this->getMockConnection('tcp://127.0.0.1:6384');
  252. $factory = $this->getMock('Predis\Connection\FactoryInterface');
  253. $factory
  254. ->expects($this->at(0))
  255. ->method('create')
  256. ->with(array(
  257. 'host' => '127.0.0.1',
  258. 'port' => '6383',
  259. ))
  260. ->will($this->returnValue($connection3));
  261. $factory
  262. ->expects($this->at(1))
  263. ->method('create')
  264. ->with(array(
  265. 'host' => '127.0.0.1',
  266. 'port' => '6384',
  267. ))
  268. ->will($this->returnValue($connection4));
  269. // TODO: I'm not sure about mocking a protected method, but it'll do for now
  270. $cluster = $this->getMock('Predis\Connection\Cluster\RedisCluster', array('getRandomConnection'), array($factory));
  271. $cluster
  272. ->expects($this->once())
  273. ->method('getRandomConnection')
  274. ->will($this->returnValue($connection1));
  275. $cluster->add($connection1);
  276. $cluster->add($connection2);
  277. $cluster->useClusterSlots(true);
  278. $this->assertInstanceOf('Iterator', $iterator = $cluster->getIterator());
  279. $connections = iterator_to_array($iterator);
  280. $this->assertCount(3, $connections);
  281. $this->assertSame($connection1, $connections[0]);
  282. $this->assertSame($connection3, $connections[1]);
  283. $this->assertSame($connection4, $connections[2]);
  284. }
  285. /**
  286. * @group disconnected
  287. */
  288. public function testAddingConnectionResetsSlotsMap()
  289. {
  290. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
  291. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
  292. $cluster = new RedisCluster(new Connection\Factory());
  293. $cluster->add($connection1);
  294. $slotmap = $cluster->getSlotMap();
  295. $slotmap->setSlots(0, 5460, '127.0.0.1:6379');
  296. $this->assertSame(array_fill(0, 5461, '127.0.0.1:6379'), $slotmap->toArray());
  297. $cluster->add($connection2);
  298. $this->assertCount(0, $slotmap);
  299. }
  300. /**
  301. * @group disconnected
  302. */
  303. public function testRemovingConnectionResetsSlotsMap()
  304. {
  305. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
  306. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
  307. $cluster = new RedisCluster(new Connection\Factory());
  308. $cluster->add($connection1);
  309. $cluster->add($connection2);
  310. $slotmap = $cluster->getSlotMap();
  311. $slotmap->setSlots(0, 5460, '127.0.0.1:6379');
  312. $slotmap->setSlots(5461, 10922, '127.0.0.1:6380');
  313. $expectedMap = array_merge(
  314. array_fill(0, 5461, '127.0.0.1:6379'),
  315. array_fill(5460, 5462, '127.0.0.1:6380')
  316. );
  317. $this->assertSame($expectedMap, $slotmap->toArray());
  318. $cluster->remove($connection1);
  319. $this->assertCount(0, $slotmap);
  320. }
  321. /**
  322. * @group disconnected
  323. */
  324. public function testCanAssignConnectionsToRangeOfSlotsFromParameters()
  325. {
  326. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379?slots=0-5460');
  327. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380?slots=5461-10922');
  328. $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=10923-16383');
  329. $cluster = new RedisCluster(new Connection\Factory());
  330. $cluster->add($connection1);
  331. $cluster->add($connection2);
  332. $cluster->add($connection3);
  333. $cluster->buildSlotMap();
  334. $expectedMap = array_merge(
  335. array_fill(0, 5461, '127.0.0.1:6379'),
  336. array_fill(5461, 5462, '127.0.0.1:6380'),
  337. array_fill(10923, 5461, '127.0.0.1:6381')
  338. );
  339. $actualMap = $cluster->getSlotMap()->toArray();
  340. ksort($actualMap);
  341. $this->assertSame($expectedMap, $actualMap);
  342. }
  343. /**
  344. * @group disconnected
  345. */
  346. public function testCanAssignConnectionsToSingleSlotOrRangesOfSlotsFromParameters()
  347. {
  348. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379?slots=0-5460,5500-5600,11000');
  349. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380?slots=5461-5499,5600-10922');
  350. $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=10923-10999,11001-16383');
  351. $cluster = new RedisCluster(new Connection\Factory());
  352. $cluster->add($connection1);
  353. $cluster->add($connection2);
  354. $cluster->add($connection3);
  355. $cluster->buildSlotMap();
  356. $expectedMap = array_merge(
  357. array_fill(0, 5461, '127.0.0.1:6379'),
  358. array_fill(5460, 39, '127.0.0.1:6380'),
  359. array_fill(5499, 101, '127.0.0.1:6379'),
  360. array_fill(5599, 5322, '127.0.0.1:6380'),
  361. array_fill(10923, 77, '127.0.0.1:6381'),
  362. array_fill(11000, 1, '127.0.0.1:6379'),
  363. array_fill(11000, 5383, '127.0.0.1:6381')
  364. );
  365. $actualMap = $cluster->getSlotMap()->toArray();
  366. ksort($actualMap);
  367. $this->assertSame($expectedMap, $actualMap);
  368. }
  369. /**
  370. * @group disconnected
  371. */
  372. public function testReturnsCorrectConnectionUsingSlotID()
  373. {
  374. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
  375. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
  376. $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381');
  377. $cluster = new RedisCluster(new Connection\Factory());
  378. $cluster->add($connection1);
  379. $cluster->add($connection2);
  380. $cluster->add($connection3);
  381. $this->assertSame($connection1, $cluster->getConnectionBySlot(0));
  382. $this->assertSame($connection2, $cluster->getConnectionBySlot(5461));
  383. $this->assertSame($connection3, $cluster->getConnectionBySlot(10923));
  384. $cluster->getSlotMap()->setSlots(5461, 7096, '127.0.0.1:6380');
  385. $this->assertSame($connection2, $cluster->getConnectionBySlot(5461));
  386. }
  387. /**
  388. * @group disconnected
  389. */
  390. public function testReturnsCorrectConnectionUsingCommandInstance()
  391. {
  392. $commands = $this->getCommandFactory();
  393. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
  394. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
  395. $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381');
  396. $cluster = new RedisCluster(new Connection\Factory());
  397. $cluster->add($connection1);
  398. $cluster->add($connection2);
  399. $cluster->add($connection3);
  400. $set = $commands->createCommand('set', array('node:1001', 'foobar'));
  401. $get = $commands->createCommand('get', array('node:1001'));
  402. $this->assertSame($connection1, $cluster->getConnectionByCommand($set));
  403. $this->assertSame($connection1, $cluster->getConnectionByCommand($get));
  404. $set = $commands->createCommand('set', array('node:1048', 'foobar'));
  405. $get = $commands->createCommand('get', array('node:1048'));
  406. $this->assertSame($connection2, $cluster->getConnectionByCommand($set));
  407. $this->assertSame($connection2, $cluster->getConnectionByCommand($get));
  408. $set = $commands->createCommand('set', array('node:1082', 'foobar'));
  409. $get = $commands->createCommand('get', array('node:1082'));
  410. $this->assertSame($connection3, $cluster->getConnectionByCommand($set));
  411. $this->assertSame($connection3, $cluster->getConnectionByCommand($get));
  412. }
  413. /**
  414. * @group disconnected
  415. */
  416. public function testWritesCommandToCorrectConnection()
  417. {
  418. $command = $this->getCommandFactory()->createCommand('get', array('node:1001'));
  419. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
  420. $connection1
  421. ->expects($this->once())
  422. ->method('writeRequest')
  423. ->with($command);
  424. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
  425. $connection2
  426. ->expects($this->never())
  427. ->method('writeRequest');
  428. $cluster = new RedisCluster(new Connection\Factory());
  429. $cluster->useClusterSlots(false);
  430. $cluster->add($connection1);
  431. $cluster->add($connection2);
  432. $cluster->writeRequest($command);
  433. }
  434. /**
  435. * @group disconnected
  436. */
  437. public function testReadsCommandFromCorrectConnection()
  438. {
  439. $command = $this->getCommandFactory()->createCommand('get', array('node:1050'));
  440. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
  441. $connection1
  442. ->expects($this->never())
  443. ->method('readResponse');
  444. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
  445. $connection2
  446. ->expects($this->once())
  447. ->method('readResponse')
  448. ->with($command);
  449. $cluster = new RedisCluster(new Connection\Factory());
  450. $cluster->useClusterSlots(false);
  451. $cluster->add($connection1);
  452. $cluster->add($connection2);
  453. $cluster->readResponse($command);
  454. }
  455. /**
  456. * @group disconnected
  457. */
  458. public function testRetriesExecutingCommandOnConnectionFailureOnlyAfterFetchingNewSlotsMap()
  459. {
  460. $slotsmap = array(
  461. array(0, 5460, array('127.0.0.1', 9381), array()),
  462. array(5461, 10922, array('127.0.0.1', 6382), array()),
  463. array(10923, 16383, array('127.0.0.1', 6383), array()),
  464. );
  465. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=0-5460');
  466. $connection1
  467. ->expects($this->once())
  468. ->method('executeCommand')
  469. ->with($this->isRedisCommand(
  470. 'GET', array('node:1001')
  471. ))
  472. ->will($this->throwException(
  473. new Connection\ConnectionException($connection1, 'Unknown connection error [127.0.0.1:6381]')
  474. ));
  475. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6382?slots=5461-10922');
  476. $connection2
  477. ->expects($this->any())
  478. ->method('executeCommand')
  479. ->with($this->isRedisCommand(
  480. 'CLUSTER', array('SLOTS')
  481. ))
  482. ->will($this->returnValue($slotsmap));
  483. $connection3 = $this->getMockConnection('tcp://127.0.0.1:6383?slots=10923-16383');
  484. $connection3
  485. ->expects($this->any())
  486. ->method('executeCommand')
  487. ->with($this->isRedisCommand(
  488. 'CLUSTER', array('SLOTS')
  489. ))
  490. ->will($this->returnValue($slotsmap));
  491. $connection4 = $this->getMockConnection('tcp://127.0.0.1:9381');
  492. $connection4
  493. ->expects($this->at(0))
  494. ->method('executeCommand')
  495. ->with($this->isRedisCommand(
  496. 'GET', array('node:1001')
  497. ))
  498. ->will($this->returnValue('value:1001'));
  499. $connection4
  500. ->expects($this->at(1))
  501. ->method('executeCommand')
  502. ->with($this->isRedisCommand(
  503. 'GET', array('node:5001')
  504. ))
  505. ->will($this->returnValue('value:5001'));
  506. $factory = $this->getMock('Predis\Connection\FactoryInterface');
  507. $factory
  508. ->expects($this->once())
  509. ->method('create')
  510. ->with(array(
  511. 'host' => '127.0.0.1',
  512. 'port' => '9381',
  513. ))
  514. ->will($this->returnValue($connection4));
  515. $cluster = new RedisCluster($factory);
  516. $cluster->add($connection1);
  517. $cluster->add($connection2);
  518. $cluster->add($connection3);
  519. $this->assertSame('value:1001', $cluster->executeCommand(
  520. Command\RawCommand::create('get', 'node:1001')
  521. ));
  522. $this->assertSame('value:5001', $cluster->executeCommand(
  523. Command\RawCommand::create('get', 'node:5001')
  524. ));
  525. }
  526. /**
  527. * @group disconnected
  528. */
  529. public function testRetriesExecutingCommandOnConnectionFailureButDoNotAskSlotMapWhenDisabled()
  530. {
  531. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=0-5500');
  532. $connection1
  533. ->expects($this->once())
  534. ->method('executeCommand')
  535. ->with($this->isRedisCommand(
  536. 'GET', array('node:1001')
  537. ))
  538. ->will($this->throwException(
  539. new Connection\ConnectionException($connection1, 'Unknown connection error [127.0.0.1:6381]')
  540. ));
  541. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6382?slots=5501-11000');
  542. $connection2
  543. ->expects($this->once())
  544. ->method('executeCommand')
  545. ->with($this->isRedisCommand(
  546. 'GET', array('node:1001')
  547. ))
  548. ->will($this->returnValue(
  549. new Response\Error('MOVED 1970 127.0.0.1:9381')
  550. ));
  551. $connection3 = $this->getMockConnection('tcp://127.0.0.1:6383?slots=11101-16383');
  552. $connection3
  553. ->expects($this->never())
  554. ->method('executeCommand');
  555. $connection4 = $this->getMockConnection('tcp://127.0.0.1:9381');
  556. $connection4
  557. ->expects($this->once())
  558. ->method('executeCommand')
  559. ->with($this->isRedisCommand(
  560. 'GET', array('node:1001')
  561. ))
  562. ->will($this->returnValue('value:1001'));
  563. $factory = $this->getMock('Predis\Connection\FactoryInterface');
  564. $factory
  565. ->expects($this->once())
  566. ->method('create')
  567. ->with(array(
  568. 'host' => '127.0.0.1',
  569. 'port' => '9381',
  570. ))
  571. ->will($this->returnValue($connection4));
  572. // TODO: I'm not sure about mocking a protected method, but it'll do for now
  573. $cluster = $this->getMock('Predis\Connection\Cluster\RedisCluster', array('getRandomConnection'), array($factory));
  574. $cluster
  575. ->expects($this->never())
  576. ->method('getRandomConnection');
  577. $cluster->useClusterSlots(false);
  578. $cluster->add($connection1);
  579. $cluster->add($connection2);
  580. $cluster->add($connection3);
  581. $this->assertSame('value:1001', $cluster->executeCommand(
  582. Command\RawCommand::create('get', 'node:1001')
  583. ));
  584. }
  585. /**
  586. * @group disconnected
  587. * @expectedException \Predis\ClientException
  588. * @expectedExceptionMessage No connections available in the pool
  589. */
  590. public function testThrowsClientExceptionWhenExecutingCommandWithEmptyPool()
  591. {
  592. $factory = $this->getMock('Predis\Connection\FactoryInterface');
  593. $factory
  594. ->expects($this->never())
  595. ->method('create');
  596. $cluster = new RedisCluster($factory);
  597. $cluster->executeCommand(
  598. Command\RawCommand::create('get', 'node:1001')
  599. );
  600. }
  601. /**
  602. * @group disconnected
  603. */
  604. public function testAskSlotMapReturnEmptyArrayOnEmptyConnectionsPool()
  605. {
  606. $factory = $this->getMock('Predis\Connection\FactoryInterface');
  607. $factory
  608. ->expects($this->never())
  609. ->method('create');
  610. $cluster = new RedisCluster($factory);
  611. $cluster->askSlotMap();
  612. $this->assertCount(0, $cluster->getSlotMap());
  613. }
  614. /**
  615. * @group disconnected
  616. */
  617. public function testAskSlotMapRetriesOnDifferentNodeOnConnectionFailure()
  618. {
  619. $slotsmap = array(
  620. array(0, 5460, array('127.0.0.1', 9381), array()),
  621. array(5461, 10922, array('127.0.0.1', 6382), array()),
  622. array(10923, 16383, array('127.0.0.1', 6383), array()),
  623. );
  624. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=0-5460');
  625. $connection1
  626. ->expects($this->once())
  627. ->method('executeCommand')
  628. ->with($this->isRedisCommand(
  629. 'CLUSTER', array('SLOTS')
  630. ))
  631. ->will($this->throwException(
  632. new Connection\ConnectionException($connection1, 'Unknown connection error [127.0.0.1:6381]')
  633. ));
  634. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6382?slots=5461-10922');
  635. $connection2
  636. ->expects($this->once())
  637. ->method('executeCommand')
  638. ->with($this->isRedisCommand(
  639. 'CLUSTER', array('SLOTS')
  640. ))
  641. ->will($this->throwException(
  642. new Connection\ConnectionException($connection2, 'Unknown connection error [127.0.0.1:6383]')
  643. ));
  644. $connection3 = $this->getMockConnection('tcp://127.0.0.1:6383?slots=10923-16383');
  645. $connection3
  646. ->expects($this->once())
  647. ->method('executeCommand')
  648. ->with($this->isRedisCommand(
  649. 'CLUSTER', array('SLOTS')
  650. ))
  651. ->will($this->returnValue($slotsmap));
  652. $factory = $this->getMock('Predis\Connection\FactoryInterface');
  653. $factory
  654. ->expects($this->never())
  655. ->method('create');
  656. // TODO: I'm not sure about mocking a protected method, but it'll do for now
  657. $cluster = $this->getMock('Predis\Connection\Cluster\RedisCluster', array('getRandomConnection'), array($factory));
  658. $cluster
  659. ->expects($this->exactly(3))
  660. ->method('getRandomConnection')
  661. ->will($this->onConsecutiveCalls($connection1, $connection2, $connection3));
  662. $cluster->add($connection1);
  663. $cluster->add($connection2);
  664. $cluster->add($connection3);
  665. $cluster->askSlotMap();
  666. $this->assertCount(16384, $cluster->getSlotMap());
  667. }
  668. /**
  669. * @group disconnected
  670. * @expectedException \Predis\Connection\ConnectionException
  671. * @expectedExceptionMessage Unknown connection error [127.0.0.1:6382]
  672. */
  673. public function testAskSlotMapHonorsRetryLimitOnMultipleConnectionFailures()
  674. {
  675. $slotsmap = array(
  676. array(0, 5460, array('127.0.0.1', 9381), array()),
  677. array(5461, 10922, array('127.0.0.1', 6382), array()),
  678. array(10923, 16383, array('127.0.0.1', 6383), array()),
  679. );
  680. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=0-5460');
  681. $connection1
  682. ->expects($this->any())
  683. ->method('executeCommand')
  684. ->with($this->isRedisCommand(
  685. 'CLUSTER', array('SLOTS')
  686. ))
  687. ->will($this->throwException(
  688. new Connection\ConnectionException($connection1, 'Unknown connection error [127.0.0.1:6381]')
  689. ));
  690. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6382?slots=5461-10922');
  691. $connection2
  692. ->expects($this->any())
  693. ->method('executeCommand')
  694. ->with($this->isRedisCommand(
  695. 'CLUSTER', array('SLOTS')
  696. ))
  697. ->will($this->throwException(
  698. new Connection\ConnectionException($connection2, 'Unknown connection error [127.0.0.1:6382]')
  699. ));
  700. $connection3 = $this->getMockConnection('tcp://127.0.0.1:6383?slots=10923-16383');
  701. $connection3
  702. ->expects($this->never())
  703. ->method('executeCommand');
  704. $factory = $this->getMock('Predis\Connection\FactoryInterface');
  705. $factory
  706. ->expects($this->never())
  707. ->method('create');
  708. // TODO: I'm not sure about mocking a protected method, but it'll do for now
  709. $cluster = $this->getMock('Predis\Connection\Cluster\RedisCluster', array('getRandomConnection'), array($factory));
  710. $cluster
  711. ->expects($this->exactly(2))
  712. ->method('getRandomConnection')
  713. ->will($this->onConsecutiveCalls($connection1, $connection2));
  714. $cluster->add($connection1);
  715. $cluster->add($connection2);
  716. $cluster->add($connection3);
  717. $cluster->setRetryLimit(1);
  718. $cluster->askSlotMap();
  719. }
  720. /**
  721. * @group disconnected
  722. */
  723. public function testSupportsKeyHashTags()
  724. {
  725. $commands = $this->getCommandFactory();
  726. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
  727. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
  728. $cluster = new RedisCluster(new Connection\Factory());
  729. $cluster->add($connection1);
  730. $cluster->add($connection2);
  731. $set = $commands->createCommand('set', array('{node:1001}:foo', 'foobar'));
  732. $get = $commands->createCommand('get', array('{node:1001}:foo'));
  733. $this->assertSame($connection1, $cluster->getConnectionByCommand($set));
  734. $this->assertSame($connection1, $cluster->getConnectionByCommand($get));
  735. $set = $commands->createCommand('set', array('{node:1001}:bar', 'foobar'));
  736. $get = $commands->createCommand('get', array('{node:1001}:bar'));
  737. $this->assertSame($connection1, $cluster->getConnectionByCommand($set));
  738. $this->assertSame($connection1, $cluster->getConnectionByCommand($get));
  739. }
  740. /**
  741. * @group disconnected
  742. */
  743. public function testAskResponseWithConnectionInPool()
  744. {
  745. $askResponse = new Response\Error('ASK 1970 127.0.0.1:6380');
  746. $command = $this->getCommandFactory()->createCommand('get', array('node:1001'));
  747. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
  748. $connection1
  749. ->expects($this->exactly(2))
  750. ->method('executeCommand')
  751. ->with($command)
  752. ->will($this->onConsecutiveCalls($askResponse, 'foobar'));
  753. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
  754. $connection2
  755. ->expects($this->at(2))
  756. ->method('executeCommand')
  757. ->with($this->isRedisCommand('ASKING'));
  758. $connection2
  759. ->expects($this->at(3))
  760. ->method('executeCommand')
  761. ->with($command)
  762. ->will($this->returnValue('foobar'));
  763. $factory = $this->getMock('Predis\Connection\Factory');
  764. $factory
  765. ->expects($this->never())
  766. ->method('create');
  767. $cluster = new RedisCluster($factory);
  768. $cluster->useClusterSlots(false);
  769. $cluster->add($connection1);
  770. $cluster->add($connection2);
  771. $this->assertSame('foobar', $cluster->executeCommand($command));
  772. $this->assertSame('foobar', $cluster->executeCommand($command));
  773. $this->assertSame(2, count($cluster));
  774. }
  775. /**
  776. * @group disconnected
  777. */
  778. public function testAskResponseWithConnectionNotInPool()
  779. {
  780. $askResponse = new Response\Error('ASK 1970 127.0.0.1:6381');
  781. $command = $this->getCommandFactory()->createCommand('get', array('node:1001'));
  782. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
  783. $connection1
  784. ->expects($this->exactly(2))
  785. ->method('executeCommand')
  786. ->with($command)
  787. ->will($this->onConsecutiveCalls($askResponse, 'foobar'));
  788. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
  789. $connection2
  790. ->expects($this->never())
  791. ->method('executeCommand');
  792. $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381');
  793. $connection3
  794. ->expects($this->at(0))
  795. ->method('executeCommand')
  796. ->with($this->isRedisCommand(
  797. 'ASKING'
  798. ));
  799. $connection3
  800. ->expects($this->at(1))
  801. ->method('executeCommand')
  802. ->with($command)
  803. ->will($this->returnValue('foobar'));
  804. $factory = $this->getMock('Predis\Connection\Factory');
  805. $factory
  806. ->expects($this->once())
  807. ->method('create')
  808. ->with(array(
  809. 'host' => '127.0.0.1',
  810. 'port' => '6381',
  811. ))
  812. ->will($this->returnValue($connection3));
  813. $cluster = new RedisCluster($factory);
  814. $cluster->useClusterSlots(false);
  815. $cluster->add($connection1);
  816. $cluster->add($connection2);
  817. $this->assertSame('foobar', $cluster->executeCommand($command));
  818. $this->assertSame('foobar', $cluster->executeCommand($command));
  819. $this->assertSame(2, count($cluster));
  820. }
  821. /**
  822. * @group disconnected
  823. */
  824. public function testMovedResponseWithConnectionInPool()
  825. {
  826. $movedResponse = new Response\Error('MOVED 1970 127.0.0.1:6380');
  827. $command = $this->getCommandFactory()->createCommand('get', array('node:1001'));
  828. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
  829. $connection1
  830. ->expects($this->exactly(1))
  831. ->method('executeCommand')
  832. ->with($command)
  833. ->will($this->returnValue($movedResponse));
  834. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
  835. $connection2
  836. ->expects($this->exactly(2))
  837. ->method('executeCommand')
  838. ->with($command)
  839. ->will($this->onConsecutiveCalls('foobar', 'foobar'));
  840. $factory = $this->getMock('Predis\Connection\Factory');
  841. $factory->expects($this->never())->method('create');
  842. $cluster = new RedisCluster($factory);
  843. $cluster->useClusterSlots(false);
  844. $cluster->add($connection1);
  845. $cluster->add($connection2);
  846. $this->assertSame('foobar', $cluster->executeCommand($command));
  847. $this->assertSame('foobar', $cluster->executeCommand($command));
  848. $this->assertSame(2, count($cluster));
  849. }
  850. /**
  851. * @group disconnected
  852. */
  853. public function testMovedResponseWithConnectionNotInPool()
  854. {
  855. $movedResponse = new Response\Error('MOVED 1970 127.0.0.1:6381');
  856. $command = $this->getCommandFactory()->createCommand('get', array('node:1001'));
  857. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
  858. $connection1
  859. ->expects($this->once())
  860. ->method('executeCommand')
  861. ->with($command)
  862. ->will($this->returnValue($movedResponse));
  863. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
  864. $connection2
  865. ->expects($this->never())
  866. ->method('executeCommand');
  867. $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381');
  868. $connection3
  869. ->expects($this->exactly(2))
  870. ->method('executeCommand')
  871. ->with($command)
  872. ->will($this->onConsecutiveCalls('foobar', 'foobar'));
  873. $factory = $this->getMock('Predis\Connection\Factory');
  874. $factory
  875. ->expects($this->once())
  876. ->method('create')
  877. ->with(array(
  878. 'host' => '127.0.0.1',
  879. 'port' => '6381',
  880. ))
  881. ->will($this->returnValue($connection3));
  882. $cluster = new RedisCluster($factory);
  883. $cluster->useClusterSlots(false);
  884. $cluster->add($connection1);
  885. $cluster->add($connection2);
  886. $this->assertSame('foobar', $cluster->executeCommand($command));
  887. $this->assertSame('foobar', $cluster->executeCommand($command));
  888. $this->assertSame(3, count($cluster));
  889. }
  890. /**
  891. * @group disconnected
  892. */
  893. public function testParseIPv6AddresseAndPortPairInRedirectionPayload()
  894. {
  895. $movedResponse = new Response\Error('MOVED 1970 2001:db8:0:f101::2:6379');
  896. $command = $this->getCommandFactory()->createCommand('get', array('node:1001'));
  897. $connection1 = $this->getMockConnection('tcp://[2001:db8:0:f101::1]:6379');
  898. $connection1
  899. ->expects($this->once())
  900. ->method('executeCommand')
  901. ->with($command)
  902. ->will($this->returnValue($movedResponse));
  903. $connection2 = $this->getMockConnection('tcp://[2001:db8:0:f101::2]:6379');
  904. $connection2
  905. ->expects($this->once())
  906. ->method('executeCommand')
  907. ->with($command)
  908. ->will($this->returnValue('foobar'));
  909. $factory = $this->getMock('Predis\Connection\Factory');
  910. $factory
  911. ->expects($this->once())
  912. ->method('create')
  913. ->with(array(
  914. 'host' => '2001:db8:0:f101::2',
  915. 'port' => '6379',
  916. ))
  917. ->will($this->returnValue($connection2));
  918. $cluster = new RedisCluster($factory);
  919. $cluster->useClusterSlots(false);
  920. $cluster->add($connection1);
  921. $cluster->executeCommand($command);
  922. }
  923. /**
  924. * @group disconnected
  925. */
  926. public function testFetchSlotsMapFromClusterWithClusterSlotsCommand()
  927. {
  928. $response = array(
  929. array(12288, 13311, array('10.1.0.51', 6387), array('10.1.0.52', 6387)),
  930. array(3072, 4095, array('10.1.0.52', 6392), array('10.1.0.51', 6392)),
  931. array(6144, 7167, array('', 6384), array('10.1.0.52', 6384)),
  932. array(14336, 15359, array('10.1.0.51', 6388), array('10.1.0.52', 6388)),
  933. array(15360, 16383, array('10.1.0.52', 6398), array('10.1.0.51', 6398)),
  934. array(1024, 2047, array('10.1.0.52', 6391), array('10.1.0.51', 6391)),
  935. array(11264, 12287, array('10.1.0.52', 6396), array('10.1.0.51', 6396)),
  936. array(5120, 6143, array('10.1.0.52', 6393), array('10.1.0.51', 6393)),
  937. array(0, 1023, array('10.1.0.51', 6381), array('10.1.0.52', 6381)),
  938. array(13312, 14335, array('10.1.0.52', 6397), array('10.1.0.51', 6397)),
  939. array(4096, 5119, array('10.1.0.51', 6383), array('10.1.0.52', 6383)),
  940. array(9216, 10239, array('10.1.0.52', 6395), array('10.1.0.51', 6395)),
  941. array(8192, 9215, array('10.1.0.51', 6385), array('10.1.0.52', 6385)),
  942. array(10240, 11263, array('10.1.0.51', 6386), array('10.1.0.52', 6386)),
  943. array(2048, 3071, array('10.1.0.51', 6382), array('10.1.0.52', 6382)),
  944. array(7168, 8191, array('10.1.0.52', 6394), array('10.1.0.51', 6394)),
  945. );
  946. $connection1 = $this->getMockConnection('tcp://10.1.0.51:6384');
  947. $connection1
  948. ->expects($this->once())
  949. ->method('executeCommand')
  950. ->with($this->isRedisCommand(
  951. 'CLUSTER', array('SLOTS')
  952. ))
  953. ->will($this->returnValue($response));
  954. $factory = $this->getMock('Predis\Connection\Factory');
  955. $cluster = new RedisCluster($factory);
  956. $cluster->add($connection1);
  957. $cluster->askSlotMap();
  958. $this->assertSame($cluster->getConnectionBySlot('6144'), $connection1);
  959. }
  960. /**
  961. * @group disconnected
  962. */
  963. public function testAskSlotMapToRedisClusterOnMovedResponseByDefault()
  964. {
  965. $cmdGET = Command\RawCommand::create('GET', 'node:1001');
  966. $rspMOVED = new Response\Error('MOVED 1970 127.0.0.1:6380');
  967. $rspSlotsArray = array(
  968. array(0, 8191, array('127.0.0.1', 6379)),
  969. array(8192, 16383, array('127.0.0.1', 6380)),
  970. );
  971. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
  972. $connection1
  973. ->expects($this->once())
  974. ->method('executeCommand')
  975. ->with($cmdGET)
  976. ->will($this->returnValue($rspMOVED));
  977. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
  978. $connection2
  979. ->expects($this->at(0))
  980. ->method('executeCommand')
  981. ->with($this->isRedisCommand(
  982. 'CLUSTER', array('SLOTS')
  983. ))
  984. ->will($this->returnValue($rspSlotsArray));
  985. $connection2
  986. ->expects($this->at(3))
  987. ->method('executeCommand')
  988. ->with($cmdGET)
  989. ->will($this->returnValue('foobar'));
  990. $factory = $this->getMock('Predis\Connection\Factory');
  991. $factory
  992. ->expects($this->once())
  993. ->method('create')
  994. ->with(array(
  995. 'host' => '127.0.0.1',
  996. 'port' => '6380',
  997. ))
  998. ->will($this->returnValue($connection2));
  999. $cluster = new RedisCluster($factory);
  1000. $cluster->add($connection1);
  1001. $this->assertSame('foobar', $cluster->executeCommand($cmdGET));
  1002. $this->assertSame(2, count($cluster));
  1003. }
  1004. /**
  1005. * @group disconnected
  1006. * @expectedException \Predis\NotSupportedException
  1007. * @expectedExceptionMessage Cannot use 'PING' with redis-cluster.
  1008. */
  1009. public function testThrowsExceptionOnNonSupportedCommand()
  1010. {
  1011. $ping = $this->getCommandFactory()->createCommand('ping');
  1012. $cluster = new RedisCluster(new Connection\Factory());
  1013. $cluster->add($this->getMockConnection('tcp://127.0.0.1:6379'));
  1014. $cluster->getConnectionByCommand($ping);
  1015. }
  1016. /**
  1017. * @medium
  1018. * @group disconnected
  1019. */
  1020. public function testCanBeSerialized()
  1021. {
  1022. $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379?slots=0-5460');
  1023. $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380?slots=5461-10922');
  1024. $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=10923-16383');
  1025. $cluster = new RedisCluster(new Connection\Factory());
  1026. $cluster->add($connection1);
  1027. $cluster->add($connection2);
  1028. $cluster->add($connection3);
  1029. $cluster->buildSlotMap();
  1030. $unserialized = unserialize(serialize($cluster));
  1031. $this->assertEquals($cluster, $unserialized);
  1032. }
  1033. }