MultiClusterPooledConnectionProviderTest.java

package redis.clients.jedis.providers;

import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.*;
import redis.clients.jedis.MultiClusterClientConfig.ClusterConfig;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisValidationException;

import java.util.concurrent.atomic.AtomicBoolean;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @see MultiClusterPooledConnectionProvider
 */
public class MultiClusterPooledConnectionProviderTest {

    private final EndpointConfig endpointStandalone0 = HostAndPorts.getRedisEndpoint("standalone0");
    private final EndpointConfig endpointStandalone1 = HostAndPorts.getRedisEndpoint("standalone1");

    private MultiClusterPooledConnectionProvider provider;

    @BeforeEach
    public void setUp() {

        ClusterConfig[] clusterConfigs = new ClusterConfig[2];
        clusterConfigs[0] = new ClusterConfig(endpointStandalone0.getHostAndPort(), endpointStandalone0.getClientConfigBuilder().build());
        clusterConfigs[1] = new ClusterConfig(endpointStandalone1.getHostAndPort(), endpointStandalone0.getClientConfigBuilder().build());

        provider = new MultiClusterPooledConnectionProvider(new MultiClusterClientConfig.Builder(clusterConfigs).build());
    }

    @Test
    public void testCircuitBreakerForcedTransitions() {

        CircuitBreaker circuitBreaker = provider.getClusterCircuitBreaker(1);
        circuitBreaker.getState();

        if (CircuitBreaker.State.FORCED_OPEN.equals(circuitBreaker.getState()))
            circuitBreaker.transitionToClosedState();

        circuitBreaker.transitionToForcedOpenState();
        assertEquals(CircuitBreaker.State.FORCED_OPEN, circuitBreaker.getState());

        circuitBreaker.transitionToClosedState();
        assertEquals(CircuitBreaker.State.CLOSED, circuitBreaker.getState());
    }

    @Test
    public void testIncrementActiveMultiClusterIndex() {
        int index = provider.incrementActiveMultiClusterIndex();
        assertEquals(2, index);
    }

    @Test
    public void testIncrementActiveMultiClusterIndexOutOfRange() {
        provider.setActiveMultiClusterIndex(1);

        int index = provider.incrementActiveMultiClusterIndex();
        assertEquals(2, index);

        assertThrows(JedisConnectionException.class,
            () -> provider.incrementActiveMultiClusterIndex()); // Should throw an exception
    }

    @Test
    public void testIsLastClusterCircuitBreakerForcedOpen() {
        provider.setActiveMultiClusterIndex(1);

        try {
            provider.incrementActiveMultiClusterIndex();
        } catch (Exception e) {}

        // This should set the isLastClusterCircuitBreakerForcedOpen to true
        try {
            provider.incrementActiveMultiClusterIndex();
        } catch (Exception e) {}

      assertTrue(provider.isLastClusterCircuitBreakerForcedOpen());
    }

    @Test
    public void testRunClusterFailoverPostProcessor() {
        ClusterConfig[] clusterConfigs = new ClusterConfig[2];
        clusterConfigs[0] = new ClusterConfig(new HostAndPort("purposefully-incorrect", 0000),
                                                                    DefaultJedisClientConfig.builder().build());
        clusterConfigs[1] = new ClusterConfig(new HostAndPort("purposefully-incorrect", 0001),
                                                                    DefaultJedisClientConfig.builder().build());

        MultiClusterClientConfig.Builder builder = new MultiClusterClientConfig.Builder(clusterConfigs);

        // Configures a single failed command to trigger an open circuit on the next subsequent failure
        builder.circuitBreakerSlidingWindowSize(1);
        builder.circuitBreakerSlidingWindowMinCalls(1);

        AtomicBoolean isValidTest = new AtomicBoolean(false);

        MultiClusterPooledConnectionProvider localProvider = new MultiClusterPooledConnectionProvider(builder.build());
        localProvider.setClusterFailoverPostProcessor(a -> { isValidTest.set(true); });

        try (UnifiedJedis jedis = new UnifiedJedis(localProvider)) {

            // This should fail after 3 retries and meet the requirements to open the circuit on the next iteration
            try {
                jedis.get("foo");
            } catch (Exception e) {}

            // This should fail after 3 retries and open the circuit which will trigger the post processor
            try {
                jedis.get("foo");
            } catch (Exception e) {}

        }

      assertTrue(isValidTest.get());
    }

    @Test
    public void testSetActiveMultiClusterIndexEqualsZero() {
        assertThrows(JedisValidationException.class,
            () -> provider.setActiveMultiClusterIndex(0)); // Should throw an exception
    }

    @Test
    public void testSetActiveMultiClusterIndexLessThanZero() {
        assertThrows(JedisValidationException.class,
            () -> provider.setActiveMultiClusterIndex(-1)); // Should throw an exception
    }

    @Test
    public void testSetActiveMultiClusterIndexOutOfRange() {
        assertThrows(JedisValidationException.class,
            () -> provider.setActiveMultiClusterIndex(3)); // Should throw an exception
    }

    @Test
    public void testConnectionPoolConfigApplied() {
        ConnectionPoolConfig poolConfig = new ConnectionPoolConfig();
        poolConfig.setMaxTotal(8);
        poolConfig.setMaxIdle(4);
        poolConfig.setMinIdle(1);
        ClusterConfig[] clusterConfigs = new ClusterConfig[2];
        clusterConfigs[0] = new ClusterConfig(endpointStandalone0.getHostAndPort(), endpointStandalone0.getClientConfigBuilder().build(), poolConfig);
        clusterConfigs[1] = new ClusterConfig(endpointStandalone1.getHostAndPort(), endpointStandalone0.getClientConfigBuilder().build(), poolConfig);
        try (MultiClusterPooledConnectionProvider customProvider = new MultiClusterPooledConnectionProvider(
                new MultiClusterClientConfig.Builder(clusterConfigs).build())) {
            MultiClusterPooledConnectionProvider.Cluster activeCluster = customProvider.getCluster();
            ConnectionPool connectionPool = activeCluster.getConnectionPool();
            assertEquals(8, connectionPool.getMaxTotal());
            assertEquals(4, connectionPool.getMaxIdle());
            assertEquals(1, connectionPool.getMinIdle());
        }
    }
}