SentineledConnectionProviderTest.java

package redis.clients.jedis;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Timeout;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisException;
import redis.clients.jedis.providers.SentineledConnectionProvider;
import redis.clients.jedis.util.ReflectionTestUtil;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasKey;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;

/**
 * @see JedisSentinelPoolTest
 */
@Tag("integration")
public class SentineledConnectionProviderTest {

  private static final String MASTER_NAME = "mymaster";

  protected static final HostAndPort sentinel1 = HostAndPorts.getSentinelServers().get(1);
  protected static final HostAndPort sentinel2 = HostAndPorts.getSentinelServers().get(3);

  private static final EndpointConfig primary = HostAndPorts.getRedisEndpoint("standalone2-primary");

  protected Set<HostAndPort> sentinels = new HashSet<>();

  @BeforeEach
  public void setUp() throws Exception {
    sentinels.clear();

    sentinels.add(sentinel1);
    sentinels.add(sentinel2);
  }

  @Test
  public void repeatedSentinelPoolInitialization() {
    for (int i = 0; i < 20; ++i) {

      try (SentineledConnectionProvider provider = new SentineledConnectionProvider(MASTER_NAME,
          DefaultJedisClientConfig.builder().timeoutMillis(1000).password("foobared").database(2).build(),
          sentinels, DefaultJedisClientConfig.builder().build())) {

        provider.getConnection().close();
      }
    }
  }

  /**
   * Ensure that getConnectionMap() does not cause connection leak. (#4323)
   */
  @Test
  @Timeout( value = 1)
  public void getConnectionMapDoesNotCauseConnectionLeak() {

    ConnectionPoolConfig config = new ConnectionPoolConfig();
    config.setMaxTotal(1);

    try (SentineledConnectionProvider sut = new SentineledConnectionProvider(MASTER_NAME,
            primary.getClientConfigBuilder().build(), config, sentinels,
            DefaultJedisClientConfig.builder().build())) {

      HostAndPort resolvedPrimary = sut.getCurrentMaster();
      ConnectionPool pool = ReflectionTestUtil.getField(sut,"pool");
      assertThat(pool.getNumActive(), equalTo(0));

      Map<?, ?> cm = sut.getConnectionMap();

      // exactly one entry for current primary
      // and no active connections
      assertThat(cm.size(), equalTo(1));
      assertThat(cm, hasKey(resolvedPrimary));
      assertThat(pool.getNumActive(), equalTo(0));
      // primary did not change
      assertThat(ReflectionTestUtil.getField(sut,"pool"), sameInstance(pool));
    }
  }

  /**
   * Ensure that getPrimaryNodesConnectionMap() does not cause connection leak. (#4323)
   */
  @Test
  @Timeout( value = 1)
  public void getPrimaryNodesConnectionMapDoesNotCauseConnectionLeak() {

    ConnectionPoolConfig config = new ConnectionPoolConfig();
    config.setMaxTotal(1);

    try (SentineledConnectionProvider sut = new SentineledConnectionProvider(MASTER_NAME,
            primary.getClientConfigBuilder().build(), config, sentinels,
            DefaultJedisClientConfig.builder().build())) {

      HostAndPort resolvedPrimary = sut.getCurrentMaster();
      ConnectionPool pool = ReflectionTestUtil.getField(sut,"pool");
      assertThat(pool.getNumActive(), equalTo(0));


      Map<?, ?> cm = sut.getPrimaryNodesConnectionMap();

      // exactly one entry for current primary
      // and no active connections
      assertThat(cm.size(), equalTo(1));
      assertThat(cm, hasKey(resolvedPrimary));
      assertThat(pool.getNumActive(), equalTo(0));
      // primary did not change
      assertThat(ReflectionTestUtil.getField(sut,"pool"), sameInstance(pool));
    }

  }

  @Test
  public void initializeWithNotAvailableSentinelsShouldThrowException() {
    Set<HostAndPort> wrongSentinels = new HashSet<>();
    wrongSentinels.add(new HostAndPort("localhost", 65432));
    wrongSentinels.add(new HostAndPort("localhost", 65431));
    assertThrows(JedisConnectionException.class, () -> {
      try (SentineledConnectionProvider provider = new SentineledConnectionProvider(MASTER_NAME,
          DefaultJedisClientConfig.builder().build(), wrongSentinels, DefaultJedisClientConfig.builder().build())) {
      }
    });
  }

