MasterSlaveReplicationTest.php 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159
  1. <?php
  2. /*
  3. * This file is part of the Predis package.
  4. *
  5. * (c) Daniele Alessandri <suppakilla@gmail.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Predis\Connection\Aggregate;
  11. use Predis\Connection;
  12. use Predis\Command;
  13. use Predis\Profile;
  14. use Predis\Replication\ReplicationStrategy;
  15. use PredisTestCase;
  16. /**
  17. *
  18. */
  19. class MasterSlaveReplicationTest extends PredisTestCase
  20. {
  21. /**
  22. * @group disconnected
  23. */
  24. public function testAddingConnectionsToReplication()
  25. {
  26. $master = $this->getMockConnection('tcp://host1?alias=master');
  27. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  28. $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
  29. $replication = new MasterSlaveReplication();
  30. $replication->add($master);
  31. $replication->add($slave1);
  32. $replication->add($slave2);
  33. $this->assertSame($master, $replication->getConnectionById('master'));
  34. $this->assertSame($slave1, $replication->getConnectionById('slave1'));
  35. $this->assertSame($slave2, $replication->getConnectionById('slave2'));
  36. $this->assertSame($master, $replication->getMaster());
  37. $this->assertSame(array($slave1, $slave2), $replication->getSlaves());
  38. }
  39. /**
  40. * @group disconnected
  41. */
  42. public function testRemovingConnectionsFromReplication()
  43. {
  44. $master = $this->getMockConnection('tcp://host1?alias=master');
  45. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  46. $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
  47. $replication = new MasterSlaveReplication();
  48. $replication->add($master);
  49. $replication->add($slave1);
  50. $this->assertTrue($replication->remove($slave1));
  51. $this->assertFalse($replication->remove($slave2));
  52. $this->assertSame($master, $replication->getMaster());
  53. $this->assertSame(array(), $replication->getSlaves());
  54. }
  55. /**
  56. * @group disconnected
  57. */
  58. public function testAddingConnectionsToReplicationWithoutAliasesResultsInCustomId()
  59. {
  60. $slave1 = $this->getMockConnection('tcp://host1');
  61. $slave2 = $this->getMockConnection('tcp://host2:6380');
  62. $replication = new MasterSlaveReplication();
  63. $replication->add($slave1);
  64. $replication->add($slave2);
  65. $this->assertSame($slave1, $replication->getConnectionById('slave-host1:6379'));
  66. $this->assertSame($slave2, $replication->getConnectionById('slave-host2:6380'));
  67. }
  68. /**
  69. * @group disconnected
  70. * @expectedException \Predis\ClientException
  71. * @expectedExceptionMessage No available connection for replication
  72. */
  73. public function testThrowsExceptionOnEmptyReplication()
  74. {
  75. $replication = new MasterSlaveReplication();
  76. $replication->connect();
  77. }
  78. /**
  79. * @group disconnected
  80. */
  81. public function testConnectsToOneOfSlaves()
  82. {
  83. $master = $this->getMockConnection('tcp://host1?alias=master');
  84. $master->expects($this->never())->method('connect');
  85. $slave = $this->getMockConnection('tcp://host2?alias=slave1');
  86. $slave->expects($this->once())->method('connect');
  87. $replication = new MasterSlaveReplication();
  88. $replication->add($master);
  89. $replication->add($slave);
  90. $replication->connect();
  91. }
  92. /**
  93. * @group disconnected
  94. */
  95. public function testConnectsToMasterOnMissingSlaves()
  96. {
  97. $master = $this->getMockConnection('tcp://host1?alias=master');
  98. $replication = new MasterSlaveReplication();
  99. $replication->add($master);
  100. $replication->connect();
  101. $this->assertSame($master, $replication->getCurrent());
  102. }
  103. /**
  104. * @group disconnected
  105. */
  106. public function testIsConnectedReturnsTrueIfAtLeastOneConnectionIsOpen()
  107. {
  108. $master = $this->getMockConnection('tcp://host1?alias=master');
  109. $master->expects($this->never())->method('isConnected')->will($this->returnValue(false));
  110. $slave = $this->getMockConnection('tcp://host2?alias=slave1');
  111. $slave->expects($this->once())->method('isConnected')->will($this->returnValue(true));
  112. $replication = new MasterSlaveReplication();
  113. $replication->add($master);
  114. $replication->add($slave);
  115. $replication->connect();
  116. $this->assertTrue($replication->isConnected());
  117. }
  118. /**
  119. * @group disconnected
  120. */
  121. public function testIsConnectedReturnsFalseIfAllConnectionsAreClosed()
  122. {
  123. $master = $this->getMockConnection('tcp://host1?alias=master');
  124. $master->expects($this->any())->method('isConnected')->will($this->returnValue(false));
  125. $slave = $this->getMockConnection('tcp://host2?alias=slave1');
  126. $slave->expects($this->any())->method('isConnected')->will($this->returnValue(false));
  127. $replication = new MasterSlaveReplication();
  128. $replication->add($master);
  129. $replication->add($slave);
  130. $this->assertFalse($replication->isConnected());
  131. $replication->connect();
  132. $replication->disconnect();
  133. $this->assertFalse($replication->isConnected());
  134. }
  135. /**
  136. * @group disconnected
  137. */
  138. public function testDisconnectForcesCurrentConnectionToDisconnect()
  139. {
  140. $master = $this->getMockConnection('tcp://host1?alias=master');
  141. $master->expects($this->once())->method('disconnect');
  142. $slave = $this->getMockConnection('tcp://host2?alias=slave1');
  143. $slave->expects($this->once())->method('disconnect');
  144. $replication = new MasterSlaveReplication();
  145. $replication->add($master);
  146. $replication->add($slave);
  147. $replication->disconnect();
  148. }
  149. /**
  150. * @group disconnected
  151. */
  152. public function testCanSwitchConnectionByAlias()
  153. {
  154. $master = $this->getMockConnection('tcp://host1?alias=master');
  155. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  156. $replication = new MasterSlaveReplication();
  157. $replication->add($master);
  158. $replication->add($slave1);
  159. $this->assertNull($replication->getCurrent());
  160. $replication->switchTo('master');
  161. $this->assertSame($master, $replication->getCurrent());
  162. $replication->switchTo('slave1');
  163. $this->assertSame($slave1, $replication->getCurrent());
  164. }
  165. /**
  166. * @group disconnected
  167. * @expectedException \InvalidArgumentException
  168. * @expectedExceptionMessage Invalid connection or connection not found.
  169. */
  170. public function testThrowsErrorWhenSwitchingToUnknownConnectionByAlias()
  171. {
  172. $replication = new MasterSlaveReplication();
  173. $replication->add($this->getMockConnection('tcp://host1?alias=master'));
  174. $replication->add($this->getMockConnection('tcp://host2?alias=slave1'));
  175. $replication->switchTo('unknown');
  176. }
  177. /**
  178. * @group disconnected
  179. */
  180. public function testCanSwitchConnectionByInstance()
  181. {
  182. $master = $this->getMockConnection('tcp://host1?alias=master');
  183. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  184. $replication = new MasterSlaveReplication();
  185. $replication->add($master);
  186. $replication->add($slave1);
  187. $this->assertNull($replication->getCurrent());
  188. $replication->switchTo($master);
  189. $this->assertSame($master, $replication->getCurrent());
  190. $replication->switchTo($slave1);
  191. $this->assertSame($slave1, $replication->getCurrent());
  192. }
  193. /**
  194. * @group disconnected
  195. * @expectedException \InvalidArgumentException
  196. * @expectedExceptionMessage Invalid connection or connection not found.
  197. */
  198. public function testThrowsErrorWhenSwitchingToUnknownConnectionByInstance()
  199. {
  200. $replication = new MasterSlaveReplication();
  201. $replication->add($this->getMockConnection('tcp://host1?alias=master'));
  202. $replication->add($this->getMockConnection('tcp://host2?alias=slave1'));
  203. $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
  204. $replication->switchTo($slave2);
  205. }
  206. /**
  207. * @group disconnected
  208. */
  209. public function testCanSwitchToMaster()
  210. {
  211. $master = $this->getMockConnection('tcp://host1?alias=master');
  212. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  213. $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
  214. $replication = new MasterSlaveReplication();
  215. $replication->add($master);
  216. $replication->add($slave1);
  217. $replication->add($slave2);
  218. $this->assertNull($replication->getCurrent());
  219. $replication->switchToMaster();
  220. $this->assertSame($master, $replication->getCurrent());
  221. }
  222. /**
  223. * @group disconnected
  224. * @expectedException \InvalidArgumentException
  225. * @expectedExceptionMessage Invalid connection or connection not found.
  226. */
  227. public function testThrowsErrorOnSwitchToMasterWithNoMasterDefined()
  228. {
  229. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  230. $replication = new MasterSlaveReplication();
  231. $replication->add($slave1);
  232. $replication->switchToMaster();
  233. }
  234. /**
  235. * @group disconnected
  236. * @todo We should find a way to test that the slave is indeed randomly selected.
  237. */
  238. public function testCanSwitchToRandomSlave()
  239. {
  240. $master = $this->getMockConnection('tcp://host1?alias=master');
  241. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  242. $replication = new MasterSlaveReplication();
  243. $replication->add($master);
  244. $replication->add($slave1);
  245. $this->assertNull($replication->getCurrent());
  246. $replication->switchToSlave();
  247. $this->assertSame($slave1, $replication->getCurrent());
  248. }
  249. /**
  250. * @group disconnected
  251. * @expectedException \InvalidArgumentException
  252. * @expectedExceptionMessage Invalid connection or connection not found.
  253. */
  254. public function testThrowsErrorOnSwitchToRandomSlaveWithNoSlavesDefined()
  255. {
  256. $master = $this->getMockConnection('tcp://host1?alias=master');
  257. $replication = new MasterSlaveReplication();
  258. $replication->add($master);
  259. $replication->switchToSlave();
  260. }
  261. /**
  262. * @group disconnected
  263. */
  264. public function testUsesSlavesOnReadOnlyCommands()
  265. {
  266. $profile = Profile\Factory::getDefault();
  267. $master = $this->getMockConnection('tcp://host1?alias=master');
  268. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  269. $replication = new MasterSlaveReplication();
  270. $replication->add($master);
  271. $replication->add($slave1);
  272. $cmd = $profile->createCommand('exists', array('foo'));
  273. $this->assertSame($slave1, $replication->getConnection($cmd));
  274. $cmd = $profile->createCommand('get', array('foo'));
  275. $this->assertSame($slave1, $replication->getConnection($cmd));
  276. }
  277. /**
  278. * @group disconnected
  279. */
  280. public function testUsesMasterOnWriteRequests()
  281. {
  282. $profile = Profile\Factory::getDefault();
  283. $master = $this->getMockConnection('tcp://host1?alias=master');
  284. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  285. $replication = new MasterSlaveReplication();
  286. $replication->add($master);
  287. $replication->add($slave1);
  288. $cmd = $profile->createCommand('set', array('foo', 'bar'));
  289. $this->assertSame($master, $replication->getConnection($cmd));
  290. $cmd = $profile->createCommand('get', array('foo'));
  291. $this->assertSame($master, $replication->getConnection($cmd));
  292. }
  293. /**
  294. * @group disconnected
  295. */
  296. public function testUsesMasterOnReadRequestsWhenNoSlavesAvailable()
  297. {
  298. $profile = Profile\Factory::getDefault();
  299. $master = $this->getMockConnection('tcp://host1?alias=master');
  300. $replication = new MasterSlaveReplication();
  301. $replication->add($master);
  302. $cmd = $profile->createCommand('exists', array('foo'));
  303. $this->assertSame($master, $replication->getConnection($cmd));
  304. $cmd = $profile->createCommand('set', array('foo', 'bar'));
  305. $this->assertSame($master, $replication->getConnection($cmd));
  306. }
  307. /**
  308. * @group disconnected
  309. */
  310. public function testSwitchesFromSlaveToMasterOnWriteRequestss()
  311. {
  312. $profile = Profile\Factory::getDefault();
  313. $master = $this->getMockConnection('tcp://host1?alias=master');
  314. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  315. $replication = new MasterSlaveReplication();
  316. $replication->add($master);
  317. $replication->add($slave1);
  318. $cmd = $profile->createCommand('exists', array('foo'));
  319. $this->assertSame($slave1, $replication->getConnection($cmd));
  320. $cmd = $profile->createCommand('set', array('foo', 'bar'));
  321. $this->assertSame($master, $replication->getConnection($cmd));
  322. $cmd = $profile->createCommand('exists', array('foo'));
  323. $this->assertSame($master, $replication->getConnection($cmd));
  324. }
  325. /**
  326. * @group disconnected
  327. */
  328. public function testWritesCommandToCorrectConnection()
  329. {
  330. $profile = Profile\Factory::getDefault();
  331. $cmdExists = $profile->createCommand('exists', array('foo'));
  332. $cmdSet = $profile->createCommand('set', array('foo', 'bar'));
  333. $master = $this->getMockConnection('tcp://host1?alias=master');
  334. $master->expects($this->once())->method('writeRequest')->with($cmdSet);
  335. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  336. $slave1->expects($this->once())->method('writeRequest')->with($cmdExists);
  337. $replication = new MasterSlaveReplication();
  338. $replication->add($master);
  339. $replication->add($slave1);
  340. $replication->writeRequest($cmdExists);
  341. $replication->writeRequest($cmdSet);
  342. }
  343. /**
  344. * @group disconnected
  345. */
  346. public function testReadsCommandFromCorrectConnection()
  347. {
  348. $profile = Profile\Factory::getDefault();
  349. $cmdExists = $profile->createCommand('exists', array('foo'));
  350. $cmdSet = $profile->createCommand('set', array('foo', 'bar'));
  351. $master = $this->getMockConnection('tcp://host1?alias=master');
  352. $master->expects($this->once())->method('readResponse')->with($cmdSet);
  353. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  354. $slave1->expects($this->once())->method('readResponse')->with($cmdExists);
  355. $replication = new MasterSlaveReplication();
  356. $replication->add($master);
  357. $replication->add($slave1);
  358. $replication->readResponse($cmdExists);
  359. $replication->readResponse($cmdSet);
  360. }
  361. /**
  362. * @group disconnected
  363. */
  364. public function testExecutesCommandOnCorrectConnection()
  365. {
  366. $profile = Profile\Factory::getDefault();
  367. $cmdExists = $profile->createCommand('exists', array('foo'));
  368. $cmdSet = $profile->createCommand('set', array('foo', 'bar'));
  369. $master = $this->getMockConnection('tcp://host1?alias=master');
  370. $master->expects($this->once())->method('executeCommand')->with($cmdSet);
  371. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  372. $slave1->expects($this->once())->method('executeCommand')->with($cmdExists);
  373. $replication = new MasterSlaveReplication();
  374. $replication->add($master);
  375. $replication->add($slave1);
  376. $replication->executeCommand($cmdExists);
  377. $replication->executeCommand($cmdSet);
  378. }
  379. /**
  380. * @group disconnected
  381. */
  382. public function testWatchTriggersSwitchToMasterConnection()
  383. {
  384. $profile = Profile\Factory::getDefault();
  385. $cmdWatch = $profile->createCommand('watch', array('foo'));
  386. $master = $this->getMockConnection('tcp://host1?alias=master');
  387. $master->expects($this->once())->method('executeCommand')->with($cmdWatch);
  388. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  389. $slave1->expects($this->never())->method('executeCommand');
  390. $replication = new MasterSlaveReplication();
  391. $replication->add($master);
  392. $replication->add($slave1);
  393. $replication->executeCommand($cmdWatch);
  394. }
  395. /**
  396. * @group disconnected
  397. */
  398. public function testMultiTriggersSwitchToMasterConnection()
  399. {
  400. $profile = Profile\Factory::getDefault();
  401. $cmdMulti = $profile->createCommand('multi');
  402. $master = $this->getMockConnection('tcp://host1?alias=master');
  403. $master->expects($this->once())->method('executeCommand')->with($cmdMulti);
  404. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  405. $slave1->expects($this->never())->method('executeCommand');
  406. $replication = new MasterSlaveReplication();
  407. $replication->add($master);
  408. $replication->add($slave1);
  409. $replication->executeCommand($cmdMulti);
  410. }
  411. /**
  412. * @group disconnected
  413. */
  414. public function testEvalTriggersSwitchToMasterConnection()
  415. {
  416. $profile = Profile\Factory::get('dev');
  417. $cmdEval = $profile->createCommand('eval', array("return redis.call('info')"));
  418. $master = $this->getMockConnection('tcp://host1?alias=master');
  419. $master->expects($this->once())->method('executeCommand')->with($cmdEval);
  420. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  421. $slave1->expects($this->never())->method('executeCommand');
  422. $replication = new MasterSlaveReplication();
  423. $replication->add($master);
  424. $replication->add($slave1);
  425. $replication->executeCommand($cmdEval);
  426. }
  427. /**
  428. * @group disconnected
  429. */
  430. public function testSortTriggersSwitchToMasterConnectionOnStoreModifier()
  431. {
  432. $profile = Profile\Factory::get('dev');
  433. $cmdSortNormal = $profile->createCommand('sort', array('key'));
  434. $cmdSortStore = $profile->createCommand('sort', array('key', array('store' => 'key:store')));
  435. $master = $this->getMockConnection('tcp://host1?alias=master');
  436. $master->expects($this->once())->method('executeCommand')->with($cmdSortStore);
  437. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  438. $slave1->expects($this->once())->method('executeCommand')->with($cmdSortNormal);
  439. $replication = new MasterSlaveReplication();
  440. $replication->add($master);
  441. $replication->add($slave1);
  442. $replication->executeCommand($cmdSortNormal);
  443. $replication->executeCommand($cmdSortStore);
  444. }
  445. /**
  446. * @group disconnected
  447. */
  448. public function testDiscardsUnreachableSlaveAndExecutesReadOnlyCommandOnNextSlave()
  449. {
  450. $profile = Profile\Factory::getDefault();
  451. $cmdExists = $profile->createCommand('exists', array('key'));
  452. $master = $this->getMockConnection('tcp://host1?alias=master');
  453. $master->expects($this->never())->method('executeCommand');
  454. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  455. $slave1->expects($this->once())
  456. ->method('executeCommand')
  457. ->with($cmdExists)
  458. ->will($this->throwException(new Connection\ConnectionException($slave1)));
  459. $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
  460. $slave2->expects($this->once())
  461. ->method('executeCommand')
  462. ->with($cmdExists)
  463. ->will($this->returnValue(1));
  464. $replication = new MasterSlaveReplication();
  465. $replication->add($master);
  466. $replication->add($slave1);
  467. $replication->add($slave2);
  468. $replication->switchTo($slave1);
  469. $response = $replication->executeCommand($cmdExists);
  470. $this->assertSame(1, $response);
  471. $this->assertNull($replication->getConnectionById('slave1'));
  472. $this->assertSame($slave2, $replication->getConnectionById('slave2'));
  473. }
  474. /**
  475. * @group disconnected
  476. */
  477. public function testDiscardsUnreachableSlavesAndExecutesReadOnlyCommandOnMaster()
  478. {
  479. $profile = Profile\Factory::getDefault();
  480. $cmdExists = $profile->createCommand('exists', array('key'));
  481. $master = $this->getMockConnection('tcp://host1?alias=master');
  482. $master->expects($this->once())
  483. ->method('executeCommand')
  484. ->with($cmdExists)
  485. ->will($this->returnValue(1));
  486. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  487. $slave1->expects($this->once())
  488. ->method('executeCommand')
  489. ->with($cmdExists)
  490. ->will($this->throwException(new Connection\ConnectionException($slave1)));
  491. $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
  492. $slave2->expects($this->once())
  493. ->method('executeCommand')
  494. ->with($cmdExists)
  495. ->will($this->throwException(new Connection\ConnectionException($slave2)));
  496. $replication = new MasterSlaveReplication();
  497. $replication->add($master);
  498. $replication->add($slave1);
  499. $replication->add($slave2);
  500. $replication->switchTo($slave1);
  501. $response = $replication->executeCommand($cmdExists);
  502. $this->assertSame(1, $response);
  503. $this->assertNull($replication->getConnectionById('slave1'));
  504. $this->assertNull($replication->getConnectionById('slave2'));
  505. }
  506. /**
  507. * @group disconnected
  508. */
  509. public function testSucceedOnReadOnlyCommandAndNoConnectionSetAsMaster()
  510. {
  511. $profile = Profile\Factory::getDefault();
  512. $cmdExists = $profile->createCommand('exists', array('key'));
  513. $slave1 = $this->getMockConnection('tcp://host1?alias=slave1');
  514. $slave1->expects($this->once())
  515. ->method('executeCommand')
  516. ->with($cmdExists)
  517. ->will($this->returnValue(1));
  518. $replication = new MasterSlaveReplication();
  519. $replication->add($slave1);
  520. $response = $replication->executeCommand($cmdExists);
  521. $this->assertSame(1, $response);
  522. }
  523. /**
  524. * @group disconnected
  525. * @expectedException \Predis\Replication\MissingMasterException
  526. * @expectedMessage No master server available for replication
  527. */
  528. public function testFailsOnWriteCommandAndNoConnectionSetAsMaster()
  529. {
  530. $profile = Profile\Factory::getDefault();
  531. $cmdSet = $profile->createCommand('set', array('key', 'value'));
  532. $slave1 = $this->getMockConnection('tcp://host1?alias=slave1');
  533. $slave1->expects($this->never())->method('executeCommand');
  534. $replication = new MasterSlaveReplication();
  535. $replication->add($slave1);
  536. $replication->executeCommand($cmdSet);
  537. }
  538. /**
  539. * @group disconnected
  540. * @expectedException \Predis\Connection\ConnectionException
  541. */
  542. public function testFailsOnUnreachableMaster()
  543. {
  544. $profile = Profile\Factory::getDefault();
  545. $cmdSet = $profile->createCommand('set', array('key', 'value'));
  546. $master = $this->getMockConnection('tcp://host1?alias=master');
  547. $master->expects($this->once())
  548. ->method('executeCommand')
  549. ->with($cmdSet)
  550. ->will($this->throwException(new Connection\ConnectionException($master)));
  551. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  552. $slave1->expects($this->never())
  553. ->method('executeCommand');
  554. $replication = new MasterSlaveReplication();
  555. $replication->add($master);
  556. $replication->add($slave1);
  557. $replication->executeCommand($cmdSet);
  558. }
  559. /**
  560. * @group disconnected
  561. * @expectedException \Predis\NotSupportedException
  562. * @expectedExceptionMessage The command 'INFO' is not allowed in replication mode.
  563. */
  564. public function testThrowsExceptionOnNonSupportedCommand()
  565. {
  566. $cmd = Profile\Factory::getDefault()->createCommand('info');
  567. $replication = new MasterSlaveReplication();
  568. $replication->add($this->getMockConnection('tcp://host1?alias=master'));
  569. $replication->add($this->getMockConnection('tcp://host2?alias=slave1'));
  570. $replication->getConnection($cmd);
  571. }
  572. /**
  573. * @group disconnected
  574. */
  575. public function testCanOverrideReadOnlyFlagForCommands()
  576. {
  577. $profile = Profile\Factory::getDefault();
  578. $cmdSet = $profile->createCommand('set', array('foo', 'bar'));
  579. $cmdGet = $profile->createCommand('get', array('foo'));
  580. $master = $this->getMockConnection('tcp://host1?alias=master');
  581. $master->expects($this->once())->method('executeCommand')->with($cmdGet);
  582. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  583. $slave1->expects($this->once())->method('executeCommand')->with($cmdSet);
  584. $replication = new MasterSlaveReplication();
  585. $replication->add($master);
  586. $replication->add($slave1);
  587. $replication->getReplicationStrategy()->setCommandReadOnly($cmdSet->getId(), true);
  588. $replication->getReplicationStrategy()->setCommandReadOnly($cmdGet->getId(), false);
  589. $replication->executeCommand($cmdSet);
  590. $replication->executeCommand($cmdGet);
  591. }
  592. /**
  593. * @group disconnected
  594. */
  595. public function testAcceptsCallableToOverrideReadOnlyFlagForCommands()
  596. {
  597. $profile = Profile\Factory::getDefault();
  598. $cmdExistsFoo = $profile->createCommand('exists', array('foo'));
  599. $cmdExistsBar = $profile->createCommand('exists', array('bar'));
  600. $master = $this->getMockConnection('tcp://host1?alias=master');
  601. $master->expects($this->once())->method('executeCommand')->with($cmdExistsBar);
  602. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  603. $slave1->expects($this->once())->method('executeCommand')->with($cmdExistsFoo);
  604. $replication = new MasterSlaveReplication();
  605. $replication->add($master);
  606. $replication->add($slave1);
  607. $replication->getReplicationStrategy()->setCommandReadOnly('exists', function ($cmd) {
  608. list($arg1) = $cmd->getArguments();
  609. return $arg1 === 'foo';
  610. });
  611. $replication->executeCommand($cmdExistsFoo);
  612. $replication->executeCommand($cmdExistsBar);
  613. }
  614. /**
  615. * @group disconnected
  616. */
  617. public function testCanSetReadOnlyFlagForEvalScripts()
  618. {
  619. $profile = Profile\Factory::get('dev');
  620. $cmdEval = $profile->createCommand('eval', array($script = "return redis.call('info');"));
  621. $cmdEvalSha = $profile->createCommand('evalsha', array($scriptSHA1 = sha1($script)));
  622. $master = $this->getMockConnection('tcp://host1?alias=master');
  623. $master->expects($this->never())->method('executeCommand');
  624. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  625. $slave1->expects($this->exactly(2))
  626. ->method('executeCommand')
  627. ->with($this->logicalOr($cmdEval, $cmdEvalSha));
  628. $replication = new MasterSlaveReplication();
  629. $replication->add($master);
  630. $replication->add($slave1);
  631. $replication->getReplicationStrategy()->setScriptReadOnly($script);
  632. $replication->executeCommand($cmdEval);
  633. $replication->executeCommand($cmdEvalSha);
  634. }
  635. /**
  636. * @group disconnected
  637. * @expectedException \Predis\ClientException
  638. * @expectedMessage Discovery requires a connection factory
  639. */
  640. public function testDiscoveryRequiresConnectionFactory()
  641. {
  642. $master = $this->getMockConnection('tcp://host1?alias=master');
  643. $replication = new MasterSlaveReplication();
  644. $replication->add($master);
  645. $replication->discover();
  646. }
  647. /**
  648. * @group disconnected
  649. */
  650. public function testDiscoversReplicationConfigurationFromMaster()
  651. {
  652. $connFactory = new Connection\Factory();
  653. $cmdInfo = Command\RawCommand::create('INFO', 'REPLICATION');
  654. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  655. $master->expects($this->once())
  656. ->method('executeCommand')
  657. ->with($cmdInfo)
  658. ->will($this->returnValue("
  659. # Replication
  660. role:master
  661. connected_slaves:2
  662. slave0:ip=127.0.0.1,port=6382,state=online,offset=12979,lag=0
  663. slave1:ip=127.0.0.1,port=6383,state=online,offset=12979,lag=1
  664. master_repl_offset:12979
  665. repl_backlog_active:1
  666. repl_backlog_size:1048576
  667. repl_backlog_first_byte_offset:2
  668. repl_backlog_histlen:12978
  669. "));
  670. $replication = new MasterSlaveReplication();
  671. $replication->setConnectionFactory($connFactory);
  672. $replication->add($master);
  673. $replication->discover();
  674. $this->assertCount(2, $slaves = $replication->getSlaves());
  675. $this->assertContainsOnlyInstancesOf('Predis\Connection\ConnectionInterface', $slaves);
  676. $this->assertSame('127.0.0.1:6381', (string) $replication->getMaster());
  677. $this->assertSame('127.0.0.1:6382', (string) $slaves[0]);
  678. $this->assertSame('127.0.0.1:6383', (string) $slaves[1]);
  679. }
  680. /**
  681. * @group disconnected
  682. */
  683. public function testDiscoversReplicationConfigurationFromSlave()
  684. {
  685. $cmdInfo = $command = Command\RawCommand::create('INFO', 'REPLICATION');
  686. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  687. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  688. $slave2 = $this->getMockConnection('tcp://127.0.0.1:6383?alias=slave2');
  689. $connFactory = $this->getMock('Predis\Connection\Factory');
  690. $connFactory->expects($this->at(0))
  691. ->method('create')
  692. ->with(array('host' => '127.0.0.1', 'port' => '6381', 'alias' => 'master'))
  693. ->will($this->returnValue($master));
  694. $connFactory->expects($this->at(1))
  695. ->method('create')
  696. ->with(array('host' => '127.0.0.1', 'port' => '6382'))
  697. ->will($this->returnValue($slave1));
  698. $connFactory->expects($this->at(2))
  699. ->method('create')
  700. ->with(array('host' => '127.0.0.1', 'port' => '6383'))
  701. ->will($this->returnValue($slave2));
  702. $slave1->expects($this->once())
  703. ->method('executeCommand')
  704. ->with($cmdInfo)
  705. ->will($this->returnValue("
  706. # Replication
  707. role:slave
  708. master_host:127.0.0.1
  709. master_port:6381
  710. master_link_status:up
  711. master_last_io_seconds_ago:8
  712. master_sync_in_progress:0
  713. slave_repl_offset:17715532
  714. slave_priority:100
  715. slave_read_only:1
  716. connected_slaves:0
  717. master_repl_offset:0
  718. repl_backlog_active:0
  719. repl_backlog_size:1048576
  720. repl_backlog_first_byte_offset:0
  721. repl_backlog_histlen:0
  722. "));
  723. $master->expects($this->once())
  724. ->method('executeCommand')
  725. ->with($cmdInfo)
  726. ->will($this->returnValue("
  727. # Replication
  728. role:master
  729. connected_slaves:2
  730. slave0:ip=127.0.0.1,port=6382,state=online,offset=12979,lag=0
  731. slave1:ip=127.0.0.1,port=6383,state=online,offset=12979,lag=1
  732. master_repl_offset:12979
  733. repl_backlog_active:1
  734. repl_backlog_size:1048576
  735. repl_backlog_first_byte_offset:2
  736. repl_backlog_histlen:12978
  737. "));
  738. $replication = new MasterSlaveReplication();
  739. $replication->setConnectionFactory($connFactory);
  740. $replication->add($slave1);
  741. $replication->discover();
  742. $this->assertCount(2, $slaves = $replication->getSlaves());
  743. $this->assertContainsOnlyInstancesOf('Predis\Connection\ConnectionInterface', $slaves);
  744. $this->assertSame('127.0.0.1:6381', (string) $replication->getMaster());
  745. $this->assertSame('127.0.0.1:6382', (string) $slaves[0]);
  746. $this->assertSame('127.0.0.1:6383', (string) $slaves[1]);
  747. }
  748. /**
  749. * @group disconnected
  750. */
  751. public function testDiscoversReplicationConfigurationFromSlaveIfMasterFails()
  752. {
  753. $cmdInfo = $command = Command\RawCommand::create('INFO', 'REPLICATION');
  754. $masterKO = $this->getMockConnection('tcp://127.0.0.1:7381?alias=master');
  755. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  756. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  757. $slave2 = $this->getMockConnection('tcp://127.0.0.1:6383?alias=slave2');
  758. $connFactory = $this->getMock('Predis\Connection\Factory');
  759. $connFactory->expects($this->at(0))
  760. ->method('create')
  761. ->with(array('host' => '127.0.0.1', 'port' => '6381', 'alias' => 'master'))
  762. ->will($this->returnValue($master));
  763. $connFactory->expects($this->at(1))
  764. ->method('create')
  765. ->with(array('host' => '127.0.0.1', 'port' => '6382'))
  766. ->will($this->returnValue($slave1));
  767. $connFactory->expects($this->at(2))
  768. ->method('create')
  769. ->with(array('host' => '127.0.0.1', 'port' => '6383'))
  770. ->will($this->returnValue($slave2));
  771. $masterKO->expects($this->once())
  772. ->method('executeCommand')
  773. ->with($cmdInfo)
  774. ->will($this->throwException(new Connection\ConnectionException($masterKO)));
  775. $slave1->expects($this->once())
  776. ->method('executeCommand')
  777. ->with($cmdInfo)
  778. ->will($this->returnValue("
  779. # Replication
  780. role:slave
  781. master_host:127.0.0.1
  782. master_port:6381
  783. master_link_status:up
  784. master_last_io_seconds_ago:8
  785. master_sync_in_progress:0
  786. slave_repl_offset:17715532
  787. slave_priority:100
  788. slave_read_only:1
  789. connected_slaves:0
  790. master_repl_offset:0
  791. repl_backlog_active:0
  792. repl_backlog_size:1048576
  793. repl_backlog_first_byte_offset:0
  794. repl_backlog_histlen:0
  795. "));
  796. $master->expects($this->once())
  797. ->method('executeCommand')
  798. ->with($cmdInfo)
  799. ->will($this->returnValue("
  800. # Replication
  801. role:master
  802. connected_slaves:2
  803. slave0:ip=127.0.0.1,port=6382,state=online,offset=12979,lag=0
  804. slave1:ip=127.0.0.1,port=6383,state=online,offset=12979,lag=1
  805. master_repl_offset:12979
  806. repl_backlog_active:1
  807. repl_backlog_size:1048576
  808. repl_backlog_first_byte_offset:2
  809. repl_backlog_histlen:12978
  810. "));
  811. $replication = new MasterSlaveReplication();
  812. $replication->setConnectionFactory($connFactory);
  813. $replication->add($masterKO);
  814. $replication->add($slave1);
  815. $replication->discover();
  816. $this->assertCount(2, $slaves = $replication->getSlaves());
  817. $this->assertContainsOnlyInstancesOf('Predis\Connection\ConnectionInterface', $slaves);
  818. $this->assertSame('127.0.0.1:6381', (string) $replication->getMaster());
  819. $this->assertSame('127.0.0.1:6382', (string) $slaves[0]);
  820. $this->assertSame('127.0.0.1:6383', (string) $slaves[1]);
  821. }
  822. /**
  823. * @group disconnected
  824. * @expectedException \Predis\ClientException
  825. * @expectedMessage Automatic discovery requires a connection factory
  826. */
  827. public function testAutomaticDiscoveryRequiresConnectionFactory()
  828. {
  829. $master = $this->getMockConnection('tcp://host1?alias=master');
  830. $replication = new MasterSlaveReplication();
  831. $replication->add($master);
  832. $replication->setAutoDiscovery(true);
  833. }
  834. /**
  835. * @group disconnected
  836. */
  837. public function testAutomaticDiscoveryOnUnreachableServer()
  838. {
  839. $cmdInfo = $command = Command\RawCommand::create('INFO', 'REPLICATION');
  840. $cmdExists = $command = Command\RawCommand::create('EXISTS', 'key');
  841. $slaveKO = $this->getMockConnection('tcp://127.0.0.1:7382?alias=slaveKO');
  842. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  843. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  844. $connFactory = $this->getMock('Predis\Connection\Factory');
  845. $connFactory->expects($this->once())
  846. ->method('create')
  847. ->with(array('host' => '127.0.0.1', 'port' => '6382'))
  848. ->will($this->returnValue($slave1));
  849. $slaveKO->expects($this->once())
  850. ->method('executeCommand')
  851. ->with($cmdExists)
  852. ->will($this->throwException(new Connection\ConnectionException($slaveKO)));
  853. $slave1->expects($this->once())
  854. ->method('executeCommand')
  855. ->with($cmdExists)
  856. ->will($this->returnValue(1));
  857. $master->expects($this->once())
  858. ->method('executeCommand')
  859. ->with($cmdInfo)
  860. ->will($this->returnValue("
  861. # Replication
  862. role:master
  863. connected_slaves:2
  864. slave0:ip=127.0.0.1,port=6382,state=online,offset=12979,lag=0
  865. master_repl_offset:12979
  866. repl_backlog_active:1
  867. repl_backlog_size:1048576
  868. repl_backlog_first_byte_offset:2
  869. repl_backlog_histlen:12978
  870. "));
  871. $replication = new MasterSlaveReplication();
  872. $replication->setConnectionFactory($connFactory);
  873. $replication->setAutoDiscovery(true);
  874. $replication->add($master);
  875. $replication->add($slaveKO);
  876. $replication->executeCommand($cmdExists);
  877. }
  878. /**
  879. * @group disconnected
  880. */
  881. public function testExposesReplicationStrategy()
  882. {
  883. $replication = new MasterSlaveReplication();
  884. $this->assertInstanceOf('Predis\Replication\ReplicationStrategy', $replication->getReplicationStrategy());
  885. $strategy = new ReplicationStrategy();
  886. $replication = new MasterSlaveReplication($strategy);
  887. $this->assertSame($strategy, $replication->getReplicationStrategy());
  888. }
  889. /**
  890. * @group disconnected
  891. */
  892. public function testCanBeSerialized()
  893. {
  894. $master = $this->getMockConnection('tcp://host1?alias=master');
  895. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  896. $replication = new MasterSlaveReplication();
  897. $replication->add($master);
  898. $replication->add($slave1);
  899. $unserialized = unserialize(serialize($replication));
  900. $this->assertEquals($master, $unserialized->getConnectionById('master'));
  901. $this->assertEquals($slave1, $unserialized->getConnectionById('slave1'));
  902. }
  903. // ******************************************************************** //
  904. // ---- HELPER METHODS ------------------------------------------------ //
  905. // ******************************************************************** //
  906. /**
  907. * Returns a base mocked connection from Predis\Connection\NodeConnectionInterface.
  908. *
  909. * @param mixed $parameters Optional parameters.
  910. *
  911. * @return mixed
  912. */
  913. protected function getMockConnection($parameters = null)
  914. {
  915. $connection = $this->getMock('Predis\Connection\NodeConnectionInterface');
  916. if ($parameters) {
  917. $parameters = Connection\Parameters::create($parameters);
  918. $hash = "{$parameters->host}:{$parameters->port}";
  919. $connection->expects($this->any())
  920. ->method('getParameters')
  921. ->will($this->returnValue($parameters));
  922. $connection->expects($this->any())
  923. ->method('__toString')
  924. ->will($this->returnValue($hash));
  925. }
  926. return $connection;
  927. }
  928. }