* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Predis\Connection\Aggregate; use Predis\Command; use Predis\Connection; use Predis\Replication; use Predis\Response; use PredisTestCase; /** * */ class SentinelReplicationTest extends PredisTestCase { /** * @group disconnected * @expectedException Predis\ClientException * @expectedExceptionMessage No sentinel server available for autodiscovery. */ public function testMethodGetSentinelConnectionThrowsExceptionOnEmptySentinelsPool() { $replication = $this->getReplicationConnection('svc', array()); $replication->getSentinelConnection(); } /** * @group disconnected */ public function testParametersForSentinelConnectionShouldNotUseDatabaseAndPassword() { $replication = $this->getReplicationConnection('svc', array( 'tcp://127.0.0.1:5381?alias=sentinel1&database=1&password=secret', )); $parameters = $replication->getSentinelConnection()->getParameters()->toArray(); $this->assertArraySubset(array('database' => null, 'password' => null), $parameters); } /** * @group disconnected */ public function testParametersForSentinelConnectionHaveDefaultTimeout() { $replication = $this->getReplicationConnection('svc', array( 'tcp://127.0.0.1:5381?alias=sentinel', )); $parameters = $replication->getSentinelConnection()->getParameters()->toArray(); $this->assertArrayHasKey('timeout', $parameters); $this->assertSame(0.100, $parameters['timeout']); } /** * @group disconnected */ public function testParametersForSentinelConnectionCanOverrideDefaultTimeout() { $replication = $this->getReplicationConnection('svc', array( 'tcp://127.0.0.1:5381?alias=sentinel&timeout=1', )); $parameters = $replication->getSentinelConnection()->getParameters()->toArray(); $this->assertArrayHasKey('timeout', $parameters); $this->assertSame('1', $parameters['timeout']); } /** * @group disconnected */ public function testConnectionParametersInstanceForSentinelConnectionIsNotModified() { $originalParameters = Connection\Parameters::create( 'tcp://127.0.0.1:5381?alias=sentinel1&database=1&password=secret' ); $replication = $this->getReplicationConnection('svc', array($originalParameters)); $parameters = $replication->getSentinelConnection()->getParameters(); $this->assertSame($originalParameters, $parameters); $this->assertNotNull($parameters->password); $this->assertNotNull($parameters->database); } /** * @group disconnected */ public function testMethodGetSentinelConnectionReturnsFirstAvailableSentinel() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $sentinel2 = $this->getMockSentinelConnection('tcp://127.0.0.1:5382?alias=sentinel2'); $sentinel3 = $this->getMockSentinelConnection('tcp://127.0.0.1:5383?alias=sentinel3'); $replication = $this->getReplicationConnection('svc', array($sentinel1, $sentinel2, $sentinel3)); $this->assertSame($sentinel1, $replication->getSentinelConnection()); } /** * @group disconnected */ public function testMethodAddAttachesMasterOrSlaveNodesToReplication() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master'); $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1'); $slave2 = $this->getMockConnection('tcp://127.0.0.1:6383?alias=slave2'); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $replication->add($master); $replication->add($slave1); $replication->add($slave2); $this->assertSame($master, $replication->getConnectionById('master')); $this->assertSame($slave1, $replication->getConnectionById('slave1')); $this->assertSame($slave2, $replication->getConnectionById('slave2')); $this->assertSame($master, $replication->getMaster()); $this->assertSame(array($slave1, $slave2), $replication->getSlaves()); } /** * @group disconnected */ public function testMethodRemoveDismissesMasterOrSlaveNodesFromReplication() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master'); $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1'); $slave2 = $this->getMockConnection('tcp://127.0.0.1:6383?alias=slave2'); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $replication->add($master); $replication->add($slave1); $replication->add($slave2); $this->assertTrue($replication->remove($slave1)); $this->assertFalse($replication->remove($sentinel1)); $this->assertSame('127.0.0.1:6381', (string) $replication->getMaster()); $this->assertCount(1, $slaves = $replication->getSlaves()); $this->assertSame('127.0.0.1:6383', (string) $slaves[0]); } /** * @group disconnected */ public function testMethodUpdateSentinelsFetchesSentinelNodes() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $sentinel1->expects($this->once()) ->method('executeCommand') ->with($this->isRedisCommand( 'SENTINEL', array('sentinels', 'svc') )) ->will($this->returnValue( array( array( 'name', '127.0.0.1:5382', 'ip', '127.0.0.1', 'port', '5382', 'runid', 'a113aa7a0d4870a85bb22b4b605fd26eb93ed40e', 'flags', 'sentinel', ), array( 'name', '127.0.0.1:5383', 'ip', '127.0.0.1', 'port', '5383', 'runid', 'f53b52d281be5cdd4873700c94846af8dbe47209', 'flags', 'sentinel', ), ) )); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $replication->updateSentinels(); // TODO: sorry for the smell... $reflection = new \ReflectionProperty($replication, 'sentinels'); $reflection->setAccessible(true); $expected = array( array('host' => '127.0.0.1', 'port' => '5381'), array('host' => '127.0.0.1', 'port' => '5382'), array('host' => '127.0.0.1', 'port' => '5383'), ); $this->assertSame($sentinel1, $replication->getSentinelConnection()); $this->assertSame($expected, array_intersect_key($expected, $reflection->getValue($replication))); } /** * @group disconnected */ public function testMethodUpdateSentinelsRemovesCurrentSentinelAndRetriesNextOneOnFailure() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $sentinel1->expects($this->once()) ->method('executeCommand') ->with($this->isRedisCommand( 'SENTINEL', array('sentinels', 'svc') )) ->will($this->throwException( new Connection\ConnectionException($sentinel1, 'Unknown connection error [127.0.0.1:5381]') )); $sentinel2 = $this->getMockSentinelConnection('tcp://127.0.0.1:5382?alias=sentinel2'); $sentinel2->expects($this->once()) ->method('executeCommand') ->with($this->isRedisCommand( 'SENTINEL', array('sentinels', 'svc') )) ->will($this->returnValue( array( array( 'name', '127.0.0.1:5383', 'ip', '127.0.0.1', 'port', '5383', 'runid', 'f53b52d281be5cdd4873700c94846af8dbe47209', 'flags', 'sentinel', ), ) )); $replication = $this->getReplicationConnection('svc', array($sentinel1, $sentinel2)); $replication->updateSentinels(); // TODO: sorry for the smell... $reflection = new \ReflectionProperty($replication, 'sentinels'); $reflection->setAccessible(true); $expected = array( array('host' => '127.0.0.1', 'port' => '5382'), array('host' => '127.0.0.1', 'port' => '5383'), ); $this->assertSame($sentinel2, $replication->getSentinelConnection()); $this->assertSame($expected, array_intersect_key($expected, $reflection->getValue($replication))); } /** * @group disconnected * @expectedException Predis\ClientException * @expectedExceptionMessage No sentinel server available for autodiscovery. */ public function testMethodUpdateSentinelsThrowsExceptionOnNoAvailableSentinel() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $sentinel1->expects($this->once()) ->method('executeCommand') ->with($this->isRedisCommand( 'SENTINEL', array('sentinels', 'svc') )) ->will($this->throwException( new Connection\ConnectionException($sentinel1, 'Unknown connection error [127.0.0.1:5381]') )); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $replication->updateSentinels(); } /** * @group disconnected */ public function testMethodQuerySentinelFetchesMasterNodeSlaveNodesAndSentinelNodes() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $sentinel1->expects($this->exactly(3)) ->method('executeCommand') ->withConsecutive( $this->isRedisCommand('SENTINEL', array('sentinels', 'svc')), $this->isRedisCommand('SENTINEL', array('get-master-addr-by-name', 'svc')), $this->isRedisCommand('SENTINEL', array('slaves', 'svc')) ) ->will($this->onConsecutiveCalls( // SENTINEL sentinels svc array( array( 'name', '127.0.0.1:5382', 'ip', '127.0.0.1', 'port', '5382', 'runid', 'a113aa7a0d4870a85bb22b4b605fd26eb93ed40e', 'flags', 'sentinel', ), ), // SENTINEL get-master-addr-by-name svc array('127.0.0.1', '6381'), // SENTINEL slaves svc array( array( 'name', '127.0.0.1:6382', 'ip', '127.0.0.1', 'port', '6382', 'runid', '112cdebd22924a7d962be496f3a1c4c7c9bad93f', 'flags', 'slave', 'master-host', '127.0.0.1', 'master-port', '6381', ), array( 'name', '127.0.0.1:6383', 'ip', '127.0.0.1', 'port', '6383', 'runid', '1c0bf1291797fbc5608c07a17da394147dc62817', 'flags', 'slave', 'master-host', '127.0.0.1', 'master-port', '6381', ), ) )); $sentinel2 = $this->getMockSentinelConnection('tcp://127.0.0.1:5382?alias=sentinel2'); $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master'); $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1'); $slave2 = $this->getMockConnection('tcp://127.0.0.1:6383?alias=slave2'); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $replication->querySentinel(); // TODO: sorry for the smell... $reflection = new \ReflectionProperty($replication, 'sentinels'); $reflection->setAccessible(true); $sentinels = array( array('host' => '127.0.0.1', 'port' => '5381'), array('host' => '127.0.0.1', 'port' => '5382'), ); $this->assertSame($sentinel1, $replication->getSentinelConnection()); $this->assertSame($sentinels, array_intersect_key($sentinels, $reflection->getValue($replication))); $master = $replication->getMaster(); $slaves = $replication->getSlaves(); $this->assertSame('127.0.0.1:6381', (string) $master); $this->assertCount(2, $slaves); $this->assertSame('127.0.0.1:6382', (string) $slaves[0]); $this->assertSame('127.0.0.1:6383', (string) $slaves[1]); } /** * @group disconnected */ public function testMethodGetMasterAsksSentinelForMasterOnMasterNotSet() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $sentinel1->expects($this->at(0)) ->method('executeCommand') ->with($this->isRedisCommand( 'SENTINEL', array('get-master-addr-by-name', 'svc') )) ->will($this->returnValue( array('127.0.0.1', '6381') )); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $this->assertSame('127.0.0.1:6381', (string) $replication->getMaster()); } /** * @group disconnected * @expectedException Predis\ClientException * @expectedExceptionMessage No sentinel server available for autodiscovery. */ public function testMethodGetMasterThrowsExceptionOnNoAvailableSentinels() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $sentinel1->expects($this->any()) ->method('executeCommand') ->with($this->isRedisCommand( 'SENTINEL', array('get-master-addr-by-name', 'svc') )) ->will($this->throwException( new Connection\ConnectionException($sentinel1, 'Unknown connection error [127.0.0.1:5381]') )); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $replication->getMaster(); } /** * @group disconnected */ public function testMethodGetSlavesOnEmptySlavePoolAsksSentinelForSlaves() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $sentinel1->expects($this->at(0)) ->method('executeCommand') ->with($this->isRedisCommand( 'SENTINEL', array('slaves', 'svc') )) ->will($this->returnValue( array( array( 'name', '127.0.0.1:6382', 'ip', '127.0.0.1', 'port', '6382', 'runid', '112cdebd22924a7d962be496f3a1c4c7c9bad93f', 'flags', 'slave', 'master-host', '127.0.0.1', 'master-port', '6381', ), array( 'name', '127.0.0.1:6383', 'ip', '127.0.0.1', 'port', '6383', 'runid', '1c0bf1291797fbc5608c07a17da394147dc62817', 'flags', 'slave', 'master-host', '127.0.0.1', 'master-port', '6381', ), ) )); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $slaves = $replication->getSlaves(); $this->assertSame('127.0.0.1:6382', (string) $slaves[0]); $this->assertSame('127.0.0.1:6383', (string) $slaves[1]); } /** * @group disconnected * @expectedException Predis\ClientException * @expectedExceptionMessage No sentinel server available for autodiscovery. */ public function testMethodGetSlavesThrowsExceptionOnNoAvailableSentinels() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $sentinel1->expects($this->any()) ->method('executeCommand') ->with($this->isRedisCommand( 'SENTINEL', array('slaves', 'svc') )) ->will($this->throwException( new Connection\ConnectionException($sentinel1, 'Unknown connection error [127.0.0.1:5381]') )); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $replication->getSlaves(); } /** * @group disconnected * @expectedException Predis\ClientException * @expectedExceptionMessage No sentinel server available for autodiscovery. */ public function testMethodConnectThrowsExceptionOnConnectWithEmptySentinelsPool() { $replication = $this->getReplicationConnection('svc', array()); $replication->connect(); } /** * @group disconnected */ public function testMethodConnectForcesConnectionToSlave() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master'); $master->expects($this->never()) ->method('connect'); $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1'); $slave1->expects($this->once()) ->method('connect'); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $replication->add($master); $replication->add($slave1); $replication->connect(); } /** * @group disconnected */ public function testMethodConnectOnEmptySlavePoolAsksSentinelForSlavesAndForcesConnectionToSlave() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $sentinel1->expects($this->any()) ->method('executeCommand') ->with($this->isRedisCommand( 'SENTINEL', array('slaves', 'svc') )) ->will($this->returnValue( array( array( 'name', '127.0.0.1:6382', 'ip', '127.0.0.1', 'port', '6382', 'runid', '112cdebd22924a7d962be496f3a1c4c7c9bad93f', 'flags', 'slave', 'master-host', '127.0.0.1', 'master-port', '6381', ), ) )); $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master'); $master->expects($this->never()) ->method('connect'); $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1'); $slave1->expects($this->once()) ->method('connect'); $factory = $this->getMock('Predis\Connection\FactoryInterface'); $factory->expects($this->once()) ->method('create') ->with(array( 'host' => '127.0.0.1', 'port' => '6382', 'alias' => 'slave-127.0.0.1:6382', )) ->will($this->returnValue($slave1)); $replication = $this->getReplicationConnection('svc', array($sentinel1), $factory); $replication->add($master); $replication->connect(); } /** * @group disconnected */ public function testMethodConnectOnEmptySlavePoolAsksSentinelForSlavesAndForcesConnectionToMasterIfStillEmpty() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $sentinel1->expects($this->at(0)) ->method('executeCommand') ->with($this->isRedisCommand( 'SENTINEL', array('slaves', 'svc') )) ->will($this->returnValue( array() )); $sentinel1->expects($this->at(1)) ->method('executeCommand') ->with($this->isRedisCommand( 'SENTINEL', array('get-master-addr-by-name', 'svc') )) ->will($this->returnValue( array('127.0.0.1', '6381') )); $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master'); $master->expects($this->once()) ->method('connect'); $factory = $this->getMock('Predis\Connection\FactoryInterface'); $factory->expects($this->once()) ->method('create') ->with(array( 'host' => '127.0.0.1', 'port' => '6381', 'alias' => 'master', )) ->will($this->returnValue($master)); $replication = $this->getReplicationConnection('svc', array($sentinel1), $factory); $replication->connect(); } /** * @group disconnected */ public function testMethodDisconnectForcesDisconnectionOnAllConnectionsInPool() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $sentinel1->expects($this->never())->method('disconnect'); $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master'); $master->expects($this->once())->method('disconnect'); $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1'); $slave1->expects($this->once())->method('disconnect'); $slave2 = $this->getMockConnection('tcp://127.0.0.1:6383?alias=slave2'); $slave2->expects($this->once())->method('disconnect'); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $replication->add($master); $replication->add($slave1); $replication->add($slave2); $replication->disconnect(); } /** * @group disconnected */ public function testMethodIsConnectedReturnConnectionStatusOfCurrentConnection() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1'); $slave1->expects($this->exactly(2)) ->method('isConnected') ->will($this->onConsecutiveCalls(true, false)); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $replication->add($slave1); $this->assertFalse($replication->isConnected()); $replication->connect(); $this->assertTrue($replication->isConnected()); $replication->getConnectionById('slave1')->disconnect(); $this->assertFalse($replication->isConnected()); } /** * @group disconnected */ public function testMethodGetConnectionByIdReturnsConnectionWhenFound() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master'); $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1'); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $replication->add($master); $replication->add($slave1); $this->assertSame($master, $replication->getConnectionById('master')); $this->assertSame($slave1, $replication->getConnectionById('slave1')); $this->assertNull($replication->getConnectionById('unknown')); } /** * @group disconnected */ public function testMethodSwitchToSelectsCurrentConnectionByConnectionAlias() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master'); $master->expects($this->once())->method('connect'); $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1'); $slave1->expects($this->never())->method('connect'); $slave2 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave2'); $slave2->expects($this->once())->method('connect'); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $replication->add($master); $replication->add($slave1); $replication->add($slave2); $replication->switchTo('master'); $this->assertSame($master, $replication->getCurrent()); $replication->switchTo('slave2'); $this->assertSame($slave2, $replication->getCurrent()); } /** * @group disconnected * @expectedException InvalidArgumentException * @expectedExceptionMessage Invalid connection or connection not found. */ public function testMethodSwitchToThrowsExceptionOnConnectionNotFound() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master'); $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1'); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $replication->add($master); $replication->add($slave1); $replication->switchTo('unknown'); } /** * @group disconnected */ public function testMethodSwitchToMasterSelectsCurrentConnectionToMaster() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master'); $master->expects($this->once())->method('connect'); $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1'); $slave1->expects($this->never())->method('connect'); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $replication->add($master); $replication->add($slave1); $replication->switchToMaster(); $this->assertSame($master, $replication->getCurrent()); } /** * @group disconnected */ public function testMethodSwitchToSlaveSelectsCurrentConnectionToRandomSlave() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master'); $master->expects($this->never())->method('connect'); $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1'); $slave1->expects($this->once())->method('connect'); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $replication->add($master); $replication->add($slave1); $replication->switchToSlave(); $this->assertSame($slave1, $replication->getCurrent()); } /** * @group disconnected */ public function testGetConnectionReturnsMasterForWriteCommands() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master'); $master->expects($this->exactly(2)) ->method('isConnected') ->will($this->onConsecutiveCalls(false, true)); $master->expects($this->at(2)) ->method('executeCommand') ->with($this->isRedisCommand('ROLE')) ->will($this->returnValue(array( 'master', 3129659, array(array('127.0.0.1', 6382, 3129242)), ))); $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1'); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $replication->add($master); $replication->add($slave1); $this->assertSame($master, $replication->getConnection( Command\RawCommand::create('set', 'key', 'value') )); $this->assertSame($master, $replication->getConnection( Command\RawCommand::create('del', 'key') )); } /** * @group disconnected */ public function testGetConnectionReturnsSlaveForReadOnlyCommands() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master'); $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1'); $slave1->expects($this->exactly(2)) ->method('isConnected') ->will($this->onConsecutiveCalls(false, true)); $slave1->expects($this->at(2)) ->method('executeCommand') ->with($this->isRedisCommand('ROLE')) ->will($this->returnValue(array( 'slave', '127.0.0.1', 9000, 'connected', 3167038, ))); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $replication->add($master); $replication->add($slave1); $this->assertSame($slave1, $replication->getConnection( Command\RawCommand::create('get', 'key') )); $this->assertSame($slave1, $replication->getConnection( Command\RawCommand::create('exists', 'key') )); } /** * @group disconnected */ public function testGetConnectionSwitchesToMasterAfterWriteCommand() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master'); $master->expects($this->exactly(2)) ->method('isConnected') ->will($this->onConsecutiveCalls(false, true)); $master->expects($this->at(2)) ->method('executeCommand') ->with($this->isRedisCommand('ROLE')) ->will($this->returnValue(array( 'master', 3129659, array(array('127.0.0.1', 6382, 3129242)), ))); $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1'); $slave1->expects($this->exactly(1)) ->method('isConnected') ->will($this->onConsecutiveCalls(false)); $slave1->expects($this->at(2)) ->method('executeCommand') ->with($this->isRedisCommand('ROLE')) ->will($this->returnValue(array( 'slave', '127.0.0.1', 9000, 'connected', 3167038, ))); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $replication->add($master); $replication->add($slave1); $this->assertSame($slave1, $replication->getConnection( Command\RawCommand::create('exists', 'key') )); $this->assertSame($master, $replication->getConnection( Command\RawCommand::create('set', 'key', 'value') )); $this->assertSame($master, $replication->getConnection( Command\RawCommand::create('get', 'key') )); } /** * @group disconnected * @expectedException Predis\Replication\RoleException * @expectedExceptionMessage Expected master but got slave [127.0.0.1:6381] */ public function testGetConnectionThrowsExceptionOnNodeRoleMismatch() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master'); $master->expects($this->once()) ->method('isConnected') ->will($this->returnValue(false)); $master->expects($this->at(2)) ->method('executeCommand') ->with($this->isRedisCommand('ROLE')) ->will($this->returnValue(array( 'slave', '127.0.0.1', 9000, 'connected', 3167038, ))); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $replication->add($master); $replication->getConnection(Command\RawCommand::create('del', 'key')); } /** * @group disconnected */ public function testGetConnectionReturnsMasterForReadOnlyOperationsOnUnavailableSlaves() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $sentinel1->expects($this->once()) ->method('executeCommand') ->with($this->isRedisCommand( 'SENTINEL', array('slaves', 'svc') )) ->will($this->returnValue( array( array( 'name', '127.0.0.1:6382', 'ip', '127.0.0.1', 'port', '6382', 'runid', '1c0bf1291797fbc5608c07a17da394147dc62817', 'flags', 'slave,s_down,disconnected', 'master-host', '127.0.0.1', 'master-port', '6381', ), ) )); $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master'); $master->expects($this->once()) ->method('isConnected') ->will($this->returnValue(false)); $master->expects($this->at(2)) ->method('executeCommand') ->with($this->isRedisCommand('ROLE')) ->will($this->returnValue(array( 'master', '0', array(), ))); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $replication->add($master); $replication->getConnection(Command\RawCommand::create('get', 'key')); } /** * @group disconnected */ public function testMethodExecuteCommandSendsCommandToNodeAndReturnsResponse() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $cmdGet = Command\RawCommand::create('get', 'key'); $cmdGetResponse = 'value'; $cmdSet = Command\RawCommand::create('set', 'key', 'value'); $cmdSetResponse = Response\Status::get('OK'); $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master'); $master->expects($this->any()) ->method('isConnected') ->will($this->returnValue(true)); $master->expects($this->at(2)) ->method('executeCommand') ->with($this->isRedisCommand('SET', array('key', $cmdGetResponse))) ->will($this->returnValue($cmdSetResponse)); $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1'); $slave1->expects($this->any()) ->method('isConnected') ->will($this->returnValue(true)); $slave1->expects($this->at(2)) ->method('executeCommand') ->with($this->isRedisCommand('GET', array('key'))) ->will($this->returnValue($cmdGetResponse)); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $replication->add($master); $replication->add($slave1); $this->assertSame($cmdGetResponse, $replication->executeCommand($cmdGet)); $this->assertSame($cmdSetResponse, $replication->executeCommand($cmdSet)); } /** * @group disconnected */ public function testMethodExecuteCommandRetriesReadOnlyCommandOnNextSlaveOnFailure() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $sentinel1->expects($this->any()) ->method('executeCommand') ->with($this->isRedisCommand( 'SENTINEL', array('slaves', 'svc') )) ->will($this->returnValue( array( array( 'name', '127.0.0.1:6383', 'ip', '127.0.0.1', 'port', '6383', 'runid', '1c0bf1291797fbc5608c07a17da394147dc62817', 'flags', 'slave', 'master-host', '127.0.0.1', 'master-port', '6381', ), ) )); $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master'); $master->expects($this->any()) ->method('isConnected') ->will($this->returnValue(true)); $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1'); $slave1->expects($this->any()) ->method('isConnected') ->will($this->returnValue(true)); $slave1->expects($this->at(2)) ->method('executeCommand') ->with($this->isRedisCommand('GET', array('key'))) ->will($this->throwException( new Connection\ConnectionException($slave1, 'Unknown connection error [127.0.0.1:6382]') )); $slave2 = $this->getMockConnection('tcp://127.0.0.1:6383?alias=slave2'); $slave2->expects($this->any()) ->method('isConnected') ->will($this->returnValue(true)); $slave2->expects($this->at(2)) ->method('executeCommand') ->with($this->isRedisCommand('GET', array('key'))) ->will($this->returnValue('value')); $factory = $this->getMock('Predis\Connection\FactoryInterface'); $factory->expects($this->once()) ->method('create') ->with(array( 'host' => '127.0.0.1', 'port' => '6383', 'alias' => 'slave-127.0.0.1:6383', )) ->will($this->returnValue($slave2)); $replication = $this->getReplicationConnection('svc', array($sentinel1), $factory); $replication->add($master); $replication->add($slave1); $this->assertSame('value', $replication->executeCommand( Command\RawCommand::create('get', 'key') )); } /** * @group disconnected */ public function testMethodExecuteCommandRetriesWriteCommandOnNewMasterOnFailure() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $sentinel1->expects($this->any()) ->method('executeCommand') ->with($this->isRedisCommand( 'SENTINEL', array('get-master-addr-by-name', 'svc') )) ->will($this->returnValue( array('127.0.0.1', '6391') )); $masterOld = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master'); $masterOld->expects($this->any()) ->method('isConnected') ->will($this->returnValue(true)); $masterOld->expects($this->at(2)) ->method('executeCommand') ->with($this->isRedisCommand('DEL', array('key'))) ->will($this->throwException( new Connection\ConnectionException($masterOld, 'Unknown connection error [127.0.0.1:6381]') )); $masterNew = $this->getMockConnection('tcp://127.0.0.1:6391?alias=master'); $masterNew->expects($this->any()) ->method('isConnected') ->will($this->returnValue(true)); $masterNew->expects($this->at(2)) ->method('executeCommand') ->with($this->isRedisCommand('DEL', array('key'))) ->will($this->returnValue(1)); $factory = $this->getMock('Predis\Connection\FactoryInterface'); $factory->expects($this->once()) ->method('create') ->with(array( 'host' => '127.0.0.1', 'port' => '6391', 'alias' => 'master', )) ->will($this->returnValue($masterNew)); $replication = $this->getReplicationConnection('svc', array($sentinel1), $factory); $replication->add($masterOld); $this->assertSame(1, $replication->executeCommand( Command\RawCommand::create('del', 'key') )); } /** * @group disconnected * @expectedException Predis\Response\ServerException * @expectedExceptionMessage ERR No such master with that name */ public function testMethodExecuteCommandThrowsExceptionOnUnknownServiceName() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $sentinel1->expects($this->any()) ->method('executeCommand') ->with($this->isRedisCommand( 'SENTINEL', array('get-master-addr-by-name', 'svc') )) ->will($this->returnValue(null)); $masterOld = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master'); $masterOld->expects($this->any()) ->method('isConnected') ->will($this->returnValue(true)); $masterOld->expects($this->at(2)) ->method('executeCommand') ->with($this->isRedisCommand('DEL', array('key'))) ->will($this->throwException( new Connection\ConnectionException($masterOld, 'Unknown connection error [127.0.0.1:6381]') )); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $replication->add($masterOld); $replication->executeCommand( Command\RawCommand::create('del', 'key') ); } /** * @group disconnected * @expectedException Predis\ClientException * @expectedExceptionMessage No sentinel server available for autodiscovery. */ public function testMethodExecuteCommandThrowsExceptionOnConnectionFailureAndNoAvailableSentinels() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $sentinel1->expects($this->any()) ->method('executeCommand') ->with($this->isRedisCommand( 'SENTINEL', array('get-master-addr-by-name', 'svc') )) ->will($this->throwException( new Connection\ConnectionException($sentinel1, 'Unknown connection error [127.0.0.1:5381]') )); $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master'); $master->expects($this->any()) ->method('isConnected') ->will($this->returnValue(true)); $master->expects($this->at(2)) ->method('executeCommand') ->with($this->isRedisCommand('DEL', array('key'))) ->will($this->throwException( new Connection\ConnectionException($master, 'Unknown connection error [127.0.0.1:6381]') )); $replication = $this->getReplicationConnection('svc', array($sentinel1)); $replication->add($master); $replication->executeCommand( Command\RawCommand::create('del', 'key') ); } /** * @group disconnected */ public function testMethodGetReplicationStrategyReturnsInstance() { $strategy = new Replication\ReplicationStrategy(); $factory = new Connection\Factory(); $replication = new SentinelReplication( 'svc', array('tcp://127.0.0.1:5381?alias=sentinel1'), $factory, $strategy ); $this->assertSame($strategy, $replication->getReplicationStrategy()); } /** * @group disconnected */ public function testMethodSerializeCanSerializeWholeObject() { $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?alias=sentinel1'); $master = $this->getMockConnection('tcp://127.0.0.1:6381?alias=master'); $slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?alias=slave1'); $slave2 = $this->getMockConnection('tcp://127.0.0.1:6383?alias=slave2'); $strategy = new Replication\ReplicationStrategy(); $factory = new Connection\Factory(); $replication = new SentinelReplication('svc', array($sentinel1), $factory, $strategy); $replication->add($master); $replication->add($slave1); $replication->add($slave2); $unserialized = unserialize(serialize($replication)); $this->assertEquals($master, $unserialized->getConnectionById('master')); $this->assertEquals($slave1, $unserialized->getConnectionById('slave1')); $this->assertEquals($master, $unserialized->getConnectionById('slave2')); $this->assertEquals($strategy, $unserialized->getReplicationStrategy()); } // ******************************************************************** // // ---- HELPER METHODS ------------------------------------------------ // // ******************************************************************** // /** * Creates a new instance of replication connection. * * @param string $service Name of the service * @param array $sentinels Array of sentinels * @param ConnectionFactoryInterface|null $factory Optional connection factory instance. * * @return SentinelReplication */ protected function getReplicationConnection($service, $sentinels, Connection\FactoryInterface $factory = null) { $factory = $factory ?: new Connection\Factory(); $replication = new SentinelReplication($service, $sentinels, $factory); $replication->setRetryWait(0); return $replication; } /** * Returns a base mocked connection from Predis\Connection\NodeConnectionInterface. * * @param mixed $parameters Optional parameters. * * @return mixed */ protected function getMockSentinelConnection($parameters = null) { $connection = $this->getMockConnection($parameters); return $connection; } }