+ * once the proxy is re-enabled: + * - the subsequent requests should be successful + */ + @ParameterizedTest + @MethodSource("arangoProvider") + void unreachableHost(ArangoDB arangoDB) throws IOException, InterruptedException { + arangoDB.getVersion(); + + // close the driver connection + getEndpoint().getProxy().disable(); + Thread.sleep(100); + + for (int i = 0; i < 10; i++) { + Throwable thrown = catchThrowable(arangoDB::getVersion); + assertThat(thrown).isInstanceOf(ArangoDBException.class); + assertThat(thrown.getMessage()).contains("Cannot contact any host"); + assertThat(thrown.getCause()).isNotNull(); + assertThat(thrown.getCause()).isInstanceOf(ArangoDBMultipleException.class); + ((ArangoDBMultipleException) thrown.getCause()).getExceptions().forEach(e -> { + assertThat(e).isInstanceOf(ConnectException.class); + }); + } + + long warnsCount = logs.getLoggedEvents().stream() + .filter(e -> e.getLevel().equals(Level.WARN)) + .filter(e -> e.getMessage().contains("Could not connect to host[addr=127.0.0.1,port=18529]")) + .count(); + assertThat(warnsCount).isGreaterThanOrEqualTo(3); + + getEndpoint().getProxy().enable(); + Thread.sleep(100); + + arangoDB.getVersion(); + arangoDB.shutdown(); + } + + /** + * on reconnection failure: + * - 3x logs WARN Could not connect to host[addr=127.0.0.1,port=8529] + * - ArangoDBException("Cannot contact any host") + *
+ * once the proxy is re-enabled: + * - the subsequent requests should be successful + */ + @ParameterizedTest + @EnumSource(Protocol.class) + void connectionTimeout(Protocol protocol) throws IOException, InterruptedException { + // https://github.com/vert-x3/vertx-web/issues/2296 + // WebClient: HTTP/2 request timeout does not throw TimeoutException + assumeTrue(protocol != Protocol.HTTP2_VPACK); + assumeTrue(protocol != Protocol.HTTP2_JSON); + + ArangoDB arangoDB = dbBuilder() + .timeout(1_000) + .useProtocol(protocol) + .build(); + + arangoDB.getVersion(); + + // slow down the driver connection + Latency toxic = getEndpoint().getProxy().toxics().latency("latency", ToxicDirection.DOWNSTREAM, 10_000); + Thread.sleep(100); + + Throwable thrown = catchThrowable(arangoDB::getVersion); + thrown.printStackTrace(); + assertThat(thrown) + .isInstanceOf(ArangoDBException.class) + .extracting(Throwable::getCause) + .isInstanceOf(TimeoutException.class); + + toxic.remove(); + Thread.sleep(100); + + arangoDB.getVersion(); + arangoDB.shutdown(); + } + +} diff --git a/resilience-tests/src/test/java/resilience/timeout/TimeoutTest.java b/resilience-tests/src/test/java/resilience/timeout/TimeoutTest.java new file mode 100644 index 000000000..079c2e3fd --- /dev/null +++ b/resilience-tests/src/test/java/resilience/timeout/TimeoutTest.java @@ -0,0 +1,73 @@ +package resilience.timeout; + +import com.arangodb.ArangoCollection; +import com.arangodb.ArangoDB; +import com.arangodb.ArangoDBException; +import com.arangodb.Protocol; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import resilience.SingleServerTest; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.TimeoutException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +/** + * @author Michele Rastelli + */ +class TimeoutTest extends SingleServerTest { + + /** + * on timeout failure: + * - throw exception + * - expect operation performed (at most) once + *
+ * after the exception:
+ * - the subsequent requests should be successful
+ */
+ @ParameterizedTest
+ @EnumSource(Protocol.class)
+ void requestTimeout(Protocol protocol) throws InterruptedException {
+ // https://github.com/vert-x3/vertx-web/issues/2296
+ // WebClient: HTTP/2 request timeout does not throw TimeoutException
+ assumeTrue(protocol != Protocol.HTTP2_VPACK);
+ assumeTrue(protocol != Protocol.HTTP2_JSON);
+
+ ArangoDB arangoDB = dbBuilder()
+ .timeout(1_000)
+ .useProtocol(protocol)
+ .build();
+
+ arangoDB.getVersion();
+ String colName = "timeoutTest";
+ ArangoCollection col = arangoDB.db().collection(colName);
+ if (!col.exists()) col.create();
+ col.truncate();
+
+ try {
+ arangoDB.db().query("" +
+ "INSERT {value:sleep(2)}\n" +
+ "INTO @@col\n" +
+ "RETURN NEW\n",
+ Collections.singletonMap("@col", colName),
+ Map.class);
+ } catch (Exception e) {
+ e.printStackTrace();
+ assertThat(e)
+ .isInstanceOf(ArangoDBException.class)
+ .extracting(Throwable::getCause)
+ .isInstanceOf(TimeoutException.class);
+ }
+
+ arangoDB.getVersion();
+
+ Thread.sleep(2_000);
+ assertThat(col.count().getCount()).isEqualTo(1);
+
+ arangoDB.shutdown();
+ }
+
+}
diff --git a/resilience-tests/src/test/java/resilience/utils/MemoryAppender.java b/resilience-tests/src/test/java/resilience/utils/MemoryAppender.java
new file mode 100644
index 000000000..018dd5415
--- /dev/null
+++ b/resilience-tests/src/test/java/resilience/utils/MemoryAppender.java
@@ -0,0 +1,30 @@
+package resilience.utils;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.read.ListAppender;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.List;
+
+public class MemoryAppender extends ListAppender