* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Predis\Connection; use \PHPUnit_Framework_TestCase as StandardTestCase; use Predis\ResponseError; use Predis\Profile\ServerProfile; /** * */ class RedisClusterTest extends StandardTestCase { /** * @group disconnected */ public function testExposesCommandHashStrategy() { $cluster = new RedisCluster(); $this->assertInstanceOf('Predis\Cluster\RedisClusterHashStrategy', $cluster->getCommandHashStrategy()); } /** * @group disconnected */ public function testAddingConnectionsToCluster() { $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379'); $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380'); $cluster = new RedisCluster(); $cluster->add($connection1); $cluster->add($connection2); $this->assertSame(2, count($cluster)); $this->assertSame($connection1, $cluster->getConnectionById('127.0.0.1:6379')); $this->assertSame($connection2, $cluster->getConnectionById('127.0.0.1:6380')); } /** * @group disconnected */ public function testRemovingConnectionsFromCluster() { $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379'); $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380'); $connection3 = $this->getMockConnection('tcp://127.0.0.1:6371'); $cluster = new RedisCluster(); $cluster->add($connection1); $cluster->add($connection2); $this->assertTrue($cluster->remove($connection1)); $this->assertFalse($cluster->remove($connection3)); $this->assertSame(1, count($cluster)); } /** * @group disconnected */ public function testRemovingConnectionsFromClusterByAlias() { $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379'); $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380'); $cluster = new RedisCluster(); $cluster->add($connection1); $cluster->add($connection2); $this->assertTrue($cluster->removeById('127.0.0.1:6380')); $this->assertFalse($cluster->removeById('127.0.0.1:6390')); $this->assertSame(1, count($cluster)); } /** * @group disconnected */ public function testCountReturnsNumberOfConnectionsInPool() { $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379'); $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380'); $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381'); $cluster = new RedisCluster(); $cluster->add($connection1); $cluster->add($connection2); $cluster->add($connection3); $this->assertSame(3, count($cluster)); $cluster->remove($connection3); $this->assertSame(2, count($cluster)); } /** * @group disconnected */ public function testConnectForcesAllConnectionsToConnect() { $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379'); $connection1->expects($this->once())->method('connect'); $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380'); $connection2->expects($this->once())->method('connect'); $cluster = new RedisCluster(); $cluster->add($connection1); $cluster->add($connection2); $cluster->connect(); } /** * @group disconnected */ public function testDisconnectForcesAllConnectionsToDisconnect() { $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379'); $connection1->expects($this->once())->method('disconnect'); $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380'); $connection2->expects($this->once())->method('disconnect'); $cluster = new RedisCluster(); $cluster->add($connection1); $cluster->add($connection2); $cluster->disconnect(); } /** * @group disconnected */ public function testIsConnectedReturnsTrueIfAtLeastOneConnectionIsOpen() { $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379'); $connection1->expects($this->once()) ->method('isConnected') ->will($this->returnValue(false)); $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380'); $connection2->expects($this->once()) ->method('isConnected') ->will($this->returnValue(true)); $cluster = new RedisCluster(); $cluster->add($connection1); $cluster->add($connection2); $this->assertTrue($cluster->isConnected()); } /** * @group disconnected */ public function testIsConnectedReturnsFalseIfAllConnectionsAreClosed() { $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379'); $connection1->expects($this->once()) ->method('isConnected') ->will($this->returnValue(false)); $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380'); $connection2->expects($this->once()) ->method('isConnected') ->will($this->returnValue(false)); $cluster = new RedisCluster(); $cluster->add($connection1); $cluster->add($connection2); $this->assertFalse($cluster->isConnected()); } /** * @group disconnected */ public function testCanReturnAnIteratorForConnections() { $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379'); $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380'); $cluster = new RedisCluster(); $cluster->add($connection1); $cluster->add($connection2); $this->assertInstanceOf('Iterator', $iterator = $cluster->getIterator()); $connections = iterator_to_array($iterator); $this->assertSame($connection1, $connections[0]); $this->assertSame($connection2, $connections[1]); } /** * @group disconnected */ public function testCanAssignConnectionsToCustomSlots() { $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379'); $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380'); $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381'); $cluster = new RedisCluster(); $cluster->add($connection1); $cluster->add($connection2); $cluster->add($connection3); $cluster->setSlots(0, 1364, '127.0.0.1:6379'); $cluster->setSlots(1365, 2729, '127.0.0.1:6380'); $cluster->setSlots(2730, 4095, '127.0.0.1:6381'); $expectedMap = array_merge( array_fill(0, 1365, '127.0.0.1:6379'), array_fill(1364, 1365, '127.0.0.1:6380'), array_fill(2729, 1366, '127.0.0.1:6381') ); $this->assertSame($expectedMap, $cluster->getSlotsMap()); } /** * @group disconnected */ public function testAddingConnectionResetsSlotsMap() { $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379'); $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380'); $cluster = new RedisCluster(); $cluster->add($connection1); $cluster->setSlots(0, 4095, '127.0.0.1:6379'); $this->assertSame(array_fill(0, 4096, '127.0.0.1:6379'), $cluster->getSlotsMap()); $cluster->add($connection2); $this->assertEmpty($cluster->getSlotsMap()); } /** * @group disconnected */ public function testRemovingConnectionResetsSlotsMap() { $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379'); $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380'); $cluster = new RedisCluster(); $cluster->add($connection1); $cluster->add($connection2); $cluster->setSlots(0, 2047, '127.0.0.1:6379'); $cluster->setSlots(2048, 4095, '127.0.0.1:6380'); $expectedMap = array_merge( array_fill(0, 2048, '127.0.0.1:6379'), array_fill(2048, 2048, '127.0.0.1:6380') ); $this->assertSame($expectedMap, $cluster->getSlotsMap()); $cluster->remove($connection1); $this->assertEmpty($cluster->getSlotsMap()); } /** * @group disconnected */ public function testCanAssignConnectionsToCustomSlotsFromParameters() { $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379?slots=0-1364'); $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380?slots=1365-2729'); $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=2730-4095'); $cluster = new RedisCluster(); $cluster->add($connection1); $cluster->add($connection2); $cluster->add($connection3); $expectedMap = array_merge( array_fill(0, 1365, '127.0.0.1:6379'), array_fill(1364, 1365, '127.0.0.1:6380'), array_fill(2729, 1366, '127.0.0.1:6381') ); $cluster->buildSlotsMap(); $this->assertSame($expectedMap, $cluster->getSlotsMap()); } /** * @group disconnected */ public function testReturnsCorrectConnectionUsingSlotID() { $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379'); $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380'); $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381'); $cluster = new RedisCluster(); $cluster->add($connection1); $cluster->add($connection2); $cluster->add($connection3); $this->assertSame($connection1, $cluster->getConnectionBySlot(0)); $this->assertSame($connection2, $cluster->getConnectionBySlot(1365)); $this->assertSame($connection3, $cluster->getConnectionBySlot(2730)); $cluster->setSlots(1365, 3000, '127.0.0.1:6380'); $this->assertSame($connection2, $cluster->getConnectionBySlot(2730)); } /** * @group disconnected */ public function testReturnsCorrectConnectionUsingCommandInstance() { $profile = ServerProfile::getDefault(); $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379'); $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380'); $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381'); $cluster = new RedisCluster(); $cluster->add($connection1); $cluster->add($connection2); $cluster->add($connection3); $set = $profile->createCommand('set', array('node:1024', 'foobar')); $get = $profile->createCommand('get', array('node:1024')); $this->assertSame($connection1, $cluster->getConnection($set)); $this->assertSame($connection1, $cluster->getConnection($get)); $set = $profile->createCommand('set', array('node:1048', 'foobar')); $get = $profile->createCommand('get', array('node:1048')); $this->assertSame($connection2, $cluster->getConnection($set)); $this->assertSame($connection2, $cluster->getConnection($get)); $set = $profile->createCommand('set', array('node:1082', 'foobar')); $get = $profile->createCommand('get', array('node:1082')); $this->assertSame($connection3, $cluster->getConnection($set)); $this->assertSame($connection3, $cluster->getConnection($get)); } /** * @group disconnected */ public function testWritesCommandToCorrectConnection() { $command = ServerProfile::getDefault()->createCommand('get', array('node:1024')); $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379'); $connection1->expects($this->once())->method('writeCommand')->with($command); $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380'); $connection2->expects($this->never())->method('writeCommand'); $cluster = new RedisCluster(); $cluster->add($connection1); $cluster->add($connection2); $cluster->writeCommand($command); } /** * @group disconnected */ public function testReadsCommandFromCorrectConnection() { $command = ServerProfile::getDefault()->createCommand('get', array('node:1048')); $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379'); $connection1->expects($this->never())->method('readResponse'); $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380'); $connection2->expects($this->once())->method('readResponse')->with($command); $cluster = new RedisCluster(); $cluster->add($connection1); $cluster->add($connection2); $cluster->readResponse($command); } /** * @group disconnected */ public function testDoesNotSupportKeyTags() { $profile = ServerProfile::getDefault(); $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379'); $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380'); $cluster = new RedisCluster(); $cluster->add($connection1); $cluster->add($connection2); $set = $profile->createCommand('set', array('{node:1024}:foo', 'foobar')); $get = $profile->createCommand('get', array('{node:1024}:foo')); $this->assertSame($connection1, $cluster->getConnection($set)); $this->assertSame($connection1, $cluster->getConnection($get)); $set = $profile->createCommand('set', array('{node:1024}:bar', 'foobar')); $get = $profile->createCommand('get', array('{node:1024}:bar')); $this->assertSame($connection2, $cluster->getConnection($set)); $this->assertSame($connection2, $cluster->getConnection($get)); } /** * @group disconnected */ public function testAskResponseWithConnectionInPool() { $askResponse = new ResponseError('ASK 373 127.0.0.1:6380'); $command = ServerProfile::getDefault()->createCommand('get', array('node:1024')); $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379'); $connection1->expects($this->exactly(2)) ->method('executeCommand') ->with($command) ->will($this->onConsecutiveCalls($askResponse, 'foobar')); $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380'); $connection2->expects($this->exactly(1)) ->method('executeCommand') ->with($command) ->will($this->returnValue('foobar')); $factory = $this->getMock('Predis\Connection\ConnectionFactory'); $factory->expects($this->never())->method('create'); $cluster = new RedisCluster($factory); $cluster->add($connection1); $cluster->add($connection2); $this->assertSame('foobar', $cluster->executeCommand($command)); $this->assertSame('foobar', $cluster->executeCommand($command)); $this->assertSame(2, count($cluster)); } /** * @group disconnected */ public function testAskResponseWithConnectionNotInPool() { $askResponse = new ResponseError('ASK 373 127.0.0.1:6381'); $command = ServerProfile::getDefault()->createCommand('get', array('node:1024')); $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379'); $connection1->expects($this->exactly(2)) ->method('executeCommand') ->with($command) ->will($this->onConsecutiveCalls($askResponse, 'foobar')); $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380'); $connection2->expects($this->never()) ->method('executeCommand'); $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381'); $connection3->expects($this->once()) ->method('executeCommand') ->with($command) ->will($this->returnValue('foobar')); $factory = $this->getMock('Predis\Connection\ConnectionFactory'); $factory->expects($this->once()) ->method('create') ->with(array('host' => '127.0.0.1', 'port' => '6381')) ->will($this->returnValue($connection3)); $cluster = new RedisCluster($factory); $cluster->add($connection1); $cluster->add($connection2); $this->assertSame('foobar', $cluster->executeCommand($command)); $this->assertSame('foobar', $cluster->executeCommand($command)); $this->assertSame(2, count($cluster)); } /** * @group disconnected */ public function testMovedResponseWithConnectionInPool() { $movedResponse = new ResponseError('MOVED 373 127.0.0.1:6380'); $command = ServerProfile::getDefault()->createCommand('get', array('node:1024')); $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379'); $connection1->expects($this->exactly(1)) ->method('executeCommand') ->with($command) ->will($this->returnValue($movedResponse)); $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380'); $connection2->expects($this->exactly(2)) ->method('executeCommand') ->with($command) ->will($this->onConsecutiveCalls('foobar', 'foobar')); $factory = $this->getMock('Predis\Connection\ConnectionFactory'); $factory->expects($this->never())->method('create'); $cluster = new RedisCluster($factory); $cluster->add($connection1); $cluster->add($connection2); $this->assertSame('foobar', $cluster->executeCommand($command)); $this->assertSame('foobar', $cluster->executeCommand($command)); $this->assertSame(2, count($cluster)); } /** * @group disconnected */ public function testMovedResponseWithConnectionNotInPool() { $movedResponse = new ResponseError('MOVED 373 127.0.0.1:6381'); $command = ServerProfile::getDefault()->createCommand('get', array('node:1024')); $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379'); $connection1->expects($this->once()) ->method('executeCommand') ->with($command) ->will($this->returnValue($movedResponse)); $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380'); $connection2->expects($this->never()) ->method('executeCommand'); $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381'); $connection3->expects($this->exactly(2)) ->method('executeCommand') ->with($command) ->will($this->onConsecutiveCalls('foobar', 'foobar')); $factory = $this->getMock('Predis\Connection\ConnectionFactory'); $factory->expects($this->once()) ->method('create') ->with(array('host' => '127.0.0.1', 'port' => '6381')) ->will($this->returnValue($connection3)); $cluster = new RedisCluster($factory); $cluster->add($connection1); $cluster->add($connection2); $this->assertSame('foobar', $cluster->executeCommand($command)); $this->assertSame('foobar', $cluster->executeCommand($command)); $this->assertSame(3, count($cluster)); } /** * @group disconnected * @expectedException Predis\NotSupportedException * @expectedExceptionMessage Cannot use PING with redis-cluster */ public function testThrowsExceptionOnNonSupportedCommand() { $ping = ServerProfile::getDefault()->createCommand('ping'); $cluster = new RedisCluster(); $cluster->add($this->getMockConnection('tcp://127.0.0.1:6379')); $cluster->getConnection($ping); } /** * @group disconnected */ public function testCanBeSerialized() { $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379?slots=0-1364'); $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380?slots=1365-2729'); $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=2730-4095'); $cluster = new RedisCluster(); $cluster->add($connection1); $cluster->add($connection2); $cluster->add($connection3); $cluster->buildSlotsMap(); $unserialized = unserialize(serialize($cluster)); $this->assertEquals($cluster, $unserialized); } // ******************************************************************** // // ---- HELPER METHODS ------------------------------------------------ // // ******************************************************************** // /** * Returns a base mocked connection from Predis\Connection\SingleConnectionInterface. * * @param mixed $parameters Optional parameters. * @return mixed */ protected function getMockConnection($parameters = null) { $connection = $this->getMock('Predis\Connection\SingleConnectionInterface'); if ($parameters) { $parameters = new ConnectionParameters($parameters); $hash = "{$parameters->host}:{$parameters->port}"; $connection->expects($this->any()) ->method('getParameters') ->will($this->returnValue($parameters)); $connection->expects($this->any()) ->method('__toString') ->will($this->returnValue($hash)); } return $connection; } }