RedisClusterTest.php 43 KB

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