SentinelReplicationTest.php 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182
  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\Replication;
  14. use Predis\Response;
  15. use PredisTestCase;
  16. /**
  17. *
  18. */
  19. class SentinelReplicationTest extends PredisTestCase
  20. {
  21. /**
  22. * @group disconnected
  23. * @expectedException Predis\ClientException
  24. * @expectedExceptionMessage No sentinel server available for autodiscovery.
  25. */
  26. public function testMethodGetSentinelConnectionThrowsExceptionOnEmptySentinelsPool()
  27. {
  28. $replication = $this->getReplicationConnection('svc', array());
  29. $replication->getSentinelConnection();
  30. }
  31. /**
  32. * @group disconnected
  33. */
  34. public function testMethodGetSentinelConnectionReturnsFirstAvailableSentinel()
  35. {
  36. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  37. $sentinel2 = $this->getMockSentinelConnection('tcp://127.0.0.1:5382?alias=sentinel2');
  38. $sentinel3 = $this->getMockSentinelConnection('tcp://127.0.0.1:5383?alias=sentinel3');
  39. $replication = $this->getReplicationConnection('svc', array($sentinel1, $sentinel2, $sentinel3));
  40. $this->assertSame($sentinel1, $replication->getSentinelConnection());
  41. }
  42. /**
  43. * @group disconnected
  44. */
  45. public function testMethodAddAttachesMasterOrSlaveNodesToReplication()
  46. {
  47. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  48. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  49. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  50. $slave2 = $this->getMockConnection('tcp://127.0.0.1:6383?alias=slave2');
  51. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  52. $replication->add($master);
  53. $replication->add($slave1);
  54. $replication->add($slave2);
  55. $this->assertSame($master, $replication->getConnectionById('master'));
  56. $this->assertSame($slave1, $replication->getConnectionById('slave1'));
  57. $this->assertSame($slave2, $replication->getConnectionById('slave2'));
  58. $this->assertSame($master, $replication->getMaster());
  59. $this->assertSame(array($slave1, $slave2), $replication->getSlaves());
  60. }
  61. /**
  62. * @group disconnected
  63. */
  64. public function testMethodRemoveDismissesMasterOrSlaveNodesFromReplication()
  65. {
  66. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  67. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  68. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  69. $slave2 = $this->getMockConnection('tcp://127.0.0.1:6383?alias=slave2');
  70. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  71. $replication->add($master);
  72. $replication->add($slave1);
  73. $replication->add($slave2);
  74. $this->assertTrue($replication->remove($slave1));
  75. $this->assertFalse($replication->remove($sentinel1));
  76. $this->assertSame('127.0.0.1:6381', (string) $replication->getMaster());
  77. $this->assertCount(1, $slaves = $replication->getSlaves());
  78. $this->assertSame('127.0.0.1:6383', (string) $slaves[0]);
  79. }
  80. /**
  81. * @group disconnected
  82. */
  83. public function testMethodUpdateSentinelsFetchesSentinelNodes()
  84. {
  85. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  86. $sentinel1->expects($this->once())
  87. ->method('executeCommand')
  88. ->with($this->isRedisCommand(
  89. 'SENTINEL', array('sentinels', 'svc')
  90. ))
  91. ->will($this->returnValue(
  92. array(
  93. array(
  94. 'name', '127.0.0.1:5382',
  95. 'ip', '127.0.0.1',
  96. 'port', '5382',
  97. 'runid', 'a113aa7a0d4870a85bb22b4b605fd26eb93ed40e',
  98. 'flags', 'sentinel',
  99. ),
  100. array(
  101. 'name', '127.0.0.1:5383',
  102. 'ip', '127.0.0.1',
  103. 'port', '5383',
  104. 'runid', 'f53b52d281be5cdd4873700c94846af8dbe47209',
  105. 'flags', 'sentinel',
  106. ),
  107. )
  108. ));
  109. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  110. $replication->updateSentinels();
  111. // TODO: sorry for the smell...
  112. $reflection = new \ReflectionProperty($replication, 'sentinels');
  113. $reflection->setAccessible(true);
  114. $expected = array(
  115. array('host' => '127.0.0.1', 'port' => '5381'),
  116. array('host' => '127.0.0.1', 'port' => '5382'),
  117. array('host' => '127.0.0.1', 'port' => '5383'),
  118. );
  119. $this->assertSame($sentinel1, $replication->getSentinelConnection());
  120. $this->assertSame($expected, array_intersect_key($expected, $reflection->getValue($replication)));
  121. }
  122. /**
  123. * @group disconnected
  124. */
  125. public function testMethodUpdateSentinelsRemovesCurrentSentinelAndRetriesNextOneOnFailure()
  126. {
  127. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  128. $sentinel1->expects($this->once())
  129. ->method('executeCommand')
  130. ->with($this->isRedisCommand(
  131. 'SENTINEL', array('sentinels', 'svc')
  132. ))
  133. ->will($this->throwException(
  134. new Connection\ConnectionException($sentinel1, 'Unknown connection error [127.0.0.1:5381]')
  135. ));
  136. $sentinel2 = $this->getMockSentinelConnection('tcp://127.0.0.1:5382?alias=sentinel2');
  137. $sentinel2->expects($this->once())
  138. ->method('executeCommand')
  139. ->with($this->isRedisCommand(
  140. 'SENTINEL', array('sentinels', 'svc')
  141. ))
  142. ->will($this->returnValue(
  143. array(
  144. array(
  145. 'name', '127.0.0.1:5383',
  146. 'ip', '127.0.0.1',
  147. 'port', '5383',
  148. 'runid', 'f53b52d281be5cdd4873700c94846af8dbe47209',
  149. 'flags', 'sentinel',
  150. ),
  151. )
  152. ));
  153. $replication = $this->getReplicationConnection('svc', array($sentinel1, $sentinel2));
  154. $replication->updateSentinels();
  155. // TODO: sorry for the smell...
  156. $reflection = new \ReflectionProperty($replication, 'sentinels');
  157. $reflection->setAccessible(true);
  158. $expected = array(
  159. array('host' => '127.0.0.1', 'port' => '5382'),
  160. array('host' => '127.0.0.1', 'port' => '5383'),
  161. );
  162. $this->assertSame($sentinel2, $replication->getSentinelConnection());
  163. $this->assertSame($expected, array_intersect_key($expected, $reflection->getValue($replication)));
  164. }
  165. /**
  166. * @group disconnected
  167. * @expectedException Predis\ClientException
  168. * @expectedExceptionMessage No sentinel server available for autodiscovery.
  169. */
  170. public function testMethodUpdateSentinelsThrowsExceptionOnNoAvailableSentinel()
  171. {
  172. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  173. $sentinel1->expects($this->once())
  174. ->method('executeCommand')
  175. ->with($this->isRedisCommand(
  176. 'SENTINEL', array('sentinels', 'svc')
  177. ))
  178. ->will($this->throwException(
  179. new Connection\ConnectionException($sentinel1, 'Unknown connection error [127.0.0.1:5381]')
  180. ));
  181. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  182. $replication->updateSentinels();
  183. }
  184. /**
  185. * @group disconnected
  186. */
  187. public function testMethodQuerySentinelFetchesMasterNodeSlaveNodesAndSentinelNodes()
  188. {
  189. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  190. $sentinel1->expects($this->exactly(3))
  191. ->method('executeCommand')
  192. ->withConsecutive(
  193. $this->isRedisCommand('SENTINEL', array('sentinels', 'svc')),
  194. $this->isRedisCommand('SENTINEL', array('get-master-addr-by-name', 'svc')),
  195. $this->isRedisCommand('SENTINEL', array('slaves', 'svc'))
  196. )
  197. ->will($this->onConsecutiveCalls(
  198. // SENTINEL sentinels svc
  199. array(
  200. array(
  201. 'name', '127.0.0.1:5382',
  202. 'ip', '127.0.0.1',
  203. 'port', '5382',
  204. 'runid', 'a113aa7a0d4870a85bb22b4b605fd26eb93ed40e',
  205. 'flags', 'sentinel',
  206. ),
  207. ),
  208. // SENTINEL get-master-addr-by-name svc
  209. array('127.0.0.1', '6381'),
  210. // SENTINEL slaves svc
  211. array(
  212. array(
  213. 'name', '127.0.0.1:6382',
  214. 'ip', '127.0.0.1',
  215. 'port', '6382',
  216. 'runid', '112cdebd22924a7d962be496f3a1c4c7c9bad93f',
  217. 'flags', 'slave',
  218. 'master-host', '127.0.0.1',
  219. 'master-port', '6381',
  220. ),
  221. array(
  222. 'name', '127.0.0.1:6383',
  223. 'ip', '127.0.0.1',
  224. 'port', '6383',
  225. 'runid', '1c0bf1291797fbc5608c07a17da394147dc62817',
  226. 'flags', 'slave',
  227. 'master-host', '127.0.0.1',
  228. 'master-port', '6381',
  229. ),
  230. )
  231. ));
  232. $sentinel2 = $this->getMockSentinelConnection('tcp://127.0.0.1:5382?alias=sentinel2');
  233. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  234. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  235. $slave2 = $this->getMockConnection('tcp://127.0.0.1:6383?alias=slave2');
  236. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  237. $replication->querySentinel();
  238. // TODO: sorry for the smell...
  239. $reflection = new \ReflectionProperty($replication, 'sentinels');
  240. $reflection->setAccessible(true);
  241. $sentinels = array(
  242. array('host' => '127.0.0.1', 'port' => '5381'),
  243. array('host' => '127.0.0.1', 'port' => '5382'),
  244. );
  245. $this->assertSame($sentinel1, $replication->getSentinelConnection());
  246. $this->assertSame($sentinels, array_intersect_key($sentinels, $reflection->getValue($replication)));
  247. $master = $replication->getMaster();
  248. $slaves = $replication->getSlaves();
  249. $this->assertSame('127.0.0.1:6381', (string) $master);
  250. $this->assertCount(2, $slaves);
  251. $this->assertSame('127.0.0.1:6382', (string) $slaves[0]);
  252. $this->assertSame('127.0.0.1:6383', (string) $slaves[1]);
  253. }
  254. /**
  255. * @group disconnected
  256. */
  257. public function testMethodGetMasterAsksSentinelForMasterOnMasterNotSet()
  258. {
  259. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  260. $sentinel1->expects($this->at(0))
  261. ->method('executeCommand')
  262. ->with($this->isRedisCommand(
  263. 'SENTINEL', array('get-master-addr-by-name', 'svc')
  264. ))
  265. ->will($this->returnValue(
  266. array('127.0.0.1', '6381')
  267. ));
  268. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  269. $this->assertSame('127.0.0.1:6381', (string) $replication->getMaster());
  270. }
  271. /**
  272. * @group disconnected
  273. * @expectedException Predis\ClientException
  274. * @expectedExceptionMessage No sentinel server available for autodiscovery.
  275. */
  276. public function testMethodGetMasterThrowsExceptionOnNoAvailableSentinels()
  277. {
  278. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  279. $sentinel1->expects($this->any())
  280. ->method('executeCommand')
  281. ->with($this->isRedisCommand(
  282. 'SENTINEL', array('get-master-addr-by-name', 'svc')
  283. ))
  284. ->will($this->throwException(
  285. new Connection\ConnectionException($sentinel1, 'Unknown connection error [127.0.0.1:5381]')
  286. ));
  287. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  288. $replication->getMaster();
  289. }
  290. /**
  291. * @group disconnected
  292. */
  293. public function testMethodGetSlavesOnEmptySlavePoolAsksSentinelForSlaves()
  294. {
  295. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  296. $sentinel1->expects($this->at(0))
  297. ->method('executeCommand')
  298. ->with($this->isRedisCommand(
  299. 'SENTINEL', array('slaves', 'svc')
  300. ))
  301. ->will($this->returnValue(
  302. array(
  303. array(
  304. 'name', '127.0.0.1:6382',
  305. 'ip', '127.0.0.1',
  306. 'port', '6382',
  307. 'runid', '112cdebd22924a7d962be496f3a1c4c7c9bad93f',
  308. 'flags', 'slave',
  309. 'master-host', '127.0.0.1',
  310. 'master-port', '6381',
  311. ),
  312. array(
  313. 'name', '127.0.0.1:6383',
  314. 'ip', '127.0.0.1',
  315. 'port', '6383',
  316. 'runid', '1c0bf1291797fbc5608c07a17da394147dc62817',
  317. 'flags', 'slave',
  318. 'master-host', '127.0.0.1',
  319. 'master-port', '6381',
  320. ),
  321. )
  322. ));
  323. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  324. $slaves = $replication->getSlaves();
  325. $this->assertSame('127.0.0.1:6382', (string) $slaves[0]);
  326. $this->assertSame('127.0.0.1:6383', (string) $slaves[1]);
  327. }
  328. /**
  329. * @group disconnected
  330. * @expectedException Predis\ClientException
  331. * @expectedExceptionMessage No sentinel server available for autodiscovery.
  332. */
  333. public function testMethodGetSlavesThrowsExceptionOnNoAvailableSentinels()
  334. {
  335. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  336. $sentinel1->expects($this->any())
  337. ->method('executeCommand')
  338. ->with($this->isRedisCommand(
  339. 'SENTINEL', array('slaves', 'svc')
  340. ))
  341. ->will($this->throwException(
  342. new Connection\ConnectionException($sentinel1, 'Unknown connection error [127.0.0.1:5381]')
  343. ));
  344. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  345. $replication->getSlaves();
  346. }
  347. /**
  348. * @group disconnected
  349. * @expectedException Predis\ClientException
  350. * @expectedExceptionMessage No sentinel server available for autodiscovery.
  351. */
  352. public function testMethodConnectThrowsExceptionOnConnectWithEmptySentinelsPool()
  353. {
  354. $replication = $this->getReplicationConnection('svc', array());
  355. $replication->connect();
  356. }
  357. /**
  358. * @group disconnected
  359. */
  360. public function testMethodConnectForcesConnectionToSlave()
  361. {
  362. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  363. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  364. $master->expects($this->never())
  365. ->method('connect');
  366. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  367. $slave1->expects($this->once())
  368. ->method('connect');
  369. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  370. $replication->add($master);
  371. $replication->add($slave1);
  372. $replication->connect();
  373. }
  374. /**
  375. * @group disconnected
  376. */
  377. public function testMethodConnectOnEmptySlavePoolAsksSentinelForSlavesAndForcesConnectionToSlave()
  378. {
  379. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  380. $sentinel1->expects($this->any())
  381. ->method('executeCommand')
  382. ->with($this->isRedisCommand(
  383. 'SENTINEL', array('slaves', 'svc')
  384. ))
  385. ->will($this->returnValue(
  386. array(
  387. array(
  388. 'name', '127.0.0.1:6382',
  389. 'ip', '127.0.0.1',
  390. 'port', '6382',
  391. 'runid', '112cdebd22924a7d962be496f3a1c4c7c9bad93f',
  392. 'flags', 'slave',
  393. 'master-host', '127.0.0.1',
  394. 'master-port', '6381',
  395. ),
  396. )
  397. ));
  398. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  399. $master->expects($this->never())
  400. ->method('connect');
  401. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  402. $slave1->expects($this->once())
  403. ->method('connect');
  404. $factory = $this->getMock('Predis\Connection\FactoryInterface');
  405. $factory->expects($this->once())
  406. ->method('create')
  407. ->with(array(
  408. 'host' => '127.0.0.1',
  409. 'port' => '6382',
  410. 'alias' => 'slave-127.0.0.1:6382',
  411. ))
  412. ->will($this->returnValue($slave1));
  413. $replication = $this->getReplicationConnection('svc', array($sentinel1), $factory);
  414. $replication->add($master);
  415. $replication->connect();
  416. }
  417. /**
  418. * @group disconnected
  419. */
  420. public function testMethodDisconnectForcesDisconnectionOnAllConnectionsInPool()
  421. {
  422. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  423. $sentinel1->expects($this->never())->method('disconnect');
  424. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  425. $master->expects($this->once())->method('disconnect');
  426. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  427. $slave1->expects($this->once())->method('disconnect');
  428. $slave2 = $this->getMockConnection('tcp://127.0.0.1:6383?alias=slave2');
  429. $slave2->expects($this->once())->method('disconnect');
  430. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  431. $replication->add($master);
  432. $replication->add($slave1);
  433. $replication->add($slave2);
  434. $replication->disconnect();
  435. }
  436. /**
  437. * @group disconnected
  438. */
  439. public function testMethodIsConnectedReturnConnectionStatusOfCurrentConnection()
  440. {
  441. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  442. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  443. $slave1->expects($this->exactly(2))
  444. ->method('isConnected')
  445. ->will($this->onConsecutiveCalls(true, false));
  446. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  447. $replication->add($slave1);
  448. $this->assertFalse($replication->isConnected());
  449. $replication->connect();
  450. $this->assertTrue($replication->isConnected());
  451. $replication->getConnectionById('slave1')->disconnect();
  452. $this->assertFalse($replication->isConnected());
  453. }
  454. /**
  455. * @group disconnected
  456. */
  457. public function testMethodGetConnectionByIdReturnsConnectionWhenFound()
  458. {
  459. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  460. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  461. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  462. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  463. $replication->add($master);
  464. $replication->add($slave1);
  465. $this->assertSame($master, $replication->getConnectionById('master'));
  466. $this->assertSame($slave1, $replication->getConnectionById('slave1'));
  467. $this->assertNull($replication->getConnectionById('unknown'));
  468. }
  469. /**
  470. * @group disconnected
  471. */
  472. public function testMethodSwitchToSelectsCurrentConnectionByConnectionAlias()
  473. {
  474. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  475. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  476. $master->expects($this->once())->method('connect');
  477. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  478. $slave1->expects($this->never())->method('connect');
  479. $slave2 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave2');
  480. $slave2->expects($this->once())->method('connect');
  481. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  482. $replication->add($master);
  483. $replication->add($slave1);
  484. $replication->add($slave2);
  485. $replication->switchTo('master');
  486. $this->assertSame($master, $replication->getCurrent());
  487. $replication->switchTo('slave2');
  488. $this->assertSame($slave2, $replication->getCurrent());
  489. }
  490. /**
  491. * @group disconnected
  492. * @expectedException InvalidArgumentException
  493. * @expectedExceptionMessage Invalid connection or connection not found.
  494. */
  495. public function testMethodSwitchToThrowsExceptionOnConnectionNotFound()
  496. {
  497. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  498. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  499. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  500. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  501. $replication->add($master);
  502. $replication->add($slave1);
  503. $replication->switchTo('unknown');
  504. }
  505. /**
  506. * @group disconnected
  507. */
  508. public function testMethodSwitchToMasterSelectsCurrentConnectionToMaster()
  509. {
  510. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  511. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  512. $master->expects($this->once())->method('connect');
  513. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  514. $slave1->expects($this->never())->method('connect');
  515. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  516. $replication->add($master);
  517. $replication->add($slave1);
  518. $replication->switchToMaster();
  519. $this->assertSame($master, $replication->getCurrent());
  520. }
  521. /**
  522. * @group disconnected
  523. */
  524. public function testMethodSwitchToSlaveSelectsCurrentConnectionToRandomSlave()
  525. {
  526. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  527. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  528. $master->expects($this->never())->method('connect');
  529. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  530. $slave1->expects($this->once())->method('connect');
  531. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  532. $replication->add($master);
  533. $replication->add($slave1);
  534. $replication->switchToSlave();
  535. $this->assertSame($slave1, $replication->getCurrent());
  536. }
  537. /**
  538. * @group disconnected
  539. */
  540. public function testGetConnectionReturnsMasterForWriteCommands()
  541. {
  542. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  543. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  544. $master->expects($this->exactly(2))
  545. ->method('isConnected')
  546. ->will($this->onConsecutiveCalls(false, true));
  547. $master->expects($this->at(2))
  548. ->method('executeCommand')
  549. ->with($this->isRedisCommand('ROLE'))
  550. ->will($this->returnValue(array(
  551. 'master', 3129659, array(array('127.0.0.1', 6382, 3129242)),
  552. )));
  553. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  554. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  555. $replication->add($master);
  556. $replication->add($slave1);
  557. $this->assertSame($master, $replication->getConnection(
  558. Command\RawCommand::create('set', 'key', 'value')
  559. ));
  560. $this->assertSame($master, $replication->getConnection(
  561. Command\RawCommand::create('del', 'key')
  562. ));
  563. }
  564. /**
  565. * @group disconnected
  566. */
  567. public function testGetConnectionReturnsSlaveForReadOnlyCommands()
  568. {
  569. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  570. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  571. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  572. $slave1->expects($this->exactly(2))
  573. ->method('isConnected')
  574. ->will($this->onConsecutiveCalls(false, true));
  575. $slave1->expects($this->at(2))
  576. ->method('executeCommand')
  577. ->with($this->isRedisCommand('ROLE'))
  578. ->will($this->returnValue(array(
  579. 'slave', '127.0.0.1', 9000, 'connected', 3167038,
  580. )));
  581. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  582. $replication->add($master);
  583. $replication->add($slave1);
  584. $this->assertSame($slave1, $replication->getConnection(
  585. Command\RawCommand::create('get', 'key')
  586. ));
  587. $this->assertSame($slave1, $replication->getConnection(
  588. Command\RawCommand::create('exists', 'key')
  589. ));
  590. }
  591. /**
  592. * @group disconnected
  593. */
  594. public function testGetConnectionSwitchesToMasterAfterWriteCommand()
  595. {
  596. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  597. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  598. $master->expects($this->exactly(2))
  599. ->method('isConnected')
  600. ->will($this->onConsecutiveCalls(false, true));
  601. $master->expects($this->at(2))
  602. ->method('executeCommand')
  603. ->with($this->isRedisCommand('ROLE'))
  604. ->will($this->returnValue(array(
  605. 'master', 3129659, array(array('127.0.0.1', 6382, 3129242)),
  606. )));
  607. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  608. $slave1->expects($this->exactly(1))
  609. ->method('isConnected')
  610. ->will($this->onConsecutiveCalls(false));
  611. $slave1->expects($this->at(2))
  612. ->method('executeCommand')
  613. ->with($this->isRedisCommand('ROLE'))
  614. ->will($this->returnValue(array(
  615. 'slave', '127.0.0.1', 9000, 'connected', 3167038,
  616. )));
  617. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  618. $replication->add($master);
  619. $replication->add($slave1);
  620. $this->assertSame($slave1, $replication->getConnection(
  621. Command\RawCommand::create('exists', 'key')
  622. ));
  623. $this->assertSame($master, $replication->getConnection(
  624. Command\RawCommand::create('set', 'key', 'value')
  625. ));
  626. $this->assertSame($master, $replication->getConnection(
  627. Command\RawCommand::create('get', 'key')
  628. ));
  629. }
  630. /**
  631. * @group disconnected
  632. * @expectedException Predis\Replication\RoleException
  633. * @expectedExceptionMessage Expected master but got slave [127.0.0.1:6381]
  634. */
  635. public function testGetConnectionThrowsExceptionOnNodeRoleMismatch()
  636. {
  637. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  638. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  639. $master->expects($this->once())
  640. ->method('isConnected')
  641. ->will($this->returnValue(false));
  642. $master->expects($this->at(2))
  643. ->method('executeCommand')
  644. ->with($this->isRedisCommand('ROLE'))
  645. ->will($this->returnValue(array(
  646. 'slave', '127.0.0.1', 9000, 'connected', 3167038,
  647. )));
  648. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  649. $replication->add($master);
  650. $replication->getConnection(Command\RawCommand::create('del', 'key'));
  651. }
  652. /**
  653. * @group disconnected
  654. */
  655. public function testGetConnectionReturnsMasterForReadOnlyOperationsOnUnavailableSlaves()
  656. {
  657. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  658. $sentinel1->expects($this->once())
  659. ->method('executeCommand')
  660. ->with($this->isRedisCommand(
  661. 'SENTINEL', array('slaves', 'svc')
  662. ))
  663. ->will($this->returnValue(
  664. array(
  665. array(
  666. 'name', '127.0.0.1:6382',
  667. 'ip', '127.0.0.1',
  668. 'port', '6382',
  669. 'runid', '1c0bf1291797fbc5608c07a17da394147dc62817',
  670. 'flags', 'slave,s_down,disconnected',
  671. 'master-host', '127.0.0.1',
  672. 'master-port', '6381',
  673. ),
  674. )
  675. ));
  676. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  677. $master->expects($this->once())
  678. ->method('isConnected')
  679. ->will($this->returnValue(false));
  680. $master->expects($this->at(2))
  681. ->method('executeCommand')
  682. ->with($this->isRedisCommand('ROLE'))
  683. ->will($this->returnValue(array(
  684. 'master', '0', array(),
  685. )));
  686. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  687. $replication->add($master);
  688. $replication->getConnection(Command\RawCommand::create('get', 'key'));
  689. }
  690. /**
  691. * @group disconnected
  692. */
  693. public function testMethodExecuteCommandSendsCommandToNodeAndReturnsResponse()
  694. {
  695. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  696. $cmdGet = Command\RawCommand::create('get', 'key');
  697. $cmdGetResponse = 'value';
  698. $cmdSet = Command\RawCommand::create('set', 'key', 'value');
  699. $cmdSetResponse = Response\Status::get('OK');
  700. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  701. $master->expects($this->any())
  702. ->method('isConnected')
  703. ->will($this->returnValue(true));
  704. $master->expects($this->at(2))
  705. ->method('executeCommand')
  706. ->with($this->isRedisCommand('SET', array('key', $cmdGetResponse)))
  707. ->will($this->returnValue($cmdSetResponse));
  708. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  709. $slave1->expects($this->any())
  710. ->method('isConnected')
  711. ->will($this->returnValue(true));
  712. $slave1->expects($this->at(2))
  713. ->method('executeCommand')
  714. ->with($this->isRedisCommand('GET', array('key')))
  715. ->will($this->returnValue($cmdGetResponse));
  716. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  717. $replication->add($master);
  718. $replication->add($slave1);
  719. $this->assertSame($cmdGetResponse, $replication->executeCommand($cmdGet));
  720. $this->assertSame($cmdSetResponse, $replication->executeCommand($cmdSet));
  721. }
  722. /**
  723. * @group disconnected
  724. */
  725. public function testMethodExecuteCommandRetriesReadOnlyCommandOnNextSlaveOnFailure()
  726. {
  727. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  728. $sentinel1->expects($this->any())
  729. ->method('executeCommand')
  730. ->with($this->isRedisCommand(
  731. 'SENTINEL', array('slaves', 'svc')
  732. ))
  733. ->will($this->returnValue(
  734. array(
  735. array(
  736. 'name', '127.0.0.1:6383',
  737. 'ip', '127.0.0.1',
  738. 'port', '6383',
  739. 'runid', '1c0bf1291797fbc5608c07a17da394147dc62817',
  740. 'flags', 'slave',
  741. 'master-host', '127.0.0.1',
  742. 'master-port', '6381',
  743. ),
  744. )
  745. ));
  746. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  747. $master->expects($this->any())
  748. ->method('isConnected')
  749. ->will($this->returnValue(true));
  750. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  751. $slave1->expects($this->any())
  752. ->method('isConnected')
  753. ->will($this->returnValue(true));
  754. $slave1->expects($this->at(2))
  755. ->method('executeCommand')
  756. ->with($this->isRedisCommand('GET', array('key')))
  757. ->will($this->throwException(
  758. new Connection\ConnectionException($slave1, 'Unknown connection error [127.0.0.1:6382]')
  759. ));
  760. $slave2 = $this->getMockConnection('tcp://127.0.0.1:6383?alias=slave2');
  761. $slave2->expects($this->any())
  762. ->method('isConnected')
  763. ->will($this->returnValue(true));
  764. $slave2->expects($this->at(2))
  765. ->method('executeCommand')
  766. ->with($this->isRedisCommand('GET', array('key')))
  767. ->will($this->returnValue('value'));
  768. $factory = $this->getMock('Predis\Connection\FactoryInterface');
  769. $factory->expects($this->once())
  770. ->method('create')
  771. ->with(array(
  772. 'host' => '127.0.0.1',
  773. 'port' => '6383',
  774. 'alias' => 'slave-127.0.0.1:6383',
  775. ))
  776. ->will($this->returnValue($slave2));
  777. $replication = $this->getReplicationConnection('svc', array($sentinel1), $factory);
  778. $replication->add($master);
  779. $replication->add($slave1);
  780. $this->assertSame('value', $replication->executeCommand(
  781. Command\RawCommand::create('get', 'key')
  782. ));
  783. }
  784. /**
  785. * @group disconnected
  786. */
  787. public function testMethodExecuteCommandRetriesWriteCommandOnNewMasterOnFailure()
  788. {
  789. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  790. $sentinel1->expects($this->any())
  791. ->method('executeCommand')
  792. ->with($this->isRedisCommand(
  793. 'SENTINEL', array('get-master-addr-by-name', 'svc')
  794. ))
  795. ->will($this->returnValue(
  796. array('127.0.0.1', '6391')
  797. ));
  798. $masterOld = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  799. $masterOld->expects($this->any())
  800. ->method('isConnected')
  801. ->will($this->returnValue(true));
  802. $masterOld->expects($this->at(2))
  803. ->method('executeCommand')
  804. ->with($this->isRedisCommand('DEL', array('key')))
  805. ->will($this->throwException(
  806. new Connection\ConnectionException($masterOld, 'Unknown connection error [127.0.0.1:6381]')
  807. ));
  808. $masterNew = $this->getMockConnection('tcp://127.0.0.1:6391?alias=master');
  809. $masterNew->expects($this->any())
  810. ->method('isConnected')
  811. ->will($this->returnValue(true));
  812. $masterNew->expects($this->at(2))
  813. ->method('executeCommand')
  814. ->with($this->isRedisCommand('DEL', array('key')))
  815. ->will($this->returnValue(1));
  816. $factory = $this->getMock('Predis\Connection\FactoryInterface');
  817. $factory->expects($this->once())
  818. ->method('create')
  819. ->with(array(
  820. 'host' => '127.0.0.1',
  821. 'port' => '6391',
  822. 'alias' => 'master',
  823. ))
  824. ->will($this->returnValue($masterNew));
  825. $replication = $this->getReplicationConnection('svc', array($sentinel1), $factory);
  826. $replication->add($masterOld);
  827. $this->assertSame(1, $replication->executeCommand(
  828. Command\RawCommand::create('del', 'key')
  829. ));
  830. }
  831. /**
  832. * @group disconnected
  833. * @expectedException Predis\Response\ServerException
  834. * @expectedExceptionMessage ERR No such master with that name
  835. */
  836. public function testMethodExecuteCommandThrowsExceptionOnUnknownServiceName()
  837. {
  838. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  839. $sentinel1->expects($this->any())
  840. ->method('executeCommand')
  841. ->with($this->isRedisCommand(
  842. 'SENTINEL', array('get-master-addr-by-name', 'svc')
  843. ))
  844. ->will($this->returnValue(null));
  845. $masterOld = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  846. $masterOld->expects($this->any())
  847. ->method('isConnected')
  848. ->will($this->returnValue(true));
  849. $masterOld->expects($this->at(2))
  850. ->method('executeCommand')
  851. ->with($this->isRedisCommand('DEL', array('key')))
  852. ->will($this->throwException(
  853. new Connection\ConnectionException($masterOld, 'Unknown connection error [127.0.0.1:6381]')
  854. ));
  855. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  856. $replication->add($masterOld);
  857. $replication->executeCommand(
  858. Command\RawCommand::create('del', 'key')
  859. );
  860. }
  861. /**
  862. * @group disconnected
  863. * @expectedException Predis\ClientException
  864. * @expectedExceptionMessage No sentinel server available for autodiscovery.
  865. */
  866. public function testMethodExecuteCommandThrowsExceptionOnConnectionFailureAndNoAvailableSentinels()
  867. {
  868. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  869. $sentinel1->expects($this->any())
  870. ->method('executeCommand')
  871. ->with($this->isRedisCommand(
  872. 'SENTINEL', array('get-master-addr-by-name', 'svc')
  873. ))
  874. ->will($this->throwException(
  875. new Connection\ConnectionException($sentinel1, 'Unknown connection error [127.0.0.1:5381]')
  876. ));
  877. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  878. $master->expects($this->any())
  879. ->method('isConnected')
  880. ->will($this->returnValue(true));
  881. $master->expects($this->at(2))
  882. ->method('executeCommand')
  883. ->with($this->isRedisCommand('DEL', array('key')))
  884. ->will($this->throwException(
  885. new Connection\ConnectionException($master, 'Unknown connection error [127.0.0.1:6381]')
  886. ));
  887. $replication = $this->getReplicationConnection('svc', array($sentinel1));
  888. $replication->add($master);
  889. $replication->executeCommand(
  890. Command\RawCommand::create('del', 'key')
  891. );
  892. }
  893. /**
  894. * @group disconnected
  895. */
  896. public function testMethodGetReplicationStrategyReturnsInstance()
  897. {
  898. $strategy = new Replication\ReplicationStrategy();
  899. $factory = new Connection\Factory();
  900. $replication = new SentinelReplication(
  901. 'svc', array('tcp://127.0.0.1:5381?alias=sentinel1'), $factory, $strategy
  902. );
  903. $this->assertSame($strategy, $replication->getReplicationStrategy());
  904. }
  905. /**
  906. * @group disconnected
  907. */
  908. public function testMethodSerializeCanSerializeWholeObject()
  909. {
  910. $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1');
  911. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  912. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  913. $slave2 = $this->getMockConnection('tcp://127.0.0.1:6383?alias=slave2');
  914. $strategy = new Replication\ReplicationStrategy();
  915. $factory = new Connection\Factory();
  916. $replication = new SentinelReplication('svc', array($sentinel1), $factory, $strategy);
  917. $replication->add($master);
  918. $replication->add($slave1);
  919. $replication->add($slave2);
  920. $unserialized = unserialize(serialize($replication));
  921. $this->assertEquals($master, $unserialized->getConnectionById('master'));
  922. $this->assertEquals($slave1, $unserialized->getConnectionById('slave1'));
  923. $this->assertEquals($master, $unserialized->getConnectionById('slave2'));
  924. $this->assertEquals($strategy, $unserialized->getReplicationStrategy());
  925. }
  926. // ******************************************************************** //
  927. // ---- HELPER METHODS ------------------------------------------------ //
  928. // ******************************************************************** //
  929. /**
  930. * Creates a new instance of replication connection.
  931. *
  932. * @param string $service Name of the service
  933. * @param array $sentinels Array of sentinels
  934. * @param ConnectionFactoryInterface|null $factory Optional connection factory instance.
  935. *
  936. * @return SentinelReplication
  937. */
  938. protected function getReplicationConnection($service, $sentinels, Connection\FactoryInterface $factory = null)
  939. {
  940. $factory = $factory ?: new Connection\Factory();
  941. $replication = new SentinelReplication($service, $sentinels, $factory);
  942. $replication->setRetryWait(0);
  943. return $replication;
  944. }
  945. /**
  946. * Returns a base mocked connection from Predis\Connection\NodeConnectionInterface.
  947. *
  948. * @param mixed $parameters Optional parameters.
  949. *
  950. * @return mixed
  951. */
  952. protected function getMockSentinelConnection($parameters = null)
  953. {
  954. $connection = $this->getMockConnection($parameters);
  955. return $connection;
  956. }
  957. /**
  958. * Returns a base mocked connection from Predis\Connection\NodeConnectionInterface.
  959. *
  960. * @param mixed $parameters Optional parameters.
  961. *
  962. * @return mixed
  963. */
  964. protected function getMockConnection($parameters = null)
  965. {
  966. $connection = $this->getMock('Predis\Connection\NodeConnectionInterface');
  967. if ($parameters) {
  968. $parameters = Connection\Parameters::create($parameters);
  969. $hash = "{$parameters->host}:{$parameters->port}";
  970. $connection->expects($this->any())
  971. ->method('getParameters')
  972. ->will($this->returnValue($parameters));
  973. $connection->expects($this->any())
  974. ->method('__toString')
  975. ->will($this->returnValue($hash));
  976. }
  977. return $connection;
  978. }
  979. }