  @Test
  public void initializeWithNotMonitoredMasterNameShouldThrowException() {
    final String wrongMasterName = "wrongMasterName";
    assertThrows(JedisException.class, () -> {
      try (SentineledConnectionProvider provider = new SentineledConnectionProvider(wrongMasterName,
          DefaultJedisClientConfig.builder().build(), sentinels, DefaultJedisClientConfig.builder().build())) {
      }
    });
  }

  @Test
  public void checkCloseableConnections() throws Exception {
    GenericObjectPoolConfig<Connection> config = new GenericObjectPoolConfig<>();

    try (JedisSentineled jedis = JedisSentineled
        .builder().masterName(MASTER_NAME).clientConfig(DefaultJedisClientConfig.builder()
            .timeoutMillis(1000).password("foobared").database(2).build())
        .poolConfig(config).sentinels(sentinels).build()) {
      assertSame(SentineledConnectionProvider.class, jedis.provider.getClass());
      jedis.set("foo", "bar");
      assertEquals("bar", jedis.get("foo"));
    }
  }

  @Test
  public void checkResourceIsCloseable() {
    GenericObjectPoolConfig<Connection> config = new GenericObjectPoolConfig<>();
    config.setMaxTotal(1);
    config.setBlockWhenExhausted(false);

    try (JedisSentineled jedis = JedisSentineled
        .builder().masterName(MASTER_NAME).clientConfig(DefaultJedisClientConfig.builder()
            .timeoutMillis(1000).password("foobared").database(2).build())
        .poolConfig(config).sentinels(sentinels).build()) {

      Connection conn = jedis.provider.getConnection();
      try {
        conn.ping();
      } finally {
        conn.close();
      }

      Connection conn2 = jedis.provider.getConnection();
      try {
        assertEquals(conn, conn2);
      } finally {
        conn2.close();
      }
    }
  }

  @Test
  public void testResetInvalidPassword() {
    DefaultRedisCredentialsProvider credentialsProvider
        = new DefaultRedisCredentialsProvider(new DefaultRedisCredentials(null, "foobared"));

    try (JedisSentineled jedis = JedisSentineled.builder().masterName(MASTER_NAME)
        .clientConfig(DefaultJedisClientConfig.builder().timeoutMillis(2000)
            .credentialsProvider(credentialsProvider).database(2).clientName("my_shiny_client_name")
            .build())
        .sentinels(sentinels).build()) {

      jedis.set("foo", "bar");

      Connection conn1_ref;
      try (Connection conn1_1 = jedis.provider.getConnection()) {
        conn1_ref = conn1_1;
        assertEquals("bar", new Jedis(conn1_1).get("foo"));
      }

      credentialsProvider.setCredentials(new DefaultRedisCredentials(null, "wrong password"));

      try (Connection conn1_2 = jedis.provider.getConnection()) {
        assertSame(conn1_ref, conn1_2);

        try (Connection conn2 = jedis.provider.getConnection()) {
          fail("Should not get resource from pool");
        } catch (JedisException e) { }
      }
    }
  }

  @Test
  public void testResetValidPassword() {
    DefaultRedisCredentialsProvider credentialsProvider
        = new DefaultRedisCredentialsProvider(new DefaultRedisCredentials(null, "wrong password"));

    try (JedisSentineled jedis = JedisSentineled.builder().masterName(MASTER_NAME)
        .clientConfig(DefaultJedisClientConfig.builder().timeoutMillis(2000)
            .credentialsProvider(credentialsProvider).database(2).clientName("my_shiny_client_name")
            .build())
        .sentinels(sentinels).build()) {

      try (Connection conn1 = jedis.provider.getConnection()) {
        fail("Should not get resource from pool");
      } catch (JedisException e) { }

      credentialsProvider.setCredentials(new DefaultRedisCredentials(null, "foobared"));

      try (Connection conn2 = jedis.provider.getConnection()) {
        new Jedis(conn2).set("foo", "bar");
        assertEquals("bar", jedis.get("foo"));
      }
    }
  }
}