/*
* Copyright 2015-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.connection.jedis;
import static org.hamcrest.core.Is.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.redis.connection.ClusterTestVariables.*;
import static org.springframework.data.redis.test.util.MockitoUtils.*;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.redis.ClusterStateFailureException;
import org.springframework.data.redis.connection.ClusterInfo;
import org.springframework.data.redis.connection.RedisClusterCommands.AddSlots;
import org.springframework.data.redis.connection.RedisClusterNode;
import org.springframework.data.redis.connection.jedis.JedisClusterConnection.JedisClusterTopologyProvider;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.exceptions.JedisConnectionException;
/**
* @author Christoph Strobl
* @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.Silent.class)
public class JedisClusterConnectionUnitTests {
private static final String CLUSTER_NODES_RESPONSE = "" //
+ MASTER_NODE_1_ID + " " + CLUSTER_HOST + ":" + MASTER_NODE_1_PORT
+ " myself,master - 0 0 1 connected 0-5460"
+ "\n" + MASTER_NODE_2_ID + " " + CLUSTER_HOST + ":"
+ MASTER_NODE_2_PORT
+ " master - 0 1427718161587 2 connected 5461-10922" + "\n"
+ MASTER_NODE_2_ID
+ " " + CLUSTER_HOST + ":" + MASTER_NODE_3_PORT + " master - 0 1427718161587 3 connected 10923-16383";
static final String CLUSTER_INFO_RESPONSE = "cluster_state:ok" + "\n"
+ "cluster_slots_assigned:16384" + "\n" + "cluster_slots_ok:16384"
+ "\n" + "cluster_slots_pfail:0" + "\n"
+ "cluster_slots_fail:0" + "\n" + "cluster_known_nodes:4"
+ "\n" + "cluster_size:3" + "\n"
+ "cluster_current_epoch:30" + "\n" + "cluster_my_epoch:2"
+ "\n" + "cluster_stats_messages_sent:2560260"
+ "\n" + "cluster_stats_messages_received:2560086";
JedisClusterConnection connection;
@Mock JedisCluster clusterMock;
@Mock JedisPool node1PoolMock;
@Mock JedisPool node2PoolMock;
@Mock JedisPool node3PoolMock;
@Mock Jedis con1Mock;
@Mock Jedis con2Mock;
@Mock Jedis con3Mock;
public @Rule ExpectedException expectedException = ExpectedException.none();
@Before
public void setUp() {
Map<String, JedisPool> nodes = new LinkedHashMap<String, JedisPool>(3);
nodes.put(CLUSTER_HOST + ":" + MASTER_NODE_1_PORT, node1PoolMock);
nodes.put(CLUSTER_HOST + ":" + MASTER_NODE_2_PORT, node2PoolMock);
nodes.put(CLUSTER_HOST + ":" + MASTER_NODE_3_PORT, node3PoolMock);
when(clusterMock.getClusterNodes()).thenReturn(nodes);
when(node1PoolMock.getResource()).thenReturn(con1Mock);
when(node2PoolMock.getResource()).thenReturn(con2Mock);
when(node3PoolMock.getResource()).thenReturn(con3Mock);
when(con1Mock.clusterNodes()).thenReturn(CLUSTER_NODES_RESPONSE);
connection = new JedisClusterConnection(clusterMock);
}
@Test // DATAREDIS-315
public void thowsExceptionWhenClusterCommandExecturorIsNull() {
expectedException.expect(IllegalArgumentException.class);
new JedisClusterConnection(clusterMock, null);
}
@Test // DATAREDIS-315
public void clusterMeetShouldSendCommandsToExistingNodesCorrectly() {
connection.clusterMeet(UNKNOWN_CLUSTER_NODE);
verify(con1Mock, times(1)).clusterMeet(UNKNOWN_CLUSTER_NODE.getHost(), UNKNOWN_CLUSTER_NODE.getPort());
verify(con2Mock, times(1)).clusterMeet(UNKNOWN_CLUSTER_NODE.getHost(), UNKNOWN_CLUSTER_NODE.getPort());
verify(con2Mock, times(1)).clusterMeet(UNKNOWN_CLUSTER_NODE.getHost(), UNKNOWN_CLUSTER_NODE.getPort());
}
@Test // DATAREDIS-315
public void clusterMeetShouldThrowExceptionWhenNodeIsNull() {
expectedException.expect(IllegalArgumentException.class);
connection.clusterMeet(null);
}
@Test // DATAREDIS-315
public void clusterForgetShouldSendCommandsToRemainingNodesCorrectly() {
connection.clusterForget(CLUSTER_NODE_2);
verify(con1Mock, times(1)).clusterForget(CLUSTER_NODE_2.getId());
verifyZeroInteractions(con2Mock);
verify(con3Mock, times(1)).clusterForget(CLUSTER_NODE_2.getId());
}
@Test // DATAREDIS-315
public void clusterReplicateShouldSendCommandsCorrectly() {
connection.clusterReplicate(CLUSTER_NODE_1, CLUSTER_NODE_2);
verify(con2Mock, times(1)).clusterReplicate(CLUSTER_NODE_1.getId());
verify(con1Mock, times(1)).clusterNodes();
verifyZeroInteractions(con1Mock);
}
@Test // DATAREDIS-315
public void closeShouldNotCloseUnderlyingClusterPool() throws IOException {
connection.close();
verify(clusterMock, never()).close();
}
@Test // DATAREDIS-315
public void isClosedShouldReturnConnectionStateCorrectly() {
assertThat(connection.isClosed(), is(false));
connection.close();
assertThat(connection.isClosed(), is(true));
}
@Test // DATAREDIS-315
public void clusterInfoShouldBeReturnedCorrectly() {
when(con1Mock.clusterInfo()).thenReturn(CLUSTER_INFO_RESPONSE);
when(con2Mock.clusterInfo()).thenReturn(CLUSTER_INFO_RESPONSE);
when(con3Mock.clusterInfo()).thenReturn(CLUSTER_INFO_RESPONSE);
ClusterInfo p = connection.clusterGetClusterInfo();
assertThat(p.getSlotsAssigned(), is(16384L));
verifyInvocationsAcross("clusterInfo", times(1), con1Mock, con2Mock, con3Mock);
}
@Test // DATAREDIS-315
public void clusterSetSlotImportingShouldBeExecutedCorrectly() {
connection.clusterSetSlot(CLUSTER_NODE_1, 100, AddSlots.IMPORTING);
verify(con1Mock, times(1)).clusterSetSlotImporting(eq(100), eq(CLUSTER_NODE_1.getId()));
}
@Test // DATAREDIS-315
public void clusterSetSlotMigratingShouldBeExecutedCorrectly() {
connection.clusterSetSlot(CLUSTER_NODE_1, 100, AddSlots.MIGRATING);
verify(con1Mock, times(1)).clusterSetSlotMigrating(eq(100), eq(CLUSTER_NODE_1.getId()));
}
@Test // DATAREDIS-315
public void clusterSetSlotStableShouldBeExecutedCorrectly() {
connection.clusterSetSlot(CLUSTER_NODE_1, 100, AddSlots.STABLE);
verify(con1Mock, times(1)).clusterSetSlotStable(eq(100));
}
@Test // DATAREDIS-315
public void clusterSetSlotNodeShouldBeExecutedCorrectly() {
connection.clusterSetSlot(CLUSTER_NODE_1, 100, AddSlots.NODE);
verify(con1Mock, times(1)).clusterSetSlotNode(eq(100), eq(CLUSTER_NODE_1.getId()));
}
@Test // DATAREDIS-315
public void clusterSetSlotShouldBeExecutedOnTargetNodeWhenNodeIdNotSet() {
connection.clusterSetSlot(new RedisClusterNode(CLUSTER_HOST, MASTER_NODE_2_PORT), 100, AddSlots.IMPORTING);
verify(con2Mock, times(1)).clusterSetSlotImporting(eq(100), eq(CLUSTER_NODE_2.getId()));
}
@Test(expected = IllegalArgumentException.class) // DATAREDIS-315
public void clusterSetSlotShouldThrowExceptionWhenModeIsNull() {
connection.clusterSetSlot(CLUSTER_NODE_1, 100, null);
}
@Test // DATAREDIS-315
public void clusterDeleteSlotsShouldBeExecutedCorrectly() {
int[] slots = new int[] { 9000, 10000 };
connection.clusterDeleteSlots(CLUSTER_NODE_2, slots);
verify(con2Mock, times(1)).clusterDelSlots((int[]) anyVararg());
}
@Test(expected = IllegalArgumentException.class) // DATAREDIS-315
public void clusterDeleteSlotShouldThrowExceptionWhenNodeIsNull() {
connection.clusterDeleteSlots(null, new int[] { 1 });
}
@Test // DATAREDIS-315
public void timeShouldBeExecutedOnArbitraryNode() {
List<String> values = Arrays.asList("1449655759", "92217");
when(con1Mock.time()).thenReturn(values);
when(con2Mock.time()).thenReturn(values);
when(con3Mock.time()).thenReturn(values);
connection.time();
verifyInvocationsAcross("time", times(1), con1Mock, con2Mock, con3Mock);
}
@Test // DATAREDIS-315
public void timeShouldBeExecutedOnSingleNode() {
when(con2Mock.time()).thenReturn(Arrays.asList("1449655759", "92217"));
connection.time(CLUSTER_NODE_2);
verify(con2Mock, times(1)).time();
verify(con1Mock, times(1)).clusterNodes();
verifyZeroInteractions(con1Mock, con3Mock);
}
@Test // DATAREDIS-315
public void resetConfigStatsShouldBeExecutedOnAllNodes() {
connection.resetConfigStats();
verify(con1Mock, times(1)).configResetStat();
verify(con2Mock, times(1)).configResetStat();
verify(con3Mock, times(1)).configResetStat();
}
@Test // DATAREDIS-315
public void resetConfigStatsShouldBeExecutedOnSingleNodeCorrectly() {
connection.resetConfigStats(CLUSTER_NODE_2);
verify(con2Mock, times(1)).configResetStat();
verify(con1Mock, never()).configResetStat();
verify(con3Mock, never()).configResetStat();
}
@Test // DATAREDIS-315
public void clusterTopologyProviderShouldCollectErrorsWhenLoadingNodes() {
expectedException.expect(ClusterStateFailureException.class);
expectedException.expectMessage("127.0.0.1:7379 failed: o.O");
expectedException.expectMessage("127.0.0.1:7380 failed: o.1");
expectedException.expectMessage("127.0.0.1:7381 failed: o.2");
when(con1Mock.clusterNodes()).thenThrow(new JedisConnectionException("o.O"));
when(con2Mock.clusterNodes()).thenThrow(new JedisConnectionException("o.1"));
when(con3Mock.clusterNodes()).thenThrow(new JedisConnectionException("o.2"));
new JedisClusterTopologyProvider(clusterMock).getTopology();
}
}