Files
predis/tests/Predis/Connection/Aggregate/RedisClusterTest.php
T
Daniele Alessandri 648be824d3 Fix fetching slots map from unreachable nodes.
When various nodes in the configuration are unreachable while trying
to send a command, we should attempt to contact a reachable node to
fetch an updated slots map up to $retryLimit times or until there are
no more servers in the pool before giving up.

It is possible that the slots map fetched from Redis contains stale
data and points to a dead server, this happens when the nodes still
have to agree that a master server is down before promoting a slave
to the role of master. In this case no further attempts to execute
the command are performed and an exception is thrown.

This still needs some more testing and will delay v1.0.4 a few days
past its scheduled release.
2016-05-27 14:28:12 +02:00

1129 lines
42 KiB
PHP

<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* 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\Profile;
use Predis\Response;
use PredisTestCase;
/**
*
*/
class RedisClusterTest extends PredisTestCase
{
/**
* @group disconnected
*/
public function testAcceptsCustomConnectionFactory()
{
$factory = $this->getMock('Predis\Connection\FactoryInterface');
$cluster = new RedisCluster($factory);
$this->assertSame($factory, $cluster->getConnectionFactory());
}
/**
* @group disconnected
*/
public function testUsesRedisClusterStrategyByDefault()
{
$cluster = new RedisCluster(new Connection\Factory());
$this->assertInstanceOf('Predis\Cluster\RedisStrategy', $cluster->getClusterStrategy());
}
/**
* @group disconnected
*/
public function testAcceptsCustomClusterStrategy()
{
$strategy = $this->getMock('Predis\Cluster\StrategyInterface');
$cluster = new RedisCluster(new Connection\Factory(), $strategy);
$this->assertSame($strategy, $cluster->getClusterStrategy());
}
/**
* @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(new Connection\Factory());
$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(new Connection\Factory());
$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(new Connection\Factory());
$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(new Connection\Factory());
$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 testConnectPicksRandomConnection()
{
$connect1 = false;
$connect2 = false;
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
$connection1->expects($this->any())
->method('connect')
->will($this->returnCallback(function () use (&$connect1) {
$connect1 = true;
}));
$connection1->expects($this->any())
->method('isConnected')
->will($this->returnCallback(function () use (&$connect1) {
return $connect1;
}));
$connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
$connection2->expects($this->any())
->method('connect')
->will($this->returnCallback(function () use (&$connect2) {
$connect2 = true;
}));
$connection2->expects($this->any())
->method('isConnected')
->will($this->returnCallback(function () use (&$connect2) {
return $connect2;
}));
$cluster = new RedisCluster(new Connection\Factory());
$cluster->add($connection1);
$cluster->add($connection2);
$cluster->connect();
$this->assertTrue($cluster->isConnected());
if ($connect1) {
$this->assertTrue($connect1);
$this->assertFalse($connect2);
} else {
$this->assertFalse($connect1);
$this->assertTrue($connect2);
}
}
/**
* @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(new Connection\Factory());
$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(new Connection\Factory());
$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(new Connection\Factory());
$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(new Connection\Factory());
$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(new Connection\Factory());
$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(new Connection\Factory());
$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(new Connection\Factory());
$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 testCanAssignConnectionsToRangeOfSlotsFromParameters()
{
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6379?slots=0-5460');
$connection2 = $this->getMockConnection('tcp://127.0.0.1:6380?slots=5461-10921');
$connection3 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=10922-16383');
$cluster = new RedisCluster(new Connection\Factory());
$cluster->add($connection1);
$cluster->add($connection2);
$cluster->add($connection3);
$cluster->buildSlotsMap();
$expectedMap = array_merge(
array_fill(0, 5461, '127.0.0.1:6379'),
array_fill(5460, 5461, '127.0.0.1:6380'),
array_fill(10921, 5462, '127.0.0.1:6381')
);
$actualMap = $cluster->getSlotsMap();
ksort($actualMap);
$this->assertSame($expectedMap, $actualMap);
}
/**
* @group disconnected
*/
public function testCanAssignConnectionsToSingleSlotOrRangesOfSlotsFromParameters()
{
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6379?slots=0-5460,5500-5600,11000');
$connection2 = $this->getMockConnection('tcp://127.0.0.1:6380?slots=5461-5499,5600-10921');
$connection3 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=10922-10999,11001-16383');
$cluster = new RedisCluster(new Connection\Factory());
$cluster->add($connection1);
$cluster->add($connection2);
$cluster->add($connection3);
$cluster->buildSlotsMap();
$expectedMap = array_merge(
array_fill(0, 5461, '127.0.0.1:6379'),
array_fill(5460, 39, '127.0.0.1:6380'),
array_fill(5499, 101, '127.0.0.1:6379'),
array_fill(5599, 5321, '127.0.0.1:6380'),
array_fill(10921, 78, '127.0.0.1:6381'),
array_fill(11000, 1, '127.0.0.1:6379'),
array_fill(11000, 5383, '127.0.0.1:6381')
);
$actualMap = $cluster->getSlotsMap();
ksort($actualMap);
$this->assertSame($expectedMap, $actualMap);
}
/**
* @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(new Connection\Factory());
$cluster->add($connection1);
$cluster->add($connection2);
$cluster->add($connection3);
$this->assertSame($connection1, $cluster->getConnectionBySlot(0));
$this->assertSame($connection2, $cluster->getConnectionBySlot(5461));
$this->assertSame($connection3, $cluster->getConnectionBySlot(10922));
$cluster->setSlots(5461, 7096, '127.0.0.1:6380');
$this->assertSame($connection2, $cluster->getConnectionBySlot(5461));
}
/**
* @group disconnected
*/
public function testReturnsCorrectConnectionUsingCommandInstance()
{
$profile = Profile\Factory::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(new Connection\Factory());
$cluster->add($connection1);
$cluster->add($connection2);
$cluster->add($connection3);
$set = $profile->createCommand('set', array('node:1001', 'foobar'));
$get = $profile->createCommand('get', array('node:1001'));
$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 = Profile\Factory::getDefault()->createCommand('get', array('node:1001'));
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
$connection1->expects($this->once())->method('writeRequest')->with($command);
$connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
$connection2->expects($this->never())->method('writeRequest');
$cluster = new RedisCluster(new Connection\Factory());
$cluster->useClusterSlots(false);
$cluster->add($connection1);
$cluster->add($connection2);
$cluster->writeRequest($command);
}
/**
* @group disconnected
*/
public function testReadsCommandFromCorrectConnection()
{
$command = Profile\Factory::getDefault()->createCommand('get', array('node:1050'));
$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(new Connection\Factory());
$cluster->useClusterSlots(false);
$cluster->add($connection1);
$cluster->add($connection2);
$cluster->readResponse($command);
}
/**
* @group disconnected
*/
public function testRetriesExecutingCommandOnConnectionFailureOnlyAfterFetchingNewSlotsMap()
{
$slotsmap = array(
array(0, 5460, array('127.0.0.1', 9381), array()),
array(5461, 10921, array('127.0.0.1', 6382), array()),
array(10922, 16383, array('127.0.0.1', 6383), array()),
);
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=0-5460');
$connection1->expects($this->once())
->method('executeCommand')
->with($this->isRedisCommand(
'GET', array('node:1001')
))
->will($this->throwException(
new Connection\ConnectionException($connection1, 'Unknown connection error [127.0.0.1:6381]')
));
$connection2 = $this->getMockConnection('tcp://127.0.0.1:6382?slots=5461-10921');
$connection2->expects($this->any())
->method('executeCommand')
->with($this->isRedisCommand(
'CLUSTER', array('SLOTS')
))
->will($this->returnValue($slotsmap));
$connection3 = $this->getMockConnection('tcp://127.0.0.1:6383?slots=10922-16383');
$connection3->expects($this->any())
->method('executeCommand')
->with($this->isRedisCommand(
'CLUSTER', array('SLOTS')
))
->will($this->returnValue($slotsmap));
$connection4 = $this->getMockConnection('tcp://127.0.0.1:9381');
$connection4->expects($this->at(0))
->method('executeCommand')
->with($this->isRedisCommand(
'GET', array('node:1001')
))
->will($this->returnValue('value:1001'));
$connection4->expects($this->at(1))
->method('executeCommand')
->with($this->isRedisCommand(
'GET', array('node:5001')
))
->will($this->returnValue('value:5001'));
$factory = $this->getMock('Predis\Connection\FactoryInterface');
$factory->expects($this->once())
->method('create')
->with(array(
'host' => '127.0.0.1',
'port' => '9381',
))
->will($this->returnValue($connection4));
$cluster = new RedisCluster($factory);
$cluster->add($connection1);
$cluster->add($connection2);
$cluster->add($connection3);
$this->assertSame('value:1001', $cluster->executeCommand(
Command\RawCommand::create('get', 'node:1001')
));
$this->assertSame('value:5001', $cluster->executeCommand(
Command\RawCommand::create('get', 'node:5001')
));
}
/**
* @group disconnected
*/
public function testRetriesExecutingCommandOnConnectionFailureButDoNotAskSlotsMapWhenDisabled()
{
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=0-5500');
$connection1->expects($this->once())
->method('executeCommand')
->with($this->isRedisCommand(
'GET', array('node:1001')
))
->will($this->throwException(
new Connection\ConnectionException($connection1, 'Unknown connection error [127.0.0.1:6381]')
));
$connection2 = $this->getMockConnection('tcp://127.0.0.1:6382?slots=5501-11000');
$connection2->expects($this->once())
->method('executeCommand')
->with($this->isRedisCommand(
'GET', array('node:1001')
))
->will($this->returnValue(
new Response\Error('MOVED 1970 127.0.0.1:9381')
));
$connection3 = $this->getMockConnection('tcp://127.0.0.1:6383?slots=11101-16383');
$connection3->expects($this->never())
->method('executeCommand');
$connection4 = $this->getMockConnection('tcp://127.0.0.1:9381');
$connection4->expects($this->once())
->method('executeCommand')
->with($this->isRedisCommand(
'GET', array('node:1001')
))
->will($this->returnValue('value:1001'));
$factory = $this->getMock('Predis\Connection\FactoryInterface');
$factory->expects($this->once())
->method('create')
->with(array(
'host' => '127.0.0.1',
'port' => '9381',
))
->will($this->returnValue($connection4));
// TODO: I'm not sure about mocking a protected method, but it'll do for now
$cluster = $this->getMock('Predis\Connection\Aggregate\RedisCluster', array('getRandomConnection'), array($factory));
$cluster->expects($this->never())
->method('getRandomConnection');
$cluster->useClusterSlots(false);
$cluster->add($connection1);
$cluster->add($connection2);
$cluster->add($connection3);
$this->assertSame('value:1001', $cluster->executeCommand(
Command\RawCommand::create('get', 'node:1001')
));
}
/**
* @group disconnected
* @expectedException \Predis\ClientException
* @expectedExceptionMessage No connections available in the pool
*/
public function testThrowsClientExceptionWhenExecutingCommandWithEmptyPool()
{
$factory = $this->getMock('Predis\Connection\FactoryInterface');
$factory->expects($this->never())->method('create');
$cluster = new RedisCluster($factory);
$cluster->executeCommand(Command\RawCommand::create('get', 'node:1001'));
}
/**
* @group disconnected
*/
public function testAskSlotsMapReturnEmptyArrayOnEmptyConnectionsPool()
{
$factory = $this->getMock('Predis\Connection\FactoryInterface');
$factory->expects($this->never())->method('create');
$cluster = new RedisCluster($factory);
$this->assertEmpty($cluster->askSlotsMap());
}
/**
* @group disconnected
*/
public function testAskSlotsMapRetriesOnDifferentNodeOnConnectionFailure()
{
$slotsmap = array(
array(0, 5460, array('127.0.0.1', 9381), array()),
array(5461, 10921, array('127.0.0.1', 6382), array()),
array(10922, 16383, array('127.0.0.1', 6383), array()),
);
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=0-5460');
$connection1->expects($this->once())
->method('executeCommand')
->with($this->isRedisCommand(
'CLUSTER', array('SLOTS')
))
->will($this->throwException(
new Connection\ConnectionException($connection1, 'Unknown connection error [127.0.0.1:6381]')
));
$connection2 = $this->getMockConnection('tcp://127.0.0.1:6382?slots=5461-10921');
$connection2->expects($this->once())
->method('executeCommand')
->with($this->isRedisCommand(
'CLUSTER', array('SLOTS')
))
->will($this->throwException(
new Connection\ConnectionException($connection2, 'Unknown connection error [127.0.0.1:6383]')
));
$connection3 = $this->getMockConnection('tcp://127.0.0.1:6383?slots=10922-16383');
$connection3->expects($this->once())
->method('executeCommand')
->with($this->isRedisCommand(
'CLUSTER', array('SLOTS')
))
->will($this->returnValue($slotsmap));
$factory = $this->getMock('Predis\Connection\FactoryInterface');
$factory->expects($this->never())->method('create');
// TODO: I'm not sure about mocking a protected method, but it'll do for now
$cluster = $this->getMock('Predis\Connection\Aggregate\RedisCluster', array('getRandomConnection'), array($factory));
$cluster->expects($this->exactly(3))
->method('getRandomConnection')
->will($this->onConsecutiveCalls($connection1, $connection2, $connection3));
$cluster->add($connection1);
$cluster->add($connection2);
$cluster->add($connection3);
$this->assertCount(16384, $cluster->askSlotsMap());
}
/**
* @group disconnected
* @expectedException \Predis\Connection\ConnectionException
* @expectedExceptionMessage Unknown connection error [127.0.0.1:6382]
*/
public function testAskSlotsMapHonorsRetryLimitOnMultipleConnectionFailures()
{
$slotsmap = array(
array(0, 5460, array('127.0.0.1', 9381), array()),
array(5461, 10921, array('127.0.0.1', 6382), array()),
array(10922, 16383, array('127.0.0.1', 6383), array()),
);
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=0-5460');
$connection1->expects($this->any())
->method('executeCommand')
->with($this->isRedisCommand(
'CLUSTER', array('SLOTS')
))
->will($this->throwException(
new Connection\ConnectionException($connection1, 'Unknown connection error [127.0.0.1:6381]')
));
$connection2 = $this->getMockConnection('tcp://127.0.0.1:6382?slots=5461-10921');
$connection2->expects($this->any())
->method('executeCommand')
->with($this->isRedisCommand(
'CLUSTER', array('SLOTS')
))
->will($this->throwException(
new Connection\ConnectionException($connection2, 'Unknown connection error [127.0.0.1:6382]')
));
$connection3 = $this->getMockConnection('tcp://127.0.0.1:6383?slots=10922-16383');
$connection3->expects($this->never())
->method('executeCommand');
$factory = $this->getMock('Predis\Connection\FactoryInterface');
$factory->expects($this->never())->method('create');
// TODO: I'm not sure about mocking a protected method, but it'll do for now
$cluster = $this->getMock('Predis\Connection\Aggregate\RedisCluster', array('getRandomConnection'), array($factory));
$cluster->expects($this->exactly(2))
->method('getRandomConnection')
->will($this->onConsecutiveCalls($connection1, $connection2));
$cluster->add($connection1);
$cluster->add($connection2);
$cluster->add($connection3);
$cluster->setRetryLimit(1);
$cluster->askSlotsMap();
}
/**
* @group disconnected
*/
public function testSupportsKeyHashTags()
{
$profile = Profile\Factory::getDefault();
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
$connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
$cluster = new RedisCluster(new Connection\Factory());
$cluster->add($connection1);
$cluster->add($connection2);
$set = $profile->createCommand('set', array('{node:1001}:foo', 'foobar'));
$get = $profile->createCommand('get', array('{node:1001}:foo'));
$this->assertSame($connection1, $cluster->getConnection($set));
$this->assertSame($connection1, $cluster->getConnection($get));
$set = $profile->createCommand('set', array('{node:1001}:bar', 'foobar'));
$get = $profile->createCommand('get', array('{node:1001}:bar'));
$this->assertSame($connection1, $cluster->getConnection($set));
$this->assertSame($connection1, $cluster->getConnection($get));
}
/**
* @group disconnected
*/
public function testAskResponseWithConnectionInPool()
{
$askResponse = new Response\Error('ASK 1970 127.0.0.1:6380');
$command = Profile\Factory::getDefault()->createCommand('get', array('node:1001'));
$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->at(2))
->method('executeCommand')
->with($this->isRedisCommand('ASKING'));
$connection2->expects($this->at(3))
->method('executeCommand')
->with($command)
->will($this->returnValue('foobar'));
$factory = $this->getMock('Predis\Connection\Factory');
$factory->expects($this->never())->method('create');
$cluster = new RedisCluster($factory);
$cluster->useClusterSlots(false);
$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 Response\Error('ASK 1970 127.0.0.1:6381');
$command = Profile\Factory::getDefault()->createCommand('get', array('node:1001'));
$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->at(0))
->method('executeCommand')
->with($this->isRedisCommand('ASKING'));
$connection3->expects($this->at(1))
->method('executeCommand')
->with($command)
->will($this->returnValue('foobar'));
$factory = $this->getMock('Predis\Connection\Factory');
$factory->expects($this->once())
->method('create')
->with(array('host' => '127.0.0.1', 'port' => '6381'))
->will($this->returnValue($connection3));
$cluster = new RedisCluster($factory);
$cluster->useClusterSlots(false);
$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 Response\Error('MOVED 1970 127.0.0.1:6380');
$command = Profile\Factory::getDefault()->createCommand('get', array('node:1001'));
$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\Factory');
$factory->expects($this->never())->method('create');
$cluster = new RedisCluster($factory);
$cluster->useClusterSlots(false);
$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 Response\Error('MOVED 1970 127.0.0.1:6381');
$command = Profile\Factory::getDefault()->createCommand('get', array('node:1001'));
$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\Factory');
$factory->expects($this->once())
->method('create')
->with(array('host' => '127.0.0.1', 'port' => '6381'))
->will($this->returnValue($connection3));
$cluster = new RedisCluster($factory);
$cluster->useClusterSlots(false);
$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
*/
public function testParseIPv6AddresseAndPortPairInRedirectionPayload()
{
$movedResponse = new Response\Error('MOVED 1970 2001:db8:0:f101::2:6379');
$command = Profile\Factory::getDefault()->createCommand('get', array('node:1001'));
$connection1 = $this->getMockConnection('tcp://[2001:db8:0:f101::1]:6379');
$connection1->expects($this->once())
->method('executeCommand')
->with($command)
->will($this->returnValue($movedResponse));
$connection2 = $this->getMockConnection('tcp://[2001:db8:0:f101::2]:6379');
$connection2->expects($this->once())
->method('executeCommand')
->with($command)
->will($this->returnValue('foobar'));
$factory = $this->getMock('Predis\Connection\Factory');
$factory->expects($this->once())
->method('create')
->with(array('host' => '2001:db8:0:f101::2', 'port' => '6379'))
->will($this->returnValue($connection2));
$cluster = new RedisCluster($factory);
$cluster->useClusterSlots(false);
$cluster->add($connection1);
$cluster->executeCommand($command);
}
/**
* @group disconnected
*/
public function testFetchSlotsMapFromClusterWithClusterSlotsCommand()
{
$response = array(
array(12288, 13311, array('10.1.0.51', 6387), array('10.1.0.52', 6387)),
array(3072, 4095, array('10.1.0.52', 6392), array('10.1.0.51', 6392)),
array(6144, 7167, array('', 6384), array('10.1.0.52', 6384)),
array(14336, 15359, array('10.1.0.51', 6388), array('10.1.0.52', 6388)),
array(15360, 16383, array('10.1.0.52', 6398), array('10.1.0.51', 6398)),
array(1024, 2047, array('10.1.0.52', 6391), array('10.1.0.51', 6391)),
array(11264, 12287, array('10.1.0.52', 6396), array('10.1.0.51', 6396)),
array(5120, 6143, array('10.1.0.52', 6393), array('10.1.0.51', 6393)),
array(0, 1023, array('10.1.0.51', 6381), array('10.1.0.52', 6381)),
array(13312, 14335, array('10.1.0.52', 6397), array('10.1.0.51', 6397)),
array(4096, 5119, array('10.1.0.51', 6383), array('10.1.0.52', 6383)),
array(9216, 10239, array('10.1.0.52', 6395), array('10.1.0.51', 6395)),
array(8192, 9215, array('10.1.0.51', 6385), array('10.1.0.52', 6385)),
array(10240, 11263, array('10.1.0.51', 6386), array('10.1.0.52', 6386)),
array(2048, 3071, array('10.1.0.51', 6382), array('10.1.0.52', 6382)),
array(7168, 8191, array('10.1.0.52', 6394), array('10.1.0.51', 6394)),
);
$command = Command\RawCommand::create('CLUSTER', 'SLOTS');
$connection1 = $this->getMockConnection('tcp://10.1.0.51:6384');
$connection1->expects($this->once())
->method('executeCommand')
->with($command)
->will($this->returnValue($response));
$factory = $this->getMock('Predis\Connection\Factory');
$cluster = new RedisCluster($factory);
$cluster->add($connection1);
$cluster->askSlotsMap();
$this->assertSame($cluster->getConnectionBySlot('6144'), $connection1);
}
/**
* @group disconnected
*/
public function testAskSlotsMapToRedisClusterOnMovedResponseByDefault()
{
$cmdGET = Command\RawCommand::create('GET', 'node:1001');
$rspMOVED = new Response\Error('MOVED 1970 127.0.0.1:6380');
$rspSlotsArray = array(
array(0, 8191, array('127.0.0.1', 6379)),
array(8192, 16383, array('127.0.0.1', 6380)),
);
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
$connection1->expects($this->once())
->method('executeCommand')
->with($cmdGET)
->will($this->returnValue($rspMOVED));
$connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
$connection2->expects($this->at(0))
->method('executeCommand')
->with($this->isRedisCommand('CLUSTER', array('SLOTS')))
->will($this->returnValue($rspSlotsArray));
$connection2->expects($this->at(2))
->method('executeCommand')
->with($cmdGET)
->will($this->returnValue('foobar'));
$factory = $this->getMock('Predis\Connection\Factory');
$factory->expects($this->once())
->method('create')
->with(array('host' => '127.0.0.1', 'port' => '6380'))
->will($this->returnValue($connection2));
$cluster = new RedisCluster($factory);
$cluster->add($connection1);
$this->assertSame('foobar', $cluster->executeCommand($cmdGET));
$this->assertSame(2, count($cluster));
}
/**
* @group disconnected
* @expectedException \Predis\NotSupportedException
* @expectedExceptionMessage Cannot use 'PING' with redis-cluster.
*/
public function testThrowsExceptionOnNonSupportedCommand()
{
$ping = Profile\Factory::getDefault()->createCommand('ping');
$cluster = new RedisCluster(new Connection\Factory());
$cluster->add($this->getMockConnection('tcp://127.0.0.1:6379'));
$cluster->getConnection($ping);
}
/**
* @medium
* @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(new Connection\Factory());
$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\NodeConnectionInterface.
*
* @param mixed $parameters Optional parameters.
*
* @return mixed
*/
protected function getMockConnection($parameters = null)
{
$connection = $this->getMock('Predis\Connection\NodeConnectionInterface');
if ($parameters) {
$parameters = Connection\Parameters::create($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;
}
}