MasterSlaveReplicationTest.php 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347
  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\ReplicationStrategy;
  14. use Predis\Response;
  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
  85. ->expects($this->never())
  86. ->method('connect');
  87. $slave = $this->getMockConnection('tcp://host2?alias=slave1');
  88. $slave
  89. ->expects($this->once())
  90. ->method('connect');
  91. $replication = new MasterSlaveReplication();
  92. $replication->add($master);
  93. $replication->add($slave);
  94. $replication->connect();
  95. }
  96. /**
  97. * @group disconnected
  98. */
  99. public function testConnectsToMasterOnMissingSlaves()
  100. {
  101. $master = $this->getMockConnection('tcp://host1?alias=master');
  102. $replication = new MasterSlaveReplication();
  103. $replication->add($master);
  104. $replication->connect();
  105. $this->assertSame($master, $replication->getCurrent());
  106. }
  107. /**
  108. * @group disconnected
  109. */
  110. public function testIsConnectedReturnsTrueIfAtLeastOneConnectionIsOpen()
  111. {
  112. $master = $this->getMockConnection('tcp://host1?alias=master');
  113. $master
  114. ->expects($this->never())
  115. ->method('isConnected')
  116. ->will($this->returnValue(false));
  117. $slave = $this->getMockConnection('tcp://host2?alias=slave1');
  118. $slave
  119. ->expects($this->once())
  120. ->method('isConnected')
  121. ->will($this->returnValue(true));
  122. $replication = new MasterSlaveReplication();
  123. $replication->add($master);
  124. $replication->add($slave);
  125. $replication->connect();
  126. $this->assertTrue($replication->isConnected());
  127. }
  128. /**
  129. * @group disconnected
  130. */
  131. public function testIsConnectedReturnsFalseIfAllConnectionsAreClosed()
  132. {
  133. $master = $this->getMockConnection('tcp://host1?alias=master');
  134. $master
  135. ->expects($this->any())
  136. ->method('isConnected')
  137. ->will($this->returnValue(false));
  138. $slave = $this->getMockConnection('tcp://host2?alias=slave1');
  139. $slave
  140. ->expects($this->any())
  141. ->method('isConnected')
  142. ->will($this->returnValue(false));
  143. $replication = new MasterSlaveReplication();
  144. $replication->add($master);
  145. $replication->add($slave);
  146. $this->assertFalse($replication->isConnected());
  147. $replication->connect();
  148. $replication->disconnect();
  149. $this->assertFalse($replication->isConnected());
  150. }
  151. /**
  152. * @group disconnected
  153. */
  154. public function testDisconnectForcesCurrentConnectionToDisconnect()
  155. {
  156. $master = $this->getMockConnection('tcp://host1?alias=master');
  157. $master
  158. ->expects($this->once())
  159. ->method('disconnect');
  160. $slave = $this->getMockConnection('tcp://host2?alias=slave1');
  161. $slave
  162. ->expects($this->once())
  163. ->method('disconnect');
  164. $replication = new MasterSlaveReplication();
  165. $replication->add($master);
  166. $replication->add($slave);
  167. $replication->disconnect();
  168. }
  169. /**
  170. * @group disconnected
  171. */
  172. public function testCanSwitchConnectionByAlias()
  173. {
  174. $master = $this->getMockConnection('tcp://host1?alias=master');
  175. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  176. $replication = new MasterSlaveReplication();
  177. $replication->add($master);
  178. $replication->add($slave1);
  179. $this->assertNull($replication->getCurrent());
  180. $replication->switchTo('master');
  181. $this->assertSame($master, $replication->getCurrent());
  182. $replication->switchTo('slave1');
  183. $this->assertSame($slave1, $replication->getCurrent());
  184. }
  185. /**
  186. * @group disconnected
  187. * @expectedException \InvalidArgumentException
  188. * @expectedExceptionMessage Invalid connection or connection not found.
  189. */
  190. public function testThrowsErrorWhenSwitchingToUnknownConnectionByAlias()
  191. {
  192. $replication = new MasterSlaveReplication();
  193. $replication->add($this->getMockConnection('tcp://host1?alias=master'));
  194. $replication->add($this->getMockConnection('tcp://host2?alias=slave1'));
  195. $replication->switchTo('unknown');
  196. }
  197. /**
  198. * @group disconnected
  199. */
  200. public function testCanSwitchConnectionByInstance()
  201. {
  202. $master = $this->getMockConnection('tcp://host1?alias=master');
  203. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  204. $replication = new MasterSlaveReplication();
  205. $replication->add($master);
  206. $replication->add($slave1);
  207. $this->assertNull($replication->getCurrent());
  208. $replication->switchTo($master);
  209. $this->assertSame($master, $replication->getCurrent());
  210. $replication->switchTo($slave1);
  211. $this->assertSame($slave1, $replication->getCurrent());
  212. }
  213. /**
  214. * @group disconnected
  215. * @expectedException \InvalidArgumentException
  216. * @expectedExceptionMessage Invalid connection or connection not found.
  217. */
  218. public function testThrowsErrorWhenSwitchingToUnknownConnectionByInstance()
  219. {
  220. $replication = new MasterSlaveReplication();
  221. $replication->add($this->getMockConnection('tcp://host1?alias=master'));
  222. $replication->add($this->getMockConnection('tcp://host2?alias=slave1'));
  223. $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
  224. $replication->switchTo($slave2);
  225. }
  226. /**
  227. * @group disconnected
  228. */
  229. public function testCanSwitchToMaster()
  230. {
  231. $master = $this->getMockConnection('tcp://host1?alias=master');
  232. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  233. $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
  234. $replication = new MasterSlaveReplication();
  235. $replication->add($master);
  236. $replication->add($slave1);
  237. $replication->add($slave2);
  238. $this->assertNull($replication->getCurrent());
  239. $replication->switchToMaster();
  240. $this->assertSame($master, $replication->getCurrent());
  241. }
  242. /**
  243. * @group disconnected
  244. * @expectedException \InvalidArgumentException
  245. * @expectedExceptionMessage Invalid connection or connection not found.
  246. */
  247. public function testThrowsErrorOnSwitchToMasterWithNoMasterDefined()
  248. {
  249. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  250. $replication = new MasterSlaveReplication();
  251. $replication->add($slave1);
  252. $replication->switchToMaster();
  253. }
  254. /**
  255. * @group disconnected
  256. *
  257. * @todo We should find a way to test that the slave is indeed randomly selected.
  258. */
  259. public function testCanSwitchToRandomSlave()
  260. {
  261. $master = $this->getMockConnection('tcp://host1?alias=master');
  262. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  263. $replication = new MasterSlaveReplication();
  264. $replication->add($master);
  265. $replication->add($slave1);
  266. $this->assertNull($replication->getCurrent());
  267. $replication->switchToSlave();
  268. $this->assertSame($slave1, $replication->getCurrent());
  269. }
  270. /**
  271. * @group disconnected
  272. * @expectedException \InvalidArgumentException
  273. * @expectedExceptionMessage Invalid connection or connection not found.
  274. */
  275. public function testThrowsErrorOnSwitchToRandomSlaveWithNoSlavesDefined()
  276. {
  277. $master = $this->getMockConnection('tcp://host1?alias=master');
  278. $replication = new MasterSlaveReplication();
  279. $replication->add($master);
  280. $replication->switchToSlave();
  281. }
  282. /**
  283. * @group disconnected
  284. */
  285. public function testUsesSlavesOnReadOnlyCommands()
  286. {
  287. $commands = $this->getCommandFactory();
  288. $master = $this->getMockConnection('tcp://host1?alias=master');
  289. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  290. $replication = new MasterSlaveReplication();
  291. $replication->add($master);
  292. $replication->add($slave1);
  293. $cmd = $commands->createCommand('exists', array('foo'));
  294. $this->assertSame($slave1, $replication->getConnection($cmd));
  295. $cmd = $commands->createCommand('get', array('foo'));
  296. $this->assertSame($slave1, $replication->getConnection($cmd));
  297. }
  298. /**
  299. * @group disconnected
  300. */
  301. public function testUsesMasterOnWriteRequests()
  302. {
  303. $commands = $this->getCommandFactory();
  304. $master = $this->getMockConnection('tcp://host1?alias=master');
  305. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  306. $replication = new MasterSlaveReplication();
  307. $replication->add($master);
  308. $replication->add($slave1);
  309. $cmd = $commands->createCommand('set', array('foo', 'bar'));
  310. $this->assertSame($master, $replication->getConnection($cmd));
  311. $cmd = $commands->createCommand('get', array('foo'));
  312. $this->assertSame($master, $replication->getConnection($cmd));
  313. }
  314. /**
  315. * @group disconnected
  316. */
  317. public function testUsesMasterOnReadRequestsWhenNoSlavesAvailable()
  318. {
  319. $commands = $this->getCommandFactory();
  320. $master = $this->getMockConnection('tcp://host1?alias=master');
  321. $replication = new MasterSlaveReplication();
  322. $replication->add($master);
  323. $cmd = $commands->createCommand('exists', array('foo'));
  324. $this->assertSame($master, $replication->getConnection($cmd));
  325. $cmd = $commands->createCommand('set', array('foo', 'bar'));
  326. $this->assertSame($master, $replication->getConnection($cmd));
  327. }
  328. /**
  329. * @group disconnected
  330. */
  331. public function testSwitchesFromSlaveToMasterOnWriteRequestss()
  332. {
  333. $commands = $this->getCommandFactory();
  334. $master = $this->getMockConnection('tcp://host1?alias=master');
  335. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  336. $replication = new MasterSlaveReplication();
  337. $replication->add($master);
  338. $replication->add($slave1);
  339. $cmd = $commands->createCommand('exists', array('foo'));
  340. $this->assertSame($slave1, $replication->getConnection($cmd));
  341. $cmd = $commands->createCommand('set', array('foo', 'bar'));
  342. $this->assertSame($master, $replication->getConnection($cmd));
  343. $cmd = $commands->createCommand('exists', array('foo'));
  344. $this->assertSame($master, $replication->getConnection($cmd));
  345. }
  346. /**
  347. * @group disconnected
  348. */
  349. public function testWritesCommandToCorrectConnection()
  350. {
  351. $commands = $this->getCommandFactory();
  352. $cmdExists = $commands->createCommand('exists', array('foo'));
  353. $cmdSet = $commands->createCommand('set', array('foo', 'bar'));
  354. $master = $this->getMockConnection('tcp://host1?alias=master');
  355. $master
  356. ->expects($this->once())
  357. ->method('writeRequest')
  358. ->with($cmdSet);
  359. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  360. $slave1
  361. ->expects($this->once())
  362. ->method('writeRequest')
  363. ->with($cmdExists);
  364. $replication = new MasterSlaveReplication();
  365. $replication->add($master);
  366. $replication->add($slave1);
  367. $replication->writeRequest($cmdExists);
  368. $replication->writeRequest($cmdSet);
  369. }
  370. /**
  371. * @group disconnected
  372. */
  373. public function testReadsCommandFromCorrectConnection()
  374. {
  375. $commands = $this->getCommandFactory();
  376. $cmdExists = $commands->createCommand('exists', array('foo'));
  377. $cmdSet = $commands->createCommand('set', array('foo', 'bar'));
  378. $master = $this->getMockConnection('tcp://host1?alias=master');
  379. $master
  380. ->expects($this->once())
  381. ->method('readResponse')
  382. ->with($cmdSet);
  383. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  384. $slave1
  385. ->expects($this->once())
  386. ->method('readResponse')
  387. ->with($cmdExists);
  388. $replication = new MasterSlaveReplication();
  389. $replication->add($master);
  390. $replication->add($slave1);
  391. $replication->readResponse($cmdExists);
  392. $replication->readResponse($cmdSet);
  393. }
  394. /**
  395. * @group disconnected
  396. */
  397. public function testExecutesCommandOnCorrectConnection()
  398. {
  399. $commands = $this->getCommandFactory();
  400. $cmdExists = $commands->createCommand('exists', array('foo'));
  401. $cmdSet = $commands->createCommand('set', array('foo', 'bar'));
  402. $master = $this->getMockConnection('tcp://host1?alias=master');
  403. $master
  404. ->expects($this->once())
  405. ->method('executeCommand')
  406. ->with($cmdSet);
  407. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  408. $slave1
  409. ->expects($this->once())
  410. ->method('executeCommand')
  411. ->with($cmdExists);
  412. $replication = new MasterSlaveReplication();
  413. $replication->add($master);
  414. $replication->add($slave1);
  415. $replication->executeCommand($cmdExists);
  416. $replication->executeCommand($cmdSet);
  417. }
  418. /**
  419. * @group disconnected
  420. */
  421. public function testWatchTriggersSwitchToMasterConnection()
  422. {
  423. $commands = $this->getCommandFactory();
  424. $cmdWatch = $commands->createCommand('watch', array('foo'));
  425. $master = $this->getMockConnection('tcp://host1?alias=master');
  426. $master
  427. ->expects($this->once())
  428. ->method('executeCommand')
  429. ->with($cmdWatch);
  430. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  431. $slave1
  432. ->expects($this->never())
  433. ->method('executeCommand');
  434. $replication = new MasterSlaveReplication();
  435. $replication->add($master);
  436. $replication->add($slave1);
  437. $replication->executeCommand($cmdWatch);
  438. }
  439. /**
  440. * @group disconnected
  441. */
  442. public function testMultiTriggersSwitchToMasterConnection()
  443. {
  444. $commands = $this->getCommandFactory();
  445. $cmdMulti = $commands->createCommand('multi');
  446. $master = $this->getMockConnection('tcp://host1?alias=master');
  447. $master
  448. ->expects($this->once())
  449. ->method('executeCommand')
  450. ->with($cmdMulti);
  451. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  452. $slave1
  453. ->expects($this->never())
  454. ->method('executeCommand');
  455. $replication = new MasterSlaveReplication();
  456. $replication->add($master);
  457. $replication->add($slave1);
  458. $replication->executeCommand($cmdMulti);
  459. }
  460. /**
  461. * @group disconnected
  462. */
  463. public function testEvalTriggersSwitchToMasterConnection()
  464. {
  465. $commands = $this->getCommandFactory();
  466. $cmdEval = $commands->createCommand('eval', array("return redis.call('info')"));
  467. $master = $this->getMockConnection('tcp://host1?alias=master');
  468. $master
  469. ->expects($this->once())
  470. ->method('executeCommand')
  471. ->with($cmdEval);
  472. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  473. $slave1
  474. ->expects($this->never())
  475. ->method('executeCommand');
  476. $replication = new MasterSlaveReplication();
  477. $replication->add($master);
  478. $replication->add($slave1);
  479. $replication->executeCommand($cmdEval);
  480. }
  481. /**
  482. * @group disconnected
  483. */
  484. public function testSortTriggersSwitchToMasterConnectionOnStoreModifier()
  485. {
  486. $commands = $this->getCommandFactory();
  487. $cmdSortNormal = $commands->createCommand('sort', array('key'));
  488. $cmdSortStore = $commands->createCommand('sort', array('key', array('store' => 'key:store')));
  489. $master = $this->getMockConnection('tcp://host1?alias=master');
  490. $master
  491. ->expects($this->once())
  492. ->method('executeCommand')
  493. ->with($cmdSortStore);
  494. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  495. $slave1
  496. ->expects($this->once())
  497. ->method('executeCommand')
  498. ->with($cmdSortNormal);
  499. $replication = new MasterSlaveReplication();
  500. $replication->add($master);
  501. $replication->add($slave1);
  502. $replication->executeCommand($cmdSortNormal);
  503. $replication->executeCommand($cmdSortStore);
  504. }
  505. /**
  506. * @group disconnected
  507. */
  508. public function testDiscardsUnreachableSlaveAndExecutesReadOnlyCommandOnNextSlave()
  509. {
  510. $commands = $this->getCommandFactory();
  511. $cmdExists = $commands->createCommand('exists', array('key'));
  512. $master = $this->getMockConnection('tcp://host1?alias=master');
  513. $master
  514. ->expects($this->never())
  515. ->method('executeCommand');
  516. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  517. $slave1
  518. ->expects($this->once())
  519. ->method('executeCommand')
  520. ->with($cmdExists)
  521. ->will($this->throwException(
  522. new Connection\ConnectionException($slave1)
  523. ));
  524. $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
  525. $slave2
  526. ->expects($this->once())
  527. ->method('executeCommand')
  528. ->with($cmdExists)
  529. ->will($this->returnValue(1));
  530. $replication = new MasterSlaveReplication();
  531. $replication->add($master);
  532. $replication->add($slave1);
  533. $replication->add($slave2);
  534. $replication->switchTo($slave1);
  535. $response = $replication->executeCommand($cmdExists);
  536. $this->assertSame(1, $response);
  537. $this->assertNull($replication->getConnectionById('slave1'));
  538. $this->assertSame($slave2, $replication->getConnectionById('slave2'));
  539. }
  540. /**
  541. * @group disconnected
  542. */
  543. public function testDiscardsUnreachableSlavesAndExecutesReadOnlyCommandOnMaster()
  544. {
  545. $commands = $this->getCommandFactory();
  546. $cmdExists = $commands->createCommand('exists', array('key'));
  547. $master = $this->getMockConnection('tcp://host1?alias=master');
  548. $master
  549. ->expects($this->once())
  550. ->method('executeCommand')
  551. ->with($cmdExists)
  552. ->will($this->returnValue(1));
  553. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  554. $slave1
  555. ->expects($this->once())
  556. ->method('executeCommand')
  557. ->with($cmdExists)
  558. ->will($this->throwException(new Connection\ConnectionException($slave1)));
  559. $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
  560. $slave2
  561. ->expects($this->once())
  562. ->method('executeCommand')
  563. ->with($cmdExists)
  564. ->will($this->throwException(
  565. new Connection\ConnectionException($slave2)
  566. ));
  567. $replication = new MasterSlaveReplication();
  568. $replication->add($master);
  569. $replication->add($slave1);
  570. $replication->add($slave2);
  571. $replication->switchTo($slave1);
  572. $response = $replication->executeCommand($cmdExists);
  573. $this->assertSame(1, $response);
  574. $this->assertNull($replication->getConnectionById('slave1'));
  575. $this->assertNull($replication->getConnectionById('slave2'));
  576. }
  577. /**
  578. * @group disconnected
  579. */
  580. public function testSucceedOnReadOnlyCommandAndNoConnectionSetAsMaster()
  581. {
  582. $commands = $this->getCommandFactory();
  583. $cmdExists = $commands->createCommand('exists', array('key'));
  584. $slave1 = $this->getMockConnection('tcp://host1?alias=slave1');
  585. $slave1
  586. ->expects($this->once())
  587. ->method('executeCommand')
  588. ->with($cmdExists)
  589. ->will($this->returnValue(1));
  590. $replication = new MasterSlaveReplication();
  591. $replication->add($slave1);
  592. $response = $replication->executeCommand($cmdExists);
  593. $this->assertSame(1, $response);
  594. }
  595. /**
  596. * @group disconnected
  597. * @expectedException \Predis\Replication\MissingMasterException
  598. * @expectedMessage No master server available for replication
  599. */
  600. public function testFailsOnWriteCommandAndNoConnectionSetAsMaster()
  601. {
  602. $commands = $this->getCommandFactory();
  603. $cmdSet = $commands->createCommand('set', array('key', 'value'));
  604. $slave1 = $this->getMockConnection('tcp://host1?alias=slave1');
  605. $slave1
  606. ->expects($this->never())
  607. ->method('executeCommand');
  608. $replication = new MasterSlaveReplication();
  609. $replication->add($slave1);
  610. $replication->executeCommand($cmdSet);
  611. }
  612. /**
  613. * @group disconnected
  614. */
  615. public function testDiscardsSlaveWhenRespondsLOADINGAndExecutesReadOnlyCommandOnNextSlave()
  616. {
  617. $master = $this->getMockConnection('tcp://host1?alias=master');
  618. $master->expects($this->never())
  619. ->method('executeCommand');
  620. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  621. $slave1
  622. ->expects($this->once())
  623. ->method('executeCommand')
  624. ->with($this->isRedisCommand(
  625. 'EXISTS', array('key')
  626. ))
  627. ->will($this->returnValue(
  628. new Response\Error('LOADING')
  629. ));
  630. $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
  631. $slave2
  632. ->expects($this->once())
  633. ->method('executeCommand')
  634. ->with($this->isRedisCommand(
  635. 'EXISTS', array('key')
  636. ))
  637. ->will($this->returnValue(1));
  638. $replication = new MasterSlaveReplication();
  639. $replication->add($master);
  640. $replication->add($slave1);
  641. $replication->add($slave2);
  642. $replication->switchTo($slave1);
  643. $response = $replication->executeCommand(
  644. Command\RawCommand::create('exists', 'key')
  645. );
  646. $this->assertSame(1, $response);
  647. $this->assertNull($replication->getConnectionById('slave1'));
  648. $this->assertSame($slave2, $replication->getConnectionById('slave2'));
  649. }
  650. /**
  651. * @group disconnected
  652. * @expectedException \Predis\Connection\ConnectionException
  653. */
  654. public function testFailsOnUnreachableMaster()
  655. {
  656. $commands = $this->getCommandFactory();
  657. $cmdSet = $commands->createCommand('set', array('key', 'value'));
  658. $master = $this->getMockConnection('tcp://host1?alias=master');
  659. $master
  660. ->expects($this->once())
  661. ->method('executeCommand')
  662. ->with($cmdSet)
  663. ->will($this->throwException(
  664. new Connection\ConnectionException($master)
  665. ));
  666. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  667. $slave1
  668. ->expects($this->never())
  669. ->method('executeCommand');
  670. $replication = new MasterSlaveReplication();
  671. $replication->add($master);
  672. $replication->add($slave1);
  673. $replication->executeCommand($cmdSet);
  674. }
  675. /**
  676. * @group disconnected
  677. * @expectedException \Predis\NotSupportedException
  678. * @expectedExceptionMessage The command 'INFO' is not allowed in replication mode.
  679. */
  680. public function testThrowsExceptionOnNonSupportedCommand()
  681. {
  682. $cmd = $this->getCommandFactory()->createCommand('info');
  683. $replication = new MasterSlaveReplication();
  684. $replication->add($this->getMockConnection('tcp://host1?alias=master'));
  685. $replication->add($this->getMockConnection('tcp://host2?alias=slave1'));
  686. $replication->getConnection($cmd);
  687. }
  688. /**
  689. * @group disconnected
  690. */
  691. public function testCanOverrideReadOnlyFlagForCommands()
  692. {
  693. $commands = $this->getCommandFactory();
  694. $cmdSet = $commands->createCommand('set', array('foo', 'bar'));
  695. $cmdGet = $commands->createCommand('get', array('foo'));
  696. $master = $this->getMockConnection('tcp://host1?alias=master');
  697. $master
  698. ->expects($this->once())
  699. ->method('executeCommand')
  700. ->with($cmdGet);
  701. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  702. $slave1
  703. ->expects($this->once())
  704. ->method('executeCommand')
  705. ->with($cmdSet);
  706. $replication = new MasterSlaveReplication();
  707. $replication->add($master);
  708. $replication->add($slave1);
  709. $replication->getReplicationStrategy()->setCommandReadOnly($cmdSet->getId(), true);
  710. $replication->getReplicationStrategy()->setCommandReadOnly($cmdGet->getId(), false);
  711. $replication->executeCommand($cmdSet);
  712. $replication->executeCommand($cmdGet);
  713. }
  714. /**
  715. * @group disconnected
  716. */
  717. public function testAcceptsCallableToOverrideReadOnlyFlagForCommands()
  718. {
  719. $commands = $this->getCommandFactory();
  720. $cmdExistsFoo = $commands->createCommand('exists', array('foo'));
  721. $cmdExistsBar = $commands->createCommand('exists', array('bar'));
  722. $master = $this->getMockConnection('tcp://host1?alias=master');
  723. $master
  724. ->expects($this->once())
  725. ->method('executeCommand')
  726. ->with($cmdExistsBar);
  727. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  728. $slave1
  729. ->expects($this->once())
  730. ->method('executeCommand')
  731. ->with($cmdExistsFoo);
  732. $replication = new MasterSlaveReplication();
  733. $replication->add($master);
  734. $replication->add($slave1);
  735. $replication
  736. ->getReplicationStrategy()
  737. ->setCommandReadOnly('exists', function ($cmd) {
  738. list($arg1) = $cmd->getArguments();
  739. return $arg1 === 'foo';
  740. });
  741. $replication->executeCommand($cmdExistsFoo);
  742. $replication->executeCommand($cmdExistsBar);
  743. }
  744. /**
  745. * @group disconnected
  746. */
  747. public function testCanSetReadOnlyFlagForEvalScripts()
  748. {
  749. $commands = $this->getCommandFactory();
  750. $cmdEval = $commands->createCommand('eval', array($script = "return redis.call('info');"));
  751. $cmdEvalSha = $commands->createCommand('evalsha', array($scriptSHA1 = sha1($script)));
  752. $master = $this->getMockConnection('tcp://host1?alias=master');
  753. $master
  754. ->expects($this->never())
  755. ->method('executeCommand');
  756. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  757. $slave1
  758. ->expects($this->exactly(2))
  759. ->method('executeCommand')
  760. ->with(
  761. $this->logicalOr($cmdEval, $cmdEvalSha)
  762. );
  763. $replication = new MasterSlaveReplication();
  764. $replication->add($master);
  765. $replication->add($slave1);
  766. $replication
  767. ->getReplicationStrategy()
  768. ->setScriptReadOnly($script);
  769. $replication->executeCommand($cmdEval);
  770. $replication->executeCommand($cmdEvalSha);
  771. }
  772. /**
  773. * @group disconnected
  774. * @expectedException \Predis\ClientException
  775. * @expectedMessage Discovery requires a connection factory
  776. */
  777. public function testDiscoveryRequiresConnectionFactory()
  778. {
  779. $replication = new MasterSlaveReplication();
  780. $replication->add($this->getMockConnection('tcp://host1?alias=master'));
  781. $replication->discover();
  782. }
  783. /**
  784. * @group disconnected
  785. */
  786. public function testDiscoversReplicationConfigurationFromMaster()
  787. {
  788. $connFactory = new Connection\Factory();
  789. $cmdInfo = Command\RawCommand::create('INFO', 'REPLICATION');
  790. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  791. $master
  792. ->expects($this->once())
  793. ->method('executeCommand')
  794. ->with($cmdInfo)
  795. ->will($this->returnValue('
  796. # Replication
  797. role:master
  798. connected_slaves:2
  799. slave0:ip=127.0.0.1,port=6382,state=online,offset=12979,lag=0
  800. slave1:ip=127.0.0.1,port=6383,state=online,offset=12979,lag=1
  801. master_repl_offset:12979
  802. repl_backlog_active:1
  803. repl_backlog_size:1048576
  804. repl_backlog_first_byte_offset:2
  805. repl_backlog_histlen:12978
  806. '
  807. ));
  808. $replication = new MasterSlaveReplication();
  809. $replication->setConnectionFactory($connFactory);
  810. $replication->add($master);
  811. $replication->discover();
  812. $this->assertCount(2, $slaves = $replication->getSlaves());
  813. $this->assertContainsOnlyInstancesOf('Predis\Connection\ConnectionInterface', $slaves);
  814. $this->assertSame('127.0.0.1:6381', (string) $replication->getMaster());
  815. $this->assertSame('127.0.0.1:6382', (string) $slaves[0]);
  816. $this->assertSame('127.0.0.1:6383', (string) $slaves[1]);
  817. }
  818. /**
  819. * @group disconnected
  820. */
  821. public function testDiscoversReplicationConfigurationFromSlave()
  822. {
  823. $cmdInfo = $command = Command\RawCommand::create('INFO', 'REPLICATION');
  824. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  825. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  826. $slave2 = $this->getMockConnection('tcp://127.0.0.1:6383?alias=slave2');
  827. $connFactory = $this->getMock('Predis\Connection\Factory');
  828. $connFactory
  829. ->expects($this->at(0))
  830. ->method('create')
  831. ->with(array(
  832. 'host' => '127.0.0.1',
  833. 'port' => '6381',
  834. 'alias' => 'master',
  835. ))
  836. ->will($this->returnValue($master));
  837. $connFactory
  838. ->expects($this->at(1))
  839. ->method('create')
  840. ->with(array(
  841. 'host' => '127.0.0.1',
  842. 'port' => '6382',
  843. ))
  844. ->will($this->returnValue($slave1));
  845. $connFactory
  846. ->expects($this->at(2))
  847. ->method('create')
  848. ->with(array(
  849. 'host' => '127.0.0.1',
  850. 'port' => '6383',
  851. ))
  852. ->will($this->returnValue($slave2));
  853. $slave1
  854. ->expects($this->once())
  855. ->method('executeCommand')
  856. ->with($cmdInfo)
  857. ->will($this->returnValue('
  858. # Replication
  859. role:slave
  860. master_host:127.0.0.1
  861. master_port:6381
  862. master_link_status:up
  863. master_last_io_seconds_ago:8
  864. master_sync_in_progress:0
  865. slave_repl_offset:17715532
  866. slave_priority:100
  867. slave_read_only:1
  868. connected_slaves:0
  869. master_repl_offset:0
  870. repl_backlog_active:0
  871. repl_backlog_size:1048576
  872. repl_backlog_first_byte_offset:0
  873. repl_backlog_histlen:0
  874. '
  875. ));
  876. $master
  877. ->expects($this->once())
  878. ->method('executeCommand')
  879. ->with($cmdInfo)
  880. ->will($this->returnValue('
  881. # Replication
  882. role:master
  883. connected_slaves:2
  884. slave0:ip=127.0.0.1,port=6382,state=online,offset=12979,lag=0
  885. slave1:ip=127.0.0.1,port=6383,state=online,offset=12979,lag=1
  886. master_repl_offset:12979
  887. repl_backlog_active:1
  888. repl_backlog_size:1048576
  889. repl_backlog_first_byte_offset:2
  890. repl_backlog_histlen:12978
  891. '
  892. ));
  893. $replication = new MasterSlaveReplication();
  894. $replication->setConnectionFactory($connFactory);
  895. $replication->add($slave1);
  896. $replication->discover();
  897. $this->assertCount(2, $slaves = $replication->getSlaves());
  898. $this->assertContainsOnlyInstancesOf('Predis\Connection\ConnectionInterface', $slaves);
  899. $this->assertSame('127.0.0.1:6381', (string) $replication->getMaster());
  900. $this->assertSame('127.0.0.1:6382', (string) $slaves[0]);
  901. $this->assertSame('127.0.0.1:6383', (string) $slaves[1]);
  902. }
  903. /**
  904. * @group disconnected
  905. */
  906. public function testDiscoversReplicationConfigurationFromSlaveIfMasterFails()
  907. {
  908. $cmdInfo = $command = Command\RawCommand::create('INFO', 'REPLICATION');
  909. $masterKO = $this->getMockConnection('tcp://127.0.0.1:7381?alias=master');
  910. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  911. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  912. $slave2 = $this->getMockConnection('tcp://127.0.0.1:6383?alias=slave2');
  913. $connFactory = $this->getMock('Predis\Connection\Factory');
  914. $connFactory
  915. ->expects($this->at(0))
  916. ->method('create')
  917. ->with(array(
  918. 'host' => '127.0.0.1',
  919. 'port' => '6381',
  920. 'alias' => 'master',
  921. ))
  922. ->will($this->returnValue($master));
  923. $connFactory
  924. ->expects($this->at(1))
  925. ->method('create')
  926. ->with(array(
  927. 'host' => '127.0.0.1',
  928. 'port' => '6382',
  929. ))
  930. ->will($this->returnValue($slave1));
  931. $connFactory
  932. ->expects($this->at(2))
  933. ->method('create')
  934. ->with(array(
  935. 'host' => '127.0.0.1',
  936. 'port' => '6383',
  937. ))
  938. ->will($this->returnValue($slave2));
  939. $masterKO
  940. ->expects($this->once())
  941. ->method('executeCommand')
  942. ->with($cmdInfo)
  943. ->will($this->throwException(
  944. new Connection\ConnectionException($masterKO)
  945. ));
  946. $slave1
  947. ->expects($this->once())
  948. ->method('executeCommand')
  949. ->with($cmdInfo)
  950. ->will($this->returnValue('
  951. # Replication
  952. role:slave
  953. master_host:127.0.0.1
  954. master_port:6381
  955. master_link_status:up
  956. master_last_io_seconds_ago:8
  957. master_sync_in_progress:0
  958. slave_repl_offset:17715532
  959. slave_priority:100
  960. slave_read_only:1
  961. connected_slaves:0
  962. master_repl_offset:0
  963. repl_backlog_active:0
  964. repl_backlog_size:1048576
  965. repl_backlog_first_byte_offset:0
  966. repl_backlog_histlen:0
  967. '
  968. ));
  969. $master
  970. ->expects($this->once())
  971. ->method('executeCommand')
  972. ->with($cmdInfo)
  973. ->will($this->returnValue('
  974. # Replication
  975. role:master
  976. connected_slaves:2
  977. slave0:ip=127.0.0.1,port=6382,state=online,offset=12979,lag=0
  978. slave1:ip=127.0.0.1,port=6383,state=online,offset=12979,lag=1
  979. master_repl_offset:12979
  980. repl_backlog_active:1
  981. repl_backlog_size:1048576
  982. repl_backlog_first_byte_offset:2
  983. repl_backlog_histlen:12978
  984. '
  985. ));
  986. $replication = new MasterSlaveReplication();
  987. $replication->setConnectionFactory($connFactory);
  988. $replication->add($masterKO);
  989. $replication->add($slave1);
  990. $replication->discover();
  991. $this->assertCount(2, $slaves = $replication->getSlaves());
  992. $this->assertContainsOnlyInstancesOf('Predis\Connection\ConnectionInterface', $slaves);
  993. $this->assertSame('127.0.0.1:6381', (string) $replication->getMaster());
  994. $this->assertSame('127.0.0.1:6382', (string) $slaves[0]);
  995. $this->assertSame('127.0.0.1:6383', (string) $slaves[1]);
  996. }
  997. /**
  998. * @group disconnected
  999. * @expectedException \Predis\ClientException
  1000. * @expectedMessage Automatic discovery requires a connection factory
  1001. */
  1002. public function testAutomaticDiscoveryRequiresConnectionFactory()
  1003. {
  1004. $master = $this->getMockConnection('tcp://host1?alias=master');
  1005. $replication = new MasterSlaveReplication();
  1006. $replication->add($master);
  1007. $replication->setAutoDiscovery(true);
  1008. }
  1009. /**
  1010. * @group disconnected
  1011. */
  1012. public function testAutomaticDiscoveryOnUnreachableServer()
  1013. {
  1014. $cmdInfo = $command = Command\RawCommand::create('INFO', 'REPLICATION');
  1015. $cmdExists = $command = Command\RawCommand::create('EXISTS', 'key');
  1016. $slaveKO = $this->getMockConnection('tcp://127.0.0.1:7382?alias=slaveKO');
  1017. $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master');
  1018. $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1');
  1019. $connFactory = $this->getMock('Predis\Connection\Factory');
  1020. $connFactory
  1021. ->expects($this->once())
  1022. ->method('create')
  1023. ->with(array(
  1024. 'host' => '127.0.0.1',
  1025. 'port' => '6382',
  1026. ))
  1027. ->will($this->returnValue($slave1));
  1028. $slaveKO
  1029. ->expects($this->once())
  1030. ->method('executeCommand')
  1031. ->with($cmdExists)
  1032. ->will($this->throwException(
  1033. new Connection\ConnectionException($slaveKO)
  1034. ));
  1035. $slave1
  1036. ->expects($this->once())
  1037. ->method('executeCommand')
  1038. ->with($cmdExists)
  1039. ->will($this->returnValue(1));
  1040. $master
  1041. ->expects($this->once())
  1042. ->method('executeCommand')
  1043. ->with($cmdInfo)
  1044. ->will($this->returnValue('
  1045. # Replication
  1046. role:master
  1047. connected_slaves:2
  1048. slave0:ip=127.0.0.1,port=6382,state=online,offset=12979,lag=0
  1049. master_repl_offset:12979
  1050. repl_backlog_active:1
  1051. repl_backlog_size:1048576
  1052. repl_backlog_first_byte_offset:2
  1053. repl_backlog_histlen:12978
  1054. '
  1055. ));
  1056. $replication = new MasterSlaveReplication();
  1057. $replication->setConnectionFactory($connFactory);
  1058. $replication->setAutoDiscovery(true);
  1059. $replication->add($master);
  1060. $replication->add($slaveKO);
  1061. $replication->executeCommand($cmdExists);
  1062. }
  1063. /**
  1064. * @group disconnected
  1065. */
  1066. public function testExposesReplicationStrategy()
  1067. {
  1068. $replication = new MasterSlaveReplication();
  1069. $this->assertInstanceOf('Predis\Replication\ReplicationStrategy', $replication->getReplicationStrategy());
  1070. $strategy = new ReplicationStrategy();
  1071. $replication = new MasterSlaveReplication($strategy);
  1072. $this->assertSame($strategy, $replication->getReplicationStrategy());
  1073. }
  1074. /**
  1075. * @group disconnected
  1076. */
  1077. public function testCanBeSerialized()
  1078. {
  1079. $master = $this->getMockConnection('tcp://host1?alias=master');
  1080. $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
  1081. $replication = new MasterSlaveReplication();
  1082. $replication->add($master);
  1083. $replication->add($slave1);
  1084. $unserialized = unserialize(serialize($replication));
  1085. $this->assertEquals($master, $unserialized->getConnectionById('master'));
  1086. $this->assertEquals($slave1, $unserialized->getConnectionById('slave1'));
  1087. }
  1088. }