SentinelReplicationTest.php 46 KB

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