MasterSlaveReplicationTest.php 39 KB

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