blob: 264db89464e2cd6d76a1361adb23f7e8b9bfa071 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.ratis.retry;
import org.apache.ratis.BaseTest;
import org.apache.ratis.server.impl.MiniRaftCluster;
import org.apache.ratis.RaftTestUtil;
import org.apache.ratis.client.retry.ClientRetryEvent;
import org.apache.ratis.client.RaftClient;
import org.apache.ratis.client.RaftClientConfigKeys;
import org.apache.ratis.conf.RaftProperties;
import org.apache.ratis.grpc.MiniRaftClusterWithGrpc;
import org.apache.ratis.protocol.exceptions.RaftRetryFailureException;
import org.apache.ratis.protocol.exceptions.TimeoutIOException;
import org.apache.ratis.server.RaftServer;
import org.apache.ratis.server.RaftServerConfigKeys;
import org.apache.ratis.statemachine.impl.SimpleStateMachine4Testing;
import org.apache.ratis.statemachine.StateMachine;
import org.apache.ratis.util.TimeDuration;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static org.junit.Assert.fail;
/**
* Class to test {@link ExceptionDependentRetry}.
*/
public class TestExceptionDependentRetry extends BaseTest implements MiniRaftClusterWithGrpc.FactoryGet {
@Test
public void testExceptionDependentRetrySuccess() {
ExceptionDependentRetry.Builder builder =
ExceptionDependentRetry.newBuilder();
int ioExceptionRetries = 1;
int timeoutExceptionRetries = 2;
int defaultExceptionRetries = 5;
long ioExceptionSleepTime = 1;
long timeoutExceptionSleepTime = 4;
long defaultExceptionSleepTime = 10;
int maxAttempts = 3;
builder.setDefaultPolicy(RetryPolicies.retryUpToMaximumCountWithFixedSleep(defaultExceptionRetries,
TimeDuration.valueOf(defaultExceptionSleepTime, TimeUnit.SECONDS)));
builder.setExceptionToPolicy(IOException.class,
RetryPolicies.retryUpToMaximumCountWithFixedSleep(ioExceptionRetries,
TimeDuration.valueOf(ioExceptionSleepTime, TimeUnit.SECONDS)));
builder.setExceptionToPolicy(TimeoutIOException.class,
RetryPolicies.retryUpToMaximumCountWithFixedSleep(timeoutExceptionRetries,
TimeDuration.valueOf(timeoutExceptionSleepTime, TimeUnit.SECONDS)));
builder.setMaxAttempts(maxAttempts);
ExceptionDependentRetry exceptionDependentRetry = builder.build();
testException(ioExceptionRetries, maxAttempts,
exceptionDependentRetry, new IOException(), ioExceptionSleepTime);
testException(timeoutExceptionRetries, maxAttempts,
exceptionDependentRetry, new TimeoutIOException("time out"),
timeoutExceptionSleepTime);
// now try with an exception which is not there in the map.
testException(defaultExceptionRetries, maxAttempts,
exceptionDependentRetry, new TimeoutException(),
defaultExceptionSleepTime);
}
@Test
public void testExceptionDependentRetryFailureWithExceptionDuplicate() {
try {
ExceptionDependentRetry.Builder builder =
ExceptionDependentRetry.newBuilder();
builder.setExceptionToPolicy(IOException.class,
RetryPolicies.retryUpToMaximumCountWithFixedSleep(1,
TimeDuration.valueOf(1, TimeUnit.SECONDS)));
builder.setExceptionToPolicy(IOException.class,
RetryPolicies.retryUpToMaximumCountWithFixedSleep(1,
TimeDuration.valueOf(1, TimeUnit.SECONDS)));
fail("testExceptionDependentRetryFailure failed");
} catch (Exception ex) {
Assert.assertEquals(IllegalStateException.class, ex.getClass());
}
}
@Test
public void testExceptionDependentRetryFailureWithExceptionMappedToNull() {
try {
ExceptionDependentRetry.Builder builder =
ExceptionDependentRetry.newBuilder();
builder.setExceptionToPolicy(IOException.class,
RetryPolicies.retryUpToMaximumCountWithFixedSleep(1,
TimeDuration.valueOf(1, TimeUnit.SECONDS)));
builder.setExceptionToPolicy(IOException.class, null);
fail("testExceptionDependentRetryFailure failed");
} catch (Exception ex) {
Assert.assertEquals(IllegalStateException.class, ex.getClass());
}
}
@Test
public void testExceptionDependentRetryFailureWithNoDefault() {
try {
ExceptionDependentRetry.Builder builder =
ExceptionDependentRetry.newBuilder();
builder.setExceptionToPolicy(IOException.class,
RetryPolicies.retryUpToMaximumCountWithFixedSleep(1,
TimeDuration.valueOf(1, TimeUnit.SECONDS)));
builder.build();
fail("testExceptionDependentRetryFailureWithNoDefault failed");
} catch (Exception ex) {
Assert.assertEquals(IllegalStateException.class, ex.getClass());
}
try {
ExceptionDependentRetry.Builder builder =
ExceptionDependentRetry.newBuilder();
builder.setExceptionToPolicy(IOException.class,
RetryPolicies.retryUpToMaximumCountWithFixedSleep(1,
TimeDuration.valueOf(1, TimeUnit.SECONDS)));
builder.setDefaultPolicy(null);
fail("testExceptionDependentRetryFailureWithNoDefault failed");
} catch (Exception ex) {
Assert.assertEquals(IllegalStateException.class, ex.getClass());
}
}
private void testException(int retries, int maxAttempts,
ExceptionDependentRetry exceptionDependentRetry, Exception exception,
long sleepTime) {
for (int i = 0; i < retries + 1; i++) {
RetryPolicy.Action action = exceptionDependentRetry
.handleAttemptFailure(new ClientRetryEvent(i, null, exception));
final boolean expected = i < retries && i < maxAttempts;
Assert.assertEquals(expected, action.shouldRetry());
if (expected) {
Assert.assertEquals(sleepTime, action.getSleepTime().getDuration());
} else {
Assert.assertEquals(0L, action.getSleepTime().getDuration());
}
}
}
@Test
public void testExceptionRetryAttempts() throws Exception {
final RaftProperties prop = getProperties();
RaftClientConfigKeys.Rpc.setRequestTimeout(prop, TimeDuration.valueOf(100, TimeUnit.MILLISECONDS));
prop.setClass(MiniRaftCluster.STATEMACHINE_CLASS_KEY,
SimpleStateMachine4Testing.class, StateMachine.class);
RaftServerConfigKeys.Write.setElementLimit(prop, 1);
runWithNewCluster(1, this::runTestExceptionRetryAttempts);
}
void runTestExceptionRetryAttempts(MiniRaftClusterWithGrpc cluster) throws Exception {
final int retryCount = 5;
final RetryPolicy timeoutPolicy = MultipleLinearRandomRetry.parseCommaSeparated("1ms, " + retryCount);
final ExceptionDependentRetry policy = ExceptionDependentRetry.newBuilder()
.setExceptionToPolicy(TimeoutIOException.class, timeoutPolicy)
.setDefaultPolicy(RetryPolicies.retryForeverNoSleep())
.build();
final RaftServer.Division leader = RaftTestUtil.waitForLeader(cluster);
// create a client with the exception dependent policy
try (final RaftClient client = cluster.createClient(policy)) {
client.async().send(new RaftTestUtil.SimpleMessage("1")).get();
}
try (final RaftClient client = cluster.createClient(policy)) {
SimpleStateMachine4Testing.get(leader).blockWriteStateMachineData();
client.async().send(new RaftTestUtil.SimpleMessage("2")).get();
Assert.fail("Test should have failed.");
} catch (ExecutionException e) {
RaftRetryFailureException rrfe = (RaftRetryFailureException) e.getCause();
final int expectedCount = 1 + retryCount; // new request attempt + retry attempts
Assert.assertEquals(expectedCount, rrfe.getAttemptCount());
} finally {
SimpleStateMachine4Testing.get(leader).unblockWriteStateMachineData();
cluster.shutdown();
}
}
}